Файловый менеджер - Редактировать - /home/clickysoft/public_html/jmapi5.clickysoft.net/league.tar
Назад
commonmark/composer.json 0000644 00000010021 15021222733 0011420 0 ustar 00 { "name": "league/commonmark", "type": "library", "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", "keywords": ["markdown","parser","commonmark","gfm","github","flavored","github-flavored","md"], "homepage": "https://commonmark.thephpleague.com", "license": "BSD-3-Clause", "authors": [ { "name": "Colin O'Dell", "email": "colinodell@gmail.com", "homepage": "https://www.colinodell.com", "role": "Lead Developer" } ], "support": { "docs": "https://commonmark.thephpleague.com/", "forum": "https://github.com/thephpleague/commonmark/discussions", "issues": "https://github.com/thephpleague/commonmark/issues", "rss": "https://github.com/thephpleague/commonmark/releases.atom", "source": "https://github.com/thephpleague/commonmark" }, "require": { "php": "^7.4 || ^8.0", "ext-mbstring": "*", "league/config": "^1.1.1", "psr/event-dispatcher": "^1.0", "symfony/deprecation-contracts": "^2.1 || ^3.0", "symfony/polyfill-php80": "^1.16" }, "require-dev": { "ext-json": "*", "cebe/markdown": "^1.0", "commonmark/cmark": "0.31.1", "commonmark/commonmark.js": "0.31.1", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", "erusev/parsedown": "^1.0", "github/gfm": "0.29.0", "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", "symfony/finder": "^5.3 | ^6.0 || ^7.0", "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, "minimum-stability": "beta", "suggest": { "symfony/yaml": "v2.3+ required if using the Front Matter extension" }, "repositories": [ { "type": "package", "package": { "name": "commonmark/commonmark.js", "version": "0.31.1", "dist": { "url": "https://github.com/commonmark/commonmark.js/archive/0.31.1.zip", "type": "zip" } } }, { "type": "package", "package": { "name": "commonmark/cmark", "version": "0.31.1", "dist": { "url": "https://github.com/commonmark/cmark/archive/0.31.1.zip", "type": "zip" } } }, { "type": "package", "package": { "name": "github/gfm", "version": "0.29.0", "dist": { "url": "https://github.com/github/cmark-gfm/archive/0.29.0.gfm.13.zip", "type": "zip" } } } ], "autoload": { "psr-4": { "League\\CommonMark\\": "src" } }, "autoload-dev": { "psr-4": { "League\\CommonMark\\Tests\\Unit\\": "tests/unit", "League\\CommonMark\\Tests\\Functional\\": "tests/functional", "League\\CommonMark\\Tests\\PHPStan\\": "tests/phpstan" } }, "scripts": { "phpcs": "phpcs", "phpstan": "phpstan analyse", "phpunit": "phpunit --no-coverage", "psalm": "psalm --stats", "test": [ "@phpcs", "@phpstan", "@psalm", "@phpunit" ] }, "extra": { "branch-alias": { "dev-main": "2.6-dev" } }, "config": { "allow-plugins": { "composer/package-versions-deprecated": true, "dealerdirect/phpcodesniffer-composer-installer": true }, "sort-packages": true } } commonmark/.phpstorm.meta.php 0000644 00000013755 15021222733 0012307 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace PHPSTORM_META { expectedArguments(\League\CommonMark\Util\HtmlElement::__construct(), 0, 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kdb', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr'); expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\Heading::__construct(), 0, 1, 2, 3, 4, 5, 6); expectedReturnValues(\League\CommonMark\Extension\CommonMark\Node\Block\Heading::getLevel(), 1, 2, 3, 4, 5, 6); registerArgumentsSet('league_commonmark_htmlblock_types', \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_1_CODE_CONTAINER, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_2_COMMENT, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_3, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_4, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_5_CDATA, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_6_BLOCK_ELEMENT, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_7_MISC_ELEMENT); expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::__construct(), 0, argumentsSet('league_commonmark_htmlblock_types')); expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::setType(), 0, argumentsSet('league_commonmark_htmlblock_types')); expectedReturnValues(\League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::getType(), argumentsSet('league_commonmark_htmlblock_types')); expectedArguments(\League\CommonMark\Util\RegexHelper::getHtmlBlockOpenRegex(), 0, argumentsSet('league_commonmark_htmlblock_types')); expectedArguments(\League\CommonMark\Util\RegexHelper::getHtmlBlockCloseRegex(), 0, argumentsSet('league_commonmark_htmlblock_types')); registerArgumentsSet('league_commonmark_newline_types', \League\CommonMark\Node\Inline\Newline::HARDBREAK, \League\CommonMark\Node\Inline\Newline::SOFTBREAK); expectedArguments(\League\CommonMark\Node\Inline\Newline::__construct(), 0, argumentsSet('league_commonmark_newline_types')); expectedReturnValues(\League\CommonMark\Node\Inline\Newline::getType(), argumentsSet('league_commonmark_newline_types')); registerArgumentsSet('league_commonmark_options', 'html_input', 'allow_unsafe_links', 'max_nesting_level', 'renderer', 'renderer/block_separator', 'renderer/inner_separator', 'renderer/soft_break', 'commonmark', 'commonmark/enable_em', 'commonmark/enable_strong', 'commonmark/use_asterisk', 'commonmark/use_underscore', 'commonmark/unordered_list_markers', 'disallowed_raw_html', 'disallowed_raw_html/disallowed_tags', 'external_link', 'external_link/html_class', 'external_link/internal_hosts', 'external_link/nofollow', 'external_link/noopener', 'external_link/noreferrer', 'external_link/open_in_new_window', 'footnote', 'footnote/backref_class', 'footnote/backref_symbol', 'footnote/container_add_hr', 'footnote/container_class', 'footnote/ref_class', 'footnote/ref_id_prefix', 'footnote/footnote_class', 'footnote/footnote_id_prefix', 'heading_permalink', 'heading_permalink/apply_id_to_heading', 'heading_permalink/heading_class', 'heading_permalink/html_class', 'heading_permalink/fragment_prefix', 'heading_permalink/id_prefix', 'heading_permalink/inner_contents', 'heading_permalink/insert', 'heading_permalink/max_heading_level', 'heading_permalink/min_heading_level', 'heading_permalink/symbol', 'heading_permalink/title', 'mentions', 'smartpunct/double_quote_closer', 'smartpunct/double_quote_opener', 'smartpunct/single_quote_closer', 'smartpunct/single_quote_opener', 'slug_normalizer', 'slug_normalizer/instance', 'slug_normalizer/max_length', 'slug_normalizer/unique', 'table', 'table/wrap', 'table/wrap/attributes', 'table/wrap/enabled', 'table/wrap/tag', 'table/alignment_attributes', 'table/alignment_attributes/left', 'table/alignment_attributes/center', 'table/alignment_attributes/right', 'table_of_contents', 'table_of_contents/html_class', 'table_of_contents/max_heading_level', 'table_of_contents/min_heading_level', 'table_of_contents/normalize', 'table_of_contents/placeholder', 'table_of_contents/position', 'table_of_contents/style', ); expectedArguments(\League\Config\ConfigurationInterface::get(), 0, argumentsSet('league_commonmark_options')); expectedArguments(\League\Config\ConfigurationInterface::exists(), 0, argumentsSet('league_commonmark_options')); expectedArguments(\League\Config\MutableConfigurationInterface::set(), 0, argumentsSet('league_commonmark_options')); } commonmark/CHANGELOG.md 0000644 00000072435 15021222733 0010530 0 ustar 00 # Change Log All notable changes to this project will be documented in this file. Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles. **Upgrading from 1.x?** See <https://commonmark.thephpleague.com/2.0/upgrading/> for additional information. ## [Unreleased][unreleased] ## [2.5.3] - 2024-08-16 ### Changed - Made compatible with CommonMark spec 0.31.1, including: - Remove `source`, add `search` to list of recognized block tags ## [2.5.2] - 2024-08-14 ### Changed - Boolean attributes now require an explicit `true` value (#1040) ### Fixed - Fixed regression where text could be misinterpreted as an attribute (#1040) ## [2.5.1] - 2024-07-24 ### Fixed - Fixed attribute parsing incorrectly parsing mustache-like syntax (#1035) - Fixed incorrect `Table` start line numbers (#1037) ## [2.5.0] - 2024-07-22 ### Added - The `AttributesExtension` now supports attributes without values (#985, #986) - The `AutolinkExtension` exposes two new configuration options to override the default behavior (#969, #987): - `autolink/allowed_protocols` - an array of protocols to allow autolinking for - `autolink/default_protocol` - the default protocol to use when none is specified ### Changed - Made compatible with CommonMark spec 0.31.0, including: - Allow closing fence to be followed by tabs - Remove restrictive limitation on inline comments - Unicode symbols now treated like punctuation (for purposes of flankingness) - Trailing tabs on the last line of indented code blocks will be excluded - Improved HTML comment matching - `Paragraph`s only containing link reference definitions will be kept in the AST until the `Document` is finalized - (These were previously removed immediately after parsing the `Paragraph`) ### Fixed - Fixed list tightness not being determined properly in some edge cases - Fixed incorrect ending line numbers for several block types in various scenarios - Fixed lowercase inline HTML declarations not being accepted ## [2.4.4] - 2024-07-22 ### Fixed - Fixed SmartPunct extension changing already-formatted quotation marks (#1030) ## [2.4.3] - 2024-07-22 ### Fixed - Fixed the Attributes extension not supporting CSS level 3 selectors (#1013) - Fixed `UrlAutolinkParser` incorrectly parsing text containing `www` anywhere before an autolink (#1025) ## [2.4.2] - 2024-02-02 ### Fixed - Fixed declaration parser being too strict - `FencedCodeRenderer`: don't add `language-` to class if already prefixed ## [2.4.1] - 2023-08-30 ### Fixed - Fixed `ExternalLinkProcessor` not fully disabling the `rel` attribute when configured to do so (#992) ## [2.4.0] - 2023-03-24 ### Added - Added generic `CommonMarkException` marker interface for all exceptions thrown by the library - Added several new specific exception types implementing that marker interface: - `AlreadyInitializedException` - `InvalidArgumentException` - `IOException` - `LogicException` - `MissingDependencyException` - `NoMatchingRendererException` - `ParserLogicException` - Added more configuration options to the Heading Permalinks extension (#939): - `heading_permalink/apply_id_to_heading` - When `true`, the `id` attribute will be applied to the heading element itself instead of the `<a>` tag - `heading_permalink/heading_class` - class to apply to the heading element - `heading_permalink/insert` - now accepts `none` to prevent the creation of the `<a>` link - Added new `table/alignment_attributes` configuration option to control how table cell alignment is rendered (#959) ### Changed - Change several thrown exceptions from `RuntimeException` to `LogicException` (or something extending it), including: - `CallbackGenerator`s that fail to set a URL or return an expected value - `MarkdownParser` when deactivating the last block parser or attempting to get an active block parser when they've all been closed - Adding items to an already-initialized `Environment` - Rendering a `Node` when no renderer has been registered for it - `HeadingPermalinkProcessor` now throws `InvalidConfigurationException` instead of `RuntimeException` when invalid config values are given. - `HtmlElement::setAttribute()` no longer requires the second parameter for boolean attributes - Several small micro-optimizations - Changed Strikethrough to only allow 1 or 2 tildes per the updated GFM spec ### Fixed - Fixed inaccurate `@throws` docblocks throughout the codebase, including `ConverterInterface`, `MarkdownConverter`, and `MarkdownConverterInterface`. - These previously suggested that only `\RuntimeException`s were thrown, which was inaccurate as `\LogicException`s were also possible. ## [2.3.9] - 2023-02-15 ### Fixed - Fixed autolink extension not detecting some URIs with underscores (#956) ## [2.3.8] - 2022-12-10 ### Fixed - Fixed parsing issues when `mb_internal_encoding()` is set to something other than `UTF-8` (#951) ## [2.3.7] - 2022-11-03 ### Fixed - Fixed `TaskListItemMarkerRenderer` not including HTML attributes set on the node by other extensions (#947) ## [2.3.6] - 2022-10-30 ### Fixed - Fixed unquoted attribute parsing when closing curly brace is followed by certain characters (like a `.`) (#943) ## [2.3.5] - 2022-07-29 ### Fixed - Fixed error using `InlineParserEngine` when no inline parsers are registered in the `Environment` (#908) ## [2.3.4] - 2022-07-17 ### Changed - Made a number of small tweaks to the embed extension's parsing behavior to fix #898: - Changed `EmbedStartParser` to always capture embed-like lines in container blocks, regardless of parent block type - Changed `EmbedProcessor` to also remove `Embed` blocks that aren't direct children of the `Document` - Increased the priority of `EmbedProcessor` to `1010` ### Fixed - Fixed `EmbedExtension` not parsing embeds following a list block (#898) ## [2.3.3] - 2022-06-07 ### Fixed - Fixed `DomainFilteringAdapter` not reindexing the embed list (#884, #885) ## [2.3.2] - 2022-06-03 ### Fixed - Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881) ## [2.2.5] - 2022-06-03 ### Fixed - Fixed FootnoteExtension stripping extra characters from tab-indented footnotes (#881) ## [2.3.1] - 2022-05-14 ### Fixed - Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867) ## [2.2.4] - 2022-05-14 ### Fixed - Fixed AutolinkExtension not ignoring trailing strikethrough syntax (#867) ## [2.3.0] - 2022-04-07 ### Added - Added new `EmbedExtension` (#805) - Added `DocumentRendererInterface` as a replacement for the now-deprecated `MarkdownRendererInterface` ### Deprecated - Deprecated `MarkdownRendererInterface`; use `DocumentRendererInterface` instead ## [2.2.3] - 2022-02-26 ### Fixed - Fixed front matter parsing with Windows line endings (#821) ## [2.1.3] - 2022-02-26 ### Fixed - Fixed front matter parsing with Windows line endings (#821) ## [2.0.4] - 2022-02-26 ### Fixed - Fixed front matter parsing with Windows line endings (#821) ## [2.2.2] - 2022-02-13 ### Fixed - Fixed double-escaping of image alt text (#806, #810) - Fixed Psalm typehints for event class names ## [2.2.1] - 2022-01-25 ### Fixed - Fixed `symfony/deprecation-contracts` constraint ### Removed - Removed deprecation trigger from `MarkdownConverterInterface` to reduce noise ## [2.2.0] - 2022-01-22 ### Added - Added new `ConverterInterface` - Added new `MarkdownToXmlConverter` class - Added new `HtmlDecorator` class which can wrap existing renderers with additional HTML tags - Added new `table/wrap` config to apply an optional wrapping/container element around a table (#780) ### Changed - `HtmlElement` contents can now consist of any `Stringable`, not just `HtmlElement` and `string` ### Deprecated - Deprecated `MarkdownConverterInterface` and its `convertToHtml()` method; use `ConverterInterface` and `convert()` instead ## [2.1.2] - 2022-02-13 ### Fixed - Fixed double-escaping of image alt text (#806, #810) - Fixed Psalm typehints for event class names ## [2.1.1] - 2022-01-02 ### Added - Added missing return type to `Environment::dispatch()` to fix deprecation warning (#778) ## [2.1.0] - 2021-12-05 ### Added - Added support for ext-yaml in FrontMatterExtension (#715) - Added support for symfony/yaml v6.0 in FrontMatterExtension (#739) - Added new `heading_permalink/aria_hidden` config option (#741) ### Fixed - Fixed PHP 8.1 deprecation warning (#759, #762) ## [2.0.3] - 2022-02-13 ### Fixed - Fixed double-escaping of image alt text (#806, #810) - Fixed Psalm typehints for event class names ## [2.0.2] - 2021-08-14 ### Changed - Bumped minimum version of league/config to support PHP 8.1 ### Fixed - Fixed ability to register block parsers that identify lines starting with letters (#706) ## [2.0.1] - 2021-07-31 ### Fixed - Fixed nested autolinks (#689) - Fixed description lists being parsed incorrectly (#692) - Fixed Table of Contents not respecting Heading Permalink prefixes (#690) ## [2.0.0] - 2021-07-24 No changes were introduced since the previous RC2 release. See all entries below for a list of changes between 1.x and 2.0. ## [2.0.0-rc2] - 2021-07-17 ### Fixed - Fixed Mentions inside of links creating nested links against the spec's rules (#688) ## [2.0.0-rc1] - 2021-07-10 No changes were introduced since the previous release. ## [2.0.0-beta3] - 2021-07-03 ### Changed - Any leading UTF-8 BOM will be stripped from the input - The `getEnvironment()` method of `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` will always return the concrete, configurable `Environment` for upgrading convenience - Optimized AST iteration - Lots of small micro-optimizations ## [2.0.0-beta2] - 2021-06-27 ### Added - Added new `Node::iterator()` method and `NodeIterator` class for faster AST iteration (#683, #684) ### Changed - Made compatible with CommonMark spec 0.30.0 - Optimized link label parsing - Optimized AST iteration for a 50% performance boost in some event listeners (#683, #684) ### Fixed - Fixed processing instructions with EOLs - Fixed case-insensitive matching for HTML tag types - Fixed type 7 HTML blocks incorrectly interrupting lazy paragraphs - Fixed newlines in reference labels not collapsing into spaces - Fixed link label normalization with escaped newlines - Fixed unnecessary AST iteration when no default attributes are configured ## [2.0.0-beta1] - 2021-06-20 ### Added - **Added three new extensions:** - `FrontMatterExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/front-matter/)) - `DescriptionListExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/description-lists/)) - `DefaultAttributesExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/default-attributes/)) - **Added new `XmlRenderer` to simplify AST debugging** ([see documentation](https://commonmark.thephpleague.com/xml/)) (#431) - **Added the ability to configure disallowed raw HTML tags** (#507) - **Added the ability for Mentions to use multiple characters for their symbol** (#514, #550) - **Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries** - **Added new configuration options:** - Added `heading_permalink/min_heading_level` and `heading_permalink/max_heading_level` options to control which headings get permalinks (#519) - Added `heading_permalink/fragment_prefix` to allow customizing the URL fragment prefix (#602) - Added `footnote/backref_symbol` option for customizing backreference link appearance (#522) - Added `slug_normalizer/max_length` option to control the maximum length of generated URL slugs - Added `slug_normalizer/unique` option to control whether unique slugs should be generated per-document or per-environment - **Added purity markers throughout the codebase** (verified with Psalm) - Added `Query` class to simplify Node traversal when looking to take action on certain Nodes - Added new `HtmlFilter` and `StringContainerHelper` utility classes - Added new `AbstractBlockContinueParser` class to simplify the creation of custom block parsers - Added several new classes and interfaces: - `BlockContinue` - `BlockContinueParserInterface` - `BlockContinueParserWithInlinesInterface` - `BlockStart` - `BlockStartParserInterface` - `ChildNodeRendererInterface` - `ConfigurableExtensionInterface` - `CursorState` - `DashParser` (extracted from `PunctuationParser`) - `DelimiterParser` - `DocumentBlockParser` - `DocumentPreRenderEvent` - `DocumentRenderedEvent` - `EllipsesParser` (extracted from `PunctuationParser`) - `ExpressionInterface` - `FallbackNodeXmlRenderer` - `InlineParserEngineInterface` - `InlineParserMatch` - `MarkdownParserState` - `MarkdownParserStateInterface` - `MarkdownRendererInterface` - `Query` - `RawMarkupContainerInterface` - `ReferenceableInterface` - `RenderedContent` - `RenderedContentInterface` - `ReplaceUnpairedQuotesListener` - `SpecReader` - `TableOfContentsRenderer` - `UniqueSlugNormalizer` - `UniqueSlugNormalizerInterface` - `XmlRenderer` - `XmlNodeRendererInterface` - Added several new methods: - `Cursor::getCurrentCharacter()` - `Environment::createDefaultConfiguration()` - `Environment::setEventDispatcher()` - `EnvironmentInterface::getExtensions()` - `EnvironmentInterface::getInlineParsers()` - `EnvironmentInterface::getSlugNormalizer()` - `FencedCode::setInfo()` - `Heading::setLevel()` - `HtmlRenderer::renderDocument()` - `InlineParserContext::getFullMatch()` - `InlineParserContext::getFullMatchLength()` - `InlineParserContext::getMatches()` - `InlineParserContext::getSubMatches()` - `LinkParserHelper::parsePartialLinkLabel()` - `LinkParserHelper::parsePartialLinkTitle()` - `Node::assertInstanceOf()` - `RegexHelper::isLetter()` - `StringContainerInterface::setLiteral()` - `TableCell::getType()` - `TableCell::setType()` - `TableCell::getAlign()` - `TableCell::setAlign()` ### Changed - **Changed the converter return type** - `CommonMarkConverter::convertToHtml()` now returns an instance of `RenderedContentInterface`. This can be cast to a string for backward compatibility with 1.x. - **Table of Contents items are no longer wrapped with `<p>` tags** (#613) - **Heading Permalinks now link to element IDs instead of using `name` attributes** (#602) - **Heading Permalink IDs and URL fragments now have a `content` prefix by default** (#602) - **Changes to configuration options:** - `enable_em` has been renamed to `commonmark/enable_em` - `enable_strong` has been renamed to `commonmark/enable_strong` - `use_asterisk` has been renamed to `commonmark/use_asterisk` - `use_underscore` has been renamed to `commonmark/use_underscore` - `unordered_list_markers` has been renamed to `commonmark/unordered_list_markers` - `mentions/*/symbol` has been renamed to `mentions/*/prefix` - `mentions/*/regex` has been renamed to `mentions/*/pattern` and requires partial regular expressions (without delimiters or flags) - `max_nesting_level` now defaults to `PHP_INT_MAX` and no longer supports floats - `heading_permalink/slug_normalizer` has been renamed to `slug_normalizer/instance` - **Event dispatching is now fully PSR-14 compliant** - **Moved and renamed several classes** - [see the full list here](https://commonmark.thephpleague.com/2.0/upgrading/#classesnamespaces-renamed) - The `HeadingPermalinkExtension` and `FootnoteExtension` were modified to ensure they never produce a slug which conflicts with slugs created by the other extension - `SlugNormalizer::normalizer()` now supports optional prefixes and max length options passed in via the `$context` argument - The `AbstractBlock::$data` and `AbstractInline::$data` arrays were replaced with a `Data` array-like object on the base `Node` class - **Implemented a new approach to block parsing.** This was a massive change, so here are the highlights: - Functionality previously found in block parsers and node elements has moved to block parser factories and block parsers, respectively ([more details](https://commonmark.thephpleague.com/2.0/upgrading/#new-block-parsing-approach)) - `ConfigurableEnvironmentInterface::addBlockParser()` is now `EnvironmentBuilderInterface::addBlockParserFactory()` - `ReferenceParser` was re-implemented and works completely different than before - The paragraph parser no longer needs to be added manually to the environment - **Implemented a new approach to inline parsing** where parsers can now specify longer strings or regular expressions they want to parse (instead of just single characters): - `InlineParserInterface::getCharacters()` is now `getMatchDefinition()` and returns an instance of `InlineParserMatch` - `InlineParserContext::__construct()` now requires the contents to be provided as a `Cursor` instead of a `string` - **Implemented delimiter parsing as a special type of inline parser** (via the new `DelimiterParser` class) - **Changed block and inline rendering to use common methods and interfaces** - `BlockRendererInterface` and `InlineRendererInterface` were replaced by `NodeRendererInterface` with slightly different parameters. All core renderers now implement this interface. - `ConfigurableEnvironmentInterface::addBlockRenderer()` and `addInlineRenderer()` were combined into `EnvironmentBuilderInterface::addRenderer()` - `EnvironmentInterface::getBlockRenderersForClass()` and `getInlineRenderersForClass()` are now just `getRenderersForClass()` - **Completely refactored the Configuration implementation** - All configuration-specific classes have been moved into a new `league/config` package with a new namespace - `Configuration` objects must now be configured with a schema and all options must match that schema - arbitrary keys are no longer permitted - `Configuration::__construct()` no longer accepts the default configuration values - use `Configuration::merge()` instead - `ConfigurationInterface` now only contains a `get(string $key)`; this method no longer allows arbitrary default values to be returned if the option is missing - `ConfigurableEnvironmentInterface` was renamed to `EnvironmentBuilderInterface` - `ExtensionInterface::register()` now requires an `EnvironmentBuilderInterface` param instead of `ConfigurableEnvironmentInterface` - **Added missing return types to virtually every class and interface method** - Re-implemented the GFM Autolink extension using the new inline parser approach instead of document processors - `EmailAutolinkProcessor` is now `EmailAutolinkParser` - `UrlAutolinkProcessor` is now `UrlAutolinkParser` - `HtmlElement` can now properly handle array (i.e. `class`) and boolean (i.e. `checked`) attribute values - `HtmlElement` automatically flattens any attributes with array values into space-separated strings, removing duplicate entries - Combined separate classes/interfaces into one: - `DisallowedRawHtmlRenderer` replaces `DisallowedRawHtmlBlockRenderer` and `DisallowedRawHtmlInlineRenderer` - `NodeRendererInterface` replaces `BlockRendererInterface` and `InlineRendererInterface` - Renamed the following methods: - `Environment` and `ConfigurableEnvironmentInterface`: - `addBlockParser()` is now `addBlockStartParser()` - `ReferenceMap` and `ReferenceMapInterface`: - `addReference()` is now `add()` - `getReference()` is now `get()` - `listReferences()` is now `getIterator()` - Various node (block/inline) classes: - `getContent()` is now `getLiteral()` - `setContent()` is now `setLiteral()` - Moved and renamed the following constants: - `EnvironmentInterface::HTML_INPUT_ALLOW` is now `HtmlFilter::ALLOW` - `EnvironmentInterface::HTML_INPUT_ESCAPE` is now `HtmlFilter::ESCAPE` - `EnvironmentInterface::HTML_INPUT_STRIP` is now `HtmlFilter::STRIP` - `TableCell::TYPE_HEAD` is now `TableCell::TYPE_HEADER` - `TableCell::TYPE_BODY` is now `TableCell::TYPE_DATA` - Changed the visibility of the following properties: - `AttributesInline::$attributes` is now `private` - `AttributesInline::$block` is now `private` - `TableCell::$align` is now `private` - `TableCell::$type` is now `private` - `TableSection::$type` is now `private` - Several methods which previously returned `$this` now return `void` - `Delimiter::setPrevious()` - `Node::replaceChildren()` - `Context::setTip()` - `Context::setContainer()` - `Context::setBlocksParsed()` - `AbstractStringContainer::setContent()` - `AbstractWebResource::setUrl()` - Several classes are now marked `final`: - `ArrayCollection` - `Emphasis` - `FencedCode` - `Heading` - `HtmlBlock` - `HtmlElement` - `HtmlInline` - `IndentedCode` - `Newline` - `Strikethrough` - `Strong` - `Text` - `Heading` nodes no longer directly contain a copy of their inner text - `StringContainerInterface` can now be used for inlines, not just blocks - `ArrayCollection` only supports integer keys - `HtmlElement` now implements `Stringable` - `Cursor::saveState()` and `Cursor::restoreState()` now use `CursorState` objects instead of arrays - `NodeWalker::next()` now enters, traverses any children, and leaves all elements which may have children (basically all blocks plus any inlines with children). Previously, it only did this for elements explicitly marked as "containers". - `InvalidOptionException` was removed - Anything with a `getReference(): ReferenceInterface` method now implements `ReferencableInterface` - The `SmartPunct` extension now replaces all unpaired `Quote` elements with `Text` elements towards the end of parsing, making the `QuoteRenderer` unnecessary - Several changes made to the Footnote extension: - Footnote identifiers can no longer contain spaces - Anonymous footnotes can now span subsequent lines - Footnotes can now contain multiple lines of content, including sub-blocks, by indenting them - Footnote event listeners now have numbered priorities (but still execute in the same order) - Footnotes must now be separated from previous content by a blank line - The line numbers (keys) returned via `MarkdownInput::getLines()` now start at 1 instead of 0 - `DelimiterProcessorCollectionInterface` now extends `Countable` - `RegexHelper::PARTIAL_` constants must always be used in case-insensitive contexts - `HeadingPermalinkProcessor` no longer accepts text normalizers via the constructor - these must be provided via configuration instead - Blocks which can't contain inlines will no longer be asked to render inlines - `AnonymousFootnoteRefParser` and `HeadingPermalinkProcessor` now implement `EnvironmentAwareInterface` instead of `ConfigurationAwareInterface` - The second argument to `TextNormalizerInterface::normalize()` must now be an array - The `title` attribute for `Link` and `Image` nodes is now stored using a dedicated property instead of stashing it in `$data` - `ListData::$delimiter` now returns either `ListBlock::DELIM_PERIOD` or `ListBlock::DELIM_PAREN` instead of the literal delimiter ### Fixed - **Fixed parsing of footnotes without content** - **Fixed rendering of orphaned footnotes and footnote refs** - **Fixed some URL autolinks breaking too early** (#492) - Fixed `AbstractStringContainer` not actually being `abstract` ### Removed - **Removed support for PHP 7.1, 7.2, and 7.3** (#625, #671) - **Removed all previously-deprecated functionality:** - Removed the ability to pass custom `Environment` instances into the `CommonMarkConverter` and `GithubFlavoredMarkdownConverter` constructors - Removed the `Converter` class and `ConverterInterface` - Removed the `bin/commonmark` script - Removed the `Html5Entities` utility class - Removed the `InlineMentionParser` (use `MentionParser` instead) - Removed `DefaultSlugGenerator` and `SlugGeneratorInterface` from the `Extension/HeadingPermalink/Slug` sub-namespace (use the new ones under `./SlugGenerator` instead) - Removed the following `ArrayCollection` methods: - `add()` - `set()` - `get()` - `remove()` - `isEmpty()` - `contains()` - `indexOf()` - `containsKey()` - `replaceWith()` - `removeGaps()` - Removed the `ConfigurableEnvironmentInterface::setConfig()` method - Removed the `ListBlock::TYPE_UNORDERED` constant - Removed the `CommonMarkConverter::VERSION` constant - Removed the `HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS` constant - Removed the `heading_permalink/inner_contents` configuration option - **Removed now-unused classes:** - `AbstractStringContainerBlock` - `BlockRendererInterface` - `Context` - `ContextInterface` - `Converter` - `ConverterInterface` - `InlineRendererInterface` - `PunctuationParser` (was split into two classes: `DashParser` and `EllipsesParser`) - `QuoteRenderer` - `UnmatchedBlockCloser` - Removed the following methods, properties, and constants: - `AbstractBlock::$open` - `AbstractBlock::$lastLineBlank` - `AbstractBlock::isContainer()` - `AbstractBlock::canContain()` - `AbstractBlock::isCode()` - `AbstractBlock::matchesNextLine()` - `AbstractBlock::endsWithBlankLine()` - `AbstractBlock::setLastLineBlank()` - `AbstractBlock::shouldLastLineBeBlank()` - `AbstractBlock::isOpen()` - `AbstractBlock::finalize()` - `AbstractBlock::getData()` - `AbstractInline::getData()` - `ConfigurableEnvironmentInterface::addBlockParser()` - `ConfigurableEnvironmentInterface::mergeConfig()` - `Delimiter::setCanClose()` - `EnvironmentInterface::getConfig()` - `EnvironmentInterface::getInlineParsersForCharacter()` - `EnvironmentInterface::getInlineParserCharacterRegex()` - `HtmlRenderer::renderBlock()` - `HtmlRenderer::renderBlocks()` - `HtmlRenderer::renderInline()` - `HtmlRenderer::renderInlines()` - `Node::isContainer()` - `RegexHelper::matchAll()` (use the new `matchFirst()` method instead) - `RegexHelper::REGEX_WHITESPACE` - Removed the second `$contents` argument from the `Heading` constructor ### Deprecated **The following things have been deprecated and will not be supported in v3.0:** - `Environment::mergeConfig()` (set configuration before instantiation instead) - `Environment::createCommonMarkEnvironment()` and `Environment::createGFMEnvironment()` - Alternative 1: Use `CommonMarkConverter` or `GithubFlavoredMarkdownConverter` if you don't need to customize the environment - Alternative 2: Instantiate a new `Environment` and add the necessary extensions yourself [unreleased]: https://github.com/thephpleague/commonmark/compare/2.5.3...main [2.5.3]: https://github.com/thephpleague/commonmark/compare/2.5.2...2.5.3 [2.5.2]: https://github.com/thephpleague/commonmark/compare/2.5.1...2.5.2 [2.5.1]: https://github.com/thephpleague/commonmark/compare/2.5.0...2.5.1 [2.5.0]: https://github.com/thephpleague/commonmark/compare/2.4.4...2.5.0 [2.4.4]: https://github.com/thephpleague/commonmark/compare/2.4.3...2.4.4 [2.4.3]: https://github.com/thephpleague/commonmark/compare/2.4.2...2.4.3 [2.4.2]: https://github.com/thephpleague/commonmark/compare/2.4.1...2.4.2 [2.4.1]: https://github.com/thephpleague/commonmark/compare/2.4.0...2.4.1 [2.4.0]: https://github.com/thephpleague/commonmark/compare/2.3.9...2.4.0 [2.3.9]: https://github.com/thephpleague/commonmark/compare/2.3.8...2.3.9 [2.3.8]: https://github.com/thephpleague/commonmark/compare/2.3.7...2.3.8 [2.3.7]: https://github.com/thephpleague/commonmark/compare/2.3.6...2.3.7 [2.3.6]: https://github.com/thephpleague/commonmark/compare/2.3.5...2.3.6 [2.3.5]: https://github.com/thephpleague/commonmark/compare/2.3.4...2.3.5 [2.3.4]: https://github.com/thephpleague/commonmark/compare/2.3.3...2.3.4 [2.3.3]: https://github.com/thephpleague/commonmark/compare/2.3.2...2.3.3 [2.3.2]: https://github.com/thephpleague/commonmark/compare/2.3.2...main [2.3.1]: https://github.com/thephpleague/commonmark/compare/2.3.0...2.3.1 [2.3.0]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.3.0 [2.2.5]: https://github.com/thephpleague/commonmark/compare/2.2.4...2.2.5 [2.2.4]: https://github.com/thephpleague/commonmark/compare/2.2.3...2.2.4 [2.2.3]: https://github.com/thephpleague/commonmark/compare/2.2.2...2.2.3 [2.2.2]: https://github.com/thephpleague/commonmark/compare/2.2.1...2.2.2 [2.2.1]: https://github.com/thephpleague/commonmark/compare/2.2.0...2.2.1 [2.2.0]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.2.0 [2.1.3]: https://github.com/thephpleague/commonmark/compare/2.1.2...2.1.3 [2.1.2]: https://github.com/thephpleague/commonmark/compare/2.1.1...2.1.2 [2.1.1]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.1 [2.1.0]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.1.0 [2.0.4]: https://github.com/thephpleague/commonmark/compare/2.0.3...2.0.4 [2.0.3]: https://github.com/thephpleague/commonmark/compare/2.0.2...2.0.3 [2.0.2]: https://github.com/thephpleague/commonmark/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/thephpleague/commonmark/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc2...2.0.0 [2.0.0-rc2]: https://github.com/thephpleague/commonmark/compare/2.0.0-rc1...2.0.0-rc2 [2.0.0-rc1]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta3...2.0.0-rc1 [2.0.0-beta3]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta2...2.0.0-beta3 [2.0.0-beta2]: https://github.com/thephpleague/commonmark/compare/2.0.0-beta1...2.0.0-beta2 [2.0.0-beta1]: https://github.com/thephpleague/commonmark/compare/1.6...2.0.0-beta1 commonmark/src/Environment/EnvironmentInterface.php 0000644 00000003124 15021222733 0016635 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Environment; use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection; use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Node\Node; use League\CommonMark\Normalizer\TextNormalizerInterface; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\Config\ConfigurationProviderInterface; use Psr\EventDispatcher\EventDispatcherInterface; interface EnvironmentInterface extends ConfigurationProviderInterface, EventDispatcherInterface { /** * Get all registered extensions * * @return ExtensionInterface[] */ public function getExtensions(): iterable; /** * @return iterable<BlockStartParserInterface> */ public function getBlockStartParsers(): iterable; /** * @return iterable<InlineParserInterface> */ public function getInlineParsers(): iterable; public function getDelimiterProcessors(): DelimiterProcessorCollection; /** * @psalm-param class-string<Node> $nodeClass * * @return iterable<NodeRendererInterface> */ public function getRenderersForClass(string $nodeClass): iterable; public function getSlugNormalizer(): TextNormalizerInterface; } commonmark/src/Environment/EnvironmentAwareInterface.php 0000644 00000000657 15021222733 0017625 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Environment; interface EnvironmentAwareInterface { public function setEnvironment(EnvironmentInterface $environment): void; } commonmark/src/Environment/Environment.php 0000644 00000035331 15021222733 0015021 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Environment; use League\CommonMark\Delimiter\DelimiterParser; use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Event\ListenerData; use League\CommonMark\Exception\AlreadyInitializedException; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Extension\GithubFlavoredMarkdownExtension; use League\CommonMark\Normalizer\SlugNormalizer; use League\CommonMark\Normalizer\TextNormalizerInterface; use League\CommonMark\Normalizer\UniqueSlugNormalizer; use League\CommonMark\Normalizer\UniqueSlugNormalizerInterface; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Block\SkipLinesStartingWithLettersParser; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlFilter; use League\CommonMark\Util\PrioritizedList; use League\Config\Configuration; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; use Nette\Schema\Expect; use Psr\EventDispatcher\EventDispatcherInterface; use Psr\EventDispatcher\ListenerProviderInterface; use Psr\EventDispatcher\StoppableEventInterface; final class Environment implements EnvironmentInterface, EnvironmentBuilderInterface, ListenerProviderInterface { /** * @var ExtensionInterface[] * * @psalm-readonly-allow-private-mutation */ private array $extensions = []; /** * @var ExtensionInterface[] * * @psalm-readonly-allow-private-mutation */ private array $uninitializedExtensions = []; /** @psalm-readonly-allow-private-mutation */ private bool $extensionsInitialized = false; /** * @var PrioritizedList<BlockStartParserInterface> * * @psalm-readonly */ private PrioritizedList $blockStartParsers; /** * @var PrioritizedList<InlineParserInterface> * * @psalm-readonly */ private PrioritizedList $inlineParsers; /** @psalm-readonly */ private DelimiterProcessorCollection $delimiterProcessors; /** * @var array<string, PrioritizedList<NodeRendererInterface>> * * @psalm-readonly-allow-private-mutation */ private array $renderersByClass = []; /** * @var PrioritizedList<ListenerData> * * @psalm-readonly-allow-private-mutation */ private PrioritizedList $listenerData; private ?EventDispatcherInterface $eventDispatcher = null; /** @psalm-readonly */ private Configuration $config; private ?TextNormalizerInterface $slugNormalizer = null; /** * @param array<string, mixed> $config */ public function __construct(array $config = []) { $this->config = self::createDefaultConfiguration(); $this->config->merge($config); $this->blockStartParsers = new PrioritizedList(); $this->inlineParsers = new PrioritizedList(); $this->listenerData = new PrioritizedList(); $this->delimiterProcessors = new DelimiterProcessorCollection(); // Performance optimization: always include a block "parser" that aborts parsing if a line starts with a letter // and is therefore unlikely to match any lines as a block start. $this->addBlockStartParser(new SkipLinesStartingWithLettersParser(), 249); } public function getConfiguration(): ConfigurationInterface { return $this->config->reader(); } /** * @deprecated Environment::mergeConfig() is deprecated since league/commonmark v2.0 and will be removed in v3.0. Configuration should be set when instantiating the environment instead. * * @param array<string, mixed> $config */ public function mergeConfig(array $config): void { @\trigger_error('Environment::mergeConfig() is deprecated since league/commonmark v2.0 and will be removed in v3.0. Configuration should be set when instantiating the environment instead.', \E_USER_DEPRECATED); $this->assertUninitialized('Failed to modify configuration.'); $this->config->merge($config); } public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add block start parser.'); $this->blockStartParsers->add($parser, $priority); $this->injectEnvironmentAndConfigurationIfNeeded($parser); return $this; } public function addInlineParser(InlineParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add inline parser.'); $this->inlineParsers->add($parser, $priority); $this->injectEnvironmentAndConfigurationIfNeeded($parser); return $this; } public function addDelimiterProcessor(DelimiterProcessorInterface $processor): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add delimiter processor.'); $this->delimiterProcessors->add($processor); $this->injectEnvironmentAndConfigurationIfNeeded($processor); return $this; } public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add renderer.'); if (! isset($this->renderersByClass[$nodeClass])) { $this->renderersByClass[$nodeClass] = new PrioritizedList(); } $this->renderersByClass[$nodeClass]->add($renderer, $priority); $this->injectEnvironmentAndConfigurationIfNeeded($renderer); return $this; } /** * {@inheritDoc} */ public function getBlockStartParsers(): iterable { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } return $this->blockStartParsers->getIterator(); } public function getDelimiterProcessors(): DelimiterProcessorCollection { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } return $this->delimiterProcessors; } /** * {@inheritDoc} */ public function getRenderersForClass(string $nodeClass): iterable { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } // If renderers are defined for this specific class, return them immediately if (isset($this->renderersByClass[$nodeClass])) { return $this->renderersByClass[$nodeClass]; } /** @psalm-suppress TypeDoesNotContainType -- Bug: https://github.com/vimeo/psalm/issues/3332 */ while (\class_exists($parent ??= $nodeClass) && $parent = \get_parent_class($parent)) { if (! isset($this->renderersByClass[$parent])) { continue; } // "Cache" this result to avoid future loops return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent]; } return []; } /** * {@inheritDoc} */ public function getExtensions(): iterable { return $this->extensions; } /** * Add a single extension * * @return $this */ public function addExtension(ExtensionInterface $extension): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add extension.'); $this->extensions[] = $extension; $this->uninitializedExtensions[] = $extension; if ($extension instanceof ConfigurableExtensionInterface) { $extension->configureSchema($this->config); } return $this; } private function initializeExtensions(): void { // Initialize the slug normalizer $this->getSlugNormalizer(); // Ask all extensions to register their components while (\count($this->uninitializedExtensions) > 0) { foreach ($this->uninitializedExtensions as $i => $extension) { $extension->register($this); unset($this->uninitializedExtensions[$i]); } } $this->extensionsInitialized = true; // Create the special delimiter parser if any processors were registered if ($this->delimiterProcessors->count() > 0) { $this->inlineParsers->add(new DelimiterParser($this->delimiterProcessors), PHP_INT_MIN); } } private function injectEnvironmentAndConfigurationIfNeeded(object $object): void { if ($object instanceof EnvironmentAwareInterface) { $object->setEnvironment($this); } if ($object instanceof ConfigurationAwareInterface) { $object->setConfiguration($this->config->reader()); } } /** * @deprecated Instantiate the environment and add the extension yourself * * @param array<string, mixed> $config */ public static function createCommonMarkEnvironment(array $config = []): Environment { $environment = new self($config); $environment->addExtension(new CommonMarkCoreExtension()); return $environment; } /** * @deprecated Instantiate the environment and add the extension yourself * * @param array<string, mixed> $config */ public static function createGFMEnvironment(array $config = []): Environment { $environment = new self($config); $environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new GithubFlavoredMarkdownExtension()); return $environment; } public function addEventListener(string $eventClass, callable $listener, int $priority = 0): EnvironmentBuilderInterface { $this->assertUninitialized('Failed to add event listener.'); $this->listenerData->add(new ListenerData($eventClass, $listener), $priority); if (\is_object($listener)) { $this->injectEnvironmentAndConfigurationIfNeeded($listener); } elseif (\is_array($listener) && \is_object($listener[0])) { $this->injectEnvironmentAndConfigurationIfNeeded($listener[0]); } return $this; } public function dispatch(object $event): object { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } if ($this->eventDispatcher !== null) { return $this->eventDispatcher->dispatch($event); } foreach ($this->getListenersForEvent($event) as $listener) { if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) { return $event; } $listener($event); } return $event; } public function setEventDispatcher(EventDispatcherInterface $dispatcher): void { $this->eventDispatcher = $dispatcher; } /** * {@inheritDoc} * * @return iterable<callable> */ public function getListenersForEvent(object $event): iterable { foreach ($this->listenerData as $listenerData) { \assert($listenerData instanceof ListenerData); /** @psalm-suppress ArgumentTypeCoercion */ if (! \is_a($event, $listenerData->getEvent())) { continue; } yield function (object $event) use ($listenerData) { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } return \call_user_func($listenerData->getListener(), $event); }; } } /** * @return iterable<InlineParserInterface> */ public function getInlineParsers(): iterable { if (! $this->extensionsInitialized) { $this->initializeExtensions(); } return $this->inlineParsers->getIterator(); } public function getSlugNormalizer(): TextNormalizerInterface { if ($this->slugNormalizer === null) { $normalizer = $this->config->get('slug_normalizer/instance'); \assert($normalizer instanceof TextNormalizerInterface); $this->injectEnvironmentAndConfigurationIfNeeded($normalizer); if ($this->config->get('slug_normalizer/unique') !== UniqueSlugNormalizerInterface::DISABLED && ! $normalizer instanceof UniqueSlugNormalizer) { $normalizer = new UniqueSlugNormalizer($normalizer); } if ($normalizer instanceof UniqueSlugNormalizer) { if ($this->config->get('slug_normalizer/unique') === UniqueSlugNormalizerInterface::PER_DOCUMENT) { $this->addEventListener(DocumentParsedEvent::class, [$normalizer, 'clearHistory'], -1000); } } $this->slugNormalizer = $normalizer; } return $this->slugNormalizer; } /** * @throws AlreadyInitializedException */ private function assertUninitialized(string $message): void { if ($this->extensionsInitialized) { throw new AlreadyInitializedException($message . ' Extensions have already been initialized.'); } } public static function createDefaultConfiguration(): Configuration { return new Configuration([ 'html_input' => Expect::anyOf(HtmlFilter::STRIP, HtmlFilter::ALLOW, HtmlFilter::ESCAPE)->default(HtmlFilter::ALLOW), 'allow_unsafe_links' => Expect::bool(true), 'max_nesting_level' => Expect::type('int')->default(PHP_INT_MAX), 'renderer' => Expect::structure([ 'block_separator' => Expect::string("\n"), 'inner_separator' => Expect::string("\n"), 'soft_break' => Expect::string("\n"), ]), 'slug_normalizer' => Expect::structure([ 'instance' => Expect::type(TextNormalizerInterface::class)->default(new SlugNormalizer()), 'max_length' => Expect::int()->min(0)->default(255), 'unique' => Expect::anyOf(UniqueSlugNormalizerInterface::DISABLED, UniqueSlugNormalizerInterface::PER_ENVIRONMENT, UniqueSlugNormalizerInterface::PER_DOCUMENT)->default(UniqueSlugNormalizerInterface::PER_DOCUMENT), ]), ]); } } commonmark/src/Environment/EnvironmentBuilderInterface.php 0000644 00000007542 15021222733 0020154 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Environment; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Exception\AlreadyInitializedException; use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Node\Node; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\Config\ConfigurationProviderInterface; /** * Interface for building the Environment with any extensions, parsers, listeners, etc. that it may need */ interface EnvironmentBuilderInterface extends ConfigurationProviderInterface { /** * Registers the given extension with the Environment * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addExtension(ExtensionInterface $extension): EnvironmentBuilderInterface; /** * Registers the given block start parser with the Environment * * @param BlockStartParserInterface $parser Block parser instance * @param int $priority Priority (a higher number will be executed earlier) * * @return $this * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface; /** * Registers the given inline parser with the Environment * * @param InlineParserInterface $parser Inline parser instance * @param int $priority Priority (a higher number will be executed earlier) * * @return $this * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addInlineParser(InlineParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface; /** * Registers the given delimiter processor with the Environment * * @param DelimiterProcessorInterface $processor Delimiter processors instance * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addDelimiterProcessor(DelimiterProcessorInterface $processor): EnvironmentBuilderInterface; /** * Registers the given node renderer with the Environment * * @param string $nodeClass The fully-qualified node element class name the renderer below should handle * @param NodeRendererInterface $renderer The renderer responsible for rendering the type of element given above * @param int $priority Priority (a higher number will be executed earlier) * * @psalm-param class-string<Node> $nodeClass * * @return $this * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0): EnvironmentBuilderInterface; /** * Registers the given event listener * * @param class-string $eventClass Fully-qualified class name of the event this listener should respond to * @param callable $listener Listener to be executed * @param int $priority Priority (a higher number will be executed earlier) * * @return $this * * @throws AlreadyInitializedException if the Environment has already been initialized */ public function addEventListener(string $eventClass, callable $listener, int $priority = 0): EnvironmentBuilderInterface; } commonmark/src/ConverterInterface.php 0000644 00000001423 15021222733 0013774 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Output\RenderedContentInterface; use League\Config\Exception\ConfigurationExceptionInterface; /** * Interface for a service which converts content from one format (like Markdown) to another (like HTML). */ interface ConverterInterface { /** * @throws CommonMarkException * @throws ConfigurationExceptionInterface */ public function convert(string $input): RenderedContentInterface; } commonmark/src/MarkdownConverter.php 0000644 00000005076 15021222733 0013666 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Output\RenderedContentInterface; use League\CommonMark\Parser\MarkdownParser; use League\CommonMark\Parser\MarkdownParserInterface; use League\CommonMark\Renderer\HtmlRenderer; use League\CommonMark\Renderer\MarkdownRendererInterface; class MarkdownConverter implements ConverterInterface, MarkdownConverterInterface { /** @psalm-readonly */ protected EnvironmentInterface $environment; /** @psalm-readonly */ protected MarkdownParserInterface $markdownParser; /** @psalm-readonly */ protected MarkdownRendererInterface $htmlRenderer; public function __construct(EnvironmentInterface $environment) { $this->environment = $environment; $this->markdownParser = new MarkdownParser($environment); $this->htmlRenderer = new HtmlRenderer($environment); } public function getEnvironment(): EnvironmentInterface { return $this->environment; } /** * Converts Markdown to HTML. * * @param string $input The Markdown to convert * * @return RenderedContentInterface Rendered HTML * * @throws CommonMarkException */ public function convert(string $input): RenderedContentInterface { $documentAST = $this->markdownParser->parse($input); return $this->htmlRenderer->renderDocument($documentAST); } /** * Converts Markdown to HTML. * * @deprecated since 2.2; use {@link convert()} instead * * @param string $markdown The Markdown to convert * * @return RenderedContentInterface Rendered HTML * * @throws CommonMarkException */ public function convertToHtml(string $markdown): RenderedContentInterface { \trigger_deprecation('league/commonmark', '2.2.0', 'Calling "convertToHtml()" on a %s class is deprecated, use "convert()" instead.', self::class); return $this->convert($markdown); } /** * Converts CommonMark to HTML. * * @see MarkdownConverter::convert() * * @throws CommonMarkException */ public function __invoke(string $markdown): RenderedContentInterface { return $this->convert($markdown); } } commonmark/src/Xml/FallbackNodeXmlRenderer.php 0000644 00000004256 15021222733 0015430 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Xml; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Node\Node; /** * @internal */ final class FallbackNodeXmlRenderer implements XmlNodeRendererInterface { /** * @var array<string, string> * * @psalm-allow-private-mutation */ private array $classCache = []; /** * @psalm-allow-private-mutation */ public function getXmlTagName(Node $node): string { $className = \get_class($node); if (isset($this->classCache[$className])) { return $this->classCache[$className]; } $type = $node instanceof AbstractBlock ? 'block' : 'inline'; $shortName = \strtolower((new \ReflectionClass($node))->getShortName()); return $this->classCache[$className] = \sprintf('custom_%s_%s', $type, $shortName); } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { $attrs = []; foreach ($node->data->export() as $k => $v) { if (self::isValueUsable($v)) { $attrs[$k] = $v; } } $reflClass = new \ReflectionClass($node); foreach ($reflClass->getProperties() as $property) { if (\in_array($property->getDeclaringClass()->getName(), [Node::class, AbstractBlock::class, AbstractInline::class], true)) { continue; } $property->setAccessible(true); $value = $property->getValue($node); if (self::isValueUsable($value)) { $attrs[$property->getName()] = $value; } } return $attrs; } /** * @param mixed $var * * @psalm-pure */ private static function isValueUsable($var): bool { return \is_string($var) || \is_int($var) || \is_float($var) || \is_bool($var); } } commonmark/src/Xml/XmlRenderer.php 0000644 00000010106 15021222733 0013171 0 ustar 00 <?php declare(strict_types=1); namespace League\CommonMark\Xml; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Event\DocumentPreRenderEvent; use League\CommonMark\Exception\InvalidArgumentException; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Node; use League\CommonMark\Node\StringContainerInterface; use League\CommonMark\Output\RenderedContent; use League\CommonMark\Output\RenderedContentInterface; use League\CommonMark\Renderer\DocumentRendererInterface; use League\CommonMark\Util\Xml; final class XmlRenderer implements DocumentRendererInterface { private const INDENTATION = ' '; private EnvironmentInterface $environment; private XmlNodeRendererInterface $fallbackRenderer; /** @var array<class-string, XmlNodeRendererInterface> */ private array $rendererCache = []; public function __construct(EnvironmentInterface $environment) { $this->environment = $environment; $this->fallbackRenderer = new FallbackNodeXmlRenderer(); } public function renderDocument(Document $document): RenderedContentInterface { $this->environment->dispatch(new DocumentPreRenderEvent($document, 'xml')); $xml = '<?xml version="1.0" encoding="UTF-8"?>'; $indent = 0; $walker = $document->walker(); while ($event = $walker->next()) { $node = $event->getNode(); $closeImmediately = ! $node->hasChildren(); $selfClosing = $closeImmediately && ! $node instanceof StringContainerInterface; $renderer = $this->findXmlRenderer($node); $tagName = $renderer->getXmlTagName($node); if ($event->isEntering()) { $attrs = $renderer->getXmlAttributes($node); $xml .= "\n" . \str_repeat(self::INDENTATION, $indent); $xml .= self::tag($tagName, $attrs, $selfClosing); if ($node instanceof StringContainerInterface) { $xml .= Xml::escape($node->getLiteral()); } if ($closeImmediately && ! $selfClosing) { $xml .= self::tag('/' . $tagName); } if (! $closeImmediately) { $indent++; } } elseif (! $closeImmediately) { $indent--; $xml .= "\n" . \str_repeat(self::INDENTATION, $indent); $xml .= self::tag('/' . $tagName); } } return new RenderedContent($document, $xml . "\n"); } /** * @param array<string, string|int|float|bool> $attrs */ private static function tag(string $name, array $attrs = [], bool $selfClosing = \false): string { $result = '<' . $name; foreach ($attrs as $key => $value) { $result .= \sprintf(' %s="%s"', $key, self::convertAndEscape($value)); } if ($selfClosing) { $result .= ' /'; } $result .= '>'; return $result; } /** * @param string|int|float|bool $value */ private static function convertAndEscape($value): string { if (\is_string($value)) { return Xml::escape($value); } if (\is_int($value) || \is_float($value)) { return (string) $value; } if (\is_bool($value)) { return $value ? 'true' : 'false'; } // @phpstan-ignore-next-line throw new InvalidArgumentException('$value must be a string, int, float, or bool'); } private function findXmlRenderer(Node $node): XmlNodeRendererInterface { $class = \get_class($node); if (\array_key_exists($class, $this->rendererCache)) { return $this->rendererCache[$class]; } foreach ($this->environment->getRenderersForClass($class) as $renderer) { if ($renderer instanceof XmlNodeRendererInterface) { return $this->rendererCache[$class] = $renderer; } } return $this->rendererCache[$class] = $this->fallbackRenderer; } } commonmark/src/Xml/XmlNodeRendererInterface.php 0000644 00000001142 15021222733 0015620 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Xml; use League\CommonMark\Node\Node; interface XmlNodeRendererInterface { public function getXmlTagName(Node $node): string; /** * @return array<string, string|int|float|bool> * * @psalm-return array<string, scalar> */ public function getXmlAttributes(Node $node): array; } commonmark/src/Xml/MarkdownToXmlConverter.php 0000644 00000003104 15021222733 0015400 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Xml; use League\CommonMark\ConverterInterface; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Output\RenderedContentInterface; use League\CommonMark\Parser\MarkdownParser; use League\CommonMark\Parser\MarkdownParserInterface; use League\CommonMark\Renderer\DocumentRendererInterface; final class MarkdownToXmlConverter implements ConverterInterface { /** @psalm-readonly */ private MarkdownParserInterface $parser; /** @psalm-readonly */ private DocumentRendererInterface $renderer; public function __construct(EnvironmentInterface $environment) { $this->parser = new MarkdownParser($environment); $this->renderer = new XmlRenderer($environment); } /** * Converts Markdown to XML * * @throws CommonMarkException */ public function convert(string $input): RenderedContentInterface { return $this->renderer->renderDocument($this->parser->parse($input)); } /** * Converts CommonMark to HTML. * * @see MarkdownToXmlConverter::convert() * * @throws CommonMarkException */ public function __invoke(string $input): RenderedContentInterface { return $this->convert($input); } } commonmark/src/Output/RenderedContentInterface.php 0000644 00000001114 15021222733 0016405 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Output; use League\CommonMark\Node\Block\Document; interface RenderedContentInterface extends \Stringable { /** * @psalm-mutation-free */ public function getDocument(): Document; /** * @psalm-mutation-free */ public function getContent(): string; } commonmark/src/Output/RenderedContent.php 0000644 00000001752 15021222733 0014574 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Output; use League\CommonMark\Node\Block\Document; class RenderedContent implements RenderedContentInterface, \Stringable { /** @psalm-readonly */ private Document $document; /** @psalm-readonly */ private string $content; public function __construct(Document $document, string $content) { $this->document = $document; $this->content = $content; } public function getDocument(): Document { return $this->document; } public function getContent(): string { return $this->content; } /** * @psalm-mutation-free */ public function __toString(): string { return $this->content; } } commonmark/src/GithubFlavoredMarkdownConverter.php 0000644 00000002255 15021222733 0016510 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; use League\CommonMark\Extension\GithubFlavoredMarkdownExtension; /** * Converts GitHub Flavored Markdown to HTML. */ final class GithubFlavoredMarkdownConverter extends MarkdownConverter { /** * Create a new Markdown converter pre-configured for GFM * * @param array<string, mixed> $config */ public function __construct(array $config = []) { $environment = new Environment($config); $environment->addExtension(new CommonMarkCoreExtension()); $environment->addExtension(new GithubFlavoredMarkdownExtension()); parent::__construct($environment); } public function getEnvironment(): Environment { \assert($this->environment instanceof Environment); return $this->environment; } } commonmark/src/CommonMarkConverter.php 0000644 00000002236 15021222733 0014142 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark; use League\CommonMark\Environment\Environment; use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension; /** * Converts CommonMark-compatible Markdown to HTML. */ final class CommonMarkConverter extends MarkdownConverter { /** * Create a new Markdown converter pre-configured for CommonMark * * @param array<string, mixed> $config */ public function __construct(array $config = []) { $environment = new Environment($config); $environment->addExtension(new CommonMarkCoreExtension()); parent::__construct($environment); } public function getEnvironment(): Environment { \assert($this->environment instanceof Environment); return $this->environment; } } commonmark/src/Event/DocumentPreParsedEvent.php 0000644 00000002164 15021222733 0015656 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Event; use League\CommonMark\Input\MarkdownInputInterface; use League\CommonMark\Node\Block\Document; /** * Event dispatched when the document is about to be parsed */ final class DocumentPreParsedEvent extends AbstractEvent { /** @psalm-readonly */ private Document $document; private MarkdownInputInterface $markdown; public function __construct(Document $document, MarkdownInputInterface $markdown) { $this->document = $document; $this->markdown = $markdown; } public function getDocument(): Document { return $this->document; } public function getMarkdown(): MarkdownInputInterface { return $this->markdown; } public function replaceMarkdown(MarkdownInputInterface $markdownInput): void { $this->markdown = $markdownInput; } } commonmark/src/Event/DocumentRenderedEvent.php 0000644 00000001604 15021222733 0015517 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Event; use League\CommonMark\Output\RenderedContentInterface; final class DocumentRenderedEvent extends AbstractEvent { private RenderedContentInterface $output; public function __construct(RenderedContentInterface $output) { $this->output = $output; } /** * @psalm-mutation-free */ public function getOutput(): RenderedContentInterface { return $this->output; } /** * @psalm-external-mutation-free */ public function replaceOutput(RenderedContentInterface $output): void { $this->output = $output; } } commonmark/src/Event/DocumentPreRenderEvent.php 0000644 00000001616 15021222733 0015660 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Event; use League\CommonMark\Node\Block\Document; /** * Event dispatched just before rendering begins */ final class DocumentPreRenderEvent extends AbstractEvent { /** @psalm-readonly */ private Document $document; /** @psalm-readonly */ private string $format; public function __construct(Document $document, string $format) { $this->document = $document; $this->format = $format; } public function getDocument(): Document { return $this->document; } public function getFormat(): string { return $this->format; } } commonmark/src/Event/DocumentParsedEvent.php 0000644 00000001330 15021222733 0015201 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Event; use League\CommonMark\Node\Block\Document; /** * Event dispatched when the document has been fully parsed */ final class DocumentParsedEvent extends AbstractEvent { /** @psalm-readonly */ private Document $document; public function __construct(Document $document) { $this->document = $document; } public function getDocument(): Document { return $this->document; } } commonmark/src/Event/ListenerData.php 0000644 00000001605 15021222733 0013646 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Event; /** * @internal * * @psalm-immutable */ final class ListenerData { /** @var class-string */ private string $event; /** @var callable */ private $listener; /** * @param class-string $event */ public function __construct(string $event, callable $listener) { $this->event = $event; $this->listener = $listener; } /** * @return class-string */ public function getEvent(): string { return $this->event; } public function getListener(): callable { return $this->listener; } } commonmark/src/Event/AbstractEvent.php 0000644 00000002752 15021222733 0014040 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the Symfony EventDispatcher "Event" contract * - (c) 2018-2019 Fabien Potencier * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Event; use Psr\EventDispatcher\StoppableEventInterface; /** * Base class for classes containing event data. * * This class contains no event data. It is used by events that do not pass * state information to an event handler when an event is raised. * * You can call the method stopPropagation() to abort the execution of * further listeners in your event listener. */ abstract class AbstractEvent implements StoppableEventInterface { /** @psalm-readonly-allow-private-mutation */ private bool $propagationStopped = false; /** * Returns whether further event listeners should be triggered. */ final public function isPropagationStopped(): bool { return $this->propagationStopped; } /** * Stops the propagation of the event to further event listeners. * * If multiple event listeners are connected to the same event, no * further event listener will be triggered once any trigger calls * stopPropagation(). */ final public function stopPropagation(): void { $this->propagationStopped = true; } } commonmark/src/Node/Query.php 0000644 00000006360 15021222733 0012203 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; use League\CommonMark\Node\Query\AndExpr; use League\CommonMark\Node\Query\OrExpr; final class Query { /** @var callable(Node): bool $condition */ private $condition; public function __construct() { $this->condition = new AndExpr(); } public function where(callable ...$conditions): self { return $this->andWhere(...$conditions); } public function andWhere(callable ...$conditions): self { if ($this->condition instanceof AndExpr) { foreach ($conditions as $condition) { $this->condition->add($condition); } } else { $this->condition = new AndExpr($this->condition, ...$conditions); } return $this; } public function orWhere(callable ...$conditions): self { if ($this->condition instanceof OrExpr) { foreach ($conditions as $condition) { $this->condition->add($condition); } } else { $this->condition = new OrExpr($this->condition, ...$conditions); } return $this; } public function findOne(Node $node): ?Node { foreach ($node->iterator() as $n) { if (\call_user_func($this->condition, $n)) { return $n; } } return null; } /** * @return iterable<Node> */ public function findAll(Node $node, ?int $limit = PHP_INT_MAX): iterable { $resultCount = 0; foreach ($node->iterator() as $n) { if ($resultCount >= $limit) { break; } if (! \call_user_func($this->condition, $n)) { continue; } ++$resultCount; yield $n; } } /** * @return callable(Node): bool */ public static function type(string $class): callable { return static fn (Node $node): bool => $node instanceof $class; } /** * @psalm-param ?callable(Node): bool $condition * * @return callable(Node): bool */ public static function hasChild(?callable $condition = null): callable { return static function (Node $node) use ($condition): bool { foreach ($node->children() as $child) { if ($condition === null || $condition($child)) { return true; } } return false; }; } /** * @psalm-param ?callable(Node): bool $condition * * @return callable(Node): bool */ public static function hasParent(?callable $condition = null): callable { return static function (Node $node) use ($condition): bool { $parent = $node->parent(); if ($parent === null) { return false; } if ($condition === null) { return true; } return $condition($parent); }; } } commonmark/src/Node/RawMarkupContainerInterface.php 0000644 00000000721 15021222733 0016466 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; /** * Interface for a node which contains raw, unprocessed markup (like HTML) */ interface RawMarkupContainerInterface extends StringContainerInterface { } commonmark/src/Node/Node.php 0000644 00000014502 15021222733 0011760 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; use Dflydev\DotAccessData\Data; use League\CommonMark\Exception\InvalidArgumentException; abstract class Node { /** @psalm-readonly */ public Data $data; /** @psalm-readonly-allow-private-mutation */ protected int $depth = 0; /** @psalm-readonly-allow-private-mutation */ protected ?Node $parent = null; /** @psalm-readonly-allow-private-mutation */ protected ?Node $previous = null; /** @psalm-readonly-allow-private-mutation */ protected ?Node $next = null; /** @psalm-readonly-allow-private-mutation */ protected ?Node $firstChild = null; /** @psalm-readonly-allow-private-mutation */ protected ?Node $lastChild = null; public function __construct() { $this->data = new Data([ 'attributes' => [], ]); } public function previous(): ?Node { return $this->previous; } public function next(): ?Node { return $this->next; } public function parent(): ?Node { return $this->parent; } protected function setParent(?Node $node = null): void { $this->parent = $node; $this->depth = $node === null ? 0 : $node->depth + 1; } /** * Inserts the $sibling node after $this */ public function insertAfter(Node $sibling): void { $sibling->detach(); $sibling->next = $this->next; if ($sibling->next) { $sibling->next->previous = $sibling; } $sibling->previous = $this; $this->next = $sibling; $sibling->setParent($this->parent); if (! $sibling->next && $sibling->parent) { $sibling->parent->lastChild = $sibling; } } /** * Inserts the $sibling node before $this */ public function insertBefore(Node $sibling): void { $sibling->detach(); $sibling->previous = $this->previous; if ($sibling->previous) { $sibling->previous->next = $sibling; } $sibling->next = $this; $this->previous = $sibling; $sibling->setParent($this->parent); if (! $sibling->previous && $sibling->parent) { $sibling->parent->firstChild = $sibling; } } public function replaceWith(Node $replacement): void { $replacement->detach(); $this->insertAfter($replacement); $this->detach(); } public function detach(): void { if ($this->previous) { $this->previous->next = $this->next; } elseif ($this->parent) { $this->parent->firstChild = $this->next; } if ($this->next) { $this->next->previous = $this->previous; } elseif ($this->parent) { $this->parent->lastChild = $this->previous; } $this->parent = null; $this->next = null; $this->previous = null; $this->depth = 0; } public function hasChildren(): bool { return $this->firstChild !== null; } public function firstChild(): ?Node { return $this->firstChild; } public function lastChild(): ?Node { return $this->lastChild; } /** * @return Node[] */ public function children(): iterable { $children = []; for ($current = $this->firstChild; $current !== null; $current = $current->next) { $children[] = $current; } return $children; } public function appendChild(Node $child): void { if ($this->lastChild) { $this->lastChild->insertAfter($child); } else { $child->detach(); $child->setParent($this); $this->lastChild = $this->firstChild = $child; } } /** * Adds $child as the very first child of $this */ public function prependChild(Node $child): void { if ($this->firstChild) { $this->firstChild->insertBefore($child); } else { $child->detach(); $child->setParent($this); $this->lastChild = $this->firstChild = $child; } } /** * Detaches all child nodes of given node */ public function detachChildren(): void { foreach ($this->children() as $children) { $children->setParent(null); } $this->firstChild = $this->lastChild = null; } /** * Replace all children of given node with collection of another * * @param iterable<Node> $children */ public function replaceChildren(iterable $children): void { $this->detachChildren(); foreach ($children as $item) { $this->appendChild($item); } } public function getDepth(): int { return $this->depth; } public function walker(): NodeWalker { return new NodeWalker($this); } public function iterator(int $flags = 0): NodeIterator { return new NodeIterator($this, $flags); } /** * Clone the current node and its children * * WARNING: This is a recursive function and should not be called on deeply-nested node trees! */ public function __clone() { // Cloned nodes are detached from their parents, siblings, and children $this->parent = null; $this->previous = null; $this->next = null; // But save a copy of the children since we'll need that in a moment $children = $this->children(); $this->detachChildren(); // The original children get cloned and re-added foreach ($children as $child) { $this->appendChild(clone $child); } } public static function assertInstanceOf(Node $node): void { if (! $node instanceof static) { throw new InvalidArgumentException(\sprintf('Incompatible node type: expected %s, got %s', static::class, \get_class($node))); } } } commonmark/src/Node/NodeWalkerEvent.php 0000644 00000001575 15021222733 0014136 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; final class NodeWalkerEvent { /** @psalm-readonly */ private Node $node; /** @psalm-readonly */ private bool $isEntering; public function __construct(Node $node, bool $isEntering = true) { $this->node = $node; $this->isEntering = $isEntering; } public function getNode(): Node { return $this->node; } public function isEntering(): bool { return $this->isEntering; } } commonmark/src/Node/Block/AbstractBlock.php 0000644 00000002661 15021222733 0014646 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Block; use League\CommonMark\Exception\InvalidArgumentException; use League\CommonMark\Node\Node; /** * Block-level element * * @method parent() ?AbstractBlock */ abstract class AbstractBlock extends Node { protected ?int $startLine = null; protected ?int $endLine = null; protected function setParent(?Node $node = null): void { if ($node && ! $node instanceof self) { throw new InvalidArgumentException('Parent of block must also be block (cannot be inline)'); } parent::setParent($node); } public function setStartLine(?int $startLine): void { $this->startLine = $startLine; if ($this->endLine === null) { $this->endLine = $startLine; } } public function getStartLine(): ?int { return $this->startLine; } public function setEndLine(?int $endLine): void { $this->endLine = $endLine; } public function getEndLine(): ?int { return $this->endLine; } } commonmark/src/Node/Block/TightBlockInterface.php 0000644 00000000662 15021222733 0016002 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Block; interface TightBlockInterface { public function isTight(): bool; public function setTight(bool $tight): void; } commonmark/src/Node/Block/Document.php 0000644 00000002402 15021222733 0013677 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Reference\ReferenceMap; use League\CommonMark\Reference\ReferenceMapInterface; class Document extends AbstractBlock { /** @psalm-readonly */ protected ReferenceMapInterface $referenceMap; public function __construct(?ReferenceMapInterface $referenceMap = null) { parent::__construct(); $this->setStartLine(1); $this->referenceMap = $referenceMap ?? new ReferenceMap(); } public function getReferenceMap(): ReferenceMapInterface { return $this->referenceMap; } public function canContain(AbstractBlock $block): bool { return true; } public function isCode(): bool { return false; } public function matchesNextLine(Cursor $cursor): bool { return true; } } commonmark/src/Node/Block/Paragraph.php 0000644 00000001063 15021222733 0014030 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Block; class Paragraph extends AbstractBlock { /** @internal */ public bool $onlyContainsLinkReferenceDefinitions = false; } commonmark/src/Node/NodeWalker.php 0000644 00000004231 15021222733 0013124 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; use League\CommonMark\Node\Block\AbstractBlock; final class NodeWalker { /** @psalm-readonly */ private Node $root; /** @psalm-readonly-allow-private-mutation */ private ?Node $current = null; /** @psalm-readonly-allow-private-mutation */ private bool $entering; public function __construct(Node $root) { $this->root = $root; $this->current = $this->root; $this->entering = true; } /** * Returns an event which contains node and entering flag * (entering is true when we enter a Node from a parent or sibling, * and false when we reenter it from child) */ public function next(): ?NodeWalkerEvent { $current = $this->current; $entering = $this->entering; if ($current === null) { return null; } if ($entering && ($current instanceof AbstractBlock || $current->hasChildren())) { if ($current->firstChild()) { $this->current = $current->firstChild(); $this->entering = true; } else { $this->entering = false; } } elseif ($current === $this->root) { $this->current = null; } elseif ($current->next() === null) { $this->current = $current->parent(); $this->entering = false; } else { $this->current = $current->next(); $this->entering = true; } return new NodeWalkerEvent($current, $entering); } /** * Resets the iterator to resume at the specified node */ public function resumeAt(Node $node, bool $entering = true): void { $this->current = $node; $this->entering = $entering; } } commonmark/src/Node/NodeIterator.php 0000644 00000002542 15021222733 0013473 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; use League\CommonMark\Node\Block\AbstractBlock; /** * @implements \IteratorAggregate<int, Node> */ final class NodeIterator implements \IteratorAggregate { public const FLAG_BLOCKS_ONLY = 1; private Node $node; private bool $blocksOnly; public function __construct(Node $node, int $flags = 0) { $this->node = $node; $this->blocksOnly = ($flags & self::FLAG_BLOCKS_ONLY) === self::FLAG_BLOCKS_ONLY; } /** * @return \Generator<int, Node> */ public function getIterator(): \Generator { $stack = [$this->node]; $index = 0; while ($stack) { $node = \array_pop($stack); yield $index++ => $node; // Push all children onto the stack in reverse order $child = $node->lastChild(); while ($child !== null) { if (! $this->blocksOnly || $child instanceof AbstractBlock) { $stack[] = $child; } $child = $child->previous(); } } } } commonmark/src/Node/StringContainerInterface.php 0000644 00000001200 15021222733 0016014 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; /** * Interface for a node which directly contains line(s) of text */ interface StringContainerInterface { public function setLiteral(string $literal): void; public function getLiteral(): string; } commonmark/src/Node/StringContainerHelper.php 0000644 00000002514 15021222733 0015344 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node; final class StringContainerHelper { /** * Extract text literals from all descendant nodes * * @param Node $node Parent node * @param array<string> $excludeTypes Optional list of node class types to exclude * * @return string Concatenated literals */ public static function getChildText(Node $node, array $excludeTypes = []): string { $text = ''; foreach ($node->iterator() as $child) { if ($child instanceof StringContainerInterface && ! self::isOneOf($child, $excludeTypes)) { $text .= $child->getLiteral(); } } return $text; } /** * @param string[] $classesOrInterfacesToCheck * * @psalm-pure */ private static function isOneOf(object $object, array $classesOrInterfacesToCheck): bool { foreach ($classesOrInterfacesToCheck as $type) { if ($object instanceof $type) { return true; } } return false; } } commonmark/src/Node/Inline/Text.php 0000644 00000001115 15021222733 0013231 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; final class Text extends AbstractStringContainer { public function append(string $literal): void { $this->literal .= $literal; } } commonmark/src/Node/Inline/AbstractStringContainer.php 0000644 00000002127 15021222733 0017106 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; use League\CommonMark\Node\StringContainerInterface; abstract class AbstractStringContainer extends AbstractInline implements StringContainerInterface { protected string $literal = ''; /** * @param array<string, mixed> $data */ public function __construct(string $contents = '', array $data = []) { parent::__construct(); $this->literal = $contents; if (\count($data) > 0) { $this->data->import($data); } } public function getLiteral(): string { return $this->literal; } public function setLiteral(string $literal): void { $this->literal = $literal; } } commonmark/src/Node/Inline/AbstractInline.php 0000644 00000001007 15021222733 0015207 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; use League\CommonMark\Node\Node; abstract class AbstractInline extends Node { } commonmark/src/Node/Inline/DelimitedInterface.php 0000644 00000000702 15021222733 0016027 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; interface DelimitedInterface { public function getOpeningDelimiter(): string; public function getClosingDelimiter(): string; } commonmark/src/Node/Inline/AdjacentTextMerger.php 0000644 00000005604 15021222733 0016034 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; use League\CommonMark\Node\Node; /** * @internal */ final class AdjacentTextMerger { public static function mergeChildNodes(Node $node): void { // No children or just one child node, no need for merging if ($node->firstChild() === $node->lastChild() || $node->firstChild() === null || $node->lastChild() === null) { return; } /** @psalm-suppress PossiblyNullArgument */ self::mergeTextNodesInclusive($node->firstChild(), $node->lastChild()); } public static function mergeTextNodesBetweenExclusive(Node $fromNode, Node $toNode): void { // No nodes between them if ($fromNode === $toNode || $fromNode->next() === $toNode || $fromNode->next() === null || $toNode->previous() === null) { return; } /** @psalm-suppress PossiblyNullArgument */ self::mergeTextNodesInclusive($fromNode->next(), $toNode->previous()); } public static function mergeWithDirectlyAdjacentNodes(Text $node): void { $start = ($previous = $node->previous()) instanceof Text ? $previous : $node; $end = ($next = $node->next()) instanceof Text ? $next : $node; self::mergeIfNeeded($start, $end); } private static function mergeTextNodesInclusive(Node $fromNode, Node $toNode): void { $first = null; $last = null; $node = $fromNode; while ($node !== null) { if ($node instanceof Text) { if ($first === null) { $first = $node; } $last = $node; } else { self::mergeIfNeeded($first, $last); $first = null; $last = null; } if ($node === $toNode) { break; } $node = $node->next(); } self::mergeIfNeeded($first, $last); } private static function mergeIfNeeded(?Text $first, ?Text $last): void { if ($first === null || $last === null || $first === $last) { // No merging needed return; } $s = $first->getLiteral(); $node = $first->next(); $stop = $last->next(); while ($node !== $stop && $node instanceof Text) { $s .= $node->getLiteral(); $unlink = $node; $node = $node->next(); $unlink->detach(); } $first->setLiteral($s); } } commonmark/src/Node/Inline/Newline.php 0000644 00000001641 15021222733 0013712 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Inline; final class Newline extends AbstractInline { // Any changes to these constants should be reflected in .phpstorm.meta.php public const HARDBREAK = 0; public const SOFTBREAK = 1; /** @psalm-readonly */ private int $type; public function __construct(int $breakType = self::HARDBREAK) { parent::__construct(); $this->type = $breakType; } /** @psalm-immutable */ public function getType(): int { return $this->type; } } commonmark/src/Node/Query/ExpressionInterface.php 0000644 00000000655 15021222733 0016164 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Query; use League\CommonMark\Node\Node; interface ExpressionInterface { public function __invoke(Node $node): bool; } commonmark/src/Node/Query/AndExpr.php 0000644 00000002156 15021222733 0013543 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Query; use League\CommonMark\Node\Node; /** * @internal */ final class AndExpr implements ExpressionInterface { /** * @var callable[] * @psalm-var list<callable(Node): bool> */ private array $conditions; /** * @psalm-param callable(Node): bool $expressions */ public function __construct(callable ...$expressions) { $this->conditions = \array_values($expressions); } /** * @param callable(Node): bool $expression */ public function add(callable $expression): void { $this->conditions[] = $expression; } public function __invoke(Node $node): bool { foreach ($this->conditions as $condition) { if (! $condition($node)) { return false; } } return true; } } commonmark/src/Node/Query/OrExpr.php 0000644 00000002153 15021222733 0013416 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Node\Query; use League\CommonMark\Node\Node; /** * @internal */ final class OrExpr implements ExpressionInterface { /** * @var callable[] * @psalm-var list<callable(Node): bool> */ private array $conditions; /** * @psalm-param callable(Node): bool $expressions */ public function __construct(callable ...$expressions) { $this->conditions = \array_values($expressions); } /** * @param callable(Node): bool $expression */ public function add(callable $expression): void { $this->conditions[] = $expression; } public function __invoke(Node $node): bool { foreach ($this->conditions as $condition) { if ($condition($node)) { return true; } } return false; } } commonmark/src/Reference/ReferenceableInterface.php 0000644 00000000625 15021222733 0016450 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Reference; interface ReferenceableInterface { public function getReference(): ReferenceInterface; } commonmark/src/Reference/ReferenceMap.php 0000644 00000003515 15021222733 0014442 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Reference; use League\CommonMark\Normalizer\TextNormalizer; /** * A collection of references, indexed by label */ final class ReferenceMap implements ReferenceMapInterface { /** @psalm-readonly */ private TextNormalizer $normalizer; /** * @var array<string, ReferenceInterface> * * @psalm-readonly-allow-private-mutation */ private array $references = []; public function __construct() { $this->normalizer = new TextNormalizer(); } public function add(ReferenceInterface $reference): void { // Normalize the key $key = $this->normalizer->normalize($reference->getLabel()); // Store the reference $this->references[$key] = $reference; } public function contains(string $label): bool { $label = $this->normalizer->normalize($label); return isset($this->references[$label]); } public function get(string $label): ?ReferenceInterface { $label = $this->normalizer->normalize($label); return $this->references[$label] ?? null; } /** * @return \Traversable<string, ReferenceInterface> */ public function getIterator(): \Traversable { foreach ($this->references as $normalizedLabel => $reference) { yield $normalizedLabel => $reference; } } public function count(): int { return \count($this->references); } } commonmark/src/Reference/ReferenceParser.php 0000644 00000022026 15021222733 0015157 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Reference; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\LinkParserHelper; final class ReferenceParser { // Looking for the start of a definition, i.e. `[` private const START_DEFINITION = 0; // Looking for and parsing the label, i.e. `[foo]` within `[foo]` private const LABEL = 1; // Parsing the destination, i.e. `/url` in `[foo]: /url` private const DESTINATION = 2; // Looking for the start of a title, i.e. the first `"` in `[foo]: /url "title"` private const START_TITLE = 3; // Parsing the content of the title, i.e. `title` in `[foo]: /url "title"` private const TITLE = 4; // End state, no matter what kind of lines we add, they won't be references private const PARAGRAPH = 5; /** @psalm-readonly-allow-private-mutation */ private string $paragraph = ''; /** * @var array<int, ReferenceInterface> * * @psalm-readonly-allow-private-mutation */ private array $references = []; /** @psalm-readonly-allow-private-mutation */ private int $state = self::START_DEFINITION; /** @psalm-readonly-allow-private-mutation */ private ?string $label = null; /** @psalm-readonly-allow-private-mutation */ private ?string $destination = null; /** * @var string string * * @psalm-readonly-allow-private-mutation */ private string $title = ''; /** @psalm-readonly-allow-private-mutation */ private ?string $titleDelimiter = null; /** @psalm-readonly-allow-private-mutation */ private bool $referenceValid = false; public function getParagraphContent(): string { return $this->paragraph; } /** * @return ReferenceInterface[] */ public function getReferences(): iterable { $this->finishReference(); return $this->references; } public function hasReferences(): bool { return $this->references !== []; } public function parse(string $line): void { if ($this->paragraph !== '') { $this->paragraph .= "\n"; } $this->paragraph .= $line; $cursor = new Cursor($line); while (! $cursor->isAtEnd()) { $result = false; switch ($this->state) { case self::PARAGRAPH: // We're in a paragraph now. Link reference definitions can only appear at the beginning, so once // we're in a paragraph, there's no going back. return; case self::START_DEFINITION: $result = $this->parseStartDefinition($cursor); break; case self::LABEL: $result = $this->parseLabel($cursor); break; case self::DESTINATION: $result = $this->parseDestination($cursor); break; case self::START_TITLE: $result = $this->parseStartTitle($cursor); break; case self::TITLE: $result = $this->parseTitle($cursor); break; default: // this should never happen break; } if (! $result) { $this->state = self::PARAGRAPH; return; } } } private function parseStartDefinition(Cursor $cursor): bool { $cursor->advanceToNextNonSpaceOrTab(); if ($cursor->isAtEnd() || $cursor->getCurrentCharacter() !== '[') { return false; } $this->state = self::LABEL; $this->label = ''; $cursor->advance(); if ($cursor->isAtEnd()) { $this->label .= "\n"; } return true; } private function parseLabel(Cursor $cursor): bool { $cursor->advanceToNextNonSpaceOrTab(); $partialLabel = LinkParserHelper::parsePartialLinkLabel($cursor); if ($partialLabel === null) { return false; } \assert($this->label !== null); $this->label .= $partialLabel; if ($cursor->isAtEnd()) { // label might continue on next line $this->label .= "\n"; return true; } if ($cursor->getCurrentCharacter() !== ']') { return false; } $cursor->advance(); // end of label if ($cursor->getCurrentCharacter() !== ':') { return false; } $cursor->advance(); // spec: A link label can have at most 999 characters inside the square brackets if (\mb_strlen($this->label, 'UTF-8') > 999) { return false; } // spec: A link label must contain at least one non-whitespace character if (\trim($this->label) === '') { return false; } $cursor->advanceToNextNonSpaceOrTab(); $this->state = self::DESTINATION; return true; } private function parseDestination(Cursor $cursor): bool { $cursor->advanceToNextNonSpaceOrTab(); $destination = LinkParserHelper::parseLinkDestination($cursor); if ($destination === null) { return false; } $this->destination = $destination; $advanced = $cursor->advanceToNextNonSpaceOrTab(); if ($cursor->isAtEnd()) { // Destination was at end of line, so this is a valid reference for sure (and maybe a title). // If not at end of line, wait for title to be valid first. $this->referenceValid = true; $this->paragraph = ''; } elseif ($advanced === 0) { // spec: The title must be separated from the link destination by whitespace return false; } $this->state = self::START_TITLE; return true; } private function parseStartTitle(Cursor $cursor): bool { $cursor->advanceToNextNonSpaceOrTab(); if ($cursor->isAtEnd()) { $this->state = self::START_DEFINITION; return true; } $this->titleDelimiter = null; switch ($c = $cursor->getCurrentCharacter()) { case '"': case "'": $this->titleDelimiter = $c; break; case '(': $this->titleDelimiter = ')'; break; default: // no title delimter found break; } if ($this->titleDelimiter !== null) { $this->state = self::TITLE; $cursor->advance(); if ($cursor->isAtEnd()) { $this->title .= "\n"; } } else { $this->finishReference(); // There might be another reference instead, try that for the same character. $this->state = self::START_DEFINITION; } return true; } private function parseTitle(Cursor $cursor): bool { \assert($this->titleDelimiter !== null); $title = LinkParserHelper::parsePartialLinkTitle($cursor, $this->titleDelimiter); if ($title === null) { // Invalid title, stop return false; } // Did we find the end delimiter? $endDelimiterFound = false; if (\substr($title, -1) === $this->titleDelimiter) { $endDelimiterFound = true; // Chop it off $title = \substr($title, 0, -1); } $this->title .= $title; if (! $endDelimiterFound && $cursor->isAtEnd()) { // Title still going, continue on next line $this->title .= "\n"; return true; } // We either hit the end delimiter or some extra whitespace $cursor->advanceToNextNonSpaceOrTab(); if (! $cursor->isAtEnd()) { // spec: No further non-whitespace characters may occur on the line. return false; } $this->referenceValid = true; $this->finishReference(); $this->paragraph = ''; // See if there's another definition $this->state = self::START_DEFINITION; return true; } private function finishReference(): void { if (! $this->referenceValid) { return; } /** @psalm-suppress PossiblyNullArgument -- these can't possibly be null if we're in this state */ $this->references[] = new Reference($this->label, $this->destination, $this->title); $this->label = null; $this->referenceValid = false; $this->destination = null; $this->title = ''; $this->titleDelimiter = null; } } commonmark/src/Reference/Reference.php 0000644 00000002206 15021222733 0014000 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Reference; /** * @psalm-immutable */ final class Reference implements ReferenceInterface { /** @psalm-readonly */ private string $label; /** @psalm-readonly */ private string $destination; /** @psalm-readonly */ private string $title; public function __construct(string $label, string $destination, string $title) { $this->label = $label; $this->destination = $destination; $this->title = $title; } public function getLabel(): string { return $this->label; } public function getDestination(): string { return $this->destination; } public function getTitle(): string { return $this->title; } } commonmark/src/Reference/ReferenceInterface.php 0000644 00000001157 15021222733 0015625 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Reference; /** * Link reference */ interface ReferenceInterface { public function getLabel(): string; public function getDestination(): string; public function getTitle(): string; } commonmark/src/Reference/ReferenceMapInterface.php 0000644 00000001423 15021222733 0016257 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Reference; /** * A collection of references * * @phpstan-extends \IteratorAggregate<ReferenceInterface> */ interface ReferenceMapInterface extends \IteratorAggregate, \Countable { public function add(ReferenceInterface $reference): void; public function contains(string $label): bool; public function get(string $label): ?ReferenceInterface; } commonmark/src/Delimiter/DelimiterInterface.php 0000644 00000002303 15021222733 0015657 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter; use League\CommonMark\Node\Inline\AbstractStringContainer; interface DelimiterInterface { public function canClose(): bool; public function canOpen(): bool; public function isActive(): bool; public function setActive(bool $active): void; public function getChar(): string; public function getIndex(): ?int; public function getNext(): ?DelimiterInterface; public function setNext(?DelimiterInterface $next): void; public function getLength(): int; public function setLength(int $length): void; public function getOriginalLength(): int; public function getInlineNode(): AbstractStringContainer; public function getPrevious(): ?DelimiterInterface; public function setPrevious(?DelimiterInterface $previous): void; } commonmark/src/Delimiter/Delimiter.php 0000644 00000005740 15021222733 0014046 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter; use League\CommonMark\Node\Inline\AbstractStringContainer; final class Delimiter implements DelimiterInterface { /** @psalm-readonly */ private string $char; /** @psalm-readonly-allow-private-mutation */ private int $length; /** @psalm-readonly */ private int $originalLength; /** @psalm-readonly */ private AbstractStringContainer $inlineNode; /** @psalm-readonly-allow-private-mutation */ private ?DelimiterInterface $previous = null; /** @psalm-readonly-allow-private-mutation */ private ?DelimiterInterface $next = null; /** @psalm-readonly */ private bool $canOpen; /** @psalm-readonly */ private bool $canClose; /** @psalm-readonly-allow-private-mutation */ private bool $active; /** @psalm-readonly */ private ?int $index = null; public function __construct(string $char, int $numDelims, AbstractStringContainer $node, bool $canOpen, bool $canClose, ?int $index = null) { $this->char = $char; $this->length = $numDelims; $this->originalLength = $numDelims; $this->inlineNode = $node; $this->canOpen = $canOpen; $this->canClose = $canClose; $this->active = true; $this->index = $index; } public function canClose(): bool { return $this->canClose; } public function canOpen(): bool { return $this->canOpen; } public function isActive(): bool { return $this->active; } public function setActive(bool $active): void { $this->active = $active; } public function getChar(): string { return $this->char; } public function getIndex(): ?int { return $this->index; } public function getNext(): ?DelimiterInterface { return $this->next; } public function setNext(?DelimiterInterface $next): void { $this->next = $next; } public function getLength(): int { return $this->length; } public function setLength(int $length): void { $this->length = $length; } public function getOriginalLength(): int { return $this->originalLength; } public function getInlineNode(): AbstractStringContainer { return $this->inlineNode; } public function getPrevious(): ?DelimiterInterface { return $this->previous; } public function setPrevious(?DelimiterInterface $previous): void { $this->previous = $previous; } } commonmark/src/Delimiter/Processor/DelimiterProcessorInterface.php 0000644 00000005417 15021222733 0021547 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter\Processor; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Node\Inline\AbstractStringContainer; /** * Interface for a delimiter processor */ interface DelimiterProcessorInterface { /** * Returns the character that marks the beginning of a delimited node. * * This must not clash with any other processors being added to the environment. */ public function getOpeningCharacter(): string; /** * Returns the character that marks the ending of a delimited node. * * This must not clash with any other processors being added to the environment. * * Note that for a symmetric delimiter such as "*", this is the same as the opening. */ public function getClosingCharacter(): string; /** * Minimum number of delimiter characters that are needed to active this. * * Must be at least 1. */ public function getMinLength(): int; /** * Determine how many (if any) of the delimiter characters should be used. * * This allows implementations to decide how many characters to be used * based on the properties of the delimiter runs. An implementation can also * return 0 when it doesn't want to allow this particular combination of * delimiter runs. * * @param DelimiterInterface $opener The opening delimiter run * @param DelimiterInterface $closer The closing delimiter run */ public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int; /** * Process the matched delimiters, e.g. by wrapping the nodes between opener * and closer in a new node, or appending a new node after the opener. * * Note that removal of the delimiter from the delimiter nodes and detaching * them is done by the caller. * * @param AbstractStringContainer $opener The node that contained the opening delimiter * @param AbstractStringContainer $closer The node that contained the closing delimiter * @param int $delimiterUse The number of delimiters that were used */ public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void; } commonmark/src/Delimiter/Processor/DelimiterProcessorCollection.php 0000644 00000005551 15021222733 0021741 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter\Processor; use League\CommonMark\Exception\InvalidArgumentException; final class DelimiterProcessorCollection implements DelimiterProcessorCollectionInterface { /** * @var array<string,DelimiterProcessorInterface>|DelimiterProcessorInterface[] * * @psalm-readonly-allow-private-mutation */ private array $processorsByChar = []; public function add(DelimiterProcessorInterface $processor): void { $opening = $processor->getOpeningCharacter(); $closing = $processor->getClosingCharacter(); if ($opening === $closing) { $old = $this->processorsByChar[$opening] ?? null; if ($old !== null && $old->getOpeningCharacter() === $old->getClosingCharacter()) { $this->addStaggeredDelimiterProcessorForChar($opening, $old, $processor); } else { $this->addDelimiterProcessorForChar($opening, $processor); } } else { $this->addDelimiterProcessorForChar($opening, $processor); $this->addDelimiterProcessorForChar($closing, $processor); } } public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface { return $this->processorsByChar[$char] ?? null; } /** * @return string[] */ public function getDelimiterCharacters(): array { return \array_keys($this->processorsByChar); } private function addDelimiterProcessorForChar(string $delimiterChar, DelimiterProcessorInterface $processor): void { if (isset($this->processorsByChar[$delimiterChar])) { throw new InvalidArgumentException(\sprintf('Delim processor for character "%s" already exists', $processor->getOpeningCharacter())); } $this->processorsByChar[$delimiterChar] = $processor; } private function addStaggeredDelimiterProcessorForChar(string $opening, DelimiterProcessorInterface $old, DelimiterProcessorInterface $new): void { if ($old instanceof StaggeredDelimiterProcessor) { $s = $old; } else { $s = new StaggeredDelimiterProcessor($opening, $old); } $s->add($new); $this->processorsByChar[$opening] = $s; } public function count(): int { return \count($this->processorsByChar); } } commonmark/src/Delimiter/Processor/DelimiterProcessorCollectionInterface.php 0000644 00000002652 15021222733 0023561 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter\Processor; use League\CommonMark\Exception\InvalidArgumentException; interface DelimiterProcessorCollectionInterface extends \Countable { /** * Add the given delim processor to the collection * * @param DelimiterProcessorInterface $processor The delim processor to add * * @throws InvalidArgumentException Exception will be thrown if attempting to add multiple processors for the same character */ public function add(DelimiterProcessorInterface $processor): void; /** * Returns the delim processor which handles the given character if one exists */ public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface; /** * Returns an array of delimiter characters who have associated processors * * @return string[] */ public function getDelimiterCharacters(): array; } commonmark/src/Delimiter/Processor/StaggeredDelimiterProcessor.php 0000644 00000007023 15021222733 0021547 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter\Processor; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Exception\InvalidArgumentException; use League\CommonMark\Node\Inline\AbstractStringContainer; /** * An implementation of DelimiterProcessorInterface that dispatches all calls to two or more other DelimiterProcessors * depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum * lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no * child is applicable, the one with the largest minimum length is chosen. * * @internal */ final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface { /** @psalm-readonly */ private string $delimiterChar; /** @psalm-readonly-allow-private-mutation */ private int $minLength = 0; /** * @var array<int, DelimiterProcessorInterface>|DelimiterProcessorInterface[] * * @psalm-readonly-allow-private-mutation */ private array $processors = []; // keyed by minLength in reverse order public function __construct(string $char, DelimiterProcessorInterface $processor) { $this->delimiterChar = $char; $this->add($processor); } public function getOpeningCharacter(): string { return $this->delimiterChar; } public function getClosingCharacter(): string { return $this->delimiterChar; } public function getMinLength(): int { return $this->minLength; } /** * Adds the given processor to this staggered delimiter processor * * @throws InvalidArgumentException if attempting to add another processors for the same character and minimum length */ public function add(DelimiterProcessorInterface $processor): void { $len = $processor->getMinLength(); if (isset($this->processors[$len])) { throw new InvalidArgumentException(\sprintf('Cannot add two delimiter processors for char "%s" and minimum length %d', $this->delimiterChar, $len)); } $this->processors[$len] = $processor; \krsort($this->processors); $this->minLength = \min($this->minLength, $len); } public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int { return $this->findProcessor($opener->getLength())->getDelimiterUse($opener, $closer); } public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void { $this->findProcessor($delimiterUse)->process($opener, $closer, $delimiterUse); } private function findProcessor(int $len): DelimiterProcessorInterface { // Find the "longest" processor which can handle this length foreach ($this->processors as $processor) { if ($processor->getMinLength() <= $len) { return $processor; } } // Just use the first one in our list $first = \reset($this->processors); \assert($first instanceof DelimiterProcessorInterface); return $first; } } commonmark/src/Delimiter/DelimiterStack.php 0000644 00000016771 15021222733 0015042 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Delimiter; use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection; use League\CommonMark\Node\Inline\AdjacentTextMerger; final class DelimiterStack { /** @psalm-readonly-allow-private-mutation */ private ?DelimiterInterface $top = null; public function push(DelimiterInterface $newDelimiter): void { $newDelimiter->setPrevious($this->top); if ($this->top !== null) { $this->top->setNext($newDelimiter); } $this->top = $newDelimiter; } private function findEarliest(?DelimiterInterface $stackBottom = null): ?DelimiterInterface { $delimiter = $this->top; while ($delimiter !== null && $delimiter->getPrevious() !== $stackBottom) { $delimiter = $delimiter->getPrevious(); } return $delimiter; } public function removeDelimiter(DelimiterInterface $delimiter): void { if ($delimiter->getPrevious() !== null) { /** @psalm-suppress PossiblyNullReference */ $delimiter->getPrevious()->setNext($delimiter->getNext()); } if ($delimiter->getNext() === null) { // top of stack $this->top = $delimiter->getPrevious(); } else { /** @psalm-suppress PossiblyNullReference */ $delimiter->getNext()->setPrevious($delimiter->getPrevious()); } } private function removeDelimiterAndNode(DelimiterInterface $delimiter): void { $delimiter->getInlineNode()->detach(); $this->removeDelimiter($delimiter); } private function removeDelimitersBetween(DelimiterInterface $opener, DelimiterInterface $closer): void { $delimiter = $closer->getPrevious(); while ($delimiter !== null && $delimiter !== $opener) { $previous = $delimiter->getPrevious(); $this->removeDelimiter($delimiter); $delimiter = $previous; } } public function removeAll(?DelimiterInterface $stackBottom = null): void { while ($this->top && $this->top !== $stackBottom) { $this->removeDelimiter($this->top); } } public function removeEarlierMatches(string $character): void { $opener = $this->top; while ($opener !== null) { if ($opener->getChar() === $character) { $opener->setActive(false); } $opener = $opener->getPrevious(); } } /** * @param string|string[] $characters */ public function searchByCharacter($characters): ?DelimiterInterface { if (! \is_array($characters)) { $characters = [$characters]; } $opener = $this->top; while ($opener !== null) { if (\in_array($opener->getChar(), $characters, true)) { break; } $opener = $opener->getPrevious(); } return $opener; } public function processDelimiters(?DelimiterInterface $stackBottom, DelimiterProcessorCollection $processors): void { $openersBottom = []; // Find first closer above stackBottom $closer = $this->findEarliest($stackBottom); // Move forward, looking for closers, and handling each while ($closer !== null) { $delimiterChar = $closer->getChar(); $delimiterProcessor = $processors->getDelimiterProcessor($delimiterChar); if (! $closer->canClose() || $delimiterProcessor === null) { $closer = $closer->getNext(); continue; } $openingDelimiterChar = $delimiterProcessor->getOpeningCharacter(); $useDelims = 0; $openerFound = false; $potentialOpenerFound = false; $opener = $closer->getPrevious(); while ($opener !== null && $opener !== $stackBottom && $opener !== ($openersBottom[$delimiterChar] ?? null)) { if ($opener->canOpen() && $opener->getChar() === $openingDelimiterChar) { $potentialOpenerFound = true; $useDelims = $delimiterProcessor->getDelimiterUse($opener, $closer); if ($useDelims > 0) { $openerFound = true; break; } } $opener = $opener->getPrevious(); } if (! $openerFound) { if (! $potentialOpenerFound) { // Only do this when we didn't even have a potential // opener (one that matches the character and can open). // If an opener was rejected because of the number of // delimiters (e.g. because of the "multiple of 3" // Set lower bound for future searches for openersrule), // we want to consider it next time because the number // of delimiters can change as we continue processing. $openersBottom[$delimiterChar] = $closer->getPrevious(); if (! $closer->canOpen()) { // We can remove a closer that can't be an opener, // once we've seen there's no matching opener. $this->removeDelimiter($closer); } } $closer = $closer->getNext(); continue; } \assert($opener !== null); $openerNode = $opener->getInlineNode(); $closerNode = $closer->getInlineNode(); // Remove number of used delimiters from stack and inline nodes. $opener->setLength($opener->getLength() - $useDelims); $closer->setLength($closer->getLength() - $useDelims); $openerNode->setLiteral(\substr($openerNode->getLiteral(), 0, -$useDelims)); $closerNode->setLiteral(\substr($closerNode->getLiteral(), 0, -$useDelims)); $this->removeDelimitersBetween($opener, $closer); // The delimiter processor can re-parent the nodes between opener and closer, // so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves. AdjacentTextMerger::mergeTextNodesBetweenExclusive($openerNode, $closerNode); $delimiterProcessor->process($openerNode, $closerNode, $useDelims); // No delimiter characters left to process, so we can remove delimiter and the now empty node. if ($opener->getLength() === 0) { $this->removeDelimiterAndNode($opener); } // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($closer->getLength() === 0) { $next = $closer->getNext(); $this->removeDelimiterAndNode($closer); $closer = $next; } } // Remove all delimiters $this->removeAll($stackBottom); } } commonmark/src/Delimiter/DelimiterParser.php 0000644 00000007160 15021222733 0015221 0 ustar 00 <?php declare(strict_types=1); namespace League\CommonMark\Delimiter; use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\RegexHelper; /** * Delimiter parsing is implemented as an Inline Parser with the lowest-possible priority * * @internal */ final class DelimiterParser implements InlineParserInterface { private DelimiterProcessorCollection $collection; public function __construct(DelimiterProcessorCollection $collection) { $this->collection = $collection; } public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::oneOf(...$this->collection->getDelimiterCharacters()); } public function parse(InlineParserContext $inlineContext): bool { $character = $inlineContext->getFullMatch(); $numDelims = 0; $cursor = $inlineContext->getCursor(); $processor = $this->collection->getDelimiterProcessor($character); \assert($processor !== null); // Delimiter processor should never be null here $charBefore = $cursor->peek(-1); if ($charBefore === null) { $charBefore = "\n"; } while ($cursor->peek($numDelims) === $character) { ++$numDelims; } if ($numDelims < $processor->getMinLength()) { return false; } $cursor->advanceBy($numDelims); $charAfter = $cursor->getCurrentCharacter(); if ($charAfter === null) { $charAfter = "\n"; } [$canOpen, $canClose] = self::determineCanOpenOrClose($charBefore, $charAfter, $character, $processor); $node = new Text(\str_repeat($character, $numDelims), [ 'delim' => true, ]); $inlineContext->getContainer()->appendChild($node); // Add entry to stack to this opener if ($canOpen || $canClose) { $delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose); $inlineContext->getDelimiterStack()->push($delimiter); } return true; } /** * @return bool[] */ private static function determineCanOpenOrClose(string $charBefore, string $charAfter, string $character, DelimiterProcessorInterface $delimiterProcessor): array { $afterIsWhitespace = \preg_match(RegexHelper::REGEX_UNICODE_WHITESPACE_CHAR, $charAfter); $afterIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charAfter); $beforeIsWhitespace = \preg_match(RegexHelper::REGEX_UNICODE_WHITESPACE_CHAR, $charBefore); $beforeIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charBefore); $leftFlanking = ! $afterIsWhitespace && (! $afterIsPunctuation || $beforeIsWhitespace || $beforeIsPunctuation); $rightFlanking = ! $beforeIsWhitespace && (! $beforeIsPunctuation || $afterIsWhitespace || $afterIsPunctuation); if ($character === '_') { $canOpen = $leftFlanking && (! $rightFlanking || $beforeIsPunctuation); $canClose = $rightFlanking && (! $leftFlanking || $afterIsPunctuation); } else { $canOpen = $leftFlanking && $character === $delimiterProcessor->getOpeningCharacter(); $canClose = $rightFlanking && $character === $delimiterProcessor->getClosingCharacter(); } return [$canOpen, $canClose]; } } commonmark/src/Exception/InvalidArgumentException.php 0000644 00000000634 15021222733 0017115 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; class InvalidArgumentException extends \InvalidArgumentException implements CommonMarkException { } commonmark/src/Exception/IOException.php 0000644 00000000607 15021222733 0014333 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; class IOException extends \RuntimeException implements CommonMarkException { } commonmark/src/Exception/MissingDependencyException.php 0000644 00000000626 15021222733 0017435 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; class MissingDependencyException extends \RuntimeException implements CommonMarkException { } commonmark/src/Exception/CommonMarkException.php 0000644 00000000664 15021222733 0016072 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; /** * Marker interface for all exceptions thrown by this library. */ interface CommonMarkException extends \Throwable { } commonmark/src/Exception/AlreadyInitializedException.php 0000644 00000000624 15021222733 0017572 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; class AlreadyInitializedException extends LogicException implements CommonMarkException { } commonmark/src/Exception/LogicException.php 0000644 00000000610 15021222733 0015053 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; class LogicException extends \LogicException implements CommonMarkException { } commonmark/src/Exception/UnexpectedEncodingException.php 0000644 00000000635 15021222733 0017600 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Exception; final class UnexpectedEncodingException extends \RuntimeException implements CommonMarkException { } commonmark/src/Normalizer/UniqueSlugNormalizer.php 0000644 00000002734 15021222733 0016500 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Normalizer; // phpcs:disable Squiz.Strings.DoubleQuoteUsage.ContainsVar final class UniqueSlugNormalizer implements UniqueSlugNormalizerInterface { private TextNormalizerInterface $innerNormalizer; /** @var array<string, bool> */ private array $alreadyUsed = []; public function __construct(TextNormalizerInterface $innerNormalizer) { $this->innerNormalizer = $innerNormalizer; } public function clearHistory(): void { $this->alreadyUsed = []; } /** * {@inheritDoc} * * @psalm-allow-private-mutation */ public function normalize(string $text, array $context = []): string { $normalized = $this->innerNormalizer->normalize($text, $context); // If it's not unique, add an incremental number to the end until we get a unique version if (\array_key_exists($normalized, $this->alreadyUsed)) { $suffix = 0; do { ++$suffix; } while (\array_key_exists("$normalized-$suffix", $this->alreadyUsed)); $normalized = "$normalized-$suffix"; } $this->alreadyUsed[$normalized] = true; return $normalized; } } commonmark/src/Normalizer/UniqueSlugNormalizerInterface.php 0000644 00000001322 15021222733 0020311 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Normalizer; interface UniqueSlugNormalizerInterface extends TextNormalizerInterface { public const DISABLED = false; public const PER_ENVIRONMENT = 'environment'; public const PER_DOCUMENT = 'document'; /** * Called by the Environment whenever the configured scope changes * * Currently, this will only be called PER_DOCUMENT. */ public function clearHistory(): void; } commonmark/src/Normalizer/TextNormalizerInterface.php 0000644 00000002017 15021222733 0017136 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Normalizer; /** * Creates a normalized version of the given input text */ interface TextNormalizerInterface { /** * @param string $text The text to normalize * @param array<string, mixed> $context Additional context about the text being normalized (optional) * * $context may include (but is not required to include) the following: * - `prefix` - A string prefix to prepend to each normalized result * - `length` - The requested maximum length * - `node` - The node we're normalizing text for * * Implementations do not have to use or respect any information within that $context */ public function normalize(string $text, array $context = []): string; } commonmark/src/Normalizer/SlugNormalizer.php 0000644 00000003232 15021222733 0015303 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Normalizer; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; /** * Creates URL-friendly strings based on the given string input */ final class SlugNormalizer implements TextNormalizerInterface, ConfigurationAwareInterface { /** @psalm-allow-private-mutation */ private int $defaultMaxLength = 255; public function setConfiguration(ConfigurationInterface $configuration): void { $this->defaultMaxLength = $configuration->get('slug_normalizer/max_length'); } /** * {@inheritDoc} * * @psalm-immutable */ public function normalize(string $text, array $context = []): string { // Add any requested prefix $slug = ($context['prefix'] ?? '') . $text; // Trim whitespace $slug = \trim($slug); // Convert to lowercase $slug = \mb_strtolower($slug, 'UTF-8'); // Try replacing whitespace with a dash $slug = \preg_replace('/\s+/u', '-', $slug) ?? $slug; // Try removing characters other than letters, numbers, and marks. $slug = \preg_replace('/[^\p{L}\p{Nd}\p{Nl}\p{M}-]+/u', '', $slug) ?? $slug; // Trim to requested length if given if ($length = $context['length'] ?? $this->defaultMaxLength) { $slug = \mb_substr($slug, 0, $length, 'UTF-8'); } return $slug; } } commonmark/src/Normalizer/TextNormalizer.php 0000644 00000001716 15021222733 0015322 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Normalizer; /*** * Normalize text input using the steps given by the CommonMark spec to normalize labels * * @see https://spec.commonmark.org/0.29/#matches * * @psalm-immutable */ final class TextNormalizer implements TextNormalizerInterface { /** * {@inheritDoc} * * @psalm-pure */ public function normalize(string $text, array $context = []): string { // Collapse internal whitespace to single space and remove // leading/trailing whitespace $text = \preg_replace('/[ \t\r\n]+/', ' ', \trim($text)); \assert(\is_string($text)); return \mb_convert_case($text, \MB_CASE_FOLD, 'UTF-8'); } } commonmark/src/MarkdownConverterInterface.php 0000644 00000001516 15021222733 0015502 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Output\RenderedContentInterface; /** * Interface for a service which converts Markdown to HTML. * * @deprecated since 2.2; use {@link ConverterInterface} instead */ interface MarkdownConverterInterface { /** * Converts Markdown to HTML. * * @deprecated since 2.2; use {@link ConverterInterface::convert()} instead * * @throws CommonMarkException */ public function convertToHtml(string $markdown): RenderedContentInterface; } commonmark/src/Parser/CursorState.php 0000644 00000002150 15021222733 0013714 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; /** * Encapsulates the current state of a cursor in case you need to rollback later. * * WARNING: Do not attempt to use this class for ANYTHING except for * type hinting and passing this object back into restoreState(). * The constructor, methods, and inner contents may change in any * future release without warning! * * @internal * * @psalm-immutable */ final class CursorState { /** * @var array<int, mixed> * * @psalm-readonly */ private array $state; /** * @internal * * @param array<int, mixed> $state */ public function __construct(array $state) { $this->state = $state; } /** * @internal * * @return array<int, mixed> */ public function toArray(): array { return $this->state; } } commonmark/src/Parser/MarkdownParserInterface.php 0000644 00000001043 15021222733 0016216 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Node\Block\Document; interface MarkdownParserInterface { /** * @throws CommonMarkException */ public function parse(string $input): Document; } commonmark/src/Parser/ParserLogicException.php 0000644 00000000701 15021222733 0015527 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\CommonMarkException; class ParserLogicException extends \LogicException implements CommonMarkException { } commonmark/src/Parser/Block/ParagraphParser.php 0000644 00000004274 15021222733 0015563 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\InlineParserEngineInterface; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceParser; final class ParagraphParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface { /** @psalm-readonly */ private Paragraph $block; /** @psalm-readonly */ private ReferenceParser $referenceParser; public function __construct() { $this->block = new Paragraph(); $this->referenceParser = new ReferenceParser(); } public function canHaveLazyContinuationLines(): bool { return true; } public function getBlock(): Paragraph { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isBlank()) { return BlockContinue::none(); } return BlockContinue::at($cursor); } public function addLine(string $line): void { $this->referenceParser->parse($line); } public function closeBlock(): void { $this->block->onlyContainsLinkReferenceDefinitions = $this->referenceParser->hasReferences() && $this->referenceParser->getParagraphContent() === ''; } public function parseInlines(InlineParserEngineInterface $inlineParser): void { $content = $this->getContentString(); if ($content !== '') { $inlineParser->parse($content, $this->block); } } public function getContentString(): string { return $this->referenceParser->getParagraphContent(); } /** * @return ReferenceInterface[] */ public function getReferences(): iterable { return $this->referenceParser->getReferences(); } } commonmark/src/Parser/Block/BlockStart.php 0000644 00000005241 15021222733 0014544 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\CursorState; /** * Result object for starting parsing of a block; see static methods for constructors */ final class BlockStart { /** * @var BlockContinueParserInterface[] * * @psalm-readonly */ private array $blockParsers; /** @psalm-readonly-allow-private-mutation */ private ?CursorState $cursorState = null; /** @psalm-readonly-allow-private-mutation */ private bool $replaceActiveBlockParser = false; private bool $isAborting = false; private function __construct(BlockContinueParserInterface ...$blockParsers) { $this->blockParsers = $blockParsers; } /** * @return BlockContinueParserInterface[] */ public function getBlockParsers(): iterable { return $this->blockParsers; } public function getCursorState(): ?CursorState { return $this->cursorState; } public function isReplaceActiveBlockParser(): bool { return $this->replaceActiveBlockParser; } /** * @internal */ public function isAborting(): bool { return $this->isAborting; } /** * Signal that we want to parse at the given cursor position * * @return $this */ public function at(Cursor $cursor): self { $this->cursorState = $cursor->saveState(); return $this; } /** * Signal that we want to replace the active block parser with this one * * @return $this */ public function replaceActiveBlockParser(): self { $this->replaceActiveBlockParser = true; return $this; } /** * Signal that we cannot parse whatever is here * * @return null */ public static function none(): ?self { return null; } /** * Signal that we'd like to register the given parser(s) so they can parse the current block */ public static function of(BlockContinueParserInterface ...$blockParsers): self { return new self(...$blockParsers); } /** * Signal that the block parsing process should be aborted (no other block starts should be checked) * * @internal */ public static function abort(): self { $ret = new self(); $ret->isAborting = true; return $ret; } } commonmark/src/Parser/Block/AbstractBlockContinueParser.php 0000644 00000001663 15021222733 0020100 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; /** * Base class for a block parser * * Slightly more convenient to extend from vs. implementing the interface */ abstract class AbstractBlockContinueParser implements BlockContinueParserInterface { public function isContainer(): bool { return false; } public function canHaveLazyContinuationLines(): bool { return false; } public function canContain(AbstractBlock $childBlock): bool { return false; } public function addLine(string $line): void { } public function closeBlock(): void { } } commonmark/src/Parser/Block/BlockStartParserInterface.php 0000644 00000002061 15021222733 0017537 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; /** * Interface for a block parser which identifies block starts. */ interface BlockStartParserInterface { /** * Check whether we should handle the block at the current position * * @param Cursor $cursor A cloned copy of the cursor at the current parsing location * @param MarkdownParserStateInterface $parserState Additional information about the state of the Markdown parser * * @return BlockStart|null The BlockStart that has been identified, or null if the block doesn't match here */ public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart; } commonmark/src/Parser/Block/DocumentBlockParser.php 0000644 00000004465 15021222733 0016411 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Cursor; use League\CommonMark\Reference\ReferenceMapInterface; /** * Parser implementation which ensures everything is added to the root-level Document */ final class DocumentBlockParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private Document $document; public function __construct(ReferenceMapInterface $referenceMap) { $this->document = new Document($referenceMap); } public function getBlock(): Document { return $this->document; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::at($cursor); } public function closeBlock(): void { $this->removeLinkReferenceDefinitions(); } private function removeLinkReferenceDefinitions(): void { $emptyNodes = []; $walker = $this->document->walker(); while ($event = $walker->next()) { $node = $event->getNode(); // TODO for v3: It would be great if we could find an alternate way to identify such paragraphs. // Unfortunately, we can't simply check for empty paragraphs here because inlines haven't been processed yet, // meaning all paragraphs will appear blank here, and we don't have a way to check the status of the reference parser // which is attached to the (already-closed) paragraph parser. if ($event->isEntering() && $node instanceof Paragraph && $node->onlyContainsLinkReferenceDefinitions) { $emptyNodes[] = $node; } } foreach ($emptyNodes as $node) { $node->detach(); } } } commonmark/src/Parser/Block/BlockContinue.php 0000644 00000003037 15021222733 0015234 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\CursorState; /** * Result object for continuing parsing of a block; see static methods for constructors. * * @psalm-immutable */ final class BlockContinue { /** @psalm-readonly */ private ?CursorState $cursorState = null; /** @psalm-readonly */ private bool $finalize; private function __construct(?CursorState $cursorState = null, bool $finalize = false) { $this->cursorState = $cursorState; $this->finalize = $finalize; } public function getCursorState(): ?CursorState { return $this->cursorState; } public function isFinalize(): bool { return $this->finalize; } /** * Signal that we cannot continue here * * @return null */ public static function none(): ?self { return null; } /** * Signal that we're continuing at the given position */ public static function at(Cursor $cursor): self { return new self($cursor->saveState(), false); } /** * Signal that we want to finalize and close the block */ public static function finished(): self { return new self(null, true); } } commonmark/src/Parser/Block/BlockContinueParserWithInlinesInterface.php 0000644 00000001151 15021222733 0022403 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\InlineParserEngineInterface; interface BlockContinueParserWithInlinesInterface extends BlockContinueParserInterface { /** * Parse any inlines inside of the current block */ public function parseInlines(InlineParserEngineInterface $inlineParser): void; } commonmark/src/Parser/Block/BlockContinueParserInterface.php 0000644 00000003500 15021222733 0020225 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Cursor; /** * Interface for a block continuation parser * * A block continue parser can only handle a single block instance. The current block being parsed is stored within this parser and * can be returned once parsing has completed. If you need to parse multiple block continuations, instantiate a new parser for each one. */ interface BlockContinueParserInterface { /** * Return the current block being parsed by this parser */ public function getBlock(): AbstractBlock; /** * Return whether we are parsing a container block */ public function isContainer(): bool; /** * Return whether we are interested in possibly lazily parsing any subsequent lines */ public function canHaveLazyContinuationLines(): bool; /** * Determine whether the current block being parsed can contain the given child block */ public function canContain(AbstractBlock $childBlock): bool; /** * Attempt to parse the given line */ public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue; /** * Add the given line of text to the current block */ public function addLine(string $line): void; /** * Close and finalize the current block */ public function closeBlock(): void; } commonmark/src/Parser/Block/SkipLinesStartingWithLettersParser.php 0000644 00000003216 15021222733 0021465 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Block; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; /** * @internal * * This "parser" is actually a performance optimization. * * Most lines in a typical Markdown document probably won't match a block start. This is especially true for lines starting * with letters - nothing in the core CommonMark spec or our supported extensions will match those lines as blocks. Therefore, * if we can identify those lines and skip block start parsing, we can optimize performance by ~10%. * * Previously this optimization was hard-coded in the MarkdownParser but did not allow users to override this behavior. * By implementing this optimization as a block parser instead, users wanting custom blocks starting with letters * can instead register their block parser with a higher priority to ensure their parser is always called first. */ final class SkipLinesStartingWithLettersParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if (! $cursor->isIndented() && RegexHelper::isLetter($cursor->getNextNonSpaceCharacter())) { $cursor->advanceToNextNonSpaceOrTab(); return BlockStart::abort(); } return BlockStart::none(); } } commonmark/src/Parser/Cursor.php 0000644 00000033673 15021222733 0012731 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Exception\UnexpectedEncodingException; class Cursor { public const INDENT_LEVEL = 4; /** @psalm-readonly */ private string $line; /** @psalm-readonly */ private int $length; /** * @var int * * It's possible for this to be 1 char past the end, meaning we've parsed all chars and have * reached the end. In this state, any character-returning method MUST return null. */ private int $currentPosition = 0; private int $column = 0; private int $indent = 0; private int $previousPosition = 0; private ?int $nextNonSpaceCache = null; private bool $partiallyConsumedTab = false; /** * @var int|false * * @psalm-readonly */ private $lastTabPosition; /** @psalm-readonly */ private bool $isMultibyte; /** @var array<int, string> */ private array $charCache = []; /** * @param string $line The line being parsed (ASCII or UTF-8) */ public function __construct(string $line) { if (! \mb_check_encoding($line, 'UTF-8')) { throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected'); } $this->line = $line; $this->length = \mb_strlen($line, 'UTF-8') ?: 0; $this->isMultibyte = $this->length !== \strlen($line); $this->lastTabPosition = $this->isMultibyte ? \mb_strrpos($line, "\t", 0, 'UTF-8') : \strrpos($line, "\t"); } /** * Returns the position of the next character which is not a space (or tab) */ public function getNextNonSpacePosition(): int { if ($this->nextNonSpaceCache !== null) { return $this->nextNonSpaceCache; } if ($this->currentPosition >= $this->length) { return $this->length; } $cols = $this->column; for ($i = $this->currentPosition; $i < $this->length; $i++) { // This if-else was copied out of getCharacter() for performance reasons if ($this->isMultibyte) { $c = $this->charCache[$i] ??= \mb_substr($this->line, $i, 1, 'UTF-8'); } else { $c = $this->line[$i]; } if ($c === ' ') { $cols++; } elseif ($c === "\t") { $cols += 4 - ($cols % 4); } else { break; } } $this->indent = $cols - $this->column; return $this->nextNonSpaceCache = $i; } /** * Returns the next character which isn't a space (or tab) */ public function getNextNonSpaceCharacter(): ?string { $index = $this->getNextNonSpacePosition(); if ($index >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$index] ??= \mb_substr($this->line, $index, 1, 'UTF-8'); } return $this->line[$index]; } /** * Calculates the current indent (number of spaces after current position) */ public function getIndent(): int { if ($this->nextNonSpaceCache === null) { $this->getNextNonSpacePosition(); } return $this->indent; } /** * Whether the cursor is indented to INDENT_LEVEL */ public function isIndented(): bool { if ($this->nextNonSpaceCache === null) { $this->getNextNonSpacePosition(); } return $this->indent >= self::INDENT_LEVEL; } public function getCharacter(?int $index = null): ?string { if ($index === null) { $index = $this->currentPosition; } // Index out-of-bounds, or we're at the end if ($index < 0 || $index >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$index] ??= \mb_substr($this->line, $index, 1, 'UTF-8'); } return $this->line[$index]; } /** * Slightly-optimized version of getCurrent(null) */ public function getCurrentCharacter(): ?string { if ($this->currentPosition >= $this->length) { return null; } if ($this->isMultibyte) { return $this->charCache[$this->currentPosition] ??= \mb_substr($this->line, $this->currentPosition, 1, 'UTF-8'); } return $this->line[$this->currentPosition]; } /** * Returns the next character (or null, if none) without advancing forwards */ public function peek(int $offset = 1): ?string { return $this->getCharacter($this->currentPosition + $offset); } /** * Whether the remainder is blank */ public function isBlank(): bool { return $this->nextNonSpaceCache === $this->length || $this->getNextNonSpacePosition() === $this->length; } /** * Move the cursor forwards */ public function advance(): void { $this->advanceBy(1); } /** * Move the cursor forwards * * @param int $characters Number of characters to advance by * @param bool $advanceByColumns Whether to advance by columns instead of spaces */ public function advanceBy(int $characters, bool $advanceByColumns = false): void { $this->previousPosition = $this->currentPosition; $this->nextNonSpaceCache = null; if ($this->currentPosition >= $this->length || $characters === 0) { return; } // Optimization to avoid tab handling logic if we have no tabs if ($this->lastTabPosition === false || $this->currentPosition > $this->lastTabPosition) { $length = \min($characters, $this->length - $this->currentPosition); $this->partiallyConsumedTab = false; $this->currentPosition += $length; $this->column += $length; return; } $nextFewChars = $this->isMultibyte ? \mb_substr($this->line, $this->currentPosition, $characters, 'UTF-8') : \substr($this->line, $this->currentPosition, $characters); if ($characters === 1) { $asArray = [$nextFewChars]; } elseif ($this->isMultibyte) { /** @var string[] $asArray */ $asArray = \mb_str_split($nextFewChars, 1, 'UTF-8'); } else { $asArray = \str_split($nextFewChars); } foreach ($asArray as $c) { if ($c === "\t") { $charsToTab = 4 - ($this->column % 4); if ($advanceByColumns) { $this->partiallyConsumedTab = $charsToTab > $characters; $charsToAdvance = $charsToTab > $characters ? $characters : $charsToTab; $this->column += $charsToAdvance; $this->currentPosition += $this->partiallyConsumedTab ? 0 : 1; $characters -= $charsToAdvance; } else { $this->partiallyConsumedTab = false; $this->column += $charsToTab; $this->currentPosition++; $characters--; } } else { $this->partiallyConsumedTab = false; $this->currentPosition++; $this->column++; $characters--; } if ($characters <= 0) { break; } } } /** * Advances the cursor by a single space or tab, if present */ public function advanceBySpaceOrTab(): bool { $character = $this->getCurrentCharacter(); if ($character === ' ' || $character === "\t") { $this->advanceBy(1, true); return true; } return false; } /** * Parse zero or more space/tab characters * * @return int Number of positions moved */ public function advanceToNextNonSpaceOrTab(): int { $newPosition = $this->nextNonSpaceCache ?? $this->getNextNonSpacePosition(); if ($newPosition === $this->currentPosition) { return 0; } $this->advanceBy($newPosition - $this->currentPosition); $this->partiallyConsumedTab = false; // We've just advanced to where that non-space is, // so any subsequent calls to find the next one will // always return the current position. $this->nextNonSpaceCache = $this->currentPosition; $this->indent = 0; return $this->currentPosition - $this->previousPosition; } /** * Parse zero or more space characters, including at most one newline. * * Tab characters are not parsed with this function. * * @return int Number of positions moved */ public function advanceToNextNonSpaceOrNewline(): int { $remainder = $this->getRemainder(); // Optimization: Avoid the regex if we know there are no spaces or newlines if ($remainder === '' || ($remainder[0] !== ' ' && $remainder[0] !== "\n")) { $this->previousPosition = $this->currentPosition; return 0; } $matches = []; \preg_match('/^ *(?:\n *)?/', $remainder, $matches, \PREG_OFFSET_CAPTURE); // [0][0] contains the matched text // [0][1] contains the index of that match $increment = $matches[0][1] + \strlen($matches[0][0]); $this->advanceBy($increment); return $this->currentPosition - $this->previousPosition; } /** * Move the position to the very end of the line * * @return int The number of characters moved */ public function advanceToEnd(): int { $this->previousPosition = $this->currentPosition; $this->nextNonSpaceCache = null; $this->currentPosition = $this->length; return $this->currentPosition - $this->previousPosition; } public function getRemainder(): string { if ($this->currentPosition >= $this->length) { return ''; } $prefix = ''; $position = $this->currentPosition; if ($this->partiallyConsumedTab) { $position++; $charsToTab = 4 - ($this->column % 4); $prefix = \str_repeat(' ', $charsToTab); } $subString = $this->isMultibyte ? \mb_substr($this->line, $position, null, 'UTF-8') : \substr($this->line, $position); return $prefix . $subString; } public function getLine(): string { return $this->line; } public function isAtEnd(): bool { return $this->currentPosition >= $this->length; } /** * Try to match a regular expression * * Returns the matching text and advances to the end of that match * * @psalm-param non-empty-string $regex */ public function match(string $regex): ?string { $subject = $this->getRemainder(); if (! \preg_match($regex, $subject, $matches, \PREG_OFFSET_CAPTURE)) { return null; } // $matches[0][0] contains the matched text // $matches[0][1] contains the index of that match if ($this->isMultibyte) { // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying $offset = \mb_strlen(\substr($subject, 0, $matches[0][1]), 'UTF-8'); $matchLength = \mb_strlen($matches[0][0], 'UTF-8'); } else { $offset = $matches[0][1]; $matchLength = \strlen($matches[0][0]); } // [0][0] contains the matched text // [0][1] contains the index of that match $this->advanceBy($offset + $matchLength); return $matches[0][0]; } /** * Encapsulates the current state of this cursor in case you need to rollback later. * * WARNING: Do not parse or use the return value for ANYTHING except for * passing it back into restoreState(), as the number of values and their * contents may change in any future release without warning. */ public function saveState(): CursorState { return new CursorState([ $this->currentPosition, $this->previousPosition, $this->nextNonSpaceCache, $this->indent, $this->column, $this->partiallyConsumedTab, ]); } /** * Restore the cursor to a previous state. * * Pass in the value previously obtained by calling saveState(). */ public function restoreState(CursorState $state): void { [ $this->currentPosition, $this->previousPosition, $this->nextNonSpaceCache, $this->indent, $this->column, $this->partiallyConsumedTab, ] = $state->toArray(); } public function getPosition(): int { return $this->currentPosition; } public function getPreviousText(): string { if ($this->isMultibyte) { return \mb_substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition, 'UTF-8'); } return \substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition); } public function getSubstring(int $start, ?int $length = null): string { if ($this->isMultibyte) { return \mb_substr($this->line, $start, $length, 'UTF-8'); } if ($length !== null) { return \substr($this->line, $start, $length); } return \substr($this->line, $start); } public function getColumn(): int { return $this->column; } } commonmark/src/Parser/InlineParserEngineInterface.php 0000644 00000001203 15021222733 0016776 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Node\Block\AbstractBlock; /** * Parser for inline content (text, links, emphasized text, etc). */ interface InlineParserEngineInterface { /** * Parse the given contents as inlines and insert them into the given block */ public function parse(string $contents, AbstractBlock $block): void; } commonmark/src/Parser/MarkdownParserStateInterface.php 0000644 00000002064 15021222733 0017223 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Parser\Block\BlockContinueParserInterface; interface MarkdownParserStateInterface { /** * Returns the deepest open block parser */ public function getActiveBlockParser(): BlockContinueParserInterface; /** * Open block parser that was last matched during the continue phase. This is different from the currently active * block parser, as an unmatched block is only closed when a new block is started. */ public function getLastMatchedBlockParser(): BlockContinueParserInterface; /** * Returns the current content of the paragraph if the matched block is a paragraph. The content can be multiple * lines separated by newlines. */ public function getParagraphContent(): ?string; } commonmark/src/Parser/InlineParserContext.php 0000644 00000005466 15021222733 0015413 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Delimiter\DelimiterStack; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Reference\ReferenceMapInterface; final class InlineParserContext { /** @psalm-readonly */ private AbstractBlock $container; /** @psalm-readonly */ private ReferenceMapInterface $referenceMap; /** @psalm-readonly */ private Cursor $cursor; /** @psalm-readonly */ private DelimiterStack $delimiterStack; /** * @var string[] * @psalm-var non-empty-array<string> * * @psalm-readonly-allow-private-mutation */ private array $matches; public function __construct(Cursor $contents, AbstractBlock $container, ReferenceMapInterface $referenceMap) { $this->referenceMap = $referenceMap; $this->container = $container; $this->cursor = $contents; $this->delimiterStack = new DelimiterStack(); } public function getContainer(): AbstractBlock { return $this->container; } public function getReferenceMap(): ReferenceMapInterface { return $this->referenceMap; } public function getCursor(): Cursor { return $this->cursor; } public function getDelimiterStack(): DelimiterStack { return $this->delimiterStack; } /** * @return string The full text that matched the InlineParserMatch definition */ public function getFullMatch(): string { return $this->matches[0]; } /** * @return int The length of the full match (in characters, not bytes) */ public function getFullMatchLength(): int { return \mb_strlen($this->matches[0], 'UTF-8'); } /** * @return string[] Similar to preg_match(), index 0 will contain the full match, and any other array elements will be captured sub-matches * * @psalm-return non-empty-array<string> */ public function getMatches(): array { return $this->matches; } /** * @return string[] */ public function getSubMatches(): array { return \array_slice($this->matches, 1); } /** * @param string[] $matches * * @psalm-param non-empty-array<string> $matches */ public function withMatches(array $matches): InlineParserContext { $ctx = clone $this; $ctx->matches = $matches; return $ctx; } } commonmark/src/Parser/MarkdownParserState.php 0000644 00000003140 15021222733 0015376 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\ParagraphParser; /** * @internal You should rely on the interface instead */ final class MarkdownParserState implements MarkdownParserStateInterface { /** @psalm-readonly */ private BlockContinueParserInterface $activeBlockParser; /** @psalm-readonly */ private BlockContinueParserInterface $lastMatchedBlockParser; public function __construct(BlockContinueParserInterface $activeBlockParser, BlockContinueParserInterface $lastMatchedBlockParser) { $this->activeBlockParser = $activeBlockParser; $this->lastMatchedBlockParser = $lastMatchedBlockParser; } public function getActiveBlockParser(): BlockContinueParserInterface { return $this->activeBlockParser; } public function getLastMatchedBlockParser(): BlockContinueParserInterface { return $this->lastMatchedBlockParser; } public function getParagraphContent(): ?string { if (! $this->lastMatchedBlockParser instanceof ParagraphParser) { return null; } $paragraphParser = $this->lastMatchedBlockParser; $content = $paragraphParser->getContentString(); return $content === '' ? null : $content; } } commonmark/src/Parser/Inline/NewlineParser.php 0000644 00000003054 15021222733 0015436 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\InlineParserContext; final class NewlineParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('\\n'); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy(1); // Check previous inline for trailing spaces $spaces = 0; $lastInline = $inlineContext->getContainer()->lastChild(); if ($lastInline instanceof Text) { $trimmed = \rtrim($lastInline->getLiteral(), ' '); $spaces = \strlen($lastInline->getLiteral()) - \strlen($trimmed); if ($spaces) { $lastInline->setLiteral($trimmed); } } if ($spaces >= 2) { $inlineContext->getContainer()->appendChild(new Newline(Newline::HARDBREAK)); } else { $inlineContext->getContainer()->appendChild(new Newline(Newline::SOFTBREAK)); } return true; } } commonmark/src/Parser/Inline/InlineParserInterface.php 0000644 00000001026 15021222733 0017071 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Parser\InlineParserContext; interface InlineParserInterface { public function getMatchDefinition(): InlineParserMatch; public function parse(InlineParserContext $inlineContext): bool; } commonmark/src/Parser/Inline/InlineParserMatch.php 0000644 00000004272 15021222733 0016233 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser\Inline; use League\CommonMark\Exception\InvalidArgumentException; final class InlineParserMatch { private string $regex; private bool $caseSensitive; private function __construct(string $regex, bool $caseSensitive = false) { $this->regex = $regex; $this->caseSensitive = $caseSensitive; } public function caseSensitive(): self { $this->caseSensitive = true; return $this; } /** * @internal * * @psalm-return non-empty-string */ public function getRegex(): string { return '/' . $this->regex . '/' . ($this->caseSensitive ? '' : 'i'); } /** * Match the given string (case-insensitive) */ public static function string(string $str): self { return new self(\preg_quote($str, '/')); } /** * Match any of the given strings (case-insensitive) */ public static function oneOf(string ...$str): self { return new self(\implode('|', \array_map(static fn (string $str): string => \preg_quote($str, '/'), $str))); } /** * Match a partial regular expression without starting/ending delimiters, anchors, or flags */ public static function regex(string $regex): self { return new self($regex); } public static function join(self ...$definitions): self { $regex = ''; $caseSensitive = null; foreach ($definitions as $definition) { $regex .= '(' . $definition->regex . ')'; if ($caseSensitive === null) { $caseSensitive = $definition->caseSensitive; } elseif ($caseSensitive !== $definition->caseSensitive) { throw new InvalidArgumentException('Case-sensitive and case-insensitive definitions cannot be combined'); } } return new self($regex, $caseSensitive ?? false); } } commonmark/src/Parser/MarkdownParser.php 0000644 00000030237 15021222733 0014404 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional code based on commonmark-java (https://github.com/commonmark/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Event\DocumentPreParsedEvent; use League\CommonMark\Exception\CommonMarkException; use League\CommonMark\Input\MarkdownInput; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Block\DocumentBlockParser; use League\CommonMark\Parser\Block\ParagraphParser; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceMap; final class MarkdownParser implements MarkdownParserInterface { /** @psalm-readonly */ private EnvironmentInterface $environment; /** @psalm-readonly-allow-private-mutation */ private int $maxNestingLevel; /** @psalm-readonly-allow-private-mutation */ private ReferenceMap $referenceMap; /** @psalm-readonly-allow-private-mutation */ private int $lineNumber = 0; /** @psalm-readonly-allow-private-mutation */ private Cursor $cursor; /** * @var array<int, BlockContinueParserInterface> * * @psalm-readonly-allow-private-mutation */ private array $activeBlockParsers = []; /** * @var array<int, BlockContinueParserWithInlinesInterface> * * @psalm-readonly-allow-private-mutation */ private array $closedBlockParsers = []; public function __construct(EnvironmentInterface $environment) { $this->environment = $environment; } private function initialize(): void { $this->referenceMap = new ReferenceMap(); $this->lineNumber = 0; $this->activeBlockParsers = []; $this->closedBlockParsers = []; $this->maxNestingLevel = $this->environment->getConfiguration()->get('max_nesting_level'); } /** * @throws CommonMarkException */ public function parse(string $input): Document { $this->initialize(); $documentParser = new DocumentBlockParser($this->referenceMap); $this->activateBlockParser($documentParser); $preParsedEvent = new DocumentPreParsedEvent($documentParser->getBlock(), new MarkdownInput($input)); $this->environment->dispatch($preParsedEvent); $markdownInput = $preParsedEvent->getMarkdown(); foreach ($markdownInput->getLines() as $lineNumber => $line) { $this->lineNumber = $lineNumber; $this->parseLine($line); } // finalizeAndProcess $this->closeBlockParsers(\count($this->activeBlockParsers), $this->lineNumber); $this->processInlines(); $this->environment->dispatch(new DocumentParsedEvent($documentParser->getBlock())); return $documentParser->getBlock(); } /** * Analyze a line of text and update the document appropriately. We parse markdown text by calling this on each * line of input, then finalizing the document. */ private function parseLine(string $line): void { $this->cursor = new Cursor($line); $matches = $this->parseBlockContinuation(); if ($matches === null) { return; } $unmatchedBlocks = \count($this->activeBlockParsers) - $matches; $blockParser = $this->activeBlockParsers[$matches - 1]; $startedNewBlock = false; // Unless last matched container is a code block, try new container starts, // adding children to the last matched container: $tryBlockStarts = $blockParser->getBlock() instanceof Paragraph || $blockParser->isContainer(); while ($tryBlockStarts) { // this is a little performance optimization if ($this->cursor->isBlank()) { $this->cursor->advanceToEnd(); break; } if ($blockParser->getBlock()->getDepth() >= $this->maxNestingLevel) { break; } $blockStart = $this->findBlockStart($blockParser); if ($blockStart === null || $blockStart->isAborting()) { $this->cursor->advanceToNextNonSpaceOrTab(); break; } if (($state = $blockStart->getCursorState()) !== null) { $this->cursor->restoreState($state); } $startedNewBlock = true; // We're starting a new block. If we have any previous blocks that need to be closed, we need to do it now. if ($unmatchedBlocks > 0) { $this->closeBlockParsers($unmatchedBlocks, $this->lineNumber - 1); $unmatchedBlocks = 0; } $oldBlockLineStart = null; if ($blockStart->isReplaceActiveBlockParser()) { $oldBlockLineStart = $this->prepareActiveBlockParserForReplacement(); } foreach ($blockStart->getBlockParsers() as $newBlockParser) { $blockParser = $this->addChild($newBlockParser, $oldBlockLineStart); $tryBlockStarts = $newBlockParser->isContainer(); } } // What remains at the offset is a text line. Add the text to the appropriate block. // First check for a lazy paragraph continuation: if (! $startedNewBlock && ! $this->cursor->isBlank() && $this->getActiveBlockParser()->canHaveLazyContinuationLines()) { $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } else { // finalize any blocks not matched if ($unmatchedBlocks > 0) { $this->closeBlockParsers($unmatchedBlocks, $this->lineNumber - 1); } if (! $blockParser->isContainer()) { $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } elseif (! $this->cursor->isBlank()) { $this->addChild(new ParagraphParser()); $this->getActiveBlockParser()->addLine($this->cursor->getRemainder()); } } } private function parseBlockContinuation(): ?int { // For each containing block, try to parse the associated line start. // The document will always match, so we can skip the first block parser and start at 1 matches $matches = 1; for ($i = 1; $i < \count($this->activeBlockParsers); $i++) { $blockParser = $this->activeBlockParsers[$i]; $blockContinue = $blockParser->tryContinue(clone $this->cursor, $this->getActiveBlockParser()); if ($blockContinue === null) { break; } if ($blockContinue->isFinalize()) { $this->closeBlockParsers(\count($this->activeBlockParsers) - $i, $this->lineNumber); return null; } if (($state = $blockContinue->getCursorState()) !== null) { $this->cursor->restoreState($state); } $matches++; } return $matches; } private function findBlockStart(BlockContinueParserInterface $lastMatchedBlockParser): ?BlockStart { $matchedBlockParser = new MarkdownParserState($this->getActiveBlockParser(), $lastMatchedBlockParser); foreach ($this->environment->getBlockStartParsers() as $blockStartParser) { \assert($blockStartParser instanceof BlockStartParserInterface); if (($result = $blockStartParser->tryStart(clone $this->cursor, $matchedBlockParser)) !== null) { return $result; } } return null; } private function closeBlockParsers(int $count, int $endLineNumber): void { for ($i = 0; $i < $count; $i++) { $blockParser = $this->deactivateBlockParser(); $this->finalize($blockParser, $endLineNumber); // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($blockParser instanceof BlockContinueParserWithInlinesInterface) { // Remember for inline parsing $this->closedBlockParsers[] = $blockParser; } } } /** * Finalize a block. Close it and do any necessary postprocessing, e.g. creating string_content from strings, * setting the 'tight' or 'loose' status of a list, and parsing the beginnings of paragraphs for reference * definitions. */ private function finalize(BlockContinueParserInterface $blockParser, int $endLineNumber): void { if ($blockParser instanceof ParagraphParser) { $this->updateReferenceMap($blockParser->getReferences()); } $blockParser->getBlock()->setEndLine($endLineNumber); $blockParser->closeBlock(); } /** * Walk through a block & children recursively, parsing string content into inline content where appropriate. */ private function processInlines(): void { $p = new InlineParserEngine($this->environment, $this->referenceMap); foreach ($this->closedBlockParsers as $blockParser) { $blockParser->parseInlines($p); } } /** * Add block of type tag as a child of the tip. If the tip can't accept children, close and finalize it and try * its parent, and so on til we find a block that can accept children. */ private function addChild(BlockContinueParserInterface $blockParser, ?int $startLineNumber = null): BlockContinueParserInterface { $blockParser->getBlock()->setStartLine($startLineNumber ?? $this->lineNumber); while (! $this->getActiveBlockParser()->canContain($blockParser->getBlock())) { $this->closeBlockParsers(1, ($startLineNumber ?? $this->lineNumber) - 1); } $this->getActiveBlockParser()->getBlock()->appendChild($blockParser->getBlock()); $this->activateBlockParser($blockParser); return $blockParser; } private function activateBlockParser(BlockContinueParserInterface $blockParser): void { $this->activeBlockParsers[] = $blockParser; } /** * @throws ParserLogicException */ private function deactivateBlockParser(): BlockContinueParserInterface { $popped = \array_pop($this->activeBlockParsers); if ($popped === null) { throw new ParserLogicException('The last block parser should not be deactivated'); } return $popped; } /** * @return int|null The line number where the old block started */ private function prepareActiveBlockParserForReplacement(): ?int { // Note that we don't want to parse inlines or finalize this block, as it's getting replaced. $old = $this->deactivateBlockParser(); if ($old instanceof ParagraphParser) { $this->updateReferenceMap($old->getReferences()); } $old->getBlock()->detach(); return $old->getBlock()->getStartLine(); } /** * @param ReferenceInterface[] $references */ private function updateReferenceMap(iterable $references): void { foreach ($references as $reference) { if (! $this->referenceMap->contains($reference->getLabel())) { $this->referenceMap->add($reference); } } } /** * @throws ParserLogicException */ public function getActiveBlockParser(): BlockContinueParserInterface { $active = \end($this->activeBlockParsers); if ($active === false) { throw new ParserLogicException('No active block parsers are available'); } return $active; } } commonmark/src/Parser/InlineParserEngine.php 0000644 00000015005 15021222733 0015162 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Parser; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Inline\AdjacentTextMerger; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Reference\ReferenceMapInterface; /** * @internal */ final class InlineParserEngine implements InlineParserEngineInterface { /** @psalm-readonly */ private EnvironmentInterface $environment; /** @psalm-readonly */ private ReferenceMapInterface $referenceMap; /** * @var array<int, InlineParserInterface|string|bool> * @psalm-var list<array{0: InlineParserInterface, 1: non-empty-string, 2: bool}> * @phpstan-var array<int, array{0: InlineParserInterface, 1: non-empty-string, 2: bool}> */ private array $parsers = []; public function __construct(EnvironmentInterface $environment, ReferenceMapInterface $referenceMap) { $this->environment = $environment; $this->referenceMap = $referenceMap; foreach ($environment->getInlineParsers() as $parser) { \assert($parser instanceof InlineParserInterface); $regex = $parser->getMatchDefinition()->getRegex(); $this->parsers[] = [$parser, $regex, \strlen($regex) !== \mb_strlen($regex, 'UTF-8')]; } } public function parse(string $contents, AbstractBlock $block): void { $contents = \trim($contents); $cursor = new Cursor($contents); $inlineParserContext = new InlineParserContext($cursor, $block, $this->referenceMap); // Have all parsers look at the line to determine what they might want to parse and what positions they exist at foreach ($this->matchParsers($contents) as $matchPosition => $parsers) { $currentPosition = $cursor->getPosition(); // We've already gone past this point if ($currentPosition > $matchPosition) { continue; } // We've skipped over some uninteresting text that should be added as a plain text node if ($currentPosition < $matchPosition) { $cursor->advanceBy($matchPosition - $currentPosition); $this->addPlainText($cursor->getPreviousText(), $block); } // We're now at a potential start - see which of the current parsers can handle it $parsed = false; foreach ($parsers as [$parser, $matches]) { \assert($parser instanceof InlineParserInterface); if ($parser->parse($inlineParserContext->withMatches($matches))) { // A parser has successfully handled the text at the given position; don't consider any others at this position $parsed = true; break; } } if ($parsed) { continue; } // Despite potentially being interested, nothing actually parsed text here, so add the current character and continue onwards $this->addPlainText((string) $cursor->getCurrentCharacter(), $block); $cursor->advance(); } // Add any remaining text that wasn't parsed if (! $cursor->isAtEnd()) { $this->addPlainText($cursor->getRemainder(), $block); } // Process any delimiters that were found $delimiterStack = $inlineParserContext->getDelimiterStack(); $delimiterStack->processDelimiters(null, $this->environment->getDelimiterProcessors()); $delimiterStack->removeAll(); // Combine adjacent text notes into one AdjacentTextMerger::mergeChildNodes($block); } private function addPlainText(string $text, AbstractBlock $container): void { $lastInline = $container->lastChild(); if ($lastInline instanceof Text && ! $lastInline->data->has('delim')) { $lastInline->append($text); } else { $container->appendChild(new Text($text)); } } /** * Given the current line, ask all the parsers which parts of the text they would be interested in parsing. * * The resulting array provides a list of character positions, which parsers are interested in trying to parse * the text at those points, and (for convenience/optimization) what the matching text happened to be. * * @return array<array<int, InlineParserInterface|string>> * * @psalm-return array<int, list<array{0: InlineParserInterface, 1: non-empty-array<string>}>> * * @phpstan-return array<int, array<int, array{0: InlineParserInterface, 1: non-empty-array<string>}>> */ private function matchParsers(string $contents): array { $contents = \trim($contents); $isMultibyte = ! \mb_check_encoding($contents, 'ASCII'); $ret = []; foreach ($this->parsers as [$parser, $regex, $isRegexMultibyte]) { if ($isMultibyte || $isRegexMultibyte) { $regex .= 'u'; } // See if the parser's InlineParserMatch regex matched against any part of the string if (! \preg_match_all($regex, $contents, $matches, \PREG_OFFSET_CAPTURE | \PREG_SET_ORDER)) { continue; } // For each part that matched... foreach ($matches as $match) { if ($isMultibyte) { // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying $offset = \mb_strlen(\substr($contents, 0, $match[0][1]), 'UTF-8'); } else { $offset = \intval($match[0][1]); } // Remove the offsets, keeping only the matched text $m = \array_column($match, 0); if ($m === []) { continue; } // Add this match to the list of character positions to stop at $ret[$offset][] = [$parser, $m]; } } // Sort matches by position so we visit them in order \ksort($ret); return $ret; } } commonmark/src/Extension/Attributes/AttributesExtension.php 0000644 00000002230 15021222733 0020326 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Attributes\Event\AttributesListener; use League\CommonMark\Extension\Attributes\Parser\AttributesBlockStartParser; use League\CommonMark\Extension\Attributes\Parser\AttributesInlineParser; use League\CommonMark\Extension\ExtensionInterface; final class AttributesExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { $environment->addBlockStartParser(new AttributesBlockStartParser()); $environment->addInlineParser(new AttributesInlineParser()); $environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener(), 'processDocument']); } } commonmark/src/Extension/Attributes/Event/AttributesListener.php 0000644 00000010503 15021222733 0021222 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Attributes\Node\Attributes; use League\CommonMark\Extension\Attributes\Node\AttributesInline; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Node\Node; final class AttributesListener { private const DIRECTION_PREFIX = 'prefix'; private const DIRECTION_SUFFIX = 'suffix'; public function processDocument(DocumentParsedEvent $event): void { foreach ($event->getDocument()->iterator() as $node) { if (! ($node instanceof Attributes || $node instanceof AttributesInline)) { continue; } [$target, $direction] = self::findTargetAndDirection($node); if ($target instanceof Node) { $parent = $target->parent(); if ($parent instanceof ListItem && $parent->parent() instanceof ListBlock && $parent->parent()->isTight()) { $target = $parent; } if ($direction === self::DIRECTION_SUFFIX) { $attributes = AttributesHelper::mergeAttributes($target, $node->getAttributes()); } else { $attributes = AttributesHelper::mergeAttributes($node->getAttributes(), $target); } $target->data->set('attributes', $attributes); } $node->detach(); } } /** * @param Attributes|AttributesInline $node * * @return array<Node|string|null> */ private static function findTargetAndDirection($node): array { $target = null; $direction = null; $previous = $next = $node; while (true) { $previous = self::getPrevious($previous); $next = self::getNext($next); if ($previous === null && $next === null) { if (! $node->parent() instanceof FencedCode) { $target = $node->parent(); $direction = self::DIRECTION_SUFFIX; } break; } if ($node instanceof AttributesInline && ($previous === null || ($previous instanceof AbstractInline && $node->isBlock()))) { continue; } if ($previous !== null && ! self::isAttributesNode($previous)) { $target = $previous; $direction = self::DIRECTION_SUFFIX; break; } if ($next !== null && ! self::isAttributesNode($next)) { $target = $next; $direction = self::DIRECTION_PREFIX; break; } } return [$target, $direction]; } /** * Get any previous block (sibling or parent) this might apply to */ private static function getPrevious(?Node $node = null): ?Node { if ($node instanceof Attributes) { if ($node->getTarget() === Attributes::TARGET_NEXT) { return null; } if ($node->getTarget() === Attributes::TARGET_PARENT) { return $node->parent(); } } return $node instanceof Node ? $node->previous() : null; } /** * Get any previous block (sibling or parent) this might apply to */ private static function getNext(?Node $node = null): ?Node { if ($node instanceof Attributes && $node->getTarget() !== Attributes::TARGET_NEXT) { return null; } return $node instanceof Node ? $node->next() : null; } private static function isAttributesNode(Node $node): bool { return $node instanceof Attributes || $node instanceof AttributesInline; } } commonmark/src/Extension/Attributes/Node/AttributesInline.php 0000644 00000002311 15021222733 0020455 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Node; use League\CommonMark\Node\Inline\AbstractInline; final class AttributesInline extends AbstractInline { /** @var array<string, mixed> */ private array $attributes; private bool $block; /** * @param array<string, mixed> $attributes */ public function __construct(array $attributes, bool $block) { parent::__construct(); $this->attributes = $attributes; $this->block = $block; } /** * @return array<string, mixed> */ public function getAttributes(): array { return $this->attributes; } /** * @param array<string, mixed> $attributes */ public function setAttributes(array $attributes): void { $this->attributes = $attributes; } public function isBlock(): bool { return $this->block; } } commonmark/src/Extension/Attributes/Node/Attributes.php 0000644 00000002567 15021222733 0017333 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Node; use League\CommonMark\Node\Block\AbstractBlock; final class Attributes extends AbstractBlock { public const TARGET_PARENT = 0; public const TARGET_PREVIOUS = 1; public const TARGET_NEXT = 2; /** @var array<string, mixed> */ private array $attributes; private int $target = self::TARGET_NEXT; /** * @param array<string, mixed> $attributes */ public function __construct(array $attributes) { parent::__construct(); $this->attributes = $attributes; } /** * @return array<string, mixed> */ public function getAttributes(): array { return $this->attributes; } /** * @param array<string, mixed> $attributes */ public function setAttributes(array $attributes): void { $this->attributes = $attributes; } public function getTarget(): int { return $this->target; } public function setTarget(int $target): void { $this->target = $target; } } commonmark/src/Extension/Attributes/Parser/AttributesBlockStartParser.php 0000644 00000002513 15021222733 0023037 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Parser; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class AttributesBlockStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { $originalPosition = $cursor->getPosition(); $attributes = AttributesHelper::parseAttributes($cursor); if ($attributes === [] && $originalPosition === $cursor->getPosition()) { return BlockStart::none(); } if ($cursor->getNextNonSpaceCharacter() !== null) { return BlockStart::none(); } return BlockStart::of(new AttributesBlockContinueParser($attributes, $parserState->getActiveBlockParser()->getBlock()))->at($cursor); } } commonmark/src/Extension/Attributes/Parser/AttributesInlineParser.php 0000644 00000003232 15021222733 0022204 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Parser; use League\CommonMark\Extension\Attributes\Node\AttributesInline; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\CommonMark\Node\StringContainerInterface; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class AttributesInlineParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::string('{'); } public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); $char = (string) $cursor->peek(-1); $attributes = AttributesHelper::parseAttributes($cursor); if ($attributes === []) { return false; } if ($char === ' ' && ($prev = $inlineContext->getContainer()->lastChild()) instanceof StringContainerInterface) { $prev->setLiteral(\rtrim($prev->getLiteral(), ' ')); } if ($char === '') { $cursor->advanceToNextNonSpaceOrNewline(); } $node = new AttributesInline($attributes, $char === ' ' || $char === ''); $inlineContext->getContainer()->appendChild($node); return true; } } commonmark/src/Extension/Attributes/Parser/AttributesBlockContinueParser.php 0000644 00000006271 15021222733 0023533 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Parser; use League\CommonMark\Extension\Attributes\Node\Attributes; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class AttributesBlockContinueParser extends AbstractBlockContinueParser { private Attributes $block; private AbstractBlock $container; private bool $hasSubsequentLine = false; /** * @param array<string, mixed> $attributes The attributes identified by the block start parser * @param AbstractBlock $container The node we were in when these attributes were discovered */ public function __construct(array $attributes, AbstractBlock $container) { $this->block = new Attributes($attributes); $this->container = $container; } public function getBlock(): AbstractBlock { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { $this->hasSubsequentLine = true; $cursor->advanceToNextNonSpaceOrTab(); // Does this next line also have attributes? $attributes = AttributesHelper::parseAttributes($cursor); $cursor->advanceToNextNonSpaceOrTab(); if ($cursor->isAtEnd() && $attributes !== []) { // It does! Merge them into what we parsed previously $this->block->setAttributes(AttributesHelper::mergeAttributes( $this->block->getAttributes(), $attributes )); // Tell the core parser we've consumed everything return BlockContinue::at($cursor); } // Okay, so there are no attributes on the next line // If this next line is blank we know we can't target the next node, it must be a previous one if ($cursor->isBlank()) { $this->block->setTarget(Attributes::TARGET_PREVIOUS); } return BlockContinue::none(); } public function closeBlock(): void { // Attributes appearing at the very end of the document won't have any last lines to check // so we can make that determination here if (! $this->hasSubsequentLine) { $this->block->setTarget(Attributes::TARGET_PREVIOUS); } // We know this block must apply to the "previous" block, but that could be a sibling or parent, // so we check the containing block to see which one it might be. if ($this->block->getTarget() === Attributes::TARGET_PREVIOUS && $this->block->parent() === $this->container) { $this->block->setTarget(Attributes::TARGET_PARENT); } } } commonmark/src/Extension/Attributes/Util/AttributesHelper.php 0000644 00000010762 15021222733 0020517 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) 2015 Martin Hasoň <martin.hason@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Attributes\Util; use League\CommonMark\Node\Node; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\RegexHelper; /** * @internal */ final class AttributesHelper { private const SINGLE_ATTRIBUTE = '\s*([.]-?[_a-z][^\s}]*|[#][^\s}]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*'; private const ATTRIBUTE_LIST = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i'; /** * @return array<string, mixed> */ public static function parseAttributes(Cursor $cursor): array { $state = $cursor->saveState(); $cursor->advanceToNextNonSpaceOrNewline(); // Quick check to see if we might have attributes if ($cursor->getCharacter() !== '{') { $cursor->restoreState($state); return []; } // Attempt to match the entire attribute list expression // While this is less performant than checking for '{' now and '}' later, it simplifies // matching individual attributes since they won't need to look ahead for the closing '}' // while dealing with the fact that attributes can technically contain curly braces. // So we'll just match the start and end braces up front. $attributeExpression = $cursor->match(self::ATTRIBUTE_LIST); if ($attributeExpression === null) { $cursor->restoreState($state); return []; } // Trim the leading '{' or '{:' and the trailing '}' $attributeExpression = \ltrim(\substr($attributeExpression, 1, -1), ':'); $attributeCursor = new Cursor($attributeExpression); /** @var array<string, mixed> $attributes */ $attributes = []; while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'))) { if ($attribute[0] === '#') { $attributes['id'] = \substr($attribute, 1); continue; } if ($attribute[0] === '.') { $attributes['class'][] = \substr($attribute, 1); continue; } /** @psalm-suppress PossiblyUndefinedArrayOffset */ [$name, $value] = \explode('=', $attribute, 2); if ($value === 'true') { $attributes[$name] = true; continue; } $first = $value[0]; $last = \substr($value, -1); if (($first === '"' && $last === '"') || ($first === "'" && $last === "'") && \strlen($value) > 1) { $value = \substr($value, 1, -1); } if (\strtolower(\trim($name)) === 'class') { foreach (\array_filter(\explode(' ', \trim($value))) as $class) { $attributes['class'][] = $class; } } else { $attributes[\trim($name)] = \trim($value); } } if (isset($attributes['class'])) { $attributes['class'] = \implode(' ', (array) $attributes['class']); } return $attributes; } /** * @param Node|array<string, mixed> $attributes1 * @param Node|array<string, mixed> $attributes2 * * @return array<string, mixed> */ public static function mergeAttributes($attributes1, $attributes2): array { $attributes = []; foreach ([$attributes1, $attributes2] as $arg) { if ($arg instanceof Node) { $arg = $arg->data->get('attributes'); } /** @var array<string, mixed> $arg */ $arg = (array) $arg; if (isset($arg['class'])) { if (\is_string($arg['class'])) { $arg['class'] = \array_filter(\explode(' ', \trim($arg['class']))); } foreach ($arg['class'] as $class) { $attributes['class'][] = $class; } unset($arg['class']); } $attributes = \array_merge($attributes, $arg); } if (isset($attributes['class'])) { $attributes['class'] = \implode(' ', $attributes['class']); } return $attributes; } } commonmark/src/Extension/DefaultAttributes/ApplyDefaultAttributesProcessor.php 0000644 00000004117 15021222733 0024157 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DefaultAttributes; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class ApplyDefaultAttributesProcessor implements ConfigurationAwareInterface { private ConfigurationInterface $config; public function onDocumentParsed(DocumentParsedEvent $event): void { /** @var array<string, array<string, mixed>> $map */ $map = $this->config->get('default_attributes'); // Don't bother iterating if no default attributes are configured if (! $map) { return; } foreach ($event->getDocument()->iterator() as $node) { // Check to see if any default attributes were defined if (($attributesToApply = $map[\get_class($node)] ?? []) === []) { continue; } $newAttributes = []; foreach ($attributesToApply as $name => $value) { if (\is_callable($value)) { $value = $value($node); // Callables are allowed to return `null` indicating that no changes should be made if ($value !== null) { $newAttributes[$name] = $value; } } else { $newAttributes[$name] = $value; } } // Merge these attributes into the node if (\count($newAttributes) > 0) { $node->data->set('attributes', AttributesHelper::mergeAttributes($node, $newAttributes)); } } } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/DefaultAttributes/DefaultAttributesExtension.php 0000644 00000002375 15021222733 0023152 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DefaultAttributes; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class DefaultAttributesExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('default_attributes', Expect::arrayOf( Expect::arrayOf( Expect::type('string|string[]|bool|callable'), // attribute value(s) 'string' // attribute name ), 'string' // node FQCN )->default([])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addEventListener(DocumentParsedEvent::class, [new ApplyDefaultAttributesProcessor(), 'onDocumentParsed']); } } commonmark/src/Extension/FrontMatter/Output/RenderedContentWithFrontMatter.php 0000644 00000002320 15021222733 0024027 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter\Output; use League\CommonMark\Extension\FrontMatter\FrontMatterProviderInterface; use League\CommonMark\Node\Block\Document; use League\CommonMark\Output\RenderedContent; /** * @psalm-immutable */ final class RenderedContentWithFrontMatter extends RenderedContent implements FrontMatterProviderInterface { /** * @var mixed * * @psalm-readonly */ private $frontMatter; /** * @param Document $document The parsed Document object * @param string $content The final HTML * @param mixed|null $frontMatter Any parsed front matter */ public function __construct(Document $document, string $content, $frontMatter) { parent::__construct($document, $content); $this->frontMatter = $frontMatter; } /** * {@inheritDoc} */ public function getFrontMatter() { return $this->frontMatter; } } commonmark/src/Extension/FrontMatter/FrontMatterProviderInterface.php 0000644 00000000677 15021222733 0022240 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter; interface FrontMatterProviderInterface { /** * @return mixed|null */ public function getFrontMatter(); } commonmark/src/Extension/FrontMatter/Exception/InvalidFrontMatterException.php 0000644 00000001220 15021222733 0024011 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter\Exception; use League\CommonMark\Exception\CommonMarkException; class InvalidFrontMatterException extends \RuntimeException implements CommonMarkException { public static function wrap(\Throwable $t): self { return new InvalidFrontMatterException('Failed to parse front matter: ' . $t->getMessage(), 0, $t); } } commonmark/src/Extension/FrontMatter/FrontMatterExtension.php 0000644 00000003372 15021222733 0020574 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\FrontMatter; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentPreParsedEvent; use League\CommonMark\Event\DocumentRenderedEvent; use League\CommonMark\Extension\ExtensionInterface; use League\CommonMark\Extension\FrontMatter\Data\FrontMatterDataParserInterface; use League\CommonMark\Extension\FrontMatter\Data\LibYamlFrontMatterParser; use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser; use League\CommonMark\Extension\FrontMatter\Listener\FrontMatterPostRenderListener; use League\CommonMark\Extension\FrontMatter\Listener\FrontMatterPreParser; final class FrontMatterExtension implements ExtensionInterface { /** @psalm-readonly */ private FrontMatterParserInterface $frontMatterParser; public function __construct(?FrontMatterDataParserInterface $dataParser = null) { $this->frontMatterParser = new FrontMatterParser($dataParser ?? LibYamlFrontMatterParser::capable() ?? new SymfonyYamlFrontMatterParser()); } public function getFrontMatterParser(): FrontMatterParserInterface { return $this->frontMatterParser; } public function register(EnvironmentBuilderInterface $environment): void { $environment->addEventListener(DocumentPreParsedEvent::class, new FrontMatterPreParser($this->frontMatterParser)); $environment->addEventListener(DocumentRenderedEvent::class, new FrontMatterPostRenderListener(), -500); } } commonmark/src/Extension/FrontMatter/Data/FrontMatterDataParserInterface.php 0000644 00000001261 15021222733 0023333 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\FrontMatter\Data; use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException; interface FrontMatterDataParserInterface { /** * @return mixed|null The parsed data (which may be null, if the input represents a null value) * * @throws InvalidFrontMatterException if parsing fails */ public function parse(string $frontMatter); } commonmark/src/Extension/FrontMatter/Data/LibYamlFrontMatterParser.php 0000644 00000002276 15021222733 0022201 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\FrontMatter\Data; use League\CommonMark\Exception\MissingDependencyException; use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException; final class LibYamlFrontMatterParser implements FrontMatterDataParserInterface { public static function capable(): ?LibYamlFrontMatterParser { if (! \extension_loaded('yaml')) { return null; } return new LibYamlFrontMatterParser(); } /** * {@inheritDoc} */ public function parse(string $frontMatter) { if (! \extension_loaded('yaml')) { throw new MissingDependencyException('Failed to parse yaml: "ext-yaml" extension is missing'); } $result = @\yaml_parse($frontMatter); if ($result === false) { throw new InvalidFrontMatterException('Failed to parse front matter'); } return $result; } } commonmark/src/Extension/FrontMatter/Data/SymfonyYamlFrontMatterParser.php 0000644 00000002141 15021222733 0023126 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\FrontMatter\Data; use League\CommonMark\Exception\MissingDependencyException; use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Yaml; final class SymfonyYamlFrontMatterParser implements FrontMatterDataParserInterface { /** * {@inheritDoc} */ public function parse(string $frontMatter) { if (! \class_exists(Yaml::class)) { throw new MissingDependencyException('Failed to parse yaml: "symfony/yaml" library is missing'); } try { /** @psalm-suppress ReservedWord */ return Yaml::parse($frontMatter); } catch (ParseException $ex) { throw InvalidFrontMatterException::wrap($ex); } } } commonmark/src/Extension/FrontMatter/FrontMatterParser.php 0000644 00000004447 15021222733 0020060 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\FrontMatter; use League\CommonMark\Extension\FrontMatter\Data\FrontMatterDataParserInterface; use League\CommonMark\Extension\FrontMatter\Exception\InvalidFrontMatterException; use League\CommonMark\Extension\FrontMatter\Input\MarkdownInputWithFrontMatter; use League\CommonMark\Parser\Cursor; final class FrontMatterParser implements FrontMatterParserInterface { /** @psalm-readonly */ private FrontMatterDataParserInterface $frontMatterParser; private const REGEX_FRONT_MATTER = '/^---\\R.*?\\R---\\R/s'; public function __construct(FrontMatterDataParserInterface $frontMatterParser) { $this->frontMatterParser = $frontMatterParser; } /** * @throws InvalidFrontMatterException if the front matter cannot be parsed */ public function parse(string $markdownContent): MarkdownInputWithFrontMatter { $cursor = new Cursor($markdownContent); // Locate the front matter $frontMatter = $cursor->match(self::REGEX_FRONT_MATTER); if ($frontMatter === null) { return new MarkdownInputWithFrontMatter($markdownContent); } // Trim the last line (ending ---s and newline) $frontMatter = \preg_replace('/---\R$/', '', $frontMatter); if ($frontMatter === null) { return new MarkdownInputWithFrontMatter($markdownContent); } // Parse the resulting YAML data $data = $this->frontMatterParser->parse($frontMatter); // Advance through any remaining newlines which separated the front matter from the Markdown text $trailingNewlines = $cursor->match('/^\R+/'); // Calculate how many lines the Markdown is offset from the front matter by counting the number of newlines // Don't forget to add 1 because we stripped one out when trimming the trailing delims $lineOffset = \preg_match_all('/\R/', $frontMatter . $trailingNewlines) + 1; return new MarkdownInputWithFrontMatter($cursor->getRemainder(), $lineOffset, $data); } } commonmark/src/Extension/FrontMatter/FrontMatterParserInterface.php 0000644 00000001020 15021222733 0021661 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter; use League\CommonMark\Extension\FrontMatter\Input\MarkdownInputWithFrontMatter; interface FrontMatterParserInterface { public function parse(string $markdownContent): MarkdownInputWithFrontMatter; } commonmark/src/Extension/FrontMatter/Listener/FrontMatterPostRenderListener.php 0000644 00000001747 15021222733 0024204 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter\Listener; use League\CommonMark\Event\DocumentRenderedEvent; use League\CommonMark\Extension\FrontMatter\Output\RenderedContentWithFrontMatter; final class FrontMatterPostRenderListener { public function __invoke(DocumentRenderedEvent $event): void { if ($event->getOutput()->getDocument()->data->get('front_matter', null) === null) { return; } $frontMatter = $event->getOutput()->getDocument()->data->get('front_matter'); $event->replaceOutput(new RenderedContentWithFrontMatter( $event->getOutput()->getDocument(), $event->getOutput()->getContent(), $frontMatter )); } } commonmark/src/Extension/FrontMatter/Listener/FrontMatterPreParser.php 0000644 00000001713 15021222733 0022305 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter\Listener; use League\CommonMark\Event\DocumentPreParsedEvent; use League\CommonMark\Extension\FrontMatter\FrontMatterParserInterface; final class FrontMatterPreParser { private FrontMatterParserInterface $parser; public function __construct(FrontMatterParserInterface $parser) { $this->parser = $parser; } public function __invoke(DocumentPreParsedEvent $event): void { $content = $event->getMarkdown()->getContent(); $parsed = $this->parser->parse($content); $event->getDocument()->data->set('front_matter', $parsed->getFrontMatter()); $event->replaceMarkdown($parsed); } } commonmark/src/Extension/FrontMatter/Input/MarkdownInputWithFrontMatter.php 0000644 00000002242 15021222733 0023350 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\FrontMatter\Input; use League\CommonMark\Extension\FrontMatter\FrontMatterProviderInterface; use League\CommonMark\Input\MarkdownInput; final class MarkdownInputWithFrontMatter extends MarkdownInput implements FrontMatterProviderInterface { /** @var mixed|null */ private $frontMatter; /** * @param string $content Markdown content without the raw front matter * @param int $lineOffset Line offset (based on number of front matter lines removed) * @param mixed|null $frontMatter Parsed front matter */ public function __construct(string $content, int $lineOffset = 0, $frontMatter = null) { parent::__construct($content, $lineOffset); $this->frontMatter = $frontMatter; } /** * {@inheritDoc} */ public function getFrontMatter() { return $this->frontMatter; } } commonmark/src/Extension/Table/TableSection.php 0000644 00000002441 15021222733 0015564 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Block\AbstractBlock; final class TableSection extends AbstractBlock { public const TYPE_HEAD = 'head'; public const TYPE_BODY = 'body'; /** * @psalm-var self::TYPE_* * @phpstan-var self::TYPE_* * * @psalm-readonly */ private string $type; /** * @psalm-param self::TYPE_* $type * * @phpstan-param self::TYPE_* $type */ public function __construct(string $type = self::TYPE_BODY) { parent::__construct(); $this->type = $type; } /** * @psalm-return self::TYPE_* * * @phpstan-return self::TYPE_* */ public function getType(): string { return $this->type; } public function isHead(): bool { return $this->type === self::TYPE_HEAD; } public function isBody(): bool { return $this->type === self::TYPE_BODY; } } commonmark/src/Extension/Table/TableRenderer.php 0000644 00000002725 15021222733 0015733 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Table $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Table::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $separator = $childRenderer->getInnerSeparator(); $children = $childRenderer->renderNodes($node->children()); return new HtmlElement('table', $attrs, $separator . \trim($children) . $separator); } public function getXmlTagName(Node $node): string { return 'table'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/Table/TableStartParser.php 0000644 00000010725 15021222733 0016436 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Block\ParagraphParser; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class TableStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { $paragraph = $parserState->getParagraphContent(); if ($paragraph === null || \strpos($paragraph, '|') === false) { return BlockStart::none(); } $columns = self::parseSeparator($cursor); if (\count($columns) === 0) { return BlockStart::none(); } $lines = \explode("\n", $paragraph); $lastLine = \array_pop($lines); $headerCells = TableParser::split($lastLine); if (\count($headerCells) > \count($columns)) { return BlockStart::none(); } $cursor->advanceToEnd(); $parsers = []; if (\count($lines) > 0) { $p = new ParagraphParser(); $p->addLine(\implode("\n", $lines)); $parsers[] = $p; } $parsers[] = new TableParser($columns, $headerCells); return BlockStart::of(...$parsers) ->at($cursor) ->replaceActiveBlockParser(); } /** * @return array<int, string|null> * * @psalm-return array<int, TableCell::ALIGN_*|null> * * @phpstan-return array<int, TableCell::ALIGN_*|null> */ private static function parseSeparator(Cursor $cursor): array { $columns = []; $pipes = 0; $valid = false; while (! $cursor->isAtEnd()) { switch ($c = $cursor->getCurrentCharacter()) { case '|': $cursor->advanceBy(1); $pipes++; if ($pipes > 1) { // More than one adjacent pipe not allowed return []; } // Need at least one pipe, even for a one-column table $valid = true; break; case '-': case ':': if ($pipes === 0 && \count($columns) > 0) { // Need a pipe after the first column (first column doesn't need to start with one) return []; } $left = false; $right = false; if ($c === ':') { $left = true; $cursor->advanceBy(1); } if ($cursor->match('/^-+/') === null) { // Need at least one dash return []; } if ($cursor->getCurrentCharacter() === ':') { $right = true; $cursor->advanceBy(1); } $columns[] = self::getAlignment($left, $right); // Next, need another pipe $pipes = 0; break; case ' ': case "\t": // White space is allowed between pipes and columns $cursor->advanceToNextNonSpaceOrTab(); break; default: // Any other character is invalid return []; } } if (! $valid) { return []; } return $columns; } /** * @psalm-return TableCell::ALIGN_*|null * * @phpstan-return TableCell::ALIGN_*|null * * @psalm-pure */ private static function getAlignment(bool $left, bool $right): ?string { if ($left && $right) { return TableCell::ALIGN_CENTER; } if ($left) { return TableCell::ALIGN_LEFT; } if ($right) { return TableCell::ALIGN_RIGHT; } return null; } } commonmark/src/Extension/Table/TableCell.php 0000644 00000004243 15021222733 0015041 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Block\AbstractBlock; final class TableCell extends AbstractBlock { public const TYPE_HEADER = 'header'; public const TYPE_DATA = 'data'; public const ALIGN_LEFT = 'left'; public const ALIGN_RIGHT = 'right'; public const ALIGN_CENTER = 'center'; /** * @psalm-var self::TYPE_* * @phpstan-var self::TYPE_* * * @psalm-readonly-allow-private-mutation */ private string $type = self::TYPE_DATA; /** * @psalm-var self::ALIGN_*|null * @phpstan-var self::ALIGN_*|null * * @psalm-readonly-allow-private-mutation */ private ?string $align = null; /** * @psalm-param self::TYPE_* $type * @psalm-param self::ALIGN_*|null $align * * @phpstan-param self::TYPE_* $type * @phpstan-param self::ALIGN_*|null $align */ public function __construct(string $type = self::TYPE_DATA, ?string $align = null) { parent::__construct(); $this->type = $type; $this->align = $align; } /** * @psalm-return self::TYPE_* * * @phpstan-return self::TYPE_* */ public function getType(): string { return $this->type; } /** * @psalm-param self::TYPE_* $type * * @phpstan-param self::TYPE_* $type */ public function setType(string $type): void { $this->type = $type; } /** * @psalm-return self::ALIGN_*|null * * @phpstan-return self::ALIGN_*|null */ public function getAlign(): ?string { return $this->align; } /** * @psalm-param self::ALIGN_*|null $align * * @phpstan-param self::ALIGN_*|null $align */ public function setAlign(?string $align): void { $this->align = $align; } } commonmark/src/Extension/Table/TableExtension.php 0000644 00000004664 15021222733 0016145 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Renderer\HtmlDecorator; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class TableExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $attributeArraySchema = Expect::arrayOf( Expect::type('string|string[]|bool'), // attribute value(s) 'string' // attribute name )->mergeDefaults(false); $builder->addSchema('table', Expect::structure([ 'wrap' => Expect::structure([ 'enabled' => Expect::bool()->default(false), 'tag' => Expect::string()->default('div'), 'attributes' => Expect::arrayOf(Expect::string()), ]), 'alignment_attributes' => Expect::structure([ 'left' => (clone $attributeArraySchema)->default(['align' => 'left']), 'center' => (clone $attributeArraySchema)->default(['align' => 'center']), 'right' => (clone $attributeArraySchema)->default(['align' => 'right']), ]), ])); } public function register(EnvironmentBuilderInterface $environment): void { $tableRenderer = new TableRenderer(); if ($environment->getConfiguration()->get('table/wrap/enabled')) { $tableRenderer = new HtmlDecorator($tableRenderer, $environment->getConfiguration()->get('table/wrap/tag'), $environment->getConfiguration()->get('table/wrap/attributes')); } $environment ->addBlockStartParser(new TableStartParser()) ->addRenderer(Table::class, $tableRenderer) ->addRenderer(TableSection::class, new TableSectionRenderer()) ->addRenderer(TableRow::class, new TableRowRenderer()) ->addRenderer(TableCell::class, new TableCellRenderer($environment->getConfiguration()->get('table/alignment_attributes'))); } } commonmark/src/Extension/Table/TableCellRenderer.php 0000644 00000005071 15021222733 0016530 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Extension\Attributes\Util\AttributesHelper; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableCellRenderer implements NodeRendererInterface, XmlNodeRendererInterface { private const DEFAULT_ATTRIBUTES = [ TableCell::ALIGN_LEFT => ['align' => 'left'], TableCell::ALIGN_CENTER => ['align' => 'center'], TableCell::ALIGN_RIGHT => ['align' => 'right'], ]; /** @var array<TableCell::ALIGN_*, array<string, string|string[]|bool>> */ private array $alignmentAttributes; /** * @param array<TableCell::ALIGN_*, array<string, string|string[]|bool>> $alignmentAttributes */ public function __construct(array $alignmentAttributes = self::DEFAULT_ATTRIBUTES) { $this->alignmentAttributes = $alignmentAttributes; } /** * @param TableCell $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { TableCell::assertInstanceOf($node); $attrs = $node->data->get('attributes'); if (($alignment = $node->getAlign()) !== null) { $attrs = AttributesHelper::mergeAttributes($attrs, $this->alignmentAttributes[$alignment]); } $tag = $node->getType() === TableCell::TYPE_HEADER ? 'th' : 'td'; return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'table_cell'; } /** * @param TableCell $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { TableCell::assertInstanceOf($node); $ret = ['type' => $node->getType()]; if (($align = $node->getAlign()) !== null) { $ret['align'] = $align; } return $ret; } } commonmark/src/Extension/Table/TableSectionRenderer.php 0000644 00000003455 15021222733 0017261 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableSectionRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param TableSection $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { TableSection::assertInstanceOf($node); if (! $node->hasChildren()) { return ''; } $attrs = $node->data->get('attributes'); $separator = $childRenderer->getInnerSeparator(); $tag = $node->getType() === TableSection::TYPE_HEAD ? 'thead' : 'tbody'; return new HtmlElement($tag, $attrs, $separator . $childRenderer->renderNodes($node->children()) . $separator); } public function getXmlTagName(Node $node): string { return 'table_section'; } /** * @param TableSection $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { TableSection::assertInstanceOf($node); return [ 'type' => $node->getType(), ]; } } commonmark/src/Extension/Table/TableParser.php 0000644 00000013470 15021222733 0015420 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\InlineParserEngineInterface; use League\CommonMark\Util\ArrayCollection; final class TableParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface { /** @psalm-readonly */ private Table $block; /** * @var ArrayCollection<string> * * @psalm-readonly-allow-private-mutation */ private ArrayCollection $bodyLines; /** * @var array<int, string|null> * @psalm-var array<int, TableCell::ALIGN_*|null> * @phpstan-var array<int, TableCell::ALIGN_*|null> * * @psalm-readonly */ private array $columns; /** * @var array<int, string> * * @psalm-readonly-allow-private-mutation */ private array $headerCells; /** @psalm-readonly-allow-private-mutation */ private bool $nextIsSeparatorLine = true; /** * @param array<int, string|null> $columns * @param array<int, string> $headerCells * * @psalm-param array<int, TableCell::ALIGN_*|null> $columns * * @phpstan-param array<int, TableCell::ALIGN_*|null> $columns */ public function __construct(array $columns, array $headerCells) { $this->block = new Table(); $this->bodyLines = new ArrayCollection(); $this->columns = $columns; $this->headerCells = $headerCells; } public function canHaveLazyContinuationLines(): bool { return true; } public function getBlock(): Table { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if (\strpos($cursor->getLine(), '|') === false) { return BlockContinue::none(); } return BlockContinue::at($cursor); } public function addLine(string $line): void { if ($this->nextIsSeparatorLine) { $this->nextIsSeparatorLine = false; } else { $this->bodyLines[] = $line; } } public function parseInlines(InlineParserEngineInterface $inlineParser): void { $headerColumns = \count($this->headerCells); $head = new TableSection(TableSection::TYPE_HEAD); $this->block->appendChild($head); $headerRow = new TableRow(); $head->appendChild($headerRow); for ($i = 0; $i < $headerColumns; $i++) { $cell = $this->headerCells[$i]; $tableCell = $this->parseCell($cell, $i, $inlineParser); $tableCell->setType(TableCell::TYPE_HEADER); $headerRow->appendChild($tableCell); } $body = null; foreach ($this->bodyLines as $rowLine) { $cells = self::split($rowLine); $row = new TableRow(); // Body can not have more columns than head for ($i = 0; $i < $headerColumns; $i++) { $cell = $cells[$i] ?? ''; $tableCell = $this->parseCell($cell, $i, $inlineParser); $row->appendChild($tableCell); } if ($body === null) { // It's valid to have a table without body. In that case, don't add an empty TableBody node. $body = new TableSection(); $this->block->appendChild($body); } $body->appendChild($row); } } private function parseCell(string $cell, int $column, InlineParserEngineInterface $inlineParser): TableCell { $tableCell = new TableCell(); if ($column < \count($this->columns)) { $tableCell->setAlign($this->columns[$column]); } $inlineParser->parse(\trim($cell), $tableCell); return $tableCell; } /** * @internal * * @return array<int, string> */ public static function split(string $line): array { $cursor = new Cursor(\trim($line)); if ($cursor->getCurrentCharacter() === '|') { $cursor->advanceBy(1); } $cells = []; $sb = ''; while (! $cursor->isAtEnd()) { switch ($c = $cursor->getCurrentCharacter()) { case '\\': if ($cursor->peek() === '|') { // Pipe is special for table parsing. An escaped pipe doesn't result in a new cell, but is // passed down to inline parsing as an unescaped pipe. Note that that applies even for the `\|` // in an input like `\\|` - in other words, table parsing doesn't support escaping backslashes. $sb .= '|'; $cursor->advanceBy(1); } else { // Preserve backslash before other characters or at end of line. $sb .= '\\'; } break; case '|': $cells[] = $sb; $sb = ''; break; default: $sb .= $c; } $cursor->advanceBy(1); } if ($sb !== '') { $cells[] = $sb; } return $cells; } } commonmark/src/Extension/Table/TableRowRenderer.php 0000644 00000002670 15021222733 0016422 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableRowRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param TableRow $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { TableRow::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $separator = $childRenderer->getInnerSeparator(); return new HtmlElement('tr', $attrs, $separator . $childRenderer->renderNodes($node->children()) . $separator); } public function getXmlTagName(Node $node): string { return 'table_row'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/Table/TableRow.php 0000644 00000000755 15021222733 0014735 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Block\AbstractBlock; final class TableRow extends AbstractBlock { } commonmark/src/Extension/Table/Table.php 0000644 00000000752 15021222733 0014242 0 ustar 00 <?php declare(strict_types=1); /* * This is part of the league/commonmark package. * * (c) Martin Hasoň <martin.hason@gmail.com> * (c) Webuni s.r.o. <info@webuni.cz> * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Table; use League\CommonMark\Node\Block\AbstractBlock; final class Table extends AbstractBlock { } commonmark/src/Extension/InlinesOnly/InlinesOnlyExtension.php 0000644 00000007203 15021222733 0020565 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\InlinesOnly; use League\CommonMark as Core; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\CommonMark; use League\CommonMark\Extension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class InlinesOnlyExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('commonmark', Expect::structure([ 'use_asterisk' => Expect::bool(true), 'use_underscore' => Expect::bool(true), 'enable_strong' => Expect::bool(true), 'enable_em' => Expect::bool(true), ])); } // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma,Squiz.WhiteSpace.SemicolonSpacing.Incorrect public function register(EnvironmentBuilderInterface $environment): void { $childRenderer = new ChildRenderer(); $environment ->addInlineParser(new Core\Parser\Inline\NewlineParser(), 200) ->addInlineParser(new CommonMark\Parser\Inline\BacktickParser(), 150) ->addInlineParser(new CommonMark\Parser\Inline\EscapableParser(), 80) ->addInlineParser(new CommonMark\Parser\Inline\EntityParser(), 70) ->addInlineParser(new CommonMark\Parser\Inline\AutolinkParser(), 50) ->addInlineParser(new CommonMark\Parser\Inline\HtmlInlineParser(), 40) ->addInlineParser(new CommonMark\Parser\Inline\CloseBracketParser(), 30) ->addInlineParser(new CommonMark\Parser\Inline\OpenBracketParser(), 20) ->addInlineParser(new CommonMark\Parser\Inline\BangParser(), 10) ->addRenderer(Core\Node\Block\Document::class, $childRenderer, 0) ->addRenderer(Core\Node\Block\Paragraph::class, $childRenderer, 0) ->addRenderer(CommonMark\Node\Inline\Code::class, new CommonMark\Renderer\Inline\CodeRenderer(), 0) ->addRenderer(CommonMark\Node\Inline\Emphasis::class, new CommonMark\Renderer\Inline\EmphasisRenderer(), 0) ->addRenderer(CommonMark\Node\Inline\HtmlInline::class, new CommonMark\Renderer\Inline\HtmlInlineRenderer(), 0) ->addRenderer(CommonMark\Node\Inline\Image::class, new CommonMark\Renderer\Inline\ImageRenderer(), 0) ->addRenderer(CommonMark\Node\Inline\Link::class, new CommonMark\Renderer\Inline\LinkRenderer(), 0) ->addRenderer(Core\Node\Inline\Newline::class, new Core\Renderer\Inline\NewlineRenderer(), 0) ->addRenderer(CommonMark\Node\Inline\Strong::class, new CommonMark\Renderer\Inline\StrongRenderer(), 0) ->addRenderer(Core\Node\Inline\Text::class, new Core\Renderer\Inline\TextRenderer(), 0) ; if ($environment->getConfiguration()->get('commonmark/use_asterisk')) { $environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*')); } if ($environment->getConfiguration()->get('commonmark/use_underscore')) { $environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_')); } } } commonmark/src/Extension/InlinesOnly/ChildRenderer.php 0000644 00000001665 15021222733 0017145 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\InlinesOnly; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; /** * Simply renders child elements as-is, adding newlines as needed. */ final class ChildRenderer implements NodeRendererInterface { public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { $out = $childRenderer->renderNodes($node->children()); if (! $node instanceof Document) { $out .= $childRenderer->getBlockSeparator(); } return $out; } } commonmark/src/Extension/ExternalLink/ExternalLinkProcessor.php 0000644 00000007303 15021222733 0021063 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\ExternalLink; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\Config\ConfigurationInterface; final class ExternalLinkProcessor { public const APPLY_NONE = ''; public const APPLY_ALL = 'all'; public const APPLY_EXTERNAL = 'external'; public const APPLY_INTERNAL = 'internal'; /** @psalm-readonly */ private ConfigurationInterface $config; public function __construct(ConfigurationInterface $config) { $this->config = $config; } public function __invoke(DocumentParsedEvent $e): void { $internalHosts = $this->config->get('external_link/internal_hosts'); $openInNewWindow = $this->config->get('external_link/open_in_new_window'); $classes = $this->config->get('external_link/html_class'); foreach ($e->getDocument()->iterator() as $link) { if (! ($link instanceof Link)) { continue; } $host = \parse_url($link->getUrl(), PHP_URL_HOST); if (! \is_string($host)) { // Something is terribly wrong with this URL continue; } if (self::hostMatches($host, $internalHosts)) { $link->data->set('external', false); $this->applyRelAttribute($link, false); continue; } // Host does not match our list $this->markLinkAsExternal($link, $openInNewWindow, $classes); } } private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $classes): void { $link->data->set('external', true); $this->applyRelAttribute($link, true); if ($openInNewWindow) { $link->data->set('attributes/target', '_blank'); } if ($classes !== '') { $link->data->append('attributes/class', $classes); } } private function applyRelAttribute(Link $link, bool $isExternal): void { $options = [ 'nofollow' => $this->config->get('external_link/nofollow'), 'noopener' => $this->config->get('external_link/noopener'), 'noreferrer' => $this->config->get('external_link/noreferrer'), ]; foreach ($options as $type => $option) { switch (true) { case $option === self::APPLY_ALL: case $isExternal && $option === self::APPLY_EXTERNAL: case ! $isExternal && $option === self::APPLY_INTERNAL: $link->data->append('attributes/rel', $type); } } // No rel attributes? Mark the attribute as 'false' so LinkRenderer doesn't add defaults if (! $link->data->has('attributes/rel')) { $link->data->set('attributes/rel', false); } } /** * @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION! * * @param non-empty-string|list<non-empty-string> $compareTo */ public static function hostMatches(string $host, $compareTo): bool { foreach ((array) $compareTo as $c) { if (\strpos($c, '/') === 0) { if (\preg_match($c, $host)) { return true; } } elseif ($c === $host) { return true; } } return false; } } commonmark/src/Extension/ExternalLink/ExternalLinkExtension.php 0000644 00000003371 15021222733 0021061 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\ExternalLink; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class ExternalLinkExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $applyOptions = [ ExternalLinkProcessor::APPLY_NONE, ExternalLinkProcessor::APPLY_ALL, ExternalLinkProcessor::APPLY_INTERNAL, ExternalLinkProcessor::APPLY_EXTERNAL, ]; $builder->addSchema('external_link', Expect::structure([ 'internal_hosts' => Expect::type('string|string[]'), 'open_in_new_window' => Expect::bool(false), 'html_class' => Expect::string()->default(''), 'nofollow' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_NONE), 'noopener' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL), 'noreferrer' => Expect::anyOf(...$applyOptions)->default(ExternalLinkProcessor::APPLY_EXTERNAL), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addEventListener(DocumentParsedEvent::class, new ExternalLinkProcessor($environment->getConfiguration()), -50); } } commonmark/src/Extension/DescriptionList/Event/LooseDescriptionHandler.php 0000644 00000004336 15021222733 0023151 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Extension\DescriptionList\Node\DescriptionList; use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\NodeIterator; final class LooseDescriptionHandler { public function __invoke(DocumentParsedEvent $event): void { foreach ($event->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $description) { if (! $description instanceof Description) { continue; } // Does this description need to be added to a list? if (! $description->parent() instanceof DescriptionList) { $list = new DescriptionList(); // Taking any preceding paragraphs with it if (($paragraph = $description->previous()) instanceof Paragraph) { $list->appendChild($paragraph); } $description->replaceWith($list); $list->appendChild($description); } // Is this description preceded by a paragraph that should really be a term? if (! (($paragraph = $description->previous()) instanceof Paragraph)) { continue; } // Convert the paragraph into one or more terms $term = new DescriptionTerm(); $paragraph->replaceWith($term); foreach ($paragraph->children() as $child) { if ($child instanceof Newline) { $newTerm = new DescriptionTerm(); $term->insertAfter($newTerm); $term = $newTerm; continue; } $term->appendChild($child); } } } } commonmark/src/Extension/DescriptionList/Event/ConsecutiveDescriptionListMerger.php 0000644 00000002223 15021222733 0025050 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\DescriptionList\Node\DescriptionList; use League\CommonMark\Node\NodeIterator; final class ConsecutiveDescriptionListMerger { public function __invoke(DocumentParsedEvent $event): void { foreach ($event->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if (! $node instanceof DescriptionList) { continue; } if (! ($prev = $node->previous()) instanceof DescriptionList) { continue; } // There's another description list behind this one; merge the current one into that foreach ($node->children() as $child) { $prev->appendChild($child); } $node->detach(); } } } commonmark/src/Extension/DescriptionList/Node/DescriptionList.php 0000644 00000000656 15021222733 0021312 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Node; use League\CommonMark\Node\Block\AbstractBlock; class DescriptionList extends AbstractBlock { } commonmark/src/Extension/DescriptionList/Node/DescriptionTerm.php 0000644 00000000656 15021222733 0021306 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Node; use League\CommonMark\Node\Block\AbstractBlock; class DescriptionTerm extends AbstractBlock { } commonmark/src/Extension/DescriptionList/Node/Description.php 0000644 00000001503 15021222733 0020446 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Node; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\TightBlockInterface; class Description extends AbstractBlock implements TightBlockInterface { private bool $tight; public function __construct(bool $tight = false) { parent::__construct(); $this->tight = $tight; } public function isTight(): bool { return $this->tight; } public function setTight(bool $tight): void { $this->tight = $tight; } } commonmark/src/Extension/DescriptionList/Parser/DescriptionListContinueParser.php 0000644 00000002760 15021222733 0024541 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\DescriptionList\Parser; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Extension\DescriptionList\Node\DescriptionList; use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class DescriptionListContinueParser extends AbstractBlockContinueParser { private DescriptionList $block; public function __construct() { $this->block = new DescriptionList(); } public function getBlock(): DescriptionList { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::at($cursor); } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return $childBlock instanceof DescriptionTerm || $childBlock instanceof Description; } } commonmark/src/Extension/DescriptionList/Parser/DescriptionTermContinueParser.php 0000644 00000003011 15021222733 0024523 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Parser; use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\InlineParserEngineInterface; final class DescriptionTermContinueParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface { private DescriptionTerm $block; private string $term; public function __construct(string $term) { $this->block = new DescriptionTerm(); $this->term = $term; } public function getBlock(): DescriptionTerm { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::finished(); } public function parseInlines(InlineParserEngineInterface $inlineParser): void { if ($this->term !== '') { $inlineParser->parse($this->term, $this->block); } } } commonmark/src/Extension/DescriptionList/Parser/DescriptionContinueParser.php 0000644 00000003534 15021222733 0023705 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\DescriptionList\Parser; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class DescriptionContinueParser extends AbstractBlockContinueParser { private Description $block; private int $indentation; public function __construct(bool $tight, int $indentation) { $this->block = new Description($tight); $this->indentation = $indentation; } public function getBlock(): Description { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isBlank()) { if ($this->block->firstChild() === null) { // Blank line after empty item return BlockContinue::none(); } $cursor->advanceToNextNonSpaceOrTab(); return BlockContinue::at($cursor); } if ($cursor->getIndent() >= $this->indentation) { $cursor->advanceBy($this->indentation, true); return BlockContinue::at($cursor); } return BlockContinue::none(); } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } } commonmark/src/Extension/DescriptionList/Parser/DescriptionStartParser.php 0000644 00000004773 15021222733 0023224 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\DescriptionList\Parser; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class DescriptionStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented()) { return BlockStart::none(); } $cursor->advanceToNextNonSpaceOrTab(); if ($cursor->match('/^:[ \t]+/') === null) { return BlockStart::none(); } $terms = $parserState->getParagraphContent(); $activeBlock = $parserState->getActiveBlockParser()->getBlock(); if ($terms !== null && $terms !== '') { // New description; tight; term(s) sitting in pending block that we will replace return BlockStart::of(...[new DescriptionListContinueParser()], ...self::splitTerms($terms), ...[new DescriptionContinueParser(true, $cursor->getPosition())]) ->at($cursor) ->replaceActiveBlockParser(); } if ($activeBlock instanceof Paragraph && $activeBlock->parent() instanceof Description) { // Additional description in the same list as the parent description return BlockStart::of(new DescriptionContinueParser(true, $cursor->getPosition()))->at($cursor); } if ($activeBlock->lastChild() instanceof Paragraph) { // New description; loose; term(s) sitting in previous closed paragraph block return BlockStart::of(new DescriptionContinueParser(false, $cursor->getPosition()))->at($cursor); } // No preceding terms return BlockStart::none(); } /** * @return array<int, DescriptionTermContinueParser> */ private static function splitTerms(string $terms): array { $ret = []; foreach (\explode("\n", $terms) as $term) { $ret[] = new DescriptionTermContinueParser($term); } return $ret; } } commonmark/src/Extension/DescriptionList/DescriptionListExtension.php 0000644 00000003557 15021222733 0022325 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\DescriptionList\Event\ConsecutiveDescriptionListMerger; use League\CommonMark\Extension\DescriptionList\Event\LooseDescriptionHandler; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Extension\DescriptionList\Node\DescriptionList; use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm; use League\CommonMark\Extension\DescriptionList\Parser\DescriptionStartParser; use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionListRenderer; use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionRenderer; use League\CommonMark\Extension\DescriptionList\Renderer\DescriptionTermRenderer; use League\CommonMark\Extension\ExtensionInterface; final class DescriptionListExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { $environment->addBlockStartParser(new DescriptionStartParser()); $environment->addEventListener(DocumentParsedEvent::class, new LooseDescriptionHandler(), 1001); $environment->addEventListener(DocumentParsedEvent::class, new ConsecutiveDescriptionListMerger(), 1000); $environment->addRenderer(DescriptionList::class, new DescriptionListRenderer()); $environment->addRenderer(DescriptionTerm::class, new DescriptionTermRenderer()); $environment->addRenderer(Description::class, new DescriptionRenderer()); } } commonmark/src/Extension/DescriptionList/Renderer/DescriptionRenderer.php 0000644 00000002022 15021222733 0023013 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Renderer; use League\CommonMark\Extension\DescriptionList\Node\Description; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; final class DescriptionRenderer implements NodeRendererInterface { /** * @param Description $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Description::assertInstanceOf($node); return new HtmlElement('dd', [], $childRenderer->renderNodes($node->children())); } } commonmark/src/Extension/DescriptionList/Renderer/DescriptionTermRenderer.php 0000644 00000002042 15021222733 0023645 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Renderer; use League\CommonMark\Extension\DescriptionList\Node\DescriptionTerm; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; final class DescriptionTermRenderer implements NodeRendererInterface { /** * @param DescriptionTerm $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { DescriptionTerm::assertInstanceOf($node); return new HtmlElement('dt', [], $childRenderer->renderNodes($node->children())); } } commonmark/src/Extension/DescriptionList/Renderer/DescriptionListRenderer.php 0000644 00000002167 15021222733 0023661 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DescriptionList\Renderer; use League\CommonMark\Extension\DescriptionList\Node\DescriptionList; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; final class DescriptionListRenderer implements NodeRendererInterface { /** * @param DescriptionList $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): HtmlElement { DescriptionList::assertInstanceOf($node); $separator = $childRenderer->getBlockSeparator(); return new HtmlElement('dl', [], $separator . $childRenderer->renderNodes($node->children()) . $separator); } } commonmark/src/Extension/GithubFlavoredMarkdownExtension.php 0000644 00000002235 15021222733 0020467 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\Autolink\AutolinkExtension; use League\CommonMark\Extension\DisallowedRawHtml\DisallowedRawHtmlExtension; use League\CommonMark\Extension\Strikethrough\StrikethroughExtension; use League\CommonMark\Extension\Table\TableExtension; use League\CommonMark\Extension\TaskList\TaskListExtension; final class GithubFlavoredMarkdownExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { $environment->addExtension(new AutolinkExtension()); $environment->addExtension(new DisallowedRawHtmlExtension()); $environment->addExtension(new StrikethroughExtension()); $environment->addExtension(new TableExtension()); $environment->addExtension(new TaskListExtension()); } } commonmark/src/Extension/Mention/Mention.php 0000644 00000004002 15021222733 0015176 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Node\Inline\Text; class Mention extends Link { private string $name; private string $prefix; private string $identifier; public function __construct(string $name, string $prefix, string $identifier, ?string $label = null) { $this->name = $name; $this->prefix = $prefix; $this->identifier = $identifier; parent::__construct('', $label ?? \sprintf('%s%s', $prefix, $identifier)); } public function getLabel(): ?string { if (($labelNode = $this->findLabelNode()) === null) { return null; } return $labelNode->getLiteral(); } public function getIdentifier(): string { return $this->identifier; } public function getName(): ?string { return $this->name; } public function getPrefix(): string { return $this->prefix; } public function hasUrl(): bool { return $this->url !== ''; } /** * @return $this */ public function setLabel(string $label): self { if (($labelNode = $this->findLabelNode()) === null) { $labelNode = new Text(); $this->prependChild($labelNode); } $labelNode->setLiteral($label); return $this; } private function findLabelNode(): ?Text { foreach ($this->children() as $child) { if ($child instanceof Text) { return $child; } } return null; } } commonmark/src/Extension/Mention/MentionParser.php 0000644 00000005606 15021222733 0016366 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention; use League\CommonMark\Extension\Mention\Generator\CallbackGenerator; use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface; use League\CommonMark\Extension\Mention\Generator\StringTemplateLinkGenerator; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class MentionParser implements InlineParserInterface { /** @psalm-readonly */ private string $name; /** @psalm-readonly */ private string $prefix; /** @psalm-readonly */ private string $identifierPattern; /** @psalm-readonly */ private MentionGeneratorInterface $mentionGenerator; public function __construct(string $name, string $prefix, string $identifierPattern, MentionGeneratorInterface $mentionGenerator) { $this->name = $name; $this->prefix = $prefix; $this->identifierPattern = $identifierPattern; $this->mentionGenerator = $mentionGenerator; } public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::join( InlineParserMatch::string($this->prefix), InlineParserMatch::regex($this->identifierPattern) ); } public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); // The prefix must not have any other characters immediately prior $previousChar = $cursor->peek(-1); if ($previousChar !== null && \preg_match('/\w/', $previousChar)) { // peek() doesn't modify the cursor, so no need to restore state first return false; } [$prefix, $identifier] = $inlineContext->getSubMatches(); $mention = $this->mentionGenerator->generateMention(new Mention($this->name, $prefix, $identifier)); if ($mention === null) { return false; } $cursor->advanceBy($inlineContext->getFullMatchLength()); $inlineContext->getContainer()->appendChild($mention); return true; } public static function createWithStringTemplate(string $name, string $prefix, string $mentionRegex, string $urlTemplate): MentionParser { return new self($name, $prefix, $mentionRegex, new StringTemplateLinkGenerator($urlTemplate)); } public static function createWithCallback(string $name, string $prefix, string $mentionRegex, callable $callback): MentionParser { return new self($name, $prefix, $mentionRegex, new CallbackGenerator($callback)); } } commonmark/src/Extension/Mention/Generator/MentionGeneratorInterface.php 0000644 00000001036 15021222733 0022620 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention\Generator; use League\CommonMark\Extension\Mention\Mention; use League\CommonMark\Node\Inline\AbstractInline; interface MentionGeneratorInterface { public function generateMention(Mention $mention): ?AbstractInline; } commonmark/src/Extension/Mention/Generator/StringTemplateLinkGenerator.php 0000644 00000001516 15021222733 0023151 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention\Generator; use League\CommonMark\Extension\Mention\Mention; use League\CommonMark\Node\Inline\AbstractInline; final class StringTemplateLinkGenerator implements MentionGeneratorInterface { private string $urlTemplate; public function __construct(string $urlTemplate) { $this->urlTemplate = $urlTemplate; } public function generateMention(Mention $mention): ?AbstractInline { $mention->setUrl(\sprintf($this->urlTemplate, $mention->getIdentifier())); return $mention; } } commonmark/src/Extension/Mention/Generator/CallbackGenerator.php 0000644 00000003107 15021222733 0021063 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention\Generator; use League\CommonMark\Exception\LogicException; use League\CommonMark\Extension\Mention\Mention; use League\CommonMark\Node\Inline\AbstractInline; final class CallbackGenerator implements MentionGeneratorInterface { /** * A callback function which sets the URL on the passed mention and returns the mention, return a new AbstractInline based object or null if the mention is not a match * * @var callable(Mention): ?AbstractInline */ private $callback; public function __construct(callable $callback) { $this->callback = $callback; } /** * @throws LogicException */ public function generateMention(Mention $mention): ?AbstractInline { $result = \call_user_func($this->callback, $mention); if ($result === null) { return null; } if ($result instanceof AbstractInline && ! ($result instanceof Mention)) { return $result; } if ($result instanceof Mention && $result->hasUrl()) { return $mention; } throw new LogicException('CallbackGenerator callable must set the URL on the passed mention and return the mention, return a new AbstractInline based object or null if the mention is not a match'); } } commonmark/src/Extension/Mention/MentionExtension.php 0000644 00000005236 15021222733 0017105 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Mention; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Extension\Mention\Generator\MentionGeneratorInterface; use League\Config\ConfigurationBuilderInterface; use League\Config\Exception\InvalidConfigurationException; use Nette\Schema\Expect; final class MentionExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $isAValidPartialRegex = static function (string $regex): bool { $regex = '/' . $regex . '/i'; return @\preg_match($regex, '') !== false; }; $builder->addSchema('mentions', Expect::arrayOf( Expect::structure([ 'prefix' => Expect::string()->required(), 'pattern' => Expect::string()->assert($isAValidPartialRegex, 'Pattern must not include starting/ending delimiters (like "/")')->required(), 'generator' => Expect::anyOf( Expect::type(MentionGeneratorInterface::class), Expect::string(), Expect::type('callable') )->required(), ]) )); } public function register(EnvironmentBuilderInterface $environment): void { $mentions = $environment->getConfiguration()->get('mentions'); foreach ($mentions as $name => $mention) { if ($mention['generator'] instanceof MentionGeneratorInterface) { $environment->addInlineParser(new MentionParser($name, $mention['prefix'], $mention['pattern'], $mention['generator'])); } elseif (\is_string($mention['generator'])) { $environment->addInlineParser(MentionParser::createWithStringTemplate($name, $mention['prefix'], $mention['pattern'], $mention['generator'])); } elseif (\is_callable($mention['generator'])) { $environment->addInlineParser(MentionParser::createWithCallback($name, $mention['prefix'], $mention['pattern'], $mention['generator'])); } else { throw new InvalidConfigurationException(\sprintf('The "generator" provided for the "%s" MentionParser configuration must be a string template, callable, or an object that implements %s.', $name, MentionGeneratorInterface::class)); } } } } commonmark/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php 0000644 00000004123 15021222733 0022461 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\HeadingPermalink; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; /** * Extension which automatically anchor links to heading elements */ final class HeadingPermalinkExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('heading_permalink', Expect::structure([ 'min_heading_level' => Expect::int()->min(1)->max(6)->default(1), 'max_heading_level' => Expect::int()->min(1)->max(6)->default(6), 'insert' => Expect::anyOf(HeadingPermalinkProcessor::INSERT_BEFORE, HeadingPermalinkProcessor::INSERT_AFTER, HeadingPermalinkProcessor::INSERT_NONE)->default(HeadingPermalinkProcessor::INSERT_BEFORE), 'id_prefix' => Expect::string()->default('content'), 'apply_id_to_heading' => Expect::bool()->default(false), 'heading_class' => Expect::string()->default(''), 'fragment_prefix' => Expect::string()->default('content'), 'html_class' => Expect::string()->default('heading-permalink'), 'title' => Expect::string()->default('Permalink'), 'symbol' => Expect::string()->default(HeadingPermalinkRenderer::DEFAULT_SYMBOL), 'aria_hidden' => Expect::bool()->default(true), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addEventListener(DocumentParsedEvent::class, new HeadingPermalinkProcessor(), -100); $environment->addRenderer(HeadingPermalink::class, new HeadingPermalinkRenderer()); } } commonmark/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php 0000644 00000005741 15021222733 0022262 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\HeadingPermalink; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; /** * Renders the HeadingPermalink elements */ final class HeadingPermalinkRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { public const DEFAULT_SYMBOL = '¶'; /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } /** * @param HeadingPermalink $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { HeadingPermalink::assertInstanceOf($node); $slug = $node->getSlug(); $fragmentPrefix = (string) $this->config->get('heading_permalink/fragment_prefix'); if ($fragmentPrefix !== '') { $fragmentPrefix .= '-'; } $attrs = $node->data->getData('attributes'); $appendId = ! $this->config->get('heading_permalink/apply_id_to_heading'); if ($appendId) { $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); if ($idPrefix !== '') { $idPrefix .= '-'; } $attrs->set('id', $idPrefix . $slug); } $attrs->set('href', '#' . $fragmentPrefix . $slug); $attrs->append('class', $this->config->get('heading_permalink/html_class')); $hidden = $this->config->get('heading_permalink/aria_hidden'); if ($hidden) { $attrs->set('aria-hidden', 'true'); } $attrs->set('title', $this->config->get('heading_permalink/title')); $symbol = $this->config->get('heading_permalink/symbol'); \assert(\is_string($symbol)); return new HtmlElement('a', $attrs->export(), \htmlspecialchars($symbol), false); } public function getXmlTagName(Node $node): string { return 'heading_permalink'; } /** * @param HeadingPermalink $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { HeadingPermalink::assertInstanceOf($node); return [ 'slug' => $node->getSlug(), ]; } } commonmark/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php 0000644 00000007353 15021222733 0022474 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\HeadingPermalink; use League\CommonMark\Environment\EnvironmentAwareInterface; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Node\NodeIterator; use League\CommonMark\Node\RawMarkupContainerInterface; use League\CommonMark\Node\StringContainerHelper; use League\CommonMark\Normalizer\TextNormalizerInterface; use League\Config\ConfigurationInterface; use League\Config\Exception\InvalidConfigurationException; /** * Searches the Document for Heading elements and adds HeadingPermalinks to each one */ final class HeadingPermalinkProcessor implements EnvironmentAwareInterface { public const INSERT_BEFORE = 'before'; public const INSERT_AFTER = 'after'; public const INSERT_NONE = 'none'; /** @psalm-readonly-allow-private-mutation */ private TextNormalizerInterface $slugNormalizer; /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function setEnvironment(EnvironmentInterface $environment): void { $this->config = $environment->getConfiguration(); $this->slugNormalizer = $environment->getSlugNormalizer(); } public function __invoke(DocumentParsedEvent $e): void { $min = (int) $this->config->get('heading_permalink/min_heading_level'); $max = (int) $this->config->get('heading_permalink/max_heading_level'); $applyToHeading = (bool) $this->config->get('heading_permalink/apply_id_to_heading'); $idPrefix = (string) $this->config->get('heading_permalink/id_prefix'); $slugLength = (int) $this->config->get('slug_normalizer/max_length'); $headingClass = (string) $this->config->get('heading_permalink/heading_class'); if ($idPrefix !== '') { $idPrefix .= '-'; } foreach ($e->getDocument()->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if ($node instanceof Heading && $node->getLevel() >= $min && $node->getLevel() <= $max) { $this->addHeadingLink($node, $slugLength, $idPrefix, $applyToHeading, $headingClass); } } } private function addHeadingLink(Heading $heading, int $slugLength, string $idPrefix, bool $applyToHeading, string $headingClass): void { $text = StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class]); $slug = $this->slugNormalizer->normalize($text, [ 'node' => $heading, 'length' => $slugLength, ]); if ($applyToHeading) { $heading->data->set('attributes/id', $idPrefix . $slug); } if ($headingClass !== '') { $heading->data->append('attributes/class', $headingClass); } $headingLinkAnchor = new HeadingPermalink($slug); switch ($this->config->get('heading_permalink/insert')) { case self::INSERT_BEFORE: $heading->prependChild($headingLinkAnchor); return; case self::INSERT_AFTER: $heading->appendChild($headingLinkAnchor); return; case self::INSERT_NONE: return; default: throw new InvalidConfigurationException("Invalid configuration value for heading_permalink/insert; expected 'before', 'after', or 'none'"); } } } commonmark/src/Extension/HeadingPermalink/HeadingPermalink.php 0000644 00000001346 15021222733 0020570 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\HeadingPermalink; use League\CommonMark\Node\Inline\AbstractInline; /** * Represents an anchor link within a heading */ final class HeadingPermalink extends AbstractInline { /** @psalm-readonly */ private string $slug; public function __construct(string $slug) { parent::__construct(); $this->slug = $slug; } public function getSlug(): string { return $this->slug; } } commonmark/src/Extension/ConfigurableExtensionInterface.php 0000644 00000001005 15021222733 0020272 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension; use League\Config\ConfigurationBuilderInterface; interface ConfigurableExtensionInterface extends ExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void; } commonmark/src/Extension/Strikethrough/StrikethroughRenderer.php 0000644 00000002473 15021222733 0021361 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Strikethrough; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class StrikethroughRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Strikethrough $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Strikethrough::assertInstanceOf($node); return new HtmlElement('del', $node->data->get('attributes'), $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'strikethrough'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/Strikethrough/StrikethroughExtension.php 0000644 00000001447 15021222733 0021567 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Strikethrough; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ExtensionInterface; final class StrikethroughExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { $environment->addDelimiterProcessor(new StrikethroughDelimiterProcessor()); $environment->addRenderer(Strikethrough::class, new StrikethroughRenderer()); } } commonmark/src/Extension/Strikethrough/StrikethroughDelimiterProcessor.php 0000644 00000003241 15021222733 0023423 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Strikethrough; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Node\Inline\AbstractStringContainer; final class StrikethroughDelimiterProcessor implements DelimiterProcessorInterface { public function getOpeningCharacter(): string { return '~'; } public function getClosingCharacter(): string { return '~'; } public function getMinLength(): int { return 1; } public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int { if ($opener->getLength() > 2 && $closer->getLength() > 2) { return 0; } if ($opener->getLength() !== $closer->getLength()) { return 0; } return \min($opener->getLength(), $closer->getLength()); } public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void { $strikethrough = new Strikethrough(\str_repeat('~', $delimiterUse)); $tmp = $opener->next(); while ($tmp !== null && $tmp !== $closer) { $next = $tmp->next(); $strikethrough->appendChild($tmp); $tmp = $next; } $opener->insertAfter($strikethrough); } } commonmark/src/Extension/Strikethrough/Strikethrough.php 0000644 00000001624 15021222733 0017667 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> and uAfrica.com (http://uafrica.com) * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Strikethrough; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Node\Inline\DelimitedInterface; final class Strikethrough extends AbstractInline implements DelimitedInterface { private string $delimiter; public function __construct(string $delimiter = '~~') { parent::__construct(); $this->delimiter = $delimiter; } public function getOpeningDelimiter(): string { return $this->delimiter; } public function getClosingDelimiter(): string { return $this->delimiter; } } commonmark/src/Extension/CommonMark/Node/Block/ListData.php 0000644 00000002070 15021222733 0017646 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; class ListData { public ?int $start = null; public int $padding = 0; /** * @psalm-var ListBlock::TYPE_* * @phpstan-var ListBlock::TYPE_* */ public string $type; /** * @psalm-var ListBlock::DELIM_*|null * @phpstan-var ListBlock::DELIM_*|null */ public ?string $delimiter = null; public ?string $bulletChar = null; public int $markerOffset; public function equals(ListData $data): bool { return $this->type === $data->type && $this->delimiter === $data->delimiter && $this->bulletChar === $data->bulletChar; } } commonmark/src/Extension/CommonMark/Node/Block/ListBlock.php 0000644 00000002470 15021222733 0020033 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\TightBlockInterface; class ListBlock extends AbstractBlock implements TightBlockInterface { public const TYPE_BULLET = 'bullet'; public const TYPE_ORDERED = 'ordered'; public const DELIM_PERIOD = 'period'; public const DELIM_PAREN = 'paren'; protected bool $tight = false; // TODO Make lists tight by default in v3 /** @psalm-readonly */ protected ListData $listData; public function __construct(ListData $listData) { parent::__construct(); $this->listData = $listData; } public function getListData(): ListData { return $this->listData; } public function isTight(): bool { return $this->tight; } public function setTight(bool $tight): void { $this->tight = $tight; } } commonmark/src/Extension/CommonMark/Node/Block/ThematicBreak.php 0000644 00000000655 15021222733 0020653 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; class ThematicBreak extends AbstractBlock { } commonmark/src/Extension/CommonMark/Node/Block/IndentedCode.php 0000644 00000001350 15021222733 0020466 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\StringContainerInterface; final class IndentedCode extends AbstractBlock implements StringContainerInterface { private string $literal = ''; public function getLiteral(): string { return $this->literal; } public function setLiteral(string $literal): void { $this->literal = $literal; } } commonmark/src/Extension/CommonMark/Node/Block/HtmlBlock.php 0000644 00000003447 15021222733 0020031 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\RawMarkupContainerInterface; final class HtmlBlock extends AbstractBlock implements RawMarkupContainerInterface { // Any changes to these constants should be reflected in .phpstorm.meta.php public const TYPE_1_CODE_CONTAINER = 1; public const TYPE_2_COMMENT = 2; public const TYPE_3 = 3; public const TYPE_4 = 4; public const TYPE_5_CDATA = 5; public const TYPE_6_BLOCK_ELEMENT = 6; public const TYPE_7_MISC_ELEMENT = 7; /** * @psalm-var self::TYPE_* $type * @phpstan-var self::TYPE_* $type */ private int $type; private string $literal = ''; /** * @psalm-param self::TYPE_* $type * * @phpstan-param self::TYPE_* $type */ public function __construct(int $type) { parent::__construct(); $this->type = $type; } /** * @psalm-return self::TYPE_* * * @phpstan-return self::TYPE_* */ public function getType(): int { return $this->type; } /** * @psalm-param self::TYPE_* $type * * @phpstan-param self::TYPE_* $type */ public function setType(int $type): void { $this->type = $type; } public function getLiteral(): string { return $this->literal; } public function setLiteral(string $literal): void { $this->literal = $literal; } } commonmark/src/Extension/CommonMark/Node/Block/Heading.php 0000644 00000001542 15021222733 0017503 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; final class Heading extends AbstractBlock { private int $level; public function __construct(int $level) { parent::__construct(); $this->level = $level; } public function getLevel(): int { return $this->level; } public function setLevel(int $level): void { $this->level = $level; } } commonmark/src/Extension/CommonMark/Node/Block/BlockQuote.php 0000644 00000000652 15021222733 0020215 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; class BlockQuote extends AbstractBlock { } commonmark/src/Extension/CommonMark/Node/Block/ListItem.php 0000644 00000001500 15021222733 0017670 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; class ListItem extends AbstractBlock { /** @psalm-readonly */ protected ListData $listData; public function __construct(ListData $listData) { parent::__construct(); $this->listData = $listData; } public function getListData(): ListData { return $this->listData; } } commonmark/src/Extension/CommonMark/Node/Block/FencedCode.php 0000644 00000003770 15021222733 0020130 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Block; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\StringContainerInterface; final class FencedCode extends AbstractBlock implements StringContainerInterface { private ?string $info = null; private string $literal = ''; private int $length; private string $char; private int $offset; public function __construct(int $length, string $char, int $offset) { parent::__construct(); $this->length = $length; $this->char = $char; $this->offset = $offset; } public function getInfo(): ?string { return $this->info; } /** * @return string[] */ public function getInfoWords(): array { return \preg_split('/\s+/', $this->info ?? '') ?: []; } public function setInfo(string $info): void { $this->info = $info; } public function getLiteral(): string { return $this->literal; } public function setLiteral(string $literal): void { $this->literal = $literal; } public function getChar(): string { return $this->char; } public function setChar(string $char): void { $this->char = $char; } public function getLength(): int { return $this->length; } public function setLength(int $length): void { $this->length = $length; } public function getOffset(): int { return $this->offset; } public function setOffset(int $offset): void { $this->offset = $offset; } } commonmark/src/Extension/CommonMark/Node/Inline/HtmlInline.php 0000644 00000001241 15021222733 0020367 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\AbstractStringContainer; use League\CommonMark\Node\RawMarkupContainerInterface; final class HtmlInline extends AbstractStringContainer implements RawMarkupContainerInterface { } commonmark/src/Extension/CommonMark/Node/Inline/Image.php 0000644 00000002125 15021222733 0017350 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\Text; class Image extends AbstractWebResource { protected ?string $title = null; public function __construct(string $url, ?string $label = null, ?string $title = null) { parent::__construct($url); if ($label !== null && $label !== '') { $this->appendChild(new Text($label)); } $this->title = $title; } public function getTitle(): ?string { if ($this->title === '') { return null; } return $this->title; } public function setTitle(?string $title): void { $this->title = $title; } } commonmark/src/Extension/CommonMark/Node/Inline/Link.php 0000644 00000002124 15021222733 0017222 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\Text; class Link extends AbstractWebResource { protected ?string $title = null; public function __construct(string $url, ?string $label = null, ?string $title = null) { parent::__construct($url); if ($label !== null && $label !== '') { $this->appendChild(new Text($label)); } $this->title = $title; } public function getTitle(): ?string { if ($this->title === '') { return null; } return $this->title; } public function setTitle(?string $title): void { $this->title = $title; } } commonmark/src/Extension/CommonMark/Node/Inline/AbstractWebResource.php 0000644 00000001557 15021222733 0022247 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\AbstractInline; abstract class AbstractWebResource extends AbstractInline { protected string $url; public function __construct(string $url) { parent::__construct(); $this->url = $url; } public function getUrl(): string { return $this->url; } public function setUrl(string $url): void { $this->url = $url; } } commonmark/src/Extension/CommonMark/Node/Inline/Emphasis.php 0000644 00000001756 15021222733 0020110 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Node\Inline\DelimitedInterface; final class Emphasis extends AbstractInline implements DelimitedInterface { private string $delimiter; public function __construct(string $delimiter = '_') { parent::__construct(); $this->delimiter = $delimiter; } public function getOpeningDelimiter(): string { return $this->delimiter; } public function getClosingDelimiter(): string { return $this->delimiter; } } commonmark/src/Extension/CommonMark/Node/Inline/Strong.php 0000644 00000001755 15021222733 0017612 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Node\Inline\DelimitedInterface; final class Strong extends AbstractInline implements DelimitedInterface { private string $delimiter; public function __construct(string $delimiter = '**') { parent::__construct(); $this->delimiter = $delimiter; } public function getOpeningDelimiter(): string { return $this->delimiter; } public function getClosingDelimiter(): string { return $this->delimiter; } } commonmark/src/Extension/CommonMark/Node/Inline/Code.php 0000644 00000001066 15021222733 0017203 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Node\Inline; use League\CommonMark\Node\Inline\AbstractStringContainer; class Code extends AbstractStringContainer { } commonmark/src/Extension/CommonMark/CommonMarkCoreExtension.php 0000644 00000012125 15021222733 0020775 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Node as CoreNode; use League\CommonMark\Parser as CoreParser; use League\CommonMark\Renderer as CoreRenderer; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class CommonMarkCoreExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('commonmark', Expect::structure([ 'use_asterisk' => Expect::bool(true), 'use_underscore' => Expect::bool(true), 'enable_strong' => Expect::bool(true), 'enable_em' => Expect::bool(true), 'unordered_list_markers' => Expect::listOf('string')->min(1)->default(['*', '+', '-'])->mergeDefaults(false), ])); } // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma,Squiz.WhiteSpace.SemicolonSpacing.Incorrect public function register(EnvironmentBuilderInterface $environment): void { $environment ->addBlockStartParser(new Parser\Block\BlockQuoteStartParser(), 70) ->addBlockStartParser(new Parser\Block\HeadingStartParser(), 60) ->addBlockStartParser(new Parser\Block\FencedCodeStartParser(), 50) ->addBlockStartParser(new Parser\Block\HtmlBlockStartParser(), 40) ->addBlockStartParser(new Parser\Block\ThematicBreakStartParser(), 20) ->addBlockStartParser(new Parser\Block\ListBlockStartParser(), 10) ->addBlockStartParser(new Parser\Block\IndentedCodeStartParser(), -100) ->addInlineParser(new CoreParser\Inline\NewlineParser(), 200) ->addInlineParser(new Parser\Inline\BacktickParser(), 150) ->addInlineParser(new Parser\Inline\EscapableParser(), 80) ->addInlineParser(new Parser\Inline\EntityParser(), 70) ->addInlineParser(new Parser\Inline\AutolinkParser(), 50) ->addInlineParser(new Parser\Inline\HtmlInlineParser(), 40) ->addInlineParser(new Parser\Inline\CloseBracketParser(), 30) ->addInlineParser(new Parser\Inline\OpenBracketParser(), 20) ->addInlineParser(new Parser\Inline\BangParser(), 10) ->addRenderer(Node\Block\BlockQuote::class, new Renderer\Block\BlockQuoteRenderer(), 0) ->addRenderer(CoreNode\Block\Document::class, new CoreRenderer\Block\DocumentRenderer(), 0) ->addRenderer(Node\Block\FencedCode::class, new Renderer\Block\FencedCodeRenderer(), 0) ->addRenderer(Node\Block\Heading::class, new Renderer\Block\HeadingRenderer(), 0) ->addRenderer(Node\Block\HtmlBlock::class, new Renderer\Block\HtmlBlockRenderer(), 0) ->addRenderer(Node\Block\IndentedCode::class, new Renderer\Block\IndentedCodeRenderer(), 0) ->addRenderer(Node\Block\ListBlock::class, new Renderer\Block\ListBlockRenderer(), 0) ->addRenderer(Node\Block\ListItem::class, new Renderer\Block\ListItemRenderer(), 0) ->addRenderer(CoreNode\Block\Paragraph::class, new CoreRenderer\Block\ParagraphRenderer(), 0) ->addRenderer(Node\Block\ThematicBreak::class, new Renderer\Block\ThematicBreakRenderer(), 0) ->addRenderer(Node\Inline\Code::class, new Renderer\Inline\CodeRenderer(), 0) ->addRenderer(Node\Inline\Emphasis::class, new Renderer\Inline\EmphasisRenderer(), 0) ->addRenderer(Node\Inline\HtmlInline::class, new Renderer\Inline\HtmlInlineRenderer(), 0) ->addRenderer(Node\Inline\Image::class, new Renderer\Inline\ImageRenderer(), 0) ->addRenderer(Node\Inline\Link::class, new Renderer\Inline\LinkRenderer(), 0) ->addRenderer(CoreNode\Inline\Newline::class, new CoreRenderer\Inline\NewlineRenderer(), 0) ->addRenderer(Node\Inline\Strong::class, new Renderer\Inline\StrongRenderer(), 0) ->addRenderer(CoreNode\Inline\Text::class, new CoreRenderer\Inline\TextRenderer(), 0) ; if ($environment->getConfiguration()->get('commonmark/use_asterisk')) { $environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*')); } if ($environment->getConfiguration()->get('commonmark/use_underscore')) { $environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_')); } } } commonmark/src/Extension/CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php 0000644 00000006225 15021222733 0025435 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java) * - (c) Atlassian Pty Ltd * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Delimiter\Processor; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis; use League\CommonMark\Extension\CommonMark\Node\Inline\Strong; use League\CommonMark\Node\Inline\AbstractStringContainer; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, ConfigurationAwareInterface { /** @psalm-readonly */ private string $char; /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; /** * @param string $char The emphasis character to use (typically '*' or '_') */ public function __construct(string $char) { $this->char = $char; } public function getOpeningCharacter(): string { return $this->char; } public function getClosingCharacter(): string { return $this->char; } public function getMinLength(): int { return 1; } public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int { // "Multiple of 3" rule for internal delimiter runs if (($opener->canClose() || $closer->canOpen()) && $closer->getOriginalLength() % 3 !== 0 && ($opener->getOriginalLength() + $closer->getOriginalLength()) % 3 === 0) { return 0; } // Calculate actual number of delimiters used from this closer if ($opener->getLength() >= 2 && $closer->getLength() >= 2) { if ($this->config->get('commonmark/enable_strong')) { return 2; } return 0; } if ($this->config->get('commonmark/enable_em')) { return 1; } return 0; } public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void { if ($delimiterUse === 1) { $emphasis = new Emphasis($this->char); } elseif ($delimiterUse === 2) { $emphasis = new Strong($this->char . $this->char); } else { return; } $next = $opener->next(); while ($next !== null && $next !== $closer) { $tmp = $next->next(); $emphasis->appendChild($next); $next = $tmp; } $opener->insertAfter($emphasis); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeParser.php 0000644 00000005344 15021222733 0021653 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\ArrayCollection; use League\CommonMark\Util\RegexHelper; final class FencedCodeParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private FencedCode $block; /** @var ArrayCollection<string> */ private ArrayCollection $strings; public function __construct(int $fenceLength, string $fenceChar, int $fenceOffset) { $this->block = new FencedCode($fenceLength, $fenceChar, $fenceOffset); $this->strings = new ArrayCollection(); } public function getBlock(): FencedCode { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { // Check for closing code fence if (! $cursor->isIndented() && $cursor->getNextNonSpaceCharacter() === $this->block->getChar()) { $match = RegexHelper::matchFirst('/^(?:`{3,}|~{3,})(?=[ \t]*$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition()); if ($match !== null && \strlen($match[0]) >= $this->block->getLength()) { // closing fence - we're at end of line, so we can finalize now return BlockContinue::finished(); } } // Skip optional spaces of fence offset // Optimization: don't attempt to match if we're at a non-space position if ($cursor->getNextNonSpacePosition() > $cursor->getPosition()) { $cursor->match('/^ {0,' . $this->block->getOffset() . '}/'); } return BlockContinue::at($cursor); } public function addLine(string $line): void { $this->strings[] = $line; } public function closeBlock(): void { // first line becomes info string $firstLine = $this->strings->first(); if ($firstLine === false) { $firstLine = ''; } $this->block->setInfo(RegexHelper::unescape(\trim($firstLine))); if ($this->strings->count() === 1) { $this->block->setLiteral(''); } else { $this->block->setLiteral(\implode("\n", $this->strings->slice(1)) . "\n"); } } } commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeStartParser.php 0000644 00000002273 15021222733 0023235 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class IndentedCodeStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if (! $cursor->isIndented()) { return BlockStart::none(); } if ($parserState->getActiveBlockParser()->getBlock() instanceof Paragraph) { return BlockStart::none(); } if ($cursor->isBlank()) { return BlockStart::none(); } $cursor->advanceBy(Cursor::INDENT_LEVEL, true); return BlockStart::of(new IndentedCodeParser())->at($cursor); } } commonmark/src/Extension/CommonMark/Parser/Block/ListItemParser.php 0000644 00000004564 15021222733 0021431 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ListData; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class ListItemParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private ListItem $block; public function __construct(ListData $listData) { $this->block = new ListItem($listData); } public function getBlock(): ListItem { return $this->block; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return ! $childBlock instanceof ListItem; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isBlank()) { if ($this->block->firstChild() === null) { // Blank line after empty list item return BlockContinue::none(); } $cursor->advanceToNextNonSpaceOrTab(); return BlockContinue::at($cursor); } $contentIndent = $this->block->getListData()->markerOffset + $this->getBlock()->getListData()->padding; if ($cursor->getIndent() >= $contentIndent) { $cursor->advanceBy($contentIndent, true); return BlockContinue::at($cursor); } // Note: We'll hit this case for lazy continuation lines, they will get added later. return BlockContinue::none(); } public function closeBlock(): void { if (($lastChild = $this->block->lastChild()) instanceof AbstractBlock) { $this->block->setEndLine($lastChild->getEndLine()); } else { // Empty list item $this->block->setEndLine($this->block->getStartLine()); } } } commonmark/src/Extension/CommonMark/Parser/Block/HeadingParser.php 0000644 00000002744 15021222733 0021234 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockContinueParserWithInlinesInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\InlineParserEngineInterface; final class HeadingParser extends AbstractBlockContinueParser implements BlockContinueParserWithInlinesInterface { /** @psalm-readonly */ private Heading $block; private string $content; public function __construct(int $level, string $content) { $this->block = new Heading($level); $this->content = $content; } public function getBlock(): Heading { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::none(); } public function parseInlines(InlineParserEngineInterface $inlineParser): void { $inlineParser->parse($this->content, $this->block); } } commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeStartParser.php 0000644 00000002342 15021222733 0022664 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class FencedCodeStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented() || ! \in_array($cursor->getNextNonSpaceCharacter(), ['`', '~'], true)) { return BlockStart::none(); } $indent = $cursor->getIndent(); $fence = $cursor->match('/^[ \t]*(?:`{3,}(?!.*`)|~{3,})/'); if ($fence === null) { return BlockStart::none(); } // fenced code block $fence = \ltrim($fence, " \t"); return BlockStart::of(new FencedCodeParser(\strlen($fence), $fence[0], $indent))->at($cursor); } } commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakParser.php 0000644 00000002226 15021222733 0022373 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class ThematicBreakParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private ThematicBreak $block; public function __construct() { $this->block = new ThematicBreak(); } public function getBlock(): ThematicBreak { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { // a horizontal rule can never container > 1 line, so fail to match return BlockContinue::none(); } } commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockParser.php 0000644 00000004372 15021222733 0021553 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\RegexHelper; final class HtmlBlockParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private HtmlBlock $block; private string $content = ''; private bool $finished = false; /** * @psalm-param HtmlBlock::TYPE_* $blockType * * @phpstan-param HtmlBlock::TYPE_* $blockType */ public function __construct(int $blockType) { $this->block = new HtmlBlock($blockType); } public function getBlock(): HtmlBlock { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($this->finished) { return BlockContinue::none(); } if ($cursor->isBlank() && \in_array($this->block->getType(), [HtmlBlock::TYPE_6_BLOCK_ELEMENT, HtmlBlock::TYPE_7_MISC_ELEMENT], true)) { return BlockContinue::none(); } return BlockContinue::at($cursor); } public function addLine(string $line): void { if ($this->content !== '') { $this->content .= "\n"; } $this->content .= $line; // Check for end condition // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($this->block->getType() <= HtmlBlock::TYPE_5_CDATA) { if (\preg_match(RegexHelper::getHtmlBlockCloseRegex($this->block->getType()), $line) === 1) { $this->finished = true; } } } public function closeBlock(): void { $this->block->setLiteral($this->content); $this->content = ''; } } commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockStartParser.php 0000644 00000004126 15021222733 0022566 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; final class HtmlBlockStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented() || $cursor->getNextNonSpaceCharacter() !== '<') { return BlockStart::none(); } $tmpCursor = clone $cursor; $tmpCursor->advanceToNextNonSpaceOrTab(); $line = $tmpCursor->getRemainder(); for ($blockType = 1; $blockType <= 7; $blockType++) { /** @psalm-var HtmlBlock::TYPE_* $blockType */ /** @phpstan-var HtmlBlock::TYPE_* $blockType */ $match = RegexHelper::matchAt( RegexHelper::getHtmlBlockOpenRegex($blockType), $line ); if ($match !== null && ($blockType < 7 || $this->isType7BlockAllowed($cursor, $parserState))) { return BlockStart::of(new HtmlBlockParser($blockType))->at($cursor); } } return BlockStart::none(); } private function isType7BlockAllowed(Cursor $cursor, MarkdownParserStateInterface $parserState): bool { // Type 7 blocks can't interrupt paragraphs if ($parserState->getLastMatchedBlockParser()->getBlock() instanceof Paragraph) { return false; } // Even lazy ones return ! $parserState->getActiveBlockParser()->canHaveLazyContinuationLines(); } } commonmark/src/Extension/CommonMark/Parser/Block/HeadingStartParser.php 0000644 00000004735 15021222733 0022254 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; class HeadingStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented() || ! \in_array($cursor->getNextNonSpaceCharacter(), ['#', '-', '='], true)) { return BlockStart::none(); } $cursor->advanceToNextNonSpaceOrTab(); if ($atxHeading = self::getAtxHeader($cursor)) { return BlockStart::of($atxHeading)->at($cursor); } $setextHeadingLevel = self::getSetextHeadingLevel($cursor); if ($setextHeadingLevel > 0) { $content = $parserState->getParagraphContent(); if ($content !== null) { $cursor->advanceToEnd(); return BlockStart::of(new HeadingParser($setextHeadingLevel, $content)) ->at($cursor) ->replaceActiveBlockParser(); } } return BlockStart::none(); } private static function getAtxHeader(Cursor $cursor): ?HeadingParser { $match = RegexHelper::matchFirst('/^#{1,6}(?:[ \t]+|$)/', $cursor->getRemainder()); if (! $match) { return null; } $cursor->advanceToNextNonSpaceOrTab(); $cursor->advanceBy(\strlen($match[0])); $level = \strlen(\trim($match[0])); $str = $cursor->getRemainder(); $str = \preg_replace('/^[ \t]*#+[ \t]*$/', '', $str); \assert(\is_string($str)); $str = \preg_replace('/[ \t]+#+[ \t]*$/', '', $str); \assert(\is_string($str)); return new HeadingParser($level, $str); } private static function getSetextHeadingLevel(Cursor $cursor): int { $match = RegexHelper::matchFirst('/^(?:=+|-+)[ \t]*$/', $cursor->getRemainder()); if ($match === null) { return 0; } return $match[0][0] === '=' ? 1 : 2; } } commonmark/src/Extension/CommonMark/Parser/Block/ListBlockParser.php 0000644 00000005600 15021222733 0021555 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListData; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class ListBlockParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private ListBlock $block; public function __construct(ListData $listData) { $this->block = new ListBlock($listData); } public function getBlock(): ListBlock { return $this->block; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return $childBlock instanceof ListItem; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { // List blocks themselves don't have any markers, only list items. So try to stay in the list. // If there is a block start other than list item, canContain makes sure that this list is closed. return BlockContinue::at($cursor); } public function closeBlock(): void { $item = $this->block->firstChild(); while ($item instanceof AbstractBlock) { // check for non-final list item ending with blank line: if ($item->next() !== null && self::endsWithBlankLine($item)) { $this->block->setTight(false); break; } // recurse into children of list item, to see if there are spaces between any of them $subitem = $item->firstChild(); while ($subitem instanceof AbstractBlock) { if ($subitem->next() && self::endsWithBlankLine($subitem)) { $this->block->setTight(false); break 2; } $subitem = $subitem->next(); } $item = $item->next(); } $lastChild = $this->block->lastChild(); if ($lastChild instanceof AbstractBlock) { $this->block->setEndLine($lastChild->getEndLine()); } } private static function endsWithBlankLine(AbstractBlock $block): bool { $next = $block->next(); return $next instanceof AbstractBlock && $block->getEndLine() !== $next->getStartLine() - 1; } } commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteParser.php 0000644 00000003052 15021222733 0021736 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; final class BlockQuoteParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private BlockQuote $block; public function __construct() { $this->block = new BlockQuote(); } public function getBlock(): BlockQuote { return $this->block; } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if (! $cursor->isIndented() && $cursor->getNextNonSpaceCharacter() === '>') { $cursor->advanceToNextNonSpaceOrTab(); $cursor->advanceBy(1); $cursor->advanceBySpaceOrTab(); return BlockContinue::at($cursor); } return BlockContinue::none(); } } commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakStartParser.php 0000644 00000002367 15021222733 0023417 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; final class ThematicBreakStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented()) { return BlockStart::none(); } $match = RegexHelper::matchAt(RegexHelper::REGEX_THEMATIC_BREAK, $cursor->getLine(), $cursor->getNextNonSpacePosition()); if ($match === null) { return BlockStart::none(); } // Advance to the end of the string, consuming the entire line (of the thematic break) $cursor->advanceToEnd(); return BlockStart::of(new ThematicBreakParser())->at($cursor); } } commonmark/src/Extension/CommonMark/Parser/Block/ListBlockStartParser.php 0000644 00000012720 15021222733 0022574 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListData; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\RegexHelper; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class ListBlockStartParser implements BlockStartParserInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ?ConfigurationInterface $config = null; /** * @psalm-var non-empty-string|null * * @psalm-readonly-allow-private-mutation */ private ?string $listMarkerRegex = null; public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented()) { return BlockStart::none(); } $listData = $this->parseList($cursor, $parserState->getParagraphContent() !== null); if ($listData === null) { return BlockStart::none(); } $listItemParser = new ListItemParser($listData); // prepend the list block if needed $matched = $parserState->getLastMatchedBlockParser(); if (! ($matched instanceof ListBlockParser) || ! $listData->equals($matched->getBlock()->getListData())) { $listBlockParser = new ListBlockParser($listData); // We start out with assuming a list is tight. If we find a blank line, we set it to loose later. // TODO for 3.0: Just make them tight by default in the block so we can remove this call $listBlockParser->getBlock()->setTight(true); return BlockStart::of($listBlockParser, $listItemParser)->at($cursor); } return BlockStart::of($listItemParser)->at($cursor); } private function parseList(Cursor $cursor, bool $inParagraph): ?ListData { $indent = $cursor->getIndent(); $tmpCursor = clone $cursor; $tmpCursor->advanceToNextNonSpaceOrTab(); $rest = $tmpCursor->getRemainder(); if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) { $data = new ListData(); $data->markerOffset = $indent; $data->type = ListBlock::TYPE_BULLET; $data->delimiter = null; $data->bulletChar = $rest[0]; $markerLength = 1; } elseif (($matches = RegexHelper::matchFirst('/^(\d{1,9})([.)])/', $rest)) && (! $inParagraph || $matches[1] === '1')) { $data = new ListData(); $data->markerOffset = $indent; $data->type = ListBlock::TYPE_ORDERED; $data->start = (int) $matches[1]; $data->delimiter = $matches[2] === '.' ? ListBlock::DELIM_PERIOD : ListBlock::DELIM_PAREN; $data->bulletChar = null; $markerLength = \strlen($matches[0]); } else { return null; } // Make sure we have spaces after $nextChar = $tmpCursor->peek($markerLength); if (! ($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) { return null; } // If it interrupts paragraph, make sure first line isn't blank if ($inParagraph && ! RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) { return null; } $cursor->advanceToNextNonSpaceOrTab(); // to start of marker $cursor->advanceBy($markerLength, true); // to end of marker $data->padding = self::calculateListMarkerPadding($cursor, $markerLength); return $data; } private static function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int { $start = $cursor->saveState(); $spacesStartCol = $cursor->getColumn(); while ($cursor->getColumn() - $spacesStartCol < 5) { if (! $cursor->advanceBySpaceOrTab()) { break; } } $blankItem = $cursor->peek() === null; $spacesAfterMarker = $cursor->getColumn() - $spacesStartCol; if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) { $cursor->restoreState($start); $cursor->advanceBySpaceOrTab(); return $markerLength + 1; } return $markerLength + $spacesAfterMarker; } /** * @psalm-return non-empty-string */ private function generateListMarkerRegex(): string { // No configuration given - use the defaults if ($this->config === null) { return $this->listMarkerRegex = '/^[*+-]/'; } $markers = $this->config->get('commonmark/unordered_list_markers'); \assert(\is_array($markers)); return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/'; } } commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeParser.php 0000644 00000004145 15021222733 0022217 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Util\ArrayCollection; final class IndentedCodeParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private IndentedCode $block; /** @var ArrayCollection<string> */ private ArrayCollection $strings; public function __construct() { $this->block = new IndentedCode(); $this->strings = new ArrayCollection(); } public function getBlock(): IndentedCode { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isIndented()) { $cursor->advanceBy(Cursor::INDENT_LEVEL, true); return BlockContinue::at($cursor); } if ($cursor->isBlank()) { $cursor->advanceToNextNonSpaceOrTab(); return BlockContinue::at($cursor); } return BlockContinue::none(); } public function addLine(string $line): void { $this->strings[] = $line; } public function closeBlock(): void { $lines = $this->strings->toArray(); // Note that indented code block cannot be empty, so $lines will always have at least one non-empty element while (\preg_match('/^[ \t]*$/', \end($lines))) { // @phpstan-ignore-line \array_pop($lines); } $this->block->setLiteral(\implode("\n", $lines) . "\n"); $this->block->setEndLine($this->block->getStartLine() + \count($lines) - 1); } } commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteStartParser.php 0000644 00000002131 15021222733 0022751 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Block; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; final class BlockQuoteStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented()) { return BlockStart::none(); } if ($cursor->getNextNonSpaceCharacter() !== '>') { return BlockStart::none(); } $cursor->advanceToNextNonSpaceOrTab(); $cursor->advanceBy(1); $cursor->advanceBySpaceOrTab(); return BlockStart::of(new BlockQuoteParser())->at($cursor); } } commonmark/src/Extension/CommonMark/Parser/Inline/BangParser.php 0000644 00000002544 15021222733 0020726 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Delimiter\Delimiter; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class BangParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::string('!['); } public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); $cursor->advanceBy(2); $node = new Text('![', ['delim' => true]); $inlineContext->getContainer()->appendChild($node); // Add entry to stack for this opener $delimiter = new Delimiter('!', 1, $node, true, false, $cursor->getPosition()); $inlineContext->getDelimiterStack()->push($delimiter); return true; } } commonmark/src/Extension/CommonMark/Parser/Inline/OpenBracketParser.php 0000644 00000002541 15021222733 0022251 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Delimiter\Delimiter; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class OpenBracketParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::string('['); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy(1); $node = new Text('[', ['delim' => true]); $inlineContext->getContainer()->appendChild($node); // Add entry to stack for this opener $delimiter = new Delimiter('[', 1, $node, true, false, $inlineContext->getCursor()->getPosition()); $inlineContext->getDelimiterStack()->push($delimiter); return true; } } commonmark/src/Extension/CommonMark/Parser/Inline/CloseBracketParser.php 0000644 00000016323 15021222733 0022420 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Environment\EnvironmentAwareInterface; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Extension\CommonMark\Node\Inline\AbstractWebResource; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Extension\Mention\Mention; use League\CommonMark\Node\Inline\AdjacentTextMerger; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceMapInterface; use League\CommonMark\Util\LinkParserHelper; use League\CommonMark\Util\RegexHelper; final class CloseBracketParser implements InlineParserInterface, EnvironmentAwareInterface { /** @psalm-readonly-allow-private-mutation */ private EnvironmentInterface $environment; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::string(']'); } public function parse(InlineParserContext $inlineContext): bool { // Look through stack of delimiters for a [ or ! $opener = $inlineContext->getDelimiterStack()->searchByCharacter(['[', '!']); if ($opener === null) { return false; } if (! $opener->isActive()) { // no matched opener; remove from emphasis stack $inlineContext->getDelimiterStack()->removeDelimiter($opener); return false; } $cursor = $inlineContext->getCursor(); $startPos = $cursor->getPosition(); $previousState = $cursor->saveState(); $cursor->advanceBy(1); // Check to see if we have a link/image // Inline link? if ($result = $this->tryParseInlineLinkAndTitle($cursor)) { $link = $result; } elseif ($link = $this->tryParseReference($cursor, $inlineContext->getReferenceMap(), $opener->getIndex(), $startPos)) { $reference = $link; $link = ['url' => $link->getDestination(), 'title' => $link->getTitle()]; } else { // No match $inlineContext->getDelimiterStack()->removeDelimiter($opener); // Remove this opener from stack $cursor->restoreState($previousState); return false; } $isImage = $opener->getChar() === '!'; $inline = $this->createInline($link['url'], $link['title'], $isImage, $reference ?? null); $opener->getInlineNode()->replaceWith($inline); while (($label = $inline->next()) !== null) { // Is there a Mention or Link contained within this link? // CommonMark does not allow nested links, so we'll restore the original text. if ($label instanceof Mention) { $label->replaceWith($replacement = new Text($label->getPrefix() . $label->getIdentifier())); $inline->appendChild($replacement); } elseif ($label instanceof Link) { foreach ($label->children() as $child) { $label->insertBefore($child); } $label->detach(); } else { $inline->appendChild($label); } } // Process delimiters such as emphasis inside link/image $delimiterStack = $inlineContext->getDelimiterStack(); $stackBottom = $opener->getPrevious(); $delimiterStack->processDelimiters($stackBottom, $this->environment->getDelimiterProcessors()); $delimiterStack->removeAll($stackBottom); // Merge any adjacent Text nodes together AdjacentTextMerger::mergeChildNodes($inline); // processEmphasis will remove this and later delimiters. // Now, for a link, we also remove earlier link openers (no links in links) if (! $isImage) { $inlineContext->getDelimiterStack()->removeEarlierMatches('['); } return true; } public function setEnvironment(EnvironmentInterface $environment): void { $this->environment = $environment; } /** * @return array<string, string>|null */ private function tryParseInlineLinkAndTitle(Cursor $cursor): ?array { if ($cursor->getCurrentCharacter() !== '(') { return null; } $previousState = $cursor->saveState(); $cursor->advanceBy(1); $cursor->advanceToNextNonSpaceOrNewline(); if (($dest = LinkParserHelper::parseLinkDestination($cursor)) === null) { $cursor->restoreState($previousState); return null; } $cursor->advanceToNextNonSpaceOrNewline(); $previousCharacter = $cursor->peek(-1); // We know from previous lines that we've advanced at least one space so far, so this next call should never be null \assert(\is_string($previousCharacter)); $title = ''; // make sure there's a space before the title: if (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $previousCharacter)) { $title = LinkParserHelper::parseLinkTitle($cursor) ?? ''; } $cursor->advanceToNextNonSpaceOrNewline(); if ($cursor->getCurrentCharacter() !== ')') { $cursor->restoreState($previousState); return null; } $cursor->advanceBy(1); return ['url' => $dest, 'title' => $title]; } private function tryParseReference(Cursor $cursor, ReferenceMapInterface $referenceMap, ?int $openerIndex, int $startPos): ?ReferenceInterface { if ($openerIndex === null) { return null; } $savePos = $cursor->saveState(); $beforeLabel = $cursor->getPosition(); $n = LinkParserHelper::parseLinkLabel($cursor); if ($n === 0 || $n === 2) { $start = $openerIndex; $length = $startPos - $openerIndex; } else { $start = $beforeLabel + 1; $length = $n - 2; } $referenceLabel = $cursor->getSubstring($start, $length); if ($n === 0) { // If shortcut reference link, rewind before spaces we skipped $cursor->restoreState($savePos); } return $referenceMap->get($referenceLabel); } private function createInline(string $url, string $title, bool $isImage, ?ReferenceInterface $reference = null): AbstractWebResource { if ($isImage) { $inline = new Image($url, null, $title); } else { $inline = new Link($url, null, $title); } if ($reference) { $inline->data->set('reference', $reference); } return $inline; } } commonmark/src/Extension/CommonMark/Parser/Inline/EscapableParser.php 0000644 00000003153 15021222733 0021733 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\RegexHelper; final class EscapableParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::string('\\'); } public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); $nextChar = $cursor->peek(); if ($nextChar === "\n") { $cursor->advanceBy(2); $inlineContext->getContainer()->appendChild(new Newline(Newline::HARDBREAK)); return true; } if ($nextChar !== null && RegexHelper::isEscapable($nextChar)) { $cursor->advanceBy(2); $inlineContext->getContainer()->appendChild(new Text($nextChar)); return true; } $cursor->advanceBy(1); $inlineContext->getContainer()->appendChild(new Text('\\')); return true; } } commonmark/src/Extension/CommonMark/Parser/Inline/AutolinkParser.php 0000644 00000003542 15021222733 0021644 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\UrlEncoder; final class AutolinkParser implements InlineParserInterface { private const EMAIL_REGEX = '<([a-zA-Z0-9.!#$%&\'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>'; private const OTHER_LINK_REGEX = '<([A-Za-z][A-Za-z0-9.+-]{1,31}:[^<>\x00-\x20]*)>'; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex(self::EMAIL_REGEX . '|' . self::OTHER_LINK_REGEX); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); $matches = $inlineContext->getMatches(); if ($matches[1] !== '') { $inlineContext->getContainer()->appendChild(new Link('mailto:' . UrlEncoder::unescapeAndEncode($matches[1]), $matches[1])); return true; } if ($matches[2] !== '') { $inlineContext->getContainer()->appendChild(new Link(UrlEncoder::unescapeAndEncode($matches[2]), $matches[2])); return true; } return false; // This should never happen } } commonmark/src/Extension/CommonMark/Parser/Inline/BacktickParser.php 0000644 00000004136 15021222733 0021571 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Code; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class BacktickParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('`+'); } public function parse(InlineParserContext $inlineContext): bool { $ticks = $inlineContext->getFullMatch(); $cursor = $inlineContext->getCursor(); $cursor->advanceBy($inlineContext->getFullMatchLength()); $currentPosition = $cursor->getPosition(); $previousState = $cursor->saveState(); while ($matchingTicks = $cursor->match('/`+/m')) { if ($matchingTicks !== $ticks) { continue; } $code = $cursor->getSubstring($currentPosition, $cursor->getPosition() - $currentPosition - \strlen($ticks)); $c = \preg_replace('/\n/m', ' ', $code) ?? ''; if ( $c !== '' && $c[0] === ' ' && \substr($c, -1, 1) === ' ' && \preg_match('/[^ ]/', $c) ) { $c = \substr($c, 1, -1); } $inlineContext->getContainer()->appendChild(new Code($c)); return true; } // If we got here, we didn't match a closing backtick sequence $cursor->restoreState($previousState); $inlineContext->getContainer()->appendChild(new Text($ticks)); return true; } } commonmark/src/Extension/CommonMark/Parser/Inline/HtmlInlineParser.php 0000644 00000002375 15021222733 0022124 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\RegexHelper; final class HtmlInlineParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex(RegexHelper::PARTIAL_HTMLTAG)->caseSensitive(); } public function parse(InlineParserContext $inlineContext): bool { $inline = $inlineContext->getFullMatch(); $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); $inlineContext->getContainer()->appendChild(new HtmlInline($inline)); return true; } } commonmark/src/Extension/CommonMark/Parser/Inline/EntityParser.php 0000644 00000002421 15021222733 0021325 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Parser\Inline; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\Html5EntityDecoder; use League\CommonMark\Util\RegexHelper; final class EntityParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex(RegexHelper::PARTIAL_ENTITY); } public function parse(InlineParserContext $inlineContext): bool { $entity = $inlineContext->getFullMatch(); $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); $inlineContext->getContainer()->appendChild(new Text(Html5EntityDecoder::decode($entity))); return true; } } commonmark/src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php 0000644 00000004261 15021222733 0022474 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Util\Xml; use League\CommonMark\Xml\XmlNodeRendererInterface; final class FencedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param FencedCode $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { FencedCode::assertInstanceOf($node); $attrs = $node->data->getData('attributes'); $infoWords = $node->getInfoWords(); if (\count($infoWords) !== 0 && $infoWords[0] !== '') { $class = $infoWords[0]; if (! \str_starts_with($class, 'language-')) { $class = 'language-' . $class; } $attrs->append('class', $class); } return new HtmlElement( 'pre', [], new HtmlElement('code', $attrs->export(), Xml::escape($node->getLiteral())) ); } public function getXmlTagName(Node $node): string { return 'code_block'; } /** * @param FencedCode $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { FencedCode::assertInstanceOf($node); if (($info = $node->getInfo()) === null || $info === '') { return []; } return ['info' => $info]; } } commonmark/src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php 0000644 00000004062 15021222733 0022246 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Extension\TaskList\TaskListItemMarker; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class ListItemRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param ListItem $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { ListItem::assertInstanceOf($node); $contents = $childRenderer->renderNodes($node->children()); if (\substr($contents, 0, 1) === '<' && ! $this->startsTaskListItem($node)) { $contents = "\n" . $contents; } if (\substr($contents, -1, 1) === '>') { $contents .= "\n"; } $attrs = $node->data->get('attributes'); return new HtmlElement('li', $attrs, $contents); } public function getXmlTagName(Node $node): string { return 'item'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } private function startsTaskListItem(ListItem $block): bool { $firstChild = $block->firstChild(); return $firstChild instanceof Paragraph && $firstChild->firstChild() instanceof TaskListItemMarker; } } commonmark/src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php 0000644 00000003625 15021222733 0022570 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\BlockQuote; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class BlockQuoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param BlockQuote $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { BlockQuote::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $filling = $childRenderer->renderNodes($node->children()); $innerSeparator = $childRenderer->getInnerSeparator(); if ($filling === '') { return new HtmlElement('blockquote', $attrs, $innerSeparator); } return new HtmlElement( 'blockquote', $attrs, $innerSeparator . $filling . $innerSeparator ); } public function getXmlTagName(Node $node): string { return 'block_quote'; } /** * @param BlockQuote $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php 0000644 00000004636 15021222733 0022411 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class ListBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param ListBlock $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { ListBlock::assertInstanceOf($node); $listData = $node->getListData(); $tag = $listData->type === ListBlock::TYPE_BULLET ? 'ul' : 'ol'; $attrs = $node->data->get('attributes'); if ($listData->start !== null && $listData->start !== 1) { $attrs['start'] = (string) $listData->start; } $innerSeparator = $childRenderer->getInnerSeparator(); return new HtmlElement($tag, $attrs, $innerSeparator . $childRenderer->renderNodes($node->children()) . $innerSeparator); } public function getXmlTagName(Node $node): string { return 'list'; } /** * @param ListBlock $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { ListBlock::assertInstanceOf($node); $data = $node->getListData(); if ($data->type === ListBlock::TYPE_BULLET) { return [ 'type' => $data->type, 'tight' => $node->isTight() ? 'true' : 'false', ]; } return [ 'type' => $data->type, 'start' => $data->start ?? 1, 'tight' => $node->isTight(), 'delimiter' => $data->delimiter ?? ListBlock::DELIM_PERIOD, ]; } } commonmark/src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php 0000644 00000003143 15021222733 0023040 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Util\Xml; use League\CommonMark\Xml\XmlNodeRendererInterface; final class IndentedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param IndentedCode $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { IndentedCode::assertInstanceOf($node); $attrs = $node->data->get('attributes'); return new HtmlElement( 'pre', [], new HtmlElement('code', $attrs, Xml::escape($node->getLiteral())) ); } public function getXmlTagName(Node $node): string { return 'code_block'; } /** * @return array<string, scalar> */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Block/ThematicBreakRenderer.php 0000644 00000002727 15021222733 0023225 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\ThematicBreak; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class ThematicBreakRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param ThematicBreak $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { ThematicBreak::assertInstanceOf($node); $attrs = $node->data->get('attributes'); return new HtmlElement('hr', $attrs, '', true); } public function getXmlTagName(Node $node): string { return 'thematic_break'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php 0000644 00000003300 15021222733 0022045 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class HeadingRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Heading $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Heading::assertInstanceOf($node); $tag = 'h' . $node->getLevel(); $attrs = $node->data->get('attributes'); return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'heading'; } /** * @param Heading $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { Heading::assertInstanceOf($node); return ['level' => $node->getLevel()]; } } commonmark/src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php 0000644 00000003453 15021222733 0022376 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Block; use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlFilter; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class HtmlBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; /** * @param HtmlBlock $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { HtmlBlock::assertInstanceOf($node); $htmlInput = $this->config->get('html_input'); return HtmlFilter::filter($node->getLiteral(), $htmlInput); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'html_block'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Inline/ImageRenderer.php 0000644 00000005735 15021222733 0021732 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Image; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\Node; use League\CommonMark\Node\NodeIterator; use League\CommonMark\Node\StringContainerInterface; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Util\RegexHelper; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class ImageRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; /** * @param Image $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Image::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $forbidUnsafeLinks = ! $this->config->get('allow_unsafe_links'); if ($forbidUnsafeLinks && RegexHelper::isLinkPotentiallyUnsafe($node->getUrl())) { $attrs['src'] = ''; } else { $attrs['src'] = $node->getUrl(); } $attrs['alt'] = $this->getAltText($node); if (($title = $node->getTitle()) !== null) { $attrs['title'] = $title; } return new HtmlElement('img', $attrs, '', true); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'image'; } /** * @param Image $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { Image::assertInstanceOf($node); return [ 'destination' => $node->getUrl(), 'title' => $node->getTitle() ?? '', ]; } private function getAltText(Image $node): string { $altText = ''; foreach ((new NodeIterator($node)) as $n) { if ($n instanceof StringContainerInterface) { $altText .= $n->getLiteral(); } elseif ($n instanceof Newline) { $altText .= "\n"; } } return $altText; } } commonmark/src/Extension/CommonMark/Renderer/Inline/LinkRenderer.php 0000644 00000005071 15021222733 0021576 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Util\RegexHelper; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class LinkRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; /** * @param Link $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Link::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $forbidUnsafeLinks = ! $this->config->get('allow_unsafe_links'); if (! ($forbidUnsafeLinks && RegexHelper::isLinkPotentiallyUnsafe($node->getUrl()))) { $attrs['href'] = $node->getUrl(); } if (($title = $node->getTitle()) !== null) { $attrs['title'] = $title; } if (isset($attrs['target']) && $attrs['target'] === '_blank' && ! isset($attrs['rel'])) { $attrs['rel'] = 'noopener noreferrer'; } return new HtmlElement('a', $attrs, $childRenderer->renderNodes($node->children())); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'link'; } /** * @param Link $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { Link::assertInstanceOf($node); return [ 'destination' => $node->getUrl(), 'title' => $node->getTitle() ?? '', ]; } } commonmark/src/Extension/CommonMark/Renderer/Inline/CodeRenderer.php 0000644 00000002745 15021222733 0021560 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Code; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Util\Xml; use League\CommonMark\Xml\XmlNodeRendererInterface; final class CodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Code $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Code::assertInstanceOf($node); $attrs = $node->data->get('attributes'); return new HtmlElement('code', $attrs, Xml::escape($node->getLiteral())); } public function getXmlTagName(Node $node): string { return 'code'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Inline/HtmlInlineRenderer.php 0000644 00000003462 15021222733 0022746 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlFilter; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class HtmlInlineRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; /** * @param HtmlInline $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { HtmlInline::assertInstanceOf($node); $htmlInput = $this->config->get('html_input'); return HtmlFilter::filter($node->getLiteral(), $htmlInput); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'html_inline'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Inline/StrongRenderer.php 0000644 00000002737 15021222733 0022163 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Strong; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class StrongRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Strong $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Strong::assertInstanceOf($node); $attrs = $node->data->get('attributes'); return new HtmlElement('strong', $attrs, $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'strong'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/CommonMark/Renderer/Inline/EmphasisRenderer.php 0000644 00000002741 15021222733 0022453 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\CommonMark\Renderer\Inline; use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class EmphasisRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Emphasis $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Emphasis::assertInstanceOf($node); $attrs = $node->data->get('attributes'); return new HtmlElement('em', $attrs, $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'emph'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/Embed/EmbedProcessor.php 0000644 00000004025 15021222733 0016111 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Node\NodeIterator; final class EmbedProcessor { public const FALLBACK_REMOVE = 'remove'; public const FALLBACK_LINK = 'link'; private EmbedAdapterInterface $adapter; private string $fallback; public function __construct(EmbedAdapterInterface $adapter, string $fallback = self::FALLBACK_REMOVE) { $this->adapter = $adapter; $this->fallback = $fallback; } public function __invoke(DocumentParsedEvent $event): void { $document = $event->getDocument(); $embeds = []; foreach (new NodeIterator($document) as $node) { if (! ($node instanceof Embed)) { continue; } if ($node->parent() !== $document) { $replacement = new Paragraph(); $replacement->appendChild(new Text($node->getUrl())); $node->replaceWith($replacement); } else { $embeds[] = $node; } } $this->adapter->updateEmbeds($embeds); foreach ($embeds as $embed) { if ($embed->getEmbedCode() !== null) { continue; } if ($this->fallback === self::FALLBACK_REMOVE) { $embed->detach(); } elseif ($this->fallback === self::FALLBACK_LINK) { $paragraph = new Paragraph(); $paragraph->appendChild(new Link($embed->getUrl(), $embed->getUrl())); $embed->replaceWith($paragraph); } } } } commonmark/src/Extension/Embed/Embed.php 0000644 00000001775 15021222733 0014222 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Node\Block\AbstractBlock; final class Embed extends AbstractBlock { private string $url; private ?string $embedCode; public function __construct(string $url, ?string $embedCode = null) { parent::__construct(); $this->url = $url; $this->embedCode = $embedCode; } public function getUrl(): string { return $this->url; } public function setUrl(string $url): void { $this->url = $url; } public function getEmbedCode(): ?string { return $this->embedCode; } public function setEmbedCode(?string $embedCode): void { $this->embedCode = $embedCode; } } commonmark/src/Extension/Embed/EmbedParser.php 0000644 00000002470 15021222733 0015370 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; class EmbedParser implements BlockContinueParserInterface { private Embed $embed; public function __construct(string $url) { $this->embed = new Embed($url); } public function getBlock(): AbstractBlock { return $this->embed; } public function isContainer(): bool { return false; } public function canHaveLazyContinuationLines(): bool { return false; } public function canContain(AbstractBlock $childBlock): bool { return false; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::none(); } public function addLine(string $line): void { } public function closeBlock(): void { } } commonmark/src/Extension/Embed/Bridge/OscaroteroEmbedAdapter.php 0000644 00000002636 15021222733 0020755 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed\Bridge; use Embed\Embed as EmbedLib; use League\CommonMark\Exception\MissingDependencyException; use League\CommonMark\Extension\Embed\Embed; use League\CommonMark\Extension\Embed\EmbedAdapterInterface; final class OscaroteroEmbedAdapter implements EmbedAdapterInterface { private EmbedLib $embedLib; public function __construct(?EmbedLib $embed = null) { if ($embed === null) { if (! \class_exists(EmbedLib::class)) { throw new MissingDependencyException('The embed/embed package is not installed. Please install it with Composer to use this adapter.'); } $embed = new EmbedLib(); } $this->embedLib = $embed; } /** * {@inheritDoc} */ public function updateEmbeds(array $embeds): void { $extractors = $this->embedLib->getMulti(...\array_map(static fn (Embed $embed) => $embed->getUrl(), $embeds)); foreach ($extractors as $i => $extractor) { if ($extractor->code !== null) { $embeds[$i]->setEmbedCode($extractor->code->html); } } } } commonmark/src/Extension/Embed/EmbedAdapterInterface.php 0000644 00000001050 15021222733 0017326 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; /** * Interface for a service which updates the embed code(s) for the given array of embeds */ interface EmbedAdapterInterface { /** * @param Embed[] $embeds */ public function updateEmbeds(array $embeds): void; } commonmark/src/Extension/Embed/EmbedRenderer.php 0000644 00000001502 15021222733 0015675 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; class EmbedRenderer implements NodeRendererInterface { /** * @param Embed $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { Embed::assertInstanceOf($node); return $node->getEmbedCode() ?? ''; } } commonmark/src/Extension/Embed/DomainFilteringAdapter.php 0000644 00000002564 15021222733 0017557 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; class DomainFilteringAdapter implements EmbedAdapterInterface { private EmbedAdapterInterface $decorated; /** @psalm-var non-empty-string */ private string $regex; /** * @param string[] $allowedDomains */ public function __construct(EmbedAdapterInterface $decorated, array $allowedDomains) { $this->decorated = $decorated; $this->regex = self::createRegex($allowedDomains); } /** * {@inheritDoc} */ public function updateEmbeds(array $embeds): void { $this->decorated->updateEmbeds(\array_values(\array_filter($embeds, function (Embed $embed): bool { return \preg_match($this->regex, $embed->getUrl()) === 1; }))); } /** * @param string[] $allowedDomains * * @psalm-return non-empty-string */ private static function createRegex(array $allowedDomains): string { $allowedDomains = \array_map('preg_quote', $allowedDomains); return '/^(?:https?:\/\/)?(?:[^.]+\.)*(' . \implode('|', $allowedDomains) . ')/'; } } commonmark/src/Extension/Embed/EmbedStartParser.php 0000644 00000003240 15021222733 0016402 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Util\LinkParserHelper; class EmbedStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented() || $parserState->getParagraphContent() !== null || ! ($parserState->getActiveBlockParser()->isContainer())) { return BlockStart::none(); } // 0-3 leading spaces are okay $cursor->advanceToNextNonSpaceOrTab(); // The line must begin with "https://" if (! str_starts_with($cursor->getRemainder(), 'https://')) { return BlockStart::none(); } // A valid link must be found next if (($dest = LinkParserHelper::parseLinkDestination($cursor)) === null) { return BlockStart::none(); } // Skip any trailing whitespace $cursor->advanceToNextNonSpaceOrTab(); // We must be at the end of the line; otherwise, this link was not by itself if (! $cursor->isAtEnd()) { return BlockStart::none(); } return BlockStart::of(new EmbedParser($dest))->at($cursor); } } commonmark/src/Extension/Embed/EmbedExtension.php 0000644 00000003345 15021222733 0016112 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Embed; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class EmbedExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('embed', Expect::structure([ 'adapter' => Expect::type(EmbedAdapterInterface::class), 'allowed_domains' => Expect::arrayOf('string')->default([]), 'fallback' => Expect::anyOf('link', 'remove')->default('link'), ])); } public function register(EnvironmentBuilderInterface $environment): void { $adapter = $environment->getConfiguration()->get('embed.adapter'); \assert($adapter instanceof EmbedAdapterInterface); $allowedDomains = $environment->getConfiguration()->get('embed.allowed_domains'); if ($allowedDomains !== []) { $adapter = new DomainFilteringAdapter($adapter, $allowedDomains); } $environment ->addBlockStartParser(new EmbedStartParser(), 300) ->addEventListener(DocumentParsedEvent::class, new EmbedProcessor($adapter, $environment->getConfiguration()->get('embed.fallback')), 1010) ->addRenderer(Embed::class, new EmbedRenderer()); } } commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderRenderer.php 0000644 00000002020 15021222733 0024070 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableOfContentsPlaceholderRenderer implements NodeRendererInterface, XmlNodeRendererInterface { public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { return '<!-- table of contents -->'; } public function getXmlTagName(Node $node): string { return 'table_of_contents_placeholder'; } /** * @return array<string, scalar> */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/TableOfContents/Node/TableOfContents.php 0000644 00000000701 15021222733 0021127 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Node; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; final class TableOfContents extends ListBlock { } commonmark/src/Extension/TableOfContents/Node/TableOfContentsPlaceholder.php 0000644 00000000677 15021222733 0023306 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Node; use League\CommonMark\Node\Block\AbstractBlock; final class TableOfContentsPlaceholder extends AbstractBlock { } commonmark/src/Extension/TableOfContents/TableOfContentsExtension.php 0000644 00000005572 15021222733 0022152 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Renderer\Block\ListBlockRenderer; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class TableOfContentsExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('table_of_contents', Expect::structure([ 'position' => Expect::anyOf(TableOfContentsBuilder::POSITION_BEFORE_HEADINGS, TableOfContentsBuilder::POSITION_PLACEHOLDER, TableOfContentsBuilder::POSITION_TOP)->default(TableOfContentsBuilder::POSITION_TOP), 'style' => Expect::anyOf(ListBlock::TYPE_BULLET, ListBlock::TYPE_ORDERED)->default(ListBlock::TYPE_BULLET), 'normalize' => Expect::anyOf(TableOfContentsGenerator::NORMALIZE_RELATIVE, TableOfContentsGenerator::NORMALIZE_FLAT, TableOfContentsGenerator::NORMALIZE_DISABLED)->default(TableOfContentsGenerator::NORMALIZE_RELATIVE), 'min_heading_level' => Expect::int()->min(1)->max(6)->default(1), 'max_heading_level' => Expect::int()->min(1)->max(6)->default(6), 'html_class' => Expect::string()->default('table-of-contents'), 'placeholder' => Expect::anyOf(Expect::string(), Expect::null())->default(null), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addRenderer(TableOfContents::class, new TableOfContentsRenderer(new ListBlockRenderer())); $environment->addEventListener(DocumentParsedEvent::class, [new TableOfContentsBuilder(), 'onDocumentParsed'], -150); // phpcs:ignore SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($environment->getConfiguration()->get('table_of_contents/position') === TableOfContentsBuilder::POSITION_PLACEHOLDER) { $environment->addBlockStartParser(TableOfContentsPlaceholderParser::blockStartParser(), 200); // If a placeholder cannot be replaced with a TOC element this renderer will ensure the parser won't error out $environment->addRenderer(TableOfContentsPlaceholder::class, new TableOfContentsPlaceholderRenderer()); } } } commonmark/src/Extension/TableOfContents/Normalizer/AsIsNormalizerStrategy.php 0000644 00000004545 15021222733 0023771 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Normalizer; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; final class AsIsNormalizerStrategy implements NormalizerStrategyInterface { /** @psalm-readonly-allow-private-mutation */ private ListBlock $parentListBlock; /** @psalm-readonly-allow-private-mutation */ private int $parentLevel = 1; /** @psalm-readonly-allow-private-mutation */ private ?ListItem $lastListItem = null; public function __construct(TableOfContents $toc) { $this->parentListBlock = $toc; } public function addItem(int $level, ListItem $listItemToAdd): void { while ($level > $this->parentLevel) { // Descend downwards, creating new ListBlocks if needed, until we reach the correct depth if ($this->lastListItem === null) { $this->lastListItem = new ListItem($this->parentListBlock->getListData()); $this->parentListBlock->appendChild($this->lastListItem); } $newListBlock = new ListBlock($this->parentListBlock->getListData()); $newListBlock->setStartLine($listItemToAdd->getStartLine()); $newListBlock->setEndLine($listItemToAdd->getEndLine()); $this->lastListItem->appendChild($newListBlock); $this->parentListBlock = $newListBlock; $this->lastListItem = null; $this->parentLevel++; } while ($level < $this->parentLevel) { // Search upwards for the previous parent list block $search = $this->parentListBlock; while ($search = $search->parent()) { if ($search instanceof ListBlock) { $this->parentListBlock = $search; break; } } $this->parentLevel--; } $this->parentListBlock->appendChild($listItemToAdd); $this->lastListItem = $listItemToAdd; } } commonmark/src/Extension/TableOfContents/Normalizer/FlatNormalizerStrategy.php 0000644 00000001510 15021222733 0024005 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Normalizer; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; final class FlatNormalizerStrategy implements NormalizerStrategyInterface { /** @psalm-readonly */ private TableOfContents $toc; public function __construct(TableOfContents $toc) { $this->toc = $toc; } public function addItem(int $level, ListItem $listItemToAdd): void { $this->toc->appendChild($listItemToAdd); } } commonmark/src/Extension/TableOfContents/Normalizer/NormalizerStrategyInterface.php 0000644 00000001006 15021222733 0025017 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Normalizer; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; interface NormalizerStrategyInterface { public function addItem(int $level, ListItem $listItemToAdd): void; } commonmark/src/Extension/TableOfContents/Normalizer/RelativeNormalizerStrategy.php 0000644 00000004254 15021222733 0024702 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents\Normalizer; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; final class RelativeNormalizerStrategy implements NormalizerStrategyInterface { /** @psalm-readonly */ private TableOfContents $toc; /** * @var array<int, ListItem> * * @psalm-readonly-allow-private-mutation */ private array $listItemStack = []; public function __construct(TableOfContents $toc) { $this->toc = $toc; } public function addItem(int $level, ListItem $listItemToAdd): void { $previousLevel = \array_key_last($this->listItemStack); // Pop the stack if we're too deep while ($previousLevel !== null && $level < $previousLevel) { \array_pop($this->listItemStack); $previousLevel = \array_key_last($this->listItemStack); } $lastListItem = \end($this->listItemStack); // Need to go one level deeper? Add that level if ($lastListItem !== false && $level > $previousLevel) { $targetListBlock = new ListBlock($lastListItem->getListData()); $targetListBlock->setStartLine($listItemToAdd->getStartLine()); $targetListBlock->setEndLine($listItemToAdd->getEndLine()); $lastListItem->appendChild($targetListBlock); // Otherwise we're at the right level // If there's no stack we're adding this item directly to the TOC element } elseif ($lastListItem === false) { $targetListBlock = $this->toc; // Otherwise add it to the last list item } else { $targetListBlock = $lastListItem->parent(); } $targetListBlock->appendChild($listItemToAdd); $this->listItemStack[$level] = $listItemToAdd; } } commonmark/src/Extension/TableOfContents/TableOfContentsGenerator.php 0000644 00000013670 15021222733 0022122 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock; use League\CommonMark\Extension\CommonMark\Node\Block\ListData; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; use League\CommonMark\Extension\TableOfContents\Normalizer\AsIsNormalizerStrategy; use League\CommonMark\Extension\TableOfContents\Normalizer\FlatNormalizerStrategy; use League\CommonMark\Extension\TableOfContents\Normalizer\NormalizerStrategyInterface; use League\CommonMark\Extension\TableOfContents\Normalizer\RelativeNormalizerStrategy; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\NodeIterator; use League\CommonMark\Node\RawMarkupContainerInterface; use League\CommonMark\Node\StringContainerHelper; use League\Config\Exception\InvalidConfigurationException; final class TableOfContentsGenerator implements TableOfContentsGeneratorInterface { public const STYLE_BULLET = ListBlock::TYPE_BULLET; public const STYLE_ORDERED = ListBlock::TYPE_ORDERED; public const NORMALIZE_DISABLED = 'as-is'; public const NORMALIZE_RELATIVE = 'relative'; public const NORMALIZE_FLAT = 'flat'; /** @psalm-readonly */ private string $style; /** @psalm-readonly */ private string $normalizationStrategy; /** @psalm-readonly */ private int $minHeadingLevel; /** @psalm-readonly */ private int $maxHeadingLevel; /** @psalm-readonly */ private string $fragmentPrefix; public function __construct(string $style, string $normalizationStrategy, int $minHeadingLevel, int $maxHeadingLevel, string $fragmentPrefix) { $this->style = $style; $this->normalizationStrategy = $normalizationStrategy; $this->minHeadingLevel = $minHeadingLevel; $this->maxHeadingLevel = $maxHeadingLevel; $this->fragmentPrefix = $fragmentPrefix; if ($fragmentPrefix !== '') { $this->fragmentPrefix .= '-'; } } public function generate(Document $document): ?TableOfContents { $toc = $this->createToc($document); $normalizer = $this->getNormalizer($toc); $firstHeading = null; foreach ($this->getHeadingLinks($document) as $headingLink) { $heading = $headingLink->parent(); // Make sure this is actually tied to a heading if (! $heading instanceof Heading) { continue; } // Skip any headings outside the configured min/max levels if ($heading->getLevel() < $this->minHeadingLevel || $heading->getLevel() > $this->maxHeadingLevel) { continue; } // Keep track of the first heading we see - we might need this later $firstHeading ??= $heading; // Keep track of the start and end lines $toc->setStartLine($firstHeading->getStartLine()); $toc->setEndLine($heading->getEndLine()); // Create the new link $link = new Link('#' . $this->fragmentPrefix . $headingLink->getSlug(), StringContainerHelper::getChildText($heading, [RawMarkupContainerInterface::class])); $listItem = new ListItem($toc->getListData()); $listItem->setStartLine($heading->getStartLine()); $listItem->setEndLine($heading->getEndLine()); $listItem->appendChild($link); // Add it to the correct place $normalizer->addItem($heading->getLevel(), $listItem); } // Don't add the TOC if no headings were present if (! $toc->hasChildren() || $firstHeading === null) { return null; } return $toc; } private function createToc(Document $document): TableOfContents { $listData = new ListData(); if ($this->style === self::STYLE_BULLET) { $listData->type = ListBlock::TYPE_BULLET; } elseif ($this->style === self::STYLE_ORDERED) { $listData->type = ListBlock::TYPE_ORDERED; } else { throw new InvalidConfigurationException(\sprintf('Invalid table of contents list style: "%s"', $this->style)); } $toc = new TableOfContents($listData); $toc->setStartLine($document->getStartLine()); $toc->setEndLine($document->getEndLine()); return $toc; } /** * @return iterable<HeadingPermalink> */ private function getHeadingLinks(Document $document): iterable { foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if (! $node instanceof Heading) { continue; } foreach ($node->children() as $child) { if ($child instanceof HeadingPermalink) { yield $child; } } } } private function getNormalizer(TableOfContents $toc): NormalizerStrategyInterface { switch ($this->normalizationStrategy) { case self::NORMALIZE_DISABLED: return new AsIsNormalizerStrategy($toc); case self::NORMALIZE_RELATIVE: return new RelativeNormalizerStrategy($toc); case self::NORMALIZE_FLAT: return new FlatNormalizerStrategy($toc); default: throw new InvalidConfigurationException(\sprintf('Invalid table of contents normalization strategy: "%s"', $this->normalizationStrategy)); } } } commonmark/src/Extension/TableOfContents/TableOfContentsRenderer.php 0000644 00000002763 15021222733 0021743 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TableOfContentsRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** @var NodeRendererInterface&XmlNodeRendererInterface */ private $innerRenderer; /** * @psalm-param NodeRendererInterface&XmlNodeRendererInterface $innerRenderer * * @phpstan-param NodeRendererInterface&XmlNodeRendererInterface $innerRenderer */ public function __construct(NodeRendererInterface $innerRenderer) { $this->innerRenderer = $innerRenderer; } /** * {@inheritDoc} */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { return $this->innerRenderer->render($node, $childRenderer); } public function getXmlTagName(Node $node): string { return 'table_of_contents'; } /** * @return array<string, scalar> */ public function getXmlAttributes(Node $node): array { return $this->innerRenderer->getXmlAttributes($node); } } commonmark/src/Extension/TableOfContents/TableOfContentsGeneratorInterface.php 0000644 00000001056 15021222733 0023736 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; use League\CommonMark\Node\Block\Document; interface TableOfContentsGeneratorInterface { public function generate(Document $document): ?TableOfContents; } commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderParser.php 0000644 00000004747 15021222733 0023600 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class TableOfContentsPlaceholderParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private TableOfContentsPlaceholder $block; public function __construct() { $this->block = new TableOfContentsPlaceholder(); } public function getBlock(): TableOfContentsPlaceholder { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { return BlockContinue::none(); } public static function blockStartParser(): BlockStartParserInterface { return new class () implements BlockStartParserInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { $placeholder = $this->config->get('table_of_contents/placeholder'); if ($placeholder === null) { return BlockStart::none(); } // The placeholder must be the only thing on the line if ($cursor->match('/^' . \preg_quote($placeholder, '/') . '$/') === null) { return BlockStart::none(); } return BlockStart::of(new TableOfContentsPlaceholderParser())->at($cursor); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } }; } } commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php 0000644 00000007364 15021222733 0021565 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TableOfContents; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\CommonMark\Node\Block\Heading; use League\CommonMark\Extension\HeadingPermalink\HeadingPermalink; use League\CommonMark\Extension\TableOfContents\Node\TableOfContents; use League\CommonMark\Extension\TableOfContents\Node\TableOfContentsPlaceholder; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\NodeIterator; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; use League\Config\Exception\InvalidConfigurationException; final class TableOfContentsBuilder implements ConfigurationAwareInterface { public const POSITION_TOP = 'top'; public const POSITION_BEFORE_HEADINGS = 'before-headings'; public const POSITION_PLACEHOLDER = 'placeholder'; /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function onDocumentParsed(DocumentParsedEvent $event): void { $document = $event->getDocument(); $generator = new TableOfContentsGenerator( (string) $this->config->get('table_of_contents/style'), (string) $this->config->get('table_of_contents/normalize'), (int) $this->config->get('table_of_contents/min_heading_level'), (int) $this->config->get('table_of_contents/max_heading_level'), (string) $this->config->get('heading_permalink/fragment_prefix'), ); $toc = $generator->generate($document); if ($toc === null) { // No linkable headers exist, so no TOC could be generated return; } // Add custom CSS class(es), if defined $class = $this->config->get('table_of_contents/html_class'); if ($class !== null) { $toc->data->append('attributes/class', $class); } // Add the TOC to the Document $position = $this->config->get('table_of_contents/position'); if ($position === self::POSITION_TOP) { $document->prependChild($toc); } elseif ($position === self::POSITION_BEFORE_HEADINGS) { $this->insertBeforeFirstLinkedHeading($document, $toc); } elseif ($position === self::POSITION_PLACEHOLDER) { $this->replacePlaceholders($document, $toc); } else { throw InvalidConfigurationException::forConfigOption('table_of_contents/position', $position); } } private function insertBeforeFirstLinkedHeading(Document $document, TableOfContents $toc): void { foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if (! $node instanceof Heading) { continue; } foreach ($node->children() as $child) { if ($child instanceof HeadingPermalink) { $node->insertBefore($toc); return; } } } } private function replacePlaceholders(Document $document, TableOfContents $toc): void { foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { // Add the block once we find a placeholder if (! $node instanceof TableOfContentsPlaceholder) { continue; } $node->replaceWith(clone $toc); } } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/SmartPunct/EllipsesParser.php 0000644 00000002117 15021222733 0017216 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class EllipsesParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::oneOf('...', '. . .'); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); $inlineContext->getContainer()->appendChild(new Text('…')); return true; } } commonmark/src/Extension/SmartPunct/DashParser.php 0000644 00000003562 15021222733 0016322 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class DashParser implements InlineParserInterface { private const EN_DASH = '–'; private const EM_DASH = '—'; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('(?<!-)(-{2,})'); } public function parse(InlineParserContext $inlineContext): bool { $count = $inlineContext->getFullMatchLength(); $inlineContext->getCursor()->advanceBy($count); $enCount = 0; $emCount = 0; if ($count % 3 === 0) { // If divisible by 3, use all em dashes $emCount = (int) ($count / 3); } elseif ($count % 2 === 0) { // If divisible by 2, use all en dashes $enCount = (int) ($count / 2); } elseif ($count % 3 === 2) { // If 2 extra dashes, use en dash for last 2; em dashes for rest $emCount = (int) (($count - 2) / 3); $enCount = 1; } else { // Use en dashes for last 4 hyphens; em dashes for rest $emCount = (int) (($count - 4) / 3); $enCount = 2; } $inlineContext->getContainer()->appendChild(new Text( \str_repeat(self::EM_DASH, $emCount) . \str_repeat(self::EN_DASH, $enCount) )); return true; } } commonmark/src/Extension/SmartPunct/QuoteParser.php 0000644 00000006321 15021222733 0016534 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Delimiter\Delimiter; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Util\RegexHelper; final class QuoteParser implements InlineParserInterface { /** * @deprecated This constant is no longer used and will be removed in a future major release */ public const DOUBLE_QUOTES = [Quote::DOUBLE_QUOTE, Quote::DOUBLE_QUOTE_OPENER, Quote::DOUBLE_QUOTE_CLOSER]; /** * @deprecated This constant is no longer used and will be removed in a future major release */ public const SINGLE_QUOTES = [Quote::SINGLE_QUOTE, Quote::SINGLE_QUOTE_OPENER, Quote::SINGLE_QUOTE_CLOSER]; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::oneOf(Quote::SINGLE_QUOTE, Quote::DOUBLE_QUOTE); } /** * Normalizes any quote characters found and manually adds them to the delimiter stack */ public function parse(InlineParserContext $inlineContext): bool { $char = $inlineContext->getFullMatch(); $cursor = $inlineContext->getCursor(); $charBefore = $cursor->peek(-1); if ($charBefore === null) { $charBefore = "\n"; } $cursor->advance(); $charAfter = $cursor->getCurrentCharacter(); if ($charAfter === null) { $charAfter = "\n"; } [$leftFlanking, $rightFlanking] = $this->determineFlanking($charBefore, $charAfter); $canOpen = $leftFlanking && ! $rightFlanking; $canClose = $rightFlanking; $node = new Quote($char, ['delim' => true]); $inlineContext->getContainer()->appendChild($node); // Add entry to stack to this opener $inlineContext->getDelimiterStack()->push(new Delimiter($char, 1, $node, $canOpen, $canClose)); return true; } /** * @return bool[] */ private function determineFlanking(string $charBefore, string $charAfter): array { $afterIsWhitespace = \preg_match('/\pZ|\s/u', $charAfter); $afterIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charAfter); $beforeIsWhitespace = \preg_match('/\pZ|\s/u', $charBefore); $beforeIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charBefore); $leftFlanking = ! $afterIsWhitespace && ! ($afterIsPunctuation && ! $beforeIsWhitespace && ! $beforeIsPunctuation); $rightFlanking = ! $beforeIsWhitespace && ! ($beforeIsPunctuation && ! $afterIsWhitespace && ! $afterIsPunctuation); return [$leftFlanking, $rightFlanking]; } } commonmark/src/Extension/SmartPunct/Quote.php 0000644 00000001501 15021222733 0015352 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Node\Inline\AbstractStringContainer; final class Quote extends AbstractStringContainer { public const DOUBLE_QUOTE = '"'; public const DOUBLE_QUOTE_OPENER = '“'; public const DOUBLE_QUOTE_CLOSER = '”'; public const SINGLE_QUOTE = "'"; public const SINGLE_QUOTE_OPENER = '‘'; public const SINGLE_QUOTE_CLOSER = '’'; } commonmark/src/Extension/SmartPunct/QuoteProcessor.php 0000644 00000004473 15021222733 0017265 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Delimiter\DelimiterInterface; use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface; use League\CommonMark\Node\Inline\AbstractStringContainer; final class QuoteProcessor implements DelimiterProcessorInterface { /** @psalm-readonly */ private string $normalizedCharacter; /** @psalm-readonly */ private string $openerCharacter; /** @psalm-readonly */ private string $closerCharacter; private function __construct(string $char, string $opener, string $closer) { $this->normalizedCharacter = $char; $this->openerCharacter = $opener; $this->closerCharacter = $closer; } public function getOpeningCharacter(): string { return $this->normalizedCharacter; } public function getClosingCharacter(): string { return $this->normalizedCharacter; } public function getMinLength(): int { return 1; } public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int { return 1; } public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void { $opener->insertAfter(new Quote($this->openerCharacter)); $closer->insertBefore(new Quote($this->closerCharacter)); } /** * Create a double-quote processor */ public static function createDoubleQuoteProcessor(string $opener = Quote::DOUBLE_QUOTE_OPENER, string $closer = Quote::DOUBLE_QUOTE_CLOSER): self { return new self(Quote::DOUBLE_QUOTE, $opener, $closer); } /** * Create a single-quote processor */ public static function createSingleQuoteProcessor(string $opener = Quote::SINGLE_QUOTE_OPENER, string $closer = Quote::SINGLE_QUOTE_CLOSER): self { return new self(Quote::SINGLE_QUOTE, $opener, $closer); } } commonmark/src/Extension/SmartPunct/SmartPunctExtension.php 0000644 00000005200 15021222733 0020252 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (http://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Renderer\Block as CoreBlockRenderer; use League\CommonMark\Renderer\Inline as CoreInlineRenderer; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class SmartPunctExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('smartpunct', Expect::structure([ 'double_quote_opener' => Expect::string(Quote::DOUBLE_QUOTE_OPENER), 'double_quote_closer' => Expect::string(Quote::DOUBLE_QUOTE_CLOSER), 'single_quote_opener' => Expect::string(Quote::SINGLE_QUOTE_OPENER), 'single_quote_closer' => Expect::string(Quote::SINGLE_QUOTE_CLOSER), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment ->addInlineParser(new QuoteParser(), 10) ->addInlineParser(new DashParser(), 0) ->addInlineParser(new EllipsesParser(), 0) ->addDelimiterProcessor(QuoteProcessor::createDoubleQuoteProcessor( $environment->getConfiguration()->get('smartpunct/double_quote_opener'), $environment->getConfiguration()->get('smartpunct/double_quote_closer') )) ->addDelimiterProcessor(QuoteProcessor::createSingleQuoteProcessor( $environment->getConfiguration()->get('smartpunct/single_quote_opener'), $environment->getConfiguration()->get('smartpunct/single_quote_closer') )) ->addEventListener(DocumentParsedEvent::class, new ReplaceUnpairedQuotesListener()) ->addRenderer(Document::class, new CoreBlockRenderer\DocumentRenderer(), 0) ->addRenderer(Paragraph::class, new CoreBlockRenderer\ParagraphRenderer(), 0) ->addRenderer(Text::class, new CoreInlineRenderer\TextRenderer(), 0); } } commonmark/src/Extension/SmartPunct/ReplaceUnpairedQuotesListener.php 0000644 00000002462 15021222733 0022236 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\SmartPunct; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Node\Inline\AdjacentTextMerger; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Node\Query; /** * Identifies any lingering Quote nodes that were missing pairs and converts them into Text nodes */ final class ReplaceUnpairedQuotesListener { public function __invoke(DocumentParsedEvent $event): void { $query = (new Query())->where(Query::type(Quote::class)); foreach ($query->findAll($event->getDocument()) as $quote) { \assert($quote instanceof Quote); $literal = $quote->getLiteral(); if ($literal === Quote::SINGLE_QUOTE) { $literal = Quote::SINGLE_QUOTE_CLOSER; } elseif ($literal === Quote::DOUBLE_QUOTE) { $literal = Quote::DOUBLE_QUOTE_OPENER; } $quote->replaceWith($new = new Text($literal)); AdjacentTextMerger::mergeWithDirectlyAdjacentNodes($new); } } } commonmark/src/Extension/Footnote/Event/FixOrphanedFootnotesAndRefsListener.php 0000644 00000004461 15021222733 0024124 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Inline\Text; final class FixOrphanedFootnotesAndRefsListener { public function onDocumentParsed(DocumentParsedEvent $event): void { $document = $event->getDocument(); $map = $this->buildMapOfKnownFootnotesAndRefs($document); foreach ($map['_flat'] as $node) { if ($node instanceof FootnoteRef && ! isset($map[Footnote::class][$node->getReference()->getLabel()])) { // Found an orphaned FootnoteRef without a corresponding Footnote // Restore the original footnote ref text $node->replaceWith(new Text(\sprintf('[^%s]', $node->getReference()->getLabel()))); } // phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed if ($node instanceof Footnote && ! isset($map[FootnoteRef::class][$node->getReference()->getLabel()])) { // Found an orphaned Footnote without a corresponding FootnoteRef // Remove the footnote $node->detach(); } } } /** @phpstan-ignore-next-line */ private function buildMapOfKnownFootnotesAndRefs(Document $document): array // @phpcs:ignore { $map = [ Footnote::class => [], FootnoteRef::class => [], '_flat' => [], ]; foreach ($document->iterator() as $node) { if ($node instanceof Footnote) { $map[Footnote::class][$node->getReference()->getLabel()] = true; $map['_flat'][] = $node; } elseif ($node instanceof FootnoteRef) { $map[FootnoteRef::class][$node->getReference()->getLabel()] = true; $map['_flat'][] = $node; } } return $map; } } commonmark/src/Extension/Footnote/Event/GatherFootnotesListener.php 0000644 00000006727 15021222733 0021673 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; use League\CommonMark\Extension\Footnote\Node\FootnoteContainer; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\NodeIterator; use League\CommonMark\Reference\Reference; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class GatherFootnotesListener implements ConfigurationAwareInterface { private ConfigurationInterface $config; public function onDocumentParsed(DocumentParsedEvent $event): void { $document = $event->getDocument(); $footnotes = []; foreach ($document->iterator(NodeIterator::FLAG_BLOCKS_ONLY) as $node) { if (! $node instanceof Footnote) { continue; } // Look for existing reference with footnote label $ref = $document->getReferenceMap()->get($node->getReference()->getLabel()); if ($ref !== null) { // Use numeric title to get footnotes order $footnotes[(int) $ref->getTitle()] = $node; } else { // Footnote call is missing, append footnote at the end $footnotes[\PHP_INT_MAX] = $node; } $key = '#' . $this->config->get('footnote/footnote_id_prefix') . $node->getReference()->getDestination(); if ($document->data->has($key)) { $this->createBackrefs($node, $document->data->get($key)); } } // Only add a footnote container if there are any if (\count($footnotes) === 0) { return; } $container = $this->getFootnotesContainer($document); \ksort($footnotes); foreach ($footnotes as $footnote) { $container->appendChild($footnote); } } private function getFootnotesContainer(Document $document): FootnoteContainer { $footnoteContainer = new FootnoteContainer(); $document->appendChild($footnoteContainer); return $footnoteContainer; } /** * Look for all footnote refs pointing to this footnote and create each footnote backrefs. * * @param Footnote $node The target footnote * @param Reference[] $backrefs References to create backrefs for */ private function createBackrefs(Footnote $node, array $backrefs): void { // Backrefs should be added to the child paragraph $target = $node->lastChild(); if ($target === null) { // This should never happen, but you never know $target = $node; } foreach ($backrefs as $backref) { $target->appendChild(new FootnoteBackref(new Reference( $backref->getLabel(), '#' . $this->config->get('footnote/ref_id_prefix') . $backref->getLabel(), $backref->getTitle() ))); } } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/Footnote/Event/AnonymousFootnotesListener.php 0000644 00000004066 15021222733 0022443 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Reference\Reference; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class AnonymousFootnotesListener implements ConfigurationAwareInterface { private ConfigurationInterface $config; public function onDocumentParsed(DocumentParsedEvent $event): void { $document = $event->getDocument(); foreach ($document->iterator() as $node) { if (! $node instanceof FootnoteRef || ($text = $node->getContent()) === null) { continue; } // Anonymous footnote needs to create a footnote from its content $existingReference = $node->getReference(); $newReference = new Reference( $existingReference->getLabel(), '#' . $this->config->get('footnote/ref_id_prefix') . $existingReference->getLabel(), $existingReference->getTitle() ); $paragraph = new Paragraph(); $paragraph->appendChild(new Text($text)); $paragraph->appendChild(new FootnoteBackref($newReference)); $footnote = new Footnote($newReference); $footnote->appendChild($paragraph); $document->appendChild($footnote); } } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/Footnote/Event/NumberFootnotesListener.php 0000644 00000004446 15021222733 0021705 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Event; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Reference\Reference; final class NumberFootnotesListener { public function onDocumentParsed(DocumentParsedEvent $event): void { $document = $event->getDocument(); $nextCounter = 1; $usedLabels = []; $usedCounters = []; foreach ($document->iterator() as $node) { if (! $node instanceof FootnoteRef) { continue; } $existingReference = $node->getReference(); $label = $existingReference->getLabel(); $counter = $nextCounter; $canIncrementCounter = true; if (\array_key_exists($label, $usedLabels)) { /* * Reference is used again, we need to point * to the same footnote. But with a different ID */ $counter = $usedCounters[$label]; $label .= '__' . ++$usedLabels[$label]; $canIncrementCounter = false; } // rewrite reference title to use a numeric link $newReference = new Reference( $label, $existingReference->getDestination(), (string) $counter ); // Override reference with numeric link $node->setReference($newReference); $document->getReferenceMap()->add($newReference); /* * Store created references in document for * creating FootnoteBackrefs */ $document->data->append($existingReference->getDestination(), $newReference); $usedLabels[$label] = 1; $usedCounters[$label] = $nextCounter; if ($canIncrementCounter) { $nextCounter++; } } } } commonmark/src/Extension/Footnote/Node/Footnote.php 0000644 00000001626 15021222733 0016444 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Node; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceableInterface; final class Footnote extends AbstractBlock implements ReferenceableInterface { /** @psalm-readonly */ private ReferenceInterface $reference; public function __construct(ReferenceInterface $reference) { parent::__construct(); $this->reference = $reference; } public function getReference(): ReferenceInterface { return $this->reference; } } commonmark/src/Extension/Footnote/Node/FootnoteBackref.php 0000644 00000001766 15021222733 0017727 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Node; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceableInterface; /** * Link from the footnote on the bottom of the document back to the reference */ final class FootnoteBackref extends AbstractInline implements ReferenceableInterface { /** @psalm-readonly */ private ReferenceInterface $reference; public function __construct(ReferenceInterface $reference) { parent::__construct(); $this->reference = $reference; } public function getReference(): ReferenceInterface { return $this->reference; } } commonmark/src/Extension/Footnote/Node/FootnoteContainer.php 0000644 00000000723 15021222733 0020304 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Node; use League\CommonMark\Node\Block\AbstractBlock; final class FootnoteContainer extends AbstractBlock { } commonmark/src/Extension/Footnote/Node/FootnoteRef.php 0000644 00000002547 15021222733 0017104 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Node; use League\CommonMark\Node\Inline\AbstractInline; use League\CommonMark\Reference\ReferenceInterface; use League\CommonMark\Reference\ReferenceableInterface; final class FootnoteRef extends AbstractInline implements ReferenceableInterface { private ReferenceInterface $reference; /** @psalm-readonly */ private ?string $content = null; /** * @param array<mixed> $data */ public function __construct(ReferenceInterface $reference, ?string $content = null, array $data = []) { parent::__construct(); $this->reference = $reference; $this->content = $content; if (\count($data) > 0) { $this->data->import($data); } } public function getReference(): ReferenceInterface { return $this->reference; } public function setReference(ReferenceInterface $reference): void { $this->reference = $reference; } public function getContent(): ?string { return $this->content; } } commonmark/src/Extension/Footnote/Parser/FootnoteStartParser.php 0000644 00000003405 15021222733 0021203 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Parser; use League\CommonMark\Parser\Block\BlockStart; use League\CommonMark\Parser\Block\BlockStartParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Parser\MarkdownParserStateInterface; use League\CommonMark\Reference\Reference; use League\CommonMark\Util\RegexHelper; final class FootnoteStartParser implements BlockStartParserInterface { public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart { if ($cursor->isIndented() || $parserState->getLastMatchedBlockParser()->canHaveLazyContinuationLines()) { return BlockStart::none(); } $match = RegexHelper::matchFirst( '/^\[\^([^\s^\]]+)\]\:(?:\s|$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition() ); if (! $match) { return BlockStart::none(); } $cursor->advanceToNextNonSpaceOrTab(); $cursor->advanceBy(\strlen($match[0])); $str = $cursor->getRemainder(); \preg_replace('/^\[\^([^\s^\]]+)\]\:(?:\s|$)/', '', $str); if (\preg_match('/^\[\^([^\s^\]]+)\]\:(?:\s|$)/', $match[0], $matches) !== 1) { return BlockStart::none(); } $reference = new Reference($matches[1], $matches[1], $matches[1]); $footnoteParser = new FootnoteParser($reference); return BlockStart::of($footnoteParser)->at($cursor); } } commonmark/src/Extension/Footnote/Parser/FootnoteParser.php 0000644 00000003404 15021222733 0020164 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Parser; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Parser\Block\AbstractBlockContinueParser; use League\CommonMark\Parser\Block\BlockContinue; use League\CommonMark\Parser\Block\BlockContinueParserInterface; use League\CommonMark\Parser\Cursor; use League\CommonMark\Reference\ReferenceInterface; final class FootnoteParser extends AbstractBlockContinueParser { /** @psalm-readonly */ private Footnote $block; /** @psalm-readonly-allow-private-mutation */ private ?int $indentation = null; public function __construct(ReferenceInterface $reference) { $this->block = new Footnote($reference); } public function getBlock(): Footnote { return $this->block; } public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue { if ($cursor->isBlank()) { return BlockContinue::at($cursor); } if ($cursor->isIndented()) { $this->indentation ??= $cursor->getIndent(); $cursor->advanceBy($this->indentation, true); return BlockContinue::at($cursor); } return BlockContinue::none(); } public function isContainer(): bool { return true; } public function canContain(AbstractBlock $childBlock): bool { return true; } } commonmark/src/Extension/Footnote/Parser/AnonymousFootnoteRefParser.php 0000644 00000004161 15021222733 0022533 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Parser; use League\CommonMark\Environment\EnvironmentAwareInterface; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Normalizer\TextNormalizerInterface; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Reference\Reference; use League\Config\ConfigurationInterface; final class AnonymousFootnoteRefParser implements InlineParserInterface, EnvironmentAwareInterface { private ConfigurationInterface $config; /** @psalm-readonly-allow-private-mutation */ private TextNormalizerInterface $slugNormalizer; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('\^\[([^\]]+)\]'); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); [$label] = $inlineContext->getSubMatches(); $reference = $this->createReference($label); $inlineContext->getContainer()->appendChild(new FootnoteRef($reference, $label)); return true; } private function createReference(string $label): Reference { $refLabel = $this->slugNormalizer->normalize($label, ['length' => 20]); return new Reference( $refLabel, '#' . $this->config->get('footnote/footnote_id_prefix') . $refLabel, $label ); } public function setEnvironment(EnvironmentInterface $environment): void { $this->config = $environment->getConfiguration(); $this->slugNormalizer = $environment->getSlugNormalizer(); } } commonmark/src/Extension/Footnote/Parser/FootnoteRefParser.php 0000644 00000003253 15021222733 0020623 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Parser; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; use League\CommonMark\Reference\Reference; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class FootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface { private ConfigurationInterface $config; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex('\[\^([^\s\]]+)\]'); } public function parse(InlineParserContext $inlineContext): bool { $inlineContext->getCursor()->advanceBy($inlineContext->getFullMatchLength()); [$label] = $inlineContext->getSubMatches(); $inlineContext->getContainer()->appendChild(new FootnoteRef($this->createReference($label))); return true; } private function createReference(string $label): Reference { return new Reference( $label, '#' . $this->config->get('footnote/footnote_id_prefix') . $label, $label ); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } } commonmark/src/Extension/Footnote/FootnoteExtension.php 0000644 00000006725 15021222733 0017461 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Event\DocumentParsedEvent; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\CommonMark\Extension\Footnote\Event\AnonymousFootnotesListener; use League\CommonMark\Extension\Footnote\Event\FixOrphanedFootnotesAndRefsListener; use League\CommonMark\Extension\Footnote\Event\GatherFootnotesListener; use League\CommonMark\Extension\Footnote\Event\NumberFootnotesListener; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; use League\CommonMark\Extension\Footnote\Node\FootnoteContainer; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Extension\Footnote\Parser\AnonymousFootnoteRefParser; use League\CommonMark\Extension\Footnote\Parser\FootnoteRefParser; use League\CommonMark\Extension\Footnote\Parser\FootnoteStartParser; use League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer; use League\CommonMark\Extension\Footnote\Renderer\FootnoteContainerRenderer; use League\CommonMark\Extension\Footnote\Renderer\FootnoteRefRenderer; use League\CommonMark\Extension\Footnote\Renderer\FootnoteRenderer; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class FootnoteExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('footnote', Expect::structure([ 'backref_class' => Expect::string('footnote-backref'), 'backref_symbol' => Expect::string('↩'), 'container_add_hr' => Expect::bool(true), 'container_class' => Expect::string('footnotes'), 'ref_class' => Expect::string('footnote-ref'), 'ref_id_prefix' => Expect::string('fnref:'), 'footnote_class' => Expect::string('footnote'), 'footnote_id_prefix' => Expect::string('fn:'), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addBlockStartParser(new FootnoteStartParser(), 51); $environment->addInlineParser(new AnonymousFootnoteRefParser(), 35); $environment->addInlineParser(new FootnoteRefParser(), 51); $environment->addRenderer(FootnoteContainer::class, new FootnoteContainerRenderer()); $environment->addRenderer(Footnote::class, new FootnoteRenderer()); $environment->addRenderer(FootnoteRef::class, new FootnoteRefRenderer()); $environment->addRenderer(FootnoteBackref::class, new FootnoteBackrefRenderer()); $environment->addEventListener(DocumentParsedEvent::class, [new AnonymousFootnotesListener(), 'onDocumentParsed'], 40); $environment->addEventListener(DocumentParsedEvent::class, [new FixOrphanedFootnotesAndRefsListener(), 'onDocumentParsed'], 30); $environment->addEventListener(DocumentParsedEvent::class, [new NumberFootnotesListener(), 'onDocumentParsed'], 20); $environment->addEventListener(DocumentParsedEvent::class, [new GatherFootnotesListener(), 'onDocumentParsed'], 10); } } commonmark/src/Extension/Footnote/Renderer/FootnoteRefRenderer.php 0000644 00000004701 15021222733 0021446 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Renderer; use League\CommonMark\Extension\Footnote\Node\FootnoteRef; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class FootnoteRefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { private ConfigurationInterface $config; /** * @param FootnoteRef $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { FootnoteRef::assertInstanceOf($node); $attrs = $node->data->getData('attributes'); $attrs->append('class', $this->config->get('footnote/ref_class')); $attrs->set('href', \mb_strtolower($node->getReference()->getDestination(), 'UTF-8')); $attrs->set('role', 'doc-noteref'); $idPrefix = $this->config->get('footnote/ref_id_prefix'); return new HtmlElement( 'sup', [ 'id' => $idPrefix . \mb_strtolower($node->getReference()->getLabel(), 'UTF-8'), ], new HtmlElement( 'a', $attrs->export(), $node->getReference()->getTitle() ), true ); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'footnote_ref'; } /** * @param FootnoteRef $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { FootnoteRef::assertInstanceOf($node); return [ 'reference' => $node->getReference()->getLabel(), ]; } } commonmark/src/Extension/Footnote/Renderer/FootnoteRenderer.php 0000644 00000004341 15021222733 0021011 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Renderer; use League\CommonMark\Extension\Footnote\Node\Footnote; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class FootnoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { private ConfigurationInterface $config; /** * @param Footnote $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { Footnote::assertInstanceOf($node); $attrs = $node->data->getData('attributes'); $attrs->append('class', $this->config->get('footnote/footnote_class')); $attrs->set('id', $this->config->get('footnote/footnote_id_prefix') . \mb_strtolower($node->getReference()->getLabel(), 'UTF-8')); $attrs->set('role', 'doc-endnote'); return new HtmlElement( 'li', $attrs->export(), $childRenderer->renderNodes($node->children()), true ); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'footnote'; } /** * @param Footnote $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { Footnote::assertInstanceOf($node); return [ 'reference' => $node->getReference()->getLabel(), ]; } } commonmark/src/Extension/Footnote/Renderer/FootnoteBackrefRenderer.php 0000644 00000004530 15021222733 0022267 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Renderer; use League\CommonMark\Extension\Footnote\Node\FootnoteBackref; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class FootnoteBackrefRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { public const DEFAULT_SYMBOL = '↩'; private ConfigurationInterface $config; /** * @param FootnoteBackref $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { FootnoteBackref::assertInstanceOf($node); $attrs = $node->data->getData('attributes'); $attrs->append('class', $this->config->get('footnote/backref_class')); $attrs->set('rev', 'footnote'); $attrs->set('href', \mb_strtolower($node->getReference()->getDestination(), 'UTF-8')); $attrs->set('role', 'doc-backlink'); $symbol = $this->config->get('footnote/backref_symbol'); \assert(\is_string($symbol)); return ' ' . new HtmlElement('a', $attrs->export(), \htmlspecialchars($symbol), true); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'footnote_backref'; } /** * @param FootnoteBackref $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { FootnoteBackref::assertInstanceOf($node); return [ 'reference' => $node->getReference()->getLabel(), ]; } } commonmark/src/Extension/Footnote/Renderer/FootnoteContainerRenderer.php 0000644 00000004101 15021222733 0022646 0 ustar 00 <?php /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * (c) Rezo Zero / Ambroise Maupate * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ declare(strict_types=1); namespace League\CommonMark\Extension\Footnote\Renderer; use League\CommonMark\Extension\Footnote\Node\FootnoteContainer; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class FootnoteContainerRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { private ConfigurationInterface $config; /** * @param FootnoteContainer $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { FootnoteContainer::assertInstanceOf($node); $attrs = $node->data->getData('attributes'); $attrs->append('class', $this->config->get('footnote/container_class')); $attrs->set('role', 'doc-endnotes'); $contents = new HtmlElement('ol', [], $childRenderer->renderNodes($node->children())); if ($this->config->get('footnote/container_add_hr')) { $contents = [new HtmlElement('hr', [], null, true), $contents]; } return new HtmlElement('div', $attrs->export(), $contents); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } public function getXmlTagName(Node $node): string { return 'footnote_container'; } /** * @return array<string, scalar> */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Extension/TaskList/TaskListItemMarkerParser.php 0000644 00000003131 15021222733 0020610 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TaskList; use League\CommonMark\Extension\CommonMark\Node\Block\ListItem; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class TaskListItemMarkerParser implements InlineParserInterface { public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::oneOf('[ ]', '[x]'); } public function parse(InlineParserContext $inlineContext): bool { $container = $inlineContext->getContainer(); // Checkbox must come at the beginning of the first paragraph of the list item if ($container->hasChildren() || ! ($container instanceof Paragraph && $container->parent() && $container->parent() instanceof ListItem)) { return false; } $cursor = $inlineContext->getCursor(); $oldState = $cursor->saveState(); $cursor->advanceBy(3); if ($cursor->getNextNonSpaceCharacter() === null) { $cursor->restoreState($oldState); return false; } $isChecked = $inlineContext->getFullMatch() !== '[ ]'; $container->appendChild(new TaskListItemMarker($isChecked)); return true; } } commonmark/src/Extension/TaskList/TaskListItemMarkerRenderer.php 0000644 00000003425 15021222733 0021130 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TaskList; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TaskListItemMarkerRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param TaskListItemMarker $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): \Stringable { TaskListItemMarker::assertInstanceOf($node); $attrs = $node->data->get('attributes'); $checkbox = new HtmlElement('input', $attrs, '', true); if ($node->isChecked()) { $checkbox->setAttribute('checked', ''); } $checkbox->setAttribute('disabled', ''); $checkbox->setAttribute('type', 'checkbox'); return $checkbox; } public function getXmlTagName(Node $node): string { return 'task_list_item_marker'; } /** * @param TaskListItemMarker $node * * @return array<string, scalar> * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlAttributes(Node $node): array { TaskListItemMarker::assertInstanceOf($node); if ($node->isChecked()) { return ['checked' => 'checked']; } return []; } } commonmark/src/Extension/TaskList/TaskListExtension.php 0000644 00000001371 15021222733 0017353 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TaskList; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ExtensionInterface; final class TaskListExtension implements ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void { $environment->addInlineParser(new TaskListItemMarkerParser(), 35); $environment->addRenderer(TaskListItemMarker::class, new TaskListItemMarkerRenderer()); } } commonmark/src/Extension/TaskList/TaskListItemMarker.php 0000644 00000001470 15021222733 0017437 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\TaskList; use League\CommonMark\Node\Inline\AbstractInline; final class TaskListItemMarker extends AbstractInline { /** @psalm-readonly-allow-private-mutation */ private bool $checked; public function __construct(bool $isCompleted) { parent::__construct(); $this->checked = $isCompleted; } public function isChecked(): bool { return $this->checked; } public function setChecked(bool $checked): void { $this->checked = $checked; } } commonmark/src/Extension/ExtensionInterface.php 0000644 00000001143 15021222733 0015754 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension; use League\CommonMark\Environment\EnvironmentBuilderInterface; interface ExtensionInterface { public function register(EnvironmentBuilderInterface $environment): void; } commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlExtension.php 0000644 00000003315 15021222733 0023033 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DisallowedRawHtml; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; use League\CommonMark\Extension\CommonMark\Node\Inline\HtmlInline; use League\CommonMark\Extension\CommonMark\Renderer\Block\HtmlBlockRenderer; use League\CommonMark\Extension\CommonMark\Renderer\Inline\HtmlInlineRenderer; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class DisallowedRawHtmlExtension implements ConfigurableExtensionInterface { private const DEFAULT_DISALLOWED_TAGS = [ 'title', 'textarea', 'style', 'xmp', 'iframe', 'noembed', 'noframes', 'script', 'plaintext', ]; public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('disallowed_raw_html', Expect::structure([ 'disallowed_tags' => Expect::listOf('string')->default(self::DEFAULT_DISALLOWED_TAGS)->mergeDefaults(false), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addRenderer(HtmlBlock::class, new DisallowedRawHtmlRenderer(new HtmlBlockRenderer()), 50); $environment->addRenderer(HtmlInline::class, new DisallowedRawHtmlRenderer(new HtmlInlineRenderer()), 50); } } commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php 0000644 00000003603 15021222733 0022625 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\DisallowedRawHtml; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class DisallowedRawHtmlRenderer implements NodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly */ private NodeRendererInterface $innerRenderer; /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function __construct(NodeRendererInterface $innerRenderer) { $this->innerRenderer = $innerRenderer; } public function render(Node $node, ChildNodeRendererInterface $childRenderer): ?string { $rendered = (string) $this->innerRenderer->render($node, $childRenderer); if ($rendered === '') { return ''; } $tags = (array) $this->config->get('disallowed_raw_html/disallowed_tags'); if (\count($tags) === 0) { return $rendered; } $regex = \sprintf('/<(\/?(?:%s)[ \/>])/i', \implode('|', \array_map('preg_quote', $tags))); // Match these types of tags: <title> </title> <title x="sdf"> <title/> <title /> return \preg_replace($regex, '<$1', $rendered); } public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; if ($this->innerRenderer instanceof ConfigurationAwareInterface) { $this->innerRenderer->setConfiguration($configuration); } } } commonmark/src/Extension/Autolink/EmailAutolinkParser.php 0000644 00000002651 15021222733 0017665 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class EmailAutolinkParser implements InlineParserInterface { private const REGEX = '[A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+'; public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::regex(self::REGEX); } public function parse(InlineParserContext $inlineContext): bool { $email = $inlineContext->getFullMatch(); // The last character cannot be - or _ if (\in_array(\substr($email, -1), ['-', '_'], true)) { return false; } // Does the URL end with punctuation that should be stripped? if (\substr($email, -1) === '.') { $email = \substr($email, 0, -1); } $inlineContext->getCursor()->advanceBy(\strlen($email)); $inlineContext->getContainer()->appendChild(new Link('mailto:' . $email, $email)); return true; } } commonmark/src/Extension/Autolink/UrlAutolinkParser.php 0000644 00000016177 15021222733 0017410 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Extension\CommonMark\Node\Inline\Link; use League\CommonMark\Parser\Inline\InlineParserInterface; use League\CommonMark\Parser\Inline\InlineParserMatch; use League\CommonMark\Parser\InlineParserContext; final class UrlAutolinkParser implements InlineParserInterface { private const ALLOWED_AFTER = [null, ' ', "\t", "\n", "\x0b", "\x0c", "\x0d", '*', '_', '~', '(']; // RegEx adapted from https://github.com/symfony/symfony/blob/6.3/src/Symfony/Component/Validator/Constraints/UrlValidator.php private const REGEX = '~ ( # Must start with a supported scheme + auth, or "www" (?: (?:%s):// # protocol (?:(?:(?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+:)?((?:[\_\.\pL\pN-]|%%[0-9A-Fa-f]{2})+)@)? # basic auth |www\.) (?: (?: (?:xn--[a-z0-9-]++\.)*+xn--[a-z0-9-]++ # a domain name using punycode | (?:[\pL\pN\pS\pM\-\_]++\.)+[\pL\pN\pM]++ # a multi-level domain name | [a-z0-9\-\_]++ # a single-level domain name )\.? | # or \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address | # or \[ (?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::)))) \] # an IPv6 address ) (?::[0-9]+)? # a port (optional) (?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path (?:\? (?:[\pL\pN\-._\~!$&\'\[\]()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional) (?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional) )~ixu'; /** * @var string[] * * @psalm-readonly */ private array $prefixes = ['www.']; /** * @psalm-var non-empty-string * * @psalm-readonly */ private string $finalRegex; private string $defaultProtocol; /** * @param array<int, string> $allowedProtocols */ public function __construct(array $allowedProtocols = ['http', 'https', 'ftp'], string $defaultProtocol = 'http') { /** * @psalm-suppress PropertyTypeCoercion */ $this->finalRegex = \sprintf(self::REGEX, \implode('|', $allowedProtocols)); foreach ($allowedProtocols as $protocol) { $this->prefixes[] = $protocol . '://'; } $this->defaultProtocol = $defaultProtocol; } public function getMatchDefinition(): InlineParserMatch { return InlineParserMatch::oneOf(...$this->prefixes); } public function parse(InlineParserContext $inlineContext): bool { $cursor = $inlineContext->getCursor(); // Autolinks can only come at the beginning of a line, after whitespace, or certain delimiting characters $previousChar = $cursor->peek(-1); if (! \in_array($previousChar, self::ALLOWED_AFTER, true)) { return false; } // Check if we have a valid URL if (! \preg_match($this->finalRegex, $cursor->getRemainder(), $matches)) { return false; } $url = $matches[0]; // Does the URL end with punctuation that should be stripped? if (\preg_match('/(.+?)([?!.,:*_~]+)$/', $url, $matches)) { // Add the punctuation later $url = $matches[1]; } // Does the URL end with something that looks like an entity reference? if (\preg_match('/(.+)(&[A-Za-z0-9]+;)$/', $url, $matches)) { $url = $matches[1]; } // Does the URL need unmatched parens chopped off? if (\substr($url, -1) === ')' && ($diff = self::diffParens($url)) > 0) { $url = \substr($url, 0, -$diff); } $cursor->advanceBy(\mb_strlen($url, 'UTF-8')); // Auto-prefix 'http(s)://' onto 'www' URLs if (\substr($url, 0, 4) === 'www.') { $inlineContext->getContainer()->appendChild(new Link($this->defaultProtocol . '://' . $url, $url)); return true; } $inlineContext->getContainer()->appendChild(new Link($url, $url)); return true; } /** * @psalm-pure */ private static function diffParens(string $content): int { // Scan the entire autolink for the total number of parentheses. // If there is a greater number of closing parentheses than opening ones, // we don’t consider ANY of the last characters as part of the autolink, // in order to facilitate including an autolink inside a parenthesis. \preg_match_all('/[()]/', $content, $matches); $charCount = ['(' => 0, ')' => 0]; foreach ($matches[0] as $char) { $charCount[$char]++; } return $charCount[')'] - $charCount['(']; } } commonmark/src/Extension/Autolink/AutolinkExtension.php 0000644 00000002477 15021222733 0017443 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Extension\Autolink; use League\CommonMark\Environment\EnvironmentBuilderInterface; use League\CommonMark\Extension\ConfigurableExtensionInterface; use League\Config\ConfigurationBuilderInterface; use Nette\Schema\Expect; final class AutolinkExtension implements ConfigurableExtensionInterface { public function configureSchema(ConfigurationBuilderInterface $builder): void { $builder->addSchema('autolink', Expect::structure([ 'allowed_protocols' => Expect::listOf('string')->default(['http', 'https', 'ftp'])->mergeDefaults(false), 'default_protocol' => Expect::string()->default('http'), ])); } public function register(EnvironmentBuilderInterface $environment): void { $environment->addInlineParser(new EmailAutolinkParser()); $environment->addInlineParser(new UrlAutolinkParser( $environment->getConfiguration()->get('autolink.allowed_protocols'), $environment->getConfiguration()->get('autolink.default_protocol'), )); } } commonmark/src/Util/Html5EntityDecoder.php 0000644 00000003427 15021222733 0014603 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; /** * @psalm-immutable */ final class Html5EntityDecoder { /** * @psalm-pure */ public static function decode(string $entity): string { if (\substr($entity, -1) !== ';') { return $entity; } if (\substr($entity, 0, 2) === '&#') { if (\strtolower(\substr($entity, 2, 1)) === 'x') { return self::fromHex(\substr($entity, 3, -1)); } return self::fromDecimal(\substr($entity, 2, -1)); } return \html_entity_decode($entity, \ENT_QUOTES | \ENT_HTML5, 'UTF-8'); } /** * @param mixed $number * * @psalm-pure */ private static function fromDecimal($number): string { // Only convert code points within planes 0-2, excluding NULL // phpcs:ignore Generic.PHP.ForbiddenFunctions.Found if (empty($number) || $number > 0x2FFFF) { return self::fromHex('fffd'); } $entity = '&#' . $number . ';'; $converted = \mb_decode_numericentity($entity, [0x0, 0x2FFFF, 0, 0xFFFF], 'UTF-8'); if ($converted === $entity) { return self::fromHex('fffd'); } return $converted; } /** * @psalm-pure */ private static function fromHex(string $hexChars): string { return self::fromDecimal(\hexdec($hexChars)); } } commonmark/src/Util/PrioritizedList.php 0000644 00000003151 15021222733 0014261 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; /** * @internal * * @phpstan-template T * @phpstan-implements \IteratorAggregate<T> */ final class PrioritizedList implements \IteratorAggregate { /** * @var array<int, array<mixed>> * @phpstan-var array<int, array<T>> */ private array $list = []; /** * @var \Traversable<mixed>|null * @phpstan-var \Traversable<T>|null */ private ?\Traversable $optimized = null; /** * @param mixed $item * * @phpstan-param T $item */ public function add($item, int $priority): void { $this->list[$priority][] = $item; $this->optimized = null; } /** * @return \Traversable<int, mixed> * * @phpstan-return \Traversable<int, T> */ #[\ReturnTypeWillChange] public function getIterator(): \Traversable { if ($this->optimized === null) { \krsort($this->list); $sorted = []; foreach ($this->list as $group) { foreach ($group as $item) { $sorted[] = $item; } } $this->optimized = new \ArrayIterator($sorted); } return $this->optimized; } } commonmark/src/Util/SpecReader.php 0000644 00000004200 15021222733 0013132 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; use League\CommonMark\Exception\IOException; /** * Reads in a CommonMark spec document and extracts the input/output examples for testing against them */ final class SpecReader { private function __construct() { } /** * @return iterable<string, array{input: string, output: string, type: string, section: string, number: int}> */ public static function read(string $data): iterable { // Normalize newlines for platform independence $data = \preg_replace('/\r\n?/', "\n", $data); \assert($data !== null); $data = \preg_replace('/<!-- END TESTS -->.*$/', '', $data); \assert($data !== null); \preg_match_all('/^`{32} (example ?\w*)\n([\s\S]*?)^\.\n([\s\S]*?)^`{32}$|^#{1,6} *(.*)$/m', $data, $matches, PREG_SET_ORDER); $currentSection = 'Example'; $exampleNumber = 0; foreach ($matches as $match) { if (isset($match[4])) { $currentSection = $match[4]; continue; } yield \trim($currentSection . ' #' . $exampleNumber) => [ 'input' => \str_replace('→', "\t", $match[2]), 'output' => \str_replace('→', "\t", $match[3]), 'type' => $match[1], 'section' => $currentSection, 'number' => $exampleNumber++, ]; } } /** * @return iterable<string, array{input: string, output: string, type: string, section: string, number: int}> * * @throws IOException if the file cannot be loaded */ public static function readFile(string $filename): iterable { if (($data = \file_get_contents($filename)) === false) { throw new IOException(\sprintf('Failed to load spec from %s', $filename)); } return self::read($data); } } commonmark/src/Util/UrlEncoder.php 0000644 00000005073 15021222733 0013170 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; use League\CommonMark\Exception\UnexpectedEncodingException; /** * @psalm-immutable */ final class UrlEncoder { private const ENCODE_CACHE = ['%00', '%01', '%02', '%03', '%04', '%05', '%06', '%07', '%08', '%09', '%0A', '%0B', '%0C', '%0D', '%0E', '%0F', '%10', '%11', '%12', '%13', '%14', '%15', '%16', '%17', '%18', '%19', '%1A', '%1B', '%1C', '%1D', '%1E', '%1F', '%20', '!', '%22', '#', '$', '%25', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '%3C', '=', '%3E', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '%5B', '%5C', '%5D', '%5E', '_', '%60', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '%7B', '%7C', '%7D', '~', '%7F']; /** * @throws UnexpectedEncodingException if a non-UTF-8-compatible encoding is used * * @psalm-pure */ public static function unescapeAndEncode(string $uri): string { // Optimization: if the URL only includes characters we know will be kept as-is, then just return the URL as-is. if (\preg_match('/^[A-Za-z0-9~!@#$&*()\-_=+;:,.\/?]+$/', $uri)) { return $uri; } if (! \mb_check_encoding($uri, 'UTF-8')) { throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected'); } $result = ''; $chars = \mb_str_split($uri, 1, 'UTF-8'); $l = \count($chars); for ($i = 0; $i < $l; $i++) { $code = $chars[$i]; if ($code === '%' && $i + 2 < $l) { if (\preg_match('/^[0-9a-f]{2}$/i', $chars[$i + 1] . $chars[$i + 2]) === 1) { $result .= '%' . $chars[$i + 1] . $chars[$i + 2]; $i += 2; continue; } } if (\ord($code) < 128) { $result .= self::ENCODE_CACHE[\ord($code)]; continue; } $result .= \rawurlencode($code); } return $result; } } commonmark/src/Util/HtmlElement.php 0000644 00000010054 15021222733 0013337 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; final class HtmlElement implements \Stringable { /** @psalm-readonly */ private string $tagName; /** @var array<string, string|bool> */ private array $attributes = []; /** @var \Stringable|\Stringable[]|string */ private $contents; /** @psalm-readonly */ private bool $selfClosing; /** * @param string $tagName Name of the HTML tag * @param array<string, string|string[]|bool> $attributes Array of attributes (values should be unescaped) * @param \Stringable|\Stringable[]|string|null $contents Inner contents, pre-escaped if needed * @param bool $selfClosing Whether the tag is self-closing */ public function __construct(string $tagName, array $attributes = [], $contents = '', bool $selfClosing = false) { $this->tagName = $tagName; $this->selfClosing = $selfClosing; foreach ($attributes as $name => $value) { $this->setAttribute($name, $value); } $this->setContents($contents ?? ''); } /** @psalm-immutable */ public function getTagName(): string { return $this->tagName; } /** * @return array<string, string|bool> * * @psalm-immutable */ public function getAllAttributes(): array { return $this->attributes; } /** * @return string|bool|null * * @psalm-immutable */ public function getAttribute(string $key) { return $this->attributes[$key] ?? null; } /** * @param string|string[]|bool $value */ public function setAttribute(string $key, $value = true): self { if (\is_array($value)) { $this->attributes[$key] = \implode(' ', \array_unique($value)); } else { $this->attributes[$key] = $value; } return $this; } /** * @return \Stringable|\Stringable[]|string * * @psalm-immutable */ public function getContents(bool $asString = true) { if (! $asString) { return $this->contents; } return $this->getContentsAsString(); } /** * Sets the inner contents of the tag (must be pre-escaped if needed) * * @param \Stringable|\Stringable[]|string $contents * * @return $this */ public function setContents($contents): self { $this->contents = $contents ?? ''; // @phpstan-ignore-line return $this; } /** @psalm-immutable */ public function __toString(): string { $result = '<' . $this->tagName; foreach ($this->attributes as $key => $value) { if ($value === true) { $result .= ' ' . $key; } elseif ($value === false) { continue; } else { $result .= ' ' . $key . '="' . Xml::escape($value) . '"'; } } if ($this->contents !== '') { $result .= '>' . $this->getContentsAsString() . '</' . $this->tagName . '>'; } elseif ($this->selfClosing && $this->tagName === 'input') { $result .= '>'; } elseif ($this->selfClosing) { $result .= ' />'; } else { $result .= '></' . $this->tagName . '>'; } return $result; } /** @psalm-immutable */ private function getContentsAsString(): string { if (\is_string($this->contents)) { return $this->contents; } if (\is_array($this->contents)) { return \implode('', $this->contents); } return (string) $this->contents; } } commonmark/src/Util/Xml.php 0000644 00000001342 15021222733 0011661 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; /** * Utility class for handling/generating XML and HTML * * @psalm-immutable */ final class Xml { /** * @psalm-pure */ public static function escape(string $string): string { return \str_replace(['&', '<', '>', '"'], ['&', '<', '>', '"'], $string); } } commonmark/src/Util/ArrayCollection.php 0000644 00000006624 15021222733 0014223 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; /** * Array collection * * Provides a wrapper around a standard PHP array. * * @internal * * @phpstan-template T * @phpstan-implements \IteratorAggregate<int, T> * @phpstan-implements \ArrayAccess<int, T> */ final class ArrayCollection implements \IteratorAggregate, \Countable, \ArrayAccess { /** * @var array<int, mixed> * @phpstan-var array<int, T> */ private array $elements; /** * Constructor * * @param array<int|string, mixed> $elements * * @phpstan-param array<int, T> $elements */ public function __construct(array $elements = []) { $this->elements = $elements; } /** * @return mixed|false * * @phpstan-return T|false */ public function first() { return \reset($this->elements); } /** * @return mixed|false * * @phpstan-return T|false */ public function last() { return \end($this->elements); } /** * Retrieve an external iterator * * @return \ArrayIterator<int, mixed> * * @phpstan-return \ArrayIterator<int, T> */ #[\ReturnTypeWillChange] public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->elements); } /** * Count elements of an object * * @return int The count as an integer. */ public function count(): int { return \count($this->elements); } /** * Whether an offset exists * * {@inheritDoc} * * @phpstan-param int $offset */ public function offsetExists($offset): bool { return \array_key_exists($offset, $this->elements); } /** * Offset to retrieve * * {@inheritDoc} * * @phpstan-param int $offset * * @phpstan-return T|null */ #[\ReturnTypeWillChange] public function offsetGet($offset) { return $this->elements[$offset] ?? null; } /** * Offset to set * * {@inheritDoc} * * @phpstan-param int|null $offset * @phpstan-param T $value */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { if ($offset === null) { $this->elements[] = $value; } else { $this->elements[$offset] = $value; } } /** * Offset to unset * * {@inheritDoc} * * @phpstan-param int $offset */ #[\ReturnTypeWillChange] public function offsetUnset($offset): void { if (! \array_key_exists($offset, $this->elements)) { return; } unset($this->elements[$offset]); } /** * Returns a subset of the array * * @return array<int, mixed> * * @phpstan-return array<int, T> */ public function slice(int $offset, ?int $length = null): array { return \array_slice($this->elements, $offset, $length, true); } /** * @return array<int, mixed> * * @phpstan-return array<int, T> */ public function toArray(): array { return $this->elements; } } commonmark/src/Util/RegexHelper.php 0000644 00000023700 15021222733 0013335 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; use League\CommonMark\Exception\InvalidArgumentException; use League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock; /** * Provides regular expressions and utilities for parsing Markdown * * All of the PARTIAL_ regex constants assume that they'll be used in case-insensitive searches * All other complete regexes provided by this class (either via constants or methods) will have case-insensitivity enabled. * * @phpcs:disable Generic.Strings.UnnecessaryStringConcat.Found * * @psalm-immutable */ final class RegexHelper { // Partial regular expressions (wrap with `/` on each side and add the case-insensitive `i` flag before use) public const PARTIAL_ENTITY = '&(?:#x[a-f0-9]{1,6}|#[0-9]{1,7}|[a-z][a-z0-9]{1,31});'; public const PARTIAL_ESCAPABLE = '[!"#$%&\'()*+,.\/:;<=>?@[\\\\\]^_`{|}~-]'; public const PARTIAL_ESCAPED_CHAR = '\\\\' . self::PARTIAL_ESCAPABLE; public const PARTIAL_IN_DOUBLE_QUOTES = '"(' . self::PARTIAL_ESCAPED_CHAR . '|[^"\x00])*"'; public const PARTIAL_IN_SINGLE_QUOTES = '\'(' . self::PARTIAL_ESCAPED_CHAR . '|[^\'\x00])*\''; public const PARTIAL_IN_PARENS = '\\((' . self::PARTIAL_ESCAPED_CHAR . '|[^)\x00])*\\)'; public const PARTIAL_REG_CHAR = '[^\\\\()\x00-\x20]'; public const PARTIAL_IN_PARENS_NOSP = '\((' . self::PARTIAL_REG_CHAR . '|' . self::PARTIAL_ESCAPED_CHAR . '|\\\\)*\)'; public const PARTIAL_TAGNAME = '[a-z][a-z0-9-]*'; public const PARTIAL_BLOCKTAGNAME = '(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h1|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)'; public const PARTIAL_ATTRIBUTENAME = '[a-z_:][a-z0-9:._-]*'; public const PARTIAL_UNQUOTEDVALUE = '[^"\'=<>`\x00-\x20]+'; public const PARTIAL_SINGLEQUOTEDVALUE = '\'[^\']*\''; public const PARTIAL_DOUBLEQUOTEDVALUE = '"[^"]*"'; public const PARTIAL_ATTRIBUTEVALUE = '(?:' . self::PARTIAL_UNQUOTEDVALUE . '|' . self::PARTIAL_SINGLEQUOTEDVALUE . '|' . self::PARTIAL_DOUBLEQUOTEDVALUE . ')'; public const PARTIAL_ATTRIBUTEVALUESPEC = '(?:' . '\s*=' . '\s*' . self::PARTIAL_ATTRIBUTEVALUE . ')'; public const PARTIAL_ATTRIBUTE = '(?:' . '\s+' . self::PARTIAL_ATTRIBUTENAME . self::PARTIAL_ATTRIBUTEVALUESPEC . '?)'; public const PARTIAL_OPENTAG = '<' . self::PARTIAL_TAGNAME . self::PARTIAL_ATTRIBUTE . '*' . '\s*\/?>'; public const PARTIAL_CLOSETAG = '<\/' . self::PARTIAL_TAGNAME . '\s*[>]'; public const PARTIAL_OPENBLOCKTAG = '<' . self::PARTIAL_BLOCKTAGNAME . self::PARTIAL_ATTRIBUTE . '*' . '\s*\/?>'; public const PARTIAL_CLOSEBLOCKTAG = '<\/' . self::PARTIAL_BLOCKTAGNAME . '\s*[>]'; public const PARTIAL_HTMLCOMMENT = '<!-->|<!--->|<!--[\s\S]*?-->'; public const PARTIAL_PROCESSINGINSTRUCTION = '[<][?][\s\S]*?[?][>]'; public const PARTIAL_DECLARATION = '<![A-Za-z]+' . '[^>]*>'; public const PARTIAL_CDATA = '<!\[CDATA\[[\s\S]*?]\]>'; public const PARTIAL_HTMLTAG = '(?:' . self::PARTIAL_OPENTAG . '|' . self::PARTIAL_CLOSETAG . '|' . self::PARTIAL_HTMLCOMMENT . '|' . self::PARTIAL_PROCESSINGINSTRUCTION . '|' . self::PARTIAL_DECLARATION . '|' . self::PARTIAL_CDATA . ')'; public const PARTIAL_HTMLBLOCKOPEN = '<(?:' . self::PARTIAL_BLOCKTAGNAME . '(?:[\s\/>]|$)' . '|' . '\/' . self::PARTIAL_BLOCKTAGNAME . '(?:[\s>]|$)' . '|' . '[?!])'; public const PARTIAL_LINK_TITLE = '^(?:"(' . self::PARTIAL_ESCAPED_CHAR . '|[^"\x00])*"' . '|' . '\'(' . self::PARTIAL_ESCAPED_CHAR . '|[^\'\x00])*\'' . '|' . '\((' . self::PARTIAL_ESCAPED_CHAR . '|[^()\x00])*\))'; public const REGEX_PUNCTUATION = '/^[!"#$%&\'()*+,\-.\\/:;<=>?@\\[\\]\\\\^_`{|}~\p{P}\p{S}]/u'; public const REGEX_UNSAFE_PROTOCOL = '/^javascript:|vbscript:|file:|data:/i'; public const REGEX_SAFE_DATA_PROTOCOL = '/^data:image\/(?:png|gif|jpeg|webp)/i'; public const REGEX_NON_SPACE = '/[^ \t\f\v\r\n]/'; public const REGEX_WHITESPACE_CHAR = '/^[ \t\n\x0b\x0c\x0d]/'; public const REGEX_UNICODE_WHITESPACE_CHAR = '/^\pZ|\s/u'; public const REGEX_THEMATIC_BREAK = '/^(?:\*[ \t]*){3,}$|^(?:_[ \t]*){3,}$|^(?:-[ \t]*){3,}$/'; public const REGEX_LINK_DESTINATION_BRACES = '/^(?:<(?:[^<>\\n\\\\\\x00]|\\\\.)*>)/'; /** * @psalm-pure */ public static function isEscapable(string $character): bool { return \preg_match('/' . self::PARTIAL_ESCAPABLE . '/', $character) === 1; } /** * @psalm-pure */ public static function isLetter(?string $character): bool { if ($character === null) { return false; } return \preg_match('/[\pL]/u', $character) === 1; } /** * Attempt to match a regex in string s at offset offset * * @psalm-param non-empty-string $regex * * @return int|null Index of match, or null * * @psalm-pure */ public static function matchAt(string $regex, string $string, int $offset = 0): ?int { $matches = []; $string = \mb_substr($string, $offset, null, 'UTF-8'); if (! \preg_match($regex, $string, $matches, \PREG_OFFSET_CAPTURE)) { return null; } // PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying $charPos = \mb_strlen(\mb_strcut($string, 0, $matches[0][1], 'UTF-8'), 'UTF-8'); return $offset + $charPos; } /** * Functional wrapper around preg_match_all which only returns the first set of matches * * @psalm-param non-empty-string $pattern * * @return string[]|null * * @psalm-pure */ public static function matchFirst(string $pattern, string $subject, int $offset = 0): ?array { if ($offset !== 0) { $subject = \substr($subject, $offset); } \preg_match_all($pattern, $subject, $matches, \PREG_SET_ORDER); if ($matches === []) { return null; } return $matches[0] ?: null; } /** * Replace backslash escapes with literal characters * * @psalm-pure */ public static function unescape(string $string): string { $allEscapedChar = '/\\\\(' . self::PARTIAL_ESCAPABLE . ')/'; $escaped = \preg_replace($allEscapedChar, '$1', $string); \assert(\is_string($escaped)); return \preg_replace_callback('/' . self::PARTIAL_ENTITY . '/i', static fn ($e) => Html5EntityDecoder::decode($e[0]), $escaped); } /** * @internal * * @param int $type HTML block type * * @psalm-param HtmlBlock::TYPE_* $type * * @phpstan-param HtmlBlock::TYPE_* $type * * @psalm-return non-empty-string * * @throws InvalidArgumentException if an invalid type is given * * @psalm-pure */ public static function getHtmlBlockOpenRegex(int $type): string { switch ($type) { case HtmlBlock::TYPE_1_CODE_CONTAINER: return '/^<(?:script|pre|textarea|style)(?:\s|>|$)/i'; case HtmlBlock::TYPE_2_COMMENT: return '/^<!--/'; case HtmlBlock::TYPE_3: return '/^<[?]/'; case HtmlBlock::TYPE_4: return '/^<![A-Z]/i'; case HtmlBlock::TYPE_5_CDATA: return '/^<!\[CDATA\[/i'; case HtmlBlock::TYPE_6_BLOCK_ELEMENT: return '%^<[/]?(?:address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[123456]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul)(?:\s|[/]?[>]|$)%i'; case HtmlBlock::TYPE_7_MISC_ELEMENT: return '/^(?:' . self::PARTIAL_OPENTAG . '|' . self::PARTIAL_CLOSETAG . ')\\s*$/i'; default: throw new InvalidArgumentException('Invalid HTML block type'); } } /** * @internal * * @param int $type HTML block type * * @psalm-param HtmlBlock::TYPE_* $type * * @phpstan-param HtmlBlock::TYPE_* $type * * @psalm-return non-empty-string * * @throws InvalidArgumentException if an invalid type is given * * @psalm-pure */ public static function getHtmlBlockCloseRegex(int $type): string { switch ($type) { case HtmlBlock::TYPE_1_CODE_CONTAINER: return '%<\/(?:script|pre|textarea|style)>%i'; case HtmlBlock::TYPE_2_COMMENT: return '/-->/'; case HtmlBlock::TYPE_3: return '/\?>/'; case HtmlBlock::TYPE_4: return '/>/'; case HtmlBlock::TYPE_5_CDATA: return '/\]\]>/'; default: throw new InvalidArgumentException('Invalid HTML block type'); } } /** * @psalm-pure */ public static function isLinkPotentiallyUnsafe(string $url): bool { return \preg_match(self::REGEX_UNSAFE_PROTOCOL, $url) !== 0 && \preg_match(self::REGEX_SAFE_DATA_PROTOCOL, $url) === 0; } } commonmark/src/Util/LinkParserHelper.php 0000644 00000007652 15021222733 0014345 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; use League\CommonMark\Parser\Cursor; /** * @psalm-immutable */ final class LinkParserHelper { /** * Attempt to parse link destination * * @return string|null The string, or null if no match */ public static function parseLinkDestination(Cursor $cursor): ?string { if ($res = $cursor->match(RegexHelper::REGEX_LINK_DESTINATION_BRACES)) { // Chop off surrounding <..>: return UrlEncoder::unescapeAndEncode( RegexHelper::unescape(\substr($res, 1, -1)) ); } if ($cursor->getCurrentCharacter() === '<') { return null; } $destination = self::manuallyParseLinkDestination($cursor); if ($destination === null) { return null; } return UrlEncoder::unescapeAndEncode( RegexHelper::unescape($destination) ); } public static function parseLinkLabel(Cursor $cursor): int { $match = $cursor->match('/^\[(?:[^\\\\\[\]]|\\\\.){0,1000}\]/'); if ($match === null) { return 0; } $length = \mb_strlen($match, 'UTF-8'); if ($length > 1001) { return 0; } return $length; } public static function parsePartialLinkLabel(Cursor $cursor): ?string { return $cursor->match('/^(?:[^\\\\\[\]]+|\\\\.?)*/'); } /** * Attempt to parse link title (sans quotes) * * @return string|null The string, or null if no match */ public static function parseLinkTitle(Cursor $cursor): ?string { if ($title = $cursor->match('/' . RegexHelper::PARTIAL_LINK_TITLE . '/')) { // Chop off quotes from title and unescape return RegexHelper::unescape(\substr($title, 1, -1)); } return null; } public static function parsePartialLinkTitle(Cursor $cursor, string $endDelimiter): ?string { $endDelimiter = \preg_quote($endDelimiter, '/'); $regex = \sprintf('/(%s|[^%s\x00])*(?:%s)?/', RegexHelper::PARTIAL_ESCAPED_CHAR, $endDelimiter, $endDelimiter); if (($partialTitle = $cursor->match($regex)) === null) { return null; } return RegexHelper::unescape($partialTitle); } private static function manuallyParseLinkDestination(Cursor $cursor): ?string { $oldPosition = $cursor->getPosition(); $oldState = $cursor->saveState(); $openParens = 0; while (($c = $cursor->getCurrentCharacter()) !== null) { if ($c === '\\' && ($peek = $cursor->peek()) !== null && RegexHelper::isEscapable($peek)) { $cursor->advanceBy(2); } elseif ($c === '(') { $cursor->advanceBy(1); $openParens++; } elseif ($c === ')') { if ($openParens < 1) { break; } $cursor->advanceBy(1); $openParens--; } elseif (\preg_match(RegexHelper::REGEX_WHITESPACE_CHAR, $c)) { break; } else { $cursor->advanceBy(1); } } if ($openParens !== 0) { return null; } if ($cursor->getPosition() === $oldPosition && (! isset($c) || $c !== ')')) { return null; } $newPos = $cursor->getPosition(); $cursor->restoreState($oldState); $cursor->advanceBy($newPos - $cursor->getPosition()); return $cursor->getPreviousText(); } } commonmark/src/Util/HtmlFilter.php 0000644 00000002674 15021222733 0013204 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Util; use League\CommonMark\Exception\InvalidArgumentException; /** * @psalm-immutable */ final class HtmlFilter { // Return the entire string as-is public const ALLOW = 'allow'; // Escape the entire string so any HTML/JS won't be interpreted as such public const ESCAPE = 'escape'; // Return an empty string public const STRIP = 'strip'; /** * Runs the given HTML through the given filter * * @param string $html HTML input to be filtered * @param string $filter One of the HtmlFilter constants * * @return string Filtered HTML * * @throws InvalidArgumentException when an invalid $filter is given * * @psalm-pure */ public static function filter(string $html, string $filter): string { switch ($filter) { case self::STRIP: return ''; case self::ESCAPE: return \htmlspecialchars($html, \ENT_NOQUOTES); case self::ALLOW: return $html; default: throw new InvalidArgumentException(\sprintf('Invalid filter provided: "%s"', $filter)); } } } commonmark/src/Renderer/HtmlRenderer.php 0000644 00000005465 15021222733 0014357 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Environment\EnvironmentInterface; use League\CommonMark\Event\DocumentPreRenderEvent; use League\CommonMark\Event\DocumentRenderedEvent; use League\CommonMark\Node\Block\AbstractBlock; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Node; use League\CommonMark\Output\RenderedContent; use League\CommonMark\Output\RenderedContentInterface; final class HtmlRenderer implements DocumentRendererInterface, ChildNodeRendererInterface { /** @psalm-readonly */ private EnvironmentInterface $environment; public function __construct(EnvironmentInterface $environment) { $this->environment = $environment; } public function renderDocument(Document $document): RenderedContentInterface { $this->environment->dispatch(new DocumentPreRenderEvent($document, 'html')); $output = new RenderedContent($document, (string) $this->renderNode($document)); $event = new DocumentRenderedEvent($output); $this->environment->dispatch($event); return $event->getOutput(); } /** * {@inheritDoc} */ public function renderNodes(iterable $nodes): string { $output = ''; $isFirstItem = true; foreach ($nodes as $node) { if (! $isFirstItem && $node instanceof AbstractBlock) { $output .= $this->getBlockSeparator(); } $output .= $this->renderNode($node); $isFirstItem = false; } return $output; } /** * @return \Stringable|string * * @throws NoMatchingRendererException */ private function renderNode(Node $node) { $renderers = $this->environment->getRenderersForClass(\get_class($node)); foreach ($renderers as $renderer) { \assert($renderer instanceof NodeRendererInterface); if (($result = $renderer->render($node, $this)) !== null) { return $result; } } throw new NoMatchingRendererException('Unable to find corresponding renderer for node type ' . \get_class($node)); } public function getBlockSeparator(): string { return $this->environment->getConfiguration()->get('renderer/block_separator'); } public function getInnerSeparator(): string { return $this->environment->getConfiguration()->get('renderer/inner_separator'); } } commonmark/src/Renderer/Block/ParagraphRenderer.php 0000644 00000003672 15021222733 0016410 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer\Block; use League\CommonMark\Node\Block\Paragraph; use League\CommonMark\Node\Block\TightBlockInterface; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\HtmlElement; use League\CommonMark\Xml\XmlNodeRendererInterface; final class ParagraphRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Paragraph $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { Paragraph::assertInstanceOf($node); if ($this->inTightList($node)) { return $childRenderer->renderNodes($node->children()); } $attrs = $node->data->get('attributes'); return new HtmlElement('p', $attrs, $childRenderer->renderNodes($node->children())); } public function getXmlTagName(Node $node): string { return 'paragraph'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } private function inTightList(Paragraph $node): bool { // Only check up to two (2) levels above this for tightness $i = 2; while (($node = $node->parent()) && $i--) { if ($node instanceof TightBlockInterface) { return $node->isTight(); } } return false; } } commonmark/src/Renderer/Block/DocumentRenderer.php 0000644 00000002673 15021222733 0016261 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer\Block; use League\CommonMark\Node\Block\Document; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Xml\XmlNodeRendererInterface; final class DocumentRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Document $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { Document::assertInstanceOf($node); $wholeDoc = $childRenderer->renderNodes($node->children()); return $wholeDoc === '' ? '' : $wholeDoc . "\n"; } public function getXmlTagName(Node $node): string { return 'document'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return [ 'xmlns' => 'http://commonmark.org/xml/1.0', ]; } } commonmark/src/Renderer/ChildNodeRendererInterface.php 0000644 00000001275 15021222733 0017120 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Node\Node; /** * Renders multiple nodes by delegating to the individual node renderers and adding spacing where needed */ interface ChildNodeRendererInterface { /** * @param Node[] $nodes */ public function renderNodes(iterable $nodes): string; public function getBlockSeparator(): string; public function getInnerSeparator(): string; } commonmark/src/Renderer/DocumentRendererInterface.php 0000644 00000001257 15021222733 0017045 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Node\Block\Document; use League\CommonMark\Output\RenderedContentInterface; /** * Renders a parsed Document AST */ interface DocumentRendererInterface extends MarkdownRendererInterface { /** * Render the given Document node (and all of its children) */ public function renderDocument(Document $document): RenderedContentInterface; } commonmark/src/Renderer/NoMatchingRendererException.php 0000644 00000000645 15021222733 0017354 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Exception\LogicException; class NoMatchingRendererException extends LogicException { } commonmark/src/Renderer/NodeRendererInterface.php 0000644 00000001226 15021222733 0016150 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Exception\InvalidArgumentException; use League\CommonMark\Node\Node; interface NodeRendererInterface { /** * @return \Stringable|string|null * * @throws InvalidArgumentException if the wrong type of Node is provided */ public function render(Node $node, ChildNodeRendererInterface $childRenderer); } commonmark/src/Renderer/MarkdownRendererInterface.php 0000644 00000001330 15021222733 0017041 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Node\Block\Document; use League\CommonMark\Output\RenderedContentInterface; /** * Renders a parsed Document AST * * @deprecated since 2.3; use {@link DocumentRendererInterface} instead */ interface MarkdownRendererInterface { /** * Render the given Document node (and all of its children) */ public function renderDocument(Document $document): RenderedContentInterface; } commonmark/src/Renderer/HtmlDecorator.php 0000644 00000002357 15021222733 0014530 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer; use League\CommonMark\Node\Node; use League\CommonMark\Util\HtmlElement; final class HtmlDecorator implements NodeRendererInterface { private NodeRendererInterface $inner; private string $tag; /** @var array<string, string|string[]|bool> */ private array $attributes; private bool $selfClosing; /** * @param array<string, string|string[]|bool> $attributes */ public function __construct(NodeRendererInterface $inner, string $tag, array $attributes = [], bool $selfClosing = false) { $this->inner = $inner; $this->tag = $tag; $this->attributes = $attributes; $this->selfClosing = $selfClosing; } /** * {@inheritDoc} */ public function render(Node $node, ChildNodeRendererInterface $childRenderer) { return new HtmlElement($this->tag, $this->attributes, $this->inner->render($node, $childRenderer), $this->selfClosing); } } commonmark/src/Renderer/Inline/NewlineRenderer.php 0000644 00000003734 15021222733 0016267 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer\Inline; use League\CommonMark\Node\Inline\Newline; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Xml\XmlNodeRendererInterface; use League\Config\ConfigurationAwareInterface; use League\Config\ConfigurationInterface; final class NewlineRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface { /** @psalm-readonly-allow-private-mutation */ private ConfigurationInterface $config; public function setConfiguration(ConfigurationInterface $configuration): void { $this->config = $configuration; } /** * @param Newline $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { Newline::assertInstanceOf($node); if ($node->getType() === Newline::HARDBREAK) { return "<br />\n"; } return $this->config->get('renderer/soft_break'); } /** * @param Newline $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function getXmlTagName(Node $node): string { Newline::assertInstanceOf($node); return $node->getType() === Newline::SOFTBREAK ? 'softbreak' : 'linebreak'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Renderer/Inline/TextRenderer.php 0000644 00000002473 15021222733 0015611 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js) * - (c) John MacFarlane * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Renderer\Inline; use League\CommonMark\Node\Inline\Text; use League\CommonMark\Node\Node; use League\CommonMark\Renderer\ChildNodeRendererInterface; use League\CommonMark\Renderer\NodeRendererInterface; use League\CommonMark\Util\Xml; use League\CommonMark\Xml\XmlNodeRendererInterface; final class TextRenderer implements NodeRendererInterface, XmlNodeRendererInterface { /** * @param Text $node * * {@inheritDoc} * * @psalm-suppress MoreSpecificImplementedParamType */ public function render(Node $node, ChildNodeRendererInterface $childRenderer): string { Text::assertInstanceOf($node); return Xml::escape($node->getLiteral()); } public function getXmlTagName(Node $node): string { return 'text'; } /** * {@inheritDoc} */ public function getXmlAttributes(Node $node): array { return []; } } commonmark/src/Input/MarkdownInput.php 0000644 00000005016 15021222733 0014107 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Input; use League\CommonMark\Exception\UnexpectedEncodingException; class MarkdownInput implements MarkdownInputInterface { /** * @var array<int, string>|null * * @psalm-readonly-allow-private-mutation */ private ?array $lines = null; /** @psalm-readonly-allow-private-mutation */ private string $content; /** @psalm-readonly-allow-private-mutation */ private ?int $lineCount = null; /** @psalm-readonly */ private int $lineOffset; public function __construct(string $content, int $lineOffset = 0) { if (! \mb_check_encoding($content, 'UTF-8')) { throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected'); } // Strip any leading UTF-8 BOM if (\substr($content, 0, 3) === "\xEF\xBB\xBF") { $content = \substr($content, 3); } $this->content = $content; $this->lineOffset = $lineOffset; } public function getContent(): string { return $this->content; } /** * {@inheritDoc} */ public function getLines(): iterable { $this->splitLinesIfNeeded(); \assert($this->lines !== null); /** @psalm-suppress PossiblyNullIterator */ foreach ($this->lines as $i => $line) { yield $this->lineOffset + $i + 1 => $line; } } public function getLineCount(): int { $this->splitLinesIfNeeded(); \assert($this->lineCount !== null); return $this->lineCount; } private function splitLinesIfNeeded(): void { if ($this->lines !== null) { return; } $lines = \preg_split('/\r\n|\n|\r/', $this->content); if ($lines === false) { throw new UnexpectedEncodingException('Failed to split Markdown content by line'); } $this->lines = $lines; // Remove any newline which appears at the very end of the string. // We've already split the document by newlines, so we can simply drop // any empty element which appears on the end. if (\end($this->lines) === '') { \array_pop($this->lines); } $this->lineCount = \count($this->lines); } } commonmark/src/Input/MarkdownInputInterface.php 0000644 00000001015 15021222733 0015723 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/commonmark package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\CommonMark\Input; interface MarkdownInputInterface { public function getContent(): string; /** * @return iterable<int, string> */ public function getLines(): iterable; public function getLineCount(): int; } commonmark/README.md 0000644 00000027675 15021222733 0010204 0 ustar 00 # league/commonmark [](https://packagist.org/packages/league/commonmark) [](https://packagist.org/packages/league/commonmark) [](LICENSE) [](https://github.com/thephpleague/commonmark/actions?query=workflow%3ATests+branch%3Amain) [](https://scrutinizer-ci.com/g/thephpleague/commonmark/code-structure) [](https://scrutinizer-ci.com/g/thephpleague/commonmark) [](https://shepherd.dev/github/thephpleague/commonmark) [](https://bestpractices.coreinfrastructure.org/projects/126) [](https://www.colinodell.com/sponsor)  **league/commonmark** is a highly-extensible PHP Markdown parser created by [Colin O'Dell][@colinodell] which supports the full [CommonMark] spec and [GitHub-Flavored Markdown]. It is based on the [CommonMark JS reference implementation][commonmark.js] by [John MacFarlane] \([@jgm]\). ## 📦 Installation & Basic Usage This project requires PHP 7.4 or higher with the `mbstring` extension. To install it via [Composer] simply run: ``` bash $ composer require league/commonmark ``` The `CommonMarkConverter` class provides a simple wrapper for converting CommonMark to HTML: ```php use League\CommonMark\CommonMarkConverter; $converter = new CommonMarkConverter([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); echo $converter->convert('# Hello World!'); // <h1>Hello World!</h1> ``` Or if you want GitHub-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead: ```php use League\CommonMark\GithubFlavoredMarkdownConverter; $converter = new GithubFlavoredMarkdownConverter([ 'html_input' => 'strip', 'allow_unsafe_links' => false, ]); echo $converter->convert('# Hello World!'); // <h1>Hello World!</h1> ``` Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library. 🔒 If you will be parsing untrusted input from users, please consider setting the `html_input` and `allow_unsafe_links` options per the example above. See <https://commonmark.thephpleague.com/security/> for more details. If you also do choose to allow raw HTML input from untrusted users, consider using a library (like [HTML Purifier](https://github.com/ezyang/htmlpurifier)) to provide additional HTML filtering. ## 📓 Documentation Full documentation on advanced usage, configuration, and customization can be found at [commonmark.thephpleague.com][docs]. ## ⏫ Upgrading Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>. ## 💻 GitHub-Flavored Markdown The `GithubFlavoredMarkdownConverter` shown earlier is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec: - Autolinks - Disallowed raw HTML - Strikethrough - Tables - Task Lists See the [Extensions documentation](https://commonmark.thephpleague.com/customization/extensions/) for more details on how to include only certain GFM features if you don't want them all. ## 🗃️ Related Packages ### Integrations - [CakePHP 3](https://github.com/gourmet/common-mark) - [Drupal](https://www.drupal.org/project/markdown) - [Laravel 4+](https://github.com/GrahamCampbell/Laravel-Markdown) - [Sculpin](https://github.com/bcremer/sculpin-commonmark-bundle) - [Symfony 2 & 3](https://github.com/webuni/commonmark-bundle) - [Symfony 4](https://github.com/avensome/commonmark-bundle) - [Twig Markdown extension](https://github.com/twigphp/markdown-extension) - [Twig filter and tag](https://github.com/aptoma/twig-markdown) - [Laravel CommonMark Blog](https://github.com/spekulatius/laravel-commonmark-blog) ### Included Extensions See [our extension documentation](https://commonmark.thephpleague.com/extensions/overview) for a full list of extensions bundled with this library. ### Community Extensions Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting: - [Emoji extension](https://github.com/ElGigi/CommonMarkEmoji) - UTF-8 emoji extension with Github tag. - [Sup Sub extensions](https://github.com/OWS/commonmark-sup-sub-extensions) - Adds support of superscript and subscript (`<sup>` and `<sub>` HTML tags) - [YouTube iframe extension](https://github.com/zoonru/commonmark-ext-youtube-iframe) - Replaces youtube link with iframe. - [Lazy Image extension](https://github.com/simonvomeyser/commonmark-ext-lazy-image) - Adds various options for lazy loading of images. - [Marker Extension](https://github.com/noah1400/commonmark-marker-extension) - Adds support of highlighted text (`<mark>` HTML tag) Others can be found on [Packagist under the `commonmark-extension` package type](https://packagist.org/packages/league/commonmark?type=commonmark-extension). If you build your own, feel free to submit a PR to add it to this list! ### Others Check out the other cool things people are doing with `league/commonmark`: <https://packagist.org/packages/league/commonmark/dependents> ## 🏷️ Versioning [SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly. Any classes or methods marked `@internal` are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them. ## 🛠️ Maintenance & Support When a new **minor** version (e.g. `2.0` -> `2.1`) is released, the previous one (`2.0`) will continue to receive security and critical bug fixes for *at least* 3 months. When a new **major** version is released (e.g. `1.6` -> `2.0`), the previous one (`1.6`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out. (This policy may change in the future and exceptions may be made on a case-by-case basis.) **Professional support, including notification of new releases and security updates, is available through a [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme).** ## 👷♀️ Contributing To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure with us. If you encounter a bug in the spec, please report it to the [CommonMark] project. Any resulting fix will eventually be implemented in this project as well. Contributions to this library are **welcome**, especially ones that: * Improve usability or flexibility without compromising our ability to adhere to the [CommonMark spec] * Mirror fixes made to the [reference implementation][commonmark.js] * Optimize performance * Fix issues with adhering to the [CommonMark spec] Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to [the reference implementation][commonmark.js]. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations. Please see [CONTRIBUTING](https://github.com/thephpleague/commonmark/blob/main/.github/CONTRIBUTING.md) for additional details. ## 🧪 Testing ``` bash $ composer test ``` This will also test league/commonmark against the latest supported spec. ## 🚀 Performance Benchmarks You can compare the performance of **league/commonmark** to other popular parsers by running the included benchmark tool: ``` bash $ ./tests/benchmark/benchmark.php ``` ## 👥 Credits & Acknowledgements - [Colin O'Dell][@colinodell] - [John MacFarlane][@jgm] - [All Contributors] This code is partially based on the [CommonMark JS reference implementation][commonmark.js] which is written, maintained and copyrighted by [John MacFarlane]. This project simply wouldn't exist without his work. ### Sponsors We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project: - [Tidelift](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) for offering support to both the maintainers and end-users through their [professional support](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) program - [Blackfire](https://www.blackfire.io/) for providing an Open-Source Profiler subscription - [JetBrains](https://www.jetbrains.com/) for supporting this project with complimentary [PhpStorm](https://www.jetbrains.com/phpstorm/) licenses - [Taylor Otwell](https://twitter.com/taylorotwell) for sponsoring this project through GitHub sponsors Are you interested in sponsoring development of this project? See <https://www.colinodell.com/sponsor> for a list of ways to contribute. ## 📄 License **league/commonmark** is licensed under the BSD-3 license. See the [`LICENSE`](LICENSE) file for more details. ## 🏛️ Governance This project is primarily maintained by [Colin O'Dell][@colinodell]. Members of the [PHP League] Leadership Team may occasionally assist with some of these duties. ## 🗺️ Who Uses It? This project is used by [Drupal](https://www.drupal.org/project/markdown), [Laravel Framework](https://laravel.com/), [Cachet](https://cachethq.io/), [Firefly III](https://firefly-iii.org/), [Neos](https://www.neos.io/), [Daux.io](https://daux.io/), and [more](https://packagist.org/packages/league/commonmark/dependents)! --- <div align="center"> <b> <a href="https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme">Get professional support for league/commonmark with a Tidelift subscription</a> </b> <br> <sub> Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies. </sub> </div> [CommonMark]: http://commonmark.org/ [CommonMark spec]: http://spec.commonmark.org/ [commonmark.js]: https://github.com/jgm/commonmark.js [GitHub-Flavored Markdown]: https://github.github.com/gfm/ [John MacFarlane]: http://johnmacfarlane.net [docs]: https://commonmark.thephpleague.com/ [docs-examples]: https://commonmark.thephpleague.com/customization/overview/#examples [docs-example-twitter]: https://commonmark.thephpleague.com/customization/inline-parsing#example-1---twitter-handles [docs-example-smilies]: https://commonmark.thephpleague.com/customization/inline-parsing#example-2---emoticons [All Contributors]: https://github.com/thephpleague/commonmark/contributors [@colinodell]: https://www.twitter.com/colinodell [@jgm]: https://github.com/jgm [jgm/stmd]: https://github.com/jgm/stmd [Composer]: https://getcomposer.org/ [PHP League]: https://thephpleague.com commonmark/LICENSE 0000644 00000003163 15021222733 0007714 0 ustar 00 BSD 3-Clause License Copyright (c) 2014-2022, Colin O'Dell. All rights reserved. Some code based on commonmark.js (copyright 2014-2018, John MacFarlane) and commonmark-java (copyright 2015-2016, Atlassian Pty Ltd) Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. mime-type-detection/composer.json 0000644 00000001441 15021222733 0013145 0 ustar 00 { "name": "league/mime-type-detection", "description": "Mime-type detection for Flysystem", "license": "MIT", "authors": [ { "name": "Frank de Jonge", "email": "info@frankdejonge.nl" } ], "scripts": { "test": "vendor/bin/phpunit", "phpstan": "vendor/bin/phpstan analyse -l 6 src" }, "require": { "php": "^7.4 || ^8.0", "ext-fileinfo": "*" }, "require-dev": { "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0", "phpstan/phpstan": "^0.12.68", "friendsofphp/php-cs-fixer": "^3.2" }, "autoload": { "psr-4": { "League\\MimeTypeDetection\\": "src" } }, "config": { "platform": { "php": "7.4.0" } } } mime-type-detection/CHANGELOG.md 0000644 00000001747 15021222733 0012245 0 ustar 00 # Changelog ## 1.16.0 - 2025-09-21 - Updated lookup - Prepped for 8.4 implicit nullable deprecation ## 1.15.0 - 2024-01-28 - Updated lookup ## 1.14.0 - 2022-10-17 ### Updated - Updated lookup ## 1.13.0 - 2023-08-05 ### Added - A reverse lookup mechanism to fetch one or all extensions for a given mimetype ## 1.12.0 - 2023-08-03 ### Updated - Updated lookup ## 1.11.0 - 2023-04-17 ### Updated - Updated lookup ## 1.10.0 - 2022-04-11 ### Fixed - Added Flysystem v1 inconclusive mime-types and made it configurable as a constructor parameter. ## 1.9.0 - 2021-11-21 ### Updated - Updated lookup ## 1.8.0 - 2021-09-25 ### Added - Added the decorator `OverridingExtensionToMimeTypeMap` which allows you to override values. ## 1.7.0 - 2021-01-18 ### Added - Added a `bufferSampleSize` parameter to the `FinfoMimeTypeDetector` class that allows you to send a reduced content sample which costs less memory. ## 1.6.0 - 2021-01-18 ### Changes - Updated generated mime-type map mime-type-detection/src/ExtensionMimeTypeDetector.php 0000644 00000002652 15021222733 0017050 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; use const PATHINFO_EXTENSION; class ExtensionMimeTypeDetector implements MimeTypeDetector, ExtensionLookup { /** * @var ExtensionToMimeTypeMap */ private $extensions; public function __construct(?ExtensionToMimeTypeMap $extensions = null) { $this->extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap(); } public function detectMimeType(string $path, $contents): ?string { return $this->detectMimeTypeFromPath($path); } public function detectMimeTypeFromPath(string $path): ?string { $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); return $this->extensions->lookupMimeType($extension); } public function detectMimeTypeFromFile(string $path): ?string { return $this->detectMimeTypeFromPath($path); } public function detectMimeTypeFromBuffer(string $contents): ?string { return null; } public function lookupExtension(string $mimetype): ?string { return $this->extensions instanceof ExtensionLookup ? $this->extensions->lookupExtension($mimetype) : null; } public function lookupAllExtensions(string $mimetype): array { return $this->extensions instanceof ExtensionLookup ? $this->extensions->lookupAllExtensions($mimetype) : []; } } mime-type-detection/src/ExtensionToMimeTypeMap.php 0000644 00000000253 15021222733 0016312 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; interface ExtensionToMimeTypeMap { public function lookupMimeType(string $extension): ?string; } mime-type-detection/src/FinfoMimeTypeDetector.php 0000644 00000005340 15021222733 0016132 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; use const FILEINFO_MIME_TYPE; use const PATHINFO_EXTENSION; use finfo; class FinfoMimeTypeDetector implements MimeTypeDetector, ExtensionLookup { private const INCONCLUSIVE_MIME_TYPES = [ 'application/x-empty', 'text/plain', 'text/x-asm', 'application/octet-stream', 'inode/x-empty', ]; /** * @var finfo */ private $finfo; /** * @var ExtensionToMimeTypeMap */ private $extensionMap; /** * @var int|null */ private $bufferSampleSize; /** * @var array<string> */ private $inconclusiveMimetypes; public function __construct( string $magicFile = '', ?ExtensionToMimeTypeMap $extensionMap = null, ?int $bufferSampleSize = null, array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES ) { $this->finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile); $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap(); $this->bufferSampleSize = $bufferSampleSize; $this->inconclusiveMimetypes = $inconclusiveMimetypes; } public function detectMimeType(string $path, $contents): ?string { $mimeType = is_string($contents) ? (@$this->finfo->buffer($this->takeSample($contents)) ?: null) : null; if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { return $mimeType; } return $this->detectMimeTypeFromPath($path); } public function detectMimeTypeFromPath(string $path): ?string { $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); return $this->extensionMap->lookupMimeType($extension); } public function detectMimeTypeFromFile(string $path): ?string { return @$this->finfo->file($path) ?: null; } public function detectMimeTypeFromBuffer(string $contents): ?string { return @$this->finfo->buffer($this->takeSample($contents)) ?: null; } private function takeSample(string $contents): string { if ($this->bufferSampleSize === null) { return $contents; } return (string) substr($contents, 0, $this->bufferSampleSize); } public function lookupExtension(string $mimetype): ?string { return $this->extensionMap instanceof ExtensionLookup ? $this->extensionMap->lookupExtension($mimetype) : null; } public function lookupAllExtensions(string $mimetype): array { return $this->extensionMap instanceof ExtensionLookup ? $this->extensionMap->lookupAllExtensions($mimetype) : []; } } mime-type-detection/src/ExtensionLookup.php 0000644 00000000416 15021222733 0015072 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; interface ExtensionLookup { public function lookupExtension(string $mimetype): ?string; /** * @return string[] */ public function lookupAllExtensions(string $mimetype): array; } mime-type-detection/src/MimeTypeDetector.php 0000644 00000000665 15021222733 0015155 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; interface MimeTypeDetector { /** * @param string|resource $contents */ public function detectMimeType(string $path, $contents): ?string; public function detectMimeTypeFromBuffer(string $contents): ?string; public function detectMimeTypeFromPath(string $path): ?string; public function detectMimeTypeFromFile(string $path): ?string; } mime-type-detection/src/EmptyExtensionToMimeTypeMap.php 0000644 00000000356 15021222733 0017335 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; class EmptyExtensionToMimeTypeMap implements ExtensionToMimeTypeMap { public function lookupMimeType(string $extension): ?string { return null; } } mime-type-detection/src/OverridingExtensionToMimeTypeMap.php 0000644 00000001226 15021222733 0020344 0 ustar 00 <?php namespace League\MimeTypeDetection; class OverridingExtensionToMimeTypeMap implements ExtensionToMimeTypeMap { /** * @var ExtensionToMimeTypeMap */ private $innerMap; /** * @var string[] */ private $overrides; /** * @param array<string, string> $overrides */ public function __construct(ExtensionToMimeTypeMap $innerMap, array $overrides) { $this->innerMap = $innerMap; $this->overrides = $overrides; } public function lookupMimeType(string $extension): ?string { return $this->overrides[$extension] ?? $this->innerMap->lookupMimeType($extension); } } mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php 0000644 00000317663 15021222733 0020151 0 ustar 00 <?php declare(strict_types=1); namespace League\MimeTypeDetection; class GeneratedExtensionToMimeTypeMap implements ExtensionToMimeTypeMap, ExtensionLookup { /** * @var array<string, string> * * @internal */ public const MIME_TYPES_FOR_EXTENSIONS = [ '1km' => 'application/vnd.1000minds.decision-model+xml', '3dml' => 'text/vnd.in3d.3dml', '3ds' => 'image/x-3ds', '3g2' => 'video/3gpp2', '3gp' => 'video/3gp', '3gpp' => 'video/3gpp', '3mf' => 'model/3mf', '7z' => 'application/x-7z-compressed', '7zip' => 'application/x-7z-compressed', '123' => 'application/vnd.lotus-1-2-3', 'aab' => 'application/x-authorware-bin', 'aac' => 'audio/acc', 'aam' => 'application/x-authorware-map', 'aas' => 'application/x-authorware-seg', 'abw' => 'application/x-abiword', 'ac' => 'application/vnd.nokia.n-gage.ac+xml', 'ac3' => 'audio/ac3', 'acc' => 'application/vnd.americandynamics.acc', 'ace' => 'application/x-ace-compressed', 'acu' => 'application/vnd.acucobol', 'acutc' => 'application/vnd.acucorp', 'adp' => 'audio/adpcm', 'adts' => 'audio/aac', 'aep' => 'application/vnd.audiograph', 'afm' => 'application/x-font-type1', 'afp' => 'application/vnd.ibm.modcap', 'age' => 'application/vnd.age', 'ahead' => 'application/vnd.ahead.space', 'ai' => 'application/pdf', 'aif' => 'audio/x-aiff', 'aifc' => 'audio/x-aiff', 'aiff' => 'audio/x-aiff', 'air' => 'application/vnd.adobe.air-application-installer-package+zip', 'ait' => 'application/vnd.dvb.ait', 'ami' => 'application/vnd.amiga.ami', 'aml' => 'application/automationml-aml+xml', 'amlx' => 'application/automationml-amlx+zip', 'amr' => 'audio/amr', 'apk' => 'application/vnd.android.package-archive', 'apng' => 'image/apng', 'appcache' => 'text/cache-manifest', 'appinstaller' => 'application/appinstaller', 'application' => 'application/x-ms-application', 'appx' => 'application/appx', 'appxbundle' => 'application/appxbundle', 'apr' => 'application/vnd.lotus-approach', 'arc' => 'application/x-freearc', 'arj' => 'application/x-arj', 'asc' => 'application/pgp-signature', 'asf' => 'video/x-ms-asf', 'asm' => 'text/x-asm', 'aso' => 'application/vnd.accpac.simply.aso', 'asx' => 'video/x-ms-asf', 'atc' => 'application/vnd.acucorp', 'atom' => 'application/atom+xml', 'atomcat' => 'application/atomcat+xml', 'atomdeleted' => 'application/atomdeleted+xml', 'atomsvc' => 'application/atomsvc+xml', 'atx' => 'application/vnd.antix.game-component', 'au' => 'audio/x-au', 'avci' => 'image/avci', 'avcs' => 'image/avcs', 'avi' => 'video/x-msvideo', 'avif' => 'image/avif', 'aw' => 'application/applixware', 'azf' => 'application/vnd.airzip.filesecure.azf', 'azs' => 'application/vnd.airzip.filesecure.azs', 'azv' => 'image/vnd.airzip.accelerator.azv', 'azw' => 'application/vnd.amazon.ebook', 'b16' => 'image/vnd.pco.b16', 'bary' => 'model/vnd.bary', 'bat' => 'application/x-msdownload', 'bcpio' => 'application/x-bcpio', 'bdf' => 'application/x-font-bdf', 'bdm' => 'application/vnd.syncml.dm+wbxml', 'bdo' => 'application/vnd.nato.bindingdataobject+xml', 'bdoc' => 'application/x-bdoc', 'bed' => 'application/vnd.realvnc.bed', 'bh2' => 'application/vnd.fujitsu.oasysprs', 'bin' => 'application/octet-stream', 'blb' => 'application/x-blorb', 'blorb' => 'application/x-blorb', 'bmi' => 'application/vnd.bmi', 'bmml' => 'application/vnd.balsamiq.bmml+xml', 'bmp' => 'image/bmp', 'book' => 'application/vnd.framemaker', 'box' => 'application/vnd.previewsystems.box', 'boz' => 'application/x-bzip2', 'bpk' => 'application/octet-stream', 'bpmn' => 'application/octet-stream', 'brf' => 'application/braille', 'bsp' => 'model/vnd.valve.source.compiled-map', 'btf' => 'image/prs.btif', 'btif' => 'image/prs.btif', 'buffer' => 'application/octet-stream', 'bz' => 'application/x-bzip', 'bz2' => 'application/x-bzip2', 'c' => 'text/x-c', 'c4d' => 'application/vnd.clonk.c4group', 'c4f' => 'application/vnd.clonk.c4group', 'c4g' => 'application/vnd.clonk.c4group', 'c4p' => 'application/vnd.clonk.c4group', 'c4u' => 'application/vnd.clonk.c4group', 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', 'cab' => 'application/vnd.ms-cab-compressed', 'caf' => 'audio/x-caf', 'cap' => 'application/vnd.tcpdump.pcap', 'car' => 'application/vnd.curl.car', 'cat' => 'application/vnd.ms-pki.seccat', 'cb7' => 'application/x-cbr', 'cba' => 'application/x-cbr', 'cbr' => 'application/x-cbr', 'cbt' => 'application/x-cbr', 'cbz' => 'application/x-cbr', 'cc' => 'text/x-c', 'cco' => 'application/x-cocoa', 'cct' => 'application/x-director', 'ccxml' => 'application/ccxml+xml', 'cdbcmsg' => 'application/vnd.contact.cmsg', 'cdf' => 'application/x-netcdf', 'cdfx' => 'application/cdfx+xml', 'cdkey' => 'application/vnd.mediastation.cdkey', 'cdmia' => 'application/cdmi-capability', 'cdmic' => 'application/cdmi-container', 'cdmid' => 'application/cdmi-domain', 'cdmio' => 'application/cdmi-object', 'cdmiq' => 'application/cdmi-queue', 'cdr' => 'application/cdr', 'cdx' => 'chemical/x-cdx', 'cdxml' => 'application/vnd.chemdraw+xml', 'cdy' => 'application/vnd.cinderella', 'cer' => 'application/pkix-cert', 'cfs' => 'application/x-cfs-compressed', 'cgm' => 'image/cgm', 'chat' => 'application/x-chat', 'chm' => 'application/vnd.ms-htmlhelp', 'chrt' => 'application/vnd.kde.kchart', 'cif' => 'chemical/x-cif', 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', 'cil' => 'application/vnd.ms-artgalry', 'cjs' => 'application/node', 'cla' => 'application/vnd.claymore', 'class' => 'application/octet-stream', 'cld' => 'model/vnd.cld', 'clkk' => 'application/vnd.crick.clicker.keyboard', 'clkp' => 'application/vnd.crick.clicker.palette', 'clkt' => 'application/vnd.crick.clicker.template', 'clkw' => 'application/vnd.crick.clicker.wordbank', 'clkx' => 'application/vnd.crick.clicker', 'clp' => 'application/x-msclip', 'cmc' => 'application/vnd.cosmocaller', 'cmdf' => 'chemical/x-cmdf', 'cml' => 'chemical/x-cml', 'cmp' => 'application/vnd.yellowriver-custom-menu', 'cmx' => 'image/x-cmx', 'cod' => 'application/vnd.rim.cod', 'coffee' => 'text/coffeescript', 'com' => 'application/x-msdownload', 'conf' => 'text/plain', 'cpio' => 'application/x-cpio', 'cpl' => 'application/cpl+xml', 'cpp' => 'text/x-c', 'cpt' => 'application/mac-compactpro', 'crd' => 'application/x-mscardfile', 'crl' => 'application/pkix-crl', 'crt' => 'application/x-x509-ca-cert', 'crx' => 'application/x-chrome-extension', 'cryptonote' => 'application/vnd.rig.cryptonote', 'csh' => 'application/x-csh', 'csl' => 'application/vnd.citationstyles.style+xml', 'csml' => 'chemical/x-csml', 'csp' => 'application/vnd.commonspace', 'csr' => 'application/octet-stream', 'css' => 'text/css', 'cst' => 'application/x-director', 'csv' => 'text/csv', 'cu' => 'application/cu-seeme', 'curl' => 'text/vnd.curl', 'cwl' => 'application/cwl', 'cww' => 'application/prs.cww', 'cxt' => 'application/x-director', 'cxx' => 'text/x-c', 'dae' => 'model/vnd.collada+xml', 'daf' => 'application/vnd.mobius.daf', 'dart' => 'application/vnd.dart', 'dataless' => 'application/vnd.fdsn.seed', 'davmount' => 'application/davmount+xml', 'dbf' => 'application/vnd.dbf', 'dbk' => 'application/docbook+xml', 'dcr' => 'application/x-director', 'dcurl' => 'text/vnd.curl.dcurl', 'dd2' => 'application/vnd.oma.dd2+xml', 'ddd' => 'application/vnd.fujixerox.ddd', 'ddf' => 'application/vnd.syncml.dmddf+xml', 'dds' => 'image/vnd.ms-dds', 'deb' => 'application/x-debian-package', 'def' => 'text/plain', 'deploy' => 'application/octet-stream', 'der' => 'application/x-x509-ca-cert', 'dfac' => 'application/vnd.dreamfactory', 'dgc' => 'application/x-dgc-compressed', 'dib' => 'image/bmp', 'dic' => 'text/x-c', 'dir' => 'application/x-director', 'dis' => 'application/vnd.mobius.dis', 'disposition-notification' => 'message/disposition-notification', 'dist' => 'application/octet-stream', 'distz' => 'application/octet-stream', 'djv' => 'image/vnd.djvu', 'djvu' => 'image/vnd.djvu', 'dll' => 'application/octet-stream', 'dmg' => 'application/x-apple-diskimage', 'dmn' => 'application/octet-stream', 'dmp' => 'application/vnd.tcpdump.pcap', 'dms' => 'application/octet-stream', 'dna' => 'application/vnd.dna', 'doc' => 'application/msword', 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'dot' => 'application/msword', 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', 'dp' => 'application/vnd.osgi.dp', 'dpg' => 'application/vnd.dpgraph', 'dpx' => 'image/dpx', 'dra' => 'audio/vnd.dra', 'drle' => 'image/dicom-rle', 'dsc' => 'text/prs.lines.tag', 'dssc' => 'application/dssc+der', 'dst' => 'application/octet-stream', 'dtb' => 'application/x-dtbook+xml', 'dtd' => 'application/xml-dtd', 'dts' => 'audio/vnd.dts', 'dtshd' => 'audio/vnd.dts.hd', 'dump' => 'application/octet-stream', 'dvb' => 'video/vnd.dvb.file', 'dvi' => 'application/x-dvi', 'dwd' => 'application/atsc-dwd+xml', 'dwf' => 'model/vnd.dwf', 'dwg' => 'image/vnd.dwg', 'dxf' => 'image/vnd.dxf', 'dxp' => 'application/vnd.spotfire.dxp', 'dxr' => 'application/x-director', 'ear' => 'application/java-archive', 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', 'ecma' => 'application/ecmascript', 'edm' => 'application/vnd.novadigm.edm', 'edx' => 'application/vnd.novadigm.edx', 'efif' => 'application/vnd.picsel', 'ei6' => 'application/vnd.pg.osasli', 'elc' => 'application/octet-stream', 'emf' => 'image/emf', 'eml' => 'message/rfc822', 'emma' => 'application/emma+xml', 'emotionml' => 'application/emotionml+xml', 'emz' => 'application/x-msmetafile', 'eol' => 'audio/vnd.digital-winds', 'eot' => 'application/vnd.ms-fontobject', 'eps' => 'application/postscript', 'epub' => 'application/epub+zip', 'es3' => 'application/vnd.eszigno3+xml', 'esa' => 'application/vnd.osgi.subsystem', 'esf' => 'application/vnd.epson.esf', 'et3' => 'application/vnd.eszigno3+xml', 'etx' => 'text/x-setext', 'eva' => 'application/x-eva', 'evy' => 'application/x-envoy', 'exe' => 'application/octet-stream', 'exi' => 'application/exi', 'exp' => 'application/express', 'exr' => 'image/aces', 'ext' => 'application/vnd.novadigm.ext', 'ez' => 'application/andrew-inset', 'ez2' => 'application/vnd.ezpix-album', 'ez3' => 'application/vnd.ezpix-package', 'f' => 'text/x-fortran', 'f4v' => 'video/mp4', 'f77' => 'text/x-fortran', 'f90' => 'text/x-fortran', 'fbs' => 'image/vnd.fastbidsheet', 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', 'fcs' => 'application/vnd.isac.fcs', 'fdf' => 'application/vnd.fdf', 'fdt' => 'application/fdt+xml', 'fe_launch' => 'application/vnd.denovo.fcselayout-link', 'fg5' => 'application/vnd.fujitsu.oasysgp', 'fgd' => 'application/x-director', 'fh' => 'image/x-freehand', 'fh4' => 'image/x-freehand', 'fh5' => 'image/x-freehand', 'fh7' => 'image/x-freehand', 'fhc' => 'image/x-freehand', 'fig' => 'application/x-xfig', 'fits' => 'image/fits', 'flac' => 'audio/x-flac', 'fli' => 'video/x-fli', 'flo' => 'application/vnd.micrografx.flo', 'flv' => 'video/x-flv', 'flw' => 'application/vnd.kde.kivio', 'flx' => 'text/vnd.fmi.flexstor', 'fly' => 'text/vnd.fly', 'fm' => 'application/vnd.framemaker', 'fnc' => 'application/vnd.frogans.fnc', 'fo' => 'application/vnd.software602.filler.form+xml', 'for' => 'text/x-fortran', 'fpx' => 'image/vnd.fpx', 'frame' => 'application/vnd.framemaker', 'fsc' => 'application/vnd.fsc.weblaunch', 'fst' => 'image/vnd.fst', 'ftc' => 'application/vnd.fluxtime.clip', 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', 'fvt' => 'video/vnd.fvt', 'fxp' => 'application/vnd.adobe.fxp', 'fxpl' => 'application/vnd.adobe.fxp', 'fzs' => 'application/vnd.fuzzysheet', 'g2w' => 'application/vnd.geoplan', 'g3' => 'image/g3fax', 'g3w' => 'application/vnd.geospace', 'gac' => 'application/vnd.groove-account', 'gam' => 'application/x-tads', 'gbr' => 'application/rpki-ghostbusters', 'gca' => 'application/x-gca-compressed', 'gdl' => 'model/vnd.gdl', 'gdoc' => 'application/vnd.google-apps.document', 'ged' => 'text/vnd.familysearch.gedcom', 'geo' => 'application/vnd.dynageo', 'geojson' => 'application/geo+json', 'gex' => 'application/vnd.geometry-explorer', 'ggb' => 'application/vnd.geogebra.file', 'ggs' => 'application/vnd.geogebra.slides', 'ggt' => 'application/vnd.geogebra.tool', 'ghf' => 'application/vnd.groove-help', 'gif' => 'image/gif', 'gim' => 'application/vnd.groove-identity-message', 'glb' => 'model/gltf-binary', 'gltf' => 'model/gltf+json', 'gml' => 'application/gml+xml', 'gmx' => 'application/vnd.gmx', 'gnumeric' => 'application/x-gnumeric', 'gpg' => 'application/gpg-keys', 'gph' => 'application/vnd.flographit', 'gpx' => 'application/gpx+xml', 'gqf' => 'application/vnd.grafeq', 'gqs' => 'application/vnd.grafeq', 'gram' => 'application/srgs', 'gramps' => 'application/x-gramps-xml', 'gre' => 'application/vnd.geometry-explorer', 'grv' => 'application/vnd.groove-injector', 'grxml' => 'application/srgs+xml', 'gsf' => 'application/x-font-ghostscript', 'gsheet' => 'application/vnd.google-apps.spreadsheet', 'gslides' => 'application/vnd.google-apps.presentation', 'gtar' => 'application/x-gtar', 'gtm' => 'application/vnd.groove-tool-message', 'gtw' => 'model/vnd.gtw', 'gv' => 'text/vnd.graphviz', 'gxf' => 'application/gxf', 'gxt' => 'application/vnd.geonext', 'gz' => 'application/gzip', 'gzip' => 'application/gzip', 'h' => 'text/x-c', 'h261' => 'video/h261', 'h263' => 'video/h263', 'h264' => 'video/h264', 'hal' => 'application/vnd.hal+xml', 'hbci' => 'application/vnd.hbci', 'hbs' => 'text/x-handlebars-template', 'hdd' => 'application/x-virtualbox-hdd', 'hdf' => 'application/x-hdf', 'heic' => 'image/heic', 'heics' => 'image/heic-sequence', 'heif' => 'image/heif', 'heifs' => 'image/heif-sequence', 'hej2' => 'image/hej2k', 'held' => 'application/atsc-held+xml', 'hh' => 'text/x-c', 'hjson' => 'application/hjson', 'hlp' => 'application/winhlp', 'hpgl' => 'application/vnd.hp-hpgl', 'hpid' => 'application/vnd.hp-hpid', 'hps' => 'application/vnd.hp-hps', 'hqx' => 'application/mac-binhex40', 'hsj2' => 'image/hsj2', 'htc' => 'text/x-component', 'htke' => 'application/vnd.kenameaapp', 'htm' => 'text/html', 'html' => 'text/html', 'hvd' => 'application/vnd.yamaha.hv-dic', 'hvp' => 'application/vnd.yamaha.hv-voice', 'hvs' => 'application/vnd.yamaha.hv-script', 'i2g' => 'application/vnd.intergeo', 'icc' => 'application/vnd.iccprofile', 'ice' => 'x-conference/x-cooltalk', 'icm' => 'application/vnd.iccprofile', 'ico' => 'image/x-icon', 'ics' => 'text/calendar', 'ief' => 'image/ief', 'ifb' => 'text/calendar', 'ifm' => 'application/vnd.shana.informed.formdata', 'iges' => 'model/iges', 'igl' => 'application/vnd.igloader', 'igm' => 'application/vnd.insors.igm', 'igs' => 'model/iges', 'igx' => 'application/vnd.micrografx.igx', 'iif' => 'application/vnd.shana.informed.interchange', 'img' => 'application/octet-stream', 'imp' => 'application/vnd.accpac.simply.imp', 'ims' => 'application/vnd.ms-ims', 'in' => 'text/plain', 'ini' => 'text/plain', 'ink' => 'application/inkml+xml', 'inkml' => 'application/inkml+xml', 'install' => 'application/x-install-instructions', 'iota' => 'application/vnd.astraea-software.iota', 'ipfix' => 'application/ipfix', 'ipk' => 'application/vnd.shana.informed.package', 'irm' => 'application/vnd.ibm.rights-management', 'irp' => 'application/vnd.irepository.package+xml', 'iso' => 'application/x-iso9660-image', 'itp' => 'application/vnd.shana.informed.formtemplate', 'its' => 'application/its+xml', 'ivp' => 'application/vnd.immervision-ivp', 'ivu' => 'application/vnd.immervision-ivu', 'jad' => 'text/vnd.sun.j2me.app-descriptor', 'jade' => 'text/jade', 'jam' => 'application/vnd.jam', 'jar' => 'application/java-archive', 'jardiff' => 'application/x-java-archive-diff', 'java' => 'text/x-java-source', 'jhc' => 'image/jphc', 'jisp' => 'application/vnd.jisp', 'jls' => 'image/jls', 'jlt' => 'application/vnd.hp-jlyt', 'jng' => 'image/x-jng', 'jnlp' => 'application/x-java-jnlp-file', 'joda' => 'application/vnd.joost.joda-archive', 'jp2' => 'image/jp2', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'jpf' => 'image/jpx', 'jpg' => 'image/jpeg', 'jpg2' => 'image/jp2', 'jpgm' => 'video/jpm', 'jpgv' => 'video/jpeg', 'jph' => 'image/jph', 'jpm' => 'video/jpm', 'jpx' => 'image/jpx', 'js' => 'application/javascript', 'json' => 'application/json', 'json5' => 'application/json5', 'jsonld' => 'application/ld+json', 'jsonml' => 'application/jsonml+json', 'jsx' => 'text/jsx', 'jt' => 'model/jt', 'jxl' => 'image/jxl', 'jxr' => 'image/jxr', 'jxra' => 'image/jxra', 'jxrs' => 'image/jxrs', 'jxs' => 'image/jxs', 'jxsc' => 'image/jxsc', 'jxsi' => 'image/jxsi', 'jxss' => 'image/jxss', 'kar' => 'audio/midi', 'karbon' => 'application/vnd.kde.karbon', 'kdb' => 'application/octet-stream', 'kdbx' => 'application/x-keepass2', 'key' => 'application/x-iwork-keynote-sffkey', 'kfo' => 'application/vnd.kde.kformula', 'kia' => 'application/vnd.kidspiration', 'kml' => 'application/vnd.google-earth.kml+xml', 'kmz' => 'application/vnd.google-earth.kmz', 'kne' => 'application/vnd.kinar', 'knp' => 'application/vnd.kinar', 'kon' => 'application/vnd.kde.kontour', 'kpr' => 'application/vnd.kde.kpresenter', 'kpt' => 'application/vnd.kde.kpresenter', 'kpxx' => 'application/vnd.ds-keypoint', 'ksp' => 'application/vnd.kde.kspread', 'ktr' => 'application/vnd.kahootz', 'ktx' => 'image/ktx', 'ktx2' => 'image/ktx2', 'ktz' => 'application/vnd.kahootz', 'kwd' => 'application/vnd.kde.kword', 'kwt' => 'application/vnd.kde.kword', 'lasxml' => 'application/vnd.las.las+xml', 'latex' => 'application/x-latex', 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', 'les' => 'application/vnd.hhe.lesson-player', 'less' => 'text/less', 'lgr' => 'application/lgr+xml', 'lha' => 'application/octet-stream', 'link66' => 'application/vnd.route66.link66+xml', 'list' => 'text/plain', 'list3820' => 'application/vnd.ibm.modcap', 'listafp' => 'application/vnd.ibm.modcap', 'litcoffee' => 'text/coffeescript', 'lnk' => 'application/x-ms-shortcut', 'log' => 'text/plain', 'lostxml' => 'application/lost+xml', 'lrf' => 'application/octet-stream', 'lrm' => 'application/vnd.ms-lrm', 'ltf' => 'application/vnd.frogans.ltf', 'lua' => 'text/x-lua', 'luac' => 'application/x-lua-bytecode', 'lvp' => 'audio/vnd.lucent.voice', 'lwp' => 'application/vnd.lotus-wordpro', 'lzh' => 'application/octet-stream', 'm1v' => 'video/mpeg', 'm2a' => 'audio/mpeg', 'm2t' => 'video/mp2t', 'm2ts' => 'video/mp2t', 'm2v' => 'video/mpeg', 'm3a' => 'audio/mpeg', 'm3u' => 'text/plain', 'm3u8' => 'application/vnd.apple.mpegurl', 'm4a' => 'audio/x-m4a', 'm4p' => 'application/mp4', 'm4s' => 'video/iso.segment', 'm4u' => 'application/vnd.mpegurl', 'm4v' => 'video/x-m4v', 'm13' => 'application/x-msmediaview', 'm14' => 'application/x-msmediaview', 'm21' => 'application/mp21', 'ma' => 'application/mathematica', 'mads' => 'application/mads+xml', 'maei' => 'application/mmt-aei+xml', 'mag' => 'application/vnd.ecowin.chart', 'maker' => 'application/vnd.framemaker', 'man' => 'text/troff', 'manifest' => 'text/cache-manifest', 'map' => 'application/json', 'mar' => 'application/octet-stream', 'markdown' => 'text/markdown', 'mathml' => 'application/mathml+xml', 'mb' => 'application/mathematica', 'mbk' => 'application/vnd.mobius.mbk', 'mbox' => 'application/mbox', 'mc1' => 'application/vnd.medcalcdata', 'mcd' => 'application/vnd.mcd', 'mcurl' => 'text/vnd.curl.mcurl', 'md' => 'text/markdown', 'mdb' => 'application/x-msaccess', 'mdi' => 'image/vnd.ms-modi', 'mdx' => 'text/mdx', 'me' => 'text/troff', 'mesh' => 'model/mesh', 'meta4' => 'application/metalink4+xml', 'metalink' => 'application/metalink+xml', 'mets' => 'application/mets+xml', 'mfm' => 'application/vnd.mfmp', 'mft' => 'application/rpki-manifest', 'mgp' => 'application/vnd.osgeo.mapguide.package', 'mgz' => 'application/vnd.proteus.magazine', 'mid' => 'audio/midi', 'midi' => 'audio/midi', 'mie' => 'application/x-mie', 'mif' => 'application/vnd.mif', 'mime' => 'message/rfc822', 'mj2' => 'video/mj2', 'mjp2' => 'video/mj2', 'mjs' => 'text/javascript', 'mk3d' => 'video/x-matroska', 'mka' => 'audio/x-matroska', 'mkd' => 'text/x-markdown', 'mks' => 'video/x-matroska', 'mkv' => 'video/x-matroska', 'mlp' => 'application/vnd.dolby.mlp', 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', 'mmf' => 'application/vnd.smaf', 'mml' => 'text/mathml', 'mmr' => 'image/vnd.fujixerox.edmics-mmr', 'mng' => 'video/x-mng', 'mny' => 'application/x-msmoney', 'mobi' => 'application/x-mobipocket-ebook', 'mods' => 'application/mods+xml', 'mov' => 'video/quicktime', 'movie' => 'video/x-sgi-movie', 'mp2' => 'audio/mpeg', 'mp2a' => 'audio/mpeg', 'mp3' => 'audio/mpeg', 'mp4' => 'video/mp4', 'mp4a' => 'audio/mp4', 'mp4s' => 'application/mp4', 'mp4v' => 'video/mp4', 'mp21' => 'application/mp21', 'mpc' => 'application/vnd.mophun.certificate', 'mpd' => 'application/dash+xml', 'mpe' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'mpf' => 'application/media-policy-dataset+xml', 'mpg' => 'video/mpeg', 'mpg4' => 'video/mp4', 'mpga' => 'audio/mpeg', 'mpkg' => 'application/vnd.apple.installer+xml', 'mpm' => 'application/vnd.blueice.multipass', 'mpn' => 'application/vnd.mophun.application', 'mpp' => 'application/vnd.ms-project', 'mpt' => 'application/vnd.ms-project', 'mpy' => 'application/vnd.ibm.minipay', 'mqy' => 'application/vnd.mobius.mqy', 'mrc' => 'application/marc', 'mrcx' => 'application/marcxml+xml', 'ms' => 'text/troff', 'mscml' => 'application/mediaservercontrol+xml', 'mseed' => 'application/vnd.fdsn.mseed', 'mseq' => 'application/vnd.mseq', 'msf' => 'application/vnd.epson.msf', 'msg' => 'application/vnd.ms-outlook', 'msh' => 'model/mesh', 'msi' => 'application/x-msdownload', 'msix' => 'application/msix', 'msixbundle' => 'application/msixbundle', 'msl' => 'application/vnd.mobius.msl', 'msm' => 'application/octet-stream', 'msp' => 'application/octet-stream', 'msty' => 'application/vnd.muvee.style', 'mtl' => 'model/mtl', 'mts' => 'video/mp2t', 'mus' => 'application/vnd.musician', 'musd' => 'application/mmt-usd+xml', 'musicxml' => 'application/vnd.recordare.musicxml+xml', 'mvb' => 'application/x-msmediaview', 'mvt' => 'application/vnd.mapbox-vector-tile', 'mwf' => 'application/vnd.mfer', 'mxf' => 'application/mxf', 'mxl' => 'application/vnd.recordare.musicxml', 'mxmf' => 'audio/mobile-xmf', 'mxml' => 'application/xv+xml', 'mxs' => 'application/vnd.triscape.mxs', 'mxu' => 'video/vnd.mpegurl', 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', 'n3' => 'text/n3', 'nb' => 'application/mathematica', 'nbp' => 'application/vnd.wolfram.player', 'nc' => 'application/x-netcdf', 'ncx' => 'application/x-dtbncx+xml', 'ndjson' => 'application/x-ndjson', 'nfo' => 'text/x-nfo', 'ngdat' => 'application/vnd.nokia.n-gage.data', 'nitf' => 'application/vnd.nitf', 'nlu' => 'application/vnd.neurolanguage.nlu', 'nml' => 'application/vnd.enliven', 'nnd' => 'application/vnd.noblenet-directory', 'nns' => 'application/vnd.noblenet-sealer', 'nnw' => 'application/vnd.noblenet-web', 'npx' => 'image/vnd.net-fpx', 'nq' => 'application/n-quads', 'nsc' => 'application/x-conference', 'nsf' => 'application/vnd.lotus-notes', 'nt' => 'application/n-triples', 'ntf' => 'application/vnd.nitf', 'numbers' => 'application/x-iwork-numbers-sffnumbers', 'nzb' => 'application/x-nzb', 'oa2' => 'application/vnd.fujitsu.oasys2', 'oa3' => 'application/vnd.fujitsu.oasys3', 'oas' => 'application/vnd.fujitsu.oasys', 'obd' => 'application/x-msbinder', 'obgx' => 'application/vnd.openblox.game+xml', 'obj' => 'model/obj', 'oda' => 'application/oda', 'odb' => 'application/vnd.oasis.opendocument.database', 'odc' => 'application/vnd.oasis.opendocument.chart', 'odf' => 'application/vnd.oasis.opendocument.formula', 'odft' => 'application/vnd.oasis.opendocument.formula-template', 'odg' => 'application/vnd.oasis.opendocument.graphics', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', 'odp' => 'application/vnd.oasis.opendocument.presentation', 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', 'odt' => 'application/vnd.oasis.opendocument.text', 'oga' => 'audio/ogg', 'ogex' => 'model/vnd.opengex', 'ogg' => 'audio/ogg', 'ogv' => 'video/ogg', 'ogx' => 'application/ogg', 'omdoc' => 'application/omdoc+xml', 'onepkg' => 'application/onenote', 'onetmp' => 'application/onenote', 'onetoc' => 'application/onenote', 'onetoc2' => 'application/onenote', 'opf' => 'application/oebps-package+xml', 'opml' => 'text/x-opml', 'oprc' => 'application/vnd.palm', 'opus' => 'audio/ogg', 'org' => 'text/x-org', 'osf' => 'application/vnd.yamaha.openscoreformat', 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', 'osm' => 'application/vnd.openstreetmap.data+xml', 'otc' => 'application/vnd.oasis.opendocument.chart-template', 'otf' => 'font/otf', 'otg' => 'application/vnd.oasis.opendocument.graphics-template', 'oth' => 'application/vnd.oasis.opendocument.text-web', 'oti' => 'application/vnd.oasis.opendocument.image-template', 'otp' => 'application/vnd.oasis.opendocument.presentation-template', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'ova' => 'application/x-virtualbox-ova', 'ovf' => 'application/x-virtualbox-ovf', 'owl' => 'application/rdf+xml', 'oxps' => 'application/oxps', 'oxt' => 'application/vnd.openofficeorg.extension', 'p' => 'text/x-pascal', 'p7a' => 'application/x-pkcs7-signature', 'p7b' => 'application/x-pkcs7-certificates', 'p7c' => 'application/pkcs7-mime', 'p7m' => 'application/pkcs7-mime', 'p7r' => 'application/x-pkcs7-certreqresp', 'p7s' => 'application/pkcs7-signature', 'p8' => 'application/pkcs8', 'p10' => 'application/x-pkcs10', 'p12' => 'application/x-pkcs12', 'pac' => 'application/x-ns-proxy-autoconfig', 'pages' => 'application/x-iwork-pages-sffpages', 'pas' => 'text/x-pascal', 'paw' => 'application/vnd.pawaafile', 'pbd' => 'application/vnd.powerbuilder6', 'pbm' => 'image/x-portable-bitmap', 'pcap' => 'application/vnd.tcpdump.pcap', 'pcf' => 'application/x-font-pcf', 'pcl' => 'application/vnd.hp-pcl', 'pclxl' => 'application/vnd.hp-pclxl', 'pct' => 'image/x-pict', 'pcurl' => 'application/vnd.curl.pcurl', 'pcx' => 'image/x-pcx', 'pdb' => 'application/x-pilot', 'pde' => 'text/x-processing', 'pdf' => 'application/pdf', 'pem' => 'application/x-x509-user-cert', 'pfa' => 'application/x-font-type1', 'pfb' => 'application/x-font-type1', 'pfm' => 'application/x-font-type1', 'pfr' => 'application/font-tdpfr', 'pfx' => 'application/x-pkcs12', 'pgm' => 'image/x-portable-graymap', 'pgn' => 'application/x-chess-pgn', 'pgp' => 'application/pgp', 'phar' => 'application/octet-stream', 'php' => 'application/x-httpd-php', 'php3' => 'application/x-httpd-php', 'php4' => 'application/x-httpd-php', 'phps' => 'application/x-httpd-php-source', 'phtml' => 'application/x-httpd-php', 'pic' => 'image/x-pict', 'pkg' => 'application/octet-stream', 'pki' => 'application/pkixcmp', 'pkipath' => 'application/pkix-pkipath', 'pkpass' => 'application/vnd.apple.pkpass', 'pl' => 'application/x-perl', 'plb' => 'application/vnd.3gpp.pic-bw-large', 'plc' => 'application/vnd.mobius.plc', 'plf' => 'application/vnd.pocketlearn', 'pls' => 'application/pls+xml', 'pm' => 'application/x-perl', 'pml' => 'application/vnd.ctc-posml', 'png' => 'image/png', 'pnm' => 'image/x-portable-anymap', 'portpkg' => 'application/vnd.macports.portpkg', 'pot' => 'application/vnd.ms-powerpoint', 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', 'ppa' => 'application/vnd.ms-powerpoint', 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', 'ppd' => 'application/vnd.cups-ppd', 'ppm' => 'image/x-portable-pixmap', 'pps' => 'application/vnd.ms-powerpoint', 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', 'ppt' => 'application/powerpoint', 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', 'pqa' => 'application/vnd.palm', 'prc' => 'model/prc', 'pre' => 'application/vnd.lotus-freelance', 'prf' => 'application/pics-rules', 'provx' => 'application/provenance+xml', 'ps' => 'application/postscript', 'psb' => 'application/vnd.3gpp.pic-bw-small', 'psd' => 'application/x-photoshop', 'psf' => 'application/x-font-linux-psf', 'pskcxml' => 'application/pskc+xml', 'pti' => 'image/prs.pti', 'ptid' => 'application/vnd.pvi.ptid1', 'pub' => 'application/x-mspublisher', 'pv' => 'application/octet-stream', 'pvb' => 'application/vnd.3gpp.pic-bw-var', 'pwn' => 'application/vnd.3m.post-it-notes', 'pxf' => 'application/octet-stream', 'pya' => 'audio/vnd.ms-playready.media.pya', 'pyo' => 'model/vnd.pytha.pyox', 'pyox' => 'model/vnd.pytha.pyox', 'pyv' => 'video/vnd.ms-playready.media.pyv', 'qam' => 'application/vnd.epson.quickanime', 'qbo' => 'application/vnd.intu.qbo', 'qfx' => 'application/vnd.intu.qfx', 'qps' => 'application/vnd.publishare-delta-tree', 'qt' => 'video/quicktime', 'qwd' => 'application/vnd.quark.quarkxpress', 'qwt' => 'application/vnd.quark.quarkxpress', 'qxb' => 'application/vnd.quark.quarkxpress', 'qxd' => 'application/vnd.quark.quarkxpress', 'qxl' => 'application/vnd.quark.quarkxpress', 'qxt' => 'application/vnd.quark.quarkxpress', 'ra' => 'audio/x-realaudio', 'ram' => 'audio/x-pn-realaudio', 'raml' => 'application/raml+yaml', 'rapd' => 'application/route-apd+xml', 'rar' => 'application/x-rar', 'ras' => 'image/x-cmu-raster', 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', 'rdf' => 'application/rdf+xml', 'rdz' => 'application/vnd.data-vision.rdz', 'relo' => 'application/p2p-overlay+xml', 'rep' => 'application/vnd.businessobjects', 'res' => 'application/x-dtbresource+xml', 'rgb' => 'image/x-rgb', 'rif' => 'application/reginfo+xml', 'rip' => 'audio/vnd.rip', 'ris' => 'application/x-research-info-systems', 'rl' => 'application/resource-lists+xml', 'rlc' => 'image/vnd.fujixerox.edmics-rlc', 'rld' => 'application/resource-lists-diff+xml', 'rm' => 'audio/x-pn-realaudio', 'rmi' => 'audio/midi', 'rmp' => 'audio/x-pn-realaudio-plugin', 'rms' => 'application/vnd.jcp.javame.midlet-rms', 'rmvb' => 'application/vnd.rn-realmedia-vbr', 'rnc' => 'application/relax-ng-compact-syntax', 'rng' => 'application/xml', 'roa' => 'application/rpki-roa', 'roff' => 'text/troff', 'rp9' => 'application/vnd.cloanto.rp9', 'rpm' => 'audio/x-pn-realaudio-plugin', 'rpss' => 'application/vnd.nokia.radio-presets', 'rpst' => 'application/vnd.nokia.radio-preset', 'rq' => 'application/sparql-query', 'rs' => 'application/rls-services+xml', 'rsa' => 'application/x-pkcs7', 'rsat' => 'application/atsc-rsat+xml', 'rsd' => 'application/rsd+xml', 'rsheet' => 'application/urc-ressheet+xml', 'rss' => 'application/rss+xml', 'rtf' => 'text/rtf', 'rtx' => 'text/richtext', 'run' => 'application/x-makeself', 'rusd' => 'application/route-usd+xml', 'rv' => 'video/vnd.rn-realvideo', 's' => 'text/x-asm', 's3m' => 'audio/s3m', 'saf' => 'application/vnd.yamaha.smaf-audio', 'sass' => 'text/x-sass', 'sbml' => 'application/sbml+xml', 'sc' => 'application/vnd.ibm.secure-container', 'scd' => 'application/x-msschedule', 'scm' => 'application/vnd.lotus-screencam', 'scq' => 'application/scvp-cv-request', 'scs' => 'application/scvp-cv-response', 'scss' => 'text/x-scss', 'scurl' => 'text/vnd.curl.scurl', 'sda' => 'application/vnd.stardivision.draw', 'sdc' => 'application/vnd.stardivision.calc', 'sdd' => 'application/vnd.stardivision.impress', 'sdkd' => 'application/vnd.solent.sdkm+xml', 'sdkm' => 'application/vnd.solent.sdkm+xml', 'sdp' => 'application/sdp', 'sdw' => 'application/vnd.stardivision.writer', 'sea' => 'application/octet-stream', 'see' => 'application/vnd.seemail', 'seed' => 'application/vnd.fdsn.seed', 'sema' => 'application/vnd.sema', 'semd' => 'application/vnd.semd', 'semf' => 'application/vnd.semf', 'senmlx' => 'application/senml+xml', 'sensmlx' => 'application/sensml+xml', 'ser' => 'application/java-serialized-object', 'setpay' => 'application/set-payment-initiation', 'setreg' => 'application/set-registration-initiation', 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', 'sfs' => 'application/vnd.spotfire.sfs', 'sfv' => 'text/x-sfv', 'sgi' => 'image/sgi', 'sgl' => 'application/vnd.stardivision.writer-global', 'sgm' => 'text/sgml', 'sgml' => 'text/sgml', 'sh' => 'application/x-sh', 'shar' => 'application/x-shar', 'shex' => 'text/shex', 'shf' => 'application/shf+xml', 'shtml' => 'text/html', 'sid' => 'image/x-mrsid-image', 'sieve' => 'application/sieve', 'sig' => 'application/pgp-signature', 'sil' => 'audio/silk', 'silo' => 'model/mesh', 'sis' => 'application/vnd.symbian.install', 'sisx' => 'application/vnd.symbian.install', 'sit' => 'application/x-stuffit', 'sitx' => 'application/x-stuffitx', 'siv' => 'application/sieve', 'skd' => 'application/vnd.koan', 'skm' => 'application/vnd.koan', 'skp' => 'application/vnd.koan', 'skt' => 'application/vnd.koan', 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', 'slim' => 'text/slim', 'slm' => 'text/slim', 'sls' => 'application/route-s-tsid+xml', 'slt' => 'application/vnd.epson.salt', 'sm' => 'application/vnd.stepmania.stepchart', 'smf' => 'application/vnd.stardivision.math', 'smi' => 'application/smil', 'smil' => 'application/smil', 'smv' => 'video/x-smv', 'smzip' => 'application/vnd.stepmania.package', 'snd' => 'audio/basic', 'snf' => 'application/x-font-snf', 'so' => 'application/octet-stream', 'spc' => 'application/x-pkcs7-certificates', 'spdx' => 'text/spdx', 'spf' => 'application/vnd.yamaha.smaf-phrase', 'spl' => 'application/x-futuresplash', 'spot' => 'text/vnd.in3d.spot', 'spp' => 'application/scvp-vp-response', 'spq' => 'application/scvp-vp-request', 'spx' => 'audio/ogg', 'sql' => 'application/x-sql', 'src' => 'application/x-wais-source', 'srt' => 'application/x-subrip', 'sru' => 'application/sru+xml', 'srx' => 'application/sparql-results+xml', 'ssdl' => 'application/ssdl+xml', 'sse' => 'application/vnd.kodak-descriptor', 'ssf' => 'application/vnd.epson.ssf', 'ssml' => 'application/ssml+xml', 'sst' => 'application/octet-stream', 'st' => 'application/vnd.sailingtracker.track', 'stc' => 'application/vnd.sun.xml.calc.template', 'std' => 'application/vnd.sun.xml.draw.template', 'step' => 'application/STEP', 'stf' => 'application/vnd.wt.stf', 'sti' => 'application/vnd.sun.xml.impress.template', 'stk' => 'application/hyperstudio', 'stl' => 'model/stl', 'stp' => 'application/STEP', 'stpx' => 'model/step+xml', 'stpxz' => 'model/step-xml+zip', 'stpz' => 'model/step+zip', 'str' => 'application/vnd.pg.format', 'stw' => 'application/vnd.sun.xml.writer.template', 'styl' => 'text/stylus', 'stylus' => 'text/stylus', 'sub' => 'text/vnd.dvb.subtitle', 'sus' => 'application/vnd.sus-calendar', 'susp' => 'application/vnd.sus-calendar', 'sv4cpio' => 'application/x-sv4cpio', 'sv4crc' => 'application/x-sv4crc', 'svc' => 'application/vnd.dvb.service', 'svd' => 'application/vnd.svd', 'svg' => 'image/svg+xml', 'svgz' => 'image/svg+xml', 'swa' => 'application/x-director', 'swf' => 'application/x-shockwave-flash', 'swi' => 'application/vnd.aristanetworks.swi', 'swidtag' => 'application/swid+xml', 'sxc' => 'application/vnd.sun.xml.calc', 'sxd' => 'application/vnd.sun.xml.draw', 'sxg' => 'application/vnd.sun.xml.writer.global', 'sxi' => 'application/vnd.sun.xml.impress', 'sxm' => 'application/vnd.sun.xml.math', 'sxw' => 'application/vnd.sun.xml.writer', 't' => 'text/troff', 't3' => 'application/x-t3vm-image', 't38' => 'image/t38', 'taglet' => 'application/vnd.mynfc', 'tao' => 'application/vnd.tao.intent-module-archive', 'tap' => 'image/vnd.tencent.tap', 'tar' => 'application/x-tar', 'tcap' => 'application/vnd.3gpp2.tcap', 'tcl' => 'application/x-tcl', 'td' => 'application/urc-targetdesc+xml', 'teacher' => 'application/vnd.smart.teacher', 'tei' => 'application/tei+xml', 'teicorpus' => 'application/tei+xml', 'tex' => 'application/x-tex', 'texi' => 'application/x-texinfo', 'texinfo' => 'application/x-texinfo', 'text' => 'text/plain', 'tfi' => 'application/thraud+xml', 'tfm' => 'application/x-tex-tfm', 'tfx' => 'image/tiff-fx', 'tga' => 'image/x-tga', 'tgz' => 'application/x-tar', 'thmx' => 'application/vnd.ms-officetheme', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'tk' => 'application/x-tcl', 'tmo' => 'application/vnd.tmobile-livetv', 'toml' => 'application/toml', 'torrent' => 'application/x-bittorrent', 'tpl' => 'application/vnd.groove-tool-template', 'tpt' => 'application/vnd.trid.tpt', 'tr' => 'text/troff', 'tra' => 'application/vnd.trueapp', 'trig' => 'application/trig', 'trm' => 'application/x-msterminal', 'ts' => 'video/mp2t', 'tsd' => 'application/timestamped-data', 'tsv' => 'text/tab-separated-values', 'ttc' => 'font/collection', 'ttf' => 'font/ttf', 'ttl' => 'text/turtle', 'ttml' => 'application/ttml+xml', 'twd' => 'application/vnd.simtech-mindmapper', 'twds' => 'application/vnd.simtech-mindmapper', 'txd' => 'application/vnd.genomatix.tuxedo', 'txf' => 'application/vnd.mobius.txf', 'txt' => 'text/plain', 'u3d' => 'model/u3d', 'u8dsn' => 'message/global-delivery-status', 'u8hdr' => 'message/global-headers', 'u8mdn' => 'message/global-disposition-notification', 'u8msg' => 'message/global', 'u32' => 'application/x-authorware-bin', 'ubj' => 'application/ubjson', 'udeb' => 'application/x-debian-package', 'ufd' => 'application/vnd.ufdl', 'ufdl' => 'application/vnd.ufdl', 'ulx' => 'application/x-glulx', 'umj' => 'application/vnd.umajin', 'unityweb' => 'application/vnd.unity', 'uo' => 'application/vnd.uoml+xml', 'uoml' => 'application/vnd.uoml+xml', 'uri' => 'text/uri-list', 'uris' => 'text/uri-list', 'urls' => 'text/uri-list', 'usda' => 'model/vnd.usda', 'usdz' => 'model/vnd.usdz+zip', 'ustar' => 'application/x-ustar', 'utz' => 'application/vnd.uiq.theme', 'uu' => 'text/x-uuencode', 'uva' => 'audio/vnd.dece.audio', 'uvd' => 'application/vnd.dece.data', 'uvf' => 'application/vnd.dece.data', 'uvg' => 'image/vnd.dece.graphic', 'uvh' => 'video/vnd.dece.hd', 'uvi' => 'image/vnd.dece.graphic', 'uvm' => 'video/vnd.dece.mobile', 'uvp' => 'video/vnd.dece.pd', 'uvs' => 'video/vnd.dece.sd', 'uvt' => 'application/vnd.dece.ttml+xml', 'uvu' => 'video/vnd.uvvu.mp4', 'uvv' => 'video/vnd.dece.video', 'uvva' => 'audio/vnd.dece.audio', 'uvvd' => 'application/vnd.dece.data', 'uvvf' => 'application/vnd.dece.data', 'uvvg' => 'image/vnd.dece.graphic', 'uvvh' => 'video/vnd.dece.hd', 'uvvi' => 'image/vnd.dece.graphic', 'uvvm' => 'video/vnd.dece.mobile', 'uvvp' => 'video/vnd.dece.pd', 'uvvs' => 'video/vnd.dece.sd', 'uvvt' => 'application/vnd.dece.ttml+xml', 'uvvu' => 'video/vnd.uvvu.mp4', 'uvvv' => 'video/vnd.dece.video', 'uvvx' => 'application/vnd.dece.unspecified', 'uvvz' => 'application/vnd.dece.zip', 'uvx' => 'application/vnd.dece.unspecified', 'uvz' => 'application/vnd.dece.zip', 'vbox' => 'application/x-virtualbox-vbox', 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', 'vcard' => 'text/vcard', 'vcd' => 'application/x-cdlink', 'vcf' => 'text/x-vcard', 'vcg' => 'application/vnd.groove-vcard', 'vcs' => 'text/x-vcalendar', 'vcx' => 'application/vnd.vcx', 'vdi' => 'application/x-virtualbox-vdi', 'vds' => 'model/vnd.sap.vds', 'vhd' => 'application/x-virtualbox-vhd', 'vis' => 'application/vnd.visionary', 'viv' => 'video/vnd.vivo', 'vlc' => 'application/videolan', 'vmdk' => 'application/x-virtualbox-vmdk', 'vob' => 'video/x-ms-vob', 'vor' => 'application/vnd.stardivision.writer', 'vox' => 'application/x-authorware-bin', 'vrml' => 'model/vrml', 'vsd' => 'application/vnd.visio', 'vsf' => 'application/vnd.vsf', 'vss' => 'application/vnd.visio', 'vst' => 'application/vnd.visio', 'vsw' => 'application/vnd.visio', 'vtf' => 'image/vnd.valve.source.texture', 'vtt' => 'text/vtt', 'vtu' => 'model/vnd.vtu', 'vxml' => 'application/voicexml+xml', 'w3d' => 'application/x-director', 'wad' => 'application/x-doom', 'wadl' => 'application/vnd.sun.wadl+xml', 'war' => 'application/java-archive', 'wasm' => 'application/wasm', 'wav' => 'audio/x-wav', 'wax' => 'audio/x-ms-wax', 'wbmp' => 'image/vnd.wap.wbmp', 'wbs' => 'application/vnd.criticaltools.wbs+xml', 'wbxml' => 'application/wbxml', 'wcm' => 'application/vnd.ms-works', 'wdb' => 'application/vnd.ms-works', 'wdp' => 'image/vnd.ms-photo', 'weba' => 'audio/webm', 'webapp' => 'application/x-web-app-manifest+json', 'webm' => 'video/webm', 'webmanifest' => 'application/manifest+json', 'webp' => 'image/webp', 'wg' => 'application/vnd.pmi.widget', 'wgsl' => 'text/wgsl', 'wgt' => 'application/widget', 'wif' => 'application/watcherinfo+xml', 'wks' => 'application/vnd.ms-works', 'wm' => 'video/x-ms-wm', 'wma' => 'audio/x-ms-wma', 'wmd' => 'application/x-ms-wmd', 'wmf' => 'image/wmf', 'wml' => 'text/vnd.wap.wml', 'wmlc' => 'application/wmlc', 'wmls' => 'text/vnd.wap.wmlscript', 'wmlsc' => 'application/vnd.wap.wmlscriptc', 'wmv' => 'video/x-ms-wmv', 'wmx' => 'video/x-ms-wmx', 'wmz' => 'application/x-msmetafile', 'woff' => 'font/woff', 'woff2' => 'font/woff2', 'word' => 'application/msword', 'wpd' => 'application/vnd.wordperfect', 'wpl' => 'application/vnd.ms-wpl', 'wps' => 'application/vnd.ms-works', 'wqd' => 'application/vnd.wqd', 'wri' => 'application/x-mswrite', 'wrl' => 'model/vrml', 'wsc' => 'message/vnd.wfa.wsc', 'wsdl' => 'application/wsdl+xml', 'wspolicy' => 'application/wspolicy+xml', 'wtb' => 'application/vnd.webturbo', 'wvx' => 'video/x-ms-wvx', 'x3d' => 'model/x3d+xml', 'x3db' => 'model/x3d+fastinfoset', 'x3dbz' => 'model/x3d+binary', 'x3dv' => 'model/x3d-vrml', 'x3dvz' => 'model/x3d+vrml', 'x3dz' => 'model/x3d+xml', 'x32' => 'application/x-authorware-bin', 'x_b' => 'model/vnd.parasolid.transmit.binary', 'x_t' => 'model/vnd.parasolid.transmit.text', 'xaml' => 'application/xaml+xml', 'xap' => 'application/x-silverlight-app', 'xar' => 'application/vnd.xara', 'xav' => 'application/xcap-att+xml', 'xbap' => 'application/x-ms-xbap', 'xbd' => 'application/vnd.fujixerox.docuworks.binder', 'xbm' => 'image/x-xbitmap', 'xca' => 'application/xcap-caps+xml', 'xcs' => 'application/calendar+xml', 'xdcf' => 'application/vnd.gov.sk.xmldatacontainer+xml', 'xdf' => 'application/xcap-diff+xml', 'xdm' => 'application/vnd.syncml.dm+xml', 'xdp' => 'application/vnd.adobe.xdp+xml', 'xdssc' => 'application/dssc+xml', 'xdw' => 'application/vnd.fujixerox.docuworks', 'xel' => 'application/xcap-el+xml', 'xenc' => 'application/xenc+xml', 'xer' => 'application/patch-ops-error+xml', 'xfdf' => 'application/xfdf', 'xfdl' => 'application/vnd.xfdl', 'xht' => 'application/xhtml+xml', 'xhtm' => 'application/vnd.pwg-xhtml-print+xml', 'xhtml' => 'application/xhtml+xml', 'xhvml' => 'application/xv+xml', 'xif' => 'image/vnd.xiff', 'xl' => 'application/excel', 'xla' => 'application/vnd.ms-excel', 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', 'xlc' => 'application/vnd.ms-excel', 'xlf' => 'application/xliff+xml', 'xlm' => 'application/vnd.ms-excel', 'xls' => 'application/vnd.ms-excel', 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlt' => 'application/vnd.ms-excel', 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', 'xlw' => 'application/vnd.ms-excel', 'xm' => 'audio/xm', 'xml' => 'application/xml', 'xns' => 'application/xcap-ns+xml', 'xo' => 'application/vnd.olpc-sugar', 'xop' => 'application/xop+xml', 'xpi' => 'application/x-xpinstall', 'xpl' => 'application/xproc+xml', 'xpm' => 'image/x-xpixmap', 'xpr' => 'application/vnd.is-xpr', 'xps' => 'application/vnd.ms-xpsdocument', 'xpw' => 'application/vnd.intercon.formnet', 'xpx' => 'application/vnd.intercon.formnet', 'xsd' => 'application/xml', 'xsf' => 'application/prs.xsf+xml', 'xsl' => 'application/xml', 'xslt' => 'application/xslt+xml', 'xsm' => 'application/vnd.syncml+xml', 'xspf' => 'application/xspf+xml', 'xul' => 'application/vnd.mozilla.xul+xml', 'xvm' => 'application/xv+xml', 'xvml' => 'application/xv+xml', 'xwd' => 'image/x-xwindowdump', 'xyz' => 'chemical/x-xyz', 'xz' => 'application/x-xz', 'yaml' => 'text/yaml', 'yang' => 'application/yang', 'yin' => 'application/yin+xml', 'yml' => 'text/yaml', 'ymp' => 'text/x-suse-ymp', 'z' => 'application/x-compress', 'z1' => 'application/x-zmachine', 'z2' => 'application/x-zmachine', 'z3' => 'application/x-zmachine', 'z4' => 'application/x-zmachine', 'z5' => 'application/x-zmachine', 'z6' => 'application/x-zmachine', 'z7' => 'application/x-zmachine', 'z8' => 'application/x-zmachine', 'zaz' => 'application/vnd.zzazz.deck+xml', 'zip' => 'application/zip', 'zir' => 'application/vnd.zul', 'zirz' => 'application/vnd.zul', 'zmm' => 'application/vnd.handheld-entertainment+xml', 'zsh' => 'text/x-scriptzsh', ]; /** * @var array<string, string> * * @internal */ public const EXTENSIONS_FOR_MIME_TIMES = [ 'application/andrew-inset' => ['ez'], 'application/appinstaller' => ['appinstaller'], 'application/applixware' => ['aw'], 'application/appx' => ['appx'], 'application/appxbundle' => ['appxbundle'], 'application/atom+xml' => ['atom'], 'application/atomcat+xml' => ['atomcat'], 'application/atomdeleted+xml' => ['atomdeleted'], 'application/atomsvc+xml' => ['atomsvc'], 'application/atsc-dwd+xml' => ['dwd'], 'application/atsc-held+xml' => ['held'], 'application/atsc-rsat+xml' => ['rsat'], 'application/automationml-aml+xml' => ['aml'], 'application/automationml-amlx+zip' => ['amlx'], 'application/bdoc' => ['bdoc'], 'application/calendar+xml' => ['xcs'], 'application/ccxml+xml' => ['ccxml'], 'application/cdfx+xml' => ['cdfx'], 'application/cdmi-capability' => ['cdmia'], 'application/cdmi-container' => ['cdmic'], 'application/cdmi-domain' => ['cdmid'], 'application/cdmi-object' => ['cdmio'], 'application/cdmi-queue' => ['cdmiq'], 'application/cpl+xml' => ['cpl'], 'application/cu-seeme' => ['cu'], 'application/cwl' => ['cwl'], 'application/dash+xml' => ['mpd'], 'application/dash-patch+xml' => ['mpp'], 'application/davmount+xml' => ['davmount'], 'application/docbook+xml' => ['dbk'], 'application/dssc+der' => ['dssc'], 'application/dssc+xml' => ['xdssc'], 'application/ecmascript' => ['ecma'], 'application/emma+xml' => ['emma'], 'application/emotionml+xml' => ['emotionml'], 'application/epub+zip' => ['epub'], 'application/exi' => ['exi'], 'application/express' => ['exp'], 'application/fdf' => ['fdf'], 'application/fdt+xml' => ['fdt'], 'application/font-tdpfr' => ['pfr'], 'application/geo+json' => ['geojson'], 'application/gml+xml' => ['gml'], 'application/gpx+xml' => ['gpx'], 'application/gxf' => ['gxf'], 'application/gzip' => ['gz', 'gzip'], 'application/hjson' => ['hjson'], 'application/hyperstudio' => ['stk'], 'application/inkml+xml' => ['ink', 'inkml'], 'application/ipfix' => ['ipfix'], 'application/its+xml' => ['its'], 'application/java-archive' => ['jar', 'war', 'ear'], 'application/java-serialized-object' => ['ser'], 'application/java-vm' => ['class'], 'application/javascript' => ['js'], 'application/json' => ['json', 'map'], 'application/json5' => ['json5'], 'application/jsonml+json' => ['jsonml'], 'application/ld+json' => ['jsonld'], 'application/lgr+xml' => ['lgr'], 'application/lost+xml' => ['lostxml'], 'application/mac-binhex40' => ['hqx'], 'application/mac-compactpro' => ['cpt'], 'application/mads+xml' => ['mads'], 'application/manifest+json' => ['webmanifest'], 'application/marc' => ['mrc'], 'application/marcxml+xml' => ['mrcx'], 'application/mathematica' => ['ma', 'nb', 'mb'], 'application/mathml+xml' => ['mathml'], 'application/mbox' => ['mbox'], 'application/media-policy-dataset+xml' => ['mpf'], 'application/mediaservercontrol+xml' => ['mscml'], 'application/metalink+xml' => ['metalink'], 'application/metalink4+xml' => ['meta4'], 'application/mets+xml' => ['mets'], 'application/mmt-aei+xml' => ['maei'], 'application/mmt-usd+xml' => ['musd'], 'application/mods+xml' => ['mods'], 'application/mp21' => ['m21', 'mp21'], 'application/mp4' => ['mp4', 'mpg4', 'mp4s', 'm4p'], 'application/msix' => ['msix'], 'application/msixbundle' => ['msixbundle'], 'application/msword' => ['doc', 'dot', 'word'], 'application/mxf' => ['mxf'], 'application/n-quads' => ['nq'], 'application/n-triples' => ['nt'], 'application/node' => ['cjs'], 'application/octet-stream' => ['bin', 'dms', 'lrf', 'mar', 'so', 'dist', 'distz', 'pkg', 'bpk', 'dump', 'elc', 'deploy', 'exe', 'dll', 'deb', 'dmg', 'iso', 'img', 'msi', 'msp', 'msm', 'buffer', 'phar', 'lha', 'lzh', 'class', 'sea', 'dmn', 'bpmn', 'kdb', 'sst', 'csr', 'dst', 'pv', 'pxf'], 'application/oda' => ['oda'], 'application/oebps-package+xml' => ['opf'], 'application/ogg' => ['ogx'], 'application/omdoc+xml' => ['omdoc'], 'application/onenote' => ['onetoc', 'onetoc2', 'onetmp', 'onepkg'], 'application/oxps' => ['oxps'], 'application/p2p-overlay+xml' => ['relo'], 'application/patch-ops-error+xml' => ['xer'], 'application/pdf' => ['pdf', 'ai'], 'application/pgp-encrypted' => ['pgp'], 'application/pgp-keys' => ['asc'], 'application/pgp-signature' => ['sig', 'asc'], 'application/pics-rules' => ['prf'], 'application/pkcs10' => ['p10'], 'application/pkcs7-mime' => ['p7m', 'p7c'], 'application/pkcs7-signature' => ['p7s'], 'application/pkcs8' => ['p8'], 'application/pkix-attr-cert' => ['ac'], 'application/pkix-cert' => ['cer'], 'application/pkix-crl' => ['crl'], 'application/pkix-pkipath' => ['pkipath'], 'application/pkixcmp' => ['pki'], 'application/pls+xml' => ['pls'], 'application/postscript' => ['ai', 'eps', 'ps'], 'application/provenance+xml' => ['provx'], 'application/prs.cww' => ['cww'], 'application/prs.xsf+xml' => ['xsf'], 'application/pskc+xml' => ['pskcxml'], 'application/raml+yaml' => ['raml'], 'application/rdf+xml' => ['rdf', 'owl'], 'application/reginfo+xml' => ['rif'], 'application/relax-ng-compact-syntax' => ['rnc'], 'application/resource-lists+xml' => ['rl'], 'application/resource-lists-diff+xml' => ['rld'], 'application/rls-services+xml' => ['rs'], 'application/route-apd+xml' => ['rapd'], 'application/route-s-tsid+xml' => ['sls'], 'application/route-usd+xml' => ['rusd'], 'application/rpki-ghostbusters' => ['gbr'], 'application/rpki-manifest' => ['mft'], 'application/rpki-roa' => ['roa'], 'application/rsd+xml' => ['rsd'], 'application/rss+xml' => ['rss'], 'application/rtf' => ['rtf'], 'application/sbml+xml' => ['sbml'], 'application/scvp-cv-request' => ['scq'], 'application/scvp-cv-response' => ['scs'], 'application/scvp-vp-request' => ['spq'], 'application/scvp-vp-response' => ['spp'], 'application/sdp' => ['sdp'], 'application/senml+xml' => ['senmlx'], 'application/sensml+xml' => ['sensmlx'], 'application/set-payment-initiation' => ['setpay'], 'application/set-registration-initiation' => ['setreg'], 'application/shf+xml' => ['shf'], 'application/sieve' => ['siv', 'sieve'], 'application/smil+xml' => ['smi', 'smil'], 'application/sparql-query' => ['rq'], 'application/sparql-results+xml' => ['srx'], 'application/sql' => ['sql'], 'application/srgs' => ['gram'], 'application/srgs+xml' => ['grxml'], 'application/sru+xml' => ['sru'], 'application/ssdl+xml' => ['ssdl'], 'application/ssml+xml' => ['ssml'], 'application/swid+xml' => ['swidtag'], 'application/tei+xml' => ['tei', 'teicorpus'], 'application/thraud+xml' => ['tfi'], 'application/timestamped-data' => ['tsd'], 'application/toml' => ['toml'], 'application/trig' => ['trig'], 'application/ttml+xml' => ['ttml'], 'application/ubjson' => ['ubj'], 'application/urc-ressheet+xml' => ['rsheet'], 'application/urc-targetdesc+xml' => ['td'], 'application/vnd.1000minds.decision-model+xml' => ['1km'], 'application/vnd.3gpp.pic-bw-large' => ['plb'], 'application/vnd.3gpp.pic-bw-small' => ['psb'], 'application/vnd.3gpp.pic-bw-var' => ['pvb'], 'application/vnd.3gpp2.tcap' => ['tcap'], 'application/vnd.3m.post-it-notes' => ['pwn'], 'application/vnd.accpac.simply.aso' => ['aso'], 'application/vnd.accpac.simply.imp' => ['imp'], 'application/vnd.acucobol' => ['acu'], 'application/vnd.acucorp' => ['atc', 'acutc'], 'application/vnd.adobe.air-application-installer-package+zip' => ['air'], 'application/vnd.adobe.formscentral.fcdt' => ['fcdt'], 'application/vnd.adobe.fxp' => ['fxp', 'fxpl'], 'application/vnd.adobe.xdp+xml' => ['xdp'], 'application/vnd.adobe.xfdf' => ['xfdf'], 'application/vnd.age' => ['age'], 'application/vnd.ahead.space' => ['ahead'], 'application/vnd.airzip.filesecure.azf' => ['azf'], 'application/vnd.airzip.filesecure.azs' => ['azs'], 'application/vnd.amazon.ebook' => ['azw'], 'application/vnd.americandynamics.acc' => ['acc'], 'application/vnd.amiga.ami' => ['ami'], 'application/vnd.android.package-archive' => ['apk'], 'application/vnd.anser-web-certificate-issue-initiation' => ['cii'], 'application/vnd.anser-web-funds-transfer-initiation' => ['fti'], 'application/vnd.antix.game-component' => ['atx'], 'application/vnd.apple.installer+xml' => ['mpkg'], 'application/vnd.apple.keynote' => ['key'], 'application/vnd.apple.mpegurl' => ['m3u8'], 'application/vnd.apple.numbers' => ['numbers'], 'application/vnd.apple.pages' => ['pages'], 'application/vnd.apple.pkpass' => ['pkpass'], 'application/vnd.aristanetworks.swi' => ['swi'], 'application/vnd.astraea-software.iota' => ['iota'], 'application/vnd.audiograph' => ['aep'], 'application/vnd.balsamiq.bmml+xml' => ['bmml'], 'application/vnd.blueice.multipass' => ['mpm'], 'application/vnd.bmi' => ['bmi'], 'application/vnd.businessobjects' => ['rep'], 'application/vnd.chemdraw+xml' => ['cdxml'], 'application/vnd.chipnuts.karaoke-mmd' => ['mmd'], 'application/vnd.cinderella' => ['cdy'], 'application/vnd.citationstyles.style+xml' => ['csl'], 'application/vnd.claymore' => ['cla'], 'application/vnd.cloanto.rp9' => ['rp9'], 'application/vnd.clonk.c4group' => ['c4g', 'c4d', 'c4f', 'c4p', 'c4u'], 'application/vnd.cluetrust.cartomobile-config' => ['c11amc'], 'application/vnd.cluetrust.cartomobile-config-pkg' => ['c11amz'], 'application/vnd.commonspace' => ['csp'], 'application/vnd.contact.cmsg' => ['cdbcmsg'], 'application/vnd.cosmocaller' => ['cmc'], 'application/vnd.crick.clicker' => ['clkx'], 'application/vnd.crick.clicker.keyboard' => ['clkk'], 'application/vnd.crick.clicker.palette' => ['clkp'], 'application/vnd.crick.clicker.template' => ['clkt'], 'application/vnd.crick.clicker.wordbank' => ['clkw'], 'application/vnd.criticaltools.wbs+xml' => ['wbs'], 'application/vnd.ctc-posml' => ['pml'], 'application/vnd.cups-ppd' => ['ppd'], 'application/vnd.curl.car' => ['car'], 'application/vnd.curl.pcurl' => ['pcurl'], 'application/vnd.dart' => ['dart'], 'application/vnd.data-vision.rdz' => ['rdz'], 'application/vnd.dbf' => ['dbf'], 'application/vnd.dece.data' => ['uvf', 'uvvf', 'uvd', 'uvvd'], 'application/vnd.dece.ttml+xml' => ['uvt', 'uvvt'], 'application/vnd.dece.unspecified' => ['uvx', 'uvvx'], 'application/vnd.dece.zip' => ['uvz', 'uvvz'], 'application/vnd.denovo.fcselayout-link' => ['fe_launch'], 'application/vnd.dna' => ['dna'], 'application/vnd.dolby.mlp' => ['mlp'], 'application/vnd.dpgraph' => ['dpg'], 'application/vnd.dreamfactory' => ['dfac'], 'application/vnd.ds-keypoint' => ['kpxx'], 'application/vnd.dvb.ait' => ['ait'], 'application/vnd.dvb.service' => ['svc'], 'application/vnd.dynageo' => ['geo'], 'application/vnd.ecowin.chart' => ['mag'], 'application/vnd.enliven' => ['nml'], 'application/vnd.epson.esf' => ['esf'], 'application/vnd.epson.msf' => ['msf'], 'application/vnd.epson.quickanime' => ['qam'], 'application/vnd.epson.salt' => ['slt'], 'application/vnd.epson.ssf' => ['ssf'], 'application/vnd.eszigno3+xml' => ['es3', 'et3'], 'application/vnd.ezpix-album' => ['ez2'], 'application/vnd.ezpix-package' => ['ez3'], 'application/vnd.fdf' => ['fdf'], 'application/vnd.fdsn.mseed' => ['mseed'], 'application/vnd.fdsn.seed' => ['seed', 'dataless'], 'application/vnd.flographit' => ['gph'], 'application/vnd.fluxtime.clip' => ['ftc'], 'application/vnd.framemaker' => ['fm', 'frame', 'maker', 'book'], 'application/vnd.frogans.fnc' => ['fnc'], 'application/vnd.frogans.ltf' => ['ltf'], 'application/vnd.fsc.weblaunch' => ['fsc'], 'application/vnd.fujitsu.oasys' => ['oas'], 'application/vnd.fujitsu.oasys2' => ['oa2'], 'application/vnd.fujitsu.oasys3' => ['oa3'], 'application/vnd.fujitsu.oasysgp' => ['fg5'], 'application/vnd.fujitsu.oasysprs' => ['bh2'], 'application/vnd.fujixerox.ddd' => ['ddd'], 'application/vnd.fujixerox.docuworks' => ['xdw'], 'application/vnd.fujixerox.docuworks.binder' => ['xbd'], 'application/vnd.fuzzysheet' => ['fzs'], 'application/vnd.genomatix.tuxedo' => ['txd'], 'application/vnd.geogebra.file' => ['ggb'], 'application/vnd.geogebra.slides' => ['ggs'], 'application/vnd.geogebra.tool' => ['ggt'], 'application/vnd.geometry-explorer' => ['gex', 'gre'], 'application/vnd.geonext' => ['gxt'], 'application/vnd.geoplan' => ['g2w'], 'application/vnd.geospace' => ['g3w'], 'application/vnd.gmx' => ['gmx'], 'application/vnd.google-apps.document' => ['gdoc'], 'application/vnd.google-apps.presentation' => ['gslides'], 'application/vnd.google-apps.spreadsheet' => ['gsheet'], 'application/vnd.google-earth.kml+xml' => ['kml'], 'application/vnd.google-earth.kmz' => ['kmz'], 'application/vnd.gov.sk.xmldatacontainer+xml' => ['xdcf'], 'application/vnd.grafeq' => ['gqf', 'gqs'], 'application/vnd.groove-account' => ['gac'], 'application/vnd.groove-help' => ['ghf'], 'application/vnd.groove-identity-message' => ['gim'], 'application/vnd.groove-injector' => ['grv'], 'application/vnd.groove-tool-message' => ['gtm'], 'application/vnd.groove-tool-template' => ['tpl'], 'application/vnd.groove-vcard' => ['vcg'], 'application/vnd.hal+xml' => ['hal'], 'application/vnd.handheld-entertainment+xml' => ['zmm'], 'application/vnd.hbci' => ['hbci'], 'application/vnd.hhe.lesson-player' => ['les'], 'application/vnd.hp-hpgl' => ['hpgl'], 'application/vnd.hp-hpid' => ['hpid'], 'application/vnd.hp-hps' => ['hps'], 'application/vnd.hp-jlyt' => ['jlt'], 'application/vnd.hp-pcl' => ['pcl'], 'application/vnd.hp-pclxl' => ['pclxl'], 'application/vnd.hydrostatix.sof-data' => ['sfd-hdstx'], 'application/vnd.ibm.minipay' => ['mpy'], 'application/vnd.ibm.modcap' => ['afp', 'listafp', 'list3820'], 'application/vnd.ibm.rights-management' => ['irm'], 'application/vnd.ibm.secure-container' => ['sc'], 'application/vnd.iccprofile' => ['icc', 'icm'], 'application/vnd.igloader' => ['igl'], 'application/vnd.immervision-ivp' => ['ivp'], 'application/vnd.immervision-ivu' => ['ivu'], 'application/vnd.insors.igm' => ['igm'], 'application/vnd.intercon.formnet' => ['xpw', 'xpx'], 'application/vnd.intergeo' => ['i2g'], 'application/vnd.intu.qbo' => ['qbo'], 'application/vnd.intu.qfx' => ['qfx'], 'application/vnd.ipunplugged.rcprofile' => ['rcprofile'], 'application/vnd.irepository.package+xml' => ['irp'], 'application/vnd.is-xpr' => ['xpr'], 'application/vnd.isac.fcs' => ['fcs'], 'application/vnd.jam' => ['jam'], 'application/vnd.jcp.javame.midlet-rms' => ['rms'], 'application/vnd.jisp' => ['jisp'], 'application/vnd.joost.joda-archive' => ['joda'], 'application/vnd.kahootz' => ['ktz', 'ktr'], 'application/vnd.kde.karbon' => ['karbon'], 'application/vnd.kde.kchart' => ['chrt'], 'application/vnd.kde.kformula' => ['kfo'], 'application/vnd.kde.kivio' => ['flw'], 'application/vnd.kde.kontour' => ['kon'], 'application/vnd.kde.kpresenter' => ['kpr', 'kpt'], 'application/vnd.kde.kspread' => ['ksp'], 'application/vnd.kde.kword' => ['kwd', 'kwt'], 'application/vnd.kenameaapp' => ['htke'], 'application/vnd.kidspiration' => ['kia'], 'application/vnd.kinar' => ['kne', 'knp'], 'application/vnd.koan' => ['skp', 'skd', 'skt', 'skm'], 'application/vnd.kodak-descriptor' => ['sse'], 'application/vnd.las.las+xml' => ['lasxml'], 'application/vnd.llamagraphics.life-balance.desktop' => ['lbd'], 'application/vnd.llamagraphics.life-balance.exchange+xml' => ['lbe'], 'application/vnd.lotus-1-2-3' => ['123'], 'application/vnd.lotus-approach' => ['apr'], 'application/vnd.lotus-freelance' => ['pre'], 'application/vnd.lotus-notes' => ['nsf'], 'application/vnd.lotus-organizer' => ['org'], 'application/vnd.lotus-screencam' => ['scm'], 'application/vnd.lotus-wordpro' => ['lwp'], 'application/vnd.macports.portpkg' => ['portpkg'], 'application/vnd.mapbox-vector-tile' => ['mvt'], 'application/vnd.mcd' => ['mcd'], 'application/vnd.medcalcdata' => ['mc1'], 'application/vnd.mediastation.cdkey' => ['cdkey'], 'application/vnd.mfer' => ['mwf'], 'application/vnd.mfmp' => ['mfm'], 'application/vnd.micrografx.flo' => ['flo'], 'application/vnd.micrografx.igx' => ['igx'], 'application/vnd.mif' => ['mif'], 'application/vnd.mobius.daf' => ['daf'], 'application/vnd.mobius.dis' => ['dis'], 'application/vnd.mobius.mbk' => ['mbk'], 'application/vnd.mobius.mqy' => ['mqy'], 'application/vnd.mobius.msl' => ['msl'], 'application/vnd.mobius.plc' => ['plc'], 'application/vnd.mobius.txf' => ['txf'], 'application/vnd.mophun.application' => ['mpn'], 'application/vnd.mophun.certificate' => ['mpc'], 'application/vnd.mozilla.xul+xml' => ['xul'], 'application/vnd.ms-artgalry' => ['cil'], 'application/vnd.ms-cab-compressed' => ['cab'], 'application/vnd.ms-excel' => ['xls', 'xlm', 'xla', 'xlc', 'xlt', 'xlw'], 'application/vnd.ms-excel.addin.macroenabled.12' => ['xlam'], 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => ['xlsb'], 'application/vnd.ms-excel.sheet.macroenabled.12' => ['xlsm'], 'application/vnd.ms-excel.template.macroenabled.12' => ['xltm'], 'application/vnd.ms-fontobject' => ['eot'], 'application/vnd.ms-htmlhelp' => ['chm'], 'application/vnd.ms-ims' => ['ims'], 'application/vnd.ms-lrm' => ['lrm'], 'application/vnd.ms-officetheme' => ['thmx'], 'application/vnd.ms-outlook' => ['msg'], 'application/vnd.ms-pki.seccat' => ['cat'], 'application/vnd.ms-pki.stl' => ['stl'], 'application/vnd.ms-powerpoint' => ['ppt', 'pps', 'pot', 'ppa'], 'application/vnd.ms-powerpoint.addin.macroenabled.12' => ['ppam'], 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => ['pptm'], 'application/vnd.ms-powerpoint.slide.macroenabled.12' => ['sldm'], 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => ['ppsm'], 'application/vnd.ms-powerpoint.template.macroenabled.12' => ['potm'], 'application/vnd.ms-project' => ['mpp', 'mpt'], 'application/vnd.ms-word.document.macroenabled.12' => ['docm'], 'application/vnd.ms-word.template.macroenabled.12' => ['dotm'], 'application/vnd.ms-works' => ['wps', 'wks', 'wcm', 'wdb'], 'application/vnd.ms-wpl' => ['wpl'], 'application/vnd.ms-xpsdocument' => ['xps'], 'application/vnd.mseq' => ['mseq'], 'application/vnd.musician' => ['mus'], 'application/vnd.muvee.style' => ['msty'], 'application/vnd.mynfc' => ['taglet'], 'application/vnd.nato.bindingdataobject+xml' => ['bdo'], 'application/vnd.neurolanguage.nlu' => ['nlu'], 'application/vnd.nitf' => ['ntf', 'nitf'], 'application/vnd.noblenet-directory' => ['nnd'], 'application/vnd.noblenet-sealer' => ['nns'], 'application/vnd.noblenet-web' => ['nnw'], 'application/vnd.nokia.n-gage.ac+xml' => ['ac'], 'application/vnd.nokia.n-gage.data' => ['ngdat'], 'application/vnd.nokia.n-gage.symbian.install' => ['n-gage'], 'application/vnd.nokia.radio-preset' => ['rpst'], 'application/vnd.nokia.radio-presets' => ['rpss'], 'application/vnd.novadigm.edm' => ['edm'], 'application/vnd.novadigm.edx' => ['edx'], 'application/vnd.novadigm.ext' => ['ext'], 'application/vnd.oasis.opendocument.chart' => ['odc'], 'application/vnd.oasis.opendocument.chart-template' => ['otc'], 'application/vnd.oasis.opendocument.database' => ['odb'], 'application/vnd.oasis.opendocument.formula' => ['odf'], 'application/vnd.oasis.opendocument.formula-template' => ['odft'], 'application/vnd.oasis.opendocument.graphics' => ['odg'], 'application/vnd.oasis.opendocument.graphics-template' => ['otg'], 'application/vnd.oasis.opendocument.image' => ['odi'], 'application/vnd.oasis.opendocument.image-template' => ['oti'], 'application/vnd.oasis.opendocument.presentation' => ['odp'], 'application/vnd.oasis.opendocument.presentation-template' => ['otp'], 'application/vnd.oasis.opendocument.spreadsheet' => ['ods'], 'application/vnd.oasis.opendocument.spreadsheet-template' => ['ots'], 'application/vnd.oasis.opendocument.text' => ['odt'], 'application/vnd.oasis.opendocument.text-master' => ['odm'], 'application/vnd.oasis.opendocument.text-template' => ['ott'], 'application/vnd.oasis.opendocument.text-web' => ['oth'], 'application/vnd.olpc-sugar' => ['xo'], 'application/vnd.oma.dd2+xml' => ['dd2'], 'application/vnd.openblox.game+xml' => ['obgx'], 'application/vnd.openofficeorg.extension' => ['oxt'], 'application/vnd.openstreetmap.data+xml' => ['osm'], 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => ['pptx'], 'application/vnd.openxmlformats-officedocument.presentationml.slide' => ['sldx'], 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => ['ppsx'], 'application/vnd.openxmlformats-officedocument.presentationml.template' => ['potx'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => ['xlsx'], 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => ['xltx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => ['docx'], 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => ['dotx'], 'application/vnd.osgeo.mapguide.package' => ['mgp'], 'application/vnd.osgi.dp' => ['dp'], 'application/vnd.osgi.subsystem' => ['esa'], 'application/vnd.palm' => ['pdb', 'pqa', 'oprc'], 'application/vnd.pawaafile' => ['paw'], 'application/vnd.pg.format' => ['str'], 'application/vnd.pg.osasli' => ['ei6'], 'application/vnd.picsel' => ['efif'], 'application/vnd.pmi.widget' => ['wg'], 'application/vnd.pocketlearn' => ['plf'], 'application/vnd.powerbuilder6' => ['pbd'], 'application/vnd.previewsystems.box' => ['box'], 'application/vnd.proteus.magazine' => ['mgz'], 'application/vnd.publishare-delta-tree' => ['qps'], 'application/vnd.pvi.ptid1' => ['ptid'], 'application/vnd.pwg-xhtml-print+xml' => ['xhtm'], 'application/vnd.quark.quarkxpress' => ['qxd', 'qxt', 'qwd', 'qwt', 'qxl', 'qxb'], 'application/vnd.rar' => ['rar'], 'application/vnd.realvnc.bed' => ['bed'], 'application/vnd.recordare.musicxml' => ['mxl'], 'application/vnd.recordare.musicxml+xml' => ['musicxml'], 'application/vnd.rig.cryptonote' => ['cryptonote'], 'application/vnd.rim.cod' => ['cod'], 'application/vnd.rn-realmedia' => ['rm'], 'application/vnd.rn-realmedia-vbr' => ['rmvb'], 'application/vnd.route66.link66+xml' => ['link66'], 'application/vnd.sailingtracker.track' => ['st'], 'application/vnd.seemail' => ['see'], 'application/vnd.sema' => ['sema'], 'application/vnd.semd' => ['semd'], 'application/vnd.semf' => ['semf'], 'application/vnd.shana.informed.formdata' => ['ifm'], 'application/vnd.shana.informed.formtemplate' => ['itp'], 'application/vnd.shana.informed.interchange' => ['iif'], 'application/vnd.shana.informed.package' => ['ipk'], 'application/vnd.simtech-mindmapper' => ['twd', 'twds'], 'application/vnd.smaf' => ['mmf'], 'application/vnd.smart.teacher' => ['teacher'], 'application/vnd.software602.filler.form+xml' => ['fo'], 'application/vnd.solent.sdkm+xml' => ['sdkm', 'sdkd'], 'application/vnd.spotfire.dxp' => ['dxp'], 'application/vnd.spotfire.sfs' => ['sfs'], 'application/vnd.stardivision.calc' => ['sdc'], 'application/vnd.stardivision.draw' => ['sda'], 'application/vnd.stardivision.impress' => ['sdd'], 'application/vnd.stardivision.math' => ['smf'], 'application/vnd.stardivision.writer' => ['sdw', 'vor'], 'application/vnd.stardivision.writer-global' => ['sgl'], 'application/vnd.stepmania.package' => ['smzip'], 'application/vnd.stepmania.stepchart' => ['sm'], 'application/vnd.sun.wadl+xml' => ['wadl'], 'application/vnd.sun.xml.calc' => ['sxc'], 'application/vnd.sun.xml.calc.template' => ['stc'], 'application/vnd.sun.xml.draw' => ['sxd'], 'application/vnd.sun.xml.draw.template' => ['std'], 'application/vnd.sun.xml.impress' => ['sxi'], 'application/vnd.sun.xml.impress.template' => ['sti'], 'application/vnd.sun.xml.math' => ['sxm'], 'application/vnd.sun.xml.writer' => ['sxw'], 'application/vnd.sun.xml.writer.global' => ['sxg'], 'application/vnd.sun.xml.writer.template' => ['stw'], 'application/vnd.sus-calendar' => ['sus', 'susp'], 'application/vnd.svd' => ['svd'], 'application/vnd.symbian.install' => ['sis', 'sisx'], 'application/vnd.syncml+xml' => ['xsm'], 'application/vnd.syncml.dm+wbxml' => ['bdm'], 'application/vnd.syncml.dm+xml' => ['xdm'], 'application/vnd.syncml.dmddf+xml' => ['ddf'], 'application/vnd.tao.intent-module-archive' => ['tao'], 'application/vnd.tcpdump.pcap' => ['pcap', 'cap', 'dmp'], 'application/vnd.tmobile-livetv' => ['tmo'], 'application/vnd.trid.tpt' => ['tpt'], 'application/vnd.triscape.mxs' => ['mxs'], 'application/vnd.trueapp' => ['tra'], 'application/vnd.ufdl' => ['ufd', 'ufdl'], 'application/vnd.uiq.theme' => ['utz'], 'application/vnd.umajin' => ['umj'], 'application/vnd.unity' => ['unityweb'], 'application/vnd.uoml+xml' => ['uoml', 'uo'], 'application/vnd.vcx' => ['vcx'], 'application/vnd.visio' => ['vsd', 'vst', 'vss', 'vsw'], 'application/vnd.visionary' => ['vis'], 'application/vnd.vsf' => ['vsf'], 'application/vnd.wap.wbxml' => ['wbxml'], 'application/vnd.wap.wmlc' => ['wmlc'], 'application/vnd.wap.wmlscriptc' => ['wmlsc'], 'application/vnd.webturbo' => ['wtb'], 'application/vnd.wolfram.player' => ['nbp'], 'application/vnd.wordperfect' => ['wpd'], 'application/vnd.wqd' => ['wqd'], 'application/vnd.wt.stf' => ['stf'], 'application/vnd.xara' => ['xar'], 'application/vnd.xfdl' => ['xfdl'], 'application/vnd.yamaha.hv-dic' => ['hvd'], 'application/vnd.yamaha.hv-script' => ['hvs'], 'application/vnd.yamaha.hv-voice' => ['hvp'], 'application/vnd.yamaha.openscoreformat' => ['osf'], 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => ['osfpvg'], 'application/vnd.yamaha.smaf-audio' => ['saf'], 'application/vnd.yamaha.smaf-phrase' => ['spf'], 'application/vnd.yellowriver-custom-menu' => ['cmp'], 'application/vnd.zul' => ['zir', 'zirz'], 'application/vnd.zzazz.deck+xml' => ['zaz'], 'application/voicexml+xml' => ['vxml'], 'application/wasm' => ['wasm'], 'application/watcherinfo+xml' => ['wif'], 'application/widget' => ['wgt'], 'application/winhlp' => ['hlp'], 'application/wsdl+xml' => ['wsdl'], 'application/wspolicy+xml' => ['wspolicy'], 'application/x-7z-compressed' => ['7z', '7zip'], 'application/x-abiword' => ['abw'], 'application/x-ace-compressed' => ['ace'], 'application/x-apple-diskimage' => ['dmg'], 'application/x-arj' => ['arj'], 'application/x-authorware-bin' => ['aab', 'x32', 'u32', 'vox'], 'application/x-authorware-map' => ['aam'], 'application/x-authorware-seg' => ['aas'], 'application/x-bcpio' => ['bcpio'], 'application/x-bdoc' => ['bdoc'], 'application/x-bittorrent' => ['torrent'], 'application/x-blorb' => ['blb', 'blorb'], 'application/x-bzip' => ['bz'], 'application/x-bzip2' => ['bz2', 'boz'], 'application/x-cbr' => ['cbr', 'cba', 'cbt', 'cbz', 'cb7'], 'application/x-cdlink' => ['vcd'], 'application/x-cfs-compressed' => ['cfs'], 'application/x-chat' => ['chat'], 'application/x-chess-pgn' => ['pgn'], 'application/x-chrome-extension' => ['crx'], 'application/x-cocoa' => ['cco'], 'application/x-conference' => ['nsc'], 'application/x-cpio' => ['cpio'], 'application/x-csh' => ['csh'], 'application/x-debian-package' => ['deb', 'udeb'], 'application/x-dgc-compressed' => ['dgc'], 'application/x-director' => ['dir', 'dcr', 'dxr', 'cst', 'cct', 'cxt', 'w3d', 'fgd', 'swa'], 'application/x-doom' => ['wad'], 'application/x-dtbncx+xml' => ['ncx'], 'application/x-dtbook+xml' => ['dtb'], 'application/x-dtbresource+xml' => ['res'], 'application/x-dvi' => ['dvi'], 'application/x-envoy' => ['evy'], 'application/x-eva' => ['eva'], 'application/x-font-bdf' => ['bdf'], 'application/x-font-ghostscript' => ['gsf'], 'application/x-font-linux-psf' => ['psf'], 'application/x-font-pcf' => ['pcf'], 'application/x-font-snf' => ['snf'], 'application/x-font-type1' => ['pfa', 'pfb', 'pfm', 'afm'], 'application/x-freearc' => ['arc'], 'application/x-futuresplash' => ['spl'], 'application/x-gca-compressed' => ['gca'], 'application/x-glulx' => ['ulx'], 'application/x-gnumeric' => ['gnumeric'], 'application/x-gramps-xml' => ['gramps'], 'application/x-gtar' => ['gtar'], 'application/x-hdf' => ['hdf'], 'application/x-httpd-php' => ['php', 'php4', 'php3', 'phtml'], 'application/x-install-instructions' => ['install'], 'application/x-iso9660-image' => ['iso'], 'application/x-iwork-keynote-sffkey' => ['key'], 'application/x-iwork-numbers-sffnumbers' => ['numbers'], 'application/x-iwork-pages-sffpages' => ['pages'], 'application/x-java-archive-diff' => ['jardiff'], 'application/x-java-jnlp-file' => ['jnlp'], 'application/x-keepass2' => ['kdbx'], 'application/x-latex' => ['latex'], 'application/x-lua-bytecode' => ['luac'], 'application/x-lzh-compressed' => ['lzh', 'lha'], 'application/x-makeself' => ['run'], 'application/x-mie' => ['mie'], 'application/x-mobipocket-ebook' => ['prc', 'mobi'], 'application/x-ms-application' => ['application'], 'application/x-ms-shortcut' => ['lnk'], 'application/x-ms-wmd' => ['wmd'], 'application/x-ms-wmz' => ['wmz'], 'application/x-ms-xbap' => ['xbap'], 'application/x-msaccess' => ['mdb'], 'application/x-msbinder' => ['obd'], 'application/x-mscardfile' => ['crd'], 'application/x-msclip' => ['clp'], 'application/x-msdos-program' => ['exe'], 'application/x-msdownload' => ['exe', 'dll', 'com', 'bat', 'msi'], 'application/x-msmediaview' => ['mvb', 'm13', 'm14'], 'application/x-msmetafile' => ['wmf', 'wmz', 'emf', 'emz'], 'application/x-msmoney' => ['mny'], 'application/x-mspublisher' => ['pub'], 'application/x-msschedule' => ['scd'], 'application/x-msterminal' => ['trm'], 'application/x-mswrite' => ['wri'], 'application/x-netcdf' => ['nc', 'cdf'], 'application/x-ns-proxy-autoconfig' => ['pac'], 'application/x-nzb' => ['nzb'], 'application/x-perl' => ['pl', 'pm'], 'application/x-pilot' => ['prc', 'pdb'], 'application/x-pkcs12' => ['p12', 'pfx'], 'application/x-pkcs7-certificates' => ['p7b', 'spc'], 'application/x-pkcs7-certreqresp' => ['p7r'], 'application/x-rar-compressed' => ['rar'], 'application/x-redhat-package-manager' => ['rpm'], 'application/x-research-info-systems' => ['ris'], 'application/x-sea' => ['sea'], 'application/x-sh' => ['sh'], 'application/x-shar' => ['shar'], 'application/x-shockwave-flash' => ['swf'], 'application/x-silverlight-app' => ['xap'], 'application/x-sql' => ['sql'], 'application/x-stuffit' => ['sit'], 'application/x-stuffitx' => ['sitx'], 'application/x-subrip' => ['srt'], 'application/x-sv4cpio' => ['sv4cpio'], 'application/x-sv4crc' => ['sv4crc'], 'application/x-t3vm-image' => ['t3'], 'application/x-tads' => ['gam'], 'application/x-tar' => ['tar', 'tgz'], 'application/x-tcl' => ['tcl', 'tk'], 'application/x-tex' => ['tex'], 'application/x-tex-tfm' => ['tfm'], 'application/x-texinfo' => ['texinfo', 'texi'], 'application/x-tgif' => ['obj'], 'application/x-ustar' => ['ustar'], 'application/x-virtualbox-hdd' => ['hdd'], 'application/x-virtualbox-ova' => ['ova'], 'application/x-virtualbox-ovf' => ['ovf'], 'application/x-virtualbox-vbox' => ['vbox'], 'application/x-virtualbox-vbox-extpack' => ['vbox-extpack'], 'application/x-virtualbox-vdi' => ['vdi'], 'application/x-virtualbox-vhd' => ['vhd'], 'application/x-virtualbox-vmdk' => ['vmdk'], 'application/x-wais-source' => ['src'], 'application/x-web-app-manifest+json' => ['webapp'], 'application/x-x509-ca-cert' => ['der', 'crt', 'pem'], 'application/x-xfig' => ['fig'], 'application/x-xliff+xml' => ['xlf'], 'application/x-xpinstall' => ['xpi'], 'application/x-xz' => ['xz'], 'application/x-zmachine' => ['z1', 'z2', 'z3', 'z4', 'z5', 'z6', 'z7', 'z8'], 'application/xaml+xml' => ['xaml'], 'application/xcap-att+xml' => ['xav'], 'application/xcap-caps+xml' => ['xca'], 'application/xcap-diff+xml' => ['xdf'], 'application/xcap-el+xml' => ['xel'], 'application/xcap-ns+xml' => ['xns'], 'application/xenc+xml' => ['xenc'], 'application/xfdf' => ['xfdf'], 'application/xhtml+xml' => ['xhtml', 'xht'], 'application/xliff+xml' => ['xlf'], 'application/xml' => ['xml', 'xsl', 'xsd', 'rng'], 'application/xml-dtd' => ['dtd'], 'application/xop+xml' => ['xop'], 'application/xproc+xml' => ['xpl'], 'application/xslt+xml' => ['xsl', 'xslt'], 'application/xspf+xml' => ['xspf'], 'application/xv+xml' => ['mxml', 'xhvml', 'xvml', 'xvm'], 'application/yang' => ['yang'], 'application/yin+xml' => ['yin'], 'application/zip' => ['zip'], 'audio/3gpp' => ['3gpp'], 'audio/aac' => ['adts', 'aac'], 'audio/adpcm' => ['adp'], 'audio/amr' => ['amr'], 'audio/basic' => ['au', 'snd'], 'audio/midi' => ['mid', 'midi', 'kar', 'rmi'], 'audio/mobile-xmf' => ['mxmf'], 'audio/mp3' => ['mp3'], 'audio/mp4' => ['m4a', 'mp4a'], 'audio/mpeg' => ['mpga', 'mp2', 'mp2a', 'mp3', 'm2a', 'm3a'], 'audio/ogg' => ['oga', 'ogg', 'spx', 'opus'], 'audio/s3m' => ['s3m'], 'audio/silk' => ['sil'], 'audio/vnd.dece.audio' => ['uva', 'uvva'], 'audio/vnd.digital-winds' => ['eol'], 'audio/vnd.dra' => ['dra'], 'audio/vnd.dts' => ['dts'], 'audio/vnd.dts.hd' => ['dtshd'], 'audio/vnd.lucent.voice' => ['lvp'], 'audio/vnd.ms-playready.media.pya' => ['pya'], 'audio/vnd.nuera.ecelp4800' => ['ecelp4800'], 'audio/vnd.nuera.ecelp7470' => ['ecelp7470'], 'audio/vnd.nuera.ecelp9600' => ['ecelp9600'], 'audio/vnd.rip' => ['rip'], 'audio/wav' => ['wav'], 'audio/wave' => ['wav'], 'audio/webm' => ['weba'], 'audio/x-aac' => ['aac'], 'audio/x-aiff' => ['aif', 'aiff', 'aifc'], 'audio/x-caf' => ['caf'], 'audio/x-flac' => ['flac'], 'audio/x-m4a' => ['m4a'], 'audio/x-matroska' => ['mka'], 'audio/x-mpegurl' => ['m3u'], 'audio/x-ms-wax' => ['wax'], 'audio/x-ms-wma' => ['wma'], 'audio/x-pn-realaudio' => ['ram', 'ra', 'rm'], 'audio/x-pn-realaudio-plugin' => ['rmp', 'rpm'], 'audio/x-realaudio' => ['ra'], 'audio/x-wav' => ['wav'], 'audio/xm' => ['xm'], 'chemical/x-cdx' => ['cdx'], 'chemical/x-cif' => ['cif'], 'chemical/x-cmdf' => ['cmdf'], 'chemical/x-cml' => ['cml'], 'chemical/x-csml' => ['csml'], 'chemical/x-xyz' => ['xyz'], 'font/collection' => ['ttc'], 'font/otf' => ['otf'], 'font/ttf' => ['ttf'], 'font/woff' => ['woff'], 'font/woff2' => ['woff2'], 'image/aces' => ['exr'], 'image/apng' => ['apng'], 'image/avci' => ['avci'], 'image/avcs' => ['avcs'], 'image/avif' => ['avif'], 'image/bmp' => ['bmp', 'dib'], 'image/cgm' => ['cgm'], 'image/dicom-rle' => ['drle'], 'image/dpx' => ['dpx'], 'image/emf' => ['emf'], 'image/fits' => ['fits'], 'image/g3fax' => ['g3'], 'image/gif' => ['gif'], 'image/heic' => ['heic'], 'image/heic-sequence' => ['heics'], 'image/heif' => ['heif'], 'image/heif-sequence' => ['heifs'], 'image/hej2k' => ['hej2'], 'image/hsj2' => ['hsj2'], 'image/ief' => ['ief'], 'image/jls' => ['jls'], 'image/jp2' => ['jp2', 'jpg2'], 'image/jpeg' => ['jpeg', 'jpg', 'jpe'], 'image/jph' => ['jph'], 'image/jphc' => ['jhc'], 'image/jpm' => ['jpm', 'jpgm'], 'image/jpx' => ['jpx', 'jpf'], 'image/jxl' => ['jxl'], 'image/jxr' => ['jxr'], 'image/jxra' => ['jxra'], 'image/jxrs' => ['jxrs'], 'image/jxs' => ['jxs'], 'image/jxsc' => ['jxsc'], 'image/jxsi' => ['jxsi'], 'image/jxss' => ['jxss'], 'image/ktx' => ['ktx'], 'image/ktx2' => ['ktx2'], 'image/png' => ['png'], 'image/prs.btif' => ['btif', 'btf'], 'image/prs.pti' => ['pti'], 'image/sgi' => ['sgi'], 'image/svg+xml' => ['svg', 'svgz'], 'image/t38' => ['t38'], 'image/tiff' => ['tif', 'tiff'], 'image/tiff-fx' => ['tfx'], 'image/vnd.adobe.photoshop' => ['psd'], 'image/vnd.airzip.accelerator.azv' => ['azv'], 'image/vnd.dece.graphic' => ['uvi', 'uvvi', 'uvg', 'uvvg'], 'image/vnd.djvu' => ['djvu', 'djv'], 'image/vnd.dvb.subtitle' => ['sub'], 'image/vnd.dwg' => ['dwg'], 'image/vnd.dxf' => ['dxf'], 'image/vnd.fastbidsheet' => ['fbs'], 'image/vnd.fpx' => ['fpx'], 'image/vnd.fst' => ['fst'], 'image/vnd.fujixerox.edmics-mmr' => ['mmr'], 'image/vnd.fujixerox.edmics-rlc' => ['rlc'], 'image/vnd.microsoft.icon' => ['ico'], 'image/vnd.ms-dds' => ['dds'], 'image/vnd.ms-modi' => ['mdi'], 'image/vnd.ms-photo' => ['wdp'], 'image/vnd.net-fpx' => ['npx'], 'image/vnd.pco.b16' => ['b16'], 'image/vnd.tencent.tap' => ['tap'], 'image/vnd.valve.source.texture' => ['vtf'], 'image/vnd.wap.wbmp' => ['wbmp'], 'image/vnd.xiff' => ['xif'], 'image/vnd.zbrush.pcx' => ['pcx'], 'image/webp' => ['webp'], 'image/wmf' => ['wmf'], 'image/x-3ds' => ['3ds'], 'image/x-cmu-raster' => ['ras'], 'image/x-cmx' => ['cmx'], 'image/x-freehand' => ['fh', 'fhc', 'fh4', 'fh5', 'fh7'], 'image/x-icon' => ['ico'], 'image/x-jng' => ['jng'], 'image/x-mrsid-image' => ['sid'], 'image/x-ms-bmp' => ['bmp'], 'image/x-pcx' => ['pcx'], 'image/x-pict' => ['pic', 'pct'], 'image/x-portable-anymap' => ['pnm'], 'image/x-portable-bitmap' => ['pbm'], 'image/x-portable-graymap' => ['pgm'], 'image/x-portable-pixmap' => ['ppm'], 'image/x-rgb' => ['rgb'], 'image/x-tga' => ['tga'], 'image/x-xbitmap' => ['xbm'], 'image/x-xpixmap' => ['xpm'], 'image/x-xwindowdump' => ['xwd'], 'message/disposition-notification' => ['disposition-notification'], 'message/global' => ['u8msg'], 'message/global-delivery-status' => ['u8dsn'], 'message/global-disposition-notification' => ['u8mdn'], 'message/global-headers' => ['u8hdr'], 'message/rfc822' => ['eml', 'mime'], 'message/vnd.wfa.wsc' => ['wsc'], 'model/3mf' => ['3mf'], 'model/gltf+json' => ['gltf'], 'model/gltf-binary' => ['glb'], 'model/iges' => ['igs', 'iges'], 'model/jt' => ['jt'], 'model/mesh' => ['msh', 'mesh', 'silo'], 'model/mtl' => ['mtl'], 'model/obj' => ['obj'], 'model/prc' => ['prc'], 'model/step+xml' => ['stpx'], 'model/step+zip' => ['stpz'], 'model/step-xml+zip' => ['stpxz'], 'model/stl' => ['stl'], 'model/u3d' => ['u3d'], 'model/vnd.bary' => ['bary'], 'model/vnd.cld' => ['cld'], 'model/vnd.collada+xml' => ['dae'], 'model/vnd.dwf' => ['dwf'], 'model/vnd.gdl' => ['gdl'], 'model/vnd.gtw' => ['gtw'], 'model/vnd.mts' => ['mts'], 'model/vnd.opengex' => ['ogex'], 'model/vnd.parasolid.transmit.binary' => ['x_b'], 'model/vnd.parasolid.transmit.text' => ['x_t'], 'model/vnd.pytha.pyox' => ['pyo', 'pyox'], 'model/vnd.sap.vds' => ['vds'], 'model/vnd.usda' => ['usda'], 'model/vnd.usdz+zip' => ['usdz'], 'model/vnd.valve.source.compiled-map' => ['bsp'], 'model/vnd.vtu' => ['vtu'], 'model/vrml' => ['wrl', 'vrml'], 'model/x3d+binary' => ['x3db', 'x3dbz'], 'model/x3d+fastinfoset' => ['x3db'], 'model/x3d+vrml' => ['x3dv', 'x3dvz'], 'model/x3d+xml' => ['x3d', 'x3dz'], 'model/x3d-vrml' => ['x3dv'], 'text/cache-manifest' => ['appcache', 'manifest'], 'text/calendar' => ['ics', 'ifb'], 'text/coffeescript' => ['coffee', 'litcoffee'], 'text/css' => ['css'], 'text/csv' => ['csv'], 'text/html' => ['html', 'htm', 'shtml'], 'text/jade' => ['jade'], 'text/javascript' => ['js', 'mjs'], 'text/jsx' => ['jsx'], 'text/less' => ['less'], 'text/markdown' => ['md', 'markdown'], 'text/mathml' => ['mml'], 'text/mdx' => ['mdx'], 'text/n3' => ['n3'], 'text/plain' => ['txt', 'text', 'conf', 'def', 'list', 'log', 'in', 'ini', 'm3u'], 'text/prs.lines.tag' => ['dsc'], 'text/richtext' => ['rtx'], 'text/rtf' => ['rtf'], 'text/sgml' => ['sgml', 'sgm'], 'text/shex' => ['shex'], 'text/slim' => ['slim', 'slm'], 'text/spdx' => ['spdx'], 'text/stylus' => ['stylus', 'styl'], 'text/tab-separated-values' => ['tsv'], 'text/troff' => ['t', 'tr', 'roff', 'man', 'me', 'ms'], 'text/turtle' => ['ttl'], 'text/uri-list' => ['uri', 'uris', 'urls'], 'text/vcard' => ['vcard'], 'text/vnd.curl' => ['curl'], 'text/vnd.curl.dcurl' => ['dcurl'], 'text/vnd.curl.mcurl' => ['mcurl'], 'text/vnd.curl.scurl' => ['scurl'], 'text/vnd.dvb.subtitle' => ['sub'], 'text/vnd.familysearch.gedcom' => ['ged'], 'text/vnd.fly' => ['fly'], 'text/vnd.fmi.flexstor' => ['flx'], 'text/vnd.graphviz' => ['gv'], 'text/vnd.in3d.3dml' => ['3dml'], 'text/vnd.in3d.spot' => ['spot'], 'text/vnd.sun.j2me.app-descriptor' => ['jad'], 'text/vnd.wap.wml' => ['wml'], 'text/vnd.wap.wmlscript' => ['wmls'], 'text/vtt' => ['vtt'], 'text/wgsl' => ['wgsl'], 'text/x-asm' => ['s', 'asm'], 'text/x-c' => ['c', 'cc', 'cxx', 'cpp', 'h', 'hh', 'dic'], 'text/x-component' => ['htc'], 'text/x-fortran' => ['f', 'for', 'f77', 'f90'], 'text/x-handlebars-template' => ['hbs'], 'text/x-java-source' => ['java'], 'text/x-lua' => ['lua'], 'text/x-markdown' => ['mkd'], 'text/x-nfo' => ['nfo'], 'text/x-opml' => ['opml'], 'text/x-org' => ['org'], 'text/x-pascal' => ['p', 'pas'], 'text/x-processing' => ['pde'], 'text/x-sass' => ['sass'], 'text/x-scss' => ['scss'], 'text/x-setext' => ['etx'], 'text/x-sfv' => ['sfv'], 'text/x-suse-ymp' => ['ymp'], 'text/x-uuencode' => ['uu'], 'text/x-vcalendar' => ['vcs'], 'text/x-vcard' => ['vcf'], 'text/xml' => ['xml'], 'text/yaml' => ['yaml', 'yml'], 'video/3gpp' => ['3gp', '3gpp'], 'video/3gpp2' => ['3g2'], 'video/h261' => ['h261'], 'video/h263' => ['h263'], 'video/h264' => ['h264'], 'video/iso.segment' => ['m4s'], 'video/jpeg' => ['jpgv'], 'video/jpm' => ['jpm', 'jpgm'], 'video/mj2' => ['mj2', 'mjp2'], 'video/mp2t' => ['ts', 'm2t', 'm2ts', 'mts'], 'video/mp4' => ['mp4', 'mp4v', 'mpg4', 'f4v'], 'video/mpeg' => ['mpeg', 'mpg', 'mpe', 'm1v', 'm2v'], 'video/ogg' => ['ogv'], 'video/quicktime' => ['qt', 'mov'], 'video/vnd.dece.hd' => ['uvh', 'uvvh'], 'video/vnd.dece.mobile' => ['uvm', 'uvvm'], 'video/vnd.dece.pd' => ['uvp', 'uvvp'], 'video/vnd.dece.sd' => ['uvs', 'uvvs'], 'video/vnd.dece.video' => ['uvv', 'uvvv'], 'video/vnd.dvb.file' => ['dvb'], 'video/vnd.fvt' => ['fvt'], 'video/vnd.mpegurl' => ['mxu', 'm4u'], 'video/vnd.ms-playready.media.pyv' => ['pyv'], 'video/vnd.uvvu.mp4' => ['uvu', 'uvvu'], 'video/vnd.vivo' => ['viv'], 'video/webm' => ['webm'], 'video/x-f4v' => ['f4v'], 'video/x-fli' => ['fli'], 'video/x-flv' => ['flv'], 'video/x-m4v' => ['m4v'], 'video/x-matroska' => ['mkv', 'mk3d', 'mks'], 'video/x-mng' => ['mng'], 'video/x-ms-asf' => ['asf', 'asx'], 'video/x-ms-vob' => ['vob'], 'video/x-ms-wm' => ['wm'], 'video/x-ms-wmv' => ['wmv'], 'video/x-ms-wmx' => ['wmx'], 'video/x-ms-wvx' => ['wvx'], 'video/x-msvideo' => ['avi'], 'video/x-sgi-movie' => ['movie'], 'video/x-smv' => ['smv'], 'x-conference/x-cooltalk' => ['ice'], 'application/x-photoshop' => ['psd'], 'application/smil' => ['smi', 'smil'], 'application/powerpoint' => ['ppt'], 'application/vnd.ms-powerpoint.addin.macroEnabled.12' => ['ppam'], 'application/vnd.ms-powerpoint.presentation.macroEnabled.12' => ['pptm', 'potm'], 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12' => ['ppsm'], 'application/wbxml' => ['wbxml'], 'application/wmlc' => ['wmlc'], 'application/x-httpd-php-source' => ['phps'], 'application/x-compress' => ['z'], 'application/x-rar' => ['rar'], 'video/vnd.rn-realvideo' => ['rv'], 'application/vnd.ms-word.template.macroEnabled.12' => ['docm', 'dotm'], 'application/vnd.ms-excel.sheet.macroEnabled.12' => ['xlsm'], 'application/vnd.ms-excel.template.macroEnabled.12' => ['xltm'], 'application/vnd.ms-excel.addin.macroEnabled.12' => ['xlam'], 'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => ['xlsb'], 'application/excel' => ['xl'], 'application/x-x509-user-cert' => ['pem'], 'application/x-pkcs10' => ['p10'], 'application/x-pkcs7-signature' => ['p7a'], 'application/pgp' => ['pgp'], 'application/gpg-keys' => ['gpg'], 'application/x-pkcs7' => ['rsa'], 'video/3gp' => ['3gp'], 'audio/acc' => ['aac'], 'application/vnd.mpegurl' => ['m4u'], 'application/videolan' => ['vlc'], 'audio/x-au' => ['au'], 'audio/ac3' => ['ac3'], 'text/x-scriptzsh' => ['zsh'], 'application/cdr' => ['cdr'], 'application/STEP' => ['step', 'stp'], 'application/x-ndjson' => ['ndjson'], 'application/braille' => ['brf'], ]; public function lookupMimeType(string $extension): ?string { return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null; } public function lookupExtension(string $mimetype): ?string { return self::EXTENSIONS_FOR_MIME_TIMES[$mimetype][0] ?? null; } /** * @return string[] */ public function lookupAllExtensions(string $mimetype): array { return self::EXTENSIONS_FOR_MIME_TIMES[$mimetype] ?? []; } } mime-type-detection/LICENSE 0000644 00000002047 15021222733 0011433 0 ustar 00 Copyright (c) 2013-2023 Frank de Jonge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. glide/composer.json 0000644 00000002154 15021222733 0010351 0 ustar 00 { "name": "league/glide", "description": "Wonderfully easy on-demand image manipulation library with an HTTP based API.", "keywords": [ "league", "image", "processing", "manipulation", "editing", "gd", "imagemagick", "imagick" ], "homepage": "http://glide.thephpleague.com", "license": "MIT", "authors": [ { "name": "Jonathan Reinink", "email": "jonathan@reinink.ca", "homepage": "http://reinink.ca" }, { "name": "Titouan Galopin", "email": "galopintitouan@gmail.com", "homepage": "https://titouangalopin.com" } ], "require": { "php": "^7.2|^8.0", "intervention/image": "^2.7", "league/flysystem": "^2.0|^3.0", "psr/http-message": "^1.0|^2.0" }, "require-dev": { "mockery/mockery": "^1.3.3", "phpunit/phpunit": "^8.5|^9.0", "phpunit/php-token-stream": "^3.1|^4.0" }, "autoload": { "psr-4": { "League\\Glide\\": "src/" } } } glide/psalm-baseline.xml 0000644 00000000430 15021222733 0011240 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <files psalm-version="dev-master@"> <file src="src/ServerFactory.php"> <UndefinedClass occurrences="3"> <code>Filesystem</code> <code>Filesystem</code> <code>Filesystem</code> </UndefinedClass> </file> </files> glide/src/Api/Api.php 0000644 00000004640 15021222733 0010353 0 ustar 00 <?php namespace League\Glide\Api; use Intervention\Image\ImageManager; use InvalidArgumentException; use League\Glide\Manipulators\ManipulatorInterface; class Api implements ApiInterface { /** * Intervention image manager. * * @var ImageManager */ protected $imageManager; /** * Collection of manipulators. * * @var array */ protected $manipulators; /** * Create API instance. * * @param ImageManager $imageManager Intervention image manager. * @param array $manipulators Collection of manipulators. */ public function __construct(ImageManager $imageManager, array $manipulators) { $this->setImageManager($imageManager); $this->setManipulators($manipulators); } /** * Set the image manager. * * @param ImageManager $imageManager Intervention image manager. * * @return void */ public function setImageManager(ImageManager $imageManager) { $this->imageManager = $imageManager; } /** * Get the image manager. * * @return ImageManager Intervention image manager. */ public function getImageManager() { return $this->imageManager; } /** * Set the manipulators. * * @param array $manipulators Collection of manipulators. * * @return void */ public function setManipulators(array $manipulators) { foreach ($manipulators as $manipulator) { if (!($manipulator instanceof ManipulatorInterface)) { throw new InvalidArgumentException('Not a valid manipulator.'); } } $this->manipulators = $manipulators; } /** * Get the manipulators. * * @return array Collection of manipulators. */ public function getManipulators() { return $this->manipulators; } /** * Perform image manipulations. * * @param string $source Source image binary data. * @param array $params The manipulation params. * * @return string Manipulated image binary data. */ public function run($source, array $params) { $image = $this->imageManager->make($source); foreach ($this->manipulators as $manipulator) { $manipulator->setParams($params); $image = $manipulator->run($image); } return $image->getEncoded(); } } glide/src/Api/ApiInterface.php 0000644 00000000524 15021222733 0012171 0 ustar 00 <?php namespace League\Glide\Api; interface ApiInterface { /** * Perform image manipulations. * * @param string $source Source image binary data. * @param array $params The manipulation params. * * @return string Manipulated image binary data. */ public function run($source, array $params); } glide/src/ServerFactory.php 0000644 00000020673 15021222733 0011733 0 ustar 00 <?php namespace League\Glide; use Intervention\Image\ImageManager; use InvalidArgumentException; use League\Flysystem\Filesystem; use League\Flysystem\FilesystemOperator; use League\Flysystem\Local\LocalFilesystemAdapter; use League\Glide\Api\Api; use League\Glide\Manipulators\Background; use League\Glide\Manipulators\Blur; use League\Glide\Manipulators\Border; use League\Glide\Manipulators\Brightness; use League\Glide\Manipulators\Contrast; use League\Glide\Manipulators\Crop; use League\Glide\Manipulators\Encode; use League\Glide\Manipulators\Filter; use League\Glide\Manipulators\Flip; use League\Glide\Manipulators\Gamma; use League\Glide\Manipulators\Orientation; use League\Glide\Manipulators\Pixelate; use League\Glide\Manipulators\Sharpen; use League\Glide\Manipulators\Size; use League\Glide\Manipulators\Watermark; use League\Glide\Responses\ResponseFactoryInterface; class ServerFactory { /** * Configuration parameters. * * @var array */ protected $config; /** * Create ServerFactory instance. * * @param array $config Configuration parameters. */ public function __construct(array $config = []) { $this->config = $config; } /** * Get configured server. * * @return Server Configured Glide server. */ public function getServer() { $server = new Server( $this->getSource(), $this->getCache(), $this->getApi() ); $server->setSourcePathPrefix($this->getSourcePathPrefix() ?: ''); $server->setCachePathPrefix($this->getCachePathPrefix() ?: ''); $server->setGroupCacheInFolders($this->getGroupCacheInFolders()); $server->setCacheWithFileExtensions($this->getCacheWithFileExtensions()); $server->setDefaults($this->getDefaults()); $server->setPresets($this->getPresets()); $server->setBaseUrl($this->getBaseUrl() ?: ''); $server->setResponseFactory($this->getResponseFactory()); $server->setCachePathCallable($this->getCachePathCallable()); if ($this->getTempDir()) { $server->setTempDir($this->getTempDir()); } return $server; } /** * Get source file system. * * @return FilesystemOperator Source file system. */ public function getSource() { if (!isset($this->config['source'])) { throw new InvalidArgumentException('A "source" file system must be set.'); } if (is_string($this->config['source'])) { return new Filesystem( new LocalFilesystemAdapter($this->config['source']) ); } return $this->config['source']; } /** * Get source path prefix. * * @return string|null Source path prefix. */ public function getSourcePathPrefix() { if (isset($this->config['source_path_prefix'])) { return $this->config['source_path_prefix']; } } /** * Get cache file system. * * @return FilesystemOperator Cache file system. */ public function getCache() { if (!isset($this->config['cache'])) { throw new InvalidArgumentException('A "cache" file system must be set.'); } if (is_string($this->config['cache'])) { return new Filesystem( new LocalFilesystemAdapter($this->config['cache']) ); } return $this->config['cache']; } /** * Get cache path prefix. * * @return string|null Cache path prefix. */ public function getCachePathPrefix() { if (isset($this->config['cache_path_prefix'])) { return $this->config['cache_path_prefix']; } } /** * Get temporary EXIF data directory. * * @return string */ public function getTempDir() { if (isset($this->config['temp_dir'])) { return $this->config['temp_dir']; } } /** * Get cache path callable. * * @return \Closure|null Cache path callable. */ public function getCachePathCallable() { return $this->config['cache_path_callable'] ?? null; } /** * Get the group cache in folders setting. * * @return bool Whether to group cache in folders. */ public function getGroupCacheInFolders() { if (isset($this->config['group_cache_in_folders'])) { return $this->config['group_cache_in_folders']; } return true; } /** * Get the cache with file extensions setting. * * @return bool Whether to cache with file extensions. */ public function getCacheWithFileExtensions() { if (isset($this->config['cache_with_file_extensions'])) { return $this->config['cache_with_file_extensions']; } return false; } /** * Get watermarks file system. * * @return FilesystemOperator|null Watermarks file system. */ public function getWatermarks() { if (!isset($this->config['watermarks'])) { return; } if (is_string($this->config['watermarks'])) { return new Filesystem( new LocalFilesystemAdapter($this->config['watermarks']) ); } return $this->config['watermarks']; } /** * Get watermarks path prefix. * * @return string|null Watermarks path prefix. */ public function getWatermarksPathPrefix() { if (isset($this->config['watermarks_path_prefix'])) { return $this->config['watermarks_path_prefix']; } } /** * Get image manipulation API. * * @return Api Image manipulation API. */ public function getApi() { return new Api( $this->getImageManager(), $this->getManipulators() ); } /** * Get Intervention image manager. * * @return ImageManager Intervention image manager. */ public function getImageManager() { $driver = 'gd'; if (isset($this->config['driver'])) { $driver = $this->config['driver']; } return new ImageManager([ 'driver' => $driver, ]); } /** * Get image manipulators. * * @return array Image manipulators. */ public function getManipulators() { return [ new Orientation(), new Crop(), new Size($this->getMaxImageSize()), new Brightness(), new Contrast(), new Gamma(), new Sharpen(), new Filter(), new Flip(), new Blur(), new Pixelate(), new Watermark($this->getWatermarks(), $this->getWatermarksPathPrefix() ?: ''), new Background(), new Border(), new Encode(), ]; } /** * Get maximum image size. * * @return int|null Maximum image size. */ public function getMaxImageSize() { if (isset($this->config['max_image_size'])) { return $this->config['max_image_size']; } } /** * Get default image manipulations. * * @return array Default image manipulations. */ public function getDefaults() { if (isset($this->config['defaults'])) { return $this->config['defaults']; } return []; } /** * Get preset image manipulations. * * @return array Preset image manipulations. */ public function getPresets() { if (isset($this->config['presets'])) { return $this->config['presets']; } return []; } /** * Get base URL. * * @return string|null Base URL. */ public function getBaseUrl() { if (isset($this->config['base_url'])) { return $this->config['base_url']; } } /** * Get response factory. * * @return ResponseFactoryInterface|null Response factory. */ public function getResponseFactory() { if (isset($this->config['response'])) { return $this->config['response']; } } /** * Create configured server. * * @param array $config Configuration parameters. * * @return Server Configured server. */ public static function create(array $config = []) { return (new self($config))->getServer(); } } glide/src/Manipulators/Flip.php 0000644 00000001456 15021222733 0012503 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $flip */ class Flip extends BaseManipulator { /** * Perform flip image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { if ($flip = $this->getFlip()) { if ('both' === $flip) { return $image->flip('h')->flip('v'); } return $image->flip($flip); } return $image; } /** * Resolve flip. * * @return string|null The resolved flip. */ public function getFlip() { if (in_array($this->flip, ['h', 'v', 'both'], true)) { return $this->flip; } } } glide/src/Manipulators/Gamma.php 0000644 00000001553 15021222733 0012631 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string|null $gam */ class Gamma extends BaseManipulator { /** * Perform gamma image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $gamma = $this->getGamma(); if ($gamma) { $image->gamma($gamma); } return $image; } /** * Resolve gamma amount. * * @return float|null The resolved gamma amount. */ public function getGamma() { if (null === $this->gam || !preg_match('/^[0-9]\.*[0-9]*$/', $this->gam)) { return; } if ($this->gam < 0.1 or $this->gam > 9.99) { return; } return (float) $this->gam; } } glide/src/Manipulators/Border.php 0000644 00000011447 15021222733 0013027 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; use League\Glide\Manipulators\Helpers\Color; use League\Glide\Manipulators\Helpers\Dimension; /** * @property string $border * @property string $dpr */ class Border extends BaseManipulator { /** * Perform border image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { if ($border = $this->getBorder($image)) { list($width, $color, $method) = $border; if ('overlay' === $method) { return $this->runOverlay($image, $width, $color); } if ('shrink' === $method) { return $this->runShrink($image, $width, $color); } if ('expand' === $method) { return $this->runExpand($image, $width, $color); } } return $image; } /** * Resolve border amount. * * @param Image $image The source image. * * @return (float|string)[]|null The resolved border amount. * * @psalm-return array{0: float, 1: string, 2: string}|null */ public function getBorder(Image $image) { if (!$this->border) { return; } $values = explode(',', $this->border); $width = $this->getWidth($image, $this->getDpr(), isset($values[0]) ? $values[0] : null); $color = $this->getColor(isset($values[1]) ? $values[1] : null); $method = $this->getMethod(isset($values[2]) ? $values[2] : null); if ($width) { return [$width, $color, $method]; } } /** * Get border width. * * @param Image $image The source image. * @param float $dpr The device pixel ratio. * @param string $width The border width. * * @return float|null The resolved border width. */ public function getWidth(Image $image, $dpr, $width) { return (new Dimension($image, $dpr))->get($width); } /** * Get formatted color. * * @param string $color The color. * * @return string The formatted color. */ public function getColor($color) { return (new Color($color))->formatted(); } /** * Resolve the border method. * * @param string $method The raw border method. * * @return string The resolved border method. */ public function getMethod($method) { if (!in_array($method, ['expand', 'shrink', 'overlay'], true)) { return 'overlay'; } return $method; } /** * Resolve the device pixel ratio. * * @return float The device pixel ratio. */ public function getDpr() { if (!is_numeric($this->dpr)) { return 1.0; } if ($this->dpr < 0 or $this->dpr > 8) { return 1.0; } return (float) $this->dpr; } /** * Run the overlay border method. * * @param Image $image The source image. * @param float $width The border width. * @param string $color The border color. * * @return Image The manipulated image. */ public function runOverlay(Image $image, $width, $color) { return $image->rectangle( (int) round($width / 2), (int) round($width / 2), (int) round($image->width() - ($width / 2)), (int) round($image->height() - ($width / 2)), function ($draw) use ($width, $color) { $draw->border($width, $color); } ); } /** * Run the shrink border method. * * @param Image $image The source image. * @param float $width The border width. * @param string $color The border color. * * @return Image The manipulated image. */ public function runShrink(Image $image, $width, $color) { return $image ->resize( (int) round($image->width() - ($width * 2)), (int) round($image->height() - ($width * 2)) ) ->resizeCanvas( (int) round($width * 2), (int) round($width * 2), 'center', true, $color ); } /** * Run the expand border method. * * @param Image $image The source image. * @param float $width The border width. * @param string $color The border color. * * @return Image The manipulated image. */ public function runExpand(Image $image, $width, $color) { return $image->resizeCanvas( (int) round($width * 2), (int) round($width * 2), 'center', true, $color ); } } glide/src/Manipulators/ManipulatorInterface.php 0000644 00000000723 15021222733 0015721 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; interface ManipulatorInterface { /** * Set the manipulation params. * * @param array $params The manipulation params. */ public function setParams(array $params); /** * Perform the image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image); } glide/src/Manipulators/Orientation.php 0000644 00000001523 15021222733 0014077 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $or */ class Orientation extends BaseManipulator { /** * Perform orientation image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $orientation = $this->getOrientation(); if ('auto' === $orientation) { return $image->orientate(); } return $image->rotate((float) $orientation); } /** * Resolve orientation. * * @return string The resolved orientation. */ public function getOrientation() { if (in_array($this->or, ['auto', '0', '90', '180', '270'], true)) { return $this->or; } return 'auto'; } } glide/src/Manipulators/Blur.php 0000644 00000001466 15021222733 0012516 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $blur */ class Blur extends BaseManipulator { /** * Perform blur image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $blur = $this->getBlur(); if (null !== $blur) { $image->blur($blur); } return $image; } /** * Resolve blur amount. * * @return int|null The resolved blur amount. */ public function getBlur() { if (!is_numeric($this->blur)) { return; } if ($this->blur < 0 or $this->blur > 100) { return; } return (int) $this->blur; } } glide/src/Manipulators/Watermark.php 0000644 00000014611 15021222733 0013543 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; use League\Flysystem\FilesystemException as FilesystemV2Exception; use League\Flysystem\FilesystemOperator; use League\Glide\Filesystem\FilesystemException; use League\Glide\Manipulators\Helpers\Dimension; /** * @property string $dpr * @property string $mark * @property string $markfit * @property string $markh * @property string $markpad * @property string $markpos * @property string $markw * @property string $markx * @property string $marky * @property string $markalpha */ class Watermark extends BaseManipulator { /** * The watermarks file system. * * @var FilesystemOperator|null */ protected $watermarks; /** * The watermarks path prefix. * * @var string */ protected $watermarksPathPrefix; /** * Create Watermark instance. * * @param FilesystemOperator $watermarks The watermarks file system. */ public function __construct(FilesystemOperator $watermarks = null, $watermarksPathPrefix = '') { $this->setWatermarks($watermarks); $this->setWatermarksPathPrefix($watermarksPathPrefix); } /** * Set the watermarks file system. * * @param FilesystemOperator $watermarks The watermarks file system. * * @return void */ public function setWatermarks(FilesystemOperator $watermarks = null) { $this->watermarks = $watermarks; } /** * Get the watermarks file system. * * @return FilesystemOperator|null The watermarks file system. */ public function getWatermarks() { return $this->watermarks; } /** * Set the watermarks path prefix. * * @param string $watermarksPathPrefix The watermarks path prefix. * * @return void */ public function setWatermarksPathPrefix($watermarksPathPrefix = '') { $this->watermarksPathPrefix = trim($watermarksPathPrefix, '/'); } /** * Get the watermarks path prefix. * * @return string The watermarks path prefix. */ public function getWatermarksPathPrefix() { return $this->watermarksPathPrefix; } /** * Perform watermark image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { if ($watermark = $this->getImage($image)) { $markw = $this->getDimension($image, 'markw'); $markh = $this->getDimension($image, 'markh'); $markx = $this->getDimension($image, 'markx'); $marky = $this->getDimension($image, 'marky'); $markpad = $this->getDimension($image, 'markpad'); $markfit = $this->getFit(); $markpos = $this->getPosition(); $markalpha = $this->getAlpha(); if ($markpad) { $markx = $marky = $markpad; } $size = new Size(); $size->setParams([ 'w' => $markw, 'h' => $markh, 'fit' => $markfit, ]); $watermark = $size->run($watermark); if ($markalpha < 100) { $watermark->opacity($markalpha); } $image->insert($watermark, $markpos, intval($markx), intval($marky)); } return $image; } /** * Get the watermark image. * * @param Image $image The source image. * * @return Image|null The watermark image. */ public function getImage(Image $image) { if (is_null($this->watermarks)) { return; } if (!is_string($this->mark)) { return; } if ('' === $this->mark) { return; } $path = $this->mark; if ($this->watermarksPathPrefix) { $path = $this->watermarksPathPrefix.'/'.$path; } try { if ($this->watermarks->fileExists($path)) { $source = $this->watermarks->read($path); return $image->getDriver()->init($source); } } catch (FilesystemV2Exception $exception) { throw new FilesystemException('Could not read the image `'.$path.'`.'); } } /** * Get a dimension. * * @param Image $image The source image. * @param string $field The requested field. * * @return float|null The dimension. */ public function getDimension(Image $image, $field) { if ($this->{$field}) { return (new Dimension($image, $this->getDpr()))->get($this->{$field}); } } /** * Resolve the device pixel ratio. * * @return float The device pixel ratio. */ public function getDpr() { if (!is_numeric($this->dpr)) { return 1.0; } if ($this->dpr < 0 or $this->dpr > 8) { return 1.0; } return (float) $this->dpr; } /** * Get the fit. * * @return string|null The fit. */ public function getFit() { $fitMethods = [ 'contain', 'max', 'stretch', 'crop', 'crop-top-left', 'crop-top', 'crop-top-right', 'crop-left', 'crop-center', 'crop-right', 'crop-bottom-left', 'crop-bottom', 'crop-bottom-right', ]; if (in_array($this->markfit, $fitMethods, true)) { return $this->markfit; } } /** * Get the position. * * @return string The position. */ public function getPosition() { $positions = [ 'top-left', 'top', 'top-right', 'left', 'center', 'right', 'bottom-left', 'bottom', 'bottom-right', ]; if (in_array($this->markpos, $positions, true)) { return $this->markpos; } return 'bottom-right'; } /** * Get the alpha channel. * * @return int The alpha. */ public function getAlpha() { if (!is_numeric($this->markalpha)) { return 100; } if ($this->markalpha < 0 or $this->markalpha > 100) { return 100; } return (int) $this->markalpha; } } glide/src/Manipulators/Background.php 0000644 00000001425 15021222733 0013664 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; use League\Glide\Manipulators\Helpers\Color; /** * @property string $bg */ class Background extends BaseManipulator { /** * Perform background image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { if (is_null($this->bg)) { return $image; } $color = (new Color($this->bg))->formatted(); if ($color) { $new = $image->getDriver()->newImage($image->width(), $image->height(), $color); $new->mime = $image->mime; $image = $new->insert($image, 'top-left', 0, 0); } return $image; } } glide/src/Manipulators/BaseManipulator.php 0000644 00000001711 15021222733 0014671 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; abstract class BaseManipulator implements ManipulatorInterface { /** * The manipulation params. * * @var array */ public $params = []; /** * Set the manipulation params. * * @param array $params The manipulation params. * * @return $this */ public function setParams(array $params) { $this->params = $params; return $this; } /** * Get a specific manipulation param. * * @param string $name The manipulation name. * * @return string The manipulation value. */ public function __get($name) { if (array_key_exists($name, $this->params)) { return $this->params[$name]; } } /** * Perform the image manipulation. * * @return Image The manipulated image. */ abstract public function run(Image $image); } glide/src/Manipulators/Sharpen.php 0000644 00000001531 15021222733 0013203 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $sharp */ class Sharpen extends BaseManipulator { /** * Perform sharpen image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $sharpen = $this->getSharpen(); if (null !== $sharpen) { $image->sharpen($sharpen); } return $image; } /** * Resolve sharpen amount. * * @return int|null The resolved sharpen amount. */ public function getSharpen() { if (!is_numeric($this->sharp)) { return; } if ($this->sharp < 0 or $this->sharp > 100) { return; } return (int) $this->sharp; } } glide/src/Manipulators/Size.php 0000644 00000030356 15021222733 0012524 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $dpr * @property string|null $fit * @property string $h * @property string $w */ class Size extends BaseManipulator { /** * Maximum image size in pixels. * * @var int|null */ protected $maxImageSize; /** * Create Size instance. * * @param int|null $maxImageSize Maximum image size in pixels. */ public function __construct($maxImageSize = null) { $this->maxImageSize = $maxImageSize; } /** * Set the maximum image size. * * @param int|null Maximum image size in pixels. * * @return void */ public function setMaxImageSize($maxImageSize) { $this->maxImageSize = $maxImageSize; } /** * Get the maximum image size. * * @return int|null Maximum image size in pixels. */ public function getMaxImageSize() { return $this->maxImageSize; } /** * Perform size image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $width = $this->getWidth(); $height = $this->getHeight(); $fit = $this->getFit(); $dpr = $this->getDpr(); list($width, $height) = $this->resolveMissingDimensions($image, $width, $height); list($width, $height) = $this->applyDpr($width, $height, $dpr); list($width, $height) = $this->limitImageSize($width, $height); if ((int) $width !== (int) $image->width() || (int) $height !== (int) $image->height() || 1.0 !== $this->getCrop()[2]) { $image = $this->runResize($image, $fit, (int) $width, (int) $height); } return $image; } /** * Resolve width. * * @return int|null The resolved width. */ public function getWidth() { if (!is_numeric($this->w)) { return; } if ($this->w <= 0) { return; } return (int) $this->w; } /** * Resolve height. * * @return int|null The resolved height. */ public function getHeight() { if (!is_numeric($this->h)) { return; } if ($this->h <= 0) { return; } return (int) $this->h; } /** * Resolve fit. * * @return string The resolved fit. */ public function getFit() { if (null === $this->fit) { return 'contain'; } if (in_array($this->fit, ['contain', 'fill', 'max', 'stretch', 'fill-max'], true)) { return $this->fit; } if (preg_match('/^(crop)(-top-left|-top|-top-right|-left|-center|-right|-bottom-left|-bottom|-bottom-right|-[\d]{1,3}-[\d]{1,3}(?:-[\d]{1,3}(?:\.\d+)?)?)*$/', $this->fit)) { return 'crop'; } return 'contain'; } /** * Resolve the device pixel ratio. * * @return float The device pixel ratio. */ public function getDpr() { if (!is_numeric($this->dpr)) { return 1.0; } if ($this->dpr < 0 or $this->dpr > 8) { return 1.0; } return (float) $this->dpr; } /** * Resolve missing image dimensions. * * @param Image $image The source image. * @param int|null $width The image width. * @param int|null $height The image height. * * @return int[] The resolved width and height. */ public function resolveMissingDimensions(Image $image, $width, $height) { if (is_null($width) and is_null($height)) { $width = $image->width(); $height = $image->height(); } if (is_null($width) || is_null($height)) { $size = (new \Intervention\Image\Size($image->width(), $image->height())) ->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); }); $width = $size->getWidth(); $height = $size->getHeight(); } return [ (int) $width, (int) $height, ]; } /** * Apply the device pixel ratio. * * @param int $width The target image width. * @param int $height The target image height. * @param float $dpr The device pixel ratio. * * @return int[] The modified width and height. */ public function applyDpr($width, $height, $dpr) { $width = $width * $dpr; $height = $height * $dpr; return [ (int) round($width), (int) round($height), ]; } /** * Limit image size to maximum allowed image size. * * @param int $width The image width. * @param int $height The image height. * * @return int[] The limited width and height. */ public function limitImageSize($width, $height) { if (null !== $this->maxImageSize) { $imageSize = $width * $height; if ($imageSize > $this->maxImageSize) { $width = $width / sqrt($imageSize / $this->maxImageSize); $height = $height / sqrt($imageSize / $this->maxImageSize); } } return [ (int) $width, (int) $height, ]; } /** * Perform resize image manipulation. * * @param Image $image The source image. * @param string $fit The fit. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runResize(Image $image, $fit, $width, $height) { if ('contain' === $fit) { return $this->runContainResize($image, $width, $height); } if ('fill' === $fit) { return $this->runFillResize($image, $width, $height); } if ('fill-max' === $fit) { return $this->runFillMaxResize($image, $width, $height); } if ('max' === $fit) { return $this->runMaxResize($image, $width, $height); } if ('stretch' === $fit) { return $this->runStretchResize($image, $width, $height); } if ('crop' === $fit) { return $this->runCropResize($image, $width, $height); } return $image; } /** * Perform contain resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runContainResize(Image $image, $width, $height) { return $image->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); }); } /** * Perform max resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runMaxResize(Image $image, $width, $height) { return $image->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); $constraint->upsize(); }); } /** * Perform fill resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runFillResize($image, $width, $height) { $image = $this->runMaxResize($image, $width, $height); return $image->resizeCanvas($width, $height, 'center'); } /** * Perform fill-max resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runFillMaxResize(Image $image, $width, $height) { $image = $image->resize($width, $height, function ($constraint) { $constraint->aspectRatio(); }); return $image->resizeCanvas($width, $height, 'center'); } /** * Perform stretch resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runStretchResize(Image $image, $width, $height) { return $image->resize($width, $height); } /** * Perform crop resize image manipulation. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return Image The manipulated image. */ public function runCropResize(Image $image, $width, $height) { list($resize_width, $resize_height) = $this->resolveCropResizeDimensions($image, $width, $height); $zoom = $this->getCrop()[2]; $image->resize($resize_width * $zoom, $resize_height * $zoom, function ($constraint) { $constraint->aspectRatio(); }); list($offset_x, $offset_y) = $this->resolveCropOffset($image, $width, $height); return $image->crop($width, $height, $offset_x, $offset_y); } /** * Resolve the crop resize dimensions. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return array The resize dimensions. */ public function resolveCropResizeDimensions(Image $image, $width, $height) { if ($height > $width * ($image->height() / $image->width())) { return [$height * ($image->width() / $image->height()), $height]; } return [$width, $width * ($image->height() / $image->width())]; } /** * Resolve the crop offset. * * @param Image $image The source image. * @param int $width The width. * @param int $height The height. * * @return array The crop offset. */ public function resolveCropOffset(Image $image, $width, $height) { list($offset_percentage_x, $offset_percentage_y) = $this->getCrop(); $offset_x = (int) (($image->width() * $offset_percentage_x / 100) - ($width / 2)); $offset_y = (int) (($image->height() * $offset_percentage_y / 100) - ($height / 2)); $max_offset_x = $image->width() - $width; $max_offset_y = $image->height() - $height; if ($offset_x < 0) { $offset_x = 0; } if ($offset_y < 0) { $offset_y = 0; } if ($offset_x > $max_offset_x) { $offset_x = $max_offset_x; } if ($offset_y > $max_offset_y) { $offset_y = $max_offset_y; } return [$offset_x, $offset_y]; } /** * Resolve crop with zoom. * * @return (float|int)[] The resolved crop. * * @psalm-return array{0: int, 1: int, 2: float} */ public function getCrop() { $cropMethods = [ 'crop-top-left' => [0, 0, 1.0], 'crop-top' => [50, 0, 1.0], 'crop-top-right' => [100, 0, 1.0], 'crop-left' => [0, 50, 1.0], 'crop-center' => [50, 50, 1.0], 'crop-right' => [100, 50, 1.0], 'crop-bottom-left' => [0, 100, 1.0], 'crop-bottom' => [50, 100, 1.0], 'crop-bottom-right' => [100, 100, 1.0], ]; if (null === $this->fit) { return [50, 50, 1.0]; } if (array_key_exists($this->fit, $cropMethods)) { return $cropMethods[$this->fit]; } if (preg_match('/^crop-([\d]{1,3})-([\d]{1,3})(?:-([\d]{1,3}(?:\.\d+)?))*$/', $this->fit, $matches)) { $matches[3] = isset($matches[3]) ? $matches[3] : 1; if ($matches[1] > 100 or $matches[2] > 100 or $matches[3] > 100) { return [50, 50, 1.0]; } return [ (int) $matches[1], (int) $matches[2], (float) $matches[3], ]; } return [50, 50, 1.0]; } } glide/src/Manipulators/Pixelate.php 0000644 00000001544 15021222733 0013362 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $pixel */ class Pixelate extends BaseManipulator { /** * Perform pixelate image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $pixelate = $this->getPixelate(); if (null !== $pixelate) { $image->pixelate($pixelate); } return $image; } /** * Resolve pixelate amount. * * @return int|null The resolved pixelate amount. */ public function getPixelate() { if (!is_numeric($this->pixel)) { return; } if ($this->pixel < 0 or $this->pixel > 1000) { return; } return (int) $this->pixel; } } glide/src/Manipulators/Helpers/Dimension.php 0000644 00000002245 15021222733 0015135 0 ustar 00 <?php namespace League\Glide\Manipulators\Helpers; use Intervention\Image\Image; class Dimension { /** * The source image. * * @var Image */ protected $image; /** * The device pixel ratio. * * @var float */ protected $dpr; /** * Create dimension helper instance. * * @param Image $image The source image. * @param float $dpr The device pixel ratio. */ public function __construct(Image $image, $dpr = 1) { $this->image = $image; $this->dpr = $dpr; } /** * Resolve the dimension. * * @param string $value The dimension value. * * @return float|null The resolved dimension. */ public function get($value) { if (is_numeric($value) and $value > 0) { return (float) $value * $this->dpr; } if (preg_match('/^(\d{1,2}(?!\d)|100)(w|h)$/', $value, $matches)) { if ('h' === $matches[2]) { return (float) $this->image->height() * ((float) $matches[1] / 100); } return (float) $this->image->width() * ((float) $matches[1] / 100); } } } glide/src/Manipulators/Helpers/Color.php 0000644 00000020051 15021222733 0014261 0 ustar 00 <?php namespace League\Glide\Manipulators\Helpers; class Color { /** * 3 digit color code expression. */ const SHORT_RGB = '/^[0-9a-f]{3}$/i'; /** * 4 digit color code expression. */ const SHORT_ARGB = '/^[0-9]{1}[0-9a-f]{3}$/i'; /** * 6 digit color code expression. */ const LONG_RGB = '/^[0-9a-f]{6}$/i'; /** * 8 digit color code expression. */ const LONG_ARGB = '/^[0-9]{2}[0-9a-f]{6}$/i'; /** * The red value. * * @var int */ protected $red; /** * The green value. * * @var int */ protected $green; /** * The blue value. * * @var int */ protected $blue; /** * The alpha value. * * @var int|float */ protected $alpha; /** * Create color helper instance. * * @param string $value The color value. */ public function __construct($value) { do { if ($hex = $this->getHexFromColorName($value)) { $rgba = $this->parseHex($hex); $alpha = 1; break; } if (preg_match(self::SHORT_RGB, $value)) { $rgba = $this->parseHex($value.$value); $alpha = 1; break; } if (preg_match(self::SHORT_ARGB, $value)) { $rgba = $this->parseHex(substr($value, 1).substr($value, 1)); $alpha = (float) substr($value, 0, 1) / 10; break; } if (preg_match(self::LONG_RGB, $value)) { $rgba = $this->parseHex($value); $alpha = 1; break; } if (preg_match(self::LONG_ARGB, $value)) { $rgba = $this->parseHex(substr($value, 2)); $alpha = (float) substr($value, 0, 2) / 100; break; } $rgba = [255, 255, 255]; $alpha = 0; } while (false); $this->red = $rgba[0]; $this->green = $rgba[1]; $this->blue = $rgba[2]; $this->alpha = $alpha; } /** * Parse hex color to RGB values. * * @param string $hex The hex value. * * @return array The RGB values. */ public function parseHex($hex) { return array_map('hexdec', str_split($hex, 2)); } /** * Format color for consumption. * * @return string The formatted color. */ public function formatted() { return 'rgba('.$this->red.', '.$this->green.', '.$this->blue.', '.$this->alpha.')'; } /** * Get hex code by color name. * * @param string $name The color name. * * @return string|null The hex code. */ public function getHexFromColorName($name) { $colors = [ 'aliceblue' => 'F0F8FF', 'antiquewhite' => 'FAEBD7', 'aqua' => '00FFFF', 'aquamarine' => '7FFFD4', 'azure' => 'F0FFFF', 'beige' => 'F5F5DC', 'bisque' => 'FFE4C4', 'black' => '000000', 'blanchedalmond' => 'FFEBCD', 'blue' => '0000FF', 'blueviolet' => '8A2BE2', 'brown' => 'A52A2A', 'burlywood' => 'DEB887', 'cadetblue' => '5F9EA0', 'chartreuse' => '7FFF00', 'chocolate' => 'D2691E', 'coral' => 'FF7F50', 'cornflowerblue' => '6495ED', 'cornsilk' => 'FFF8DC', 'crimson' => 'DC143C', 'cyan' => '00FFFF', 'darkblue' => '00008B', 'darkcyan' => '008B8B', 'darkgoldenrod' => 'B8860B', 'darkgray' => 'A9A9A9', 'darkgreen' => '006400', 'darkkhaki' => 'BDB76B', 'darkmagenta' => '8B008B', 'darkolivegreen' => '556B2F', 'darkorange' => 'FF8C00', 'darkorchid' => '9932CC', 'darkred' => '8B0000', 'darksalmon' => 'E9967A', 'darkseagreen' => '8FBC8F', 'darkslateblue' => '483D8B', 'darkslategray' => '2F4F4F', 'darkturquoise' => '00CED1', 'darkviolet' => '9400D3', 'deeppink' => 'FF1493', 'deepskyblue' => '00BFFF', 'dimgray' => '696969', 'dodgerblue' => '1E90FF', 'firebrick' => 'B22222', 'floralwhite' => 'FFFAF0', 'forestgreen' => '228B22', 'fuchsia' => 'FF00FF', 'gainsboro' => 'DCDCDC', 'ghostwhite' => 'F8F8FF', 'gold' => 'FFD700', 'goldenrod' => 'DAA520', 'gray' => '808080', 'green' => '008000', 'greenyellow' => 'ADFF2F', 'honeydew' => 'F0FFF0', 'hotpink' => 'FF69B4', 'indianred' => 'CD5C5C', 'indigo' => '4B0082', 'ivory' => 'FFFFF0', 'khaki' => 'F0E68C', 'lavender' => 'E6E6FA', 'lavenderblush' => 'FFF0F5', 'lawngreen' => '7CFC00', 'lemonchiffon' => 'FFFACD', 'lightblue' => 'ADD8E6', 'lightcoral' => 'F08080', 'lightcyan' => 'E0FFFF', 'lightgoldenrodyellow' => 'FAFAD2', 'lightgray' => 'D3D3D3', 'lightgreen' => '90EE90', 'lightpink' => 'FFB6C1', 'lightsalmon' => 'FFA07A', 'lightseagreen' => '20B2AA', 'lightskyblue' => '87CEFA', 'lightslategray' => '778899', 'lightsteelblue' => 'B0C4DE', 'lightyellow' => 'FFFFE0', 'lime' => '00FF00', 'limegreen' => '32CD32', 'linen' => 'FAF0E6', 'magenta' => 'FF00FF', 'maroon' => '800000', 'mediumaquamarine' => '66CDAA', 'mediumblue' => '0000CD', 'mediumorchid' => 'BA55D3', 'mediumpurple' => '9370DB', 'mediumseagreen' => '3CB371', 'mediumslateblue' => '7B68EE', 'mediumspringgreen' => '00FA9A', 'mediumturquoise' => '48D1CC', 'mediumvioletred' => 'C71585', 'midnightblue' => '191970', 'mintcream' => 'F5FFFA', 'mistyrose' => 'FFE4E1', 'moccasin' => 'FFE4B5', 'navajowhite' => 'FFDEAD', 'navy' => '000080', 'oldlace' => 'FDF5E6', 'olive' => '808000', 'olivedrab' => '6B8E23', 'orange' => 'FFA500', 'orangered' => 'FF4500', 'orchid' => 'DA70D6', 'palegoldenrod' => 'EEE8AA', 'palegreen' => '98FB98', 'paleturquoise' => 'AFEEEE', 'palevioletred' => 'DB7093', 'papayawhip' => 'FFEFD5', 'peachpuff' => 'FFDAB9', 'peru' => 'CD853F', 'pink' => 'FFC0CB', 'plum' => 'DDA0DD', 'powderblue' => 'B0E0E6', 'purple' => '800080', 'rebeccapurple' => '663399', 'red' => 'FF0000', 'rosybrown' => 'BC8F8F', 'royalblue' => '4169E1', 'saddlebrown' => '8B4513', 'salmon' => 'FA8072', 'sandybrown' => 'F4A460', 'seagreen' => '2E8B57', 'seashell' => 'FFF5EE', 'sienna' => 'A0522D', 'silver' => 'C0C0C0', 'skyblue' => '87CEEB', 'slateblue' => '6A5ACD', 'slategray' => '708090', 'snow' => 'FFFAFA', 'springgreen' => '00FF7F', 'steelblue' => '4682B4', 'tan' => 'D2B48C', 'teal' => '008080', 'thistle' => 'D8BFD8', 'tomato' => 'FF6347', 'turquoise' => '40E0D0', 'violet' => 'EE82EE', 'wheat' => 'F5DEB3', 'white' => 'FFFFFF', 'whitesmoke' => 'F5F5F5', 'yellow' => 'FFFF00', 'yellowgreen' => '9ACD32', ]; $name = strtolower($name); if (array_key_exists($name, $colors)) { return $colors[$name]; } } } glide/src/Manipulators/Filter.php 0000644 00000002406 15021222733 0013032 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $filt */ class Filter extends BaseManipulator { /** * Perform filter image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { if ('greyscale' === $this->filt) { return $this->runGreyscaleFilter($image); } if ('sepia' === $this->filt) { return $this->runSepiaFilter($image); } return $image; } /** * Perform greyscale manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function runGreyscaleFilter(Image $image) { return $image->greyscale(); } /** * Perform sepia manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function runSepiaFilter(Image $image) { $image->greyscale(); $image->brightness(-10); $image->contrast(10); $image->colorize(38, 27, 12); $image->brightness(-10); $image->contrast(10); return $image; } } glide/src/Manipulators/Contrast.php 0000644 00000001610 15021222733 0013376 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string|null $con */ class Contrast extends BaseManipulator { /** * Perform contrast image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $contrast = $this->getContrast(); if (null !== $contrast) { $image->contrast($contrast); } return $image; } /** * Resolve contrast amount. * * @return int|null The resolved contrast amount. */ public function getContrast() { if (null === $this->con || !preg_match('/^-*[0-9]+$/', $this->con)) { return; } if ($this->con < -100 or $this->con > 100) { return; } return (int) $this->con; } } glide/src/Manipulators/Encode.php 0000644 00000004041 15021222733 0012777 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string $fm * @property string $q */ class Encode extends BaseManipulator { /** * Perform output image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $format = $this->getFormat($image); $quality = $this->getQuality(); if (in_array($format, ['jpg', 'pjpg'], true)) { $image = $image->getDriver() ->newImage($image->width(), $image->height(), '#fff') ->insert($image, 'top-left', 0, 0); } if ('pjpg' === $format) { $image->interlace(); $format = 'jpg'; } return $image->encode($format, $quality); } /** * Resolve format. * * @param Image $image The source image. * * @return string The resolved format. */ public function getFormat(Image $image) { if (array_key_exists($this->fm, static::supportedFormats())) { return $this->fm; } return array_search($image->mime(), static::supportedFormats(), true) ?: 'jpg'; } /** * Get a list of supported image formats and MIME types. * * @return array<string,string> */ public static function supportedFormats() { return [ 'avif' => 'image/avif', 'gif' => 'image/gif', 'jpg' => 'image/jpeg', 'pjpg' => 'image/jpeg', 'png' => 'image/png', 'webp' => 'image/webp', 'tiff' => 'image/tiff', ]; } /** * Resolve quality. * * @return int The resolved quality. */ public function getQuality() { $default = 90; if (!is_numeric($this->q)) { return $default; } if ($this->q < 0 or $this->q > 100) { return $default; } return (int) $this->q; } } glide/src/Manipulators/Crop.php 0000644 00000004624 15021222733 0012514 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string|null $crop */ class Crop extends BaseManipulator { /** * Perform crop image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $coordinates = $this->getCoordinates($image); if ($coordinates) { $coordinates = $this->limitToImageBoundaries($image, $coordinates); $image->crop( $coordinates[0], $coordinates[1], $coordinates[2], $coordinates[3] ); } return $image; } /** * Resolve coordinates. * * @param Image $image The source image. * * @return int[]|null The resolved coordinates. * * @psalm-return array{0: int, 1: int, 2: int, 3: int}|null */ public function getCoordinates(Image $image) { if (null === $this->crop) { return; } $coordinates = explode(',', $this->crop); if (4 !== count($coordinates) or (!is_numeric($coordinates[0])) or (!is_numeric($coordinates[1])) or (!is_numeric($coordinates[2])) or (!is_numeric($coordinates[3])) or ($coordinates[0] <= 0) or ($coordinates[1] <= 0) or ($coordinates[2] < 0) or ($coordinates[3] < 0) or ($coordinates[2] >= $image->width()) or ($coordinates[3] >= $image->height())) { return; } return [ (int) $coordinates[0], (int) $coordinates[1], (int) $coordinates[2], (int) $coordinates[3], ]; } /** * Limit coordinates to image boundaries. * * @param Image $image The source image. * @param int[] $coordinates The coordinates. * * @return int[] The limited coordinates. */ public function limitToImageBoundaries(Image $image, array $coordinates) { if ($coordinates[0] > ($image->width() - $coordinates[2])) { $coordinates[0] = $image->width() - $coordinates[2]; } if ($coordinates[1] > ($image->height() - $coordinates[3])) { $coordinates[1] = $image->height() - $coordinates[3]; } return $coordinates; } } glide/src/Manipulators/Brightness.php 0000644 00000001634 15021222733 0013717 0 ustar 00 <?php namespace League\Glide\Manipulators; use Intervention\Image\Image; /** * @property string|null $bri */ class Brightness extends BaseManipulator { /** * Perform brightness image manipulation. * * @param Image $image The source image. * * @return Image The manipulated image. */ public function run(Image $image) { $brightness = $this->getBrightness(); if (null !== $brightness) { $image->brightness($brightness); } return $image; } /** * Resolve brightness amount. * * @return int|null The resolved brightness amount. */ public function getBrightness() { if (null === $this->bri || !preg_match('/^-*[0-9]+$/', $this->bri)) { return; } if ($this->bri < -100 or $this->bri > 100) { return; } return (int) $this->bri; } } glide/src/Responses/PsrResponseFactory.php 0000644 00000003263 15021222733 0014725 0 ustar 00 <?php namespace League\Glide\Responses; use Closure; use League\Flysystem\FilesystemOperator; use Psr\Http\Message\ResponseInterface; class PsrResponseFactory implements ResponseFactoryInterface { /** * Base response object. * * @var ResponseInterface */ protected $response; /** * Callback to create stream. * * @var Closure */ protected $streamCallback; /** * Create PsrResponseFactory instance. * * @param ResponseInterface $response Base response object. * @param Closure $streamCallback Callback to create stream. */ public function __construct(ResponseInterface $response, Closure $streamCallback) { $this->response = $response; $this->streamCallback = $streamCallback; } /** * Create response. * * @param FilesystemOperator $cache Cache file system. * @param string $path Cached file path. * * @return ResponseInterface Response object. */ public function create(FilesystemOperator $cache, $path) { $stream = $this->streamCallback->__invoke( $cache->readStream($path) ); $contentType = $cache->mimeType($path); $contentLength = (string) $cache->fileSize($path); $cacheControl = 'max-age=31536000, public'; $expires = date_create('+1 years')->format('D, d M Y H:i:s').' GMT'; return $this->response->withBody($stream) ->withHeader('Content-Type', $contentType) ->withHeader('Content-Length', $contentLength) ->withHeader('Cache-Control', $cacheControl) ->withHeader('Expires', $expires); } } glide/src/Responses/ResponseFactoryInterface.php 0000644 00000000616 15021222733 0016060 0 ustar 00 <?php namespace League\Glide\Responses; use League\Flysystem\FilesystemOperator; interface ResponseFactoryInterface { /** * Create response. * * @param FilesystemOperator $cache Cache file system. * @param string $path Cached file path. * * @return mixed The response object. */ public function create(FilesystemOperator $cache, $path); } glide/src/Filesystem/FilesystemException.php 0000644 00000000153 15021222733 0015253 0 ustar 00 <?php namespace League\Glide\Filesystem; use Exception; class FilesystemException extends Exception { } glide/src/Filesystem/FileNotFoundException.php 0000644 00000000155 15021222733 0015465 0 ustar 00 <?php namespace League\Glide\Filesystem; use Exception; class FileNotFoundException extends Exception { } glide/src/Urls/UrlBuilderFactory.php 0000644 00000001171 15021222733 0013453 0 ustar 00 <?php namespace League\Glide\Urls; use League\Glide\Signatures\SignatureFactory; class UrlBuilderFactory { /** * Create UrlBuilder instance. * * @param string $baseUrl URL prefixed to generated URL. * @param string|null $signKey Secret key used to secure URLs. * * @return UrlBuilder The UrlBuilder instance. */ public static function create($baseUrl, $signKey = null) { $httpSignature = null; if (!is_null($signKey)) { $httpSignature = SignatureFactory::create($signKey); } return new UrlBuilder($baseUrl, $httpSignature); } } glide/src/Urls/UrlBuilder.php 0000644 00000005563 15021222733 0012134 0 ustar 00 <?php namespace League\Glide\Urls; use InvalidArgumentException; use League\Glide\Signatures\SignatureInterface; class UrlBuilder { /** * The base URL. * * @var string */ protected $baseUrl; /** * Whether the base URL is a relative domain. * * @var bool */ protected $isRelativeDomain = false; /** * The HTTP signature used to sign URLs. * * @var SignatureInterface */ protected $signature; /** * Create UrlBuilder instance. * * @param string $baseUrl The base URL. * @param SignatureInterface|null $signature The HTTP signature used to sign URLs. */ public function __construct($baseUrl = '', SignatureInterface $signature = null) { $this->setBaseUrl($baseUrl); $this->setSignature($signature); } /** * Set the base URL. * * @param string $baseUrl The base URL. * * @return void */ public function setBaseUrl($baseUrl) { if ('//' === substr($baseUrl, 0, 2)) { $baseUrl = 'http:'.$baseUrl; $this->isRelativeDomain = true; } $this->baseUrl = rtrim($baseUrl, '/'); } /** * Set the HTTP signature. * * @param SignatureInterface|null $signature The HTTP signature used to sign URLs. * * @return void */ public function setSignature(SignatureInterface $signature = null) { $this->signature = $signature; } /** * Get the URL. * * @param string $path The resource path. * @param array $params The manipulation parameters. * * @return string The URL. */ public function getUrl($path, array $params = []) { $parts = parse_url($this->baseUrl.'/'.trim($path, '/')); if (false === $parts) { throw new InvalidArgumentException('Not a valid path.'); } $parts['path'] = '/'.trim($parts['path'], '/'); if ($this->signature) { $params = $this->signature->addSignature($parts['path'], $params); } return $this->buildUrl($parts, $params); } /** * Build the URL. * * @param array $parts The URL parts. * @param array $params The manipulation parameters. * * @return string The built URL. */ protected function buildUrl($parts, $params) { $url = ''; if (isset($parts['host'])) { if ($this->isRelativeDomain) { $url .= '//'.$parts['host']; } else { $url .= $parts['scheme'].'://'.$parts['host']; } if (isset($parts['port'])) { $url .= ':'.$parts['port']; } } $url .= $parts['path']; if (count($params)) { $url .= '?'.http_build_query($params); } return $url; } } glide/src/Server.php 0000644 00000041052 15021222733 0010375 0 ustar 00 <?php namespace League\Glide; use Closure; use InvalidArgumentException; use League\Flysystem\FilesystemException as FilesystemV2Exception; use League\Flysystem\FilesystemOperator; use League\Glide\Api\ApiInterface; use League\Glide\Filesystem\FileNotFoundException; use League\Glide\Filesystem\FilesystemException; use League\Glide\Responses\ResponseFactoryInterface; class Server { /** * Source file system. * * @var FilesystemOperator */ protected $source; /** * Source path prefix. * * @var string */ protected $sourcePathPrefix; /** * Cache file system. * * @var FilesystemOperator */ protected $cache; /** * Cache path prefix. * * @var string */ protected $cachePathPrefix; /** * Temporary EXIF data directory. * * @var string */ protected $tempDir; /** * Whether to group cache in folders. * * @var bool */ protected $groupCacheInFolders = true; /** * Whether to cache with file extensions. * * @var bool */ protected $cacheWithFileExtensions = false; /** * Image manipulation API. * * @var ApiInterface */ protected $api; /** * Response factory. * * @var ResponseFactoryInterface|null */ protected $responseFactory; /** * Base URL. * * @var string */ protected $baseUrl; /** * Default image manipulations. * * @var array */ protected $defaults = []; /** * Preset image manipulations. * * @var array */ protected $presets = []; /** * Custom cache path callable. * * @var \Closure|null */ protected $cachePathCallable; /** * Create Server instance. * * @param FilesystemOperator $source Source file system. * @param FilesystemOperator $cache Cache file system. * @param ApiInterface $api Image manipulation API. */ public function __construct(FilesystemOperator $source, FilesystemOperator $cache, ApiInterface $api) { $this->setSource($source); $this->setCache($cache); $this->setApi($api); $this->tempDir = sys_get_temp_dir(); } /** * Set source file system. * * @param FilesystemOperator $source Source file system. * * @return void */ public function setSource(FilesystemOperator $source) { $this->source = $source; } /** * Get source file system. * * @return FilesystemOperator Source file system. */ public function getSource() { return $this->source; } /** * Set source path prefix. * * @param string $sourcePathPrefix Source path prefix. * * @return void */ public function setSourcePathPrefix($sourcePathPrefix) { $this->sourcePathPrefix = trim($sourcePathPrefix, '/'); } /** * Get source path prefix. * * @return string Source path prefix. */ public function getSourcePathPrefix() { return $this->sourcePathPrefix; } /** * Get source path. * * @param string $path Image path. * * @return string The source path. * * @throws FileNotFoundException */ public function getSourcePath($path) { $path = trim($path, '/'); $baseUrl = $this->baseUrl.'/'; if (substr($path, 0, strlen($baseUrl)) === $baseUrl) { $path = trim(substr($path, strlen($baseUrl)), '/'); } if ('' === $path) { throw new FileNotFoundException('Image path missing.'); } if ($this->sourcePathPrefix) { $path = $this->sourcePathPrefix.'/'.$path; } return rawurldecode($path); } /** * Check if a source file exists. * * @param string $path Image path. * * @return bool Whether the source file exists. */ public function sourceFileExists($path) { try { return $this->source->fileExists($this->getSourcePath($path)); } catch (FilesystemV2Exception $exception) { return false; } } /** * Set base URL. * * @param string $baseUrl Base URL. * * @return void */ public function setBaseUrl($baseUrl) { $this->baseUrl = trim($baseUrl, '/'); } /** * Get base URL. * * @return string Base URL. */ public function getBaseUrl() { return $this->baseUrl; } /** * Set cache file system. * * @param FilesystemOperator $cache Cache file system. * * @return void */ public function setCache(FilesystemOperator $cache) { $this->cache = $cache; } /** * Get cache file system. * * @return FilesystemOperator Cache file system. */ public function getCache() { return $this->cache; } /** * Set cache path prefix. * * @param string $cachePathPrefix Cache path prefix. * * @return void */ public function setCachePathPrefix($cachePathPrefix) { $this->cachePathPrefix = trim($cachePathPrefix, '/'); } /** * Get cache path prefix. * * @return string Cache path prefix. */ public function getCachePathPrefix() { return $this->cachePathPrefix; } /** * Get temporary EXIF data directory. * * @return string */ public function getTempDir() { return $this->tempDir; } /** * Set temporary EXIF data directory. This directory must be a local path and exists on the filesystem. * * @param string $tempDir * * @return void * * @throws InvalidArgumentException */ public function setTempDir($tempDir) { if (!$tempDir || !is_dir($tempDir)) { throw new InvalidArgumentException(sprintf('Invalid temp dir provided: "%s" does not exist.', $tempDir)); } $this->tempDir = rtrim($tempDir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; } /** * Set the group cache in folders setting. * * @param bool $groupCacheInFolders Whether to group cache in folders. * * @return void */ public function setGroupCacheInFolders($groupCacheInFolders) { $this->groupCacheInFolders = $groupCacheInFolders; } /** * Get the group cache in folders setting. * * @return bool Whether to group cache in folders. */ public function getGroupCacheInFolders() { return $this->groupCacheInFolders; } /** * Set the cache with file extensions setting. * * @param bool $cacheWithFileExtensions Whether to cache with file extensions. * * @return void */ public function setCacheWithFileExtensions($cacheWithFileExtensions) { $this->cacheWithFileExtensions = $cacheWithFileExtensions; } /** * Get the cache with file extensions setting. * * @return bool Whether to cache with file extensions. */ public function getCacheWithFileExtensions() { return $this->cacheWithFileExtensions; } /** * Set a custom cachePathCallable. * * @param \Closure|null $cachePathCallable The custom cache path callable. It receives the same arguments as @see getCachePath */ public function setCachePathCallable(?Closure $cachePathCallable) { $this->cachePathCallable = $cachePathCallable; } /** * Gets the custom cachePathCallable. * * @return \Closure|null The custom cache path callable. It receives the same arguments as @see getCachePath */ public function getCachePathCallable() { return $this->cachePathCallable; } /** * Get cache path. * * @param string $path Image path. * @param array $params Image manipulation params. * * @return string Cache path. */ public function getCachePath($path, array $params = []) { $customCallable = $this->getCachePathCallable(); if (null !== $customCallable) { $boundCallable = Closure::bind($customCallable, $this, static::class); return $boundCallable($path, $params); } $sourcePath = $this->getSourcePath($path); if ($this->sourcePathPrefix) { $sourcePath = substr($sourcePath, strlen($this->sourcePathPrefix) + 1); } $params = $this->getAllParams($params); unset($params['s'], $params['p']); ksort($params); $cachedPath = md5($sourcePath.'?'.http_build_query($params)); if ($this->groupCacheInFolders) { $cachedPath = $sourcePath.'/'.$cachedPath; } if ($this->cachePathPrefix) { $cachedPath = $this->cachePathPrefix.'/'.$cachedPath; } if ($this->cacheWithFileExtensions) { $ext = (isset($params['fm']) ? $params['fm'] : pathinfo($path)['extension']); $ext = ('pjpg' === $ext) ? 'jpg' : $ext; $cachedPath .= '.'.$ext; } return $cachedPath; } /** * Check if a cache file exists. * * @param string $path Image path. * @param array $params Image manipulation params. * * @return bool Whether the cache file exists. */ public function cacheFileExists($path, array $params) { try { return $this->cache->fileExists( $this->getCachePath($path, $params) ); } catch (FilesystemV2Exception $exception) { return false; } } /** * Delete cached manipulations for an image. * * @param string $path Image path. * * @return bool Whether the delete succeeded. */ public function deleteCache($path) { if (!$this->groupCacheInFolders) { throw new InvalidArgumentException('Deleting cached image manipulations is not possible when grouping cache into folders is disabled.'); } try { $this->cache->deleteDirectory( dirname($this->getCachePath($path)) ); return true; } catch (FilesystemV2Exception $exception) { return false; } } /** * Set image manipulation API. * * @param ApiInterface $api Image manipulation API. * * @return void */ public function setApi(ApiInterface $api) { $this->api = $api; } /** * Get image manipulation API. * * @return ApiInterface Image manipulation API. */ public function getApi() { return $this->api; } /** * Set default image manipulations. * * @param array $defaults Default image manipulations. * * @return void */ public function setDefaults(array $defaults) { $this->defaults = $defaults; } /** * Get default image manipulations. * * @return array Default image manipulations. */ public function getDefaults() { return $this->defaults; } /** * Set preset image manipulations. * * @param array $presets Preset image manipulations. * * @return void */ public function setPresets(array $presets) { $this->presets = $presets; } /** * Get preset image manipulations. * * @return array Preset image manipulations. */ public function getPresets() { return $this->presets; } /** * Get all image manipulations params, including defaults and presets. * * @param array $params Image manipulation params. * * @return array All image manipulation params. */ public function getAllParams(array $params) { $all = $this->defaults; if (isset($params['p'])) { foreach (explode(',', $params['p']) as $preset) { if (isset($this->presets[$preset])) { $all = array_merge($all, $this->presets[$preset]); } } } return array_merge($all, $params); } /** * Set response factory. * * @param ResponseFactoryInterface|null $responseFactory Response factory. * * @return void */ public function setResponseFactory(ResponseFactoryInterface $responseFactory = null) { $this->responseFactory = $responseFactory; } /** * Get response factory. * * @return ResponseFactoryInterface|null Response factory. */ public function getResponseFactory() { return $this->responseFactory; } /** * Generate and return image response. * * @param string $path Image path. * @param array $params Image manipulation params. * * @return mixed Image response. * * @throws InvalidArgumentException */ public function getImageResponse($path, array $params) { if (is_null($this->responseFactory)) { throw new InvalidArgumentException('Unable to get image response, no response factory defined.'); } $path = $this->makeImage($path, $params); return $this->responseFactory->create($this->cache, $path); } /** * Generate and return Base64 encoded image. * * @param string $path Image path. * @param array $params Image manipulation params. * * @return string Base64 encoded image. * * @throws FilesystemException */ public function getImageAsBase64($path, array $params) { $path = $this->makeImage($path, $params); try { $source = $this->cache->read($path); return 'data:'.$this->cache->mimeType($path).';base64,'.base64_encode($source); } catch (FilesystemV2Exception $exception) { throw new FilesystemException('Could not read the image `'.$path.'`.'); } } /** * Generate and output image. * * @param string $path Image path. * @param array $params Image manipulation params. * * @throws InvalidArgumentException * * @return void */ public function outputImage($path, array $params) { $path = $this->makeImage($path, $params); try { header('Content-Type:'.$this->cache->mimeType($path)); header('Content-Length:'.$this->cache->fileSize($path)); header('Cache-Control:'.'max-age=31536000, public'); header('Expires:'.date_create('+1 years')->format('D, d M Y H:i:s').' GMT'); $stream = $this->cache->readStream($path); if (0 !== ftell($stream)) { rewind($stream); } fpassthru($stream); fclose($stream); } catch (FilesystemV2Exception $exception) { throw new FilesystemException('Could not read the image `'.$path.'`.'); } } /** * Generate manipulated image. * * @param string $path Image path. * @param array $params Image manipulation params. * * @return string Cache path. * * @throws FileNotFoundException * @throws FilesystemException */ public function makeImage($path, array $params) { $sourcePath = $this->getSourcePath($path); $cachedPath = $this->getCachePath($path, $params); if (true === $this->cacheFileExists($path, $params)) { return $cachedPath; } if (false === $this->sourceFileExists($path)) { throw new FileNotFoundException('Could not find the image `'.$sourcePath.'`.'); } try { $source = $this->source->read( $sourcePath ); } catch (FilesystemV2Exception $exception) { throw new FilesystemException('Could not read the image `'.$sourcePath.'`.'); } // We need to write the image to the local disk before // doing any manipulations. This is because EXIF data // can only be read from an actual file. $tmp = tempnam($this->tempDir, 'Glide'); if (false === file_put_contents($tmp, $source)) { throw new FilesystemException('Unable to write temp file for `'.$sourcePath.'`.'); } try { $this->cache->write( $cachedPath, $this->api->run($tmp, $this->getAllParams($params)) ); } catch (FilesystemV2Exception $exception) { throw new FilesystemException('Could not write the image `'.$cachedPath.'`.'); } finally { unlink($tmp); } return $cachedPath; } } glide/src/Signatures/SignatureInterface.php 0000644 00000001172 15021222733 0015034 0 ustar 00 <?php namespace League\Glide\Signatures; interface SignatureInterface { /** * Add an HTTP signature to manipulation params. * * @param string $path The resource path. * @param array $params The manipulation params. * * @return array The updated manipulation params. */ public function addSignature($path, array $params); /** * Validate a request signature. * * @param string $path The resource path. * @param array $params The manipulation params. * * @throws SignatureException */ public function validateRequest($path, array $params); } glide/src/Signatures/SignatureFactory.php 0000644 00000000544 15021222733 0014545 0 ustar 00 <?php namespace League\Glide\Signatures; class SignatureFactory { /** * Create HttpSignature instance. * * @param string $signKey Secret key used to generate signature. * * @return Signature The HttpSignature instance. */ public static function create($signKey) { return new Signature($signKey); } } glide/src/Signatures/Signature.php 0000644 00000003451 15021222733 0013215 0 ustar 00 <?php namespace League\Glide\Signatures; class Signature implements SignatureInterface { /** * Secret key used to generate signature. * * @var string */ protected $signKey; /** * Create Signature instance. * * @param string $signKey Secret key used to generate signature. */ public function __construct($signKey) { $this->signKey = $signKey; } /** * Add an HTTP signature to manipulation parameters. * * @param string $path The resource path. * @param array $params The manipulation parameters. * * @return array The updated manipulation parameters. */ public function addSignature($path, array $params) { return array_merge($params, ['s' => $this->generateSignature($path, $params)]); } /** * Validate a request signature. * * @param string $path The resource path. * @param array $params The manipulation params. * * @throws SignatureException * * @return void */ public function validateRequest($path, array $params) { if (!isset($params['s'])) { throw new SignatureException('Signature is missing.'); } if ($params['s'] !== $this->generateSignature($path, $params)) { throw new SignatureException('Signature is not valid.'); } } /** * Generate an HTTP signature. * * @param string $path The resource path. * @param array $params The manipulation parameters. * * @return string The generated HTTP signature. */ public function generateSignature($path, array $params) { unset($params['s']); ksort($params); return md5($this->signKey.':'.ltrim($path, '/').'?'.http_build_query($params)); } } glide/src/Signatures/SignatureException.php 0000644 00000000152 15021222733 0015067 0 ustar 00 <?php namespace League\Glide\Signatures; use Exception; class SignatureException extends Exception { } glide/README.md 0000644 00000007066 15021222733 0007115 0 ustar 00 # Glide [](https://github.com/thephpleague/glide/releases) [](https://github.com/thephpleague/glide/blob/master/LICENSE) [](https://github.com/thephpleague/glide/actions/workflows/test.yaml?query=branch%3Amaster++) [](https://app.codecov.io/gh/thephpleague/glide/) [](https://packagist.org/packages/league/glide) [](https://github.com/thephpleague/glide) [](https://twitter.com/reinink) [](https://twitter.com/titouangalopin) Glide is a wonderfully easy on-demand image manipulation library written in PHP. Its straightforward API is exposed via HTTP, similar to cloud image processing services like [Imgix](http://www.imgix.com/) and [Cloudinary](http://cloudinary.com/). Glide leverages powerful libraries like [Intervention Image](http://image.intervention.io/) (for image handling and manipulation) and [Flysystem](http://flysystem.thephpleague.com/) (for file system abstraction). [](https://glide.herokuapp.com/1.0/kayaks.jpg?w=1000) > © Photo Joel Reynolds ## Highlights - Adjust, resize and add effects to images using a simple HTTP based API. - Manipulated images are automatically cached and served with far-future expires headers. - Create your own image processing server or integrate Glide directly into your app. - Supports both the [GD](http://php.net/manual/en/book.image.php) library and the [Imagick](http://php.net/manual/en/book.imagick.php) PHP extension. - Supports many response methods, including PSR-7, HttpFoundation and more. - Ability to secure image URLs using HTTP signatures. - Works with many different file systems, thanks to the [Flysystem](http://flysystem.thephpleague.com/) library. - Powered by the battle tested [Intervention Image](http://image.intervention.io/) image handling and manipulation library. - Framework-agnostic, will work with any project. - Composer ready and PSR-2 compliant. ## Documentation Full documentation can be found at [glide.thephpleague.com](http://glide.thephpleague.com). ## Installation Glide is available via Composer: ```bash $ composer require league/glide ``` ## Testing Glide has a [PHPUnit](https://phpunit.de/) test suite. To run the tests, run the following command from the project folder: ```bash $ phpunit ``` ## Contributing Contributions are welcome and will be fully credited. Please see [CONTRIBUTING](https://github.com/thephpleague/glide/blob/master/CONTRIBUTING.md) for details. ## Security If you discover any security related issues, please email jonathan@reinink.ca instead of using the issue tracker. ## Credits - [Jonathan Reinink](https://github.com/reinink) - [All Contributors](https://github.com/thephpleague/glide/contributors) ## License The MIT License (MIT). Please see [LICENSE](https://github.com/thephpleague/glide/blob/master/LICENSE) for more information. glide/LICENSE 0000644 00000002120 15021222733 0006625 0 ustar 00 The MIT License (MIT) Copyright (c) 2015 Jonathan Reinink <jonathan@reinink.ca> Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flysystem/readme.md 0000644 00000007170 15021222733 0010364 0 ustar 00 # League\Flysystem [](https://twitter.com/frankdejonge) [](https://github.com/thephpleague/flysystem) [](https://github.com/thephpleague/flysystem/releases) [](https://github.com/thephpleague/flysystem/blob/master/LICENSE) [](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22) [](https://packagist.org/packages/league/flysystem)  ## About Flysystem Flysystem is a file storage library for PHP. It provides one interface to interact with many types of filesystems. When you use Flysystem, you're not only protected from vendor lock-in, you'll also have a consistent experience for which ever storage is right for you. ## Getting Started * **[New in V3](https://flysystem.thephpleague.com/docs/what-is-new/)**: What is new in Flysystem V2/V3? * **[Architecture](https://flysystem.thephpleague.com/docs/architecture/)**: Flysystem's internal architecture * **[Flysystem API](https://flysystem.thephpleague.com/docs/usage/filesystem-api/)**: How to interact with your Flysystem instance * **[Upgrade from 1x](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/)**: How to upgrade from 1.x/2.x ### Officially supported adapters * **[Local](https://flysystem.thephpleague.com/docs/adapter/local/)** * **[FTP](https://flysystem.thephpleague.com/docs/adapter/ftp/)** * **[SFTP](https://flysystem.thephpleague.com/docs/adapter/sftp-v3/)** * **[Memory](https://flysystem.thephpleague.com/docs/adapter/in-memory/)** * **[AWS S3](https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/)** * **[AsyncAws S3](https://flysystem.thephpleague.com/docs/adapter/async-aws-s3/)** * **[Google Cloud Storage](https://flysystem.thephpleague.com/docs/adapter/google-cloud-storage/)** * **[Azure Blob Storage](https://flysystem.thephpleague.com/docs/adapter/azure-blob-storage/)** * **[WebDAV](https://flysystem.thephpleague.com/docs/adapter/webdav/)** * **[ZipArchive](https://flysystem.thephpleague.com/docs/adapter/zip-archive/)** ### Third party Adapters * **[Gitlab](https://github.com/RoyVoetman/flysystem-gitlab-storage)** * **[Google Drive (using regular paths)](https://github.com/masbug/flysystem-google-drive-ext)** * **[bunny.net / BunnyCDN](https://github.com/PlatformCommunity/flysystem-bunnycdn/tree/v3)** * **[Sharepoint 365 / One Drive (Using MS Graph)](https://github.com/shitware-ltd/flysystem-msgraph)** * **[OneDrive](https://github.com/doerffler/flysystem-onedrive)** * **[Dropbox](https://github.com/spatie/flysystem-dropbox)** * **[ReplicateAdapter](https://github.com/ajgarlag/flysystem-replicate)** * **[Uploadcare](https://github.com/vormkracht10/flysystem-uploadcare)** * **[Useful adapters (FallbackAdapter, LogAdapter, ReadWriteAdapter, RetryAdapter)](https://github.com/ElGigi/FlysystemUsefulAdapters)** You can always [create an adapter](https://flysystem.thephpleague.com/docs/advanced/creating-an-adapter/) yourself. ## Security If you discover any security related issues, please email info@frankdejonge.nl instead of using the issue tracker. ## Enjoy Oh, and if you've come down this far, you might as well follow me on [twitter](https://twitter.com/frankdejonge). flysystem/composer.json 0000644 00000004016 15021222733 0011323 0 ustar 00 { "name": "league/flysystem", "description": "File storage abstraction for PHP", "keywords": [ "filesystem", "filesystems", "files", "storage", "aws", "s3", "ftp", "sftp", "webdav", "file", "cloud" ], "scripts": { "phpstan": "vendor/bin/phpstan analyse -l 6 src" }, "type": "library", "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { "League\\Flysystem\\": "src" } }, "require": { "php": "^8.0.2", "league/flysystem-local": "^3.0.0", "league/mime-type-detection": "^1.0.0" }, "require-dev": { "ext-zip": "*", "ext-fileinfo": "*", "ext-ftp": "*", "ext-mongodb": "^1.3", "microsoft/azure-storage-blob": "^1.1", "phpunit/phpunit": "^9.5.11|^10.0", "phpstan/phpstan": "^1.10", "phpseclib/phpseclib": "^3.0.36", "aws/aws-sdk-php": "^3.295.10", "composer/semver": "^3.0", "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "async-aws/s3": "^1.5 || ^2.0", "async-aws/simple-s3": "^1.1 || ^2.0", "mongodb/mongodb": "^1.2", "sabre/dav": "^4.6.0", "guzzlehttp/psr7": "^2.6" }, "conflict": { "async-aws/core": "<1.19.0", "async-aws/s3": "<1.14.0", "symfony/http-client": "<5.2", "guzzlehttp/ringphp": "<1.1.1", "guzzlehttp/guzzle": "<7.0", "aws/aws-sdk-php": "3.209.31 || 3.210.0", "phpseclib/phpseclib": "3.0.15" }, "license": "MIT", "authors": [ { "name": "Frank de Jonge", "email": "info@frankdejonge.nl" } ], "repositories": [ { "type": "package", "package": { "name": "league/flysystem-local", "version": "3.0.0", "dist": { "type": "path", "url": "src/Local" } } } ] } flysystem/INFO.md 0000644 00000000213 15021222733 0007651 0 ustar 00 View the docs at: https://flysystem.thephpleague.com/docs/ Changelog at: https://github.com/thephpleague/flysystem/blob/3.x/CHANGELOG.md flysystem/src/UnixVisibility/VisibilityConverter.php 0000644 00000000621 15021222733 0017111 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UnixVisibility; interface VisibilityConverter { public function forFile(string $visibility): int; public function forDirectory(string $visibility): int; public function inverseForFile(int $visibility): string; public function inverseForDirectory(int $visibility): string; public function defaultForDirectories(): int; } flysystem/src/UnixVisibility/PortableVisibilityConverter.php 0000644 00000004521 15021222733 0020605 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UnixVisibility; use League\Flysystem\PortableVisibilityGuard; use League\Flysystem\Visibility; class PortableVisibilityConverter implements VisibilityConverter { public function __construct( private int $filePublic = 0644, private int $filePrivate = 0600, private int $directoryPublic = 0755, private int $directoryPrivate = 0700, private string $defaultForDirectories = Visibility::PRIVATE ) { } public function forFile(string $visibility): int { PortableVisibilityGuard::guardAgainstInvalidInput($visibility); return $visibility === Visibility::PUBLIC ? $this->filePublic : $this->filePrivate; } public function forDirectory(string $visibility): int { PortableVisibilityGuard::guardAgainstInvalidInput($visibility); return $visibility === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate; } public function inverseForFile(int $visibility): string { if ($visibility === $this->filePublic) { return Visibility::PUBLIC; } elseif ($visibility === $this->filePrivate) { return Visibility::PRIVATE; } return Visibility::PUBLIC; // default } public function inverseForDirectory(int $visibility): string { if ($visibility === $this->directoryPublic) { return Visibility::PUBLIC; } elseif ($visibility === $this->directoryPrivate) { return Visibility::PRIVATE; } return Visibility::PUBLIC; // default } public function defaultForDirectories(): int { return $this->defaultForDirectories === Visibility::PUBLIC ? $this->directoryPublic : $this->directoryPrivate; } /** * @param array<mixed> $permissionMap */ public static function fromArray(array $permissionMap, string $defaultForDirectories = Visibility::PRIVATE): PortableVisibilityConverter { return new PortableVisibilityConverter( $permissionMap['file']['public'] ?? 0644, $permissionMap['file']['private'] ?? 0600, $permissionMap['dir']['public'] ?? 0755, $permissionMap['dir']['private'] ?? 0700, $defaultForDirectories ); } } flysystem/src/PortableVisibilityGuard.php 0000644 00000000777 15021222733 0014716 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; final class PortableVisibilityGuard { public static function guardAgainstInvalidInput(string $visibility): void { if ($visibility !== Visibility::PUBLIC && $visibility !== Visibility::PRIVATE) { $className = Visibility::class; throw InvalidVisibilityProvided::withVisibility( $visibility, "either {$className}::PUBLIC or {$className}::PRIVATE" ); } } } flysystem/src/UnableToSetVisibility.php 0000644 00000001726 15021222733 0014343 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; use function rtrim; final class UnableToSetVisibility extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location; /** * @var string */ private $reason; public function reason(): string { return $this->reason; } public static function atLocation(string $filename, string $extraMessage = '', ?Throwable $previous = null): self { $message = "Unable to set visibility for file {$filename}. $extraMessage"; $e = new static(rtrim($message), 0, $previous); $e->reason = $extraMessage; $e->location = $filename; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_SET_VISIBILITY; } public function location(): string { return $this->location; } } flysystem/src/DecoratedAdapter.php 0000644 00000004565 15021222733 0013305 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; abstract class DecoratedAdapter implements FilesystemAdapter { public function __construct(protected FilesystemAdapter $adapter) { } public function fileExists(string $path): bool { return $this->adapter->fileExists($path); } public function directoryExists(string $path): bool { return $this->adapter->directoryExists($path); } public function write(string $path, string $contents, Config $config): void { $this->adapter->write($path, $contents, $config); } public function writeStream(string $path, $contents, Config $config): void { $this->adapter->writeStream($path, $contents, $config); } public function read(string $path): string { return $this->adapter->read($path); } public function readStream(string $path) { return $this->adapter->readStream($path); } public function delete(string $path): void { $this->adapter->delete($path); } public function deleteDirectory(string $path): void { $this->adapter->deleteDirectory($path); } public function createDirectory(string $path, Config $config): void { $this->adapter->createDirectory($path, $config); } public function setVisibility(string $path, string $visibility): void { $this->adapter->setVisibility($path, $visibility); } public function visibility(string $path): FileAttributes { return $this->adapter->visibility($path); } public function mimeType(string $path): FileAttributes { return $this->adapter->mimeType($path); } public function lastModified(string $path): FileAttributes { return $this->adapter->lastModified($path); } public function fileSize(string $path): FileAttributes { return $this->adapter->fileSize($path); } public function listContents(string $path, bool $deep): iterable { return $this->adapter->listContents($path, $deep); } public function move(string $source, string $destination, Config $config): void { $this->adapter->move($source, $destination, $config); } public function copy(string $source, string $destination, Config $config): void { $this->adapter->copy($source, $destination, $config); } } flysystem/src/UnableToProvideChecksum.php 0000644 00000000603 15021222733 0014624 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToProvideChecksum extends RuntimeException implements FilesystemException { public function __construct(string $reason, string $path, ?Throwable $previous = null) { parent::__construct("Unable to get checksum for $path: $reason", 0, $previous); } } flysystem/src/FilesystemWriter.php 0000644 00000002650 15021222733 0013424 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; interface FilesystemWriter { /** * @throws UnableToWriteFile * @throws FilesystemException */ public function write(string $location, string $contents, array $config = []): void; /** * @param mixed $contents * * @throws UnableToWriteFile * @throws FilesystemException */ public function writeStream(string $location, $contents, array $config = []): void; /** * @throws UnableToSetVisibility * @throws FilesystemException */ public function setVisibility(string $path, string $visibility): void; /** * @throws UnableToDeleteFile * @throws FilesystemException */ public function delete(string $location): void; /** * @throws UnableToDeleteDirectory * @throws FilesystemException */ public function deleteDirectory(string $location): void; /** * @throws UnableToCreateDirectory * @throws FilesystemException */ public function createDirectory(string $location, array $config = []): void; /** * @throws UnableToMoveFile * @throws FilesystemException */ public function move(string $source, string $destination, array $config = []): void; /** * @throws UnableToCopyFile * @throws FilesystemException */ public function copy(string $source, string $destination, array $config = []): void; } flysystem/src/UnableToCheckFileExistence.php 0000644 00000000367 15021222733 0015225 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class UnableToCheckFileExistence extends UnableToCheckExistence { public function operation(): string { return FilesystemOperationFailed::OPERATION_FILE_EXISTS; } } flysystem/src/Visibility.php 0000644 00000000243 15021222733 0012226 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; final class Visibility { public const PUBLIC = 'public'; public const PRIVATE = 'private'; } flysystem/src/CorruptedPathDetected.php 0000644 00000000474 15021222733 0014333 0 ustar 00 <?php namespace League\Flysystem; use RuntimeException; final class CorruptedPathDetected extends RuntimeException implements FilesystemException { public static function forPath(string $path): CorruptedPathDetected { return new CorruptedPathDetected("Corrupted path detected: " . $path); } } flysystem/src/UnableToMoveFile.php 0000644 00000003142 15021222733 0013240 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToMoveFile extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $source; /** * @var string */ private $destination; public static function sourceAndDestinationAreTheSame(string $source, string $destination): UnableToMoveFile { return UnableToMoveFile::because('Source and destination are the same', $source, $destination); } public function source(): string { return $this->source; } public function destination(): string { return $this->destination; } public static function fromLocationTo( string $sourcePath, string $destinationPath, ?Throwable $previous = null ): UnableToMoveFile { $message = $previous?->getMessage() ?? "Unable to move file from $sourcePath to $destinationPath"; $e = new static($message, 0, $previous); $e->source = $sourcePath; $e->destination = $destinationPath; return $e; } public static function because( string $reason, string $sourcePath, string $destinationPath, ): UnableToMoveFile { $message = "Unable to move file from $sourcePath to $destinationPath, because $reason"; $e = new static($message); $e->source = $sourcePath; $e->destination = $destinationPath; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_MOVE; } } flysystem/src/UnableToCreateDirectory.php 0000644 00000002516 15021222733 0014626 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToCreateDirectory extends RuntimeException implements FilesystemOperationFailed { private string $location; private string $reason = ''; public static function atLocation(string $dirname, string $errorMessage = '', ?Throwable $previous = null): UnableToCreateDirectory { $message = "Unable to create a directory at {$dirname}. {$errorMessage}"; $e = new static(rtrim($message), 0, $previous); $e->location = $dirname; $e->reason = $errorMessage; return $e; } public static function dueToFailure(string $dirname, Throwable $previous): UnableToCreateDirectory { $reason = $previous instanceof UnableToCreateDirectory ? $previous->reason() : ''; $message = "Unable to create a directory at $dirname. $reason"; $e = new static(rtrim($message), 0, $previous); $e->location = $dirname; $e->reason = $reason ?: $message; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_CREATE_DIRECTORY; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } } flysystem/src/InvalidStreamProvided.php 0000644 00000000341 15021222733 0014335 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use InvalidArgumentException as BaseInvalidArgumentException; class InvalidStreamProvided extends BaseInvalidArgumentException implements FilesystemException { } flysystem/src/Config.php 0000644 00000002615 15021222733 0011311 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use function array_diff_key; use function array_flip; use function array_merge; class Config { public const OPTION_COPY_IDENTICAL_PATH = 'copy_destination_same_as_source'; public const OPTION_MOVE_IDENTICAL_PATH = 'move_destination_same_as_source'; public const OPTION_VISIBILITY = 'visibility'; public const OPTION_DIRECTORY_VISIBILITY = 'directory_visibility'; public const OPTION_RETAIN_VISIBILITY = 'retain_visibility'; public function __construct(private array $options = []) { } /** * @param mixed $default * * @return mixed */ public function get(string $property, $default = null) { return $this->options[$property] ?? $default; } public function extend(array $options): Config { return new Config(array_merge($this->options, $options)); } public function withDefaults(array $defaults): Config { return new Config($this->options + $defaults); } public function toArray(): array { return $this->options; } public function withSetting(string $property, mixed $setting): Config { return $this->extend([$property => $setting]); } public function withoutSettings(string ...$settings): Config { return new Config(array_diff_key($this->options, array_flip($settings))); } } flysystem/src/FilesystemException.php 0000644 00000000202 15021222733 0014075 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use Throwable; interface FilesystemException extends Throwable { } flysystem/src/UnableToReadFile.php 0000644 00000001635 15021222733 0013212 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToReadFile extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location = ''; /** * @var string */ private $reason = ''; public static function fromLocation(string $location, string $reason = '', ?Throwable $previous = null): UnableToReadFile { $e = new static(rtrim("Unable to read file from location: {$location}. {$reason}"), 0, $previous); $e->location = $location; $e->reason = $reason; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_READ; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } } flysystem/src/Filesystem.php 0000644 00000022655 15021222733 0012236 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use DateTimeInterface; use Generator; use League\Flysystem\UrlGeneration\PrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\PublicUrlGenerator; use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator; use League\Flysystem\UrlGeneration\TemporaryUrlGenerator; use Throwable; use function array_key_exists; use function is_array; class Filesystem implements FilesystemOperator { use CalculateChecksumFromStream; private Config $config; private PathNormalizer $pathNormalizer; public function __construct( private FilesystemAdapter $adapter, array $config = [], ?PathNormalizer $pathNormalizer = null, private ?PublicUrlGenerator $publicUrlGenerator = null, private ?TemporaryUrlGenerator $temporaryUrlGenerator = null, ) { $this->config = new Config($config); $this->pathNormalizer = $pathNormalizer ?? new WhitespacePathNormalizer(); } public function fileExists(string $location): bool { return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location)); } public function directoryExists(string $location): bool { return $this->adapter->directoryExists($this->pathNormalizer->normalizePath($location)); } public function has(string $location): bool { $path = $this->pathNormalizer->normalizePath($location); return $this->adapter->fileExists($path) || $this->adapter->directoryExists($path); } public function write(string $location, string $contents, array $config = []): void { $this->adapter->write( $this->pathNormalizer->normalizePath($location), $contents, $this->config->extend($config) ); } public function writeStream(string $location, $contents, array $config = []): void { /* @var resource $contents */ $this->assertIsResource($contents); $this->rewindStream($contents); $this->adapter->writeStream( $this->pathNormalizer->normalizePath($location), $contents, $this->config->extend($config) ); } public function read(string $location): string { return $this->adapter->read($this->pathNormalizer->normalizePath($location)); } public function readStream(string $location) { return $this->adapter->readStream($this->pathNormalizer->normalizePath($location)); } public function delete(string $location): void { $this->adapter->delete($this->pathNormalizer->normalizePath($location)); } public function deleteDirectory(string $location): void { $this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location)); } public function createDirectory(string $location, array $config = []): void { $this->adapter->createDirectory( $this->pathNormalizer->normalizePath($location), $this->config->extend($config) ); } public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing { $path = $this->pathNormalizer->normalizePath($location); $listing = $this->adapter->listContents($path, $deep); return new DirectoryListing($this->pipeListing($location, $deep, $listing)); } private function pipeListing(string $location, bool $deep, iterable $listing): Generator { try { foreach ($listing as $item) { yield $item; } } catch (Throwable $exception) { throw UnableToListContents::atLocation($location, $deep, $exception); } } public function move(string $source, string $destination, array $config = []): void { $config = $this->resolveConfigForMoveAndCopy($config); $from = $this->pathNormalizer->normalizePath($source); $to = $this->pathNormalizer->normalizePath($destination); if ($from === $to) { $resolutionStrategy = $config->get(Config::OPTION_MOVE_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY); if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) { throw UnableToMoveFile::sourceAndDestinationAreTheSame($source, $destination); } elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) { return; } } $this->adapter->move($from, $to, $config); } public function copy(string $source, string $destination, array $config = []): void { $config = $this->resolveConfigForMoveAndCopy($config); $from = $this->pathNormalizer->normalizePath($source); $to = $this->pathNormalizer->normalizePath($destination); if ($from === $to) { $resolutionStrategy = $config->get(Config::OPTION_COPY_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY); if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) { throw UnableToCopyFile::sourceAndDestinationAreTheSame($source, $destination); } elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) { return; } } $this->adapter->copy($from, $to, $config); } public function lastModified(string $path): int { return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified(); } public function fileSize(string $path): int { return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize(); } public function mimeType(string $path): string { return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType(); } public function setVisibility(string $path, string $visibility): void { $this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility); } public function visibility(string $path): string { return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility(); } public function publicUrl(string $path, array $config = []): string { $this->publicUrlGenerator ??= $this->resolvePublicUrlGenerator() ?? throw UnableToGeneratePublicUrl::noGeneratorConfigured($path); $config = $this->config->extend($config); return $this->publicUrlGenerator->publicUrl($this->pathNormalizer->normalizePath($path), $config); } public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string { $generator = $this->temporaryUrlGenerator ?? $this->adapter; if ($generator instanceof TemporaryUrlGenerator) { return $generator->temporaryUrl( $this->pathNormalizer->normalizePath($path), $expiresAt, $this->config->extend($config) ); } throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path); } public function checksum(string $path, array $config = []): string { $config = $this->config->extend($config); if ( ! $this->adapter instanceof ChecksumProvider) { return $this->calculateChecksumFromStream($path, $config); } try { return $this->adapter->checksum($path, $config); } catch (ChecksumAlgoIsNotSupported) { return $this->calculateChecksumFromStream($path, $config); } } private function resolvePublicUrlGenerator(): ?PublicUrlGenerator { if ($publicUrl = $this->config->get('public_url')) { return match (true) { is_array($publicUrl) => new ShardedPrefixPublicUrlGenerator($publicUrl), default => new PrefixPublicUrlGenerator($publicUrl), }; } if ($this->adapter instanceof PublicUrlGenerator) { return $this->adapter; } return null; } /** * @param mixed $contents */ private function assertIsResource($contents): void { if (is_resource($contents) === false) { throw new InvalidStreamProvided( "Invalid stream provided, expected stream resource, received " . gettype($contents) ); } elseif ($type = get_resource_type($contents) !== 'stream') { throw new InvalidStreamProvided( "Invalid stream provided, expected stream resource, received resource of type " . $type ); } } /** * @param resource $resource */ private function rewindStream($resource): void { if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) { rewind($resource); } } private function resolveConfigForMoveAndCopy(array $config): Config { $retainVisibility = $this->config->get(Config::OPTION_RETAIN_VISIBILITY, $config[Config::OPTION_RETAIN_VISIBILITY] ?? true); $fullConfig = $this->config->extend($config); /* * By default, we retain visibility. When we do not retain visibility, the visibility setting * from the default configuration is ignored. Only when it is set explicitly, we propagate the * setting. */ if ($retainVisibility && ! array_key_exists(Config::OPTION_VISIBILITY, $config)) { $fullConfig = $fullConfig->withoutSettings(Config::OPTION_VISIBILITY)->extend($config); } return $fullConfig; } } flysystem/src/DirectoryListing.php 0000644 00000004036 15021222733 0013401 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use ArrayIterator; use Generator; use IteratorAggregate; use Traversable; /** * @template T */ class DirectoryListing implements IteratorAggregate { /** * @param iterable<T> $listing */ public function __construct(private iterable $listing) { } /** * @param callable(T): bool $filter * * @return DirectoryListing<T> */ public function filter(callable $filter): DirectoryListing { $generator = (static function (iterable $listing) use ($filter): Generator { foreach ($listing as $item) { if ($filter($item)) { yield $item; } } })($this->listing); return new DirectoryListing($generator); } /** * @template R * * @param callable(T): R $mapper * * @return DirectoryListing<R> */ public function map(callable $mapper): DirectoryListing { $generator = (static function (iterable $listing) use ($mapper): Generator { foreach ($listing as $item) { yield $mapper($item); } })($this->listing); return new DirectoryListing($generator); } /** * @return DirectoryListing<T> */ public function sortByPath(): DirectoryListing { $listing = $this->toArray(); usort($listing, function (StorageAttributes $a, StorageAttributes $b) { return $a->path() <=> $b->path(); }); return new DirectoryListing($listing); } /** * @return Traversable<T> */ public function getIterator(): Traversable { return $this->listing instanceof Traversable ? $this->listing : new ArrayIterator($this->listing); } /** * @return T[] */ public function toArray(): array { return $this->listing instanceof Traversable ? iterator_to_array($this->listing, false) : (array) $this->listing; } } flysystem/src/PathPrefixer.php 0000644 00000002206 15021222733 0012501 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use function rtrim; use function strlen; use function substr; final class PathPrefixer { private string $prefix = ''; public function __construct(string $prefix, private string $separator = '/') { $this->prefix = rtrim($prefix, '\\/'); if ($this->prefix !== '' || $prefix === $separator) { $this->prefix .= $separator; } } public function prefixPath(string $path): string { return $this->prefix . ltrim($path, '\\/'); } public function stripPrefix(string $path): string { /* @var string */ return substr($path, strlen($this->prefix)); } public function stripDirectoryPrefix(string $path): string { return rtrim($this->stripPrefix($path), '\\/'); } public function prefixDirectoryPath(string $path): string { $prefixedPath = $this->prefixPath(rtrim($path, '\\/')); if ($prefixedPath === '' || substr($prefixedPath, -1) === $this->separator) { return $prefixedPath; } return $prefixedPath . $this->separator; } } flysystem/src/UnableToWriteFile.php 0000644 00000001630 15021222733 0013424 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToWriteFile extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location = ''; /** * @var string */ private $reason; public static function atLocation(string $location, string $reason = '', ?Throwable $previous = null): UnableToWriteFile { $e = new static(rtrim("Unable to write file at location: {$location}. {$reason}"), 0, $previous); $e->location = $location; $e->reason = $reason; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_WRITE; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } } flysystem/src/FileAttributes.php 0000644 00000005002 15021222733 0013023 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class FileAttributes implements StorageAttributes { use ProxyArrayAccessToProperties; private string $type = StorageAttributes::TYPE_FILE; public function __construct( private string $path, private ?int $fileSize = null, private ?string $visibility = null, private ?int $lastModified = null, private ?string $mimeType = null, private array $extraMetadata = [] ) { $this->path = ltrim($this->path, '/'); } public function type(): string { return $this->type; } public function path(): string { return $this->path; } public function fileSize(): ?int { return $this->fileSize; } public function visibility(): ?string { return $this->visibility; } public function lastModified(): ?int { return $this->lastModified; } public function mimeType(): ?string { return $this->mimeType; } public function extraMetadata(): array { return $this->extraMetadata; } public function isFile(): bool { return true; } public function isDir(): bool { return false; } public function withPath(string $path): self { $clone = clone $this; $clone->path = $path; return $clone; } public static function fromArray(array $attributes): self { return new FileAttributes( $attributes[StorageAttributes::ATTRIBUTE_PATH], $attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null, $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null, $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null, $attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null, $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? [] ); } public function jsonSerialize(): array { return [ StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE, StorageAttributes::ATTRIBUTE_PATH => $this->path, StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize, StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility, StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified, StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType, StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata, ]; } } flysystem/src/UrlGeneration/ChainedPublicUrlGenerator.php 0000644 00000001353 15021222733 0017704 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UrlGeneration; use League\Flysystem\Config; use League\Flysystem\UnableToGeneratePublicUrl; final class ChainedPublicUrlGenerator implements PublicUrlGenerator { /** * @param PublicUrlGenerator[] $generators */ public function __construct(private iterable $generators) { } public function publicUrl(string $path, Config $config): string { foreach ($this->generators as $generator) { try { return $generator->publicUrl($path, $config); } catch (UnableToGeneratePublicUrl) { } } throw new UnableToGeneratePublicUrl('No supported public url generator found.', $path); } } flysystem/src/UrlGeneration/ShardedPrefixPublicUrlGenerator.php 0000644 00000001674 15021222733 0021107 0 ustar 00 <?php namespace League\Flysystem\UrlGeneration; use InvalidArgumentException; use League\Flysystem\Config; use League\Flysystem\PathPrefixer; use function array_map; use function count; use function crc32; final class ShardedPrefixPublicUrlGenerator implements PublicUrlGenerator { /** @var PathPrefixer[] */ private array $prefixes; private int $count; /** * @param string[] $prefixes */ public function __construct(array $prefixes) { $this->count = count($prefixes); if ($this->count === 0) { throw new InvalidArgumentException('At least one prefix is required.'); } $this->prefixes = array_map(static fn (string $prefix) => new PathPrefixer($prefix, '/'), $prefixes); } public function publicUrl(string $path, Config $config): string { $index = abs(crc32($path)) % $this->count; return $this->prefixes[$index]->prefixPath($path); } } flysystem/src/UrlGeneration/TemporaryUrlGenerator.php 0000644 00000000571 15021222733 0017175 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UrlGeneration; use DateTimeInterface; use League\Flysystem\Config; use League\Flysystem\UnableToGenerateTemporaryUrl; interface TemporaryUrlGenerator { /** * @throws UnableToGenerateTemporaryUrl */ public function temporaryUrl(string $path, DateTimeInterface $expiresAt, Config $config): string; } flysystem/src/UrlGeneration/PublicUrlGenerator.php 0000644 00000000471 15021222733 0016430 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UrlGeneration; use League\Flysystem\Config; use League\Flysystem\UnableToGeneratePublicUrl; interface PublicUrlGenerator { /** * @throws UnableToGeneratePublicUrl */ public function publicUrl(string $path, Config $config): string; } flysystem/src/UrlGeneration/PrefixPublicUrlGenerator.php 0000644 00000000763 15021222733 0017612 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\UrlGeneration; use League\Flysystem\Config; use League\Flysystem\PathPrefixer; class PrefixPublicUrlGenerator implements PublicUrlGenerator { private PathPrefixer $prefixer; public function __construct(string $urlPrefix) { $this->prefixer = new PathPrefixer($urlPrefix, '/'); } public function publicUrl(string $path, Config $config): string { return $this->prefixer->prefixPath($path); } } flysystem/src/MountManager.php 0000644 00000035777 15021222733 0012520 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use DateTimeInterface; use Throwable; use function compact; use function method_exists; use function sprintf; class MountManager implements FilesystemOperator { /** * @var array<string, FilesystemOperator> */ private $filesystems = []; /** * @var Config */ private $config; /** * MountManager constructor. * * @param array<string,FilesystemOperator> $filesystems */ public function __construct(array $filesystems = [], array $config = []) { $this->mountFilesystems($filesystems); $this->config = new Config($config); } /** * It is not recommended to mount filesystems after creation because interacting * with the Mount Manager becomes unpredictable. Use this as an escape hatch. */ public function dangerouslyMountFilesystems(string $key, FilesystemOperator $filesystem): void { $this->mountFilesystem($key, $filesystem); } /** * @param array<string,FilesystemOperator> $filesystems */ public function extend(array $filesystems, array $config = []): MountManager { $clone = clone $this; $clone->config = $this->config->extend($config); $clone->mountFilesystems($filesystems); return $clone; } public function fileExists(string $location): bool { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->fileExists($path); } catch (Throwable $exception) { throw UnableToCheckFileExistence::forLocation($location, $exception); } } public function has(string $location): bool { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->fileExists($path) || $filesystem->directoryExists($path); } catch (Throwable $exception) { throw UnableToCheckExistence::forLocation($location, $exception); } } public function directoryExists(string $location): bool { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->directoryExists($path); } catch (Throwable $exception) { throw UnableToCheckDirectoryExistence::forLocation($location, $exception); } } public function read(string $location): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->read($path); } catch (UnableToReadFile $exception) { throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception); } } public function readStream(string $location) { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->readStream($path); } catch (UnableToReadFile $exception) { throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception); } } public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing { /** @var FilesystemOperator $filesystem */ [$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location); return $filesystem ->listContents($path, $deep) ->map( function (StorageAttributes $attributes) use ($mountIdentifier) { return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path())); } ); } public function lastModified(string $location): int { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->lastModified($path); } catch (UnableToRetrieveMetadata $exception) { throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception); } } public function fileSize(string $location): int { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->fileSize($path); } catch (UnableToRetrieveMetadata $exception) { throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception); } } public function mimeType(string $location): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { return $filesystem->mimeType($path); } catch (UnableToRetrieveMetadata $exception) { throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception); } } public function visibility(string $path): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $location] = $this->determineFilesystemAndPath($path); try { return $filesystem->visibility($location); } catch (UnableToRetrieveMetadata $exception) { throw UnableToRetrieveMetadata::visibility($path, $exception->reason(), $exception); } } public function write(string $location, string $contents, array $config = []): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { $filesystem->write($path, $contents, $this->config->extend($config)->toArray()); } catch (UnableToWriteFile $exception) { throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception); } } public function writeStream(string $location, $contents, array $config = []): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); $filesystem->writeStream($path, $contents, $this->config->extend($config)->toArray()); } public function setVisibility(string $path, string $visibility): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($path); $filesystem->setVisibility($path, $visibility); } public function delete(string $location): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { $filesystem->delete($path); } catch (UnableToDeleteFile $exception) { throw UnableToDeleteFile::atLocation($location, $exception->reason(), $exception); } } public function deleteDirectory(string $location): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { $filesystem->deleteDirectory($path); } catch (UnableToDeleteDirectory $exception) { throw UnableToDeleteDirectory::atLocation($location, $exception->reason(), $exception); } } public function createDirectory(string $location, array $config = []): void { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($location); try { $filesystem->createDirectory($path, $this->config->extend($config)->toArray()); } catch (UnableToCreateDirectory $exception) { throw UnableToCreateDirectory::dueToFailure($location, $exception); } } public function move(string $source, string $destination, array $config = []): void { /** @var FilesystemOperator $sourceFilesystem */ /* @var FilesystemOperator $destinationFilesystem */ [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source); [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination); $sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem( $sourceFilesystem, $sourcePath, $destinationPath, $source, $destination, $config, ) : $this->moveAcrossFilesystems($source, $destination, $config); } public function copy(string $source, string $destination, array $config = []): void { /** @var FilesystemOperator $sourceFilesystem */ /* @var FilesystemOperator $destinationFilesystem */ [$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source); [$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination); $sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem( $sourceFilesystem, $sourcePath, $destinationPath, $source, $destination, $config, ) : $this->copyAcrossFilesystem( $sourceFilesystem, $sourcePath, $destinationFilesystem, $destinationPath, $source, $destination, $config, ); } public function publicUrl(string $path, array $config = []): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($path); if ( ! method_exists($filesystem, 'publicUrl')) { throw new UnableToGeneratePublicUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path); } return $filesystem->publicUrl($path, $config); } public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($path); if ( ! method_exists($filesystem, 'temporaryUrl')) { throw new UnableToGenerateTemporaryUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path); } return $filesystem->temporaryUrl($path, $expiresAt, $this->config->extend($config)->toArray()); } public function checksum(string $path, array $config = []): string { /** @var FilesystemOperator $filesystem */ [$filesystem, $path] = $this->determineFilesystemAndPath($path); if ( ! method_exists($filesystem, 'checksum')) { throw new UnableToProvideChecksum(sprintf('%s does not support providing checksums.', $filesystem::class), $path); } return $filesystem->checksum($path, $this->config->extend($config)->toArray()); } private function mountFilesystems(array $filesystems): void { foreach ($filesystems as $key => $filesystem) { $this->guardAgainstInvalidMount($key, $filesystem); /* @var string $key */ /* @var FilesystemOperator $filesystem */ $this->mountFilesystem($key, $filesystem); } } private function guardAgainstInvalidMount(mixed $key, mixed $filesystem): void { if ( ! is_string($key)) { throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key); } if ( ! $filesystem instanceof FilesystemOperator) { throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem); } } private function mountFilesystem(string $key, FilesystemOperator $filesystem): void { $this->filesystems[$key] = $filesystem; } /** * @param string $path * * @return array{0:FilesystemOperator, 1:string, 2:string} */ private function determineFilesystemAndPath(string $path): array { if (strpos($path, '://') < 1) { throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path); } /** @var string $mountIdentifier */ /** @var string $mountPath */ [$mountIdentifier, $mountPath] = explode('://', $path, 2); if ( ! array_key_exists($mountIdentifier, $this->filesystems)) { throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier); } return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier]; } private function copyInSameFilesystem( FilesystemOperator $sourceFilesystem, string $sourcePath, string $destinationPath, string $source, string $destination, array $config, ): void { try { $sourceFilesystem->copy($sourcePath, $destinationPath, $this->config->extend($config)->toArray()); } catch (UnableToCopyFile $exception) { throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); } } private function copyAcrossFilesystem( FilesystemOperator $sourceFilesystem, string $sourcePath, FilesystemOperator $destinationFilesystem, string $destinationPath, string $source, string $destination, array $config, ): void { $config = $this->config->extend($config); $retainVisibility = (bool) $config->get(Config::OPTION_RETAIN_VISIBILITY, true); $visibility = $config->get(Config::OPTION_VISIBILITY); try { if ($visibility == null && $retainVisibility) { $visibility = $sourceFilesystem->visibility($sourcePath); $config = $config->extend(compact('visibility')); } $stream = $sourceFilesystem->readStream($sourcePath); $destinationFilesystem->writeStream($destinationPath, $stream, $config->toArray()); } catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) { throw UnableToCopyFile::fromLocationTo($source, $destination, $exception); } } private function moveInTheSameFilesystem( FilesystemOperator $sourceFilesystem, string $sourcePath, string $destinationPath, string $source, string $destination, array $config, ): void { try { $sourceFilesystem->move($sourcePath, $destinationPath, $this->config->extend($config)->toArray()); } catch (UnableToMoveFile $exception) { throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); } } private function moveAcrossFilesystems(string $source, string $destination, array $config = []): void { try { $this->copy($source, $destination, $config); $this->delete($source); } catch (UnableToCopyFile | UnableToDeleteFile $exception) { throw UnableToMoveFile::fromLocationTo($source, $destination, $exception); } } } flysystem/src/UnableToCheckDirectoryExistence.php 0000644 00000000401 15021222733 0016277 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class UnableToCheckDirectoryExistence extends UnableToCheckExistence { public function operation(): string { return FilesystemOperationFailed::OPERATION_DIRECTORY_EXISTS; } } flysystem/src/PathTraversalDetected.php 0000644 00000000742 15021222733 0014325 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; class PathTraversalDetected extends RuntimeException implements FilesystemException { private string $path; public function path(): string { return $this->path; } public static function forPath(string $path): PathTraversalDetected { $e = new PathTraversalDetected("Path traversal detected: {$path}"); $e->path = $path; return $e; } } flysystem/src/UnableToDeleteFile.php 0000644 00000001633 15021222733 0013537 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToDeleteFile extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location = ''; /** * @var string */ private $reason; public static function atLocation(string $location, string $reason = '', ?Throwable $previous = null): UnableToDeleteFile { $e = new static(rtrim("Unable to delete file located at: {$location}. {$reason}"), 0, $previous); $e->location = $location; $e->reason = $reason; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_DELETE; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } } flysystem/src/UnableToDeleteDirectory.php 0000644 00000001716 15021222733 0014626 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToDeleteDirectory extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location = ''; /** * @var string */ private $reason; public static function atLocation( string $location, string $reason = '', ?Throwable $previous = null ): UnableToDeleteDirectory { $e = new static(rtrim("Unable to delete directory located at: {$location}. {$reason}"), 0, $previous); $e->location = $location; $e->reason = $reason; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_DELETE_DIRECTORY; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } } flysystem/src/DirectoryAttributes.php 0000644 00000004027 15021222733 0014116 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class DirectoryAttributes implements StorageAttributes { use ProxyArrayAccessToProperties; private string $type = StorageAttributes::TYPE_DIRECTORY; public function __construct( private string $path, private ?string $visibility = null, private ?int $lastModified = null, private array $extraMetadata = [] ) { $this->path = trim($this->path, '/'); } public function path(): string { return $this->path; } public function type(): string { return $this->type; } public function visibility(): ?string { return $this->visibility; } public function lastModified(): ?int { return $this->lastModified; } public function extraMetadata(): array { return $this->extraMetadata; } public function isFile(): bool { return false; } public function isDir(): bool { return true; } public function withPath(string $path): self { $clone = clone $this; $clone->path = $path; return $clone; } public static function fromArray(array $attributes): self { return new DirectoryAttributes( $attributes[StorageAttributes::ATTRIBUTE_PATH], $attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null, $attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null, $attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? [] ); } /** * @inheritDoc */ public function jsonSerialize(): array { return [ StorageAttributes::ATTRIBUTE_TYPE => $this->type, StorageAttributes::ATTRIBUTE_PATH => $this->path, StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility, StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified, StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata, ]; } } flysystem/src/ResolveIdenticalPathConflict.php 0000644 00000000306 15021222733 0015632 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class ResolveIdenticalPathConflict { public const IGNORE = 'ignore'; public const FAIL = 'fail'; public const TRY = 'try'; } flysystem/src/FilesystemReader.php 0000644 00000004171 15021222733 0013352 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use DateTimeInterface; /** * This interface contains everything to read from and inspect * a filesystem. All methods containing are non-destructive. * * @method string publicUrl(string $path, array $config = []) Will be added in 4.0 * @method string temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []) Will be added in 4.0 * @method string checksum(string $path, array $config = []) Will be added in 4.0 */ interface FilesystemReader { public const LIST_SHALLOW = false; public const LIST_DEEP = true; /** * @throws FilesystemException * @throws UnableToCheckExistence */ public function fileExists(string $location): bool; /** * @throws FilesystemException * @throws UnableToCheckExistence */ public function directoryExists(string $location): bool; /** * @throws FilesystemException * @throws UnableToCheckExistence */ public function has(string $location): bool; /** * @throws UnableToReadFile * @throws FilesystemException */ public function read(string $location): string; /** * @return resource * * @throws UnableToReadFile * @throws FilesystemException */ public function readStream(string $location); /** * @return DirectoryListing<StorageAttributes> * * @throws FilesystemException * @throws UnableToListContents */ public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function lastModified(string $path): int; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function fileSize(string $path): int; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function mimeType(string $path): string; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function visibility(string $path): string; } flysystem/src/ChecksumProvider.php 0000644 00000000443 15021222733 0013356 0 ustar 00 <?php namespace League\Flysystem; interface ChecksumProvider { /** * @return string MD5 hash of the file contents * * @throws UnableToProvideChecksum * @throws ChecksumAlgoIsNotSupported */ public function checksum(string $path, Config $config): string; } flysystem/src/CalculateChecksumFromStream.php 0000644 00000001431 15021222733 0015457 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use function hash_final; use function hash_init; use function hash_update_stream; trait CalculateChecksumFromStream { private function calculateChecksumFromStream(string $path, Config $config): string { try { $stream = $this->readStream($path); $algo = (string) $config->get('checksum_algo', 'md5'); $context = hash_init($algo); hash_update_stream($context, $stream); return hash_final($context); } catch (FilesystemException $exception) { throw new UnableToProvideChecksum($exception->getMessage(), $path, $exception); } } /** * @return resource */ abstract public function readStream(string $path); } flysystem/src/UnableToCopyFile.php 0000644 00000002762 15021222733 0013253 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToCopyFile extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $source; /** * @var string */ private $destination; public function source(): string { return $this->source; } public function destination(): string { return $this->destination; } public static function fromLocationTo( string $sourcePath, string $destinationPath, ?Throwable $previous = null ): UnableToCopyFile { $e = new static("Unable to copy file from $sourcePath to $destinationPath", 0 , $previous); $e->source = $sourcePath; $e->destination = $destinationPath; return $e; } public static function sourceAndDestinationAreTheSame(string $source, string $destination): UnableToCopyFile { return UnableToCopyFile::because('Source and destination are the same', $source, $destination); } public static function because(string $reason, string $sourcePath, string $destinationPath): UnableToCopyFile { $e = new static("Unable to copy file from $sourcePath to $destinationPath, because $reason"); $e->source = $sourcePath; $e->destination = $destinationPath; return $e; } public function operation(): string { return FilesystemOperationFailed::OPERATION_COPY; } } flysystem/src/PathNormalizer.php 0000644 00000000224 15021222733 0013035 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; interface PathNormalizer { public function normalizePath(string $path): string; } flysystem/src/FilesystemAdapter.php 0000644 00000005340 15021222733 0013527 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; interface FilesystemAdapter { /** * @throws FilesystemException * @throws UnableToCheckExistence */ public function fileExists(string $path): bool; /** * @throws FilesystemException * @throws UnableToCheckExistence */ public function directoryExists(string $path): bool; /** * @throws UnableToWriteFile * @throws FilesystemException */ public function write(string $path, string $contents, Config $config): void; /** * @param resource $contents * * @throws UnableToWriteFile * @throws FilesystemException */ public function writeStream(string $path, $contents, Config $config): void; /** * @throws UnableToReadFile * @throws FilesystemException */ public function read(string $path): string; /** * @return resource * * @throws UnableToReadFile * @throws FilesystemException */ public function readStream(string $path); /** * @throws UnableToDeleteFile * @throws FilesystemException */ public function delete(string $path): void; /** * @throws UnableToDeleteDirectory * @throws FilesystemException */ public function deleteDirectory(string $path): void; /** * @throws UnableToCreateDirectory * @throws FilesystemException */ public function createDirectory(string $path, Config $config): void; /** * @throws InvalidVisibilityProvided * @throws FilesystemException */ public function setVisibility(string $path, string $visibility): void; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function visibility(string $path): FileAttributes; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function mimeType(string $path): FileAttributes; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function lastModified(string $path): FileAttributes; /** * @throws UnableToRetrieveMetadata * @throws FilesystemException */ public function fileSize(string $path): FileAttributes; /** * @return iterable<StorageAttributes> * * @throws FilesystemException */ public function listContents(string $path, bool $deep): iterable; /** * @throws UnableToMoveFile * @throws FilesystemException */ public function move(string $source, string $destination, Config $config): void; /** * @throws UnableToCopyFile * @throws FilesystemException */ public function copy(string $source, string $destination, Config $config): void; } flysystem/src/UnableToListContents.php 0000644 00000001212 15021222733 0014157 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToListContents extends RuntimeException implements FilesystemOperationFailed { public static function atLocation(string $location, bool $deep, Throwable $previous): UnableToListContents { $message = "Unable to list contents for '$location', " . ($deep ? 'deep' : 'shallow') . " listing\n\n" . 'Reason: ' . $previous->getMessage(); return new UnableToListContents($message, 0, $previous); } public function operation(): string { return self::OPERATION_LIST_CONTENTS; } } flysystem/src/WhitespacePathNormalizer.php 0000644 00000002210 15021222733 0015047 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; class WhitespacePathNormalizer implements PathNormalizer { public function normalizePath(string $path): string { $path = str_replace('\\', '/', $path); $this->rejectFunkyWhiteSpace($path); return $this->normalizeRelativePath($path); } private function rejectFunkyWhiteSpace(string $path): void { if (preg_match('#\p{C}+#u', $path)) { throw CorruptedPathDetected::forPath($path); } } private function normalizeRelativePath(string $path): string { $parts = []; foreach (explode('/', $path) as $part) { switch ($part) { case '': case '.': break; case '..': if (empty($parts)) { throw PathTraversalDetected::forPath($path); } array_pop($parts); break; default: $parts[] = $part; break; } } return implode('/', $parts); } } flysystem/src/UnableToCheckExistence.php 0000644 00000001246 15021222733 0014422 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; class UnableToCheckExistence extends RuntimeException implements FilesystemOperationFailed { final public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); } public static function forLocation(string $path, ?Throwable $exception = null): static { return new static("Unable to check existence for: {$path}", 0, $exception); } public function operation(): string { return FilesystemOperationFailed::OPERATION_EXISTENCE_CHECK; } } flysystem/src/FilesystemOperationFailed.php 0000644 00000001672 15021222733 0015220 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; interface FilesystemOperationFailed extends FilesystemException { public const OPERATION_WRITE = 'WRITE'; public const OPERATION_UPDATE = 'UPDATE'; // not used public const OPERATION_EXISTENCE_CHECK = 'EXISTENCE_CHECK'; public const OPERATION_DIRECTORY_EXISTS = 'DIRECTORY_EXISTS'; public const OPERATION_FILE_EXISTS = 'FILE_EXISTS'; public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY'; public const OPERATION_DELETE = 'DELETE'; public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY'; public const OPERATION_MOVE = 'MOVE'; public const OPERATION_RETRIEVE_METADATA = 'RETRIEVE_METADATA'; public const OPERATION_COPY = 'COPY'; public const OPERATION_READ = 'READ'; public const OPERATION_SET_VISIBILITY = 'SET_VISIBILITY'; public const OPERATION_LIST_CONTENTS = 'LIST_CONTENTS'; public function operation(): string; } flysystem/src/UnableToResolveFilesystemMount.php 0000644 00000001323 15021222733 0016240 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; class UnableToResolveFilesystemMount extends RuntimeException implements FilesystemException { public static function becauseTheSeparatorIsMissing(string $path): UnableToResolveFilesystemMount { return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the path ($path) is missing a separator (://)."); } public static function becauseTheMountWasNotRegistered(string $mountIdentifier): UnableToResolveFilesystemMount { return new UnableToResolveFilesystemMount("Unable to resolve the filesystem mount because the mount ($mountIdentifier) was not registered."); } } flysystem/src/StorageAttributes.php 0000644 00000002013 15021222733 0013547 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use ArrayAccess; use JsonSerializable; interface StorageAttributes extends JsonSerializable, ArrayAccess { public const ATTRIBUTE_PATH = 'path'; public const ATTRIBUTE_TYPE = 'type'; public const ATTRIBUTE_FILE_SIZE = 'file_size'; public const ATTRIBUTE_VISIBILITY = 'visibility'; public const ATTRIBUTE_LAST_MODIFIED = 'last_modified'; public const ATTRIBUTE_MIME_TYPE = 'mime_type'; public const ATTRIBUTE_EXTRA_METADATA = 'extra_metadata'; public const TYPE_FILE = 'file'; public const TYPE_DIRECTORY = 'dir'; public function path(): string; public function type(): string; public function visibility(): ?string; public function lastModified(): ?int; public static function fromArray(array $attributes): StorageAttributes; public function isFile(): bool; public function isDir(): bool; public function withPath(string $path): StorageAttributes; public function extraMetadata(): array; } flysystem/src/UnableToGenerateTemporaryUrl.php 0000644 00000001370 15021222733 0015653 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToGenerateTemporaryUrl extends RuntimeException implements FilesystemException { public function __construct(string $reason, string $path, ?Throwable $previous = null) { parent::__construct("Unable to generate temporary url for $path: $reason", 0, $previous); } public static function dueToError(string $path, Throwable $exception): static { return new static($exception->getMessage(), $path, $exception); } public static function noGeneratorConfigured(string $path, string $extraReason = ''): static { return new static('No generator was configured ' . $extraReason, $path); } } flysystem/src/UnableToMountFilesystem.php 0000644 00000001623 15021222733 0014703 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use LogicException; class UnableToMountFilesystem extends LogicException implements FilesystemException { /** * @param mixed $key */ public static function becauseTheKeyIsNotValid($key): UnableToMountFilesystem { return new UnableToMountFilesystem( 'Unable to mount filesystem, key was invalid. String expected, received: ' . gettype($key) ); } /** * @param mixed $filesystem */ public static function becauseTheFilesystemWasNotValid($filesystem): UnableToMountFilesystem { $received = is_object($filesystem) ? get_class($filesystem) : gettype($filesystem); return new UnableToMountFilesystem( 'Unable to mount filesystem, filesystem was invalid. Instance of ' . FilesystemOperator::class . ' expected, received: ' . $received ); } } flysystem/src/FilesystemOperator.php 0000644 00000000212 15021222733 0013733 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; interface FilesystemOperator extends FilesystemReader, FilesystemWriter { } flysystem/src/UnableToRetrieveMetadata.php 0000644 00000003753 15021222733 0014770 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToRetrieveMetadata extends RuntimeException implements FilesystemOperationFailed { /** * @var string */ private $location; /** * @var string */ private $metadataType; /** * @var string */ private $reason; public static function lastModified(string $location, string $reason = '', ?Throwable $previous = null): self { return static::create($location, FileAttributes::ATTRIBUTE_LAST_MODIFIED, $reason, $previous); } public static function visibility(string $location, string $reason = '', ?Throwable $previous = null): self { return static::create($location, FileAttributes::ATTRIBUTE_VISIBILITY, $reason, $previous); } public static function fileSize(string $location, string $reason = '', ?Throwable $previous = null): self { return static::create($location, FileAttributes::ATTRIBUTE_FILE_SIZE, $reason, $previous); } public static function mimeType(string $location, string $reason = '', ?Throwable $previous = null): self { return static::create($location, FileAttributes::ATTRIBUTE_MIME_TYPE, $reason, $previous); } public static function create(string $location, string $type, string $reason = '', ?Throwable $previous = null): self { $e = new static("Unable to retrieve the $type for file at location: $location. {$reason}", 0, $previous); $e->reason = $reason; $e->location = $location; $e->metadataType = $type; return $e; } public function reason(): string { return $this->reason; } public function location(): string { return $this->location; } public function metadataType(): string { return $this->metadataType; } public function operation(): string { return FilesystemOperationFailed::OPERATION_RETRIEVE_METADATA; } } flysystem/src/ChecksumAlgoIsNotSupported.php 0000644 00000000251 15021222733 0015326 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use InvalidArgumentException; final class ChecksumAlgoIsNotSupported extends InvalidArgumentException { } flysystem/src/SymbolicLinkEncountered.php 0000644 00000001023 15021222733 0014667 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; final class SymbolicLinkEncountered extends RuntimeException implements FilesystemException { private string $location; public function location(): string { return $this->location; } public static function atLocation(string $pathName): SymbolicLinkEncountered { $e = new static("Unsupported symbolic link encountered at location $pathName"); $e->location = $pathName; return $e; } } flysystem/src/UnableToGeneratePublicUrl.php 0000644 00000001362 15021222733 0015110 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; use Throwable; final class UnableToGeneratePublicUrl extends RuntimeException implements FilesystemException { public function __construct(string $reason, string $path, ?Throwable $previous = null) { parent::__construct("Unable to generate public url for $path: $reason", 0, $previous); } public static function dueToError(string $path, Throwable $exception): static { return new static($exception->getMessage(), $path, $exception); } public static function noGeneratorConfigured(string $path, string $extraReason = ''): static { return new static('No generator was configured ' . $extraReason, $path); } } flysystem/src/ProxyArrayAccessToProperties.php 0000644 00000002315 15021222733 0015723 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; /** * @internal */ trait ProxyArrayAccessToProperties { private function formatPropertyName(string $offset): string { return str_replace('_', '', lcfirst(ucwords($offset, '_'))); } /** * @param mixed $offset * * @return bool */ public function offsetExists($offset): bool { $property = $this->formatPropertyName((string) $offset); return isset($this->{$property}); } /** * @param mixed $offset * * @return mixed */ #[\ReturnTypeWillChange] public function offsetGet($offset) { $property = $this->formatPropertyName((string) $offset); return $this->{$property}; } /** * @param mixed $offset * @param mixed $value */ #[\ReturnTypeWillChange] public function offsetSet($offset, $value): void { throw new RuntimeException('Properties can not be manipulated'); } /** * @param mixed $offset */ #[\ReturnTypeWillChange] public function offsetUnset($offset): void { throw new RuntimeException('Properties can not be manipulated'); } } flysystem/src/UnreadableFileEncountered.php 0000644 00000001054 15021222733 0015136 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use RuntimeException; final class UnreadableFileEncountered extends RuntimeException implements FilesystemException { /** * @var string */ private $location; public function location(): string { return $this->location; } public static function atLocation(string $location): UnreadableFileEncountered { $e = new static("Unreadable file encountered at location {$location}."); $e->location = $location; return $e; } } flysystem/src/InvalidVisibilityProvided.php 0000644 00000001051 15021222733 0015230 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem; use InvalidArgumentException; use function var_export; class InvalidVisibilityProvided extends InvalidArgumentException implements FilesystemException { public static function withVisibility(string $visibility, string $expectedMessage): InvalidVisibilityProvided { $provided = var_export($visibility, true); $message = "Invalid visibility provided. Expected {$expectedMessage}, received {$provided}"; throw new InvalidVisibilityProvided($message); } } flysystem/LICENSE 0000644 00000002047 15021222733 0007610 0 ustar 00 Copyright (c) 2013-2024 Frank de Jonge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flysystem-local/composer.json 0000644 00000001172 15021222733 0012413 0 ustar 00 { "name": "league/flysystem-local", "description": "Local filesystem adapter for Flysystem.", "keywords": ["flysystem", "filesystem", "local", "file", "files"], "type": "library", "prefer-stable": true, "autoload": { "psr-4": { "League\\Flysystem\\Local\\": "" } }, "require": { "php": "^8.0.2", "ext-fileinfo": "*", "league/flysystem": "^3.0.0", "league/mime-type-detection": "^1.0.0" }, "license": "MIT", "authors": [ { "name": "Frank de Jonge", "email": "info@frankdejonge.nl" } ] } flysystem-local/LocalFilesystemAdapter.php 0000644 00000036101 15021222733 0015002 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\Local; use const DIRECTORY_SEPARATOR; use const LOCK_EX; use DirectoryIterator; use FilesystemIterator; use Generator; use League\Flysystem\ChecksumProvider; use League\Flysystem\Config; use League\Flysystem\DirectoryAttributes; use League\Flysystem\FileAttributes; use League\Flysystem\FilesystemAdapter; use League\Flysystem\PathPrefixer; use League\Flysystem\SymbolicLinkEncountered; use League\Flysystem\UnableToCopyFile; use League\Flysystem\UnableToCreateDirectory; use League\Flysystem\UnableToDeleteDirectory; use League\Flysystem\UnableToDeleteFile; use League\Flysystem\UnableToMoveFile; use League\Flysystem\UnableToProvideChecksum; use League\Flysystem\UnableToReadFile; use League\Flysystem\UnableToRetrieveMetadata; use League\Flysystem\UnableToSetVisibility; use League\Flysystem\UnableToWriteFile; use League\Flysystem\UnixVisibility\PortableVisibilityConverter; use League\Flysystem\UnixVisibility\VisibilityConverter; use League\MimeTypeDetection\FinfoMimeTypeDetector; use League\MimeTypeDetection\MimeTypeDetector; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use SplFileInfo; use Throwable; use function chmod; use function clearstatcache; use function dirname; use function error_clear_last; use function error_get_last; use function file_exists; use function file_put_contents; use function hash_file; use function is_dir; use function is_file; use function mkdir; use function rename; class LocalFilesystemAdapter implements FilesystemAdapter, ChecksumProvider { /** * @var int */ public const SKIP_LINKS = 0001; /** * @var int */ public const DISALLOW_LINKS = 0002; private PathPrefixer $prefixer; private VisibilityConverter $visibility; private MimeTypeDetector $mimeTypeDetector; private string $rootLocation; /** * @var bool */ private $rootLocationIsSetup = false; public function __construct( string $location, ?VisibilityConverter $visibility = null, private int $writeFlags = LOCK_EX, private int $linkHandling = self::DISALLOW_LINKS, ?MimeTypeDetector $mimeTypeDetector = null, bool $lazyRootCreation = false, bool $useInconclusiveMimeTypeFallback = false, ) { $this->prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR); $visibility ??= new PortableVisibilityConverter(); $this->visibility = $visibility; $this->rootLocation = $location; $this->mimeTypeDetector = $mimeTypeDetector ?? new FallbackMimeTypeDetector( detector: new FinfoMimeTypeDetector(), useInconclusiveMimeTypeFallback: $useInconclusiveMimeTypeFallback, ); if ( ! $lazyRootCreation) { $this->ensureRootDirectoryExists(); } } private function ensureRootDirectoryExists(): void { if ($this->rootLocationIsSetup) { return; } $this->ensureDirectoryExists($this->rootLocation, $this->visibility->defaultForDirectories()); $this->rootLocationIsSetup = true; } public function write(string $path, string $contents, Config $config): void { $this->writeToFile($path, $contents, $config); } public function writeStream(string $path, $contents, Config $config): void { $this->writeToFile($path, $contents, $config); } /** * @param resource|string $contents */ private function writeToFile(string $path, $contents, Config $config): void { $prefixedLocation = $this->prefixer->prefixPath($path); $this->ensureRootDirectoryExists(); $this->ensureDirectoryExists( dirname($prefixedLocation), $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) ); error_clear_last(); if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) { throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? ''); } if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { $this->setVisibility($path, (string) $visibility); } } public function delete(string $path): void { $location = $this->prefixer->prefixPath($path); if ( ! file_exists($location)) { return; } error_clear_last(); if ( ! @unlink($location)) { throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? ''); } } public function deleteDirectory(string $prefix): void { $location = $this->prefixer->prefixPath($prefix); if ( ! is_dir($location)) { return; } $contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST); /** @var SplFileInfo $file */ foreach ($contents as $file) { if ( ! $this->deleteFileInfoObject($file)) { throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname()); } } unset($contents); if ( ! @rmdir($location)) { throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? ''); } } private function listDirectoryRecursively( string $path, int $mode = RecursiveIteratorIterator::SELF_FIRST ): Generator { if ( ! is_dir($path)) { return; } yield from new RecursiveIteratorIterator( new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), $mode ); } protected function deleteFileInfoObject(SplFileInfo $file): bool { switch ($file->getType()) { case 'dir': return @rmdir((string) $file->getRealPath()); case 'link': return @unlink((string) $file->getPathname()); default: return @unlink((string) $file->getRealPath()); } } public function listContents(string $path, bool $deep): iterable { $location = $this->prefixer->prefixPath($path); if ( ! is_dir($location)) { return; } /** @var SplFileInfo[] $iterator */ $iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location); foreach ($iterator as $fileInfo) { $pathName = $fileInfo->getPathname(); try { if ($fileInfo->isLink()) { if ($this->linkHandling & self::SKIP_LINKS) { continue; } throw SymbolicLinkEncountered::atLocation($pathName); } $path = $this->prefixer->stripPrefix($pathName); $lastModified = $fileInfo->getMTime(); $isDirectory = $fileInfo->isDir(); $permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4)); $visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions); yield $isDirectory ? new DirectoryAttributes(str_replace('\\', '/', $path), $visibility, $lastModified) : new FileAttributes( str_replace('\\', '/', $path), $fileInfo->getSize(), $visibility, $lastModified ); } catch (Throwable $exception) { if (file_exists($pathName)) { throw $exception; } } } } public function move(string $source, string $destination, Config $config): void { $sourcePath = $this->prefixer->prefixPath($source); $destinationPath = $this->prefixer->prefixPath($destination); $this->ensureRootDirectoryExists(); $this->ensureDirectoryExists( dirname($destinationPath), $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) ); if ( ! @rename($sourcePath, $destinationPath)) { throw UnableToMoveFile::because(error_get_last()['message'] ?? 'unknown reason', $source, $destination); } if ($visibility = $config->get(Config::OPTION_VISIBILITY)) { $this->setVisibility($destination, (string) $visibility); } } public function copy(string $source, string $destination, Config $config): void { $sourcePath = $this->prefixer->prefixPath($source); $destinationPath = $this->prefixer->prefixPath($destination); $this->ensureRootDirectoryExists(); $this->ensureDirectoryExists( dirname($destinationPath), $this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY)) ); if ( ! @copy($sourcePath, $destinationPath)) { throw UnableToCopyFile::because(error_get_last()['message'] ?? 'unknown', $source, $destination); } $visibility = $config->get( Config::OPTION_VISIBILITY, $config->get(Config::OPTION_RETAIN_VISIBILITY, true) ? $this->visibility($source)->visibility() : null, ); if ($visibility) { $this->setVisibility($destination, (string) $visibility); } } public function read(string $path): string { $location = $this->prefixer->prefixPath($path); error_clear_last(); $contents = @file_get_contents($location); if ($contents === false) { throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? ''); } return $contents; } public function readStream(string $path) { $location = $this->prefixer->prefixPath($path); error_clear_last(); $contents = @fopen($location, 'rb'); if ($contents === false) { throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? ''); } return $contents; } protected function ensureDirectoryExists(string $dirname, int $visibility): void { if (is_dir($dirname)) { return; } error_clear_last(); if ( ! @mkdir($dirname, $visibility, true)) { $mkdirError = error_get_last(); } clearstatcache(true, $dirname); if ( ! is_dir($dirname)) { $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; throw UnableToCreateDirectory::atLocation($dirname, $errorMessage); } } public function fileExists(string $location): bool { $location = $this->prefixer->prefixPath($location); return is_file($location); } public function directoryExists(string $location): bool { $location = $this->prefixer->prefixPath($location); return is_dir($location); } public function createDirectory(string $path, Config $config): void { $this->ensureRootDirectoryExists(); $location = $this->prefixer->prefixPath($path); $visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY)); $permissions = $this->resolveDirectoryVisibility($visibility); if (is_dir($location)) { $this->setPermissions($location, $permissions); return; } error_clear_last(); if ( ! @mkdir($location, $permissions, true)) { throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? ''); } } public function setVisibility(string $path, string $visibility): void { $path = $this->prefixer->prefixPath($path); $visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile( $visibility ); $this->setPermissions($path, $visibility); } public function visibility(string $path): FileAttributes { $location = $this->prefixer->prefixPath($path); clearstatcache(false, $location); error_clear_last(); $fileperms = @fileperms($location); if ($fileperms === false) { throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? ''); } $permissions = $fileperms & 0777; $visibility = $this->visibility->inverseForFile($permissions); return new FileAttributes($path, null, $visibility); } private function resolveDirectoryVisibility(?string $visibility): int { return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory( $visibility ); } public function mimeType(string $path): FileAttributes { $location = $this->prefixer->prefixPath($path); error_clear_last(); if ( ! is_file($location)) { throw UnableToRetrieveMetadata::mimeType($location, 'No such file exists.'); } $mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location); if ($mimeType === null) { throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? ''); } return new FileAttributes($path, null, null, null, $mimeType); } public function lastModified(string $path): FileAttributes { $location = $this->prefixer->prefixPath($path); error_clear_last(); $lastModified = @filemtime($location); if ($lastModified === false) { throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? ''); } return new FileAttributes($path, null, null, $lastModified); } public function fileSize(string $path): FileAttributes { $location = $this->prefixer->prefixPath($path); error_clear_last(); if (is_file($location) && ($fileSize = @filesize($location)) !== false) { return new FileAttributes($path, $fileSize); } throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? ''); } public function checksum(string $path, Config $config): string { $algo = $config->get('checksum_algo', 'md5'); $location = $this->prefixer->prefixPath($path); error_clear_last(); $checksum = @hash_file($algo, $location); if ($checksum === false) { throw new UnableToProvideChecksum(error_get_last()['message'] ?? '', $path); } return $checksum; } private function listDirectory(string $location): Generator { $iterator = new DirectoryIterator($location); foreach ($iterator as $item) { if ($item->isDot()) { continue; } yield $item; } } private function setPermissions(string $location, int $visibility): void { error_clear_last(); if ( ! @chmod($location, $visibility)) { $extraMessage = error_get_last()['message'] ?? ''; throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage); } } } flysystem-local/LICENSE 0000644 00000002047 15021222733 0010700 0 ustar 00 Copyright (c) 2013-2024 Frank de Jonge Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flysystem-local/FallbackMimeTypeDetector.php 0000644 00000002700 15021222733 0015243 0 ustar 00 <?php declare(strict_types=1); namespace League\Flysystem\Local; use League\MimeTypeDetection\MimeTypeDetector; use function in_array; class FallbackMimeTypeDetector implements MimeTypeDetector { private const INCONCLUSIVE_MIME_TYPES = [ 'application/x-empty', 'text/plain', 'text/x-asm', 'application/octet-stream', 'inode/x-empty', ]; public function __construct( private MimeTypeDetector $detector, private array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES, private bool $useInconclusiveMimeTypeFallback = false, ) { } public function detectMimeType(string $path, $contents): ?string { return $this->detector->detectMimeType($path, $contents); } public function detectMimeTypeFromBuffer(string $contents): ?string { return $this->detector->detectMimeTypeFromBuffer($contents); } public function detectMimeTypeFromPath(string $path): ?string { return $this->detector->detectMimeTypeFromPath($path); } public function detectMimeTypeFromFile(string $path): ?string { $mimeType = $this->detector->detectMimeTypeFromFile($path); if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) { return $mimeType; } return $this->detector->detectMimeTypeFromPath($path) ?? ($this->useInconclusiveMimeTypeFallback ? $mimeType : null); } } config/composer.json 0000644 00000003652 15021222733 0010536 0 ustar 00 { "name": "league/config", "type": "library", "description": "Define configuration arrays with strict schemas and access values with dot notation", "keywords": ["configuration","config","schema","array","nested","dot","dot-access"], "homepage": "https://config.thephpleague.com", "license": "BSD-3-Clause", "authors": [ { "name": "Colin O'Dell", "email": "colinodell@gmail.com", "homepage": "https://www.colinodell.com", "role": "Lead Developer" } ], "support": { "docs": "https://config.thephpleague.com/", "issues": "https://github.com/thephpleague/config/issues", "rss": "https://github.com/thephpleague/config/releases.atom", "source": "https://github.com/thephpleague/config" }, "require": { "php": "^7.4 || ^8.0", "dflydev/dot-access-data": "^3.0.1", "nette/schema": "^1.2" }, "require-dev": { "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.5", "scrutinizer/ocular": "^1.8.1", "unleashedtech/php-coding-standard": "^3.1", "vimeo/psalm": "^4.7.3" }, "minimum-stability": "dev", "prefer-stable": true, "autoload": { "psr-4": { "League\\Config\\": "src" } }, "autoload-dev": { "psr-4": { "League\\Config\\Tests\\": "tests" } }, "scripts": { "phpcs": "phpcs", "phpstan": "phpstan analyse", "phpunit": "phpunit --no-coverage", "psalm": "psalm", "test": [ "@phpcs", "@phpstan", "@psalm", "@phpunit" ] }, "extra": { "branch-alias": { "dev-main": "1.2-dev" } }, "config": { "sort-packages": true, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true } } } config/CHANGELOG.md 0000644 00000002403 15021222733 0007616 0 ustar 00 # Change Log All notable changes to this project will be documented in this file. Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles. ## [Unreleased][unreleased] ## [1.2.0] - 2022-12-11 ### Changed - Values can now be set prior to the corresponding schema being registered. - `exists()` and `get()` now only trigger validation for the relevant schema, not the entire config at once. ## [1.1.1] - 2021-08-14 ### Changed - Bumped the minimum version of dflydev/dot-access-data for PHP 8.1 support ## [1.1.0] - 2021-06-19 ### Changed - Bumped the minimum PHP version to 7.4+ - Bumped the minimum version of nette/schema to 1.2.0 ## [1.0.1] - 2021-05-31 ### Fixed - Fixed the `ConfigurationExceptionInterface` marker interface not extending `Throwable` (#2) ## [1.0.0] - 2021-05-31 Initial release! 🎉 [unreleased]: https://github.com/thephpleague/config/compare/v1.2.0...main [1.2.0]: https://github.com/thephpleague/config/compare/v1.1.1...v.1.2.0 [1.1.1]: https://github.com/thephpleague/config/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/thephpleague/config/compare/v1.0.1...v1.1.0 [1.0.1]: https://github.com/thephpleague/config/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/thephpleague/config/releases/tag/v1.0.0 config/LICENSE.md 0000644 00000002751 15021222733 0007417 0 ustar 00 BSD 3-Clause License Copyright (c) 2022, Colin O'Dell. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. config/src/SchemaBuilderInterface.php 0000644 00000001112 15021222733 0013631 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; use Nette\Schema\Schema; /** * Interface that allows new schemas to be added to a configuration */ interface SchemaBuilderInterface { /** * Registers a new configuration schema at the given top-level key */ public function addSchema(string $key, Schema $schema): void; } config/src/ConfigurationInterface.php 0000644 00000002226 15021222733 0013740 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; use League\Config\Exception\UnknownOptionException; use League\Config\Exception\ValidationException; /** * Interface for reading configuration values */ interface ConfigurationInterface { /** * @param string $key Configuration option path/key * * @psalm-param non-empty-string $key * * @return mixed * * @throws ValidationException if the schema failed to validate the given input * @throws UnknownOptionException if the requested key does not exist or is malformed */ public function get(string $key); /** * @param string $key Configuration option path/key * * @psalm-param non-empty-string $key * * @return bool Whether the given option exists * * @throws ValidationException if the schema failed to validate the given input */ public function exists(string $key): bool; } config/src/ConfigurationAwareInterface.php 0000644 00000001004 15021222733 0014711 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; /** * Implement this class to facilitate setter injection of the configuration where needed */ interface ConfigurationAwareInterface { public function setConfiguration(ConfigurationInterface $configuration): void; } config/src/ReadOnlyConfiguration.php 0000644 00000001442 15021222733 0013554 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; /** * Provides read-only access to a given Configuration object */ final class ReadOnlyConfiguration implements ConfigurationInterface { private Configuration $config; public function __construct(Configuration $config) { $this->config = $config; } /** * {@inheritDoc} */ public function get(string $key) { return $this->config->get($key); } public function exists(string $key): bool { return $this->config->exists($key); } } config/src/Exception/UnknownOptionException.php 0000644 00000001354 15021222733 0015756 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config\Exception; use Throwable; final class UnknownOptionException extends \InvalidArgumentException implements ConfigurationExceptionInterface { private string $path; public function __construct(string $message, string $path, int $code = 0, ?Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->path = $path; } public function getPath(): string { return $this->path; } } config/src/Exception/ConfigurationExceptionInterface.php 0000644 00000000673 15021222733 0017561 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config\Exception; /** * Marker interface for any/all exceptions thrown by this library */ interface ConfigurationExceptionInterface extends \Throwable { } config/src/Exception/InvalidConfigurationException.php 0000644 00000002403 15021222733 0017240 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config\Exception; class InvalidConfigurationException extends \UnexpectedValueException implements ConfigurationExceptionInterface { /** * @param string $option Name/path of the option * @param mixed $valueGiven The invalid option that was provided * @param ?string $description Additional text describing the issue (optional) */ public static function forConfigOption(string $option, $valueGiven, ?string $description = null): self { $message = \sprintf('Invalid config option for "%s": %s', $option, self::getDebugValue($valueGiven)); if ($description !== null) { $message .= \sprintf(' (%s)', $description); } return new self($message); } /** * @param mixed $value * * @psalm-pure */ private static function getDebugValue($value): string { if (\is_object($value)) { return \get_class($value); } return \print_r($value, true); } } config/src/Exception/ValidationException.php 0000644 00000001536 15021222733 0015222 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config\Exception; use Nette\Schema\ValidationException as NetteException; final class ValidationException extends InvalidConfigurationException { /** @var string[] */ private array $messages; public function __construct(NetteException $innerException) { parent::__construct($innerException->getMessage(), (int) $innerException->getCode(), $innerException); $this->messages = $innerException->getMessages(); } /** * @return string[] */ public function getMessages(): array { return $this->messages; } } config/src/Configuration.php 0000644 00000014524 15021222733 0012123 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; use Dflydev\DotAccessData\Data; use Dflydev\DotAccessData\DataInterface; use Dflydev\DotAccessData\Exception\DataException; use Dflydev\DotAccessData\Exception\InvalidPathException; use Dflydev\DotAccessData\Exception\MissingPathException; use League\Config\Exception\UnknownOptionException; use League\Config\Exception\ValidationException; use Nette\Schema\Expect; use Nette\Schema\Processor; use Nette\Schema\Schema; use Nette\Schema\ValidationException as NetteValidationException; final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface { /** @psalm-readonly */ private Data $userConfig; /** * @var array<string, Schema> * * @psalm-allow-private-mutation */ private array $configSchemas = []; /** @psalm-allow-private-mutation */ private Data $finalConfig; /** * @var array<string, mixed> * * @psalm-allow-private-mutation */ private array $cache = []; /** @psalm-readonly */ private ConfigurationInterface $reader; /** * @param array<string, Schema> $baseSchemas */ public function __construct(array $baseSchemas = []) { $this->configSchemas = $baseSchemas; $this->userConfig = new Data(); $this->finalConfig = new Data(); $this->reader = new ReadOnlyConfiguration($this); } /** * Registers a new configuration schema at the given top-level key * * @psalm-allow-private-mutation */ public function addSchema(string $key, Schema $schema): void { $this->invalidate(); $this->configSchemas[$key] = $schema; } /** * {@inheritDoc} * * @psalm-allow-private-mutation */ public function merge(array $config = []): void { $this->invalidate(); $this->userConfig->import($config, DataInterface::REPLACE); } /** * {@inheritDoc} * * @psalm-allow-private-mutation */ public function set(string $key, $value): void { $this->invalidate(); try { $this->userConfig->set($key, $value); } catch (DataException $ex) { throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex); } } /** * {@inheritDoc} * * @psalm-external-mutation-free */ public function get(string $key) { if (\array_key_exists($key, $this->cache)) { return $this->cache[$key]; } try { $this->build(self::getTopLevelKey($key)); return $this->cache[$key] = $this->finalConfig->get($key); } catch (InvalidPathException | MissingPathException $ex) { throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex); } } /** * {@inheritDoc} * * @psalm-external-mutation-free */ public function exists(string $key): bool { if (\array_key_exists($key, $this->cache)) { return true; } try { $this->build(self::getTopLevelKey($key)); return $this->finalConfig->has($key); } catch (InvalidPathException | UnknownOptionException $ex) { return false; } } /** * @psalm-mutation-free */ public function reader(): ConfigurationInterface { return $this->reader; } /** * @psalm-external-mutation-free */ private function invalidate(): void { $this->cache = []; $this->finalConfig = new Data(); } /** * Applies the schema against the configuration to return the final configuration * * @throws ValidationException|UnknownOptionException|InvalidPathException * * @psalm-allow-private-mutation */ private function build(string $topLevelKey): void { if ($this->finalConfig->has($topLevelKey)) { return; } if (! isset($this->configSchemas[$topLevelKey])) { throw new UnknownOptionException(\sprintf('Missing config schema for "%s"', $topLevelKey), $topLevelKey); } try { $userData = [$topLevelKey => $this->userConfig->get($topLevelKey)]; } catch (DataException $ex) { $userData = []; } try { $schema = $this->configSchemas[$topLevelKey]; $processor = new Processor(); $processed = $processor->process(Expect::structure([$topLevelKey => $schema]), $userData); $this->raiseAnyDeprecationNotices($processor->getWarnings()); $this->finalConfig->import((array) self::convertStdClassesToArrays($processed)); } catch (NetteValidationException $ex) { throw new ValidationException($ex); } } /** * Recursively converts stdClass instances to arrays * * @phpstan-template T * * @param T $data * * @return mixed * * @phpstan-return ($data is \stdClass ? array<string, mixed> : T) * * @psalm-pure */ private static function convertStdClassesToArrays($data) { if ($data instanceof \stdClass) { $data = (array) $data; } if (\is_array($data)) { foreach ($data as $k => $v) { $data[$k] = self::convertStdClassesToArrays($v); } } return $data; } /** * @param string[] $warnings */ private function raiseAnyDeprecationNotices(array $warnings): void { foreach ($warnings as $warning) { @\trigger_error($warning, \E_USER_DEPRECATED); } } /** * @throws InvalidPathException */ private static function getTopLevelKey(string $path): string { if (\strlen($path) === 0) { throw new InvalidPathException('Path cannot be an empty string'); } $path = \str_replace(['.', '/'], '.', $path); $firstDelimiter = \strpos($path, '.'); if ($firstDelimiter === false) { return $path; } return \substr($path, 0, $firstDelimiter); } } config/src/ConfigurationBuilderInterface.php 0000644 00000000762 15021222733 0015252 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; /** * An interface that provides the ability to set both the schema and configuration values */ interface ConfigurationBuilderInterface extends MutableConfigurationInterface, SchemaBuilderInterface { } config/src/MutableConfigurationInterface.php 0000644 00000001462 15021222733 0015253 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; use League\Config\Exception\UnknownOptionException; /** * Interface for setting/merging user-defined configuration values into the configuration object */ interface MutableConfigurationInterface { /** * @param mixed $value * * @throws UnknownOptionException if $key contains a nested path which doesn't point to an array value */ public function set(string $key, $value): void; /** * @param array<string, mixed> $config */ public function merge(array $config = []): void; } config/src/ConfigurationProviderInterface.php 0000644 00000000745 15021222733 0015457 0 ustar 00 <?php declare(strict_types=1); /* * This file is part of the league/config package. * * (c) Colin O'Dell <colinodell@gmail.com> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace League\Config; /** * Interface for a service which provides a readable configuration object */ interface ConfigurationProviderInterface { public function getConfiguration(): ConfigurationInterface; } config/README.md 0000644 00000016002 15021222733 0007264 0 ustar 00 # league/config [](https://packagist.org/packages/league/config) [](https://packagist.org/packages/league/config) [](LICENSE) [](https://github.com/thephpleague/config/actions?query=workflow%3ATests+branch%3Amain) [](https://scrutinizer-ci.com/g/thephpleague/config/code-structure) [](https://scrutinizer-ci.com/g/thephpleague/config) [](https://www.colinodell.com/sponsor) **league/config** helps you define nested configuration arrays with strict schemas and access configuration values with dot notation. It was created by [Colin O'Dell][@colinodell]. ## 📦 Installation This project requires PHP 7.4 or higher. To install it via [Composer] simply run: ```bash composer require league/config ``` ## 🧰️ Basic Usage The `Configuration` class provides everything you need to define the configuration structure and fetch values: ```php use League\Config\Configuration; use Nette\Schema\Expect; // Define your configuration schema $config = new Configuration([ 'database' => Expect::structure([ 'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(), 'host' => Expect::string()->default('localhost'), 'port' => Expect::int()->min(1)->max(65535), 'ssl' => Expect::bool(), 'database' => Expect::string()->required(), 'username' => Expect::string()->required(), 'password' => Expect::string()->nullable(), ]), 'logging' => Expect::structure([ 'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true), 'file' => Expect::string()->deprecated("use logging.path instead"), 'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(), ]), ]); // Set the values, either all at once with `merge()`: $config->merge([ 'database' => [ 'driver' => 'mysql', 'port' => 3306, 'database' => 'mydb', 'username' => 'user', 'password' => 'secret', ], ]); // Or one-at-a-time with `set()`: $config->set('logging.path', '/var/log/myapp.log'); // You can now retrieve those values with `get()`. // Validation and defaults will be applied for you automatically $config->get('database'); // Fetches the entire "database" section as an array $config->get('database.driver'); // Fetch a specific nested value with dot notation $config->get('database/driver'); // Fetch a specific nested value with slash notation $config->get('database.host'); // Returns the default value "localhost" $config->get('logging.path'); // Guaranteed to be writeable thanks to the assertion in the schema // If validation fails an `InvalidConfigurationException` will be thrown: $config->set('database.driver', 'mongodb'); $config->get('database.driver'); // InvalidConfigurationException // Attempting to fetch a non-existent key will result in an `InvalidConfigurationException` $config->get('foo.bar'); // You could avoid this by checking whether that item exists: $config->exists('foo.bar'); // Returns `false` ``` ## 📓 Documentation Full documentation can be found at [config.thephpleague.com][docs]. ## 💭 Philosophy This library aims to provide a **simple yet opinionated** approach to configuration with the following goals: - The configuration should operate on **arrays with nested values** which are easily accessible - The configuration structure should be **defined with strict schemas** defining the overall structure, allowed types, and allowed values - Schemas should be defined using a **simple, fluent interface** - You should be able to **add and combine schemas but never modify existing ones** - Both the configuration values and the schema should be **defined and managed with PHP code** - Schemas should be **immutable**; they should never change once they are set - Configuration values should never define or influence the schemas As a result, this library will likely **never** support features like: - Loading and/or exporting configuration values or schemas using YAML, XML, or other files - Parsing configuration values from a command line or other user interface - Dynamically changing the schema, allowed values, or default values based on other configuration values If you need that functionality you should check out other libraries like: - [symfony/config] - [symfony/options-resolver] - [hassankhan/config] - [consolidation/config] - [laminas/laminas-config] ## 🏷️ Versioning [SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase. Any classes or methods marked `@internal` are not intended for use outside this library and are subject to breaking changes at any time, so please avoid using them. ## 🛠️ Maintenance & Support When a new **minor** version (e.g. `1.0` -> `1.1`) is released, the previous one (`1.0`) will continue to receive security and critical bug fixes for *at least* 3 months. When a new **major** version is released (e.g. `1.1` -> `2.0`), the previous one (`1.1`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out. (This policy may change in the future and exceptions may be made on a case-by-case basis.) ## 👷️ Contributing Contributions to this library are **welcome**! We only ask that you adhere to our [contributor guidelines] and avoid making changes that conflict with our Philosophy above. ## 🧪 Testing ```bash composer test ``` ## 📄 License **league/config** is licensed under the BSD-3 license. See the [`LICENSE.md`][license] file for more details. ## 🗺️ Who Uses It? This project is used by [league/commonmark][league-commonmark]. [docs]: https://config.thephpleague.com/ [@colinodell]: https://www.twitter.com/colinodell [Composer]: https://getcomposer.org/ [PHP League]: https://thephpleague.com [symfony/config]: https://symfony.com/doc/current/components/config.html [symfony/options-resolver]: https://symfony.com/doc/current/components/options_resolver.html [hassankhan/config]: https://github.com/hassankhan/config [consolidation/config]: https://github.com/consolidation/config [laminas/laminas-config]: https://docs.laminas.dev/laminas-config/ [contributor guidelines]: https://github.com/thephpleague/config/blob/main/.github/CONTRIBUTING.md [license]: https://github.com/thephpleague/config/blob/main/LICENSE.md [league-commonmark]: https://commonmark.thephpleague.com
| ver. 1.4 |
Github
|
.
| PHP 8.1.29 | Генерация страницы: 0.68 |
proxy
|
phpinfo
|
Настройка