diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..385493a --- /dev/null +++ b/composer.json @@ -0,0 +1,6 @@ +{ + "require": { + "twig/twig": "^3.5", + "league/commonmark": "^2.4" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..5754775 --- /dev/null +++ b/composer.lock @@ -0,0 +1,872 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "7fd366f96757f4d4aff1594a505dd600", + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + }, + "time": "2022-10-27T11:44:00+00:00" + }, + { + "name": "league/commonmark", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "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", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "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" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "time": "2023-03-24T15:16:10+00:00" + }, + { + "name": "league/config", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "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" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "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" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "time": "2022-12-11T20:36:23+00:00" + }, + { + "name": "nette/schema", + "version": "v1.2.3", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.3" + }, + "time": "2022-10-13T01:24:26+00:00" + }, + { + "name": "nette/utils", + "version": "v4.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "shasum": "" + }, + "require": { + "php": ">=8.0 <8.3" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.4", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.0" + }, + "time": "2023-02-02T10:41:53+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-03-01T10:25:55+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "twig/twig", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "time": "2023-02-08T07:49:20+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/css/event.css b/css/event.css new file mode 100644 index 0000000..27452e9 --- /dev/null +++ b/css/event.css @@ -0,0 +1,4 @@ +img { + display: block; + margin: auto; +} \ No newline at end of file diff --git a/css/events.css b/css/events.css new file mode 100644 index 0000000..852d59f --- /dev/null +++ b/css/events.css @@ -0,0 +1,32 @@ +div.eventblock { + position: relative; + background-color: var(--nav-background-color); + padding: 2px 10px 2px 10px; + margin: 10px 0px 10px 0px; +} + +div.eventblock a { + color: white !important; + display: block; + margin: auto; +} + +div.eventblock a:hover { + text-decoration: none; +} + +div.eventblock h3 { + margin: 0; +} + +div.eventblock h3.a { + text-align: left; + width: 50%; + display: inline-block; +} + +div.eventblock h3.b { + text-align: right; + width: 50%; + display: inline-block; +} \ No newline at end of file diff --git a/css/home.css b/css/home.css new file mode 100644 index 0000000..780fcc0 --- /dev/null +++ b/css/home.css @@ -0,0 +1,15 @@ +main img { + max-width: 950px; + width: 100%; +} + +main div.rundgang { + height: 140px; +} + +@media only screen and (max-width: 885px) { + main img { + height: 140px; + object-fit: cover; + } +} \ No newline at end of file diff --git a/css/kontakt.css b/css/kontakt.css new file mode 100644 index 0000000..c407156 --- /dev/null +++ b/css/kontakt.css @@ -0,0 +1,16 @@ +main { + text-align: center; +} + +main a { + display: inline-block; + padding: 20px; +} + +main a:hover { + text-decoration: none; +} + +main a img { + width: 200px; +} \ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..0e8e121 --- /dev/null +++ b/css/main.css @@ -0,0 +1,214 @@ +:root { + --background-color: #193360; + --nav-background-color: #14284c; + --logo-background-color: #0e1e38; + +} + +* { + box-sizing: border-box; + word-wrap: break-word; +} + +html, body { + padding: 0; + margin: 0; + background-color: var(--background-color); + color: white; + font-family: sans-serif; + line-height: 150%; +} + +a { + color: #22bb22 !important; + text-decoration: none; +} + +a:hover { + text-decoration: underline; + cursor: pointer; +} + +a:visited { + color: #bb66ff !important; +} + +a.imgLink div { + position: relative; + text-align: center; + color: white; +} + +a.imgLink:hover { + text-decoration: none; +} + +a.imgLink div { + position: relative; + text-align: center; + color: white !important; + font-size: 0; + transform: scale(1); + transition: transform .5s; + text-shadow: 2px 2px black; +} + +a.imgLink:hover div { + font-size: 200%; + transform: scale(.9); + transition: font-size .5s, transform .5s; +} + +a.imgLink div h2 { + position: absolute; + top: 35%; + left: 50%; + transform: translate(-50%, -50%); +} + +header { + background-color: var(--logo-background-color); + padding: 0 !important; +} + +header a#navToggle { + color: white !important; + font-size: 64px; + display: none; + background-color: var(--nav-background-color); + padding: 20px 100px 20px 0; + text-decoration: none !important; + text-align: right; +} + +nav { + padding: 10px 0 10px 0; + background-color: var(--nav-background-color); +} + +nav ul { + list-style: none; + text-align: center; +} + +nav ul li { + display: inline-block; + padding: 0 2vw 0 2vw; + font-size: 125%; +} + +nav ul li a { + color: white !important; + text-decoration: none; +} + +nav ul li a.active { + text-decoration: underline; + text-decoration-style: double; +} + +nav ul li a:visited { + color: white !important; +} + +header { + text-align: center; + padding: 10px 0 0 0; +} + +header img { + max-width: 200px; + max-height: 200px; + width: 90vw; + margin-bottom: 10px; +} + +main { + max-width: 950px; + min-height: 100vh; + height: auto; + width: 90%; + margin: auto; + padding: 10px 0 0 0; +} + +footer { + background-color: var(--nav-background-color); + height: 100px; +} + +iframe.osm { + width: 500px; + height: 500px; + max-width: 90vw; + max-height: 90vw; + margin: auto; + display: block; +} + +div.newBanner { + display: block; + position: fixed; + top: 30px; + left: -40px; + background-color: #DD0000; + text-align: center; + transform: rotateZ(-45deg); + transition: transform .5s; + z-index: 1; +} + +div.newBanner:hover { + transform: rotateZ(-45deg) scale(1.4); + transition: transform .5s; +} + +div.newBanner a { + color: white !important; + padding: 100px 50px 100px 50px; +} + +div.newBanner a:hover { + text-decoration: none; +} + +h3.topic { + margin-bottom: 0; +} + +p.topic { + margin-top: 0; +} + +a.red-text { + color: #DD0000 !important; +} + +a.green-text { + color: #00DD00 !important; +} + +.text-left { + text-align: left; +} + +@media only screen and (max-width: 885px) { + header a#navToggle { + display: block; + } + + header nav { + display: none; + } + + nav ul li { + display: block; + font-size: 200%; + padding: 30px 100px 30px 0; + text-align: right; + } + + a.imgLink div { + font-size: 200%; + } +} \ No newline at end of file diff --git a/events/0.md b/events/0.md new file mode 100644 index 0000000..b510cce --- /dev/null +++ b/events/0.md @@ -0,0 +1,9 @@ +Testeintrag +Ein wunderbarer Testeintrag. +2023-05-10 - 2023-05-16 +16:00 - 20:00 +Chaostreff Dortmund e.V. +------------------------ +# Testeintrag + +Dies ist ein wundervoller Test! \ No newline at end of file diff --git a/events/brunch.md b/events/brunch.md new file mode 100644 index 0000000..28491a5 --- /dev/null +++ b/events/brunch.md @@ -0,0 +1,15 @@ +ChaosBrunch +Einmal in der Woche treffen wir uns regulär zum quatschen, basteln, frickeln, programmieren, hacken, kochen, essen, … +{{ brunchdatum }} +11:00 - 15:00 +Chaostreff Dortmund e.V. +------------------------ +# ChaosBrunch + +Alle zwei Wochen an einem Sonntag lädt der CTDO zum **Mitbring-Brunch** in seinen Räume im Kulturzentrum [„Langer August“](/?page=treff) ein! + +Um **ca. 11 Uhr** geht es los, das Ende ist wie immer offen. Die Details dazu gibt es im [ctdo wiki](https://wiki.ctdo.de/events/hackerbrunch)! + +Schaut einfach vorab im [MitbringPad](https://md.ctdo.de/brunch) rein und kündigt an was ihr auf den Tisch werft, alles kann, nichts muss! + +Es sind sowohl alte als auch neue Menschen gerne gesehen! Bringt was zu futtern, euer aktuelles Projekt, beides oder einfach nur Neugierde mit! \ No newline at end of file diff --git a/events/repaircafe.md b/events/repaircafe.md new file mode 100644 index 0000000..6a493a3 --- /dev/null +++ b/events/repaircafe.md @@ -0,0 +1,26 @@ +RepairCafe +“ Wegwerfen? Denk’ste! „ +{{ repaircafedatum }} +19:00 - 23:00 +Chaostreff Dortmund e.V. +------------------------ +# RepairCafe + +![ctdo repaircafe logo](/images/events/ctdo-logo-reperaturcafe.png) + +Seit Ende Juni 2014 bietet der Chaostreff Dortmund an jedem letzten Donnerstag im Monat ab 19 Uhr ein Repair Café an. Dazu ist jeder eingeladen, defekte Geräte, sei es der Küchenmixer, Notebook oder der alte Plattenspieler, mitzubringen und selber zu reparieren. Technischen Sachverstand, Rat und auch Tat wird es von uns geben. Wir helfen wo wir können – reparieren müsst ihr aber selber – das ist der Grundgedanke des Repair Cafés. Als Stärkung haben wir natürlich Getränke und Snacks. + +Bereits Anfang Juni 2014 startete unser Repair Cafe, welches wir im Rahmen des Projekts Selber Machen! speziell für Kinder und Jugendliche zwischen 8 und 16 Jahren angeboten haben. + +Seit dem Start des normalen Repair Cafes für jedermann/frau konnten wir bereits einigen Geräten neues Leben einhauchen. Manchmal ist es nur eine kalte Lötstelle, ein gebrochenes Kabel oder eine defekte Sicherung, die das gesamte Gerät lahmlegen. Im Wiki gibt es weitere Informationen zum Repair Cafe. + +Hier noch mal auf einen Blick: + +Repair Café: jeden letzten Donnerstag im Monat ab 19 Uhr im Chaostreff Dortmund +Wir sind auch auf [www.repaircafe.org](https://www.repaircafe.org/) eingetragen. + +Du findest uns in der Braunschweiger Str 22, 44145 Dortmund im „Langen August“. Geh durch das große rote Tor und klingel beim „Chaostreff“. Zweites OG. Per E-Mail kannst du uns über die [Mailingliste](/?page=mail) erreichen. + +Anmeldung nicht erforderlich! + +Achtung: Bei Smartphones, Tablets und zum Großteil bei Apple Produkten können wir leider nicht behilflich sein. \ No newline at end of file diff --git a/events/topictreff.md b/events/topictreff.md new file mode 100644 index 0000000..c051460 --- /dev/null +++ b/events/topictreff.md @@ -0,0 +1,9 @@ +TopicTreff (Plenum) +Der Topictreff ist das zweimal im Monat stattfindende Plenum des CTDO. +{{ topicdatum }} +20:00 - 21:00 +Chaostreff Dortmund e.V. +------------------------ +# TopicTreff (Plenum) + +Der Topictreff ist das zweimal im Monat stattfindende Plenum des CTDO. Aktuelle Themen können im [Wiki](https://wiki.ctdo.de/de/verein/topictreff) eingesehen werden. \ No newline at end of file diff --git a/events/treff.md b/events/treff.md new file mode 100644 index 0000000..61bc487 --- /dev/null +++ b/events/treff.md @@ -0,0 +1,13 @@ +Treff +Einmal in der Woche treffen wir uns regulär zum quatschen, basteln, frickeln, programmieren, hacken, kochen, essen, … +{{ treffdatum }} +19:00 - ende offen +Chaostreff Dortmund e.V. +------------------------ +# Treff + +Einmal in der Woche treffen wir uns regulär zum quatschen, basteln, frickeln, programmieren, hacken, kochen, essen, … + +Los gehts wenn Jemand da ist (meist ab 19 Uhr). Ende offen. + +Es darf jeder kommen, egal ob Mitglied oder nicht. Wenn du unsicher bist, ob Jemand da ist, wie lange Jemand bleibt: ruf gerne an, oder frage im Chat nach. \ No newline at end of file diff --git a/images/adresse_knopf.webp b/images/adresse_knopf.webp new file mode 100644 index 0000000..8d5eaf9 Binary files /dev/null and b/images/adresse_knopf.webp differ diff --git a/images/chat_knopf.webp b/images/chat_knopf.webp new file mode 100644 index 0000000..f77af52 Binary files /dev/null and b/images/chat_knopf.webp differ diff --git a/images/events/ctdo-logo-reperaturcafe.png b/images/events/ctdo-logo-reperaturcafe.png new file mode 100644 index 0000000..cd13be6 Binary files /dev/null and b/images/events/ctdo-logo-reperaturcafe.png differ diff --git a/images/header.jpg b/images/header.jpg new file mode 100644 index 0000000..570600b Binary files /dev/null and b/images/header.jpg differ diff --git a/images/logo_ctdo.svg b/images/logo_ctdo.svg new file mode 100644 index 0000000..350a39b --- /dev/null +++ b/images/logo_ctdo.svg @@ -0,0 +1,3 @@ + + +image/svg+xml diff --git a/images/mail_knopf.webp b/images/mail_knopf.webp new file mode 100644 index 0000000..7ba805c Binary files /dev/null and b/images/mail_knopf.webp differ diff --git a/images/posts/DORTBUNT.nebenan_Banner-fuer-Anmeldende-768x154.png b/images/posts/DORTBUNT.nebenan_Banner-fuer-Anmeldende-768x154.png new file mode 100644 index 0000000..cbeb604 Binary files /dev/null and b/images/posts/DORTBUNT.nebenan_Banner-fuer-Anmeldende-768x154.png differ diff --git a/images/tel_knopf.webp b/images/tel_knopf.webp new file mode 100644 index 0000000..9a8d4af Binary files /dev/null and b/images/tel_knopf.webp differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..27dc1f1 --- /dev/null +++ b/index.php @@ -0,0 +1,149 @@ + 'strip', + 'allow_unsafe_links' => false, +]); + +$templateloader = new FilesystemLoader(__DIR__ . '/templates'); + +$twig_templates = new Environment($templateloader); + +$title = ''; +$content = ''; + +$raumstatus_b = get_web_json('https://status.ctdo.de/api/simple/v2')["state"]; +$raumstatus = 'unbekannt'; + +$pages = array('index', 'about', 'treff', 'blog', 'events', 'kontakt', 'support', 'verein'); +$page_names = array('index', 'über uns', 'zeiten & location', 'blog', 'events', 'kontakt', 'support', 'verein'); + +$active_page = ''; + +if(gettype($raumstatus_b) == "boolean") { + if($raumstatus_b) + $raumstatus = html_link('https://status.ctdo.de/', 'green-text', 'offen', TRUE); + else + $raumstatus = html_link('https://status.ctdo.de/', 'red-text', 'geschlossen', TRUE); +} + +$topic = get_next_topic(); + +$treff = get_next_treff(); + +$repaircafe = get_next_repaircafe(); + +$brunch = get_next_brunch(); + +$css = ['main']; + +if(isset($_GET['page'])) { + $page = $_GET['page']; + $active_page = str_mass_replace(array('adresse', 'irc', 'mail'), array('kontakt', 'kontakt', 'kontakt'), $page); + $title = $page_names[array_search($active_page, $pages)]; + switch ($page) { + case 'index': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/index.md')); + $content = $twig_templates->render('index.twig', ['content' => $content, 'topic' => $topic, 'treff' => $treff, 'repaircafe' => $repaircafe, 'brunch' => $brunch, 'events' => generate_event_list() ]); + $filetoedit = "pages/index.md"; + $css[] = 'home'; + $css[] = 'events'; + break; + case 'about': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/about.md')); + $filetoedit = "pages/about.md"; + $css[] = 'about'; + break; + case 'treff': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/treff.md')); + $filetoedit = "pages/treff.md"; + break; + case 'blog': + if(!isset($_GET['id'])) { + $content = $twig_templates->render('blog.twig', [ 'posts' => generate_post_list() ]); + $filetoedit = "templates/blog.twig"; + $css[] = 'events'; + } else { + $content = $converter->convert(get_post_content($_GET['id'])); + $filetoedit = "posts/".$_GET['id'].".md"; + $css[] = 'event'; + } + break; + case 'events': + if(!isset($_GET['id'])) { + $content = $twig_templates->render('events.twig', [ 'topic' => $topic, 'treff' => $treff, 'repaircafe' => $repaircafe, 'brunch' => $brunch, 'events' => generate_event_list() ]); + $filetoedit = "templates/events.twig"; + $css[] = 'events'; + } else { + $content = str_mass_replace(array('{{ topicdatum }}', 'Chaostreff Dortmund e.V.', '{{ treffdatum }}', '{{ repaircafedatum }}', '{{ brunchdatum }}'), array(get_next_topic()->date, html_link('/?page=treff', '', 'Chaostreff Dortmund e.V.', FALSE), get_next_treff()->date, get_next_repaircafe()->date, get_next_brunch()->date), $converter->convert(get_event_content($_GET['id']))); + $filetoedit = "events/".$_GET['id'].".md"; + $css[] = 'event'; + } + break; + case 'kontakt': + $content = $twig_templates->render('kontakt.twig'); + $filetoedit = "templates/kontakt.twig"; + $css[] = 'kontakt'; + break; + case 'adresse': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/kontakt/adresse.md')).''; + $filetoedit = "pages/kontakt/adresse.md"; + break; + case 'irc': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/kontakt/irc.md')); + $filetoedit = "pages/kontakt/irc.md"; + break; + case 'mail': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/kontakt/mail.md')); + $filetoedit = "pages/kontakt/mail.md"; + break; + case 'tel': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/kontakt/tel.md')); + $filetoedit = "pages/kontakt/tel.md"; + break; + case 'support': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/support.md')); + $filetoedit = "pages/kontakt/support.md"; + break; + case 'verein': + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/verein.md')); + $filetoedit = "pages/verein.md"; + break; + case 'impressum': + $title = 'impressum'; + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/impressum.md')); + $filetoedit = "pages/impressum.md"; + break; + case 'datenschutz': + $title = 'datenschutz'; + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/datenschutz.md')); + $filetoedit = "pages/datenschutz.md"; + break; + default: + $content = $twig_templates->render('404.twig', ['page' => $_GET['page']]); + $filetoedit = "templates/404.twig"; + break; + } +} else { + $active_page = 'index'; + $title = $active_page; + $content = $converter->convert(file_get_contents(__DIR__ . '/pages/index.md')); + $content = $twig_templates->render('index.twig', ['content' => $content, 'topic' => $topic, 'treff' => $treff, 'repaircafe' => $repaircafe, 'brunch' => $brunch, 'events' => generate_event_list() ]); + $css[] = 'home'; + $css[] = 'events'; +} + +$nav = generate_nav($active_page, $pages, $page_names); + +echo $twig_templates->render('default.twig', ['title' => $title, 'css' => $css, 'nav' => $nav, 'raumstatus' => $raumstatus, 'main' => $content, 'file_to_edit' => $filetoedit]); +?> diff --git a/js/nav.js b/js/nav.js new file mode 100644 index 0000000..5c9feea --- /dev/null +++ b/js/nav.js @@ -0,0 +1,18 @@ +var nav = document.getElementsByTagName("nav")[0]; + +function ToggleNav() { + showNav = nav.style.display != "" && nav.style.display != "none"; + if (showNav) { + nav.style.display = "none"; + } else { + nav.style.display = "block"; + } +} + +window.addEventListener("resize", function(){ + if (window.innerWidth > 885) { + nav.style.display = "block"; + } else { + nav.style.display = "none"; + } +}); \ No newline at end of file diff --git a/pages/about.md b/pages/about.md new file mode 100644 index 0000000..5e0938a --- /dev/null +++ b/pages/about.md @@ -0,0 +1,48 @@ +# Wer wir sind + +Der Chaostreff Dortmund e.V. ist ein Erfa-Kreis des CCC* und eine bunt gemischte Gruppe aus computer-, technikbegeisterten und kreativen Menschen. Wir sind nicht finanziell orientiert und betreiben den Chaostreff aus Spaß an der Sache und um interessierten Menschen eine Plattform zu bieten, über die man sich austauschen kann. Unser Ziel ist es den Chaostreff fortwährend aus Spenden der einzelnen Teilnehmenden bzw. Mitglieder zu finanzieren. Wenn du dir angucken möchtest, wie das so bei uns in den Räumen aussieht, dann komm doch einfach vorbei. + +Wir schließen niemanden aufgrund körperlicher Disposition, Religion, Alter, Weltanschauung, sexueller Ausrichtung oder sonstigen nichtigen Gründen aus! Alle, die interessiert sind, sind auch bei uns willkommen. + +*Wir sind ein Teil des Chaos Computer Club (CCC), ein s. g. Erfa Kreis (Erfahrungsaustausch Kreis), da wir seit 2017-04-16¹ den Erfa Status vom Regiotreff zuerkannt bekommen haben. + +# Unsere Ziele + +- Schaffung einer Plattform für technikbegeisterte Menschen +- Bereitstellung einer technischen Infrastruktur +- Gemütliche Räumlichkeiten mit Getränken etc. +- Die Möglichkeit, gemeinsam kreativ mit Technik umzugehen +- Nützliches Mitglied des Nordstadtquartiers zu sein +- Sich und andere weiterzubilden +- Weltherrschaft natürlich!² + +# Unsere Zielgruppe + +Alle, die sich für Technik, Kunst, Kreativität, OpenSource, Netzpolitik, den Geist in der Maschine oder ähnliches begeistern können; wie bereits oben erwähnt schließen wir niemanden aus irgendwelchen dämlichen/rassistischen/vorurteilsbehafteten Gründen aus. + +Wir freuen uns schon auf deinen ersten Besuch bei uns. + +# Wir bieten dir + +- Viel Platz für eigene Projekte +- Chillout Platz mit Sofas und gemütlicher Atmosphäre +- Eine Küche zum gemeinsamen Kochen +- Einen Raum für Siebdruck und Platinenherstellung +- Maschinen wie Lasercutter, Stickmaschine, 3D Drucker +- Werkzeuge, sowie Lötarbeitsplätze und Messgeräte +- PC-Arbeitsplätze +- Email/Shell Konten und Webspace oder VServer +- Kisten/Regale damit du dein Zeug verstauen kannst +- Freundliche Leute mit denen man sich austauschen kann +- u.v.m + +# Du gehörst nicht zu uns, wenn… + +- …du einen 24/7 kostenlosen PC-Hilfe Notdienst suchst. +- …deine Festplatte kaputt ist, und du vergessen hast, ein Backup zu machen. (Ausnahmen bestätigen die Regel) +- …du der Meinung bist, wir wären dein kostenloses Internetcafé +- …du der irrigen Annahme verfallen bist, wir würden für dich irgendeinen FTP-/WebServer/eMail-Account-deiner-ExFreundin hacken, geschweige denn dir dabei helfen + +¹ [https://en.wikipedia.org/wiki/ISO_8601](https://en.wikipedia.org/wiki/ISO_8601) + +² Dies ist eine witzige Bemerkung ein s.g. “Running Gag” und ist als sarkastische Bemerkung zu verstehen! \ No newline at end of file diff --git a/pages/datenschutz.md b/pages/datenschutz.md new file mode 100644 index 0000000..d6d244f --- /dev/null +++ b/pages/datenschutz.md @@ -0,0 +1,252 @@ +# Datenschutzerklärung + +*(English version below)* + +## 1. Datenschutz auf einen Blick + +### Allgemeine Hinweise + +Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie unsere Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung. + +### Datenerfassung auf unserer Website + +#### Wer ist verantwortlich für die Datenerfassung auf dieser Website? + +Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Impressum dieser Website entnehmen. + +#### Wie erfassen wir Ihre Daten? + +Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z.B. um Daten handeln, die Sie in ein Kontaktformular eingeben. + +Andere Daten werden automatisch beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie unsere Website betreten. + +#### Wofür nutzen wir Ihre Daten? + +Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gewährleisten. Andere Daten können zur Analyse Ihres Nutzerverhaltens verwendet werden. + +#### Welche Rechte haben Sie bezüglich Ihrer Daten? + +Sie haben jederzeit das Recht unentgeltlich Auskunft über Herkunft, Empfänger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben außerdem ein Recht, die Berichtigung, Sperrung oder Löschung dieser Daten zu verlangen. Hierzu sowie zu weiteren Fragen zum Thema Datenschutz können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden. Des Weiteren steht Ihnen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. + +## 2. Allgemeine Hinweise und Pflichtinformationen + +### Datenschutz + +Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend der gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung. + +Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie persönlich identifiziert werden können. Die vorliegende Datenschutzerklärung erläutert, welche Daten wir erheben und wofür wir sie nutzen. Sie erläutert auch, wie und zu welchem Zweck das geschieht. + +Wir weisen darauf hin, dass die Datenübertragung im Internet (z.B. bei der Kommunikation per E-Mail) Sicherheitslücken aufweisen kann. Ein lückenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht möglich. + +### Hinweis zur verantwortlichen Stelle + +Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist: + +``` +Chaostreff Dortmund e.V. +Braunschweiger Str. 22 +44145 Dortmund +Deutschland +``` + +#### Vertretungsberechtigter Vorstand + +- Tim Windelschmidt (1. Vorsitzender) + +- Stefan Hausschild (2. Vorsitzender) + +Telefon: [+49 (0)231 8 404 777](tel:+492318404777) + +E-Mail: [vorstand@chaostreff-dortmund.de](mailto:vorstand@chaostreff-dortmund.de) + +Verantwortliche Stelle ist die natürliche oder juristische Person, die allein oder gemeinsam mit anderen über die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z.B. Namen, E-Mail-Adressen o. Ä.) entscheidet. + +### Widerruf Ihrer Einwilligung zur Datenverarbeitung + +Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Dazu reicht eine formlose Mitteilung per E-Mail an uns. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt. + +### Beschwerderecht bei der zuständigen Aufsichtsbehörde + +Im Falle datenschutzrechtlicher Verstöße steht dem Betroffenen ein Beschwerderecht bei der zuständigen Aufsichtsbehörde zu. Zuständige Aufsichtsbehörde in datenschutzrechtlichen Fragen ist der Landesdatenschutzbeauftragte des Bundeslandes, in dem unser Unternehmen seinen Sitz hat. Eine Liste der Datenschutzbeauftragten sowie deren Kontaktdaten können folgendem Link entnommen werden: [https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html](https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html). + +### Recht auf Datenübertragbarkeit + +Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist. + +### SSL- bzw. TLS-Verschlüsselung + +Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL-bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von “http://” auf “https://” wechselt und an dem Schloss-Symbol in Ihrer Browserzeile. + +Wenn die SSL- bzw. TLS-Verschlüsselung aktiviert ist, können die Daten, die Sie an uns übermitteln, nicht von Dritten mitgelesen werden. + +### Auskunft, Sperrung, Löschung + +Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft über Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empfänger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung, Sperrung oder Löschung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten können Sie sich jederzeit unter der im Impressum angegebenen Adresse an uns wenden. + +## 3. Datenerfassung auf unserer Website + +### Server-Log-Dateien + +Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind: + + +- Browsertyp und Browserversion +- verwendetes Betriebssystem +- Referrer URL +- Hostname des zugreifenden Rechners +- Uhrzeit der Serveranfrage +- IP-Adresse + + +Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen. + +Grundlage für die Datenverarbeitung ist Art. 6 Abs. 1 lit. f DSGVO, der die Verarbeitung von Daten zur Erfüllung eines Vertrags oder vorvertraglicher Maßnahmen gestattet. + +## 4. Plugins und Tools + +### YouTube + +Unsere Website nutzt Plugins der von Google betriebenen Seite YouTube. Betreiber der Seiten ist die YouTube, LLC, 901 Cherry Ave., San Bruno, CA 94066, USA. + +Wenn Sie eine unserer mit einem YouTube-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von YouTube hergestellt. Dabei wird dem YouTube-Server mitgeteilt, welche unserer Seiten Sie besucht haben. + +Wenn Sie in Ihrem YouTube-Account eingeloggt sind, ermöglichen Sie YouTube, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem YouTube-Account ausloggen. + +Die Nutzung von YouTube erfolgt im Interesse einer ansprechenden Darstellung unserer Online-Angebote. Dies stellt ein berechtigtes Interesse im Sinne von Art. 6 Abs. 1 lit. f DSGVO dar. + +Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von YouTube unter: [https://www.google.de/intl/de/policies/privacy](https://www.google.de/intl/de/policies/privacy). + +### Vimeo + +Unsere Website nutzt Plugins des Videoportals Vimeo. Anbieter ist die Vimeo Inc., 555 West 18th Street, New York, New York 10011, USA. + +Wenn Sie eine unserer mit einem Vimeo-Plugin ausgestatteten Seiten besuchen, wird eine Verbindung zu den Servern von Vimeo hergestellt. Dabei wird dem Vimeo-Server mitgeteilt, welche unserer Seiten Sie besucht haben. Zudem erlangt Vimeo Ihre IP-Adresse. Dies gilt auch dann, wenn Sie nicht bei Vimeo eingeloggt sind oder keinen Account bei Vimeo besitzen. Die von Vimeo erfassten Informationen werden an den Vimeo-Server in den USA übermittelt. + +Wenn Sie in Ihrem Vimeo-Account eingeloggt sind, ermöglichen Sie Vimeo, Ihr Surfverhalten direkt Ihrem persönlichen Profil zuzuordnen. Dies können Sie verhindern, indem Sie sich aus Ihrem Vimeo-Account ausloggen. + +Weitere Informationen zum Umgang mit Nutzerdaten finden Sie in der Datenschutzerklärung von Vimeo unter: [https://vimeo.com/privacy](https://vimeo.com/privacy). + +# Privacy Policy + +## 1. An overview of data protection + +### General + +The following gives a simple overview of what happens to your personal information when you visit our website. Personal information is any data with which you could be personally identified. Detailed information on the subject of data protection can be found in our privacy policy found below. + +### Data collection on our website + +#### Who is responsible for the data collection on this website? + +The data collected on this website are processed by the website operator. The operator’s contact details can be found in the website’s required legal notice. + +#### How do we collect your data? + +Some data are collected when you provide it to us. This could, for example, be data you enter on a contact form. + +Other data are collected automatically by our IT systems when you visit the website. These data are primarily technical data such as the browser and operating system you are using or when you accessed the page. These data are collected automatically as soon as you enter our website. + +#### What do we use your data for? + +Part of the data is collected to ensure the proper functioning of the website. Other data can be used to analyze how visitors use the site. + +#### What rights do you have regarding your data? + +You always have the right to request information about your stored data, its origin, its recipients, and the purpose of its collection at no charge. You also have the right to request that it be corrected, blocked, or deleted. You can contact us at any time using the address given in the legal notice if you have further questions about the issue of privacy and data protection. You may also, of course, file a complaint with the competent regulatory authorities. + +## 2. General information and mandatory information + +### Data protection + +The operators of this website take the protection of your personal data very seriously. We treat your personal data as confidential and in accordance with the statutory data protection regulations and this privacy policy. + +If you use this website, various pieces of personal data will be collected. Personal information is any data with which you could be personally identified. This privacy policy explains what information we collect and what we use it for. It also explains how and for what purpose this happens. + +Please note that data transmitted via the internet (e.g. via email communication) may be subject to security breaches. Complete protection of your data from third-party access is not possible. + +### Notice concerning the party responsible for this website + +The party responsible for processing data on this website is: + +``` +Chaostreff Dortmund e.V. +Braunschweiger Str. 22 +44145 Dortmund +Germany +``` + +#### Authorized representatives + +- Tim Windelschmidt (1. Vorsitzender) + +- Stefan Hausschild (2. Vorsitzender) + +Telefon: [+49 (0)231 8 404 777](tel:+492318404777) + +E-Mail: [vorstand@chaostreff-dortmund.de](mailto:vorstand@chaostreff-dortmund.de) + +The responsible party is the natural or legal person who alone or jointly with others decides on the purposes and means of processing personal data (names, email addresses, etc.). + +### Revocation of your consent to the processing of your data + +Many data processing operations are only possible with your express consent. You may revoke your consent at any time with future effect. An informal email making this request is sufficient. The data processed before we receive your request may still be legally processed. + +### Right to file complaints with regulatory authorities + +

If there has been a breach of data protection legislation, the person affected may file a complaint with the competent regulatory authorities. The competent regulatory authority for matters related to data protection legislation is the data protection officer of the German state in which our company is headquartered. A list of data protection officers and their contact details can be found at the following link: [https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html](https://www.bfdi.bund.de/DE/Infothek/Anschriften_Links/anschriften_links-node.html). + +### Right to data portability + +You have the right to have data which we process based on your consent or in fulfillment of a contract automatically delivered to yourself or to a third party in a standard, machine-readable format. If you require the direct transfer of data to another responsible party, this will only be done to the extent technically feasible. + +### SSL or TLS encryption + +This site uses SSL or TLS encryption for security reasons and for the protection of the transmission of confidential content, such as the inquiries you send to us as the site operator. You can recognize an encrypted connection in your browser’s address line when it changes from „http://“ to „https://“ and the lock icon is displayed in your browser’s address bar. + +If SSL or TLS encryption is activated, the data you transfer to us cannot be read by third parties. + +### Information, blocking, deletion + +As permitted by law, you have the right to be provided at any time with information free of charge about any of your personal data that is stored as well as its origin, the recipient and the purpose for which it has been processed. You also have the right to have this data corrected, blocked or deleted. You can contact us at any time using the address given in our legal notice if you have further questions on the topic of personal data. + +## 3. Data collection on our website + +### Server log files + +The website provider automatically collects and stores information that your browser automatically transmits to us in „server log files“. These are: + + +- Browser type and browser version +- Operating system used +- Referrer URL +- Host name of the accessing computer +- Time of the server request +- IP address + +These data will not be combined with data from other sources. + +The basis for data processing is Art. 6 (1) (f) DSGVO, which allows the processing of data to fulfill a contract or for measures preliminary to a contract. + +## 4. Plugins and tools + +### YouTube + +Our website uses plugins from YouTube, which is operated by Google. The operator of the pages is YouTube LLC, 901 Cherry Ave., San Bruno, CA 94066, USA. + +If you visit one of our pages featuring a YouTube plugin, a connection to the YouTube servers is established. Here the YouTube server is informed about which of our pages you have visited. + +If you’re logged in to your YouTube account, YouTube allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your YouTube account. + +YouTube is used to help make our website appealing. This constitutes a justified interest pursuant to Art. 6 (1) (f) DSGVO. + +Further information about handling user data, can be found in the data protection declaration of YouTube under [https://www.google.de/intl/de/policies/privacy](https://www.google.de/intl/de/policies/privacy). + +### Vimeo + +Our website uses features provided by the Vimeo video portal. This service is provided by Vimeo Inc., 555 West 18th Street, New York, New York 10011, USA. + +If you visit one of our pages featuring a Vimeo plugin, a connection to the Vimeo servers is established. Here the Vimeo server is informed about which of our pages you have visited. In addition, Vimeo will receive your IP address. This also applies if you are not logged in to Vimeo when you visit our website or do not have a Vimeo account. The information is ransmitted to a Vimeo server in the US, where it is stored. + +If you are logged in to your Vimeo account, Vimeo allows you to associate your browsing behavior directly with your personal profile. You can prevent this by logging out of your Vimeo account. + +For more information on how to handle user data, please refer to the Vimeo Privacy Policy at [https://vimeo.com/privacy](https://vimeo.com/privacy). \ No newline at end of file diff --git a/pages/impressum.md b/pages/impressum.md new file mode 100644 index 0000000..bd1d5d4 --- /dev/null +++ b/pages/impressum.md @@ -0,0 +1,45 @@ +# Impressum + +## Postanschrift + +``` +Chaostreff Dortmund e.V. +Braunschweiger Str. 22 +44145 Dortmund +Deutschland +``` + +### Vertretungsberechtigter Vorstand + +- Tim Windelschmidt (1. Vorsitzender) +- Stefan Hausschild (2. Vorsitzender) +- Jan Münch (Kassenwart) + +## E-Mail + +- Presseanfragen bitte per E-Mail an unsere Pressestelle [presse@chaostreff-dortmund.de](mailto:presse@chaostreff-dortmund.de) +- Netz-Missbrauch Meldungen an die abuse-addresse [abuse@chaostreff-dortmund.de](mailto:abuse@chaostreff-dortmund.de) +- Anfragen bezüglich inhaltlicher Gestaltung bitte per E-Mail an den Webmaster [webmaster@chaostreff-dortmund.de](mailto:webmaster@chaostreff-dortmund.de) +- Technischer Kontakt: [postmaster@chaostreff-dortmund.de](mailto:postmaster@chaostreff-dortmund.de) + +## Telefon + +- Chaos-Raum (Di+Do ab ca 19Uhr): [+49 (0)231 8 404 777](tel:+492318404777) +- Fax: +49 (0)231 8 404 779 + + +## Kontodaten + +``` +Empfänger: Chaostreff Dortmund e.V. +IBAN: DE19 4306 0967 4009 3686 00 +BIC: GENODEM1GLS +``` + +## Logo + +Unser Logo für die Verarbeitung in redaktionellen Inhalten und sonstigen Presse-Erzeugnissen findet Ihr [hier](https://www.chaostreff-dortmund.de/presse/logo) + +## Flyer + +Unseren Aktuellen Fyler als PDF gibt es [hier](https://www.chaostreff-dortmund.de/presse/flyer/) \ No newline at end of file diff --git a/pages/index.md b/pages/index.md new file mode 100644 index 0000000..242a6ea --- /dev/null +++ b/pages/index.md @@ -0,0 +1,12 @@ +[![Raum 2 mit gemütlichen Sofas](/images/header.jpg)](https://www.chaostreff-dortmund.de/rundgang/) + +# Hi + +Wir sind eine bunt gemischte Gruppe aus computer-, technikbegeisterten und kreativen Menschen. Wir betreiben den Chaostreff aus Spaß an der Sache und um interessierten Menschen eine Plattform zu bieten, über die man sich austauschen kann. + +Wenn du Lust hast dir das mal anzuschauen, dann komm gerne vorbei. [zeiten & location](/?page=treff) + +Egal wer du bist, egal was du machst bei uns sind alle Menschen willkommen. Leider sind unsere Räume aktuell nicht barrierefrei. +Falls du Fragen hast schreibe uns an. [kontakt](/?page=kontakt) + +Wenn du mehr über uns wissen möchtest, schau hier: [über uns](/?page=about) \ No newline at end of file diff --git a/pages/kontakt/adresse.md b/pages/kontakt/adresse.md new file mode 100644 index 0000000..9f4617e --- /dev/null +++ b/pages/kontakt/adresse.md @@ -0,0 +1,9 @@ +[Zurück](?page=kontakt) + +# Adresse +``` +Chaostreff Dortmund e.V. +Braunschweiger Str. 22 +44145 Dortmund +Deutschland +``` \ No newline at end of file diff --git a/pages/kontakt/irc.md b/pages/kontakt/irc.md new file mode 100644 index 0000000..92c1cc0 --- /dev/null +++ b/pages/kontakt/irc.md @@ -0,0 +1,34 @@ +[Zurück](?page=kontakt) + +# IRC + +Wir sind im Hackint IRC-Netzwerk erreichbar, der Server ist **irc.hackint.org** (SSL Port **6697**), Channel **#ccc.do**. + +Um Missverständnisse und böses Blut zu vermeiden, hier mal reinschauen: + +[RFC-1855](https://tools.ietf.org/html/rfc1855), [RFC-2811](https://tools.ietf.org/html/rfc2811) + +## Warum Hackint + +Das Hackint-Netzwerk wurde unter anderem vom Chaostreff Dortmund ins Leben gerufen, um eine einfache und trotzdem sichere Kommunikation zu ermöglichen. Um das zu gewährleisten sind alle Server-Verbindungen verschlüsselt und alle Server ermöglichen den Benutzern eine verschlüsselte Verbindung via SSL aufzubauen. Eines der Ziele des Hackints ist es, eine Heimat für Hacker, Haecksen und ähnliche Leute auf der ganzen Welt zu schaffen und einfache Verbindungen zwischen einzelnen Gruppen zu ermöglichen. Damit das klappt, müssen sich möglichst viele chaosnahe Gruppen in diesem Netz anfinden. + +## Anleitung für des IRCens Unkundige + +### Einen IRC-Client suchen + + +Unix/Linux-nutzende können [HexChat](https://hexchat.github.io/) benutzen, oder, für das CLI-Feeling weechat oder irssi. + +Windows-nutzenden ist ebenfalls [HexChat](https://hexchat.github.io/) zu empfehlen. + +todo: Macs + +### Konfiguration von HexChat + +Als erstes muss du dir einen Nick-(Spitz-)namen ausdenken. Jede Person hat einen einzigartigen Nick, daher nicht wundern wenn gängige Namen schon vergeben sind. Ein Nick darf maximal 15 Buchstaben/Zahlen lang sein. Sonderzeichen funktionieren zwar, sehen aber komisch aus. + +Wenn du einen Nick gefunden hast, klick bei den Netzwerken aufs Plus. Dort vergibst du den Namen "hackint" und klickst direkt weiter auf bearbeiten. Statt dem voreingetragenen Server schreib dort irc.hackint.org/6697 hin. Dann wähle den Haken um SSL für alle Server des Netzwerks zu verwenden an. Nur noch die Netzwerkkonfiguration schließen und auf verbinden klicken. + +Nach dem Verbinden sollte HexChat fragen welchem Channel du joinen möchtest. Gib dort #ccc.do ein und drücke ok. + +\o/ du kannst uns jetzt Nachrichten schreiben. \ No newline at end of file diff --git a/pages/kontakt/mail.md b/pages/kontakt/mail.md new file mode 100644 index 0000000..113c9a8 --- /dev/null +++ b/pages/kontakt/mail.md @@ -0,0 +1,45 @@ +[Zurück](?page=kontakt) + +# eMail + +Wenn du eine einfache Anfrage hast, schreib einfach an unsere Mailingliste (aber schreib bitte dabei, dass du nicht auf der Liste eingetragen bist. Sonst erreichen dich die Antworten möglicherweise nicht). Wenn du dich zum Chaostreff gesellen möchtest, trägst du dich am besten auf der Liste ein. + +Unsere Mailinglist ist unter [discuss@lists.chaostreff-dortmund.de](mailto:discuss@lists.chaostreff-dortmund.de), diese ist auch unsere öffentliche Mailingliste. + +Presse: [presse@chaostreff-dortmund.de](mailto:presse@chaostreff-dortmund.de) + +Spenden: [spenden@chaostreff-dortmund.de](mailto:spenden@chaostreff-dortmund.de) + + +## Subscriben/Unsubscriben auf die Mailingliste + +Die Mailingliste wird von Mailman gemanaged und ist offen für alle. Subscriben kann man sich auf der Mailingliste, indem man auf die [Mailman](https://lists.chaostreff-dortmund.de/cgi-bin/mailman/listinfo/discuss) Seite geht und sich dort einträgt. + +## Was ist so auf der Mailingliste + +Alle Chaostreff-Aktivitäten werden (sollten zumindest) über die Mailingliste geplant oder zumindest angekündigt. Wenn man nur wissen möchte, was der Chaostreff gerade so macht, kann man natürlich auch regelmässig auf unsere Webseite gucken. Wenn man wirklich wissen möchte was gerade geht, sollte man sich auf die Mailingliste eintragen. Hier können und werden aktuelle Weltherrschaftspläne geschmiedet¹, Kontakte geschlossen, und auch sonstige, den Chaostreff betreffende, Themen besprochen. + +¹liebes Finanzamt, das ist nur Spaß :) + +Außerdem findet man alles was für den Chaostreff von Interesse sein könnte und mit dem ein entfernter Bezug zum Chaostreff hergestellt werden kann. Also von Diskussionen zu aktueller Hardware und dem kreativen Umgang damit, über die neuesten Fort- und Rückschritte in aktuellen Gesetzgebungsverfahren und anderen Zeitgeschehen. + +Grundsätzlich gilt: Die Mailingliste ist nicht readonly und lebt durch eure Postings. Also postet! + + +## FAQ + +### Wer kann die Subscriptions lesen? + +Jeder der auf der Liste subscribed ist kann sich die Subscriber-Liste ansehen. + +### Warum kommt meine Mail nicht auf der Mailingliste an? + +Damit die Mailingliste nicht als Spam-Multiplexer fungiert, ist sie so konfiguriert, dass alle Mails, die nicht eine auf der Liste eingetragene Mailaddresse im From: Header stehen haben, erstmal zur Moderator Mailingliste weitergeleitet werden. Da auch Moderatoren noch ein anderes Leben haben, kann das dann etwas dauern. + +### Wer sind diese Moderatoren? + +Zur Zeit: tarja, fisch und claas + +### Ich habe technische Probleme + +Bei technischen Problemen (subscription passt nicht) kann dem Listen-Owner geschrieben werden. Diese/r wird versuchen, dich innerhalb ihrer/seiner Möglichkeiten, nach bestem Wissen und Gewissen, zu unterstützen. \ No newline at end of file diff --git a/pages/kontakt/tel.md b/pages/kontakt/tel.md new file mode 100644 index 0000000..de4441b --- /dev/null +++ b/pages/kontakt/tel.md @@ -0,0 +1,5 @@ +[Zurück](?page=kontakt) + +# Telefon + +Wenn du eher auf Voice-Communication stehst (oder einfach wissen willst, ob jemand im Raum ist), kannst du uns auch anrufen: Es klingelt im Raum, wenn man [+49 (0)231 8 404 777](tel:+492318404777) wählt. \ No newline at end of file diff --git a/pages/support.md b/pages/support.md new file mode 100644 index 0000000..4d8ccc0 --- /dev/null +++ b/pages/support.md @@ -0,0 +1,23 @@ +# Spenden + +Der Chaostreff Dortmund finanziert sich vollkommen aus sich selbst: Das bedeutet aus dem Portemonnaie seiner Mitglieder und oder derer, die den Treff für sinnvoll halten. + +## Wieso und wofür? + +Wir haben gewisse Kosten zu decken, dazu gehören die Miete, Kosten für Heizung, Strom und Internet. Letztere Punkte werden durch das positive Klima im Langen August für uns sehr angenehm gehalten. Damit ist jedoch gerade einmal die grundsätzliche Infrastruktur geschaffen. + +Für Projekte fehlt es dem Treff schon wieder an Geld. Allein die Finanzierung der gemeinsamen Räume basiert Gerüchten zufolge eher auf einer raffinierten Mate-Wiederverkaufsstrategie in Kombination mit einer Ausnutzung der suchterregenden Auswirkungen dieses Getränks. + +Als groben Richtwert kann man sagen, dass der Spendendurchschnitt irgendwo zwischen 23 und 42 Euro monatlich liegt. An diesem Punkt sei erwähnt dass wir über jede Spende sehr erfreut sind, am liebsten natürlich wenn sie regelmäßig ist. ;) + +## Gerne, wo soll ich unterschreiben? + +``` +Empfänger: Chaostreff Dortmund e.V. +IBAN: DE19 4306 0967 4009 3686 00 +BIC: GENODEM1GLS +``` + +Der Chaostreff Dortmund ist vom Finanzamt als gemeinnützig anerkannt und deshalb dürfen wir Spendenbescheinigungen ausstellen. + +*Der Chaostreff sagt in jedem Fall schon mal Danke!* \ No newline at end of file diff --git a/pages/treff.md b/pages/treff.md new file mode 100644 index 0000000..3a6efc4 --- /dev/null +++ b/pages/treff.md @@ -0,0 +1,20 @@ +# Der Treff + +## Zeiten + +Wir sind für gewöhnlich **Freitags ab 19:00** (±1hr Chaos-Verspätung) im Treff. Bevor du kommst schau einmal nach ob jemand da ist. Das kannst du oben beim Raumstatus sehen. + +## Location +Die Räume sind im **Langen August**. Wenn du den Langen August gefunden hast, geh durch das erste Tor, dann findest du auf der rechten Seite eine Tür in das Treppenhaus. Da noch zwei Stockwerke hoch, die rechte Tür führt zu uns. Wenn die Türen verschlossen sind, einfach (beim Chaostreff) klingeln. + +## Was wir tun + +Von dem gemütlichen Beisammensitzen, über das Fachsimpeln von technischen Themen und das Diskutieren von aktuellen politischen Themen ist eigentlich alles dabei. Bei uns ist viel Raum für eigene Ideen und Projekte. Alle sind dazu eingeladen uns zu besuchen und sich einzubringen. + +Wir freuen uns sehr über neue Gesichter, egal welchen Alters oder Geschlechts ihr seid. Spezielles Vorwissen über Computer und Technik ist nicht erforderlich. + +Wir sind bemüht unsere Interessen in Projekten zu organisieren. Du bist herzlich eingeladen mal einen Blick auf diese zu werfen. + +Einmal im Monat halten Leute im Chaostreff Vorträge über verschiedene Themen technischer und auch nichttechnischer Natur. Informationen darüber findest du unter Events. + +Jeden ersten Donnerstag und jeden dritten Dienstag im Monat treffen wir uns zum „Topic Treff“ um verschiedene Themen rund um den Chaostreff zu besprechen. Alle sind herzlich eingeladen, Start ist um 20:00 Uhr. \ No newline at end of file diff --git a/pages/verein.md b/pages/verein.md new file mode 100644 index 0000000..c846f62 --- /dev/null +++ b/pages/verein.md @@ -0,0 +1,33 @@ +# Mitglied werden + +Durch deine Mitgliedschaft kannst du dem CTDO direkt deine Unterstützung zukommen lassen. Wenn du uns zu deiner Mitgliedschaft einen freiwilligen Betrag spendest, wird dieser für neue Projekte und die Umsetzung von tollen Ideen verwendet. + +Du kannst den Mitgliedsantrag [hier](https://www.chaostreff-dortmund.de/wp-content/uploads/2011/07/mitgliedsantrag-2017.pdf) herunterladen, ausfüllen, und zum Chaostreff mitbringen. + +Selbstverständlich kannst du auch im Chaostreff-Dortmund aktiv sein, ohne Mitglied in unserem Verein zu sein. Es erfolgt keine Bestätigung über die Annahme des Mitgliedsantrages. Im Falle einer Ablehnung erfolgt eine Mitteilung. + +## Satzung + +Die aktuelle Satzung des Vereins Chaostreff Dortmund e.V. kann stets hier auf unserer Webseite heruntergeladen werden. Darin eventuell referenzierte Anhänge oder Zusatzvereinbarungen, die nicht notwendigerweise Teil der Satzung selbst sind, sind jeweils der Einfachheit halber als ein PDF-Dokument direkt der aktuellen Satzung anhängig. + +[Aktuelle Satzung](https://www.chaostreff-dortmund.de/presse/satzung/aktuelle_satzung.pdf) + +[Alle Satzungen](https://www.chaostreff-dortmund.de/presse/satzung/) + +## Gründungsgeschichte + +Der Verein wurde als „Trägerverein“ des Chaostreffs im Jahre 2009 gegründet um dem Chaostreff als losen Verbund von Menschen gleicher Interessen eine (solide) rechtliche Grundlage zu geben. + +Der Wechsel zur Rechtsform des eingetragenen Vereins sollte verschiedene Haftungsprobleme, vor allem finanzieller und mietrechtlicher Natur, aus der Welt schaffen. So lief der Mietvertrag für die Räume des Chaostreffs ursprünglich über eine Einzelperson, ebenso wie die jeweilige Kassenverwaltung die Finanzen über ein privates Konto führte. + +Erste Diskussionen über den Sinn der Gründung eines Vereins gab es bereits im Jahre 2007, der Prozess kam aber immer wieder zum Stillstand und wurde erst 2009 wieder aufgenommen. + +Die Gründung gestaltete sich auch deswegen so schwierig, weil unter den Mitgliedern/Besuchenden des Chaostreffs verschiedene Strömungen existierten, deren Standpunkte zur Vereinsgründung unterschiedlicher nicht sein konnten, woraufhin sich manch eine Person gar vollständig aus dem Chaostreff zurückzog. + +Der Chaostreff teilte sich grob in zwei Lager, von denen das eine die Gründung eines Vereins (u.a. in Furcht vor Vereinsmeierei) vollständig ablehnte. Wohl auch teilweise zu Recht, weil es die Befürchtung gab, dass der Verein unerwünschten Einfluss auf das Konstrukt „Chaostreff“ ausüben könnte, der bis dahin selbstverwaltet, oder etwas überspitzt ausgedrückt, „anarchisch“ daherkam. Das andere Lager betrachtete die Vereinsgründung hingegen als „notwendiges Übel“, weil sich in Sachen Rechtssicherheit keine Alternative offenbarte. + +Nach der Gründungsversammlung existierte der CTDO längere Zeit als eingetragener Verein „iG“ (in Gründung). Es folgte eine mehrfache Überarbeitung der Satzung, was im wesentlichen der Zusammenarbeit mit dem Finanzamt geschuldet war, in dem Bemühen, als gemeinnützig anerkannt zu werden. 2011-08-05 lag die Satzung schließlich dem Notar vor. + +Doch durch Umzüge von einigen Gründungsmitgliedern verzögerte sich die Eintragung weiterhin, da noch Unterschriften (u.a. aus dem Ausland) von eben diesen Mitgliedern organisiert werden mussten. + +Schließlich haben wir es aber geschafft: 2012-02-09 kann als Datum gesehen werden, ab dem sich der Chaostreff Dortmund als eingetragener Verein bezeichnen darf. Einen weiteren Meilenstein erreichten wir etwas später: Das Finanzamt stellte uns die Anerkennung der Gemeinnützigkeit in Aussicht, zu der es noch einer Änderung in der Satzung bedurfte. \ No newline at end of file diff --git a/php/events.php b/php/events.php new file mode 100644 index 0000000..ed2f920 --- /dev/null +++ b/php/events.php @@ -0,0 +1,129 @@ += 4 && $f != 'treff.md' && $f != 'topictreff.md' && $f != 'repaircafe.md' && $f != 'brunch.md') + $output[] = $f; + } + return $output; +} + +function generate_event_list($limit = -1) { + $events = scan_for_events(); + $output = ""; + if ($limit == -1) { + foreach ($events as $event) { + $lines = file(__DIR__ . '/../events/' . $event); + $title = $lines[0]; + $desc = $lines[1]; + $date = $lines[2]; + $veranstaltungsort = $lines[4]; + $output .= '

'.$veranstaltungsort.'

'.$date.'

'.$title.'

'.$desc.'

'; + } + } else { + if($limit > count($events)) + $limit = count($events); + for ($i = 0; $i < $limit; $i++) { + $lines = file(__DIR__ . '/../events/' . $event); + $title = $lines[0]; + $desc = $lines[1]; + $date = $lines[2]; + $veranstaltungsort = $lines[4]; + $output .= '

'.$veranstaltungsort.'

'.$date.'

'.$title.'

'.$desc.'

'; + } + } + return $output; +} + +function get_event_content($id) { + $lines = file(__DIR__ . '/../events/' . str_replace('.', '', $id) . '.md'); + $output = ""; + $output .= '## Datum/Zeit'."\n\n"; + $output .= $lines[2]."\n".$lines[3]."\n\n## Veranstaltungsort\n\n".$lines[4]."\n\n"; + for ($i = 6; $i < count($lines); $i++) + $output .= $lines[$i] . "\n"; + return $output; +} + +function get_next_topic() { + $output = new stdClass(); + $currentDate = new DateTime(); + + $next_topic = clone $currentDate; + $next_topic->modify('second Tuesday of this month +1 week'); + while ($next_topic->format('N') !== '2') { + $next_topic->add(new DateInterval('P1D')); + } + + $output->days = $currentDate->diff($next_topic)->days+1; + $output->date = $next_topic->format('Y-m-d'); + + return $output; +} + +function get_next_treff() { + $output = new stdClass(); + // Get current date and time + $now = new DateTime(); + + // Find the next Friday + $now->modify('next Friday'); + + // Calculate the number of days until the next Friday + $diff = $now->diff(new DateTime()); + $days_until = $diff->format('%a'); + + $output->days = $days_until+1; + $output->date = $now->format('Y-m-d'); + // Return an array with the count and date of the next Friday + return $output; +} + +function get_next_repaircafe() { + $output = new stdClass(); + $today = new DateTime(); + $lastDayOfMonth = clone $today; + $lastDayOfMonth->modify('last day of this month'); + $lastThursday = clone $lastDayOfMonth; + + while ($lastThursday->format('w') != 4) { // Thursday is represented by 4 (0-6, where 0 is Sunday) + $lastThursday->modify('-1 day'); + } + + $daysUntilLastThursday = $today->diff($lastThursday)->days; + + $output->days = $daysUntilLastThursday; + $output->date = $lastThursday->format('Y-m-d'); + + return $output; +} + +function get_next_brunch() { + $now = new DateTime(); + + if ($now->format('w') == 0) { + $nextSunday = clone $now; + } else { + $nextSunday = new DateTime('next Sunday'); + } + + $weekNumber = (int)$nextSunday->format('W'); + $isEvenWeek = ($weekNumber % 2) == 0; + + if ($isEvenWeek) { + $nextSunday->modify('+1 week'); + } + + $differenz = $nextSunday->diff($now); + $days = $differenz->days; + + $output = new stdClass(); + $output->date = $nextSunday->format('Y-m-d'); + $output->days = $days+1; + + return $output; +} + +?> \ No newline at end of file diff --git a/php/posts.php b/php/posts.php new file mode 100644 index 0000000..543d536 --- /dev/null +++ b/php/posts.php @@ -0,0 +1,48 @@ += 4) + $output[] = $f; + } + return $output; +} + +function generate_post_list($limit = -1) { + $events = scan_for_posts(); + $output = ""; + if ($limit == -1) { + foreach ($events as $event) { + $lines = file(__DIR__ . '/../posts/' . $event); + $title = $lines[0]; + $desc = $lines[1]; + $date = $lines[2]; + $signatur = $lines[3]; + $output .= '

'.$signatur.'
'.$date.'

'.$title.'

'.$desc.'

'; + } + } else { + if($limit > count($events)) + $limit = count($events); + for ($i = 0; $i < $limit; $i++) { + $lines = file(__DIR__ . '/../posts/' . $events[$i]); + $title = $lines[0]; + $desc = $lines[1]; + $date = $lines[2]; + $signatur = $lines[3]; + $output .= '

'.$signatur.'

'.$date.'

'.$title.'

'.$desc.'

'; + } + } + return $output; +} + +function get_post_content($id) { + $lines = file(__DIR__ . '/../posts/' . str_replace('.', '', $id) . '.md'); + $output = ""; + for ($i = 5; $i < count($lines); $i++) + $output .= $lines[$i] . "\n"; + return $output; +} + +?> \ No newline at end of file diff --git a/php/util.php b/php/util.php new file mode 100644 index 0000000..92ae965 --- /dev/null +++ b/php/util.php @@ -0,0 +1,38 @@ +'; +} + +function generate_nav($active_page, $pages, $page_names) { + $output = array(); + foreach ($pages as $key => $page) { + if ($page == $active_page) + $output[] = ['page' => $page, 'name' => $page_names[$key], 'active' => TRUE]; + else + $output[] = ['page' => $page, 'name' => $page_names[$key], 'active' => FALSE]; + } + + return $output; +} + +function html_link($href, $class, $innerHTML, $blank) { + if($blank) + return ''.$innerHTML.''; + else + return ''.$innerHTML.''; +} + +function get_web_json($url) { + $data = json_decode(file_get_contents($url), true); + return $data; +} + +function str_mass_replace($searchs, $replacers, $string) { + foreach ($searchs as $key => $search) { + $string = str_replace($search, $replacers[$key], $string); + } + return $string; +} + +?> \ No newline at end of file diff --git a/posts/0.md b/posts/0.md new file mode 100644 index 0000000..d651a33 --- /dev/null +++ b/posts/0.md @@ -0,0 +1,14 @@ +ChaosBrunch am Sonntag, 02.04.2023 +Alle zwei Wochen an einem Sonntag lädt der CTDO zum Mitbring-Brunch in seinen Räume im Kulturzentrum „Langer August“ ein! +2023-05-01 +starcalc +------------------------ +# ChaosBrunch am Sonntag, 02.04.2023 + +Alle zwei Wochen an einem Sonntag lädt der CTDO zum **Mitbring-Brunch** in seinen Räume im Kulturzentrum „[Langer August](/?page=treff)“ ein! + +Um **ca. 11 Uhr** geht es los, das Ende ist wie immer offen. Die Details dazu gibt es im [ctdo wiki](https://wiki.ctdo.de/events/hackerbrunch)! + +Schaut einfach vorab im (MitbringPad)[https://md.ctdo.de/brunch] rein und kündigt an was ihr auf den Tisch werft, alles kann, nichts muss! + +Es sind sowohl alte als auch neue Menschen gerne gesehen! Bringt was zu futtern, euer aktuelles Projekt, beides oder einfach nur Neugierde mit! \ No newline at end of file diff --git a/posts/1.md b/posts/1.md new file mode 100644 index 0000000..bd3aa63 --- /dev/null +++ b/posts/1.md @@ -0,0 +1,14 @@ +Bunt, Bunter, Chaostreff Dortmund +Wir öffnen unsere Türen und laden Euch herzlich in unseren Chaostreff im Langen August ein. +2023-05-02 +Fisch +------------------------ +# Bunt, Bunter, Chaostreff Dortmundchten + +![DORTBUND](/images/events/DORTBUNT.nebenan_Banner-fuer-Anmeldende-768x154.png) + +Wir öffnen unsere Türen und laden Euch herzlich in unseren Chaostreff im [Langen August](/?page=treff) ein. + +Am 5. und 6. Mai zwischen 14 und 20 Uhr könnt ihr einfach vorbeikommen und erhaltet einen Einblick in unseren Space, unsere Vereinsräume mitsamt allen Werkstätten. Um das ganze netter zu gestalten, gibt es kalte Getränke, Waffeln und auch der Grill wird angemacht. + +Wir freuen uns auf Euch, egal ob jung, alt, groß, klein, laut oder leise. Hauptsache bunt! \ No newline at end of file diff --git a/templates/404.twig b/templates/404.twig new file mode 100644 index 0000000..ea9d678 --- /dev/null +++ b/templates/404.twig @@ -0,0 +1 @@ +

404 Die Seite "{{ page }}" konnte nicht gefunden werden!

\ No newline at end of file diff --git a/templates/blog.twig b/templates/blog.twig new file mode 100644 index 0000000..7352f40 --- /dev/null +++ b/templates/blog.twig @@ -0,0 +1,2 @@ +

Blog

+{{ posts | raw }} \ No newline at end of file diff --git a/templates/default.twig b/templates/default.twig new file mode 100644 index 0000000..0e57b82 --- /dev/null +++ b/templates/default.twig @@ -0,0 +1,54 @@ + + + + + + + + ctdo - {{ title }} + + {% for item in css %} + + {% endfor %} + + + + + +
+ = + +
+ +
+ +

Raumstatus: {{ raumstatus | raw }}

+ + {{ main | raw }} + +
+ + + + + \ No newline at end of file diff --git a/templates/events.twig b/templates/events.twig new file mode 100644 index 0000000..f415331 --- /dev/null +++ b/templates/events.twig @@ -0,0 +1,35 @@ +

Events

+ +{% if topic.days == 0 %} +

Der nächste Topictreff findet heute [{{ topic.date }}] statt!

+{% elseif topic.days == 1 %} +

Der nächste Topictreff findet morgen [{{ topic.date }}] statt!

+{% else %} +

Der nächste Topictreff findet in {{ topic.days }} Tagen [{{ topic.date }}] statt!

+{% endif %} + +{% if treff.days == 0 %} +

Der nächste Treff findet heute [{{ treff.date }}] statt!

+{% elseif treff.days == 1 %} +

Der nächste Treff findet morgen [{{ treff.date }}] statt!

+{% else %} +

Der nächste Treff findet in {{ treff.days }} Tagen [{{ treff.date }}] statt!

+{% endif %} + +{% if repaircafe.days == 0 %} +

Das nächste RepairCafe findet heute [{{ repaircafe.date }}] statt!

+{% elseif repaircafe.days == 1 %} +

Das nächste RepairCafe findet morgen [{{ repaircafe.date }}] statt!

+{% else %} +

Das nächste RepairCafe findet in Tagen {{ repaircafe.days }} [{{ repaircafe.date }}] statt!

+{% endif %} + +{% if brunch.days == 0 %} +

Der nächste Brunch findet heute [{{ brunch.date }}] statt!

+{% elseif brunch.days == 1 %} +

Der nächste Brunch findet morgen [{{ brunch.date }}] statt!

+{% else %} +

Der nächste Brunch findet in {{ brunch.days }} Tagen [{{ brunch.date }}] statt!

+{% endif %} + +{{ events | raw }} \ No newline at end of file diff --git a/templates/index.twig b/templates/index.twig new file mode 100644 index 0000000..b7d44f3 --- /dev/null +++ b/templates/index.twig @@ -0,0 +1,37 @@ +{{ content | raw }} + +

Die nächsten Events:

+ +{% if topic.days == 0 %} +

Der nächste Topictreff findet heute [{{ topic.date }}] statt!

+{% elseif topic.days == 1 %} +

Der nächste Topictreff findet morgen [{{ topic.date }}] statt!

+{% else %} +

Der nächste Topictreff findet in {{ topic.days }} Tagen [{{ topic.date }}] statt!

+{% endif %} + +{% if treff.days == 0 %} +

Der nächste Treff findet heute [{{ treff.date }}] statt!

+{% elseif treff.days == 1 %} +

Der nächste Treff findet morgen [{{ treff.date }}] statt!

+{% else %} +

Der nächste Treff findet in {{ treff.days }} Tagen [{{ treff.date }}] statt!

+{% endif %} + +{% if repaircafe.days == 0 %} +

Das nächste RepairCafe findet heute [{{ repaircafe.date }}] statt!

+{% elseif repaircafe.days == 1 %} +

Das nächste RepairCafe findet morgen [{{ repaircafe.date }}] statt!

+{% else %} +

Das nächste RepairCafe findet in Tagen {{ repaircafe.days }} [{{ repaircafe.date }}] statt!

+{% endif %} + +{% if brunch.days == 0 %} +

Der nächste Brunch findet heute [{{ brunch.date }}] statt!

+{% elseif brunch.days == 1 %} +

Der nächste Brunch findet morgen [{{ brunch.date }}] statt!

+{% else %} +

Der nächste Brunch findet in {{ brunch.days }} Tagen [{{ brunch.date }}] statt!

+{% endif %} + +{{ events | raw }} \ No newline at end of file diff --git a/templates/kontakt.twig b/templates/kontakt.twig new file mode 100644 index 0000000..70a3186 --- /dev/null +++ b/templates/kontakt.twig @@ -0,0 +1,13 @@ + + irc + + + email + +
+ + tel + + + adresse + \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..d54ca61 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,25 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + /** @var \Closure(string):void */ + private static $includeFile; + + /** @var ?string */ + private $vendorDir; + + // PSR-4 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixLengthsPsr4 = array(); + /** + * @var array[] + * @psalm-var array> + */ + private $prefixDirsPsr4 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr4 = array(); + + // PSR-0 + /** + * @var array[] + * @psalm-var array> + */ + private $prefixesPsr0 = array(); + /** + * @var array[] + * @psalm-var array + */ + private $fallbackDirsPsr0 = array(); + + /** @var bool */ + private $useIncludePath = false; + + /** + * @var string[] + * @psalm-var array + */ + private $classMap = array(); + + /** @var bool */ + private $classMapAuthoritative = false; + + /** + * @var bool[] + * @psalm-var array + */ + private $missingClasses = array(); + + /** @var ?string */ + private $apcuPrefix; + + /** + * @var self[] + */ + private static $registeredLoaders = array(); + + /** + * @param ?string $vendorDir + */ + public function __construct($vendorDir = null) + { + $this->vendorDir = $vendorDir; + self::initializeIncludeClosure(); + } + + /** + * @return string[] + */ + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + /** + * @return array[] + * @psalm-return array> + */ + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + /** + * @return array[] + * @psalm-return array + */ + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + /** + * @return string[] Array of classname => path + * @psalm-return array + */ + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param string[] $classMap Class to filename map + * @psalm-param array $classMap + * + * @return void + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + * + * @return void + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param string[]|string $paths The PSR-0 base directories + * + * @return void + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param string[]|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + * + * @return void + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + * + * @return void + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + * + * @return void + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + * + * @return void + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + * + * @return void + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + + if (null === $this->vendorDir) { + return; + } + + if ($prepend) { + self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; + } else { + unset(self::$registeredLoaders[$this->vendorDir]); + self::$registeredLoaders[$this->vendorDir] = $this; + } + } + + /** + * Unregisters this instance as an autoloader. + * + * @return void + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + + if (null !== $this->vendorDir) { + unset(self::$registeredLoaders[$this->vendorDir]); + } + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return true|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + $includeFile = self::$includeFile; + $includeFile($file); + + return true; + } + + return null; + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + /** + * Returns the currently registered loaders indexed by their corresponding vendor directories. + * + * @return self[] + */ + public static function getRegisteredLoaders() + { + return self::$registeredLoaders; + } + + /** + * @param string $class + * @param string $ext + * @return string|false + */ + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } + + /** + * @return void + */ + private static function initializeIncludeClosure() + { + if (self::$includeFile !== null) { + return; + } + + /** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + * + * @param string $file + * @return void + */ + self::$includeFile = \Closure::bind(static function($file) { + include $file; + }, null, null); + } +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..c6b54af --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,352 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer; + +use Composer\Autoload\ClassLoader; +use Composer\Semver\VersionParser; + +/** + * This class is copied in every Composer installed project and available to all + * + * See also https://getcomposer.org/doc/07-runtime.md#installed-versions + * + * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final + */ +class InstalledVersions +{ + /** + * @var mixed[]|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null + */ + private static $installed; + + /** + * @var bool|null + */ + private static $canGetVendors; + + /** + * @var array[] + * @psalm-var array}> + */ + private static $installedByVendor = array(); + + /** + * Returns a list of all package names which are present, either by being installed, replaced or provided + * + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackages() + { + $packages = array(); + foreach (self::getInstalled() as $installed) { + $packages[] = array_keys($installed['versions']); + } + + if (1 === \count($packages)) { + return $packages[0]; + } + + return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); + } + + /** + * Returns a list of all package names with a specific type e.g. 'library' + * + * @param string $type + * @return string[] + * @psalm-return list + */ + public static function getInstalledPackagesByType($type) + { + $packagesByType = array(); + + foreach (self::getInstalled() as $installed) { + foreach ($installed['versions'] as $name => $package) { + if (isset($package['type']) && $package['type'] === $type) { + $packagesByType[] = $name; + } + } + } + + return $packagesByType; + } + + /** + * Checks whether the given package is installed + * + * This also returns true if the package name is provided or replaced by another package + * + * @param string $packageName + * @param bool $includeDevRequirements + * @return bool + */ + public static function isInstalled($packageName, $includeDevRequirements = true) + { + foreach (self::getInstalled() as $installed) { + if (isset($installed['versions'][$packageName])) { + return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); + } + } + + return false; + } + + /** + * Checks whether the given package satisfies a version constraint + * + * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: + * + * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') + * + * @param VersionParser $parser Install composer/semver to have access to this class and functionality + * @param string $packageName + * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package + * @return bool + */ + public static function satisfies(VersionParser $parser, $packageName, $constraint) + { + $constraint = $parser->parseConstraints($constraint); + $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + + return $provided->matches($constraint); + } + + /** + * Returns a version constraint representing all the range(s) which are installed for a given package + * + * It is easier to use this via isInstalled() with the $constraint argument if you need to check + * whether a given version of a package is installed, and not just whether it exists + * + * @param string $packageName + * @return string Version constraint usable with composer/semver + */ + public static function getVersionRanges($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + $ranges = array(); + if (isset($installed['versions'][$packageName]['pretty_version'])) { + $ranges[] = $installed['versions'][$packageName]['pretty_version']; + } + if (array_key_exists('aliases', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); + } + if (array_key_exists('replaced', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); + } + if (array_key_exists('provided', $installed['versions'][$packageName])) { + $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); + } + + return implode(' || ', $ranges); + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['version'])) { + return null; + } + + return $installed['versions'][$packageName]['version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present + */ + public static function getPrettyVersion($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['pretty_version'])) { + return null; + } + + return $installed['versions'][$packageName]['pretty_version']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference + */ + public static function getReference($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + if (!isset($installed['versions'][$packageName]['reference'])) { + return null; + } + + return $installed['versions'][$packageName]['reference']; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @param string $packageName + * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. + */ + public static function getInstallPath($packageName) + { + foreach (self::getInstalled() as $installed) { + if (!isset($installed['versions'][$packageName])) { + continue; + } + + return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; + } + + throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); + } + + /** + * @return array + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} + */ + public static function getRootPackage() + { + $installed = self::getInstalled(); + + return $installed[0]['root']; + } + + /** + * Returns the raw installed.php data for custom implementations + * + * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. + * @return array[] + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} + */ + public static function getRawData() + { + @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = include __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + + return self::$installed; + } + + /** + * Returns the raw data of all installed.php which are currently loaded for custom implementations + * + * @return array[] + * @psalm-return list}> + */ + public static function getAllRawData() + { + return self::getInstalled(); + } + + /** + * Lets you reload the static array from another file + * + * This is only useful for complex integrations in which a project needs to use + * this class but then also needs to execute another project's autoloader in process, + * and wants to ensure both projects have access to their version of installed.php. + * + * A typical case would be PHPUnit, where it would need to make sure it reads all + * the data it needs from this class, then call reload() with + * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure + * the project in which it runs can then also use this class safely, without + * interference between PHPUnit's dependencies and the project's dependencies. + * + * @param array[] $data A vendor/composer/installed.php data set + * @return void + * + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data + */ + public static function reload($data) + { + self::$installed = $data; + self::$installedByVendor = array(); + } + + /** + * @return array[] + * @psalm-return list}> + */ + private static function getInstalled() + { + if (null === self::$canGetVendors) { + self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); + } + + $installed = array(); + + if (self::$canGetVendors) { + foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + if (isset(self::$installedByVendor[$vendorDir])) { + $installed[] = self::$installedByVendor[$vendorDir]; + } elseif (is_file($vendorDir.'/composer/installed.php')) { + $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; + if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { + self::$installed = $installed[count($installed) - 1]; + } + } + } + } + + if (null === self::$installed) { + // only require the installed.php file if this file is loaded from its dumped location, + // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 + if (substr(__DIR__, -8, 1) !== 'C') { + self::$installed = require __DIR__ . '/installed.php'; + } else { + self::$installed = array(); + } + } + $installed[] = self::$installed; + + return $installed; + } +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +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. + diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..1395a2a --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,72 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => $vendorDir . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => $vendorDir . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Schema\\Context' => $vendorDir . '/nette/schema/src/Schema/Context.php', + 'Nette\\Schema\\DynamicParameter' => $vendorDir . '/nette/schema/src/Schema/DynamicParameter.php', + 'Nette\\Schema\\Elements\\AnyOf' => $vendorDir . '/nette/schema/src/Schema/Elements/AnyOf.php', + 'Nette\\Schema\\Elements\\Base' => $vendorDir . '/nette/schema/src/Schema/Elements/Base.php', + 'Nette\\Schema\\Elements\\Structure' => $vendorDir . '/nette/schema/src/Schema/Elements/Structure.php', + 'Nette\\Schema\\Elements\\Type' => $vendorDir . '/nette/schema/src/Schema/Elements/Type.php', + 'Nette\\Schema\\Expect' => $vendorDir . '/nette/schema/src/Schema/Expect.php', + 'Nette\\Schema\\Helpers' => $vendorDir . '/nette/schema/src/Schema/Helpers.php', + 'Nette\\Schema\\Message' => $vendorDir . '/nette/schema/src/Schema/Message.php', + 'Nette\\Schema\\Processor' => $vendorDir . '/nette/schema/src/Schema/Processor.php', + 'Nette\\Schema\\Schema' => $vendorDir . '/nette/schema/src/Schema/Schema.php', + 'Nette\\Schema\\ValidationException' => $vendorDir . '/nette/schema/src/Schema/ValidationException.php', + 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => $vendorDir . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => $vendorDir . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => $vendorDir . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => $vendorDir . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php', + 'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..99f22b3 --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,13 @@ + $vendorDir . '/symfony/deprecation-contracts/function.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..15a2ff3 --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,9 @@ + array($vendorDir . '/twig/twig/src'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), + 'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'), + 'League\\Config\\' => array($vendorDir . '/league/config/src'), + 'League\\CommonMark\\' => array($vendorDir . '/league/commonmark/src'), + 'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..a6c3e5c --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,50 @@ +register(true); + + $filesToLoad = \Composer\Autoload\ComposerStaticInitce41dc6e38789c9f43c485905030864d::$files; + $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + + require $file; + } + }, null, null); + foreach ($filesToLoad as $fileIdentifier => $file) { + $requireFile($fileIdentifier, $file); + } + + return $loader; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..3286ac4 --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,152 @@ + __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php', + '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + ); + + public static $prefixLengthsPsr4 = array ( + 'T' => + array ( + 'Twig\\' => 5, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Polyfill\\Ctype\\' => 23, + ), + 'P' => + array ( + 'Psr\\EventDispatcher\\' => 20, + ), + 'L' => + array ( + 'League\\Config\\' => 14, + 'League\\CommonMark\\' => 18, + ), + 'D' => + array ( + 'Dflydev\\DotAccessData\\' => 22, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Twig\\' => + array ( + 0 => __DIR__ . '/..' . '/twig/twig/src', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Polyfill\\Ctype\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', + ), + 'Psr\\EventDispatcher\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/event-dispatcher/src', + ), + 'League\\Config\\' => + array ( + 0 => __DIR__ . '/..' . '/league/config/src', + ), + 'League\\CommonMark\\' => + array ( + 0 => __DIR__ . '/..' . '/league/commonmark/src', + ), + 'Dflydev\\DotAccessData\\' => + array ( + 0 => __DIR__ . '/..' . '/dflydev/dot-access-data/src', + ), + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\HtmlStringable' => __DIR__ . '/..' . '/nette/utils/src/HtmlStringable.php', + 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Localization\\Translator' => __DIR__ . '/..' . '/nette/utils/src/Translator.php', + 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Schema\\Context' => __DIR__ . '/..' . '/nette/schema/src/Schema/Context.php', + 'Nette\\Schema\\DynamicParameter' => __DIR__ . '/..' . '/nette/schema/src/Schema/DynamicParameter.php', + 'Nette\\Schema\\Elements\\AnyOf' => __DIR__ . '/..' . '/nette/schema/src/Schema/Elements/AnyOf.php', + 'Nette\\Schema\\Elements\\Base' => __DIR__ . '/..' . '/nette/schema/src/Schema/Elements/Base.php', + 'Nette\\Schema\\Elements\\Structure' => __DIR__ . '/..' . '/nette/schema/src/Schema/Elements/Structure.php', + 'Nette\\Schema\\Elements\\Type' => __DIR__ . '/..' . '/nette/schema/src/Schema/Elements/Type.php', + 'Nette\\Schema\\Expect' => __DIR__ . '/..' . '/nette/schema/src/Schema/Expect.php', + 'Nette\\Schema\\Helpers' => __DIR__ . '/..' . '/nette/schema/src/Schema/Helpers.php', + 'Nette\\Schema\\Message' => __DIR__ . '/..' . '/nette/schema/src/Schema/Message.php', + 'Nette\\Schema\\Processor' => __DIR__ . '/..' . '/nette/schema/src/Schema/Processor.php', + 'Nette\\Schema\\Schema' => __DIR__ . '/..' . '/nette/schema/src/Schema/Schema.php', + 'Nette\\Schema\\ValidationException' => __DIR__ . '/..' . '/nette/schema/src/Schema/ValidationException.php', + 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/SmartObject.php', + 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/StaticClass.php', + 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/exceptions.php', + 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileInfo' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileInfo.php', + 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Finder' => __DIR__ . '/..' . '/nette/utils/src/Utils/Finder.php', + 'Nette\\Utils\\Floats' => __DIR__ . '/..' . '/nette/utils/src/Utils/Floats.php', + 'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/compatibility.php', + 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\Type' => __DIR__ . '/..' . '/nette/utils/src/Utils/Type.php', + 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php', + 'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInitce41dc6e38789c9f43c485905030864d::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitce41dc6e38789c9f43c485905030864d::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInitce41dc6e38789c9f43c485905030864d::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..9366046 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,892 @@ +{ + "packages": [ + { + "name": "dflydev/dot-access-data", + "version": "v3.0.2", + "version_normalized": "3.0.2.0", + "source": { + "type": "git", + "url": "https://github.com/dflydev/dflydev-dot-access-data.git", + "reference": "f41715465d65213d644d3141a6a93081be5d3549" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/f41715465d65213d644d3141a6a93081be5d3549", + "reference": "f41715465d65213d644d3141a6a93081be5d3549", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "time": "2022-10-27T11:44:00+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": [ + "access", + "data", + "dot", + "notation" + ], + "support": { + "issues": "https://github.com/dflydev/dflydev-dot-access-data/issues", + "source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.2" + }, + "install-path": "../dflydev/dot-access-data" + }, + { + "name": "league/commonmark", + "version": "2.4.0", + "version_normalized": "2.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/commonmark.git", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "reference": "d44a24690f16b8c1808bf13b1bd54ae4c63ea048", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "league/config": "^1.1.1", + "php": "^7.4 || ^8.0", + "psr/event-dispatcher": "^1.0", + "symfony/deprecation-contracts": "^2.1 || ^3.0", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "cebe/markdown": "^1.0", + "commonmark/cmark": "0.30.0", + "commonmark/commonmark.js": "0.30.0", + "composer/package-versions-deprecated": "^1.8", + "embed/embed": "^4.4", + "erusev/parsedown": "^1.0", + "ext-json": "*", + "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", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "unleashedtech/php-coding-standard": "^3.1.1", + "vimeo/psalm": "^4.24.0 || ^5.0.0" + }, + "suggest": { + "symfony/yaml": "v2.3+ required if using the Front Matter extension" + }, + "time": "2023-03-24T15:16:10+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\CommonMark\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)", + "homepage": "https://commonmark.thephpleague.com", + "keywords": [ + "commonmark", + "flavored", + "gfm", + "github", + "github-flavored", + "markdown", + "md", + "parser" + ], + "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" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/commonmark", + "type": "tidelift" + } + ], + "install-path": "../league/commonmark" + }, + { + "name": "league/config", + "version": "v1.2.0", + "version_normalized": "1.2.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/config.git", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/config/zipball/754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "reference": "754b3604fb2984c71f4af4a9cbe7b57f346ec1f3", + "shasum": "" + }, + "require": { + "dflydev/dot-access-data": "^3.0.1", + "nette/schema": "^1.2", + "php": "^7.4 || ^8.0" + }, + "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" + }, + "time": "2022-12-11T20:36:23+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Config\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com", + "role": "Lead Developer" + } + ], + "description": "Define configuration arrays with strict schemas and access values with dot notation", + "homepage": "https://config.thephpleague.com", + "keywords": [ + "array", + "config", + "configuration", + "dot", + "dot-access", + "nested", + "schema" + ], + "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" + }, + "funding": [ + { + "url": "https://www.colinodell.com/sponsor", + "type": "custom" + }, + { + "url": "https://www.paypal.me/colinpodell/10.00", + "type": "custom" + }, + { + "url": "https://github.com/colinodell", + "type": "github" + } + ], + "install-path": "../league/config" + }, + { + "name": "nette/schema", + "version": "v1.2.3", + "version_normalized": "1.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "reference": "abbdbb70e0245d5f3bf77874cea1dfb0c930d06f", + "shasum": "" + }, + "require": { + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", + "php": ">=7.1 <8.3" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "phpstan/phpstan-nette": "^1.0", + "tracy/tracy": "^2.7" + }, + "time": "2022-10-13T01:24:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "support": { + "issues": "https://github.com/nette/schema/issues", + "source": "https://github.com/nette/schema/tree/v1.2.3" + }, + "install-path": "../nette/schema" + }, + { + "name": "nette/utils", + "version": "v4.0.0", + "version_normalized": "4.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/cacdbf5a91a657ede665c541eda28941d4b09c1e", + "reference": "cacdbf5a91a657ede665c541eda28941d4b09c1e", + "shasum": "" + }, + "require": { + "php": ">=8.0 <8.3" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "require-dev": { + "jetbrains/phpstorm-attributes": "dev-master", + "nette/tester": "^2.4", + "phpstan/phpstan": "^1.0", + "tracy/tracy": "^2.9" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "time": "2023-02-02T10:41:53+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v4.0.0" + }, + "install-path": "../nette/utils" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "time": "2019-01-08T18:20:26+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "install-path": "../psr/event-dispatcher" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.2.1", + "version_normalized": "3.2.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "reference": "e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "time": "2023-03-01T10:25:55+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/deprecation-contracts" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-ctype" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.27.0", + "version_normalized": "1.27.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "time": "2022-11-03T14:55:06+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "twig/twig", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15", + "reference": "a6e0510cc793912b451fd40ab983a1d28f611c15", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-ctype": "^1.8", + "symfony/polyfill-mbstring": "^1.3" + }, + "require-dev": { + "psr/container": "^1.0", + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0" + }, + "time": "2023-02-08T07:49:20+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Twig\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "https://twig.symfony.com", + "keywords": [ + "templating" + ], + "support": { + "issues": "https://github.com/twigphp/Twig/issues", + "source": "https://github.com/twigphp/Twig/tree/v3.5.1" + }, + "funding": [ + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/twig/twig", + "type": "tidelift" + } + ], + "install-path": "../twig/twig" + } + ], + "dev": true, + "dev-package-names": [] +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..82a762c --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,122 @@ + array( + 'name' => '__root__', + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '29984ad7d3ad42a4ed8e223bd715d77685c1a699', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev' => true, + ), + 'versions' => array( + '__root__' => array( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'reference' => '29984ad7d3ad42a4ed8e223bd715d77685c1a699', + 'type' => 'library', + 'install_path' => __DIR__ . '/../../', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'dflydev/dot-access-data' => array( + 'pretty_version' => 'v3.0.2', + 'version' => '3.0.2.0', + 'reference' => 'f41715465d65213d644d3141a6a93081be5d3549', + 'type' => 'library', + 'install_path' => __DIR__ . '/../dflydev/dot-access-data', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'league/commonmark' => array( + 'pretty_version' => '2.4.0', + 'version' => '2.4.0.0', + 'reference' => 'd44a24690f16b8c1808bf13b1bd54ae4c63ea048', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/commonmark', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'league/config' => array( + 'pretty_version' => 'v1.2.0', + 'version' => '1.2.0.0', + 'reference' => '754b3604fb2984c71f4af4a9cbe7b57f346ec1f3', + 'type' => 'library', + 'install_path' => __DIR__ . '/../league/config', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nette/schema' => array( + 'pretty_version' => 'v1.2.3', + 'version' => '1.2.3.0', + 'reference' => 'abbdbb70e0245d5f3bf77874cea1dfb0c930d06f', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nette/schema', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'nette/utils' => array( + 'pretty_version' => 'v4.0.0', + 'version' => '4.0.0.0', + 'reference' => 'cacdbf5a91a657ede665c541eda28941d4b09c1e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../nette/utils', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'psr/event-dispatcher' => array( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'reference' => 'dbefd12671e8a14ec7f180cab83036ed26714bb0', + 'type' => 'library', + 'install_path' => __DIR__ . '/../psr/event-dispatcher', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/deprecation-contracts' => array( + 'pretty_version' => 'v3.2.1', + 'version' => '3.2.1.0', + 'reference' => 'e2d1534420bd723d0ef5aec58a22c5fe60ce6f5e', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/deprecation-contracts', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-ctype' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '5bbc823adecdae860bb64756d639ecfec17b050a', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-mbstring' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '8ad114f6b39e2c98a8b0e3bd907732c207c2b534', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'symfony/polyfill-php80' => array( + 'pretty_version' => 'v1.27.0', + 'version' => '1.27.0.0', + 'reference' => '7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936', + 'type' => 'library', + 'install_path' => __DIR__ . '/../symfony/polyfill-php80', + 'aliases' => array(), + 'dev_requirement' => false, + ), + 'twig/twig' => array( + 'pretty_version' => 'v3.5.1', + 'version' => '3.5.1.0', + 'reference' => 'a6e0510cc793912b451fd40ab983a1d28f611c15', + 'type' => 'library', + 'install_path' => __DIR__ . '/../twig/twig', + 'aliases' => array(), + 'dev_requirement' => false, + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..4c3a5d6 --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,26 @@ += 80100)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; +} + +if ($issues) { + if (!headers_sent()) { + header('HTTP/1.1 500 Internal Server Error'); + } + if (!ini_get('display_errors')) { + if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { + fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); + } elseif (!headers_sent()) { + echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; + } + } + trigger_error( + 'Composer detected issues in your platform: ' . implode(' ', $issues), + E_USER_ERROR + ); +} diff --git a/vendor/dflydev/dot-access-data/CHANGELOG.md b/vendor/dflydev/dot-access-data/CHANGELOG.md new file mode 100644 index 0000000..8e92c23 --- /dev/null +++ b/vendor/dflydev/dot-access-data/CHANGELOG.md @@ -0,0 +1,67 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.2] - 2022-10-27 + +### Fixed + + - Added missing return types to docblocks (#44, #45) + +## [3.0.1] - 2021-08-13 + +### Added + + - Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40) + +## [3.0.0] - 2021-01-01 + +### Added + - Added support for both `.` and `/`-delimited key paths (#24) + - Added parameter and return types to everything; enabled strict type checks (#18) + - Added new exception classes to better identify certain types of errors (#20) + - `Data` now implements `ArrayAccess` (#17) + - Added ability to merge non-associative array values (#31, #32) + +### Changed + - All thrown exceptions are now instances or subclasses of `DataException` (#20) + - Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29) + - Bumped supported PHP versions to 7.1 - 8.x (#18) + +### Fixed + - Fixed incorrect merging of array values into string values (#32) + - Fixed `get()` method behaving as if keys with `null` values didn't exist + +## [2.0.0] - 2017-12-21 + +### Changed + - Bumped supported PHP versions to 7.0 - 7.4 (#12) + - Switched to PSR-4 autoloading + +## [1.1.0] - 2017-01-20 + +### Added + - Added new `has()` method to check for the existence of the given key (#4, #7) + +## [1.0.1] - 2015-08-12 + +### Added + - Added new optional `$default` parameter to the `get()` method (#2) + +## [1.0.0] - 2012-07-17 + +**Initial release!** + +[Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.2...main +[3.0.2]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.1...v3.0.2 +[3.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...v3.0.1 +[3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0 +[2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0 +[1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0 +[1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0 diff --git a/vendor/dflydev/dot-access-data/LICENSE b/vendor/dflydev/dot-access-data/LICENSE new file mode 100644 index 0000000..b6880d4 --- /dev/null +++ b/vendor/dflydev/dot-access-data/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2012 Dragonfly Development Inc. + +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. diff --git a/vendor/dflydev/dot-access-data/README.md b/vendor/dflydev/dot-access-data/README.md new file mode 100644 index 0000000..775fbdf --- /dev/null +++ b/vendor/dflydev/dot-access-data/README.md @@ -0,0 +1,158 @@ +Dot Access Data +=============== + +[![Latest Version](https://img.shields.io/packagist/v/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data) +[![Total Downloads](https://img.shields.io/packagist/dt/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data) +[![Software License](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/github/workflow/status/dflydev/dflydev-dot-access-data/Tests/main.svg?style=flat-square)](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/) +[![Quality Score](https://img.shields.io/scrutinizer/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data) + +Given a deep data structure, access data by dot notation. + + +Requirements +------------ + + * PHP (7.1+) + +> For PHP (5.3+) please refer to version `1.0`. + + +Usage +----- + +Abstract example: + +```php +use Dflydev\DotAccessData\Data; + +$data = new Data; + +$data->set('a.b.c', 'C'); +$data->set('a.b.d', 'D1'); +$data->append('a.b.d', 'D2'); +$data->set('a.b.e', ['E0', 'E1', 'E2']); + +// C +$data->get('a.b.c'); + +// ['D1', 'D2'] +$data->get('a.b.d'); + +// ['E0', 'E1', 'E2'] +$data->get('a.b.e'); + +// true +$data->has('a.b.c'); + +// false +$data->has('a.b.d.j'); + + +// 'some-default-value' +$data->get('some.path.that.does.not.exist', 'some-default-value'); + +// throws a MissingPathException because no default was given +$data->get('some.path.that.does.not.exist'); +``` + +A more concrete example: + +```php +use Dflydev\DotAccessData\Data; + +$data = new Data([ + 'hosts' => [ + 'hewey' => [ + 'username' => 'hman', + 'password' => 'HPASS', + 'roles' => ['web'], + ], + 'dewey' => [ + 'username' => 'dman', + 'password' => 'D---S', + 'roles' => ['web', 'db'], + 'nick' => 'dewey dman', + ], + 'lewey' => [ + 'username' => 'lman', + 'password' => 'LP@$$', + 'roles' => ['db'], + ], + ], +]); + +// hman +$username = $data->get('hosts.hewey.username'); +// HPASS +$password = $data->get('hosts.hewey.password'); +// ['web'] +$roles = $data->get('hosts.hewey.roles'); +// dewey dman +$nick = $data->get('hosts.dewey.nick'); +// Unknown +$nick = $data->get('hosts.lewey.nick', 'Unknown'); + +// DataInterface instance +$dewey = $data->getData('hosts.dewey'); +// dman +$username = $dewey->get('username'); +// D---S +$password = $dewey->get('password'); +// ['web', 'db'] +$roles = $dewey->get('roles'); + +// No more lewey +$data->remove('hosts.lewey'); + +// Add DB to hewey's roles +$data->append('hosts.hewey.roles', 'db'); + +$data->set('hosts.april', [ + 'username' => 'aman', + 'password' => '@---S', + 'roles' => ['web'], +]); + +// Check if a key exists (true to this case) +$hasKey = $data->has('hosts.dewey.username'); +``` + +`Data` may be used as an array, since it implements `ArrayAccess` interface: + +```php +// Get +$data->get('name') === $data['name']; // true + +$data['name'] = 'Dewey'; +// is equivalent to +$data->set($name, 'Dewey'); + +isset($data['name']) === $data->has('name'); + +// Remove key +unset($data['name']); +``` + +`/` can also be used as a path delimiter: + +```php +$data->set('a/b/c', 'd'); +echo $data->get('a/b/c'); // "d" + +$data->get('a/b/c') === $data->get('a.b.c'); // true +``` + +License +------- + +This library is licensed under the MIT License - see the LICENSE file +for details. + + +Community +--------- + +If you have questions or want to help out, join us in the +[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net. diff --git a/vendor/dflydev/dot-access-data/composer.json b/vendor/dflydev/dot-access-data/composer.json new file mode 100644 index 0000000..44dc5ed --- /dev/null +++ b/vendor/dflydev/dot-access-data/composer.json @@ -0,0 +1,67 @@ +{ + "name": "dflydev/dot-access-data", + "type": "library", + "description": "Given a deep data structure, access data by dot notation.", + "homepage": "https://github.com/dflydev/dflydev-dot-access-data", + "keywords": ["dot", "access", "data", "notation"], + "license": "MIT", + "authors": [ + { + "name": "Dragonfly Development Inc.", + "email": "info@dflydev.com", + "homepage": "http://dflydev.com" + }, + { + "name": "Beau Simensen", + "email": "beau@dflydev.com", + "homepage": "http://beausimensen.com" + }, + { + "name": "Carlos Frutos", + "email": "carlos@kiwing.it", + "homepage": "https://github.com/cfrutos" + }, + { + "name": "Colin O'Dell", + "email": "colinodell@gmail.com", + "homepage": "https://www.colinodell.com" + } + ], + "require": { + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.42", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.3", + "scrutinizer/ocular": "1.6.0", + "squizlabs/php_codesniffer": "^3.5", + "vimeo/psalm": "^4.0.0" + }, + "autoload": { + "psr-4": { + "Dflydev\\DotAccessData\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Dflydev\\DotAccessData\\": "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "scripts": { + "phpcs": "phpcs", + "phpstan": "phpstan analyse", + "phpunit": "phpunit --no-coverage", + "psalm": "psalm", + "test": [ + "@phpcs", + "@phpstan", + "@psalm", + "@phpunit" + ] + } +} diff --git a/vendor/dflydev/dot-access-data/src/Data.php b/vendor/dflydev/dot-access-data/src/Data.php new file mode 100644 index 0000000..3409b8e --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/Data.php @@ -0,0 +1,286 @@ + + */ +class Data implements DataInterface, ArrayAccess +{ + private const DELIMITERS = ['.', '/']; + + /** + * Internal representation of data data + * + * @var array + */ + protected $data; + + /** + * Constructor + * + * @param array $data + */ + public function __construct(array $data = []) + { + $this->data = $data; + } + + /** + * {@inheritdoc} + */ + public function append(string $key, $value = null): void + { + $currentValue =& $this->data; + $keyPath = self::keyToPathArray($key); + + $endKey = array_pop($keyPath); + foreach ($keyPath as $currentKey) { + if (! isset($currentValue[$currentKey])) { + $currentValue[$currentKey] = []; + } + $currentValue =& $currentValue[$currentKey]; + } + + if (!isset($currentValue[$endKey])) { + $currentValue[$endKey] = []; + } + + if (!is_array($currentValue[$endKey])) { + // Promote this key to an array. + // TODO: Is this really what we want to do? + $currentValue[$endKey] = [$currentValue[$endKey]]; + } + + $currentValue[$endKey][] = $value; + } + + /** + * {@inheritdoc} + */ + public function set(string $key, $value = null): void + { + $currentValue =& $this->data; + $keyPath = self::keyToPathArray($key); + + $endKey = array_pop($keyPath); + foreach ($keyPath as $currentKey) { + if (!isset($currentValue[$currentKey])) { + $currentValue[$currentKey] = []; + } + if (!is_array($currentValue[$currentKey])) { + throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key))); + } + $currentValue =& $currentValue[$currentKey]; + } + $currentValue[$endKey] = $value; + } + + /** + * {@inheritdoc} + */ + public function remove(string $key): void + { + $currentValue =& $this->data; + $keyPath = self::keyToPathArray($key); + + $endKey = array_pop($keyPath); + foreach ($keyPath as $currentKey) { + if (!isset($currentValue[$currentKey])) { + return; + } + $currentValue =& $currentValue[$currentKey]; + } + unset($currentValue[$endKey]); + } + + /** + * {@inheritdoc} + * + * @psalm-mutation-free + */ + public function get(string $key, $default = null) + { + /** @psalm-suppress ImpureFunctionCall */ + $hasDefault = \func_num_args() > 1; + + $currentValue = $this->data; + $keyPath = self::keyToPathArray($key); + + foreach ($keyPath as $currentKey) { + if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) { + if ($hasDefault) { + return $default; + } + + throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath))); + } + + $currentValue = $currentValue[$currentKey]; + } + + return $currentValue === null ? $default : $currentValue; + } + + /** + * {@inheritdoc} + * + * @psalm-mutation-free + */ + public function has(string $key): bool + { + $currentValue = $this->data; + + foreach (self::keyToPathArray($key) as $currentKey) { + if ( + !is_array($currentValue) || + !array_key_exists($currentKey, $currentValue) + ) { + return false; + } + $currentValue = $currentValue[$currentKey]; + } + + return true; + } + + /** + * {@inheritdoc} + * + * @psalm-mutation-free + */ + public function getData(string $key): DataInterface + { + $value = $this->get($key); + if (is_array($value) && Util::isAssoc($value)) { + return new Data($value); + } + + throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key))); + } + + /** + * {@inheritdoc} + */ + public function import(array $data, int $mode = self::REPLACE): void + { + $this->data = Util::mergeAssocArray($this->data, $data, $mode); + } + + /** + * {@inheritdoc} + */ + public function importData(DataInterface $data, int $mode = self::REPLACE): void + { + $this->import($data->export(), $mode); + } + + /** + * {@inheritdoc} + * + * @psalm-mutation-free + */ + public function export(): array + { + return $this->data; + } + + /** + * {@inheritdoc} + * + * @return bool + */ + #[\ReturnTypeWillChange] + public function offsetExists($key) + { + return $this->has($key); + } + + /** + * {@inheritdoc} + * + * @return mixed + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->get($key, null); + } + + /** + * {@inheritdoc} + * + * @param string $key + * @param mixed $value + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetSet($key, $value) + { + $this->set($key, $value); + } + + /** + * {@inheritdoc} + * + * @return void + */ + #[\ReturnTypeWillChange] + public function offsetUnset($key) + { + $this->remove($key); + } + + /** + * @param string $path + * + * @return string[] + * + * @psalm-return non-empty-list + * + * @psalm-pure + */ + protected static function keyToPathArray(string $path): array + { + if (\strlen($path) === 0) { + throw new InvalidPathException('Path cannot be an empty string'); + } + + $path = \str_replace(self::DELIMITERS, '.', $path); + + return \explode('.', $path); + } + + /** + * @param string|string[] $path + * + * @return string + * + * @psalm-pure + */ + protected static function formatPath($path): string + { + if (is_string($path)) { + $path = self::keyToPathArray($path); + } + + return implode(' » ', $path); + } +} diff --git a/vendor/dflydev/dot-access-data/src/DataInterface.php b/vendor/dflydev/dot-access-data/src/DataInterface.php new file mode 100644 index 0000000..5909a8c --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/DataInterface.php @@ -0,0 +1,131 @@ + $data + * @param self::PRESERVE|self::REPLACE|self::MERGE $mode + */ + public function import(array $data, int $mode = self::REPLACE): void; + + /** + * Import data from an external data into existing data + * + * @param DataInterface $data + * @param self::PRESERVE|self::REPLACE|self::MERGE $mode + */ + public function importData(DataInterface $data, int $mode = self::REPLACE): void; + + /** + * Export data as raw data + * + * @return array + * + * @psalm-mutation-free + */ + public function export(): array; +} diff --git a/vendor/dflydev/dot-access-data/src/Exception/DataException.php b/vendor/dflydev/dot-access-data/src/Exception/DataException.php new file mode 100644 index 0000000..2faf9f5 --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/Exception/DataException.php @@ -0,0 +1,21 @@ +path = $path; + + parent::__construct($message, $code, $previous); + } + + public function getPath(): string + { + return $this->path; + } +} diff --git a/vendor/dflydev/dot-access-data/src/Util.php b/vendor/dflydev/dot-access-data/src/Util.php new file mode 100644 index 0000000..5634c51 --- /dev/null +++ b/vendor/dflydev/dot-access-data/src/Util.php @@ -0,0 +1,78 @@ + $arr + * + * @return bool + * + * @psalm-pure + */ + public static function isAssoc(array $arr): bool + { + return !count($arr) || count(array_filter(array_keys($arr), 'is_string')) == count($arr); + } + + /** + * Merge contents from one associtative array to another + * + * @param mixed $to + * @param mixed $from + * @param DataInterface::PRESERVE|DataInterface::REPLACE|DataInterface::MERGE $mode + * + * @return mixed + * + * @psalm-pure + */ + public static function mergeAssocArray($to, $from, int $mode = DataInterface::REPLACE) + { + if ($mode === DataInterface::MERGE && self::isList($to) && self::isList($from)) { + return array_merge($to, $from); + } + + if (is_array($from) && is_array($to)) { + foreach ($from as $k => $v) { + if (!isset($to[$k])) { + $to[$k] = $v; + } else { + $to[$k] = self::mergeAssocArray($to[$k], $v, $mode); + } + } + + return $to; + } + + return $mode === DataInterface::PRESERVE ? $to : $from; + } + + /** + * @param mixed $value + * + * @return bool + * + * @psalm-pure + */ + private static function isList($value): bool + { + return is_array($value) && array_values($value) === $value; + } +} diff --git a/vendor/league/commonmark/.phpstorm.meta.php b/vendor/league/commonmark/.phpstorm.meta.php new file mode 100644 index 0000000..ca1bec7 --- /dev/null +++ b/vendor/league/commonmark/.phpstorm.meta.php @@ -0,0 +1,104 @@ + + * + * 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')); +} diff --git a/vendor/league/commonmark/CHANGELOG.md b/vendor/league/commonmark/CHANGELOG.md new file mode 100644 index 0000000..9c43dbf --- /dev/null +++ b/vendor/league/commonmark/CHANGELOG.md @@ -0,0 +1,588 @@ +# 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 for additional information. + +## [Unreleased][unreleased] + +## [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 `` tag + - `heading_permalink/heading_class` - class to apply to the heading element + - `heading_permalink/insert` - now accepts `none` to prevent the creation of the `` 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 `

` 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.4.0...main +[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 diff --git a/vendor/league/commonmark/LICENSE b/vendor/league/commonmark/LICENSE new file mode 100644 index 0000000..5f04fad --- /dev/null +++ b/vendor/league/commonmark/LICENSE @@ -0,0 +1,28 @@ +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. diff --git a/vendor/league/commonmark/README.md b/vendor/league/commonmark/README.md new file mode 100644 index 0000000..a8e2266 --- /dev/null +++ b/vendor/league/commonmark/README.md @@ -0,0 +1,221 @@ +# league/commonmark + +[![Latest Version](https://img.shields.io/packagist/v/league/commonmark.svg?style=flat-square)](https://packagist.org/packages/league/commonmark) +[![Total Downloads](https://img.shields.io/packagist/dt/league/commonmark.svg?style=flat-square)](https://packagist.org/packages/league/commonmark) +[![Software License](https://img.shields.io/badge/License-BSD--3-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/commonmark/Tests/main.svg?style=flat-square)](https://github.com/thephpleague/commonmark/actions?query=workflow%3ATests+branch%3Amain) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/commonmark.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/commonmark/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/commonmark.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/commonmark) +[![Psalm Type Coverage](https://shepherd.dev/github/thephpleague/commonmark/coverage.svg)](https://shepherd.dev/github/thephpleague/commonmark) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/126/badge)](https://bestpractices.coreinfrastructure.org/projects/126) +[![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](https://www.colinodell.com/sponsor) + +![league/commonmark](commonmark-banner.png) + +**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!'); + +//

Hello World!

+``` + +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!'); + +//

Hello World!

+``` + +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 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 . + +## 💻 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: + + - [Alt Three Emoji](https://github.com/AltThree/Emoji) An emoji parser for CommonMark. + - [Sup Sub extensions](https://github.com/OWS/commonmark-sup-sub-extensions) - Adds support of superscript and subscript (`` and `` 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 (`` 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`: + +## 🏷️ 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 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)! + +--- + +
+ + Get professional support for league/commonmark with a Tidelift subscription + +
+ + Tidelift helps make open source sustainable for maintainers while giving companies
assurances about security, maintenance, and licensing for their dependencies. +
+
+ +[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 diff --git a/vendor/league/commonmark/composer.json b/vendor/league/commonmark/composer.json new file mode 100644 index 0000000..3dfb755 --- /dev/null +++ b/vendor/league/commonmark/composer.json @@ -0,0 +1,125 @@ +{ + "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.30.0", + "commonmark/commonmark.js": "0.30.0", + "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", + "scrutinizer/ocular": "^1.8.1", + "symfony/finder": "^5.3 | ^6.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.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.30.0", + "dist": { + "url": "https://github.com/commonmark/commonmark.js/archive/0.30.0.zip", + "type": "zip" + } + } + }, + { + "type": "package", + "package": { + "name": "commonmark/cmark", + "version": "0.30.0", + "dist": { + "url": "https://github.com/commonmark/cmark/archive/0.30.0.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.9.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.5-dev" + } + }, + "config": { + "allow-plugins": { + "composer/package-versions-deprecated": true, + "dealerdirect/phpcodesniffer-composer-installer": true + }, + "sort-packages": true + } +} diff --git a/vendor/league/commonmark/src/CommonMarkConverter.php b/vendor/league/commonmark/src/CommonMarkConverter.php new file mode 100644 index 0000000..4d70053 --- /dev/null +++ b/vendor/league/commonmark/src/CommonMarkConverter.php @@ -0,0 +1,46 @@ + + * + * 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 $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; + } +} diff --git a/vendor/league/commonmark/src/ConverterInterface.php b/vendor/league/commonmark/src/ConverterInterface.php new file mode 100644 index 0000000..8192b0f --- /dev/null +++ b/vendor/league/commonmark/src/ConverterInterface.php @@ -0,0 +1,30 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Delimiter/Delimiter.php b/vendor/league/commonmark/src/Delimiter/Delimiter.php new file mode 100644 index 0000000..2f04f24 --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/Delimiter.php @@ -0,0 +1,134 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Delimiter/DelimiterInterface.php b/vendor/league/commonmark/src/Delimiter/DelimiterInterface.php new file mode 100644 index 0000000..6bfa32e --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/DelimiterInterface.php @@ -0,0 +1,50 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Delimiter/DelimiterParser.php b/vendor/league/commonmark/src/Delimiter/DelimiterParser.php new file mode 100644 index 0000000..3f96add --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/DelimiterParser.php @@ -0,0 +1,102 @@ +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]; + } +} diff --git a/vendor/league/commonmark/src/Delimiter/DelimiterStack.php b/vendor/league/commonmark/src/Delimiter/DelimiterStack.php new file mode 100644 index 0000000..fb95b90 --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/DelimiterStack.php @@ -0,0 +1,214 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollection.php b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollection.php new file mode 100644 index 0000000..6e9f336 --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollection.php @@ -0,0 +1,89 @@ + + * + * 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|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); + } +} diff --git a/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollectionInterface.php b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollectionInterface.php new file mode 100644 index 0000000..fea3ddb --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollectionInterface.php @@ -0,0 +1,46 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorInterface.php b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorInterface.php new file mode 100644 index 0000000..465378c --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/Processor/DelimiterProcessorInterface.php @@ -0,0 +1,78 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Delimiter/Processor/StaggeredDelimiterProcessor.php b/vendor/league/commonmark/src/Delimiter/Processor/StaggeredDelimiterProcessor.php new file mode 100644 index 0000000..7d33e83 --- /dev/null +++ b/vendor/league/commonmark/src/Delimiter/Processor/StaggeredDelimiterProcessor.php @@ -0,0 +1,111 @@ + + * + * 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|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; + } +} diff --git a/vendor/league/commonmark/src/Environment/Environment.php b/vendor/league/commonmark/src/Environment/Environment.php new file mode 100644 index 0000000..3c24749 --- /dev/null +++ b/vendor/league/commonmark/src/Environment/Environment.php @@ -0,0 +1,447 @@ + + * + * 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 + * + * @psalm-readonly + */ + private PrioritizedList $blockStartParsers; + + /** + * @var PrioritizedList + * + * @psalm-readonly + */ + private PrioritizedList $inlineParsers; + + /** @psalm-readonly */ + private DelimiterProcessorCollection $delimiterProcessors; + + /** + * @var array> + * + * @psalm-readonly-allow-private-mutation + */ + private array $renderersByClass = []; + + /** + * @var PrioritizedList + * + * @psalm-readonly-allow-private-mutation + */ + private PrioritizedList $listenerData; + + private ?EventDispatcherInterface $eventDispatcher = null; + + /** @psalm-readonly */ + private Configuration $config; + + private ?TextNormalizerInterface $slugNormalizer = null; + + /** + * @param array $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 $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 $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 $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 + */ + 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 + */ + 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), + ]), + ]); + } +} diff --git a/vendor/league/commonmark/src/Environment/EnvironmentAwareInterface.php b/vendor/league/commonmark/src/Environment/EnvironmentAwareInterface.php new file mode 100644 index 0000000..44b9d3e --- /dev/null +++ b/vendor/league/commonmark/src/Environment/EnvironmentAwareInterface.php @@ -0,0 +1,19 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Environment/EnvironmentBuilderInterface.php b/vendor/league/commonmark/src/Environment/EnvironmentBuilderInterface.php new file mode 100644 index 0000000..4df9761 --- /dev/null +++ b/vendor/league/commonmark/src/Environment/EnvironmentBuilderInterface.php @@ -0,0 +1,97 @@ + + * + * 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 $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; +} diff --git a/vendor/league/commonmark/src/Environment/EnvironmentInterface.php b/vendor/league/commonmark/src/Environment/EnvironmentInterface.php new file mode 100644 index 0000000..8e19a52 --- /dev/null +++ b/vendor/league/commonmark/src/Environment/EnvironmentInterface.php @@ -0,0 +1,55 @@ + + * + * 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 + */ + public function getBlockStartParsers(): iterable; + + /** + * @return iterable + */ + public function getInlineParsers(): iterable; + + public function getDelimiterProcessors(): DelimiterProcessorCollection; + + /** + * @psalm-param class-string $nodeClass + * + * @return iterable + */ + public function getRenderersForClass(string $nodeClass): iterable; + + public function getSlugNormalizer(): TextNormalizerInterface; +} diff --git a/vendor/league/commonmark/src/Event/AbstractEvent.php b/vendor/league/commonmark/src/Event/AbstractEvent.php new file mode 100644 index 0000000..8c83f92 --- /dev/null +++ b/vendor/league/commonmark/src/Event/AbstractEvent.php @@ -0,0 +1,54 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Event/DocumentParsedEvent.php b/vendor/league/commonmark/src/Event/DocumentParsedEvent.php new file mode 100644 index 0000000..04664c5 --- /dev/null +++ b/vendor/league/commonmark/src/Event/DocumentParsedEvent.php @@ -0,0 +1,35 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Event/DocumentPreParsedEvent.php b/vendor/league/commonmark/src/Event/DocumentPreParsedEvent.php new file mode 100644 index 0000000..ad72512 --- /dev/null +++ b/vendor/league/commonmark/src/Event/DocumentPreParsedEvent.php @@ -0,0 +1,49 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Event/DocumentPreRenderEvent.php b/vendor/league/commonmark/src/Event/DocumentPreRenderEvent.php new file mode 100644 index 0000000..c569ca3 --- /dev/null +++ b/vendor/league/commonmark/src/Event/DocumentPreRenderEvent.php @@ -0,0 +1,44 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Event/DocumentRenderedEvent.php b/vendor/league/commonmark/src/Event/DocumentRenderedEvent.php new file mode 100644 index 0000000..7e49d01 --- /dev/null +++ b/vendor/league/commonmark/src/Event/DocumentRenderedEvent.php @@ -0,0 +1,42 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Event/ListenerData.php b/vendor/league/commonmark/src/Event/ListenerData.php new file mode 100644 index 0000000..4cf3b3a --- /dev/null +++ b/vendor/league/commonmark/src/Event/ListenerData.php @@ -0,0 +1,50 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Exception/AlreadyInitializedException.php b/vendor/league/commonmark/src/Exception/AlreadyInitializedException.php new file mode 100644 index 0000000..5faa6f8 --- /dev/null +++ b/vendor/league/commonmark/src/Exception/AlreadyInitializedException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/CommonMarkException.php b/vendor/league/commonmark/src/Exception/CommonMarkException.php new file mode 100644 index 0000000..9fb349e --- /dev/null +++ b/vendor/league/commonmark/src/Exception/CommonMarkException.php @@ -0,0 +1,21 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/IOException.php b/vendor/league/commonmark/src/Exception/IOException.php new file mode 100644 index 0000000..09a5578 --- /dev/null +++ b/vendor/league/commonmark/src/Exception/IOException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/InvalidArgumentException.php b/vendor/league/commonmark/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..fc67ac4 --- /dev/null +++ b/vendor/league/commonmark/src/Exception/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/LogicException.php b/vendor/league/commonmark/src/Exception/LogicException.php new file mode 100644 index 0000000..c1d00df --- /dev/null +++ b/vendor/league/commonmark/src/Exception/LogicException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/MissingDependencyException.php b/vendor/league/commonmark/src/Exception/MissingDependencyException.php new file mode 100644 index 0000000..b8eb841 --- /dev/null +++ b/vendor/league/commonmark/src/Exception/MissingDependencyException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Exception/UnexpectedEncodingException.php b/vendor/league/commonmark/src/Exception/UnexpectedEncodingException.php new file mode 100644 index 0000000..0f4e399 --- /dev/null +++ b/vendor/league/commonmark/src/Exception/UnexpectedEncodingException.php @@ -0,0 +1,18 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/AttributesExtension.php b/vendor/league/commonmark/src/Extension/Attributes/AttributesExtension.php new file mode 100644 index 0000000..2ef3d85 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/AttributesExtension.php @@ -0,0 +1,32 @@ + + * (c) 2015 Martin Hasoň + * + * 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']); + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Event/AttributesListener.php b/vendor/league/commonmark/src/Extension/Attributes/Event/AttributesListener.php new file mode 100644 index 0000000..feec8cc --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Event/AttributesListener.php @@ -0,0 +1,139 @@ + + * (c) 2015 Martin Hasoň + * + * 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 + */ + 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Node/Attributes.php b/vendor/league/commonmark/src/Extension/Attributes/Node/Attributes.php new file mode 100644 index 0000000..096f04a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Node/Attributes.php @@ -0,0 +1,65 @@ + + * (c) 2015 Martin Hasoň + * + * 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 */ + private array $attributes; + + private int $target = self::TARGET_NEXT; + + /** + * @param array $attributes + */ + public function __construct(array $attributes) + { + parent::__construct(); + + $this->attributes = $attributes; + } + + /** + * @return array + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @param array $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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Node/AttributesInline.php b/vendor/league/commonmark/src/Extension/Attributes/Node/AttributesInline.php new file mode 100644 index 0000000..d8b0d08 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Node/AttributesInline.php @@ -0,0 +1,57 @@ + + * (c) 2015 Martin Hasoň + * + * 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 */ + private array $attributes; + + private bool $block; + + /** + * @param array $attributes + */ + public function __construct(array $attributes, bool $block) + { + parent::__construct(); + + $this->attributes = $attributes; + $this->block = $block; + } + + /** + * @return array + */ + public function getAttributes(): array + { + return $this->attributes; + } + + /** + * @param array $attributes + */ + public function setAttributes(array $attributes): void + { + $this->attributes = $attributes; + } + + public function isBlock(): bool + { + return $this->block; + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockContinueParser.php b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockContinueParser.php new file mode 100644 index 0000000..6e0cdc6 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockContinueParser.php @@ -0,0 +1,92 @@ + + * (c) 2015 Martin Hasoň + * + * 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 $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); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockStartParser.php b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockStartParser.php new file mode 100644 index 0000000..299ccd4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockStartParser.php @@ -0,0 +1,40 @@ + + * (c) 2015 Martin Hasoň + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesInlineParser.php b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesInlineParser.php new file mode 100644 index 0000000..26af3ca --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Parser/AttributesInlineParser.php @@ -0,0 +1,54 @@ + + * (c) 2015 Martin Hasoň + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Attributes/Util/AttributesHelper.php b/vendor/league/commonmark/src/Extension/Attributes/Util/AttributesHelper.php new file mode 100644 index 0000000..de5c111 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Attributes/Util/AttributesHelper.php @@ -0,0 +1,137 @@ + + * (c) 2015 Martin Hasoň + * + * 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-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*'; + private const ATTRIBUTE_LIST = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i'; + + /** + * @return array + */ + 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 $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); + + $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 $attributes1 + * @param Node|array $attributes2 + * + * @return array + */ + public static function mergeAttributes($attributes1, $attributes2): array + { + $attributes = []; + foreach ([$attributes1, $attributes2] as $arg) { + if ($arg instanceof Node) { + $arg = $arg->data->get('attributes'); + } + + /** @var array $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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Autolink/AutolinkExtension.php b/vendor/league/commonmark/src/Extension/Autolink/AutolinkExtension.php new file mode 100644 index 0000000..3516ebb --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Autolink/AutolinkExtension.php @@ -0,0 +1,26 @@ + + * + * 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\ExtensionInterface; + +final class AutolinkExtension implements ExtensionInterface +{ + public function register(EnvironmentBuilderInterface $environment): void + { + $environment->addInlineParser(new EmailAutolinkParser()); + $environment->addInlineParser(new UrlAutolinkParser()); + } +} diff --git a/vendor/league/commonmark/src/Extension/Autolink/EmailAutolinkParser.php b/vendor/league/commonmark/src/Extension/Autolink/EmailAutolinkParser.php new file mode 100644 index 0000000..15a7d34 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Autolink/EmailAutolinkParser.php @@ -0,0 +1,48 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Autolink/UrlAutolinkParser.php b/vendor/league/commonmark/src/Extension/Autolink/UrlAutolinkParser.php new file mode 100644 index 0000000..6b4290a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Autolink/UrlAutolinkParser.php @@ -0,0 +1,146 @@ + + * + * 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-readonly */ + private string $finalRegex; + + /** + * @param array $allowedProtocols + */ + public function __construct(array $allowedProtocols = ['http', 'https', 'ftp']) + { + $this->finalRegex = \sprintf(self::REGEX, \implode('|', $allowedProtocols)); + + foreach ($allowedProtocols as $protocol) { + $this->prefixes[] = $protocol . '://'; + } + } + + 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://' onto 'www' URLs + if (\substr($url, 0, 4) === 'www.') { + $inlineContext->getContainer()->appendChild(new Link('http://' . $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['(']; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/CommonMarkCoreExtension.php b/vendor/league/commonmark/src/Extension/CommonMark/CommonMarkCoreExtension.php new file mode 100644 index 0000000..91f7a22 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/CommonMarkCoreExtension.php @@ -0,0 +1,92 @@ + + * + * 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('_')); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php b/vendor/league/commonmark/src/Extension/CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php new file mode 100644 index 0000000..84b46ee --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php @@ -0,0 +1,108 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/BlockQuote.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/BlockQuote.php new file mode 100644 index 0000000..11094b9 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/BlockQuote.php @@ -0,0 +1,20 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/FencedCode.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/FencedCode.php new file mode 100644 index 0000000..b50b407 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/FencedCode.php @@ -0,0 +1,100 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/Heading.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/Heading.php new file mode 100644 index 0000000..1cf1184 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/Heading.php @@ -0,0 +1,41 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/HtmlBlock.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/HtmlBlock.php new file mode 100644 index 0000000..9879a89 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/HtmlBlock.php @@ -0,0 +1,79 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/IndentedCode.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/IndentedCode.php new file mode 100644 index 0000000..d18be15 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/IndentedCode.php @@ -0,0 +1,32 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListBlock.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListBlock.php new file mode 100644 index 0000000..74f9ca8 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListBlock.php @@ -0,0 +1,56 @@ + + * + * 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; + + /** @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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListData.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListData.php new file mode 100644 index 0000000..7108a93 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListData.php @@ -0,0 +1,47 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListItem.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListItem.php new file mode 100644 index 0000000..f136b7e --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ListItem.php @@ -0,0 +1,37 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ThematicBreak.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ThematicBreak.php new file mode 100644 index 0000000..bb6cea0 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Block/ThematicBreak.php @@ -0,0 +1,20 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/AbstractWebResource.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/AbstractWebResource.php new file mode 100644 index 0000000..dc0ed0a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/AbstractWebResource.php @@ -0,0 +1,41 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Code.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Code.php new file mode 100644 index 0000000..3a6aca2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Code.php @@ -0,0 +1,23 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Emphasis.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Emphasis.php new file mode 100644 index 0000000..fab6869 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Emphasis.php @@ -0,0 +1,42 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/HtmlInline.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/HtmlInline.php new file mode 100644 index 0000000..8594a06 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/HtmlInline.php @@ -0,0 +1,24 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Image.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Image.php new file mode 100644 index 0000000..20e3f87 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Image.php @@ -0,0 +1,49 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Link.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Link.php new file mode 100644 index 0000000..76d5609 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Link.php @@ -0,0 +1,49 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Strong.php b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Strong.php new file mode 100644 index 0000000..827960f --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Node/Inline/Strong.php @@ -0,0 +1,42 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteParser.php new file mode 100644 index 0000000..78db6c5 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteParser.php @@ -0,0 +1,60 @@ + + * + * 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(); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteStartParser.php new file mode 100644 index 0000000..de9a6bc --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteStartParser.php @@ -0,0 +1,39 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeParser.php new file mode 100644 index 0000000..88572c7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeParser.php @@ -0,0 +1,84 @@ + + * + * 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 */ + 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,})(?= *$)/', $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"); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeStartParser.php new file mode 100644 index 0000000..be1b1dc --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeStartParser.php @@ -0,0 +1,40 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingParser.php new file mode 100644 index 0000000..c3e3108 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingParser.php @@ -0,0 +1,51 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingStartParser.php new file mode 100644 index 0000000..404f403 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingStartParser.php @@ -0,0 +1,80 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockParser.php new file mode 100644 index 0000000..6778676 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockParser.php @@ -0,0 +1,82 @@ + + * + * 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 = ''; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockStartParser.php new file mode 100644 index 0000000..bcef0af --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockStartParser.php @@ -0,0 +1,62 @@ + + * + * 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(); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeParser.php new file mode 100644 index 0000000..b7c425a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeParser.php @@ -0,0 +1,83 @@ + + * + * 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 */ + 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 + { + $reversed = \array_reverse($this->strings->toArray(), true); + foreach ($reversed as $index => $line) { + if ($line !== '' && $line !== "\n" && ! \preg_match('/^(\n *)$/', $line)) { + break; + } + + unset($reversed[$index]); + } + + $fixed = \array_reverse($reversed); + $tmp = \implode("\n", $fixed); + if (\substr($tmp, -1) !== "\n") { + $tmp .= "\n"; + } + + $this->block->setLiteral($tmp); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeStartParser.php new file mode 100644 index 0000000..bea4bde --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeStartParser.php @@ -0,0 +1,42 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockParser.php new file mode 100644 index 0000000..4dffb7a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockParser.php @@ -0,0 +1,79 @@ + + * + * 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; + + private bool $hadBlankLine = false; + + private int $linesAfterBlank = 0; + + 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 + { + if (! $childBlock instanceof ListItem) { + return false; + } + + // Another list item is being added to this list block. + // If the previous line was blank, that means this list + // block is "loose" (not tight). + if ($this->hadBlankLine && $this->linesAfterBlank === 1) { + $this->block->setTight(false); + $this->hadBlankLine = false; + } + + return true; + } + + public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue + { + if ($cursor->isBlank()) { + $this->hadBlankLine = true; + $this->linesAfterBlank = 0; + } elseif ($this->hadBlankLine) { + $this->linesAfterBlank++; + } + + // 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockStartParser.php new file mode 100644 index 0000000..74b0c2b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockStartParser.php @@ -0,0 +1,146 @@ + + * + * 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-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. + $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; + } + + 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), '/') . ']/'; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListItemParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListItemParser.php new file mode 100644 index 0000000..73b98be --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ListItemParser.php @@ -0,0 +1,90 @@ + + * + * 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\Node\Block\Paragraph; +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; + + private bool $hadBlankLine = false; + + 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 + { + if ($this->hadBlankLine) { + // We saw a blank line in this list item, that means the list block is loose. + // + // spec: if any of its constituent list items directly contain two block-level elements with a blank line + // between them + $parent = $this->block->parent(); + if ($parent instanceof ListBlock) { + $parent->setTight(false); + } + } + + return true; + } + + 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(); + } + + $activeBlock = $activeBlockParser->getBlock(); + // If the active block is a code block, blank lines in it should not affect if the list is tight. + $this->hadBlankLine = $activeBlock instanceof Paragraph || $activeBlock instanceof ListItem; + $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(); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakParser.php new file mode 100644 index 0000000..fb46637 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakParser.php @@ -0,0 +1,42 @@ + + * + * 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(); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakStartParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakStartParser.php new file mode 100644 index 0000000..ba7ddf3 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakStartParser.php @@ -0,0 +1,40 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/AutolinkParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/AutolinkParser.php new file mode 100644 index 0000000..810769d --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/AutolinkParser.php @@ -0,0 +1,54 @@ + + * + * 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 + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BacktickParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BacktickParser.php new file mode 100644 index 0000000..9618f2e --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BacktickParser.php @@ -0,0 +1,70 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BangParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BangParser.php new file mode 100644 index 0000000..8a9e1bd --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/BangParser.php @@ -0,0 +1,46 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/CloseBracketParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/CloseBracketParser.php new file mode 100644 index 0000000..16f24dc --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/CloseBracketParser.php @@ -0,0 +1,212 @@ + + * + * 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|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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EntityParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EntityParser.php new file mode 100644 index 0000000..4122ff7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EntityParser.php @@ -0,0 +1,42 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EscapableParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EscapableParser.php new file mode 100644 index 0000000..64e6fab --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/EscapableParser.php @@ -0,0 +1,57 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/HtmlInlineParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/HtmlInlineParser.php new file mode 100644 index 0000000..f38db13 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/HtmlInlineParser.php @@ -0,0 +1,41 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/OpenBracketParser.php b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/OpenBracketParser.php new file mode 100644 index 0000000..2b52d1c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Parser/Inline/OpenBracketParser.php @@ -0,0 +1,44 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php new file mode 100644 index 0000000..4a59bd3 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php @@ -0,0 +1,70 @@ + + * + * 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 + * + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function getXmlAttributes(Node $node): array + { + return []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php new file mode 100644 index 0000000..e7204ea --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php @@ -0,0 +1,76 @@ + + * + * 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] !== '') { + $attrs->append('class', 'language-' . $infoWords[0]); + } + + 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 + * + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function getXmlAttributes(Node $node): array + { + FencedCode::assertInstanceOf($node); + + if (($info = $node->getInfo()) === null || $info === '') { + return []; + } + + return ['info' => $info]; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php new file mode 100644 index 0000000..8718b8c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php @@ -0,0 +1,64 @@ + + * + * 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 + * + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function getXmlAttributes(Node $node): array + { + Heading::assertInstanceOf($node); + + return ['level' => $node->getLevel()]; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php new file mode 100644 index 0000000..63a1907 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php @@ -0,0 +1,66 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php new file mode 100644 index 0000000..c4bd4eb --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php @@ -0,0 +1,61 @@ + + * + * 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 + */ + public function getXmlAttributes(Node $node): array + { + return []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php new file mode 100644 index 0000000..f79b44d --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php @@ -0,0 +1,86 @@ + + * + * 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 + * + * @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, + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php new file mode 100644 index 0000000..570f5a7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php @@ -0,0 +1,74 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ThematicBreakRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ThematicBreakRenderer.php new file mode 100644 index 0000000..392bfee --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Block/ThematicBreakRenderer.php @@ -0,0 +1,56 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/CodeRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/CodeRenderer.php new file mode 100644 index 0000000..de030e8 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/CodeRenderer.php @@ -0,0 +1,57 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/EmphasisRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/EmphasisRenderer.php new file mode 100644 index 0000000..41169c4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/EmphasisRenderer.php @@ -0,0 +1,56 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/HtmlInlineRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/HtmlInlineRenderer.php new file mode 100644 index 0000000..69f0fd5 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/HtmlInlineRenderer.php @@ -0,0 +1,66 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/ImageRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/ImageRenderer.php new file mode 100644 index 0000000..7bf09ac --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/ImageRenderer.php @@ -0,0 +1,107 @@ + + * + * 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 + * + * @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; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/LinkRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/LinkRenderer.php new file mode 100644 index 0000000..4ef9645 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/LinkRenderer.php @@ -0,0 +1,89 @@ + + * + * 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 + * + * @psalm-suppress MoreSpecificImplementedParamType + */ + public function getXmlAttributes(Node $node): array + { + Link::assertInstanceOf($node); + + return [ + 'destination' => $node->getUrl(), + 'title' => $node->getTitle() ?? '', + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/StrongRenderer.php b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/StrongRenderer.php new file mode 100644 index 0000000..f0bb8f9 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/CommonMark/Renderer/Inline/StrongRenderer.php @@ -0,0 +1,56 @@ + + * + * 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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/ConfigurableExtensionInterface.php b/vendor/league/commonmark/src/Extension/ConfigurableExtensionInterface.php new file mode 100644 index 0000000..63e467c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/ConfigurableExtensionInterface.php @@ -0,0 +1,21 @@ + + * + * 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; +} diff --git a/vendor/league/commonmark/src/Extension/DefaultAttributes/ApplyDefaultAttributesProcessor.php b/vendor/league/commonmark/src/Extension/DefaultAttributes/ApplyDefaultAttributesProcessor.php new file mode 100644 index 0000000..6b519f8 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DefaultAttributes/ApplyDefaultAttributesProcessor.php @@ -0,0 +1,65 @@ + + * + * 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> $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; + } +} diff --git a/vendor/league/commonmark/src/Extension/DefaultAttributes/DefaultAttributesExtension.php b/vendor/league/commonmark/src/Extension/DefaultAttributes/DefaultAttributesExtension.php new file mode 100644 index 0000000..152c29a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DefaultAttributes/DefaultAttributesExtension.php @@ -0,0 +1,39 @@ + + * + * 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']); + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/DescriptionListExtension.php b/vendor/league/commonmark/src/Extension/DescriptionList/DescriptionListExtension.php new file mode 100644 index 0000000..9ddd2a8 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/DescriptionListExtension.php @@ -0,0 +1,42 @@ + + * + * 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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Event/ConsecutiveDescriptionListMerger.php b/vendor/league/commonmark/src/Extension/DescriptionList/Event/ConsecutiveDescriptionListMerger.php new file mode 100644 index 0000000..15210e7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Event/ConsecutiveDescriptionListMerger.php @@ -0,0 +1,41 @@ + + * + * 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(); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Event/LooseDescriptionHandler.php b/vendor/league/commonmark/src/Extension/DescriptionList/Event/LooseDescriptionHandler.php new file mode 100644 index 0000000..a8823fa --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Event/LooseDescriptionHandler.php @@ -0,0 +1,66 @@ + + * + * 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); + } + } + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Node/Description.php b/vendor/league/commonmark/src/Extension/DescriptionList/Node/Description.php new file mode 100644 index 0000000..ccef962 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Node/Description.php @@ -0,0 +1,39 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionList.php b/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionList.php new file mode 100644 index 0000000..90d026c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionList.php @@ -0,0 +1,20 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionTerm.php b/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionTerm.php new file mode 100644 index 0000000..b13ec75 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Node/DescriptionTerm.php @@ -0,0 +1,20 @@ + + * + * 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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionContinueParser.php b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionContinueParser.php new file mode 100644 index 0000000..0cdd9d5 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionContinueParser.php @@ -0,0 +1,71 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionListContinueParser.php b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionListContinueParser.php new file mode 100644 index 0000000..1d446a7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionListContinueParser.php @@ -0,0 +1,53 @@ + + * + * 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; + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionStartParser.php b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionStartParser.php new file mode 100644 index 0000000..b4e8c98 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionStartParser.php @@ -0,0 +1,73 @@ + + * + * 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 + */ + private static function splitTerms(string $terms): array + { + $ret = []; + foreach (\explode("\n", $terms) as $term) { + $ret[] = new DescriptionTermContinueParser($term); + } + + return $ret; + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionTermContinueParser.php b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionTermContinueParser.php new file mode 100644 index 0000000..7b43882 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionTermContinueParser.php @@ -0,0 +1,52 @@ + + * + * 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); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionListRenderer.php b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionListRenderer.php new file mode 100644 index 0000000..7723038 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionListRenderer.php @@ -0,0 +1,39 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionRenderer.php b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionRenderer.php new file mode 100644 index 0000000..5fcffd6 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionRenderer.php @@ -0,0 +1,37 @@ + + * + * 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())); + } +} diff --git a/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionTermRenderer.php b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionTermRenderer.php new file mode 100644 index 0000000..ce8a1c4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionTermRenderer.php @@ -0,0 +1,37 @@ + + * + * 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())); + } +} diff --git a/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlExtension.php b/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlExtension.php new file mode 100644 index 0000000..0ece0c2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlExtension.php @@ -0,0 +1,51 @@ + + * + * 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); + } +} diff --git a/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php b/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php new file mode 100644 index 0000000..06252a3 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php @@ -0,0 +1,62 @@ + + * + * 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 /> + return \preg_replace($regex, '<$1', $rendered); + } + + public function setConfiguration(ConfigurationInterface $configuration): void + { + $this->config = $configuration; + + if ($this->innerRenderer instanceof ConfigurationAwareInterface) { + $this->innerRenderer->setConfiguration($configuration); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/Bridge/OscaroteroEmbedAdapter.php b/vendor/league/commonmark/src/Extension/Embed/Bridge/OscaroteroEmbedAdapter.php new file mode 100644 index 0000000..06b8190 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/Bridge/OscaroteroEmbedAdapter.php @@ -0,0 +1,50 @@ +<?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); + } + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/DomainFilteringAdapter.php b/vendor/league/commonmark/src/Extension/Embed/DomainFilteringAdapter.php new file mode 100644 index 0000000..69dc096 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/DomainFilteringAdapter.php @@ -0,0 +1,50 @@ +<?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; + + 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 + */ + private static function createRegex(array $allowedDomains): string + { + $allowedDomains = \array_map('preg_quote', $allowedDomains); + + return '/^(?:https?:\/\/)?(?:[^.]+\.)*(' . \implode('|', $allowedDomains) . ')/'; + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/Embed.php b/vendor/league/commonmark/src/Extension/Embed/Embed.php new file mode 100644 index 0000000..94c1980 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/Embed.php @@ -0,0 +1,50 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedAdapterInterface.php b/vendor/league/commonmark/src/Extension/Embed/EmbedAdapterInterface.php new file mode 100644 index 0000000..9880a43 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedAdapterInterface.php @@ -0,0 +1,25 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedExtension.php b/vendor/league/commonmark/src/Extension/Embed/EmbedExtension.php new file mode 100644 index 0000000..babf048 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedExtension.php @@ -0,0 +1,48 @@ +<?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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedParser.php b/vendor/league/commonmark/src/Extension/Embed/EmbedParser.php new file mode 100644 index 0000000..e957caf --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedParser.php @@ -0,0 +1,62 @@ +<?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 + { + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedProcessor.php b/vendor/league/commonmark/src/Extension/Embed/EmbedProcessor.php new file mode 100644 index 0000000..68fb9ee --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedProcessor.php @@ -0,0 +1,70 @@ +<?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); + } + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedRenderer.php b/vendor/league/commonmark/src/Extension/Embed/EmbedRenderer.php new file mode 100644 index 0000000..91655d8 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedRenderer.php @@ -0,0 +1,35 @@ +<?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() ?? ''; + } +} diff --git a/vendor/league/commonmark/src/Extension/Embed/EmbedStartParser.php b/vendor/league/commonmark/src/Extension/Embed/EmbedStartParser.php new file mode 100644 index 0000000..5ff3808 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Embed/EmbedStartParser.php @@ -0,0 +1,53 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/ExtensionInterface.php b/vendor/league/commonmark/src/Extension/ExtensionInterface.php new file mode 100644 index 0000000..01a9f2e --- /dev/null +++ b/vendor/league/commonmark/src/Extension/ExtensionInterface.php @@ -0,0 +1,24 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkExtension.php b/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkExtension.php new file mode 100644 index 0000000..df0079c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkExtension.php @@ -0,0 +1,47 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkProcessor.php b/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkProcessor.php new file mode 100644 index 0000000..1250535 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/ExternalLink/ExternalLinkProcessor.php @@ -0,0 +1,114 @@ +<?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); + } + } + } + + /** + * @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION! + * + * @param mixed $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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Event/AnonymousFootnotesListener.php b/vendor/league/commonmark/src/Extension/Footnote/Event/AnonymousFootnotesListener.php new file mode 100644 index 0000000..401613a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Event/AnonymousFootnotesListener.php @@ -0,0 +1,62 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Event/FixOrphanedFootnotesAndRefsListener.php b/vendor/league/commonmark/src/Extension/Footnote/Event/FixOrphanedFootnotesAndRefsListener.php new file mode 100644 index 0000000..a0295b5 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Event/FixOrphanedFootnotesAndRefsListener.php @@ -0,0 +1,68 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Event/GatherFootnotesListener.php b/vendor/league/commonmark/src/Extension/Footnote/Event/GatherFootnotesListener.php new file mode 100644 index 0000000..ae8d00b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Event/GatherFootnotesListener.php @@ -0,0 +1,106 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Event/NumberFootnotesListener.php b/vendor/league/commonmark/src/Extension/Footnote/Event/NumberFootnotesListener.php new file mode 100644 index 0000000..65600fa --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Event/NumberFootnotesListener.php @@ -0,0 +1,75 @@ +<?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++; + } + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/FootnoteExtension.php b/vendor/league/commonmark/src/Extension/Footnote/FootnoteExtension.php new file mode 100644 index 0000000..0fa8038 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/FootnoteExtension.php @@ -0,0 +1,70 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Node/Footnote.php b/vendor/league/commonmark/src/Extension/Footnote/Node/Footnote.php new file mode 100644 index 0000000..c3f77ca --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Node/Footnote.php @@ -0,0 +1,37 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteBackref.php b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteBackref.php new file mode 100644 index 0000000..f56daa5 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteBackref.php @@ -0,0 +1,40 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteContainer.php b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteContainer.php new file mode 100644 index 0000000..af4ee35 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteContainer.php @@ -0,0 +1,21 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteRef.php b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteRef.php new file mode 100644 index 0000000..429a1dc --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Node/FootnoteRef.php @@ -0,0 +1,57 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Parser/AnonymousFootnoteRefParser.php b/vendor/league/commonmark/src/Extension/Footnote/Parser/AnonymousFootnoteRefParser.php new file mode 100644 index 0000000..4ed93da --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Parser/AnonymousFootnoteRefParser.php @@ -0,0 +1,66 @@ +<?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(); + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteParser.php b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteParser.php new file mode 100644 index 0000000..2192546 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteParser.php @@ -0,0 +1,68 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteRefParser.php b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteRefParser.php new file mode 100644 index 0000000..4032abd --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteRefParser.php @@ -0,0 +1,57 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteStartParser.php b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteStartParser.php new file mode 100644 index 0000000..734e678 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Parser/FootnoteStartParser.php @@ -0,0 +1,56 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteBackrefRenderer.php b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteBackrefRenderer.php new file mode 100644 index 0000000..3b7bc3c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteBackrefRenderer.php @@ -0,0 +1,81 @@ +<?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(), + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteContainerRenderer.php b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteContainerRenderer.php new file mode 100644 index 0000000..74d35ef --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteContainerRenderer.php @@ -0,0 +1,71 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRefRenderer.php b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRefRenderer.php new file mode 100644 index 0000000..c0c07d7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRefRenderer.php @@ -0,0 +1,87 @@ +<?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(), + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRenderer.php b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRenderer.php new file mode 100644 index 0000000..cdd027e --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRenderer.php @@ -0,0 +1,80 @@ +<?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(), + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Data/FrontMatterDataParserInterface.php b/vendor/league/commonmark/src/Extension/FrontMatter/Data/FrontMatterDataParserInterface.php new file mode 100644 index 0000000..6e9db40 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Data/FrontMatterDataParserInterface.php @@ -0,0 +1,26 @@ +<?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); +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Data/LibYamlFrontMatterParser.php b/vendor/league/commonmark/src/Extension/FrontMatter/Data/LibYamlFrontMatterParser.php new file mode 100644 index 0000000..b7194f4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Data/LibYamlFrontMatterParser.php @@ -0,0 +1,47 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Data/SymfonyYamlFrontMatterParser.php b/vendor/league/commonmark/src/Extension/FrontMatter/Data/SymfonyYamlFrontMatterParser.php new file mode 100644 index 0000000..b21e9f7 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Data/SymfonyYamlFrontMatterParser.php @@ -0,0 +1,38 @@ +<?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 { + return Yaml::parse($frontMatter); + } catch (ParseException $ex) { + throw InvalidFrontMatterException::wrap($ex); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Exception/InvalidFrontMatterException.php b/vendor/league/commonmark/src/Extension/FrontMatter/Exception/InvalidFrontMatterException.php new file mode 100644 index 0000000..ffe0c28 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Exception/InvalidFrontMatterException.php @@ -0,0 +1,24 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterExtension.php b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterExtension.php new file mode 100644 index 0000000..019ecb4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterExtension.php @@ -0,0 +1,46 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParser.php b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParser.php new file mode 100644 index 0000000..69c41d1 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParser.php @@ -0,0 +1,64 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParserInterface.php b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParserInterface.php new file mode 100644 index 0000000..197a33b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterParserInterface.php @@ -0,0 +1,21 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterProviderInterface.php b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterProviderInterface.php new file mode 100644 index 0000000..b5a7278 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/FrontMatterProviderInterface.php @@ -0,0 +1,22 @@ +<?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(); +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Input/MarkdownInputWithFrontMatter.php b/vendor/league/commonmark/src/Extension/FrontMatter/Input/MarkdownInputWithFrontMatter.php new file mode 100644 index 0000000..86c982b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Input/MarkdownInputWithFrontMatter.php @@ -0,0 +1,43 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPostRenderListener.php b/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPostRenderListener.php new file mode 100644 index 0000000..14b7191 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPostRenderListener.php @@ -0,0 +1,35 @@ +<?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 + )); + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPreParser.php b/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPreParser.php new file mode 100644 index 0000000..b0afbee --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPreParser.php @@ -0,0 +1,37 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/FrontMatter/Output/RenderedContentWithFrontMatter.php b/vendor/league/commonmark/src/Extension/FrontMatter/Output/RenderedContentWithFrontMatter.php new file mode 100644 index 0000000..efaa342 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/FrontMatter/Output/RenderedContentWithFrontMatter.php @@ -0,0 +1,51 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/GithubFlavoredMarkdownExtension.php b/vendor/league/commonmark/src/Extension/GithubFlavoredMarkdownExtension.php new file mode 100644 index 0000000..b3920aa --- /dev/null +++ b/vendor/league/commonmark/src/Extension/GithubFlavoredMarkdownExtension.php @@ -0,0 +1,33 @@ +<?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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalink.php b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalink.php new file mode 100644 index 0000000..df9bded --- /dev/null +++ b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalink.php @@ -0,0 +1,37 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php new file mode 100644 index 0000000..96473a2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php @@ -0,0 +1,49 @@ +<?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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php new file mode 100644 index 0000000..871aa21 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php @@ -0,0 +1,101 @@ +<?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'"); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php new file mode 100644 index 0000000..59a86a1 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php @@ -0,0 +1,106 @@ +<?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(), + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/InlinesOnly/ChildRenderer.php b/vendor/league/commonmark/src/Extension/InlinesOnly/ChildRenderer.php new file mode 100644 index 0000000..403e948 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/InlinesOnly/ChildRenderer.php @@ -0,0 +1,35 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/InlinesOnly/InlinesOnlyExtension.php b/vendor/league/commonmark/src/Extension/InlinesOnly/InlinesOnlyExtension.php new file mode 100644 index 0000000..7777510 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/InlinesOnly/InlinesOnlyExtension.php @@ -0,0 +1,73 @@ +<?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('_')); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Mention/Generator/CallbackGenerator.php b/vendor/league/commonmark/src/Extension/Mention/Generator/CallbackGenerator.php new file mode 100644 index 0000000..d0b6292 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/Generator/CallbackGenerator.php @@ -0,0 +1,54 @@ +<?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'); + } +} diff --git a/vendor/league/commonmark/src/Extension/Mention/Generator/MentionGeneratorInterface.php b/vendor/league/commonmark/src/Extension/Mention/Generator/MentionGeneratorInterface.php new file mode 100644 index 0000000..30d4a51 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/Generator/MentionGeneratorInterface.php @@ -0,0 +1,22 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/Mention/Generator/StringTemplateLinkGenerator.php b/vendor/league/commonmark/src/Extension/Mention/Generator/StringTemplateLinkGenerator.php new file mode 100644 index 0000000..5d92897 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/Generator/StringTemplateLinkGenerator.php @@ -0,0 +1,34 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Mention/Mention.php b/vendor/league/commonmark/src/Extension/Mention/Mention.php new file mode 100644 index 0000000..74eaee4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/Mention.php @@ -0,0 +1,93 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Mention/MentionExtension.php b/vendor/league/commonmark/src/Extension/Mention/MentionExtension.php new file mode 100644 index 0000000..c848c26 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/MentionExtension.php @@ -0,0 +1,61 @@ +<?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)); + } + } + } +} diff --git a/vendor/league/commonmark/src/Extension/Mention/MentionParser.php b/vendor/league/commonmark/src/Extension/Mention/MentionParser.php new file mode 100644 index 0000000..a81c787 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Mention/MentionParser.php @@ -0,0 +1,87 @@ +<?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)); + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/DashParser.php b/vendor/league/commonmark/src/Extension/SmartPunct/DashParser.php new file mode 100644 index 0000000..cf0e1af --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/DashParser.php @@ -0,0 +1,59 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/EllipsesParser.php b/vendor/league/commonmark/src/Extension/SmartPunct/EllipsesParser.php new file mode 100644 index 0000000..9f5b3bd --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/EllipsesParser.php @@ -0,0 +1,38 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/Quote.php b/vendor/league/commonmark/src/Extension/SmartPunct/Quote.php new file mode 100644 index 0000000..dee9759 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/Quote.php @@ -0,0 +1,30 @@ +<?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 = '’'; +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/QuoteParser.php b/vendor/league/commonmark/src/Extension/SmartPunct/QuoteParser.php new file mode 100644 index 0000000..97c9d1a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/QuoteParser.php @@ -0,0 +1,105 @@ +<?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 +{ + public const DOUBLE_QUOTES = [Quote::DOUBLE_QUOTE, Quote::DOUBLE_QUOTE_OPENER, Quote::DOUBLE_QUOTE_CLOSER]; + public const SINGLE_QUOTES = [Quote::SINGLE_QUOTE, Quote::SINGLE_QUOTE_OPENER, Quote::SINGLE_QUOTE_CLOSER]; + + public function getMatchDefinition(): InlineParserMatch + { + return InlineParserMatch::oneOf(...\array_merge(self::DOUBLE_QUOTES, self::SINGLE_QUOTES)); + } + + /** + * 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(); + + $normalizedCharacter = $this->getNormalizedQuoteCharacter($char); + + $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($normalizedCharacter, ['delim' => true]); + $inlineContext->getContainer()->appendChild($node); + + // Add entry to stack to this opener + $inlineContext->getDelimiterStack()->push(new Delimiter($normalizedCharacter, 1, $node, $canOpen, $canClose)); + + return true; + } + + private function getNormalizedQuoteCharacter(string $character): string + { + if (\in_array($character, self::DOUBLE_QUOTES, true)) { + return Quote::DOUBLE_QUOTE; + } + + if (\in_array($character, self::SINGLE_QUOTES, true)) { + return Quote::SINGLE_QUOTE; + } + + return $character; + } + + /** + * @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]; + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/QuoteProcessor.php b/vendor/league/commonmark/src/Extension/SmartPunct/QuoteProcessor.php new file mode 100644 index 0000000..1fc30d4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/QuoteProcessor.php @@ -0,0 +1,82 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/ReplaceUnpairedQuotesListener.php b/vendor/league/commonmark/src/Extension/SmartPunct/ReplaceUnpairedQuotesListener.php new file mode 100644 index 0000000..3536452 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/ReplaceUnpairedQuotesListener.php @@ -0,0 +1,43 @@ +<?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); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/SmartPunct/SmartPunctExtension.php b/vendor/league/commonmark/src/Extension/SmartPunct/SmartPunctExtension.php new file mode 100644 index 0000000..8524ca1 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/SmartPunct/SmartPunctExtension.php @@ -0,0 +1,64 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/Strikethrough/Strikethrough.php b/vendor/league/commonmark/src/Extension/Strikethrough/Strikethrough.php new file mode 100644 index 0000000..20ad161 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Strikethrough/Strikethrough.php @@ -0,0 +1,39 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughDelimiterProcessor.php b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughDelimiterProcessor.php new file mode 100644 index 0000000..af0fdb1 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughDelimiterProcessor.php @@ -0,0 +1,63 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughExtension.php b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughExtension.php new file mode 100644 index 0000000..96ffe7a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughExtension.php @@ -0,0 +1,26 @@ +<?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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughRenderer.php b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughRenderer.php new file mode 100644 index 0000000..a50b895 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Strikethrough/StrikethroughRenderer.php @@ -0,0 +1,50 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/Table.php b/vendor/league/commonmark/src/Extension/Table/Table.php new file mode 100644 index 0000000..2fe441d --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/Table.php @@ -0,0 +1,22 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableCell.php b/vendor/league/commonmark/src/Extension/Table/TableCell.php new file mode 100644 index 0000000..6ed359a --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableCell.php @@ -0,0 +1,99 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableCellRenderer.php b/vendor/league/commonmark/src/Extension/Table/TableCellRenderer.php new file mode 100644 index 0000000..99512c3 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableCellRenderer.php @@ -0,0 +1,89 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableExtension.php b/vendor/league/commonmark/src/Extension/Table/TableExtension.php new file mode 100644 index 0000000..27a58bb --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableExtension.php @@ -0,0 +1,62 @@ +<?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'))); + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableParser.php b/vendor/league/commonmark/src/Extension/Table/TableParser.php new file mode 100644 index 0000000..ca340a3 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableParser.php @@ -0,0 +1,200 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableRenderer.php b/vendor/league/commonmark/src/Extension/Table/TableRenderer.php new file mode 100644 index 0000000..7799e22 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableRenderer.php @@ -0,0 +1,58 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableRow.php b/vendor/league/commonmark/src/Extension/Table/TableRow.php new file mode 100644 index 0000000..cd6ac99 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableRow.php @@ -0,0 +1,22 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableRowRenderer.php b/vendor/league/commonmark/src/Extension/Table/TableRowRenderer.php new file mode 100644 index 0000000..dee72d2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableRowRenderer.php @@ -0,0 +1,56 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableSection.php b/vendor/league/commonmark/src/Extension/Table/TableSection.php new file mode 100644 index 0000000..9edd63b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableSection.php @@ -0,0 +1,64 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableSectionRenderer.php b/vendor/league/commonmark/src/Extension/Table/TableSectionRenderer.php new file mode 100644 index 0000000..cccf06c --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableSectionRenderer.php @@ -0,0 +1,70 @@ +<?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(), + ]; + } +} diff --git a/vendor/league/commonmark/src/Extension/Table/TableStartParser.php b/vendor/league/commonmark/src/Extension/Table/TableStartParser.php new file mode 100644 index 0000000..12206d2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/Table/TableStartParser.php @@ -0,0 +1,158 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContents.php b/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContents.php new file mode 100644 index 0000000..e040d86 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContents.php @@ -0,0 +1,20 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContentsPlaceholder.php b/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContentsPlaceholder.php new file mode 100644 index 0000000..6d6db10 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Node/TableOfContentsPlaceholder.php @@ -0,0 +1,20 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/AsIsNormalizerStrategy.php b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/AsIsNormalizerStrategy.php new file mode 100644 index 0000000..f5bb9a4 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/AsIsNormalizerStrategy.php @@ -0,0 +1,72 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/FlatNormalizerStrategy.php b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/FlatNormalizerStrategy.php new file mode 100644 index 0000000..8e805ae --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/FlatNormalizerStrategy.php @@ -0,0 +1,33 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/NormalizerStrategyInterface.php b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/NormalizerStrategyInterface.php new file mode 100644 index 0000000..f30afb1 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/NormalizerStrategyInterface.php @@ -0,0 +1,21 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/RelativeNormalizerStrategy.php b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/RelativeNormalizerStrategy.php new file mode 100644 index 0000000..1b2197f --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/Normalizer/RelativeNormalizerStrategy.php @@ -0,0 +1,67 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php new file mode 100644 index 0000000..7fe2b09 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php @@ -0,0 +1,106 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsExtension.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsExtension.php new file mode 100644 index 0000000..9c8223b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsExtension.php @@ -0,0 +1,53 @@ +<?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()); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGenerator.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGenerator.php new file mode 100644 index 0000000..f0df96b --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGenerator.php @@ -0,0 +1,168 @@ +<?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)); + } + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGeneratorInterface.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGeneratorInterface.php new file mode 100644 index 0000000..64ecb8e --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsGeneratorInterface.php @@ -0,0 +1,22 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderParser.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderParser.php new file mode 100644 index 0000000..b27ddee --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderParser.php @@ -0,0 +1,74 @@ +<?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; + } + }; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderRenderer.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderRenderer.php new file mode 100644 index 0000000..0366cb9 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderRenderer.php @@ -0,0 +1,40 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsRenderer.php b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsRenderer.php new file mode 100644 index 0000000..da1b698 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TableOfContents/TableOfContentsRenderer.php @@ -0,0 +1,56 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Extension/TaskList/TaskListExtension.php b/vendor/league/commonmark/src/Extension/TaskList/TaskListExtension.php new file mode 100644 index 0000000..bf4b0d2 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TaskList/TaskListExtension.php @@ -0,0 +1,26 @@ +<?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()); + } +} diff --git a/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarker.php b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarker.php new file mode 100644 index 0000000..125ae40 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarker.php @@ -0,0 +1,39 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerParser.php b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerParser.php new file mode 100644 index 0000000..30e2731 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerParser.php @@ -0,0 +1,55 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerRenderer.php b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerRenderer.php new file mode 100644 index 0000000..a1eb745 --- /dev/null +++ b/vendor/league/commonmark/src/Extension/TaskList/TaskListItemMarkerRenderer.php @@ -0,0 +1,70 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/GithubFlavoredMarkdownConverter.php b/vendor/league/commonmark/src/GithubFlavoredMarkdownConverter.php new file mode 100644 index 0000000..f2524b2 --- /dev/null +++ b/vendor/league/commonmark/src/GithubFlavoredMarkdownConverter.php @@ -0,0 +1,45 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Input/MarkdownInput.php b/vendor/league/commonmark/src/Input/MarkdownInput.php new file mode 100644 index 0000000..bbe1618 --- /dev/null +++ b/vendor/league/commonmark/src/Input/MarkdownInput.php @@ -0,0 +1,102 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Input/MarkdownInputInterface.php b/vendor/league/commonmark/src/Input/MarkdownInputInterface.php new file mode 100644 index 0000000..bb8d6f1 --- /dev/null +++ b/vendor/league/commonmark/src/Input/MarkdownInputInterface.php @@ -0,0 +1,26 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/MarkdownConverter.php b/vendor/league/commonmark/src/MarkdownConverter.php new file mode 100644 index 0000000..037ecff --- /dev/null +++ b/vendor/league/commonmark/src/MarkdownConverter.php @@ -0,0 +1,93 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/MarkdownConverterInterface.php b/vendor/league/commonmark/src/MarkdownConverterInterface.php new file mode 100644 index 0000000..a52a286 --- /dev/null +++ b/vendor/league/commonmark/src/MarkdownConverterInterface.php @@ -0,0 +1,34 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Node/Block/AbstractBlock.php b/vendor/league/commonmark/src/Node/Block/AbstractBlock.php new file mode 100644 index 0000000..417f89b --- /dev/null +++ b/vendor/league/commonmark/src/Node/Block/AbstractBlock.php @@ -0,0 +1,64 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Block/Document.php b/vendor/league/commonmark/src/Node/Block/Document.php new file mode 100644 index 0000000..ee7ee44 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Block/Document.php @@ -0,0 +1,56 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Block/Paragraph.php b/vendor/league/commonmark/src/Node/Block/Paragraph.php new file mode 100644 index 0000000..5b7d17c --- /dev/null +++ b/vendor/league/commonmark/src/Node/Block/Paragraph.php @@ -0,0 +1,21 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Node/Block/TightBlockInterface.php b/vendor/league/commonmark/src/Node/Block/TightBlockInterface.php new file mode 100644 index 0000000..21a5868 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Block/TightBlockInterface.php @@ -0,0 +1,21 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Node/Inline/AbstractInline.php b/vendor/league/commonmark/src/Node/Inline/AbstractInline.php new file mode 100644 index 0000000..d3705b4 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/AbstractInline.php @@ -0,0 +1,23 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Node/Inline/AbstractStringContainer.php b/vendor/league/commonmark/src/Node/Inline/AbstractStringContainer.php new file mode 100644 index 0000000..f0aab84 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/AbstractStringContainer.php @@ -0,0 +1,47 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Inline/AdjacentTextMerger.php b/vendor/league/commonmark/src/Node/Inline/AdjacentTextMerger.php new file mode 100644 index 0000000..43922d4 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/AdjacentTextMerger.php @@ -0,0 +1,105 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Node/Inline/DelimitedInterface.php b/vendor/league/commonmark/src/Node/Inline/DelimitedInterface.php new file mode 100644 index 0000000..89773fa --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/DelimitedInterface.php @@ -0,0 +1,21 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Node/Inline/Newline.php b/vendor/league/commonmark/src/Node/Inline/Newline.php new file mode 100644 index 0000000..68790de --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/Newline.php @@ -0,0 +1,40 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Inline/Text.php b/vendor/league/commonmark/src/Node/Inline/Text.php new file mode 100644 index 0000000..31387f9 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Inline/Text.php @@ -0,0 +1,25 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Node.php b/vendor/league/commonmark/src/Node/Node.php new file mode 100644 index 0000000..484b39c --- /dev/null +++ b/vendor/league/commonmark/src/Node/Node.php @@ -0,0 +1,262 @@ +<?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))); + } + } +} diff --git a/vendor/league/commonmark/src/Node/NodeIterator.php b/vendor/league/commonmark/src/Node/NodeIterator.php new file mode 100644 index 0000000..3d295ef --- /dev/null +++ b/vendor/league/commonmark/src/Node/NodeIterator.php @@ -0,0 +1,58 @@ +<?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(); + } + } + } +} diff --git a/vendor/league/commonmark/src/Node/NodeWalker.php b/vendor/league/commonmark/src/Node/NodeWalker.php new file mode 100644 index 0000000..6f922e8 --- /dev/null +++ b/vendor/league/commonmark/src/Node/NodeWalker.php @@ -0,0 +1,80 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/NodeWalkerEvent.php b/vendor/league/commonmark/src/Node/NodeWalkerEvent.php new file mode 100644 index 0000000..773ec3a --- /dev/null +++ b/vendor/league/commonmark/src/Node/NodeWalkerEvent.php @@ -0,0 +1,42 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Query.php b/vendor/league/commonmark/src/Node/Query.php new file mode 100644 index 0000000..7e76fe3 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Query.php @@ -0,0 +1,139 @@ +<?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); + }; + } +} diff --git a/vendor/league/commonmark/src/Node/Query/AndExpr.php b/vendor/league/commonmark/src/Node/Query/AndExpr.php new file mode 100644 index 0000000..d2cd615 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Query/AndExpr.php @@ -0,0 +1,55 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/Query/ExpressionInterface.php b/vendor/league/commonmark/src/Node/Query/ExpressionInterface.php new file mode 100644 index 0000000..2bbbc7f --- /dev/null +++ b/vendor/league/commonmark/src/Node/Query/ExpressionInterface.php @@ -0,0 +1,21 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Node/Query/OrExpr.php b/vendor/league/commonmark/src/Node/Query/OrExpr.php new file mode 100644 index 0000000..b0baad8 --- /dev/null +++ b/vendor/league/commonmark/src/Node/Query/OrExpr.php @@ -0,0 +1,55 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/RawMarkupContainerInterface.php b/vendor/league/commonmark/src/Node/RawMarkupContainerInterface.php new file mode 100644 index 0000000..1545285 --- /dev/null +++ b/vendor/league/commonmark/src/Node/RawMarkupContainerInterface.php @@ -0,0 +1,21 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Node/StringContainerHelper.php b/vendor/league/commonmark/src/Node/StringContainerHelper.php new file mode 100644 index 0000000..8e1ec34 --- /dev/null +++ b/vendor/league/commonmark/src/Node/StringContainerHelper.php @@ -0,0 +1,54 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Node/StringContainerInterface.php b/vendor/league/commonmark/src/Node/StringContainerInterface.php new file mode 100644 index 0000000..23564ae --- /dev/null +++ b/vendor/league/commonmark/src/Node/StringContainerInterface.php @@ -0,0 +1,27 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Normalizer/SlugNormalizer.php b/vendor/league/commonmark/src/Normalizer/SlugNormalizer.php new file mode 100644 index 0000000..d41ea24 --- /dev/null +++ b/vendor/league/commonmark/src/Normalizer/SlugNormalizer.php @@ -0,0 +1,56 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Normalizer/TextNormalizer.php b/vendor/league/commonmark/src/Normalizer/TextNormalizer.php new file mode 100644 index 0000000..7860f1b --- /dev/null +++ b/vendor/league/commonmark/src/Normalizer/TextNormalizer.php @@ -0,0 +1,39 @@ +<?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'); + } +} diff --git a/vendor/league/commonmark/src/Normalizer/TextNormalizerInterface.php b/vendor/league/commonmark/src/Normalizer/TextNormalizerInterface.php new file mode 100644 index 0000000..f476234 --- /dev/null +++ b/vendor/league/commonmark/src/Normalizer/TextNormalizerInterface.php @@ -0,0 +1,33 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizer.php b/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizer.php new file mode 100644 index 0000000..591f19f --- /dev/null +++ b/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizer.php @@ -0,0 +1,56 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizerInterface.php b/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizerInterface.php new file mode 100644 index 0000000..642edeb --- /dev/null +++ b/vendor/league/commonmark/src/Normalizer/UniqueSlugNormalizerInterface.php @@ -0,0 +1,28 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Output/RenderedContent.php b/vendor/league/commonmark/src/Output/RenderedContent.php new file mode 100644 index 0000000..4bf612d --- /dev/null +++ b/vendor/league/commonmark/src/Output/RenderedContent.php @@ -0,0 +1,49 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Output/RenderedContentInterface.php b/vendor/league/commonmark/src/Output/RenderedContentInterface.php new file mode 100644 index 0000000..2179b1b --- /dev/null +++ b/vendor/league/commonmark/src/Output/RenderedContentInterface.php @@ -0,0 +1,29 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/Block/AbstractBlockContinueParser.php b/vendor/league/commonmark/src/Parser/Block/AbstractBlockContinueParser.php new file mode 100644 index 0000000..889532e --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/AbstractBlockContinueParser.php @@ -0,0 +1,47 @@ +<?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 + { + } +} diff --git a/vendor/league/commonmark/src/Parser/Block/BlockContinue.php b/vendor/league/commonmark/src/Parser/Block/BlockContinue.php new file mode 100644 index 0000000..4b5f37d --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/BlockContinue.php @@ -0,0 +1,73 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Parser/Block/BlockContinueParserInterface.php b/vendor/league/commonmark/src/Parser/Block/BlockContinueParserInterface.php new file mode 100644 index 0000000..b6e5472 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/BlockContinueParserInterface.php @@ -0,0 +1,64 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/Block/BlockContinueParserWithInlinesInterface.php b/vendor/league/commonmark/src/Parser/Block/BlockContinueParserWithInlinesInterface.php new file mode 100644 index 0000000..6f826c9 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/BlockContinueParserWithInlinesInterface.php @@ -0,0 +1,24 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/Block/BlockStart.php b/vendor/league/commonmark/src/Parser/Block/BlockStart.php new file mode 100644 index 0000000..5576622 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/BlockStart.php @@ -0,0 +1,124 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Parser/Block/BlockStartParserInterface.php b/vendor/league/commonmark/src/Parser/Block/BlockStartParserInterface.php new file mode 100644 index 0000000..90ed781 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/BlockStartParserInterface.php @@ -0,0 +1,33 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/Block/DocumentBlockParser.php b/vendor/league/commonmark/src/Parser/Block/DocumentBlockParser.php new file mode 100644 index 0000000..bacb512 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/DocumentBlockParser.php @@ -0,0 +1,53 @@ +<?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\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); + } +} diff --git a/vendor/league/commonmark/src/Parser/Block/ParagraphParser.php b/vendor/league/commonmark/src/Parser/Block/ParagraphParser.php new file mode 100644 index 0000000..1573429 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/ParagraphParser.php @@ -0,0 +1,87 @@ +<?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 + { + if ($this->referenceParser->hasReferences() && $this->referenceParser->getParagraphContent() === '') { + $this->block->detach(); + } + } + + 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(); + } +} diff --git a/vendor/league/commonmark/src/Parser/Block/SkipLinesStartingWithLettersParser.php b/vendor/league/commonmark/src/Parser/Block/SkipLinesStartingWithLettersParser.php new file mode 100644 index 0000000..95d8bd2 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Block/SkipLinesStartingWithLettersParser.php @@ -0,0 +1,45 @@ +<?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(); + } +} diff --git a/vendor/league/commonmark/src/Parser/Cursor.php b/vendor/league/commonmark/src/Parser/Cursor.php new file mode 100644 index 0000000..f2b6c96 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Cursor.php @@ -0,0 +1,491 @@ +<?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 + */ + 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; + } +} diff --git a/vendor/league/commonmark/src/Parser/CursorState.php b/vendor/league/commonmark/src/Parser/CursorState.php new file mode 100644 index 0000000..4a6c2d9 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/CursorState.php @@ -0,0 +1,56 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Parser/Inline/InlineParserInterface.php b/vendor/league/commonmark/src/Parser/Inline/InlineParserInterface.php new file mode 100644 index 0000000..fd13435 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Inline/InlineParserInterface.php @@ -0,0 +1,23 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/Inline/InlineParserMatch.php b/vendor/league/commonmark/src/Parser/Inline/InlineParserMatch.php new file mode 100644 index 0000000..516a2c2 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Inline/InlineParserMatch.php @@ -0,0 +1,85 @@ +<?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 + */ + 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); + } +} diff --git a/vendor/league/commonmark/src/Parser/Inline/NewlineParser.php b/vendor/league/commonmark/src/Parser/Inline/NewlineParser.php new file mode 100644 index 0000000..eb10d91 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/Inline/NewlineParser.php @@ -0,0 +1,53 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Parser/InlineParserContext.php b/vendor/league/commonmark/src/Parser/InlineParserContext.php new file mode 100644 index 0000000..796f2f3 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/InlineParserContext.php @@ -0,0 +1,120 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Parser/InlineParserEngine.php b/vendor/league/commonmark/src/Parser/InlineParserEngine.php new file mode 100644 index 0000000..da5c25e --- /dev/null +++ b/vendor/league/commonmark/src/Parser/InlineParserEngine.php @@ -0,0 +1,177 @@ +<?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: string, 2: bool}> + * @phpstan-var array<int, array{0: InlineParserInterface, 1: 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; + } +} diff --git a/vendor/league/commonmark/src/Parser/InlineParserEngineInterface.php b/vendor/league/commonmark/src/Parser/InlineParserEngineInterface.php new file mode 100644 index 0000000..8a0986d --- /dev/null +++ b/vendor/league/commonmark/src/Parser/InlineParserEngineInterface.php @@ -0,0 +1,27 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/MarkdownParser.php b/vendor/league/commonmark/src/Parser/MarkdownParser.php new file mode 100644 index 0000000..dcf9a42 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/MarkdownParser.php @@ -0,0 +1,346 @@ +<?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; + } + + if ($blockStart->isReplaceActiveBlockParser()) { + $this->prepareActiveBlockParserForReplacement(); + } + + foreach ($blockStart->getBlockParsers() as $newBlockParser) { + $blockParser = $this->addChild($newBlockParser); + $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); + } + + 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): BlockContinueParserInterface + { + $blockParser->getBlock()->setStartLine($this->lineNumber); + + while (! $this->getActiveBlockParser()->canContain($blockParser->getBlock())) { + $this->closeBlockParsers(1, $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; + } + + private function prepareActiveBlockParserForReplacement(): void + { + // 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(); + } + + /** + * @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; + } +} diff --git a/vendor/league/commonmark/src/Parser/MarkdownParserInterface.php b/vendor/league/commonmark/src/Parser/MarkdownParserInterface.php new file mode 100644 index 0000000..e0a6be4 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/MarkdownParserInterface.php @@ -0,0 +1,25 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/MarkdownParserState.php b/vendor/league/commonmark/src/Parser/MarkdownParserState.php new file mode 100644 index 0000000..79abd42 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/MarkdownParserState.php @@ -0,0 +1,57 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Parser/MarkdownParserStateInterface.php b/vendor/league/commonmark/src/Parser/MarkdownParserStateInterface.php new file mode 100644 index 0000000..21a9d3a --- /dev/null +++ b/vendor/league/commonmark/src/Parser/MarkdownParserStateInterface.php @@ -0,0 +1,36 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Parser/ParserLogicException.php b/vendor/league/commonmark/src/Parser/ParserLogicException.php new file mode 100644 index 0000000..592b1a2 --- /dev/null +++ b/vendor/league/commonmark/src/Parser/ParserLogicException.php @@ -0,0 +1,20 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Reference/Reference.php b/vendor/league/commonmark/src/Reference/Reference.php new file mode 100644 index 0000000..a0d571d --- /dev/null +++ b/vendor/league/commonmark/src/Reference/Reference.php @@ -0,0 +1,54 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Reference/ReferenceInterface.php b/vendor/league/commonmark/src/Reference/ReferenceInterface.php new file mode 100644 index 0000000..244b354 --- /dev/null +++ b/vendor/league/commonmark/src/Reference/ReferenceInterface.php @@ -0,0 +1,29 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Reference/ReferenceMap.php b/vendor/league/commonmark/src/Reference/ReferenceMap.php new file mode 100644 index 0000000..982cb12 --- /dev/null +++ b/vendor/league/commonmark/src/Reference/ReferenceMap.php @@ -0,0 +1,77 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Reference/ReferenceMapInterface.php b/vendor/league/commonmark/src/Reference/ReferenceMapInterface.php new file mode 100644 index 0000000..71daa19 --- /dev/null +++ b/vendor/league/commonmark/src/Reference/ReferenceMapInterface.php @@ -0,0 +1,31 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Reference/ReferenceParser.php b/vendor/league/commonmark/src/Reference/ReferenceParser.php new file mode 100644 index 0000000..c01dd21 --- /dev/null +++ b/vendor/league/commonmark/src/Reference/ReferenceParser.php @@ -0,0 +1,324 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Reference/ReferenceableInterface.php b/vendor/league/commonmark/src/Reference/ReferenceableInterface.php new file mode 100644 index 0000000..b45f379 --- /dev/null +++ b/vendor/league/commonmark/src/Reference/ReferenceableInterface.php @@ -0,0 +1,19 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Renderer/Block/DocumentRenderer.php b/vendor/league/commonmark/src/Renderer/Block/DocumentRenderer.php new file mode 100644 index 0000000..3262691 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/Block/DocumentRenderer.php @@ -0,0 +1,57 @@ +<?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', + ]; + } +} diff --git a/vendor/league/commonmark/src/Renderer/Block/ParagraphRenderer.php b/vendor/league/commonmark/src/Renderer/Block/ParagraphRenderer.php new file mode 100644 index 0000000..934eac2 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/Block/ParagraphRenderer.php @@ -0,0 +1,74 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Renderer/ChildNodeRendererInterface.php b/vendor/league/commonmark/src/Renderer/ChildNodeRendererInterface.php new file mode 100644 index 0000000..8e866b5 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/ChildNodeRendererInterface.php @@ -0,0 +1,31 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Renderer/DocumentRendererInterface.php b/vendor/league/commonmark/src/Renderer/DocumentRendererInterface.php new file mode 100644 index 0000000..dd34dd6 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/DocumentRendererInterface.php @@ -0,0 +1,28 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Renderer/HtmlDecorator.php b/vendor/league/commonmark/src/Renderer/HtmlDecorator.php new file mode 100644 index 0000000..46a38d9 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/HtmlDecorator.php @@ -0,0 +1,45 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Renderer/HtmlRenderer.php b/vendor/league/commonmark/src/Renderer/HtmlRenderer.php new file mode 100644 index 0000000..2e05cfb --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/HtmlRenderer.php @@ -0,0 +1,100 @@ +<?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'); + } +} diff --git a/vendor/league/commonmark/src/Renderer/Inline/NewlineRenderer.php b/vendor/league/commonmark/src/Renderer/Inline/NewlineRenderer.php new file mode 100644 index 0000000..f64cc58 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/Inline/NewlineRenderer.php @@ -0,0 +1,76 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Renderer/Inline/TextRenderer.php b/vendor/league/commonmark/src/Renderer/Inline/TextRenderer.php new file mode 100644 index 0000000..40ad02a --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/Inline/TextRenderer.php @@ -0,0 +1,54 @@ +<?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 []; + } +} diff --git a/vendor/league/commonmark/src/Renderer/MarkdownRendererInterface.php b/vendor/league/commonmark/src/Renderer/MarkdownRendererInterface.php new file mode 100644 index 0000000..83af8cd --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/MarkdownRendererInterface.php @@ -0,0 +1,30 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Renderer/NoMatchingRendererException.php b/vendor/league/commonmark/src/Renderer/NoMatchingRendererException.php new file mode 100644 index 0000000..14fe493 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/NoMatchingRendererException.php @@ -0,0 +1,20 @@ +<?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 +{ +} diff --git a/vendor/league/commonmark/src/Renderer/NodeRendererInterface.php b/vendor/league/commonmark/src/Renderer/NodeRendererInterface.php new file mode 100644 index 0000000..5d40582 --- /dev/null +++ b/vendor/league/commonmark/src/Renderer/NodeRendererInterface.php @@ -0,0 +1,27 @@ +<?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); +} diff --git a/vendor/league/commonmark/src/Util/ArrayCollection.php b/vendor/league/commonmark/src/Util/ArrayCollection.php new file mode 100644 index 0000000..7210770 --- /dev/null +++ b/vendor/league/commonmark/src/Util/ArrayCollection.php @@ -0,0 +1,173 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Util/Html5EntityDecoder.php b/vendor/league/commonmark/src/Util/Html5EntityDecoder.php new file mode 100644 index 0000000..52550a0 --- /dev/null +++ b/vendor/league/commonmark/src/Util/Html5EntityDecoder.php @@ -0,0 +1,75 @@ +<?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)); + } +} diff --git a/vendor/league/commonmark/src/Util/HtmlElement.php b/vendor/league/commonmark/src/Util/HtmlElement.php new file mode 100644 index 0000000..51fa6de --- /dev/null +++ b/vendor/league/commonmark/src/Util/HtmlElement.php @@ -0,0 +1,160 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Util/HtmlFilter.php b/vendor/league/commonmark/src/Util/HtmlFilter.php new file mode 100644 index 0000000..b1e0555 --- /dev/null +++ b/vendor/league/commonmark/src/Util/HtmlFilter.php @@ -0,0 +1,55 @@ +<?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)); + } + } +} diff --git a/vendor/league/commonmark/src/Util/LinkParserHelper.php b/vendor/league/commonmark/src/Util/LinkParserHelper.php new file mode 100644 index 0000000..e329669 --- /dev/null +++ b/vendor/league/commonmark/src/Util/LinkParserHelper.php @@ -0,0 +1,142 @@ +<?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(); + } +} diff --git a/vendor/league/commonmark/src/Util/PrioritizedList.php b/vendor/league/commonmark/src/Util/PrioritizedList.php new file mode 100644 index 0000000..77ec24a --- /dev/null +++ b/vendor/league/commonmark/src/Util/PrioritizedList.php @@ -0,0 +1,73 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Util/RegexHelper.php b/vendor/league/commonmark/src/Util/RegexHelper.php new file mode 100644 index 0000000..8ead731 --- /dev/null +++ b/vendor/league/commonmark/src/Util/RegexHelper.php @@ -0,0 +1,229 @@ +<?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|section|source|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 = '<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->'; + public const PARTIAL_PROCESSINGINSTRUCTION = '[<][?][\s\S]*?[?][>]'; + public const PARTIAL_DECLARATION = '<![A-Z]+' . '\s+[^>]*>'; + 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 = '/^[\x{2000}-\x{206F}\x{2E00}-\x{2E7F}\p{Pc}\p{Pd}\p{Pe}\p{Pf}\p{Pi}\p{Po}\p{Ps}\\\\\'!"#\$%&\(\)\*\+,\-\.\\/:;<=>\?@\[\]\^_`\{\|\}~]/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 + * + * @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 + * + * @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 + * + * @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 + * + * @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; + } +} diff --git a/vendor/league/commonmark/src/Util/SpecReader.php b/vendor/league/commonmark/src/Util/SpecReader.php new file mode 100644 index 0000000..7523304 --- /dev/null +++ b/vendor/league/commonmark/src/Util/SpecReader.php @@ -0,0 +1,71 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Util/UrlEncoder.php b/vendor/league/commonmark/src/Util/UrlEncoder.php new file mode 100644 index 0000000..bba1af3 --- /dev/null +++ b/vendor/league/commonmark/src/Util/UrlEncoder.php @@ -0,0 +1,69 @@ +<?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; + } +} diff --git a/vendor/league/commonmark/src/Util/Xml.php b/vendor/league/commonmark/src/Util/Xml.php new file mode 100644 index 0000000..8f9e84d --- /dev/null +++ b/vendor/league/commonmark/src/Util/Xml.php @@ -0,0 +1,33 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Xml/FallbackNodeXmlRenderer.php b/vendor/league/commonmark/src/Xml/FallbackNodeXmlRenderer.php new file mode 100644 index 0000000..48aa3c8 --- /dev/null +++ b/vendor/league/commonmark/src/Xml/FallbackNodeXmlRenderer.php @@ -0,0 +1,85 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Xml/MarkdownToXmlConverter.php b/vendor/league/commonmark/src/Xml/MarkdownToXmlConverter.php new file mode 100644 index 0000000..538ad98 --- /dev/null +++ b/vendor/league/commonmark/src/Xml/MarkdownToXmlConverter.php @@ -0,0 +1,59 @@ +<?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); + } +} diff --git a/vendor/league/commonmark/src/Xml/XmlNodeRendererInterface.php b/vendor/league/commonmark/src/Xml/XmlNodeRendererInterface.php new file mode 100644 index 0000000..aafc9f1 --- /dev/null +++ b/vendor/league/commonmark/src/Xml/XmlNodeRendererInterface.php @@ -0,0 +1,28 @@ +<?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; +} diff --git a/vendor/league/commonmark/src/Xml/XmlRenderer.php b/vendor/league/commonmark/src/Xml/XmlRenderer.php new file mode 100644 index 0000000..2973dd7 --- /dev/null +++ b/vendor/league/commonmark/src/Xml/XmlRenderer.php @@ -0,0 +1,135 @@ +<?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; + } +} diff --git a/vendor/league/config/CHANGELOG.md b/vendor/league/config/CHANGELOG.md new file mode 100644 index 0000000..9a7813a --- /dev/null +++ b/vendor/league/config/CHANGELOG.md @@ -0,0 +1,42 @@ +# 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 diff --git a/vendor/league/config/LICENSE.md b/vendor/league/config/LICENSE.md new file mode 100644 index 0000000..1a444a1 --- /dev/null +++ b/vendor/league/config/LICENSE.md @@ -0,0 +1,28 @@ +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. diff --git a/vendor/league/config/README.md b/vendor/league/config/README.md new file mode 100644 index 0000000..2304746 --- /dev/null +++ b/vendor/league/config/README.md @@ -0,0 +1,153 @@ +# league/config + +[![Latest Version](https://img.shields.io/packagist/v/league/config.svg?style=flat-square)](https://packagist.org/packages/league/config) +[![Total Downloads](https://img.shields.io/packagist/dt/league/config.svg?style=flat-square)](https://packagist.org/packages/league/config) +[![Software License](https://img.shields.io/badge/License-BSD--3-brightgreen.svg?style=flat-square)](LICENSE) +[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/config/Tests/main.svg?style=flat-square)](https://github.com/thephpleague/config/actions?query=workflow%3ATests+branch%3Amain) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/config.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/config/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/config.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/config) +[![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](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 diff --git a/vendor/league/config/composer.json b/vendor/league/config/composer.json new file mode 100644 index 0000000..3cd8d87 --- /dev/null +++ b/vendor/league/config/composer.json @@ -0,0 +1,69 @@ +{ + "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 + } + } +} diff --git a/vendor/league/config/src/Configuration.php b/vendor/league/config/src/Configuration.php new file mode 100644 index 0000000..6294367 --- /dev/null +++ b/vendor/league/config/src/Configuration.php @@ -0,0 +1,255 @@ +<?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); + } +} diff --git a/vendor/league/config/src/ConfigurationAwareInterface.php b/vendor/league/config/src/ConfigurationAwareInterface.php new file mode 100644 index 0000000..ec5d7b3 --- /dev/null +++ b/vendor/league/config/src/ConfigurationAwareInterface.php @@ -0,0 +1,22 @@ +<?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; +} diff --git a/vendor/league/config/src/ConfigurationBuilderInterface.php b/vendor/league/config/src/ConfigurationBuilderInterface.php new file mode 100644 index 0000000..e9c5ed6 --- /dev/null +++ b/vendor/league/config/src/ConfigurationBuilderInterface.php @@ -0,0 +1,21 @@ +<?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 +{ +} diff --git a/vendor/league/config/src/ConfigurationInterface.php b/vendor/league/config/src/ConfigurationInterface.php new file mode 100644 index 0000000..534bd9f --- /dev/null +++ b/vendor/league/config/src/ConfigurationInterface.php @@ -0,0 +1,46 @@ +<?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; +} diff --git a/vendor/league/config/src/ConfigurationProviderInterface.php b/vendor/league/config/src/ConfigurationProviderInterface.php new file mode 100644 index 0000000..7af6148 --- /dev/null +++ b/vendor/league/config/src/ConfigurationProviderInterface.php @@ -0,0 +1,22 @@ +<?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; +} diff --git a/vendor/league/config/src/Exception/ConfigurationExceptionInterface.php b/vendor/league/config/src/Exception/ConfigurationExceptionInterface.php new file mode 100644 index 0000000..db9ee78 --- /dev/null +++ b/vendor/league/config/src/Exception/ConfigurationExceptionInterface.php @@ -0,0 +1,21 @@ +<?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 +{ +} diff --git a/vendor/league/config/src/Exception/InvalidConfigurationException.php b/vendor/league/config/src/Exception/InvalidConfigurationException.php new file mode 100644 index 0000000..f2a6b69 --- /dev/null +++ b/vendor/league/config/src/Exception/InvalidConfigurationException.php @@ -0,0 +1,46 @@ +<?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); + } +} diff --git a/vendor/league/config/src/Exception/UnknownOptionException.php b/vendor/league/config/src/Exception/UnknownOptionException.php new file mode 100644 index 0000000..5afba12 --- /dev/null +++ b/vendor/league/config/src/Exception/UnknownOptionException.php @@ -0,0 +1,33 @@ +<?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; + } +} diff --git a/vendor/league/config/src/Exception/ValidationException.php b/vendor/league/config/src/Exception/ValidationException.php new file mode 100644 index 0000000..b43e2f5 --- /dev/null +++ b/vendor/league/config/src/Exception/ValidationException.php @@ -0,0 +1,37 @@ +<?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; + } +} diff --git a/vendor/league/config/src/MutableConfigurationInterface.php b/vendor/league/config/src/MutableConfigurationInterface.php new file mode 100644 index 0000000..2d4b2ee --- /dev/null +++ b/vendor/league/config/src/MutableConfigurationInterface.php @@ -0,0 +1,34 @@ +<?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; +} diff --git a/vendor/league/config/src/ReadOnlyConfiguration.php b/vendor/league/config/src/ReadOnlyConfiguration.php new file mode 100644 index 0000000..58e6171 --- /dev/null +++ b/vendor/league/config/src/ReadOnlyConfiguration.php @@ -0,0 +1,40 @@ +<?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); + } +} diff --git a/vendor/league/config/src/SchemaBuilderInterface.php b/vendor/league/config/src/SchemaBuilderInterface.php new file mode 100644 index 0000000..3a19807 --- /dev/null +++ b/vendor/league/config/src/SchemaBuilderInterface.php @@ -0,0 +1,27 @@ +<?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; +} diff --git a/vendor/nette/schema/composer.json b/vendor/nette/schema/composer.json new file mode 100644 index 0000000..bece68a --- /dev/null +++ b/vendor/nette/schema/composer.json @@ -0,0 +1,39 @@ +{ + "name": "nette/schema", + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "keywords": ["nette", "config"], + "homepage": "https://nette.org", + "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "require": { + "php": ">=7.1 <8.3", + "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0" + }, + "require-dev": { + "nette/tester": "^2.3 || ^2.4", + "tracy/tracy": "^2.7", + "phpstan/phpstan-nette": "^1.0" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse", + "tester": "tester tests -s" + }, + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + } +} diff --git a/vendor/nette/schema/contributing.md b/vendor/nette/schema/contributing.md new file mode 100644 index 0000000..184152c --- /dev/null +++ b/vendor/nette/schema/contributing.md @@ -0,0 +1,33 @@ +How to contribute & use the issue tracker +========================================= + +Nette welcomes your contributions. There are several ways to help out: + +* Create an issue on GitHub, if you have found a bug +* Write test cases for open bug issues +* Write fixes for open bug/feature issues, preferably with test cases included +* Contribute to the [documentation](https://nette.org/en/writing) + +Issues +------ + +Please **do not use the issue tracker to ask questions**. We will be happy to help you +on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. + +**Feature requests** are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. + +Contributing +------------ + +If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). + +The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. + +Please do not fix whitespace, format code, or make a purely cosmetic patch. + +Thanks! :heart: diff --git a/vendor/nette/schema/license.md b/vendor/nette/schema/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/schema/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +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 "Nette Framework" 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 owner 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. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/schema/readme.md b/vendor/nette/schema/readme.md new file mode 100644 index 0000000..20b5de2 --- /dev/null +++ b/vendor/nette/schema/readme.md @@ -0,0 +1,441 @@ +Nette Schema +************ + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/schema.svg)](https://packagist.org/packages/nette/schema) +[![Tests](https://github.com/nette/schema/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/schema/actions) +[![Coverage Status](https://coveralls.io/repos/github/nette/schema/badge.svg?branch=master)](https://coveralls.io/github/nette/schema?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/schema/v/stable)](https://github.com/nette/schema/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/schema/blob/master/license.md) + + +Introduction +============ + +A practical library for validation and normalization of data structures against a given schema with a smart & easy-to-understand API. + +Documentation can be found on the [website](https://doc.nette.org/schema). + +Installation: + +```shell +composer require nette/schema +``` + +It requires PHP version 7.1 and supports PHP up to 8.2. + + +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette Schema? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! + + +Basic Usage +----------- + +In variable `$schema` we have a validation schema (what exactly this means and how to create it we will say later) and in variable `$data` we have a data structure that we want to validate and normalize. This can be, for example, data sent by the user through an API, configuration file, etc. + +The task is handled by the [Nette\Schema\Processor](https://api.nette.org/3.0/Nette/Schema/Processor.html) class, which processes the input and either returns normalized data or throws an [Nette\Schema\ValidationException](https://api.nette.org/3.0/Nette/Schema/ValidationException.html) exception on error. + +```php +$processor = new Nette\Schema\Processor; + +try { + $normalized = $processor->process($schema, $data); +} catch (Nette\Schema\ValidationException $e) { + echo 'Data is invalid: ' . $e->getMessage(); +} +``` + +Method `$e->getMessages()` returns array of all message strings and `$e->getMessageObjects()` return all messages as [Nette\Schema\Message](https://api.nette.org/3.1/Nette/Schema/Message.html) objects. + + +Defining Schema +--------------- + +And now let's create a schema. The class [Nette\Schema\Expect](https://api.nette.org/3.0/Nette/Schema/Expect.html) is used to define it, we actually define expectations of what the data should look like. Let's say that the input data must be a structure (e.g. an array) containing elements `processRefund` of type bool and `refundAmount` of type int. + +```php +use Nette\Schema\Expect; + +$schema = Expect::structure([ + 'processRefund' => Expect::bool(), + 'refundAmount' => Expect::int(), +]); +``` + +We believe that the schema definition looks clear, even if you see it for the very first time. + +Lets send the following data for validation: + +```php +$data = [ + 'processRefund' => true, + 'refundAmount' => 17, +]; + +$normalized = $processor->process($schema, $data); // OK, it passes +``` + +The output, i.e. the value `$normalized`, is the object `stdClass`. If we want the output to be an array, we add a cast to schema `Expect::structure([...])->castTo('array')`. + +All elements of the structure are optional and have a default value `null`. Example: + +```php +$data = [ + 'refundAmount' => 17, +]; + +$normalized = $processor->process($schema, $data); // OK, it passes +// $normalized = {'processRefund' => null, 'refundAmount' => 17} +``` + +The fact that the default value is `null` does not mean that it would be accepted in the input data `'processRefund' => null`. No, the input must be boolean, i.e. only `true` or `false`. We would have to explicitly allow `null` via `Expect::bool()->nullable()`. + +An item can be made mandatory using `Expect::bool()->required()`. We change the default value to `false` using `Expect::bool()->default(false)` or shortly using `Expect::bool(false)`. + +And what if we wanted to accept `1` and `0` besides booleans? Then we list the allowed values, which we will also normalize to boolean: + +```php +$schema = Expect::structure([ + 'processRefund' => Expect::anyOf(true, false, 1, 0)->castTo('bool'), + 'refundAmount' => Expect::int(), +]); + +$normalized = $processor->process($schema, $data); +is_bool($normalized->processRefund); // true +``` + +Now you know the basics of how the schema is defined and how the individual elements of the structure behave. We will now show what all the other elements can be used in defining a schema. + + + +Data Types: type() +------------------ + +All standard PHP data types can be listed in the schema: + +```php +Expect::string($default = null) +Expect::int($default = null) +Expect::float($default = null) +Expect::bool($default = null) +Expect::null() +Expect::array($default = []) +``` + +And then all types [supported by the Validators](https://doc.nette.org/validators#toc-validation-rules) via `Expect::type('scalar')` or abbreviated `Expect::scalar()`. Also class or interface names are accepted, e.g. `Expect::type('AddressEntity')`. + +You can also use union notation: + +```php +Expect::type('bool|string|array') +``` + +The default value is always `null` except for `array` and `list`, where it is an empty array. (A list is an array indexed in ascending order of numeric keys from zero, that is, a non-associative array). + + +Array of Values: arrayOf() listOf() +----------------------------------- + +The array is too general structure, it is more useful to specify exactly what elements it can contain. For example, an array whose elements can only be strings: + +```php +$schema = Expect::arrayOf('string'); + +$processor->process($schema, ['hello', 'world']); // OK +$processor->process($schema, ['a' => 'hello', 'b' => 'world']); // OK +$processor->process($schema, ['key' => 123]); // ERROR: 123 is not a string +``` + +The list is an indexed array: + +```php +$schema = Expect::listOf('string'); + +$processor->process($schema, ['a', 'b']); // OK +$processor->process($schema, ['a', 123]); // ERROR: 123 is not a string +$processor->process($schema, ['key' => 'a']); // ERROR: is not a list +$processor->process($schema, [1 => 'a', 0 => 'b']); // ERROR: is not a list +``` + +The parameter can also be a schema, so we can write: + +```php +Expect::arrayOf(Expect::bool()) +``` + +The default value is an empty array. If you specify default value, it will be merged with the passed data. This can be disabled using `mergeDefaults(false)`. + + +Enumeration: anyOf() +-------------------- + +`anyOf()` is a set of values ​​or schemas that a value can be. Here's how to write an array of elements that can be either `'a'`, `true`, or `null`: + +```php +$schema = Expect::listOf( + Expect::anyOf('a', true, null) +); + +$processor->process($schema, ['a', true, null, 'a']); // OK +$processor->process($schema, ['a', false]); // ERROR: false does not belong there +``` + +The enumeration elements can also be schemas: + +```php +$schema = Expect::listOf( + Expect::anyOf(Expect::string(), true, null) +); + +$processor->process($schema, ['foo', true, null, 'bar']); // OK +$processor->process($schema, [123]); // ERROR +``` + +The default value is `null`. + + +Structures +---------- + +Structures are objects with defined keys. Each of these key => value pairs is referred to as a "property": + +Structures accept arrays and objects and return objects `stdClass` (unless you change it with `castTo('array')`, etc.). + +By default, all properties are optional and have a default value of `null`. You can define mandatory properties using `required()`: + +```php +$schema = Expect::structure([ + 'required' => Expect::string()->required(), + 'optional' => Expect::string(), // the default value is null +]); + +$processor->process($schema, ['optional' => '']); +// ERROR: item 'required' is missing + +$processor->process($schema, ['required' => 'foo']); +// OK, returns {'required' => 'foo', 'optional' => null} +``` + +Although `null` is the default value of the `optional` property, it is not allowed in the input data (the value must be a string). Properties accepting `null` are defined using `nullable()`: + +```php +$schema = Expect::structure([ + 'optional' => Expect::string(), + 'nullable' => Expect::string()->nullable(), +]); + +$processor->process($schema, ['optional' => null]); +// ERROR: 'optional' expects to be string, null given. + +$processor->process($schema, ['nullable' => null]); +// OK, returns {'optional' => null, 'nullable' => null} +``` + +By default, there can be no extra items in the input data: + +```php +$schema = Expect::structure([ + 'key' => Expect::string(), +]); + +$processor->process($schema, ['additional' => 1]); +// ERROR: Unexpected item 'additional' +``` + +Which we can change with `otherItems()`. As a parameter, we will specify the schema for each extra element: + +```php +$schema = Expect::structure([ + 'key' => Expect::string(), +])->otherItems(Expect::int()); + +$processor->process($schema, ['additional' => 1]); // OK +$processor->process($schema, ['additional' => true]); // ERROR +``` + +Deprecations +------------ + +You can deprecate property using the `deprecated([string $message])` method. Deprecation notices are returned by `$processor->getWarnings()` (since v1.1): + +```php +$schema = Expect::structure([ + 'old' => Expect::int()->deprecated('The item %path% is deprecated'), +]); + +$processor->process($schema, ['old' => 1]); // OK +$processor->getWarnings(); // ["The item 'old' is deprecated"] +``` + +Ranges: min() max() +------------------- + +Use `min()` and `max()` to limit the number of elements for arrays: + +```php +// array, at least 10 items, maximum 20 items +Expect::array()->min(10)->max(20); +``` + +For strings, limit their length: + +```php +// string, at least 10 characters long, maximum 20 characters +Expect::string()->min(10)->max(20); +``` + +For numbers, limit their value: + +```php +// integer, between 10 and 20 inclusive +Expect::int()->min(10)->max(20); +``` + +Of course, it is possible to mention only `min()`, or only `max()`: + +```php +// string, maximum 20 characters +Expect::string()->max(20); +``` + + +Regular Expressions: pattern() +------------------------------ + +Using `pattern()`, you can specify a regular expression which the **whole** input string must match (i.e. as if it were wrapped in characters `^` a `$`): + +```php +// just 9 digits +Expect::string()->pattern('\d{9}'); +``` + + +Custom Assertions: assert() +--------------------------- + +You can add any other restrictions using `assert(callable $fn)`. + +```php +$countIsEven = function ($v) { return count($v) % 2 === 0; }; + +$schema = Expect::arrayOf('string') + ->assert($countIsEven); // the count must be even + +$processor->process($schema, ['a', 'b']); // OK +$processor->process($schema, ['a', 'b', 'c']); // ERROR: 3 is not even +``` + +Or + +```php +Expect::string()->assert('is_file'); // the file must exist +``` + +You can add your own description for each assertions. It will be part of the error message. + +```php +$schema = Expect::arrayOf('string') + ->assert($countIsEven, 'Even items in array'); + +$processor->process($schema, ['a', 'b', 'c']); +// Failed assertion "Even items in array" for item with value array. +``` + +The method can be called repeatedly to add more assertions. + + +Mapping to Objects: from() +-------------------------- + +You can generate structure schema from the class. Example: + +```php +class Config +{ + /** @var string */ + public $name; + /** @var string|null */ + public $password; + /** @var bool */ + public $admin = false; +} + +$schema = Expect::from(new Config); + +$data = [ + 'name' => 'jeff', +]; + +$normalized = $processor->process($schema, $data); +// $normalized instanceof Config +// $normalized = {'name' => 'jeff', 'password' => null, 'admin' => false} +``` + +If you are using PHP 7.4 or higher, you can use native types: + +```php +class Config +{ + public string $name; + public ?string $password; + public bool $admin = false; +} + +$schema = Expect::from(new Config); +``` + +Anonymous classes are also supported: + +```php +$schema = Expect::from(new class { + public string $name; + public ?string $password; + public bool $admin = false; +}); +``` + +Because the information obtained from the class definition may not be sufficient, you can add a custom schema for the elements with the second parameter: + +```php +$schema = Expect::from(new Config, [ + 'name' => Expect::string()->pattern('\w:.*'), +]); +``` + + +Casting: castTo() +----------------- + +Successfully validated data can be cast: + +```php +Expect::scalar()->castTo('string'); +``` + +In addition to native PHP types, you can also cast to classes: + +```php +Expect::scalar()->castTo('AddressEntity'); +``` + + +Normalization: before() +----------------------- + +Prior to the validation itself, the data can be normalized using the method `before()`. As an example, let's have an element that must be an array of strings (eg `['a', 'b', 'c']`), but receives input in the form of a string `a b c`: + +```php +$explode = function ($v) { return explode(' ', $v); }; + +$schema = Expect::arrayOf('string') + ->before($explode); + +$normalized = $processor->process($schema, 'a b c'); +// OK, returns ['a', 'b', 'c'] +``` diff --git a/vendor/nette/schema/src/Schema/Context.php b/vendor/nette/schema/src/Schema/Context.php new file mode 100644 index 0000000..e486d2e --- /dev/null +++ b/vendor/nette/schema/src/Schema/Context.php @@ -0,0 +1,49 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; + + +final class Context +{ + use Nette\SmartObject; + + /** @var bool */ + public $skipDefaults = false; + + /** @var string[] */ + public $path = []; + + /** @var bool */ + public $isKey = false; + + /** @var Message[] */ + public $errors = []; + + /** @var Message[] */ + public $warnings = []; + + /** @var array[] */ + public $dynamics = []; + + + public function addError(string $message, string $code, array $variables = []): Message + { + $variables['isKey'] = $this->isKey; + return $this->errors[] = new Message($message, $code, $this->path, $variables); + } + + + public function addWarning(string $message, string $code, array $variables = []): Message + { + return $this->warnings[] = new Message($message, $code, $this->path, $variables); + } +} diff --git a/vendor/nette/schema/src/Schema/DynamicParameter.php b/vendor/nette/schema/src/Schema/DynamicParameter.php new file mode 100644 index 0000000..8dd6105 --- /dev/null +++ b/vendor/nette/schema/src/Schema/DynamicParameter.php @@ -0,0 +1,15 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + + +interface DynamicParameter +{ +} diff --git a/vendor/nette/schema/src/Schema/Elements/AnyOf.php b/vendor/nette/schema/src/Schema/Elements/AnyOf.php new file mode 100644 index 0000000..475b3e2 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Elements/AnyOf.php @@ -0,0 +1,141 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema\Elements; + +use Nette; +use Nette\Schema\Context; +use Nette\Schema\Helpers; +use Nette\Schema\Schema; + + +final class AnyOf implements Schema +{ + use Base; + use Nette\SmartObject; + + /** @var array */ + private $set; + + + /** + * @param mixed|Schema ...$set + */ + public function __construct(...$set) + { + if (!$set) { + throw new Nette\InvalidStateException('The enumeration must not be empty.'); + } + + $this->set = $set; + } + + + public function firstIsDefault(): self + { + $this->default = $this->set[0]; + return $this; + } + + + public function nullable(): self + { + $this->set[] = null; + return $this; + } + + + public function dynamic(): self + { + $this->set[] = new Type(Nette\Schema\DynamicParameter::class); + return $this; + } + + + /********************* processing ****************d*g**/ + + + public function normalize($value, Context $context) + { + return $this->doNormalize($value, $context); + } + + + public function merge($value, $base) + { + if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) { + unset($value[Helpers::PREVENT_MERGING]); + return $value; + } + + return Helpers::merge($value, $base); + } + + + public function complete($value, Context $context) + { + $expecteds = $innerErrors = []; + foreach ($this->set as $item) { + if ($item instanceof Schema) { + $dolly = new Context; + $dolly->path = $context->path; + $res = $item->complete($item->normalize($value, $dolly), $dolly); + if (!$dolly->errors) { + $context->warnings = array_merge($context->warnings, $dolly->warnings); + return $this->doFinalize($res, $context); + } + + foreach ($dolly->errors as $error) { + if ($error->path !== $context->path || empty($error->variables['expected'])) { + $innerErrors[] = $error; + } else { + $expecteds[] = $error->variables['expected']; + } + } + } else { + if ($item === $value) { + return $this->doFinalize($value, $context); + } + + $expecteds[] = Nette\Schema\Helpers::formatValue($item); + } + } + + if ($innerErrors) { + $context->errors = array_merge($context->errors, $innerErrors); + } else { + $context->addError( + 'The %label% %path% expects to be %expected%, %value% given.', + Nette\Schema\Message::TYPE_MISMATCH, + [ + 'value' => $value, + 'expected' => implode('|', array_unique($expecteds)), + ] + ); + } + } + + + public function completeDefault(Context $context) + { + if ($this->required) { + $context->addError( + 'The mandatory item %path% is missing.', + Nette\Schema\Message::MISSING_ITEM + ); + return null; + } + + if ($this->default instanceof Schema) { + return $this->default->completeDefault($context); + } + + return $this->default; + } +} diff --git a/vendor/nette/schema/src/Schema/Elements/Base.php b/vendor/nette/schema/src/Schema/Elements/Base.php new file mode 100644 index 0000000..6081761 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Elements/Base.php @@ -0,0 +1,200 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema\Elements; + +use Nette; +use Nette\Schema\Context; + + +/** + * @internal + */ +trait Base +{ + /** @var bool */ + private $required = false; + + /** @var mixed */ + private $default; + + /** @var callable|null */ + private $before; + + /** @var array[] */ + private $asserts = []; + + /** @var string|null */ + private $castTo; + + /** @var string|null */ + private $deprecated; + + + public function default($value): self + { + $this->default = $value; + return $this; + } + + + public function required(bool $state = true): self + { + $this->required = $state; + return $this; + } + + + public function before(callable $handler): self + { + $this->before = $handler; + return $this; + } + + + public function castTo(string $type): self + { + $this->castTo = $type; + return $this; + } + + + public function assert(callable $handler, ?string $description = null): self + { + $this->asserts[] = [$handler, $description]; + return $this; + } + + + /** Marks as deprecated */ + public function deprecated(string $message = 'The item %path% is deprecated.'): self + { + $this->deprecated = $message; + return $this; + } + + + public function completeDefault(Context $context) + { + if ($this->required) { + $context->addError( + 'The mandatory item %path% is missing.', + Nette\Schema\Message::MISSING_ITEM + ); + return null; + } + + return $this->default; + } + + + public function doNormalize($value, Context $context) + { + if ($this->before) { + $value = ($this->before)($value); + } + + return $value; + } + + + private function doDeprecation(Context $context): void + { + if ($this->deprecated !== null) { + $context->addWarning( + $this->deprecated, + Nette\Schema\Message::DEPRECATED + ); + } + } + + + private function doValidate($value, string $expected, Context $context): bool + { + if (!Nette\Utils\Validators::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + $context->addError( + 'The %label% %path% expects to be %expected%, %value% given.', + Nette\Schema\Message::TYPE_MISMATCH, + ['value' => $value, 'expected' => $expected] + ); + return false; + } + + return true; + } + + + private function doValidateRange($value, array $range, Context $context, string $types = ''): bool + { + if (is_array($value) || is_string($value)) { + [$length, $label] = is_array($value) + ? [count($value), 'items'] + : (in_array('unicode', explode('|', $types), true) + ? [Nette\Utils\Strings::length($value), 'characters'] + : [strlen($value), 'bytes']); + + if (!self::isInRange($length, $range)) { + $context->addError( + "The length of %label% %path% expects to be in range %expected%, %length% $label given.", + Nette\Schema\Message::LENGTH_OUT_OF_RANGE, + ['value' => $value, 'length' => $length, 'expected' => implode('..', $range)] + ); + return false; + } + } elseif ((is_int($value) || is_float($value)) && !self::isInRange($value, $range)) { + $context->addError( + 'The %label% %path% expects to be in range %expected%, %value% given.', + Nette\Schema\Message::VALUE_OUT_OF_RANGE, + ['value' => $value, 'expected' => implode('..', $range)] + ); + return false; + } + + return true; + } + + + private function isInRange($value, array $range): bool + { + return ($range[0] === null || $value >= $range[0]) + && ($range[1] === null || $value <= $range[1]); + } + + + private function doFinalize($value, Context $context) + { + if ($this->castTo) { + if (Nette\Utils\Reflection::isBuiltinType($this->castTo)) { + settype($value, $this->castTo); + } else { + $object = new $this->castTo; + foreach ($value as $k => $v) { + $object->$k = $v; + } + + $value = $object; + } + } + + foreach ($this->asserts as $i => [$handler, $description]) { + if (!$handler($value)) { + $expected = $description ?: (is_string($handler) ? "$handler()" : "#$i"); + $context->addError( + 'Failed assertion ' . ($description ? "'%assertion%'" : '%assertion%') . ' for %label% %path% with value %value%.', + Nette\Schema\Message::FAILED_ASSERTION, + ['value' => $value, 'assertion' => $expected] + ); + return; + } + } + + return $value; + } +} diff --git a/vendor/nette/schema/src/Schema/Elements/Structure.php b/vendor/nette/schema/src/Schema/Elements/Structure.php new file mode 100644 index 0000000..a622dc1 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Elements/Structure.php @@ -0,0 +1,208 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema\Elements; + +use Nette; +use Nette\Schema\Context; +use Nette\Schema\Helpers; +use Nette\Schema\Schema; + + +final class Structure implements Schema +{ + use Base; + use Nette\SmartObject; + + /** @var Schema[] */ + private $items; + + /** @var Schema|null for array|list */ + private $otherItems; + + /** @var array{?int, ?int} */ + private $range = [null, null]; + + /** @var bool */ + private $skipDefaults = false; + + + /** + * @param Schema[] $items + */ + public function __construct(array $items) + { + (function (Schema ...$items) {})(...array_values($items)); + $this->items = $items; + $this->castTo = 'object'; + $this->required = true; + } + + + public function default($value): self + { + throw new Nette\InvalidStateException('Structure cannot have default value.'); + } + + + public function min(?int $min): self + { + $this->range[0] = $min; + return $this; + } + + + public function max(?int $max): self + { + $this->range[1] = $max; + return $this; + } + + + /** + * @param string|Schema $type + */ + public function otherItems($type = 'mixed'): self + { + $this->otherItems = $type instanceof Schema ? $type : new Type($type); + return $this; + } + + + public function skipDefaults(bool $state = true): self + { + $this->skipDefaults = $state; + return $this; + } + + + /********************* processing ****************d*g**/ + + + public function normalize($value, Context $context) + { + if ($prevent = (is_array($value) && isset($value[Helpers::PREVENT_MERGING]))) { + unset($value[Helpers::PREVENT_MERGING]); + } + + $value = $this->doNormalize($value, $context); + if (is_object($value)) { + $value = (array) $value; + } + + if (is_array($value)) { + foreach ($value as $key => $val) { + $itemSchema = $this->items[$key] ?? $this->otherItems; + if ($itemSchema) { + $context->path[] = $key; + $value[$key] = $itemSchema->normalize($val, $context); + array_pop($context->path); + } + } + + if ($prevent) { + $value[Helpers::PREVENT_MERGING] = true; + } + } + + return $value; + } + + + public function merge($value, $base) + { + if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) { + unset($value[Helpers::PREVENT_MERGING]); + $base = null; + } + + if (is_array($value) && is_array($base)) { + $index = 0; + foreach ($value as $key => $val) { + if ($key === $index) { + $base[] = $val; + $index++; + } elseif (array_key_exists($key, $base)) { + $itemSchema = $this->items[$key] ?? $this->otherItems; + $base[$key] = $itemSchema + ? $itemSchema->merge($val, $base[$key]) + : Helpers::merge($val, $base[$key]); + } else { + $base[$key] = $val; + } + } + + return $base; + } + + return Helpers::merge($value, $base); + } + + + public function complete($value, Context $context) + { + if ($value === null) { + $value = []; // is unable to distinguish null from array in NEON + } + + $this->doDeprecation($context); + + if (!$this->doValidate($value, 'array', $context) + || !$this->doValidateRange($value, $this->range, $context) + ) { + return; + } + + $errCount = count($context->errors); + $items = $this->items; + if ($extraKeys = array_keys(array_diff_key($value, $items))) { + if ($this->otherItems) { + $items += array_fill_keys($extraKeys, $this->otherItems); + } else { + $keys = array_map('strval', array_keys($items)); + foreach ($extraKeys as $key) { + $hint = Nette\Utils\ObjectHelpers::getSuggestion($keys, (string) $key); + $context->addError( + 'Unexpected item %path%' . ($hint ? ", did you mean '%hint%'?" : '.'), + Nette\Schema\Message::UNEXPECTED_ITEM, + ['hint' => $hint] + )->path[] = $key; + } + } + } + + foreach ($items as $itemKey => $itemVal) { + $context->path[] = $itemKey; + if (array_key_exists($itemKey, $value)) { + $value[$itemKey] = $itemVal->complete($value[$itemKey], $context); + } else { + $default = $itemVal->completeDefault($context); // checks required item + if (!$context->skipDefaults && !$this->skipDefaults) { + $value[$itemKey] = $default; + } + } + + array_pop($context->path); + } + + if (count($context->errors) > $errCount) { + return; + } + + return $this->doFinalize($value, $context); + } + + + public function completeDefault(Context $context) + { + return $this->required + ? $this->complete([], $context) + : null; + } +} diff --git a/vendor/nette/schema/src/Schema/Elements/Type.php b/vendor/nette/schema/src/Schema/Elements/Type.php new file mode 100644 index 0000000..5d8b2c0 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Elements/Type.php @@ -0,0 +1,230 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema\Elements; + +use Nette; +use Nette\Schema\Context; +use Nette\Schema\DynamicParameter; +use Nette\Schema\Helpers; +use Nette\Schema\Schema; + + +final class Type implements Schema +{ + use Base; + use Nette\SmartObject; + + /** @var string */ + private $type; + + /** @var Schema|null for arrays */ + private $itemsValue; + + /** @var Schema|null for arrays */ + private $itemsKey; + + /** @var array{?float, ?float} */ + private $range = [null, null]; + + /** @var string|null */ + private $pattern; + + /** @var bool */ + private $merge = true; + + + public function __construct(string $type) + { + $defaults = ['list' => [], 'array' => []]; + $this->type = $type; + $this->default = strpos($type, '[]') ? [] : $defaults[$type] ?? null; + } + + + public function nullable(): self + { + $this->type = 'null|' . $this->type; + return $this; + } + + + public function mergeDefaults(bool $state = true): self + { + $this->merge = $state; + return $this; + } + + + public function dynamic(): self + { + $this->type = DynamicParameter::class . '|' . $this->type; + return $this; + } + + + public function min(?float $min): self + { + $this->range[0] = $min; + return $this; + } + + + public function max(?float $max): self + { + $this->range[1] = $max; + return $this; + } + + + /** + * @param string|Schema $valueType + * @param string|Schema|null $keyType + * @internal use arrayOf() or listOf() + */ + public function items($valueType = 'mixed', $keyType = null): self + { + $this->itemsValue = $valueType instanceof Schema + ? $valueType + : new self($valueType); + $this->itemsKey = $keyType instanceof Schema || $keyType === null + ? $keyType + : new self($keyType); + return $this; + } + + + public function pattern(?string $pattern): self + { + $this->pattern = $pattern; + return $this; + } + + + /********************* processing ****************d*g**/ + + + public function normalize($value, Context $context) + { + if ($prevent = (is_array($value) && isset($value[Helpers::PREVENT_MERGING]))) { + unset($value[Helpers::PREVENT_MERGING]); + } + + $value = $this->doNormalize($value, $context); + if (is_array($value) && $this->itemsValue) { + $res = []; + foreach ($value as $key => $val) { + $context->path[] = $key; + $context->isKey = true; + $key = $this->itemsKey + ? $this->itemsKey->normalize($key, $context) + : $key; + $context->isKey = false; + $res[$key] = $this->itemsValue->normalize($val, $context); + array_pop($context->path); + } + + $value = $res; + } + + if ($prevent && is_array($value)) { + $value[Helpers::PREVENT_MERGING] = true; + } + + return $value; + } + + + public function merge($value, $base) + { + if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) { + unset($value[Helpers::PREVENT_MERGING]); + return $value; + } + + if (is_array($value) && is_array($base) && $this->itemsValue) { + $index = 0; + foreach ($value as $key => $val) { + if ($key === $index) { + $base[] = $val; + $index++; + } else { + $base[$key] = array_key_exists($key, $base) + ? $this->itemsValue->merge($val, $base[$key]) + : $val; + } + } + + return $base; + } + + return Helpers::merge($value, $base); + } + + + public function complete($value, Context $context) + { + $merge = $this->merge; + if (is_array($value) && isset($value[Helpers::PREVENT_MERGING])) { + unset($value[Helpers::PREVENT_MERGING]); + $merge = false; + } + + if ($value === null && is_array($this->default)) { + $value = []; // is unable to distinguish null from array in NEON + } + + $this->doDeprecation($context); + + if (!$this->doValidate($value, $this->type, $context) + || !$this->doValidateRange($value, $this->range, $context, $this->type) + ) { + return; + } + + if ($value !== null && $this->pattern !== null && !preg_match("\x01^(?:$this->pattern)$\x01Du", $value)) { + $context->addError( + "The %label% %path% expects to match pattern '%pattern%', %value% given.", + Nette\Schema\Message::PATTERN_MISMATCH, + ['value' => $value, 'pattern' => $this->pattern] + ); + return; + } + + if ($value instanceof DynamicParameter) { + $expected = $this->type . ($this->range === [null, null] ? '' : ':' . implode('..', $this->range)); + $context->dynamics[] = [$value, str_replace(DynamicParameter::class . '|', '', $expected)]; + } + + if ($this->itemsValue) { + $errCount = count($context->errors); + $res = []; + foreach ($value as $key => $val) { + $context->path[] = $key; + $context->isKey = true; + $key = $this->itemsKey ? $this->itemsKey->complete($key, $context) : $key; + $context->isKey = false; + $res[$key] = $this->itemsValue->complete($val, $context); + array_pop($context->path); + } + + if (count($context->errors) > $errCount) { + return null; + } + + $value = $res; + } + + if ($merge) { + $value = Helpers::merge($value, $this->default); + } + + return $this->doFinalize($value, $context); + } +} diff --git a/vendor/nette/schema/src/Schema/Expect.php b/vendor/nette/schema/src/Schema/Expect.php new file mode 100644 index 0000000..f299950 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Expect.php @@ -0,0 +1,119 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; +use Nette\Schema\Elements\AnyOf; +use Nette\Schema\Elements\Structure; +use Nette\Schema\Elements\Type; + + +/** + * Schema generator. + * + * @method static Type scalar($default = null) + * @method static Type string($default = null) + * @method static Type int($default = null) + * @method static Type float($default = null) + * @method static Type bool($default = null) + * @method static Type null() + * @method static Type array($default = []) + * @method static Type list($default = []) + * @method static Type mixed($default = null) + * @method static Type email($default = null) + * @method static Type unicode($default = null) + */ +final class Expect +{ + use Nette\SmartObject; + + public static function __callStatic(string $name, array $args): Type + { + $type = new Type($name); + if ($args) { + $type->default($args[0]); + } + + return $type; + } + + + public static function type(string $type): Type + { + return new Type($type); + } + + + /** + * @param mixed|Schema ...$set + */ + public static function anyOf(...$set): AnyOf + { + return new AnyOf(...$set); + } + + + /** + * @param Schema[] $items + */ + public static function structure(array $items): Structure + { + return new Structure($items); + } + + + /** + * @param object $object + */ + public static function from($object, array $items = []): Structure + { + $ro = new \ReflectionObject($object); + foreach ($ro->getProperties() as $prop) { + $type = Helpers::getPropertyType($prop) ?? 'mixed'; + $item = &$items[$prop->getName()]; + if (!$item) { + $item = new Type($type); + if (PHP_VERSION_ID >= 70400 && !$prop->isInitialized($object)) { + $item->required(); + } else { + $def = $prop->getValue($object); + if (is_object($def)) { + $item = static::from($def); + } elseif ($def === null && !Nette\Utils\Validators::is(null, $type)) { + $item->required(); + } else { + $item->default($def); + } + } + } + } + + return (new Structure($items))->castTo($ro->getName()); + } + + + /** + * @param string|Schema $valueType + * @param string|Schema|null $keyType + */ + public static function arrayOf($valueType, $keyType = null): Type + { + return (new Type('array'))->items($valueType, $keyType); + } + + + /** + * @param string|Schema $type + */ + public static function listOf($type): Type + { + return (new Type('list'))->items($type); + } +} diff --git a/vendor/nette/schema/src/Schema/Helpers.php b/vendor/nette/schema/src/Schema/Helpers.php new file mode 100644 index 0000000..0d2f12c --- /dev/null +++ b/vendor/nette/schema/src/Schema/Helpers.php @@ -0,0 +1,110 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; +use Nette\Utils\Reflection; + + +/** + * @internal + */ +final class Helpers +{ + use Nette\StaticClass; + + public const PREVENT_MERGING = '_prevent_merging'; + + + /** + * Merges dataset. Left has higher priority than right one. + * @return array|string + */ + public static function merge($value, $base) + { + if (is_array($value) && isset($value[self::PREVENT_MERGING])) { + unset($value[self::PREVENT_MERGING]); + return $value; + } + + if (is_array($value) && is_array($base)) { + $index = 0; + foreach ($value as $key => $val) { + if ($key === $index) { + $base[] = $val; + $index++; + } else { + $base[$key] = static::merge($val, $base[$key] ?? null); + } + } + + return $base; + + } elseif ($value === null && is_array($base)) { + return $base; + + } else { + return $value; + } + } + + + public static function getPropertyType(\ReflectionProperty $prop): ?string + { + if (!class_exists(Nette\Utils\Type::class)) { + throw new Nette\NotSupportedException('Expect::from() requires nette/utils 3.x'); + } elseif ($type = Nette\Utils\Type::fromReflection($prop)) { + return (string) $type; + } elseif ($type = preg_replace('#\s.*#', '', (string) self::parseAnnotation($prop, 'var'))) { + $class = Reflection::getPropertyDeclaringClass($prop); + return preg_replace_callback('#[\w\\\\]+#', function ($m) use ($class) { + return Reflection::expandClassName($m[0], $class); + }, $type); + } + + return null; + } + + + /** + * Returns an annotation value. + * @param \ReflectionProperty $ref + */ + public static function parseAnnotation(\Reflector $ref, string $name): ?string + { + if (!Reflection::areCommentsAvailable()) { + throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); + } + + $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#'; + if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) { + return $m[1] ?? ''; + } + + return null; + } + + + /** + * @param mixed $value + */ + public static function formatValue($value): string + { + if (is_object($value)) { + return 'object ' . get_class($value); + } elseif (is_string($value)) { + return "'" . Nette\Utils\Strings::truncate($value, 15, '...') . "'"; + } elseif (is_scalar($value)) { + return var_export($value, true); + } else { + return strtolower(gettype($value)); + } + } +} diff --git a/vendor/nette/schema/src/Schema/Message.php b/vendor/nette/schema/src/Schema/Message.php new file mode 100644 index 0000000..49c32bd --- /dev/null +++ b/vendor/nette/schema/src/Schema/Message.php @@ -0,0 +1,79 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; + + +final class Message +{ + use Nette\SmartObject; + + /** variables: {value: mixed, expected: string} */ + public const TYPE_MISMATCH = 'schema.typeMismatch'; + + /** variables: {value: mixed, expected: string} */ + public const VALUE_OUT_OF_RANGE = 'schema.valueOutOfRange'; + + /** variables: {value: mixed, length: int, expected: string} */ + public const LENGTH_OUT_OF_RANGE = 'schema.lengthOutOfRange'; + + /** variables: {value: string, pattern: string} */ + public const PATTERN_MISMATCH = 'schema.patternMismatch'; + + /** variables: {value: mixed, assertion: string} */ + public const FAILED_ASSERTION = 'schema.failedAssertion'; + + /** no variables */ + public const MISSING_ITEM = 'schema.missingItem'; + + /** variables: {hint: string} */ + public const UNEXPECTED_ITEM = 'schema.unexpectedItem'; + + /** no variables */ + public const DEPRECATED = 'schema.deprecated'; + + /** @var string */ + public $message; + + /** @var string */ + public $code; + + /** @var string[] */ + public $path; + + /** @var string[] */ + public $variables; + + + public function __construct(string $message, string $code, array $path, array $variables = []) + { + $this->message = $message; + $this->code = $code; + $this->path = $path; + $this->variables = $variables; + } + + + public function toString(): string + { + $vars = $this->variables; + $vars['label'] = empty($vars['isKey']) ? 'item' : 'key of item'; + $vars['path'] = $this->path + ? "'" . implode("\u{a0}›\u{a0}", $this->path) . "'" + : null; + $vars['value'] = Helpers::formatValue($vars['value'] ?? null); + + return preg_replace_callback('~( ?)%(\w+)%~', function ($m) use ($vars) { + [, $space, $key] = $m; + return $vars[$key] === null ? '' : $space . $vars[$key]; + }, $this->message); + } +} diff --git a/vendor/nette/schema/src/Schema/Processor.php b/vendor/nette/schema/src/Schema/Processor.php new file mode 100644 index 0000000..a10a0ef --- /dev/null +++ b/vendor/nette/schema/src/Schema/Processor.php @@ -0,0 +1,105 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; + + +/** + * Schema validator. + */ +final class Processor +{ + use Nette\SmartObject; + + /** @var array */ + public $onNewContext = []; + + /** @var Context|null */ + private $context; + + /** @var bool */ + private $skipDefaults; + + + public function skipDefaults(bool $value = true) + { + $this->skipDefaults = $value; + } + + + /** + * Normalizes and validates data. Result is a clean completed data. + * @return mixed + * @throws ValidationException + */ + public function process(Schema $schema, $data) + { + $this->createContext(); + $data = $schema->normalize($data, $this->context); + $this->throwsErrors(); + $data = $schema->complete($data, $this->context); + $this->throwsErrors(); + return $data; + } + + + /** + * Normalizes and validates and merges multiple data. Result is a clean completed data. + * @return mixed + * @throws ValidationException + */ + public function processMultiple(Schema $schema, array $dataset) + { + $this->createContext(); + $flatten = null; + $first = true; + foreach ($dataset as $data) { + $data = $schema->normalize($data, $this->context); + $this->throwsErrors(); + $flatten = $first ? $data : $schema->merge($data, $flatten); + $first = false; + } + + $data = $schema->complete($flatten, $this->context); + $this->throwsErrors(); + return $data; + } + + + /** + * @return string[] + */ + public function getWarnings(): array + { + $res = []; + foreach ($this->context->warnings as $message) { + $res[] = $message->toString(); + } + + return $res; + } + + + private function throwsErrors(): void + { + if ($this->context->errors) { + throw new ValidationException(null, $this->context->errors); + } + } + + + private function createContext() + { + $this->context = new Context; + $this->context->skipDefaults = $this->skipDefaults; + $this->onNewContext($this->context); + } +} diff --git a/vendor/nette/schema/src/Schema/Schema.php b/vendor/nette/schema/src/Schema/Schema.php new file mode 100644 index 0000000..bbd8bd0 --- /dev/null +++ b/vendor/nette/schema/src/Schema/Schema.php @@ -0,0 +1,37 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + + +interface Schema +{ + /** + * Normalization. + * @return mixed + */ + function normalize($value, Context $context); + + /** + * Merging. + * @return mixed + */ + function merge($value, $base); + + /** + * Validation and finalization. + * @return mixed + */ + function complete($value, Context $context); + + /** + * @return mixed + */ + function completeDefault(Context $context); +} diff --git a/vendor/nette/schema/src/Schema/ValidationException.php b/vendor/nette/schema/src/Schema/ValidationException.php new file mode 100644 index 0000000..04c581f --- /dev/null +++ b/vendor/nette/schema/src/Schema/ValidationException.php @@ -0,0 +1,55 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Schema; + +use Nette; + + +/** + * Validation error. + */ +class ValidationException extends Nette\InvalidStateException +{ + /** @var Message[] */ + private $messages; + + + /** + * @param Message[] $messages + */ + public function __construct(?string $message, array $messages = []) + { + parent::__construct($message ?: $messages[0]->toString()); + $this->messages = $messages; + } + + + /** + * @return string[] + */ + public function getMessages(): array + { + $res = []; + foreach ($this->messages as $message) { + $res[] = $message->toString(); + } + + return $res; + } + + + /** + * @return Message[] + */ + public function getMessageObjects(): array + { + return $this->messages; + } +} diff --git a/vendor/nette/utils/.phpstorm.meta.php b/vendor/nette/utils/.phpstorm.meta.php new file mode 100644 index 0000000..25851af --- /dev/null +++ b/vendor/nette/utils/.phpstorm.meta.php @@ -0,0 +1,13 @@ +<?php + +declare(strict_types=1); + +namespace PHPSTORM_META; + +override(\Nette\Utils\Arrays::get(0), elementType(0)); +override(\Nette\Utils\Arrays::getRef(0), elementType(0)); +override(\Nette\Utils\Arrays::grep(0), type(0)); +override(\Nette\Utils\Arrays::toObject(0), type(1)); + +expectedArguments(\Nette\Utils\Image::resize(), 2, \Nette\Utils\Image::ShrinkOnly, \Nette\Utils\Image::Stretch, \Nette\Utils\Image::OrSmaller, \Nette\Utils\Image::OrBigger, \Nette\Utils\Image::Cover); +expectedArguments(\Nette\Utils\Image::calculateSize(), 4, \Nette\Utils\Image::ShrinkOnly, \Nette\Utils\Image::Stretch, \Nette\Utils\Image::OrSmaller, \Nette\Utils\Image::OrBigger, \Nette\Utils\Image::Cover); diff --git a/vendor/nette/utils/composer.json b/vendor/nette/utils/composer.json new file mode 100644 index 0000000..bbea123 --- /dev/null +++ b/vendor/nette/utils/composer.json @@ -0,0 +1,52 @@ +{ + "name": "nette/utils", + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "keywords": ["nette", "images", "json", "password", "validation", "utility", "string", "array", "core", "slugify", "utf-8", "unicode", "paginator", "datetime"], + "homepage": "https://nette.org", + "license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "require": { + "php": ">=8.0 <8.3" + }, + "require-dev": { + "nette/tester": "^2.4", + "tracy/tracy": "^2.9", + "phpstan/phpstan": "^1.0", + "jetbrains/phpstorm-attributes": "dev-master" + }, + "conflict": { + "nette/finder": "<3", + "nette/schema": "<1.2.2" + }, + "suggest": { + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available", + "ext-gd": "to use Image", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse", + "tester": "tester tests -s" + }, + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + } +} diff --git a/vendor/nette/utils/license.md b/vendor/nette/utils/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/utils/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +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 "Nette Framework" 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 owner 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. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/utils/readme.md b/vendor/nette/utils/readme.md new file mode 100644 index 0000000..ff2ab46 --- /dev/null +++ b/vendor/nette/utils/readme.md @@ -0,0 +1,56 @@ +Nette Utility Classes +===================== + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils) +[![Tests](https://github.com/nette/utils/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/utils/actions) +[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md) + + +Introduction +------------ + +In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use: + +- [Arrays](https://doc.nette.org/utils/arrays) - manipulate arrays +- [Callback](https://doc.nette.org/utils/callback) - PHP callbacks +- [Date and Time](https://doc.nette.org/utils/datetime) - modify times and dates +- [Filesystem](https://doc.nette.org/utils/filesystem) - copying, renaming, … +- [Finder](https://doc.nette.org/utils/finder) - finds files and directories +- [Helper Functions](https://doc.nette.org/utils/helpers) +- [HTML elements](https://doc.nette.org/utils/html-elements) - generate HTML +- [Images](https://doc.nette.org/utils/images) - crop, resize, rotate images +- [JSON](https://doc.nette.org/utils/json) - encoding and decoding +- [Generating Random Strings](https://doc.nette.org/utils/random) +- [Paginator](https://doc.nette.org/utils/paginator) - pagination math +- [PHP Reflection](https://doc.nette.org/utils/reflection) +- [Strings](https://doc.nette.org/utils/strings) - useful text functions +- [SmartObject](https://doc.nette.org/utils/smartobject) - PHP object enhancements +- [Validation](https://doc.nette.org/utils/validators) - validate inputs +- [Type](https://doc.nette.org/utils/type) - PHP data type + + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/utils +``` + +- Nette Utils 4.0 is compatible with PHP 8.0 to 8.2 +- Nette Utils 3.2 is compatible with PHP 7.2 to 8.2 +- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0 +- Nette Utils 3.0 is compatible with PHP 7.1 to 8.0 +- Nette Utils 2.5 is compatible with PHP 5.6 to 8.0 + +[Support Me](https://github.com/sponsors/dg) +-------------------------------------------- + +Do you like Nette Utils? Are you looking forward to the new features? + +[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg) + +Thank you! diff --git a/vendor/nette/utils/src/HtmlStringable.php b/vendor/nette/utils/src/HtmlStringable.php new file mode 100644 index 0000000..d749d4e --- /dev/null +++ b/vendor/nette/utils/src/HtmlStringable.php @@ -0,0 +1,22 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette; + + +interface HtmlStringable +{ + /** + * Returns string in HTML format + */ + function __toString(): string; +} + + +interface_exists(Utils\IHtmlString::class); diff --git a/vendor/nette/utils/src/Iterators/CachingIterator.php b/vendor/nette/utils/src/Iterators/CachingIterator.php new file mode 100644 index 0000000..55368d9 --- /dev/null +++ b/vendor/nette/utils/src/Iterators/CachingIterator.php @@ -0,0 +1,164 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Iterators; + +use Nette; + + +/** + * Smarter caching iterator. + * + * @property-read bool $first + * @property-read bool $last + * @property-read bool $empty + * @property-read bool $odd + * @property-read bool $even + * @property-read int $counter + * @property-read mixed $nextKey + * @property-read mixed $nextValue + */ +class CachingIterator extends \CachingIterator implements \Countable +{ + use Nette\SmartObject; + + private int $counter = 0; + + + public function __construct($iterator) + { + if (is_array($iterator) || $iterator instanceof \stdClass) { + $iterator = new \ArrayIterator($iterator); + + } elseif ($iterator instanceof \IteratorAggregate) { + do { + $iterator = $iterator->getIterator(); + } while ($iterator instanceof \IteratorAggregate); + + assert($iterator instanceof \Iterator); + + } elseif ($iterator instanceof \Iterator) { + } elseif ($iterator instanceof \Traversable) { + $iterator = new \IteratorIterator($iterator); + } else { + throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', self::class, is_object($iterator) ? $iterator::class : gettype($iterator))); + } + + parent::__construct($iterator, 0); + } + + + /** + * Is the current element the first one? + */ + public function isFirst(?int $gridWidth = null): bool + { + return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); + } + + + /** + * Is the current element the last one? + */ + public function isLast(?int $gridWidth = null): bool + { + return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); + } + + + /** + * Is the iterator empty? + */ + public function isEmpty(): bool + { + return $this->counter === 0; + } + + + /** + * Is the counter odd? + */ + public function isOdd(): bool + { + return $this->counter % 2 === 1; + } + + + /** + * Is the counter even? + */ + public function isEven(): bool + { + return $this->counter % 2 === 0; + } + + + /** + * Returns the counter. + */ + public function getCounter(): int + { + return $this->counter; + } + + + /** + * Returns the count of elements. + */ + public function count(): int + { + $inner = $this->getInnerIterator(); + if ($inner instanceof \Countable) { + return $inner->count(); + + } else { + throw new Nette\NotSupportedException('Iterator is not countable.'); + } + } + + + /** + * Forwards to the next element. + */ + public function next(): void + { + parent::next(); + if (parent::valid()) { + $this->counter++; + } + } + + + /** + * Rewinds the Iterator. + */ + public function rewind(): void + { + parent::rewind(); + $this->counter = parent::valid() ? 1 : 0; + } + + + /** + * Returns the next key. + */ + public function getNextKey(): mixed + { + return $this->getInnerIterator()->key(); + } + + + /** + * Returns the next element. + */ + public function getNextValue(): mixed + { + return $this->getInnerIterator()->current(); + } +} diff --git a/vendor/nette/utils/src/Iterators/Mapper.php b/vendor/nette/utils/src/Iterators/Mapper.php new file mode 100644 index 0000000..87823ba --- /dev/null +++ b/vendor/nette/utils/src/Iterators/Mapper.php @@ -0,0 +1,34 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Iterators; + + + +/** + * Applies the callback to the elements of the inner iterator. + */ +class Mapper extends \IteratorIterator +{ + /** @var callable */ + private $callback; + + + public function __construct(\Traversable $iterator, callable $callback) + { + parent::__construct($iterator); + $this->callback = $callback; + } + + + public function current(): mixed + { + return ($this->callback)(parent::current(), parent::key()); + } +} diff --git a/vendor/nette/utils/src/SmartObject.php b/vendor/nette/utils/src/SmartObject.php new file mode 100644 index 0000000..dca02d9 --- /dev/null +++ b/vendor/nette/utils/src/SmartObject.php @@ -0,0 +1,140 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette; + +use Nette\Utils\ObjectHelpers; + + +/** + * Strict class for better experience. + * - 'did you mean' hints + * - access to undeclared members throws exceptions + * - support for @property annotations + * - support for calling event handlers stored in $onEvent via onEvent() + */ +trait SmartObject +{ + /** + * @return mixed + * @throws MemberAccessException + */ + public function __call(string $name, array $args) + { + $class = static::class; + + if (ObjectHelpers::hasProperty($class, $name) === 'event') { // calling event handlers + $handlers = $this->$name ?? null; + if (is_iterable($handlers)) { + foreach ($handlers as $handler) { + $handler(...$args); + } + } elseif ($handlers !== null) { + throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.'); + } + + return null; + } + + ObjectHelpers::strictCall($class, $name); + } + + + /** + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args) + { + ObjectHelpers::strictStaticCall(static::class, $name); + } + + + /** + * @return mixed + * @throws MemberAccessException if the property is not defined. + */ + public function &__get(string $name) + { + $class = static::class; + + if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + + $m = ($prop & 0b0010 ? 'get' : 'is') . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + } else { + ObjectHelpers::strictGet($class, $name); + } + } + + + /** + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set(string $name, mixed $value): void + { + $class = static::class; + + if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + + $m = 'set' . ucfirst($name); + if ($prop & 0b10000) { + $trace = debug_backtrace(0, 1)[0]; // suppose this method is called from __call() + $loc = isset($trace['file'], $trace['line']) + ? " in $trace[file] on line $trace[line]" + : ''; + trigger_error("Property $class::\$$name is deprecated, use $class::$m() method$loc.", E_USER_DEPRECATED); + } + + $this->$m($value); + + } else { + ObjectHelpers::strictSet($class, $name); + } + } + + + /** + * @throws MemberAccessException + */ + public function __unset(string $name): void + { + $class = static::class; + if (!ObjectHelpers::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + public function __isset(string $name): bool + { + return isset(ObjectHelpers::getMagicProperties(static::class)[$name]); + } +} diff --git a/vendor/nette/utils/src/StaticClass.php b/vendor/nette/utils/src/StaticClass.php new file mode 100644 index 0000000..c719e3b --- /dev/null +++ b/vendor/nette/utils/src/StaticClass.php @@ -0,0 +1,36 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette; + + +/** + * Static class. + */ +trait StaticClass +{ + /** + * @return never + * @throws \Error + */ + final public function __construct() + { + throw new \Error('Class ' . static::class . ' is static and cannot be instantiated.'); + } + + + /** + * Call to undefined static method. + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args): mixed + { + Utils\ObjectHelpers::strictStaticCall(static::class, $name); + } +} diff --git a/vendor/nette/utils/src/Translator.php b/vendor/nette/utils/src/Translator.php new file mode 100644 index 0000000..f973f5f --- /dev/null +++ b/vendor/nette/utils/src/Translator.php @@ -0,0 +1,25 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Localization; + + +/** + * Translator adapter. + */ +interface Translator +{ + /** + * Translates the given string. + */ + function translate(string|\Stringable $message, mixed ...$parameters): string|\Stringable; +} + + +interface_exists(ITranslator::class); diff --git a/vendor/nette/utils/src/Utils/ArrayHash.php b/vendor/nette/utils/src/Utils/ArrayHash.php new file mode 100644 index 0000000..2e2db46 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayHash.php @@ -0,0 +1,104 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Provides objects to work as array. + * @template T + */ +class ArrayHash extends \stdClass implements \ArrayAccess, \Countable, \IteratorAggregate +{ + /** + * Transforms array to ArrayHash. + * @param array<T> $array + */ + public static function from(array $array, bool $recursive = true): static + { + $obj = new static; + foreach ($array as $key => $value) { + $obj->$key = $recursive && is_array($value) + ? static::from($value, true) + : $value; + } + + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator<int|string, T> + */ + public function &getIterator(): \Iterator + { + foreach ((array) $this as $key => $foo) { + yield $key => $this->$key; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count((array) $this); + } + + + /** + * Replaces or appends a item. + * @param string|int $key + * @param T $value + */ + public function offsetSet($key, $value): void + { + if (!is_scalar($key)) { // prevents null + throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key))); + } + + $this->$key = $value; + } + + + /** + * Returns a item. + * @param string|int $key + * @return T + */ + #[\ReturnTypeWillChange] + public function offsetGet($key) + { + return $this->$key; + } + + + /** + * Determines whether a item exists. + * @param string|int $key + */ + public function offsetExists($key): bool + { + return isset($this->$key); + } + + + /** + * Removes the element from this list. + * @param string|int $key + */ + public function offsetUnset($key): void + { + unset($this->$key); + } +} diff --git a/vendor/nette/utils/src/Utils/ArrayList.php b/vendor/nette/utils/src/Utils/ArrayList.php new file mode 100644 index 0000000..4f32061 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayList.php @@ -0,0 +1,134 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Provides the base class for a generic list (items can be accessed by index). + * @template T + */ +class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate +{ + use Nette\SmartObject; + + private array $list = []; + + + /** + * Transforms array to ArrayList. + * @param array<T> $array + */ + public static function from(array $array): static + { + if (!Arrays::isList($array)) { + throw new Nette\InvalidArgumentException('Array is not valid list.'); + } + + $obj = new static; + $obj->list = $array; + return $obj; + } + + + /** + * Returns an iterator over all items. + * @return \Iterator<int, T> + */ + public function &getIterator(): \Iterator + { + foreach ($this->list as &$item) { + yield $item; + } + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count($this->list); + } + + + /** + * Replaces or appends a item. + * @param int|null $index + * @param T $value + * @throws Nette\OutOfRangeException + */ + public function offsetSet($index, $value): void + { + if ($index === null) { + $this->list[] = $value; + + } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + + } else { + $this->list[$index] = $value; + } + } + + + /** + * Returns a item. + * @param int $index + * @return T + * @throws Nette\OutOfRangeException + */ + public function offsetGet($index): mixed + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + return $this->list[$index]; + } + + + /** + * Determines whether a item exists. + * @param int $index + */ + public function offsetExists($index): bool + { + return is_int($index) && $index >= 0 && $index < count($this->list); + } + + + /** + * Removes the element at the specified position in this list. + * @param int $index + * @throws Nette\OutOfRangeException + */ + public function offsetUnset($index): void + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + + array_splice($this->list, $index, 1); + } + + + /** + * Prepends a item. + * @param T $value + */ + public function prepend(mixed $value): void + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } +} diff --git a/vendor/nette/utils/src/Utils/Arrays.php b/vendor/nette/utils/src/Utils/Arrays.php new file mode 100644 index 0000000..336b3df --- /dev/null +++ b/vendor/nette/utils/src/Utils/Arrays.php @@ -0,0 +1,448 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use JetBrains\PhpStorm\Language; +use Nette; +use function is_array, is_int, is_object, count; + + +/** + * Array tools library. + */ +class Arrays +{ + use Nette\StaticClass; + + /** + * Returns item from array. If it does not exist, it throws an exception, unless a default value is set. + * @template T + * @param array<T> $array + * @param array-key|array-key[] $key + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function get(array $array, string|int|array $key, mixed $default = null): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) && array_key_exists($k, $array)) { + $array = $array[$k]; + } else { + if (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$k'."); + } + + return $default; + } + } + + return $array; + } + + + /** + * Returns reference to array item. If the index does not exist, new one is created with value null. + * @template T + * @param array<T> $array + * @param array-key|array-key[] $key + * @return ?T + * @throws Nette\InvalidArgumentException if traversed item is not an array + */ + public static function &getRef(array &$array, string|int|array $key): mixed + { + foreach (is_array($key) ? $key : [$key] as $k) { + if (is_array($array) || $array === null) { + $array = &$array[$k]; + } else { + throw new Nette\InvalidArgumentException('Traversed item is not an array.'); + } + } + + return $array; + } + + + /** + * Recursively merges two fields. It is useful, for example, for merging tree structures. It behaves as + * the + operator for array, ie. it adds a key/value pair from the second array to the first one and retains + * the value from the first array in the case of a key collision. + * @template T1 + * @template T2 + * @param array<T1> $array1 + * @param array<T2> $array2 + * @return array<T1|T2> + */ + public static function mergeTree(array $array1, array $array2): array + { + $res = $array1 + $array2; + foreach (array_intersect_key($array1, $array2) as $k => $v) { + if (is_array($v) && is_array($array2[$k])) { + $res[$k] = self::mergeTree($v, $array2[$k]); + } + } + + return $res; + } + + + /** + * Returns zero-indexed position of given array key. Returns null if key is not found. + */ + public static function getKeyOffset(array $array, string|int $key): ?int + { + return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true)); + } + + + /** + * @deprecated use getKeyOffset() + */ + public static function searchKey(array $array, $key): ?int + { + return self::getKeyOffset($array, $key); + } + + + /** + * Tests an array for the presence of value. + */ + public static function contains(array $array, mixed $value): bool + { + return in_array($value, $array, true); + } + + + /** + * Returns the first item from the array or null if array is empty. + * @template T + * @param array<T> $array + * @return ?T + */ + public static function first(array $array): mixed + { + return count($array) ? reset($array) : null; + } + + + /** + * Returns the last item from the array or null if array is empty. + * @template T + * @param array<T> $array + * @return ?T + */ + public static function last(array $array): mixed + { + return count($array) ? end($array) : null; + } + + + /** + * Inserts the contents of the $inserted array into the $array immediately after the $key. + * If $key is null (or does not exist), it is inserted at the beginning. + */ + public static function insertBefore(array &$array, string|int|null $key, array $inserted): void + { + $offset = $key === null ? 0 : (int) self::getKeyOffset($array, $key); + $array = array_slice($array, 0, $offset, true) + + $inserted + + array_slice($array, $offset, count($array), true); + } + + + /** + * Inserts the contents of the $inserted array into the $array before the $key. + * If $key is null (or does not exist), it is inserted at the end. + */ + public static function insertAfter(array &$array, string|int|null $key, array $inserted): void + { + if ($key === null || ($offset = self::getKeyOffset($array, $key)) === null) { + $offset = count($array) - 1; + } + + $array = array_slice($array, 0, $offset + 1, true) + + $inserted + + array_slice($array, $offset + 1, count($array), true); + } + + + /** + * Renames key in array. + */ + public static function renameKey(array &$array, string|int $oldKey, string|int $newKey): bool + { + $offset = self::getKeyOffset($array, $oldKey); + if ($offset === null) { + return false; + } + + $val = &$array[$oldKey]; + $keys = array_keys($array); + $keys[$offset] = $newKey; + $array = array_combine($keys, $array); + $array[$newKey] = &$val; + return true; + } + + + /** + * Returns only those array items, which matches a regular expression $pattern. + * @param string[] $array + * @return string[] + */ + public static function grep( + array $array, + #[Language('RegExp')] + string $pattern, + bool|int $invert = false, + ): array + { + $flags = $invert ? PREG_GREP_INVERT : 0; + return Strings::pcre('preg_grep', [$pattern, $array, $flags]); + } + + + /** + * Transforms multidimensional array to flat array. + */ + public static function flatten(array $array, bool $preserveKeys = false): array + { + $res = []; + $cb = $preserveKeys + ? function ($v, $k) use (&$res): void { $res[$k] = $v; } + : function ($v) use (&$res): void { $res[] = $v; }; + array_walk_recursive($array, $cb); + return $res; + } + + + /** + * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. + */ + public static function isList(mixed $value): bool + { + return is_array($value) && (PHP_VERSION_ID < 80100 + ? !$value || array_keys($value) === range(0, count($value) - 1) + : array_is_list($value) + ); + } + + + /** + * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. + * @param string|string[] $path + */ + public static function associate(array $array, $path): array|\stdClass + { + $parts = is_array($path) + ? $path + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { + throw new Nette\InvalidArgumentException("Invalid path '$path'."); + } + + $res = $parts[0] === '->' ? new \stdClass : []; + + foreach ($array as $rowOrig) { + $row = (array) $rowOrig; + $x = &$res; + + for ($i = 0; $i < count($parts); $i++) { + $part = $parts[$i]; + if ($part === '[]') { + $x = &$x[]; + + } elseif ($part === '=') { + if (isset($parts[++$i])) { + $x = $row[$parts[$i]]; + $row = null; + } + } elseif ($part === '->') { + if (isset($parts[++$i])) { + if ($x === null) { + $x = new \stdClass; + } + + $x = &$x->{$row[$parts[$i]]}; + } else { + $row = is_object($rowOrig) ? $rowOrig : (object) $row; + } + } elseif ($part !== '|') { + $x = &$x[(string) $row[$part]]; + } + } + + if ($x === null) { + $x = $row; + } + } + + return $res; + } + + + /** + * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. + */ + public static function normalize(array $array, mixed $filling = null): array + { + $res = []; + foreach ($array as $k => $v) { + $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; + } + + return $res; + } + + + /** + * Returns and removes the value of an item from an array. If it does not exist, it throws an exception, + * or returns $default, if provided. + * @template T + * @param array<T> $array + * @param ?T $default + * @return ?T + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function pick(array &$array, string|int $key, mixed $default = null): mixed + { + if (array_key_exists($key, $array)) { + $value = $array[$key]; + unset($array[$key]); + return $value; + + } elseif (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$key'."); + + } else { + return $default; + } + } + + + /** + * Tests whether at least one element in the array passes the test implemented by the + * provided callback with signature `function ($value, $key, array $array): bool`. + */ + public static function some(iterable $array, callable $callback): bool + { + foreach ($array as $k => $v) { + if ($callback($v, $k, $array)) { + return true; + } + } + + return false; + } + + + /** + * Tests whether all elements in the array pass the test implemented by the provided function, + * which has the signature `function ($value, $key, array $array): bool`. + */ + public static function every(iterable $array, callable $callback): bool + { + foreach ($array as $k => $v) { + if (!$callback($v, $k, $array)) { + return false; + } + } + + return true; + } + + + /** + * Calls $callback on all elements in the array and returns the array of return values. + * The callback has the signature `function ($value, $key, array $array): bool`. + */ + public static function map(iterable $array, callable $callback): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $callback($v, $k, $array); + } + + return $res; + } + + + /** + * Invokes all callbacks and returns array of results. + * @param callable[] $callbacks + */ + public static function invoke(iterable $callbacks, ...$args): array + { + $res = []; + foreach ($callbacks as $k => $cb) { + $res[$k] = $cb(...$args); + } + + return $res; + } + + + /** + * Invokes method on every object in an array and returns array of results. + * @param object[] $objects + */ + public static function invokeMethod(iterable $objects, string $method, ...$args): array + { + $res = []; + foreach ($objects as $k => $obj) { + $res[$k] = $obj->$method(...$args); + } + + return $res; + } + + + /** + * Copies the elements of the $array array to the $object object and then returns it. + * @template T of object + * @param T $object + * @return T + */ + public static function toObject(iterable $array, object $object): object + { + foreach ($array as $k => $v) { + $object->$k = $v; + } + + return $object; + } + + + /** + * Converts value to array key. + */ + public static function toKey(mixed $value): int|string + { + return key([$value => null]); + } + + + /** + * Returns copy of the $array where every item is converted to string + * and prefixed by $prefix and suffixed by $suffix. + * @param string[] $array + * @return string[] + */ + public static function wrap(array $array, string $prefix = '', string $suffix = ''): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $prefix . $v . $suffix; + } + + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Callback.php b/vendor/nette/utils/src/Utils/Callback.php new file mode 100644 index 0000000..496c1e7 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Callback.php @@ -0,0 +1,136 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; +use function is_array, is_object, is_string; + + +/** + * PHP callable tools. + */ +final class Callback +{ + use Nette\StaticClass; + + /** + * Invokes internal PHP function with own error handler. + */ + public static function invokeSafe(string $function, array $args, callable $onError): mixed + { + $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool { + if ($file === __FILE__) { + $msg = ini_get('html_errors') + ? Html::htmlToText($message) + : $message; + $msg = preg_replace("#^$function\\(.*?\\): #", '', $msg); + if ($onError($msg, $severity) !== false) { + return null; + } + } + + return $prev ? $prev(...func_get_args()) : false; + }); + + try { + return $function(...$args); + } finally { + restore_error_handler(); + } + } + + + /** + * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies + * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists. + * @return callable + * @throws Nette\InvalidArgumentException + */ + public static function check(mixed $callable, bool $syntax = false) + { + if (!is_callable($callable, $syntax)) { + throw new Nette\InvalidArgumentException( + $syntax + ? 'Given value is not a callable type.' + : sprintf("Callback '%s' is not callable.", self::toString($callable)), + ); + } + + return $callable; + } + + + /** + * Converts PHP callback to textual form. Class or method may not exists. + */ + public static function toString(mixed $callable): string + { + if ($callable instanceof \Closure) { + $inner = self::unwrap($callable); + return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}'); + } else { + is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual); + return $textual; + } + } + + + /** + * Returns reflection for method or function used in PHP callback. + * @param callable $callable type check is escalated to ReflectionException + * @throws \ReflectionException if callback is not valid + */ + public static function toReflection($callable): \ReflectionMethod|\ReflectionFunction + { + if ($callable instanceof \Closure) { + $callable = self::unwrap($callable); + } + + if (is_string($callable) && str_contains($callable, '::')) { + return new \ReflectionMethod($callable); + } elseif (is_array($callable)) { + return new \ReflectionMethod($callable[0], $callable[1]); + } elseif (is_object($callable) && !$callable instanceof \Closure) { + return new \ReflectionMethod($callable, '__invoke'); + } else { + return new \ReflectionFunction($callable); + } + } + + + /** + * Checks whether PHP callback is function or static method. + */ + public static function isStatic(callable $callable): bool + { + return is_string(is_array($callable) ? $callable[0] : $callable); + } + + + /** + * Unwraps closure created by Closure::fromCallable(). + */ + public static function unwrap(\Closure $closure): callable|array + { + $r = new \ReflectionFunction($closure); + if (str_ends_with($r->name, '}')) { + return $closure; + + } elseif ($obj = $r->getClosureThis()) { + return [$obj, $r->name]; + + } elseif ($class = $r->getClosureScopeClass()) { + return [$class->name, $r->name]; + + } else { + return $r->name; + } + } +} diff --git a/vendor/nette/utils/src/Utils/DateTime.php b/vendor/nette/utils/src/Utils/DateTime.php new file mode 100644 index 0000000..963407c --- /dev/null +++ b/vendor/nette/utils/src/Utils/DateTime.php @@ -0,0 +1,140 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * DateTime. + */ +class DateTime extends \DateTime implements \JsonSerializable +{ + use Nette\SmartObject; + + /** minute in seconds */ + public const MINUTE = 60; + + /** hour in seconds */ + public const HOUR = 60 * self::MINUTE; + + /** day in seconds */ + public const DAY = 24 * self::HOUR; + + /** week in seconds */ + public const WEEK = 7 * self::DAY; + + /** average month in seconds */ + public const MONTH = 2_629_800; + + /** average year in seconds */ + public const YEAR = 31_557_600; + + + /** + * Creates a DateTime object from a string, UNIX timestamp, or other DateTimeInterface object. + * @throws \Exception if the date and time are not valid. + */ + public static function from(string|int|\DateTimeInterface|null $time): static + { + if ($time instanceof \DateTimeInterface) { + return new static($time->format('Y-m-d H:i:s.u'), $time->getTimezone()); + + } elseif (is_numeric($time)) { + if ($time <= self::YEAR) { + $time += time(); + } + + return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get())); + + } else { // textual or null + return new static((string) $time); + } + } + + + /** + * Creates DateTime object. + * @throws Nette\InvalidArgumentException if the date and time are not valid. + */ + public static function fromParts( + int $year, + int $month, + int $day, + int $hour = 0, + int $minute = 0, + float $second = 0.0, + ): static + { + $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5F', $year, $month, $day, $hour, $minute, $second); + if ( + !checkdate($month, $day, $year) + || $hour < 0 + || $hour > 23 + || $minute < 0 + || $minute > 59 + || $second < 0 + || $second >= 60 + ) { + throw new Nette\InvalidArgumentException("Invalid date '$s'"); + } + + return new static($s); + } + + + /** + * Returns new DateTime object formatted according to the specified format. + */ + public static function createFromFormat( + string $format, + string $time, + string|\DateTimeZone|null $timezone = null, + ): static|false + { + if ($timezone === null) { + $timezone = new \DateTimeZone(date_default_timezone_get()); + + } elseif (is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + } + + $date = parent::createFromFormat($format, $time, $timezone); + return $date ? static::from($date) : false; + } + + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + */ + public function jsonSerialize(): string + { + return $this->format('c'); + } + + + /** + * Returns the date and time in the format 'Y-m-d H:i:s'. + */ + public function __toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + + /** + * Creates a copy with a modified time. + */ + public function modifyClone(string $modify = ''): static + { + $dolly = clone $this; + return $modify ? $dolly->modify($modify) : $dolly; + } +} diff --git a/vendor/nette/utils/src/Utils/FileInfo.php b/vendor/nette/utils/src/Utils/FileInfo.php new file mode 100644 index 0000000..fb92d11 --- /dev/null +++ b/vendor/nette/utils/src/Utils/FileInfo.php @@ -0,0 +1,69 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Represents the file or directory returned by the Finder. + * @internal do not create instances directly + */ +final class FileInfo extends \SplFileInfo +{ + private string $relativePath; + + + public function __construct(string $file, string $relativePath = '') + { + parent::__construct($file); + $this->setInfoClass(static::class); + $this->relativePath = $relativePath; + } + + + /** + * Returns the relative directory path. + */ + public function getRelativePath(): string + { + return $this->relativePath; + } + + + /** + * Returns the relative path including file name. + */ + public function getRelativePathname(): string + { + return ($this->relativePath === '' ? '' : $this->relativePath . DIRECTORY_SEPARATOR) + . $this->getBasename(); + } + + + /** + * Returns the contents of the file. + * @throws Nette\IOException + */ + public function read(): string + { + return FileSystem::read($this->getPathname()); + } + + + /** + * Writes the contents to the file. + * @throws Nette\IOException + */ + public function write(string $content): void + { + FileSystem::write($this->getPathname(), $content); + } +} diff --git a/vendor/nette/utils/src/Utils/FileSystem.php b/vendor/nette/utils/src/Utils/FileSystem.php new file mode 100644 index 0000000..a51539b --- /dev/null +++ b/vendor/nette/utils/src/Utils/FileSystem.php @@ -0,0 +1,328 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * File system tool. + */ +final class FileSystem +{ + use Nette\StaticClass; + + /** + * Creates a directory if it does not exist, including parent directories. + * @throws Nette\IOException on error occurred + */ + public static function createDir(string $dir, int $mode = 0777): void + { + if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist + throw new Nette\IOException(sprintf( + "Unable to create directory '%s' with mode %s. %s", + self::normalizePath($dir), + decoct($mode), + Helpers::getLastError(), + )); + } + } + + + /** + * Copies a file or an entire directory. Overwrites existing files and directories by default. + * @throws Nette\IOException on error occurred + * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists + */ + public static function copy(string $origin, string $target, bool $overwrite = true): void + { + if (stream_is_local($origin) && !file_exists($origin)) { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin))); + + } elseif (!$overwrite && file_exists($target)) { + throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target))); + + } elseif (is_dir($origin)) { + static::createDir($target); + foreach (new \FilesystemIterator($target) as $item) { + static::delete($item->getPathname()); + } + + foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { + if ($item->isDir()) { + static::createDir($target . '/' . $iterator->getSubPathName()); + } else { + static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName()); + } + } + } else { + static::createDir(dirname($target)); + if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to copy file '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Opens file and returns resource. + * @return resource + * @throws Nette\IOException on error occurred + */ + public static function open(string $path, string $mode) + { + $f = @fopen($path, $mode); // @ is escalated to exception + if (!$f) { + throw new Nette\IOException(sprintf( + "Unable to open file '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + return $f; + } + + + /** + * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first. + * @throws Nette\IOException on error occurred + */ + public static function delete(string $path): void + { + if (is_file($path) || is_link($path)) { + $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; + if (!@$func($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::delete($item->getPathname()); + } + + if (!@rmdir($path)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to delete directory '%s'. %s", + self::normalizePath($path), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Renames or moves a file or a directory. Overwrites existing files and directories by default. + * @throws Nette\IOException on error occurred + * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists + */ + public static function rename(string $origin, string $target, bool $overwrite = true): void + { + if (!$overwrite && file_exists($target)) { + throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target))); + + } elseif (!file_exists($origin)) { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin))); + + } else { + static::createDir(dirname($target)); + if (realpath($origin) !== realpath($target)) { + static::delete($target); + } + + if (!@rename($origin, $target)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to rename file or directory '%s' to '%s'. %s", + self::normalizePath($origin), + self::normalizePath($target), + Helpers::getLastError(), + )); + } + } + } + + + /** + * Reads the content of a file. + * @throws Nette\IOException on error occurred + */ + public static function read(string $file): string + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === false) { + throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + return $content; + } + + + /** + * Reads the file content line by line. Because it reads continuously as we iterate over the lines, + * it is possible to read files larger than the available memory. + * @return \Generator<int, string> + * @throws Nette\IOException on error occurred + */ + public static function readLines(string $file, bool $stripNewLines = true): \Generator + { + return (function ($f) use ($file, $stripNewLines) { + $counter = 0; + do { + $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf( + "Unable to read file '%s'. %s", + self::normalizePath($file), + $error, + ))); + if ($line === false) { + fclose($f); + break; + } + if ($stripNewLines) { + $line = rtrim($line, "\r\n"); + } + + yield $counter++ => $line; + + } while (true); + })(static::open($file, 'r')); + } + + + /** + * Writes the string to a file. + * @throws Nette\IOException on error occurred + */ + public static function write(string $file, string $content, ?int $mode = 0666): void + { + static::createDir(dirname($file)); + if (@file_put_contents($file, $content) === false) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to write file '%s'. %s", + self::normalizePath($file), + Helpers::getLastError(), + )); + } + + if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($file), + decoct($mode), + Helpers::getLastError(), + )); + } + } + + + /** + * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`. + * Recursively traverses and sets permissions on the entire contents of the directory as well. + * @throws Nette\IOException on error occurred + */ + public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void + { + if (is_file($path)) { + if (!@chmod($path, $fileMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod file '%s' to mode %s. %s", + self::normalizePath($path), + decoct($fileMode), + Helpers::getLastError(), + )); + } + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::makeWritable($item->getPathname(), $dirMode, $fileMode); + } + + if (!@chmod($path, $dirMode)) { // @ is escalated to exception + throw new Nette\IOException(sprintf( + "Unable to chmod directory '%s' to mode %s. %s", + self::normalizePath($path), + decoct($dirMode), + Helpers::getLastError(), + )); + } + } else { + throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path))); + } + } + + + /** + * Determines if the path is absolute. + */ + public static function isAbsolute(string $path): bool + { + return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); + } + + + /** + * Normalizes `..` and `.` and directory separators in path. + */ + public static function normalizePath(string $path): string + { + $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path); + $res = []; + foreach ($parts as $part) { + if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') { + array_pop($res); + } elseif ($part !== '.') { + $res[] = $part; + } + } + + return $res === [''] + ? DIRECTORY_SEPARATOR + : implode(DIRECTORY_SEPARATOR, $res); + } + + + /** + * Joins all segments of the path and normalizes the result. + */ + public static function joinPaths(string ...$paths): string + { + return self::normalizePath(implode('/', $paths)); + } + + + /** + * Converts backslashes to slashes. + */ + public static function unixSlashes(string $path): string + { + return strtr($path, '\\', '/'); + } + + + /** + * Converts slashes to platform-specific directory separators. + */ + public static function platformSlashes(string $path): string + { + return DIRECTORY_SEPARATOR === '/' + ? strtr($path, '\\', '/') + : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol:// + } +} diff --git a/vendor/nette/utils/src/Utils/Finder.php b/vendor/nette/utils/src/Utils/Finder.php new file mode 100644 index 0000000..491f690 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Finder.php @@ -0,0 +1,505 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Finder allows searching through directory trees using iterator. + * + * Finder::findFiles('*.php') + * ->size('> 10kB') + * ->from('.') + * ->exclude('temp'); + * + * @implements \IteratorAggregate<string, FileInfo> + */ +class Finder implements \IteratorAggregate +{ + use Nette\SmartObject; + + /** @var array<array{string, string}> */ + private array $find = []; + + /** @var string[] */ + private array $in = []; + + /** @var \Closure[] */ + private array $filters = []; + + /** @var \Closure[] */ + private array $descentFilters = []; + + /** @var array<string|self> */ + private array $appends = []; + private bool $childFirst = false; + + /** @var ?callable */ + private $sort; + private int $maxDepth = -1; + private bool $ignoreUnreadableDirs = true; + + + /** + * Begins search for files and directories matching mask. + */ + public static function find(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir')->addMask($masks, 'file'); + } + + + /** + * Begins search for files matching mask. + */ + public static function findFiles(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'file'); + } + + + /** + * Begins search for directories matching mask. + */ + public static function findDirectories(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + return (new static)->addMask($masks, 'dir'); + } + + + /** + * Finds files matching the specified masks. + */ + public function files(string|array $masks): static + { + return $this->addMask((array) $masks, 'file'); + } + + + /** + * Finds directories matching the specified masks. + */ + public function directories(string|array $masks): static + { + return $this->addMask((array) $masks, 'dir'); + } + + + private function addMask(array $masks, string $mode): static + { + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if ($mode === 'dir') { + $mask = rtrim($mask, '/'); + } + if ($mask === '' || ($mode === 'file' && str_ends_with($mask, '/'))) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + if (str_starts_with($mask, '**/')) { + $mask = substr($mask, 3); + } + $this->find[] = [$mask, $mode]; + } + return $this; + } + + + /** + * Searches in the given directories. Wildcards are allowed. + */ + public function in(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, ''); + return $this; + } + + + /** + * Searches recursively from the given directories. Wildcards are allowed. + */ + public function from(string|array $paths): static + { + $paths = is_array($paths) ? $paths : func_get_args(); // compatibility with variadic + $this->addLocation($paths, '/**'); + return $this; + } + + + private function addLocation(array $paths, string $ext): void + { + foreach ($paths as $path) { + if ($path === '') { + throw new Nette\InvalidArgumentException("Invalid directory '$path'"); + } + $path = rtrim(FileSystem::unixSlashes($path), '/'); + $this->in[] = $path . $ext; + } + } + + + /** + * Lists directory's contents before the directory itself. By default, this is disabled. + */ + public function childFirst(bool $state = true): static + { + $this->childFirst = $state; + return $this; + } + + + /** + * Ignores unreadable directories. By default, this is enabled. + */ + public function ignoreUnreadableDirs(bool $state = true): static + { + $this->ignoreUnreadableDirs = $state; + return $this; + } + + + /** + * Set a compare function for sorting directory entries. The function will be called to sort entries from the same directory. + * @param callable(FileInfo, FileInfo): int $callback + */ + public function sortBy(callable $callback): static + { + $this->sort = $callback; + return $this; + } + + + /** + * Sorts files in each directory naturally by name. + */ + public function sortByName(): static + { + $this->sort = fn(FileInfo $a, FileInfo $b): int => strnatcmp($a->getBasename(), $b->getBasename()); + return $this; + } + + + /** + * Adds the specified paths or appends a new finder that returns. + */ + public function append(string|array|null $paths = null): static + { + if ($paths === null) { + return $this->appends[] = new static; + } + + $this->appends = array_merge($this->appends, (array) $paths); + return $this; + } + + + /********************* filtering ****************d*g**/ + + + /** + * Skips entries that matches the given masks relative to the ones defined with the in() or from() methods. + */ + public function exclude(string|array $masks): static + { + $masks = is_array($masks) ? $masks : func_get_args(); // compatibility with variadic + foreach ($masks as $mask) { + $mask = FileSystem::unixSlashes($mask); + if (!preg_match('~^/?(\*\*/)?(.+)(/\*\*|/\*|/|)$~D', $mask, $m)) { + throw new Nette\InvalidArgumentException("Invalid mask '$mask'"); + } + $end = $m[3]; + $re = $this->buildPattern($m[2]); + $filter = fn(FileInfo $file): bool => ($end && !$file->isDir()) + || !preg_match($re, FileSystem::unixSlashes($file->getRelativePathname())); + + $this->descentFilter($filter); + if ($end !== '/*') { + $this->filter($filter); + } + } + + return $this; + } + + + /** + * Yields only entries which satisfy the given filter. + * @param callable(FileInfo): bool $callback + */ + public function filter(callable $callback): static + { + $this->filters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * It descends only to directories that match the specified filter. + * @param callable(FileInfo): bool $callback + */ + public function descentFilter(callable $callback): static + { + $this->descentFilters[] = \Closure::fromCallable($callback); + return $this; + } + + + /** + * Sets the maximum depth of entries. + */ + public function limitDepth(?int $depth): static + { + $this->maxDepth = $depth ?? -1; + return $this; + } + + + /** + * Restricts the search by size. $operator accepts "[operator] [size] [unit]" example: >=10kB + */ + public function size(string $operator, ?int $size = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?((?:\d*\.)?\d+)\s*(K|M|G|)B?$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid size predicate format.'); + } + + [, $operator, $size, $unit] = $matches; + $units = ['' => 1, 'k' => 1e3, 'm' => 1e6, 'g' => 1e9]; + $size *= $units[strtolower($unit)]; + $operator = $operator ?: '='; + } + + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getSize(), $operator, $size)); + } + + + /** + * Restricts the search by modified time. $operator accepts "[operator] [date]" example: >1978-01-23 + */ + public function date(string $operator, string|int|\DateTimeInterface|null $date = null): static + { + if (func_num_args() === 1) { // in $operator is predicate + if (!preg_match('#^(?:([=<>!]=?|<>)\s*)?(.+)$#Di', $operator, $matches)) { + throw new Nette\InvalidArgumentException('Invalid date predicate format.'); + } + + [, $operator, $date] = $matches; + $operator = $operator ?: '='; + } + + $date = DateTime::from($date)->format('U'); + return $this->filter(fn(FileInfo $file): bool => !$file->isFile() || Helpers::compare($file->getMTime(), $operator, $date)); + } + + + /********************* iterator generator ****************d*g**/ + + + /** + * Returns an array with all found files and directories. + */ + public function collect(): array + { + return iterator_to_array($this->getIterator()); + } + + + /** @return \Generator<string, FileInfo> */ + public function getIterator(): \Generator + { + $plan = $this->buildPlan(); + foreach ($plan as $dir => $searches) { + yield from $this->traverseDir($dir, $searches); + } + + foreach ($this->appends as $item) { + if ($item instanceof self) { + yield from $item->getIterator(); + } else { + $item = FileSystem::platformSlashes($item); + yield $item => new FileInfo($item); + } + } + } + + + /** + * @param array<\stdClass{pattern: string, mode: string, recursive: bool}> $searches + * @param string[] $subdirs + * @return \Generator<string, FileInfo> + */ + private function traverseDir(string $dir, array $searches, array $subdirs = []): \Generator + { + if ($this->maxDepth >= 0 && count($subdirs) > $this->maxDepth) { + return; + } elseif (!is_dir($dir)) { + throw new Nette\InvalidStateException("Directory '$dir' not found."); + } + + try { + $pathNames = new \FilesystemIterator($dir, \FilesystemIterator::FOLLOW_SYMLINKS | \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::CURRENT_AS_PATHNAME | \FilesystemIterator::UNIX_PATHS); + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + return; + } else { + throw new Nette\InvalidStateException($e->getMessage()); + } + } + + $files = $this->convertToFiles($pathNames, implode('/', $subdirs), FileSystem::isAbsolute($dir)); + + if ($this->sort) { + $files = iterator_to_array($files); + usort($files, $this->sort); + } + + foreach ($files as $file) { + $pathName = $file->getPathname(); + $cache = $subSearch = []; + + if ($file->isDir()) { + foreach ($searches as $search) { + if ($search->recursive && $this->proveFilters($this->descentFilters, $file, $cache)) { + $subSearch[] = $search; + } + } + } + + if ($this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + + $relativePathname = FileSystem::unixSlashes($file->getRelativePathname()); + foreach ($searches as $search) { + if ( + $file->getType() === $search->mode + && preg_match($search->pattern, $relativePathname) + && $this->proveFilters($this->filters, $file, $cache) + ) { + yield $pathName => $file; + break; + } + } + + if (!$this->childFirst && $subSearch) { + yield from $this->traverseDir($pathName, $subSearch, array_merge($subdirs, [$file->getBasename()])); + } + } + } + + + private function convertToFiles(iterable $pathNames, string $relativePath, bool $absolute): \Generator + { + foreach ($pathNames as $pathName) { + if (!$absolute) { + $pathName = preg_replace('~\.?/~A', '', $pathName); + } + $pathName = FileSystem::platformSlashes($pathName); + yield new FileInfo($pathName, $relativePath); + } + } + + + private function proveFilters(array $filters, FileInfo $file, array &$cache): bool + { + foreach ($filters as $filter) { + $res = &$cache[spl_object_id($filter)]; + $res ??= $filter($file); + if (!$res) { + return false; + } + } + + return true; + } + + + /** @return array<string, array<\stdClass{pattern: string, mode: string, recursive: bool}>> */ + private function buildPlan(): array + { + $plan = $dirCache = []; + foreach ($this->find as [$mask, $mode]) { + $splits = []; + if (FileSystem::isAbsolute($mask)) { + if ($this->in) { + throw new Nette\InvalidStateException("You cannot combine the absolute path in the mask '$mask' and the directory to search '{$this->in[0]}'."); + } + $splits[] = self::splitRecursivePart($mask); + } else { + foreach ($this->in ?: ['.'] as $in) { + $in = strtr($in, ['[' => '[[]', ']' => '[]]']); // in path, do not treat [ and ] as a pattern by glob() + $splits[] = self::splitRecursivePart($in . '/' . $mask); + } + } + + foreach ($splits as [$base, $rest, $recursive]) { + $base = $base === '' ? '.' : $base; + $dirs = $dirCache[$base] ??= strpbrk($base, '*?[') + ? glob($base, GLOB_NOSORT | GLOB_ONLYDIR | GLOB_NOESCAPE) + : [strtr($base, ['[[]' => '[', '[]]' => ']'])]; // unescape [ and ] + + $search = (object) ['pattern' => $this->buildPattern($rest), 'mode' => $mode, 'recursive' => $recursive]; + foreach ($dirs as $dir) { + $plan[$dir][] = $search; + } + } + } + + return $plan; + } + + + /** + * Since glob() does not know ** wildcard, we divide the path into a part for glob and a part for manual traversal. + */ + private static function splitRecursivePart(string $path): array + { + $a = strrpos($path, '/'); + $parts = preg_split('~(?<=^|/)\*\*($|/)~', substr($path, 0, $a + 1), 2); + return isset($parts[1]) + ? [$parts[0], $parts[1] . substr($path, $a + 1), true] + : [$parts[0], substr($path, $a + 1), false]; + } + + + /** + * Converts wildcards to regular expression. + */ + private function buildPattern(string $mask): string + { + if ($mask === '*') { + return '##'; + } elseif (str_starts_with($mask, './')) { + $anchor = '^'; + $mask = substr($mask, 2); + } else { + $anchor = '(?:^|/)'; + } + + $pattern = strtr( + preg_quote($mask, '#'), + [ + '\*\*/' => '(.+/)?', + '\*' => '[^/]*', + '\?' => '[^/]', + '\[\!' => '[^', + '\[' => '[', + '\]' => ']', + '\-' => '-', + ], + ); + return '#' . $anchor . $pattern . '$#D' . (defined('PHP_WINDOWS_VERSION_BUILD') ? 'i' : ''); + } +} diff --git a/vendor/nette/utils/src/Utils/Floats.php b/vendor/nette/utils/src/Utils/Floats.php new file mode 100644 index 0000000..cc2781d --- /dev/null +++ b/vendor/nette/utils/src/Utils/Floats.php @@ -0,0 +1,107 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Floating-point numbers comparison. + */ +class Floats +{ + use Nette\StaticClass; + + private const Epsilon = 1e-10; + + + public static function isZero(float $value): bool + { + return abs($value) < self::Epsilon; + } + + + public static function isInteger(float $value): bool + { + return abs(round($value) - $value) < self::Epsilon; + } + + + /** + * Compare two floats. If $a < $b it returns -1, if they are equal it returns 0 and if $a > $b it returns 1 + * @throws \LogicException if one of parameters is NAN + */ + public static function compare(float $a, float $b): int + { + if (is_nan($a) || is_nan($b)) { + throw new \LogicException('Trying to compare NAN'); + + } elseif (!is_finite($a) && !is_finite($b) && $a === $b) { + return 0; + } + + $diff = abs($a - $b); + if (($diff < self::Epsilon || ($diff / max(abs($a), abs($b)) < self::Epsilon))) { + return 0; + } + + return $a < $b ? -1 : 1; + } + + + /** + * Returns true if $a = $b + * @throws \LogicException if one of parameters is NAN + */ + public static function areEqual(float $a, float $b): bool + { + return self::compare($a, $b) === 0; + } + + + /** + * Returns true if $a < $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThan(float $a, float $b): bool + { + return self::compare($a, $b) < 0; + } + + + /** + * Returns true if $a <= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isLessThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) <= 0; + } + + + /** + * Returns true if $a > $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThan(float $a, float $b): bool + { + return self::compare($a, $b) > 0; + } + + + /** + * Returns true if $a >= $b + * @throws \LogicException if one of parameters is NAN + */ + public static function isGreaterThanOrEqualTo(float $a, float $b): bool + { + return self::compare($a, $b) >= 0; + } +} diff --git a/vendor/nette/utils/src/Utils/Helpers.php b/vendor/nette/utils/src/Utils/Helpers.php new file mode 100644 index 0000000..b3586c1 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Helpers.php @@ -0,0 +1,104 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +class Helpers +{ + /** + * Executes a callback and returns the captured output as a string. + */ + public static function capture(callable $func): string + { + ob_start(function () {}); + try { + $func(); + return ob_get_clean(); + } catch (\Throwable $e) { + ob_end_clean(); + throw $e; + } + } + + + /** + * Returns the last occurred PHP error or an empty string if no error occurred. Unlike error_get_last(), + * it is nit affected by the PHP directive html_errors and always returns text, not HTML. + */ + public static function getLastError(): string + { + $message = error_get_last()['message'] ?? ''; + $message = ini_get('html_errors') ? Html::htmlToText($message) : $message; + $message = preg_replace('#^\w+\(.*?\): #', '', $message); + return $message; + } + + + /** + * Converts false to null, does not change other values. + */ + public static function falseToNull(mixed $value): mixed + { + return $value === false ? null : $value; + } + + + /** + * Returns value clamped to the inclusive range of min and max. + */ + public static function clamp(int|float $value, int|float $min, int|float $max): int|float + { + if ($min > $max) { + throw new Nette\InvalidArgumentException("Minimum ($min) is not less than maximum ($max)."); + } + + return min(max($value, $min), $max); + } + + + /** + * Looks for a string from possibilities that is most similar to value, but not the same (for 8-bit encoding). + * @param string[] $possibilities + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities) as $item) { + if ($item !== $value && ($len = levenshtein($item, $value, 10, 11, 10)) < $min) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + /** + * Compares two values in the same way that PHP does. Recognizes operators: >, >=, <, <=, =, ==, ===, !=, !==, <> + */ + public static function compare(mixed $left, string $operator, mixed $right): bool + { + return match ($operator) { + '>' => $left > $right, + '>=' => $left >= $right, + '<' => $left < $right, + '<=' => $left <= $right, + '=', '==' => $left == $right, + '===' => $left === $right, + '!=', '<>' => $left != $right, + '!==' => $left !== $right, + default => throw new Nette\InvalidArgumentException("Unknown operator '$operator'"), + }; + } +} diff --git a/vendor/nette/utils/src/Utils/Html.php b/vendor/nette/utils/src/Utils/Html.php new file mode 100644 index 0000000..7e401b8 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Html.php @@ -0,0 +1,839 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; +use Nette\HtmlStringable; +use function is_array, is_float, is_object, is_string; + + +/** + * HTML helper. + * + * @property string|null $accept + * @property string|null $accesskey + * @property string|null $action + * @property string|null $align + * @property string|null $allow + * @property string|null $alt + * @property bool|null $async + * @property string|null $autocapitalize + * @property string|null $autocomplete + * @property bool|null $autofocus + * @property bool|null $autoplay + * @property string|null $charset + * @property bool|null $checked + * @property string|null $cite + * @property string|null $class + * @property int|null $cols + * @property int|null $colspan + * @property string|null $content + * @property bool|null $contenteditable + * @property bool|null $controls + * @property string|null $coords + * @property string|null $crossorigin + * @property string|null $data + * @property string|null $datetime + * @property string|null $decoding + * @property bool|null $default + * @property bool|null $defer + * @property string|null $dir + * @property string|null $dirname + * @property bool|null $disabled + * @property bool|null $download + * @property string|null $draggable + * @property string|null $dropzone + * @property string|null $enctype + * @property string|null $for + * @property string|null $form + * @property string|null $formaction + * @property string|null $formenctype + * @property string|null $formmethod + * @property bool|null $formnovalidate + * @property string|null $formtarget + * @property string|null $headers + * @property int|null $height + * @property bool|null $hidden + * @property float|null $high + * @property string|null $href + * @property string|null $hreflang + * @property string|null $id + * @property string|null $integrity + * @property string|null $inputmode + * @property bool|null $ismap + * @property string|null $itemprop + * @property string|null $kind + * @property string|null $label + * @property string|null $lang + * @property string|null $list + * @property bool|null $loop + * @property float|null $low + * @property float|null $max + * @property int|null $maxlength + * @property int|null $minlength + * @property string|null $media + * @property string|null $method + * @property float|null $min + * @property bool|null $multiple + * @property bool|null $muted + * @property string|null $name + * @property bool|null $novalidate + * @property bool|null $open + * @property float|null $optimum + * @property string|null $pattern + * @property string|null $ping + * @property string|null $placeholder + * @property string|null $poster + * @property string|null $preload + * @property string|null $radiogroup + * @property bool|null $readonly + * @property string|null $rel + * @property bool|null $required + * @property bool|null $reversed + * @property int|null $rows + * @property int|null $rowspan + * @property string|null $sandbox + * @property string|null $scope + * @property bool|null $selected + * @property string|null $shape + * @property int|null $size + * @property string|null $sizes + * @property string|null $slot + * @property int|null $span + * @property string|null $spellcheck + * @property string|null $src + * @property string|null $srcdoc + * @property string|null $srclang + * @property string|null $srcset + * @property int|null $start + * @property float|null $step + * @property string|null $style + * @property int|null $tabindex + * @property string|null $target + * @property string|null $title + * @property string|null $translate + * @property string|null $type + * @property string|null $usemap + * @property string|null $value + * @property int|null $width + * @property string|null $wrap + * + * @method self accept(?string $val) + * @method self accesskey(?string $val, bool $state = null) + * @method self action(?string $val) + * @method self align(?string $val) + * @method self allow(?string $val, bool $state = null) + * @method self alt(?string $val) + * @method self async(?bool $val) + * @method self autocapitalize(?string $val) + * @method self autocomplete(?string $val) + * @method self autofocus(?bool $val) + * @method self autoplay(?bool $val) + * @method self charset(?string $val) + * @method self checked(?bool $val) + * @method self cite(?string $val) + * @method self class(?string $val, bool $state = null) + * @method self cols(?int $val) + * @method self colspan(?int $val) + * @method self content(?string $val) + * @method self contenteditable(?bool $val) + * @method self controls(?bool $val) + * @method self coords(?string $val) + * @method self crossorigin(?string $val) + * @method self datetime(?string $val) + * @method self decoding(?string $val) + * @method self default(?bool $val) + * @method self defer(?bool $val) + * @method self dir(?string $val) + * @method self dirname(?string $val) + * @method self disabled(?bool $val) + * @method self download(?bool $val) + * @method self draggable(?string $val) + * @method self dropzone(?string $val) + * @method self enctype(?string $val) + * @method self for(?string $val) + * @method self form(?string $val) + * @method self formaction(?string $val) + * @method self formenctype(?string $val) + * @method self formmethod(?string $val) + * @method self formnovalidate(?bool $val) + * @method self formtarget(?string $val) + * @method self headers(?string $val, bool $state = null) + * @method self height(?int $val) + * @method self hidden(?bool $val) + * @method self high(?float $val) + * @method self hreflang(?string $val) + * @method self id(?string $val) + * @method self integrity(?string $val) + * @method self inputmode(?string $val) + * @method self ismap(?bool $val) + * @method self itemprop(?string $val) + * @method self kind(?string $val) + * @method self label(?string $val) + * @method self lang(?string $val) + * @method self list(?string $val) + * @method self loop(?bool $val) + * @method self low(?float $val) + * @method self max(?float $val) + * @method self maxlength(?int $val) + * @method self minlength(?int $val) + * @method self media(?string $val) + * @method self method(?string $val) + * @method self min(?float $val) + * @method self multiple(?bool $val) + * @method self muted(?bool $val) + * @method self name(?string $val) + * @method self novalidate(?bool $val) + * @method self open(?bool $val) + * @method self optimum(?float $val) + * @method self pattern(?string $val) + * @method self ping(?string $val, bool $state = null) + * @method self placeholder(?string $val) + * @method self poster(?string $val) + * @method self preload(?string $val) + * @method self radiogroup(?string $val) + * @method self readonly(?bool $val) + * @method self rel(?string $val) + * @method self required(?bool $val) + * @method self reversed(?bool $val) + * @method self rows(?int $val) + * @method self rowspan(?int $val) + * @method self sandbox(?string $val, bool $state = null) + * @method self scope(?string $val) + * @method self selected(?bool $val) + * @method self shape(?string $val) + * @method self size(?int $val) + * @method self sizes(?string $val) + * @method self slot(?string $val) + * @method self span(?int $val) + * @method self spellcheck(?string $val) + * @method self src(?string $val) + * @method self srcdoc(?string $val) + * @method self srclang(?string $val) + * @method self srcset(?string $val) + * @method self start(?int $val) + * @method self step(?float $val) + * @method self style(?string $property, string $val = null) + * @method self tabindex(?int $val) + * @method self target(?string $val) + * @method self title(?string $val) + * @method self translate(?string $val) + * @method self type(?string $val) + * @method self usemap(?string $val) + * @method self value(?string $val) + * @method self width(?int $val) + * @method self wrap(?string $val) + */ +class Html implements \ArrayAccess, \Countable, \IteratorAggregate, HtmlStringable +{ + use Nette\SmartObject; + + /** @var array<string, mixed> element's attributes */ + public $attrs = []; + + /** void elements */ + public static $emptyElements = [ + 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, + 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, + 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, + ]; + + /** @var array<int, HtmlStringable|string> nodes */ + protected $children = []; + + /** element's name */ + private string $name = ''; + + private bool $isEmpty = false; + + + /** + * Constructs new HTML element. + * @param array|string $attrs element's attributes or plain text content + */ + public static function el(?string $name = null, array|string|null $attrs = null): static + { + $el = new static; + $parts = explode(' ', (string) $name, 2); + $el->setName($parts[0]); + + if (is_array($attrs)) { + $el->attrs = $attrs; + + } elseif ($attrs !== null) { + $el->setText($attrs); + } + + if (isset($parts[1])) { + foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { + $el->attrs[$m[1]] = $m[3] ?? true; + } + } + + return $el; + } + + + /** + * Returns an object representing HTML text. + */ + public static function fromHtml(string $html): static + { + return (new static)->setHtml($html); + } + + + /** + * Returns an object representing plain text. + */ + public static function fromText(string $text): static + { + return (new static)->setText($text); + } + + + /** + * Converts to HTML. + */ + final public function toHtml(): string + { + return $this->render(); + } + + + /** + * Converts to plain text. + */ + final public function toText(): string + { + return $this->getText(); + } + + + /** + * Converts given HTML code to plain text. + */ + public static function htmlToText(string $html): string + { + return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + + /** + * Changes element's name. + */ + final public function setName(string $name, ?bool $isEmpty = null): static + { + $this->name = $name; + $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); + return $this; + } + + + /** + * Returns element's name. + */ + final public function getName(): string + { + return $this->name; + } + + + /** + * Is element empty? + */ + final public function isEmpty(): bool + { + return $this->isEmpty; + } + + + /** + * Sets multiple attributes. + */ + public function addAttributes(array $attrs): static + { + $this->attrs = array_merge($this->attrs, $attrs); + return $this; + } + + + /** + * Appends value to element's attribute. + */ + public function appendAttribute(string $name, mixed $value, mixed $option = true): static + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; + } + + return $this; + } + + + /** + * Sets element's attribute. + */ + public function setAttribute(string $name, mixed $value): static + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + */ + public function getAttribute(string $name): mixed + { + return $this->attrs[$name] ?? null; + } + + + /** + * Unsets element's attribute. + */ + public function removeAttribute(string $name): static + { + unset($this->attrs[$name]); + return $this; + } + + + /** + * Unsets element's attributes. + */ + public function removeAttributes(array $attributes): static + { + foreach ($attributes as $name) { + unset($this->attrs[$name]); + } + + return $this; + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __set(string $name, mixed $value): void + { + $this->attrs[$name] = $value; + } + + + /** + * Overloaded getter for element's attribute. + */ + final public function &__get(string $name): mixed + { + return $this->attrs[$name]; + } + + + /** + * Overloaded tester for element's attribute. + */ + final public function __isset(string $name): bool + { + return isset($this->attrs[$name]); + } + + + /** + * Overloaded unsetter for element's attribute. + */ + final public function __unset(string $name): void + { + unset($this->attrs[$name]); + } + + + /** + * Overloaded setter for element's attribute. + */ + final public function __call(string $m, array $args): mixed + { + $p = substr($m, 0, 3); + if ($p === 'get' || $p === 'set' || $p === 'add') { + $m = substr($m, 3); + $m[0] = $m[0] | "\x20"; + if ($p === 'get') { + return $this->attrs[$m] ?? null; + + } elseif ($p === 'add') { + $args[] = true; + } + } + + if (count($args) === 0) { // invalid + + } elseif (count($args) === 1) { // set + $this->attrs[$m] = $args[0]; + + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); + } + + return $this; + } + + + /** + * Special setter for element's attribute. + */ + final public function href(string $path, array $query = []): static + { + if ($query) { + $query = http_build_query($query, '', '&'); + if ($query !== '') { + $path .= '?' . $query; + } + } + + $this->attrs['href'] = $path; + return $this; + } + + + /** + * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. + */ + public function data(string $name, mixed $value = null): static + { + if (func_num_args() === 1) { + $this->attrs['data'] = $name; + } else { + $this->attrs["data-$name"] = is_bool($value) + ? json_encode($value) + : $value; + } + + return $this; + } + + + /** + * Sets element's HTML content. + */ + final public function setHtml(mixed $html): static + { + $this->children = [(string) $html]; + return $this; + } + + + /** + * Returns element's HTML content. + */ + final public function getHtml(): string + { + return implode('', $this->children); + } + + + /** + * Sets element's textual content. + */ + final public function setText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + $this->children = [(string) $text]; + return $this; + } + + + /** + * Returns element's textual content. + */ + final public function getText(): string + { + return self::htmlToText($this->getHtml()); + } + + + /** + * Adds new element's child. + */ + final public function addHtml(mixed $child): static + { + return $this->insert(null, $child); + } + + + /** + * Appends plain-text string to element content. + */ + public function addText(mixed $text): static + { + if (!$text instanceof HtmlStringable) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + + return $this->insert(null, $text); + } + + + /** + * Creates and adds a new Html child. + */ + final public function create(string $name, array|string|null $attrs = null): static + { + $this->insert(null, $child = static::el($name, $attrs)); + return $child; + } + + + /** + * Inserts child node. + */ + public function insert(?int $index, HtmlStringable|string $child, bool $replace = false): static + { + $child = $child instanceof self ? $child : (string) $child; + if ($index === null) { // append + $this->children[] = $child; + + } else { // insert or replace + array_splice($this->children, $index, $replace ? 1 : 0, [$child]); + } + + return $this; + } + + + /** + * Inserts (replaces) child node (\ArrayAccess implementation). + * @param int|null $index position or null for appending + * @param Html|string $child Html node or raw HTML string + */ + final public function offsetSet($index, $child): void + { + $this->insert($index, $child, true); + } + + + /** + * Returns child node (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetGet($index): HtmlStringable|string + { + return $this->children[$index]; + } + + + /** + * Exists child node? (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetExists($index): bool + { + return isset($this->children[$index]); + } + + + /** + * Removes child node (\ArrayAccess implementation). + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this->children[$index])) { + array_splice($this->children, $index, 1); + } + } + + + /** + * Returns children count. + */ + final public function count(): int + { + return count($this->children); + } + + + /** + * Removes all children. + */ + public function removeChildren(): void + { + $this->children = []; + } + + + /** + * Iterates over elements. + * @return \ArrayIterator<int, HtmlStringable|string> + */ + final public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + + /** + * Returns all children. + */ + final public function getChildren(): array + { + return $this->children; + } + + + /** + * Renders element's start tag, content and end tag. + */ + final public function render(?int $indent = null): string + { + $s = $this->startTag(); + + if (!$this->isEmpty) { + // add content + if ($indent !== null) { + $indent++; + } + + foreach ($this->children as $child) { + if ($child instanceof self) { + $s .= $child->render($indent); + } else { + $s .= $child; + } + } + + // add end tag + $s .= $this->endTag(); + } + + if ($indent !== null) { + return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); + } + + return $s; + } + + + final public function __toString(): string + { + return $this->render(); + } + + + /** + * Returns element's start tag. + */ + final public function startTag(): string + { + return $this->name + ? '<' . $this->name . $this->attributes() . '>' + : ''; + } + + + /** + * Returns element's end tag. + */ + final public function endTag(): string + { + return $this->name && !$this->isEmpty ? '</' . $this->name . '>' : ''; + } + + + /** + * Returns element's attributes. + * @internal + */ + final public function attributes(): string + { + if (!is_array($this->attrs)) { + return ''; + } + + $s = ''; + $attrs = $this->attrs; + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) { + continue; + + } elseif ($value === true) { + $s .= ' ' . $key; + + continue; + + } elseif (is_array($value)) { + if (strncmp($key, 'data-', 5) === 0) { + $value = Json::encode($value); + + } else { + $tmp = null; + foreach ($value as $k => $v) { + if ($v != null) { // intentionally ==, skip nulls & empty string + // composite 'style' vs. 'others' + $tmp[] = $v === true + ? $k + : (is_string($k) ? $k . ':' . $v : $v); + } + } + + if ($tmp === null) { + continue; + } + + $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); + } + } elseif (is_float($value)) { + $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); + + } else { + $value = (string) $value; + } + + $q = str_contains($value, '"') ? "'" : '"'; + $s .= ' ' . $key . '=' . $q + . str_replace( + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', '<'], + $value, + ) + . (str_contains($value, '`') && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . $q; + } + + $s = str_replace('@', '@', $s); + return $s; + } + + + /** + * Clones all children too. + */ + public function __clone() + { + foreach ($this->children as $key => $value) { + if (is_object($value)) { + $this->children[$key] = clone $value; + } + } + } +} diff --git a/vendor/nette/utils/src/Utils/Image.php b/vendor/nette/utils/src/Utils/Image.php new file mode 100644 index 0000000..a24fd18 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Image.php @@ -0,0 +1,752 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Basic manipulation with images. Supported types are JPEG, PNG, GIF, WEBP, AVIF and BMP. + * + * <code> + * $image = Image::fromFile('nette.jpg'); + * $image->resize(150, 100); + * $image->sharpen(); + * $image->send(); + * </code> + * + * @method Image affine(array $affine, array $clip = null) + * @method array affineMatrixConcat(array $m1, array $m2) + * @method array affineMatrixGet(int $type, mixed $options = null) + * @method void alphaBlending(bool $on) + * @method void antialias(bool $on) + * @method void arc($x, $y, $w, $h, $start, $end, $color) + * @method void char(int $font, $x, $y, string $char, $color) + * @method void charUp(int $font, $x, $y, string $char, $color) + * @method int colorAllocate($red, $green, $blue) + * @method int colorAllocateAlpha($red, $green, $blue, $alpha) + * @method int colorAt($x, $y) + * @method int colorClosest($red, $green, $blue) + * @method int colorClosestAlpha($red, $green, $blue, $alpha) + * @method int colorClosestHWB($red, $green, $blue) + * @method void colorDeallocate($color) + * @method int colorExact($red, $green, $blue) + * @method int colorExactAlpha($red, $green, $blue, $alpha) + * @method void colorMatch(Image $image2) + * @method int colorResolve($red, $green, $blue) + * @method int colorResolveAlpha($red, $green, $blue, $alpha) + * @method void colorSet($index, $red, $green, $blue) + * @method array colorsForIndex($index) + * @method int colorsTotal() + * @method int colorTransparent($color = null) + * @method void convolution(array $matrix, float $div, float $offset) + * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH) + * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1) + * @method void ellipse($cx, $cy, $w, $h, $color) + * @method void fill($x, $y, $color) + * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style) + * @method void filledEllipse($cx, $cy, $w, $h, $color) + * @method void filledPolygon(array $points, $numPoints, $color) + * @method void filledRectangle($x1, $y1, $x2, $y2, $color) + * @method void fillToBorder($x, $y, $border, $color) + * @method void filter($filtertype) + * @method void flip(int $mode) + * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null) + * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method array getClip() + * @method int interlace($interlace = null) + * @method bool isTrueColor() + * @method void layerEffect($effect) + * @method void line($x1, $y1, $x2, $y2, $color) + * @method void openPolygon(array $points, int $num_points, int $color) + * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() + * @method void polygon(array $points, $numPoints, $color) + * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null) + * @method void rectangle($x1, $y1, $x2, $y2, $col) + * @method mixed resolution(int $res_x = null, int $res_y = null) + * @method Image rotate(float $angle, $backgroundColor) + * @method void saveAlpha(bool $saveflag) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) + * @method void setBrush(Image $brush) + * @method void setClip(int $x1, int $y1, int $x2, int $y2) + * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED) + * @method void setPixel($x, $y, $color) + * @method void setStyle(array $style) + * @method void setThickness($thickness) + * @method void setTile(Image $tile) + * @method void string($font, $x, $y, string $s, $col) + * @method void stringUp($font, $x, $y, string $s, $col) + * @method void trueColorToPalette(bool $dither, $ncolors) + * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) + * @property-read int $width + * @property-read int $height + * @property-read \GdImage $imageResource + */ +class Image +{ + use Nette\SmartObject; + + /** Prevent from getting resized to a bigger size than the original */ + public const ShrinkOnly = 0b0001; + + /** Resizes to a specified width and height without keeping aspect ratio */ + public const Stretch = 0b0010; + + /** Resizes to fit into a specified width and height and preserves aspect ratio */ + public const OrSmaller = 0b0000; + + /** Resizes while bounding the smaller dimension to the specified width or height and preserves aspect ratio */ + public const OrBigger = 0b0100; + + /** Resizes to the smallest possible size to completely cover specified width and height and reserves aspect ratio */ + public const Cover = 0b1000; + + /** @deprecated use Image::ShrinkOnly */ + public const SHRINK_ONLY = self::ShrinkOnly; + + /** @deprecated use Image::Stretch */ + public const STRETCH = self::Stretch; + + /** @deprecated use Image::OrSmaller */ + public const FIT = self::OrSmaller; + + /** @deprecated use Image::OrBigger */ + public const FILL = self::OrBigger; + + /** @deprecated use Image::Cover */ + public const EXACT = self::Cover; + + /** @deprecated use Image::EmptyGIF */ + public const EMPTY_GIF = self::EmptyGIF; + + /** image types */ + public const + JPEG = IMAGETYPE_JPEG, + PNG = IMAGETYPE_PNG, + GIF = IMAGETYPE_GIF, + WEBP = IMAGETYPE_WEBP, + AVIF = 19, // IMAGETYPE_AVIF, + BMP = IMAGETYPE_BMP; + + public const EmptyGIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; + + private const Formats = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::AVIF => 'avif', self::BMP => 'bmp']; + + private \GdImage $image; + + + /** + * Returns RGB color (0..255) and transparency (0..127). + */ + public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array + { + return [ + 'red' => max(0, min(255, $red)), + 'green' => max(0, min(255, $green)), + 'blue' => max(0, min(255, $blue)), + 'alpha' => max(0, min(127, $transparency)), + ]; + } + + + /** + * Reads an image from a file and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws UnknownImageFileException if file not found or file type is not known + */ + public static function fromFile(string $file, ?int &$type = null): static + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + $type = self::detectTypeFromFile($file); + if (!$type) { + throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); + } + + return self::invokeSafe('imagecreatefrom' . self::Formats[$type], $file, "Unable to open file '$file'.", __METHOD__); + } + + + /** + * Reads an image from a string and returns its type in $type. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws ImageException + */ + public static function fromString(string $s, ?int &$type = null): static + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + $type = self::detectTypeFromString($s); + if (!$type) { + throw new UnknownImageFileException('Unknown type of image.'); + } + + return self::invokeSafe('imagecreatefromstring', $s, 'Unable to open image from string.', __METHOD__); + } + + + private static function invokeSafe(string $func, string $arg, string $message, string $callee): static + { + $errors = []; + $res = Callback::invokeSafe($func, [$arg], function (string $message) use (&$errors): void { + $errors[] = $message; + }); + + if (!$res) { + throw new ImageException($message . ' Errors: ' . implode(', ', $errors)); + } elseif ($errors) { + trigger_error($callee . '(): ' . implode(', ', $errors), E_USER_WARNING); + } + + return new static($res); + } + + + /** + * Creates a new true color image of the given dimensions. The default color is black. + * @throws Nette\NotSupportedException if gd extension is not loaded + */ + public static function fromBlank(int $width, int $height, ?array $color = null): static + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + if ($width < 1 || $height < 1) { + throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); + } + + $image = imagecreatetruecolor($width, $height); + if ($color) { + $color += ['alpha' => 0]; + $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']); + imagealphablending($image, false); + imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color); + imagealphablending($image, true); + } + + return new static($image); + } + + + /** + * Returns the type of image from file. + */ + public static function detectTypeFromFile(string $file, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesize($file); // @ - files smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the type of image from string. + */ + public static function detectTypeFromString(string $s, &$width = null, &$height = null): ?int + { + [$width, $height, $type] = @getimagesizefromstring($s); // @ - strings smaller than 12 bytes causes read error + return isset(self::Formats[$type]) ? $type : null; + } + + + /** + * Returns the file extension for the given `Image::XXX` constant. + */ + public static function typeToExtension(int $type): string + { + if (!isset(self::Formats[$type])) { + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + + return self::Formats[$type]; + } + + + /** + * Returns the `Image::XXX` constant for given file extension. + */ + public static function extensionToType(string $extension): int + { + $extensions = array_flip(self::Formats) + ['jpg' => self::JPEG]; + $extension = strtolower($extension); + if (!isset($extensions[$extension])) { + throw new Nette\InvalidArgumentException("Unsupported file extension '$extension'."); + } + + return $extensions[$extension]; + } + + + /** + * Returns the mime type for the given `Image::XXX` constant. + */ + public static function typeToMimeType(int $type): string + { + return 'image/' . self::typeToExtension($type); + } + + + /** + * Wraps GD image. + */ + public function __construct(\GdImage $image) + { + $this->setImageResource($image); + imagesavealpha($image, true); + } + + + /** + * Returns image width. + */ + public function getWidth(): int + { + return imagesx($this->image); + } + + + /** + * Returns image height. + */ + public function getHeight(): int + { + return imagesy($this->image); + } + + + /** + * Sets image resource. + */ + protected function setImageResource(\GdImage $image): static + { + $this->image = $image; + return $this; + } + + + /** + * Returns image GD resource. + */ + public function getImageResource(): \GdImage + { + return $this->image; + } + + + /** + * Scales an image. Width and height accept pixels or percent. + * @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode + */ + public function resize(int|string|null $width, int|string|null $height, int $mode = self::OrSmaller): static + { + if ($mode & self::Cover) { + return $this->resize($width, $height, self::OrBigger)->crop('50%', '50%', $width, $height); + } + + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $mode); + + if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize + $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource(); + imagecopyresampled( + $newImage, + $this->image, + 0, + 0, + 0, + 0, + $newWidth, + $newHeight, + $this->getWidth(), + $this->getHeight(), + ); + $this->image = $newImage; + } + + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); + } + + return $this; + } + + + /** + * Calculates dimensions of resized image. Width and height accept pixels or percent. + * @param self::OrSmaller|self::OrBigger|self::Stretch|self::Cover|self::ShrinkOnly $mode + */ + public static function calculateSize( + int $srcWidth, + int $srcHeight, + $newWidth, + $newHeight, + int $mode = self::OrSmaller, + ): array + { + if ($newWidth === null) { + } elseif (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); + $percents = true; + } else { + $newWidth = abs($newWidth); + } + + if ($newHeight === null) { + } elseif (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); + $mode |= empty($percents) ? 0 : self::Stretch; + } else { + $newHeight = abs($newHeight); + } + + if ($mode & self::Stretch) { // non-proportional + if (!$newWidth || !$newHeight) { + throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); + } + + if ($mode & self::ShrinkOnly) { + $newWidth = min($srcWidth, $newWidth); + $newHeight = min($srcHeight, $newHeight); + } + } else { // proportional + if (!$newWidth && !$newHeight) { + throw new Nette\InvalidArgumentException('At least width or height must be specified.'); + } + + $scale = []; + if ($newWidth > 0) { // fit width + $scale[] = $newWidth / $srcWidth; + } + + if ($newHeight > 0) { // fit height + $scale[] = $newHeight / $srcHeight; + } + + if ($mode & self::OrBigger) { + $scale = [max($scale)]; + } + + if ($mode & self::ShrinkOnly) { + $scale[] = 1; + } + + $scale = min($scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); + } + + return [max($newWidth, 1), max($newHeight, 1)]; + } + + + /** + * Crops image. Arguments accepts pixels or percent. + */ + public function crop(int|string $left, int|string $top, int|string $width, int|string $height): static + { + [$r['x'], $r['y'], $r['width'], $r['height']] + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') { + $this->image = imagecrop($this->image, $r); + imagesavealpha($this->image, true); + } else { + $newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource(); + imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); + $this->image = $newImage; + } + + return $this; + } + + + /** + * Calculates dimensions of cutout in image. Arguments accepts pixels or percent. + */ + public static function calculateCutout( + int $srcWidth, + int $srcHeight, + int|string $left, + int|string $top, + int|string $newWidth, + int|string $newHeight, + ): array + { + if (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * $newWidth); + } + + if (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * $newHeight); + } + + if (self::isPercent($left)) { + $left = (int) round(($srcWidth - $newWidth) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($srcHeight - $newHeight) / 100 * $top); + } + + if ($left < 0) { + $newWidth += $left; + $left = 0; + } + + if ($top < 0) { + $newHeight += $top; + $top = 0; + } + + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; + } + + + /** + * Sharpens image a little bit. + */ + public function sharpen(): static + { + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); + return $this; + } + + + /** + * Puts another image into this image. Left and top accepts pixels or percent. + * @param int $opacity 0..100 + */ + public function place(self $image, int|string $left = 0, int|string $top = 0, int $opacity = 100): static + { + $opacity = max(0, min(100, $opacity)); + if ($opacity === 0) { + return $this; + } + + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (self::isPercent($left)) { + $left = (int) round(($this->getWidth() - $width) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($this->getHeight() - $height) / 100 * $top); + } + + $output = $input = $image->image; + if ($opacity < 100) { + $tbl = []; + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } + + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, false); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + + imagealphablending($output, true); + } + + imagecopy( + $this->image, + $output, + $left, + $top, + 0, + 0, + $width, + $height, + ); + return $this; + } + + + /** + * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @throws ImageException + */ + public function save(string $file, ?int $quality = null, ?int $type = null): void + { + $type ??= self::extensionToType(pathinfo($file, PATHINFO_EXTENSION)); + $this->output($type, $quality, $file); + } + + + /** + * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + */ + public function toString(int $type = self::JPEG, ?int $quality = null): string + { + return Helpers::capture(function () use ($type, $quality): void { + $this->output($type, $quality); + }); + } + + + /** + * Outputs image to string. + */ + public function __toString(): string + { + return $this->toString(); + } + + + /** + * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85), WEBP (default 80) and AVIF (default 30) and 0..9 for PNG (default 9). + * @throws ImageException + */ + public function send(int $type = self::JPEG, ?int $quality = null): void + { + header('Content-Type: ' . self::typeToMimeType($type)); + $this->output($type, $quality); + } + + + /** + * Outputs image to browser or file. + * @throws ImageException + */ + private function output(int $type, ?int $quality, ?string $file = null): void + { + switch ($type) { + case self::JPEG: + $quality = $quality === null ? 85 : max(0, min(100, $quality)); + $success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::PNG: + $quality = $quality === null ? 9 : max(0, min(9, $quality)); + $success = @imagepng($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::GIF: + $success = @imagegif($this->image, $file); // @ is escalated to exception + break; + + case self::WEBP: + $quality = $quality === null ? 80 : max(0, min(100, $quality)); + $success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::AVIF: + $quality = $quality === null ? 30 : max(0, min(100, $quality)); + $success = @imageavif($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::BMP: + $success = @imagebmp($this->image, $file); // @ is escalated to exception + break; + + default: + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + + if (!$success) { + throw new ImageException(Helpers::getLastError() ?: 'Unknown error'); + } + } + + + /** + * Call to undefined method. + * @throws Nette\MemberAccessException + */ + public function __call(string $name, array $args): mixed + { + $function = 'image' . $name; + if (!function_exists($function)) { + ObjectHelpers::strictCall(static::class, $name); + } + + foreach ($args as $key => $value) { + if ($value instanceof self) { + $args[$key] = $value->getImageResource(); + + } elseif (is_array($value) && isset($value['red'])) { // rgb + $args[$key] = imagecolorallocatealpha( + $this->image, + $value['red'], + $value['green'], + $value['blue'], + $value['alpha'], + ) ?: imagecolorresolvealpha( + $this->image, + $value['red'], + $value['green'], + $value['blue'], + $value['alpha'], + ); + } + } + + $res = $function($this->image, ...$args); + return $res instanceof \GdImage + ? $this->setImageResource($res) + : $res; + } + + + public function __clone() + { + ob_start(function () {}); + imagepng($this->image, null, 0); + $this->setImageResource(imagecreatefromstring(ob_get_clean())); + } + + + private static function isPercent(int|string &$num): bool + { + if (is_string($num) && str_ends_with($num, '%')) { + $num = (float) substr($num, 0, -1); + return true; + } elseif (is_int($num) || $num === (string) (int) $num) { + $num = (int) $num; + return false; + } + + throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given."); + } + + + /** + * Prevents serialization. + */ + public function __sleep(): array + { + throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); + } +} diff --git a/vendor/nette/utils/src/Utils/Json.php b/vendor/nette/utils/src/Utils/Json.php new file mode 100644 index 0000000..b87917b --- /dev/null +++ b/vendor/nette/utils/src/Utils/Json.php @@ -0,0 +1,84 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * JSON encoder and decoder. + */ +final class Json +{ + use Nette\StaticClass; + + /** @deprecated use Json::decode(..., forceArrays: true) */ + public const FORCE_ARRAY = JSON_OBJECT_AS_ARRAY; + + /** @deprecated use Json::encode(..., pretty: true) */ + public const PRETTY = JSON_PRETTY_PRINT; + + /** @deprecated use Json::encode(..., asciiSafe: true) */ + public const ESCAPE_UNICODE = 1 << 19; + + + /** + * Converts value to JSON format. Use $pretty for easier reading and clarity, $asciiSafe for ASCII output + * and $htmlSafe for HTML escaping, $forceObjects enforces the encoding of non-associateve arrays as objects. + * @throws JsonException + */ + public static function encode( + mixed $value, + bool|int $pretty = false, + bool $asciiSafe = false, + bool $htmlSafe = false, + bool $forceObjects = false, + ): string + { + if (is_int($pretty)) { // back compatibility + $flags = ($pretty & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE) | ($pretty & ~self::ESCAPE_UNICODE); + } else { + $flags = ($asciiSafe ? 0 : JSON_UNESCAPED_UNICODE) + | ($pretty ? JSON_PRETTY_PRINT : 0) + | ($forceObjects ? JSON_FORCE_OBJECT : 0) + | ($htmlSafe ? JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_TAG : 0); + } + + $flags |= JSON_UNESCAPED_SLASHES + | (defined('JSON_PRESERVE_ZERO_FRACTION') ? JSON_PRESERVE_ZERO_FRACTION : 0); // since PHP 5.6.6 & PECL JSON-C 1.3.7 + + $json = json_encode($value, $flags); + if ($error = json_last_error()) { + throw new JsonException(json_last_error_msg(), $error); + } + + return $json; + } + + + /** + * Parses JSON to PHP value. The $forceArrays enforces the decoding of objects as arrays. + * @throws JsonException + */ + public static function decode(string $json, bool|int $forceArrays = false): mixed + { + $flags = is_int($forceArrays) // back compatibility + ? $forceArrays + : ($forceArrays ? JSON_OBJECT_AS_ARRAY : 0); + $flags |= JSON_BIGINT_AS_STRING; + + $value = json_decode($json, flags: $flags); + if ($error = json_last_error()) { + throw new JsonException(json_last_error_msg(), $error); + } + + return $value; + } +} diff --git a/vendor/nette/utils/src/Utils/ObjectHelpers.php b/vendor/nette/utils/src/Utils/ObjectHelpers.php new file mode 100644 index 0000000..f4bd55f --- /dev/null +++ b/vendor/nette/utils/src/Utils/ObjectHelpers.php @@ -0,0 +1,229 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; +use Nette\MemberAccessException; + + +/** + * Nette\SmartObject helpers. + * @internal + */ +final class ObjectHelpers +{ + use Nette\StaticClass; + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictGet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictSet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), fn($p) => !$p->isStatic()), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m'), + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictCall(string $class, string $method, array $additionalMethods = []): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __call() + $context = ($trace[1]['function'] ?? null) === '__call' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:static[ \t]+)?(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods, + ), $method); + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * @return never + * @throws MemberAccessException + */ + public static function strictStaticCall(string $class, string $method): void + { + $trace = debug_backtrace(0, 3); // suppose this method is called from __callStatic() + $context = ($trace[1]['function'] ?? null) === '__callStatic' + ? ($trace[2]['class'] ?? null) + : null; + + if ($context && is_a($class, $context, true) && method_exists($context, $method)) { // called parent::$method() + $class = get_parent_class($context); + } + + if (method_exists($class, $method)) { // insufficient visibility + $rm = new \ReflectionMethod($class, $method); + $visibility = $rm->isPrivate() + ? 'private ' + : ($rm->isProtected() ? 'protected ' : ''); + throw new MemberAccessException("Call to {$visibility}method $class::$method() from " . ($context ? "scope $context." : 'global scope.')); + + } else { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), fn($m) => $m->isStatic()), + $method, + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + } + + + /** + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] + * @internal + */ + public static function getMagicProperties(string $class): array + { + static $cache; + $props = &$cache[$class]; + if ($props !== null) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write|-deprecated) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), + $matches, + PREG_SET_ORDER, + ); + + $props = []; + foreach ($matches as [, $type, $name]) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3 | ($type === '-deprecated') << 4; + } + } + + foreach ($rc->getTraits() as $trait) { + $props += self::getMagicProperties($trait->name); + } + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + + return $props; + } + + + /** + * Finds the best suggestion (for 8-bit encoding). + * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities + * @internal + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->name : $item; + if ($item !== $value && ( + ($len = levenshtein($item, $value, 10, 11, 10)) < $min + || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min + )) { + $min = $len; + $best = $item; + } + } + + return $best; + } + + + private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array + { + do { + $doc[] = $rc->getDocComment(); + $traits = $rc->getTraits(); + while ($trait = array_pop($traits)) { + $doc[] = $trait->getDocComment(); + $traits += $trait->getTraits(); + } + } while ($rc = $rc->getParentClass()); + + return preg_match_all($pattern, implode('', $doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * Returns 'event' if the property exists and has event like name + * @internal + */ + public static function hasProperty(string $class, string $name): bool|string + { + static $cache; + $prop = &$cache[$class][$name]; + if ($prop === null) { + $prop = false; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; + } + } catch (\ReflectionException $e) { + } + } + + return $prop; + } +} diff --git a/vendor/nette/utils/src/Utils/Paginator.php b/vendor/nette/utils/src/Utils/Paginator.php new file mode 100644 index 0000000..91d55c5 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Paginator.php @@ -0,0 +1,231 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Paginating math. + * + * @property int $page + * @property-read int $firstPage + * @property-read int|null $lastPage + * @property-read int $firstItemOnPage + * @property-read int $lastItemOnPage + * @property int $base + * @property-read bool $first + * @property-read bool $last + * @property-read int|null $pageCount + * @property int $itemsPerPage + * @property int|null $itemCount + * @property-read int $offset + * @property-read int|null $countdownOffset + * @property-read int $length + */ +class Paginator +{ + use Nette\SmartObject; + + private int $base = 1; + private int $itemsPerPage = 1; + private int $page = 1; + private ?int $itemCount = null; + + + /** + * Sets current page number. + */ + public function setPage(int $page): static + { + $this->page = $page; + return $this; + } + + + /** + * Returns current page number. + */ + public function getPage(): int + { + return $this->base + $this->getPageIndex(); + } + + + /** + * Returns first page number. + */ + public function getFirstPage(): int + { + return $this->base; + } + + + /** + * Returns last page number. + */ + public function getLastPage(): ?int + { + return $this->itemCount === null + ? null + : $this->base + max(0, $this->getPageCount() - 1); + } + + + /** + * Returns the sequence number of the first element on the page + */ + public function getFirstItemOnPage(): int + { + return $this->itemCount !== 0 + ? $this->offset + 1 + : 0; + } + + + /** + * Returns the sequence number of the last element on the page + */ + public function getLastItemOnPage(): int + { + return $this->offset + $this->length; + } + + + /** + * Sets first page (base) number. + */ + public function setBase(int $base): static + { + $this->base = $base; + return $this; + } + + + /** + * Returns first page (base) number. + */ + public function getBase(): int + { + return $this->base; + } + + + /** + * Returns zero-based page number. + */ + protected function getPageIndex(): int + { + $index = max(0, $this->page - $this->base); + return $this->itemCount === null + ? $index + : min($index, max(0, $this->getPageCount() - 1)); + } + + + /** + * Is the current page the first one? + */ + public function isFirst(): bool + { + return $this->getPageIndex() === 0; + } + + + /** + * Is the current page the last one? + */ + public function isLast(): bool + { + return $this->itemCount === null + ? false + : $this->getPageIndex() >= $this->getPageCount() - 1; + } + + + /** + * Returns the total number of pages. + */ + public function getPageCount(): ?int + { + return $this->itemCount === null + ? null + : (int) ceil($this->itemCount / $this->itemsPerPage); + } + + + /** + * Sets the number of items to display on a single page. + */ + public function setItemsPerPage(int $itemsPerPage): static + { + $this->itemsPerPage = max(1, $itemsPerPage); + return $this; + } + + + /** + * Returns the number of items to display on a single page. + */ + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + + /** + * Sets the total number of items. + */ + public function setItemCount(?int $itemCount = null): static + { + $this->itemCount = $itemCount === null ? null : max(0, $itemCount); + return $this; + } + + + /** + * Returns the total number of items. + */ + public function getItemCount(): ?int + { + return $this->itemCount; + } + + + /** + * Returns the absolute index of the first item on current page. + */ + public function getOffset(): int + { + return $this->getPageIndex() * $this->itemsPerPage; + } + + + /** + * Returns the absolute index of the first item on current page in countdown paging. + */ + public function getCountdownOffset(): ?int + { + return $this->itemCount === null + ? null + : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); + } + + + /** + * Returns the number of items on current page. + */ + public function getLength(): int + { + return $this->itemCount === null + ? $this->itemsPerPage + : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); + } +} diff --git a/vendor/nette/utils/src/Utils/Random.php b/vendor/nette/utils/src/Utils/Random.php new file mode 100644 index 0000000..d8ebcdc --- /dev/null +++ b/vendor/nette/utils/src/Utils/Random.php @@ -0,0 +1,49 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Secure random string generator. + */ +final class Random +{ + use Nette\StaticClass; + + /** + * Generates a random string of given length from characters specified in second argument. + * Supports intervals, such as `0-9` or `A-Z`. + */ + public static function generate(int $length = 10, string $charlist = '0-9a-z'): string + { + $charlist = preg_replace_callback( + '#.-.#', + fn(array $m): string => implode('', range($m[0][0], $m[0][2])), + $charlist, + ); + $charlist = count_chars($charlist, mode: 3); + $chLen = strlen($charlist); + + if ($length < 1) { + throw new Nette\InvalidArgumentException('Length must be greater than zero.'); + } elseif ($chLen < 2) { + throw new Nette\InvalidArgumentException('Character list must contain at least two chars.'); + } + + $res = ''; + for ($i = 0; $i < $length; $i++) { + $res .= $charlist[random_int(0, $chLen - 1)]; + } + + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Reflection.php b/vendor/nette/utils/src/Utils/Reflection.php new file mode 100644 index 0000000..f5a36a2 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Reflection.php @@ -0,0 +1,319 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * PHP reflection helpers. + */ +final class Reflection +{ + use Nette\StaticClass; + + /** @deprecated use Nette\Utils\Validator::isBuiltinType() */ + public static function isBuiltinType(string $type): bool + { + return Validators::isBuiltinType($type); + } + + + /** @deprecated use Nette\Utils\Validator::isClassKeyword() */ + public static function isClassKeyword(string $name): bool + { + return Validators::isClassKeyword($name); + } + + + /** @deprecated use native ReflectionParameter::getDefaultValue() */ + public static function getParameterDefaultValue(\ReflectionParameter $param): mixed + { + if ($param->isDefaultValueConstant()) { + $const = $orig = $param->getDefaultValueConstantName(); + $pair = explode('::', $const); + if (isset($pair[1])) { + $pair[0] = Type::resolve($pair[0], $param); + try { + $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); + } catch (\ReflectionException $e) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); + } + + return $rcc->getValue(); + + } elseif (!defined($const)) { + $const = substr((string) strrchr($const, '\\'), 1); + if (!defined($const)) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); + } + } + + return constant($const); + } + + return $param->getDefaultValue(); + } + + + /** + * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait. + */ + public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass + { + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->name) + // doc-comment guessing as workaround for insufficient PHP reflection + && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment() + ) { + return self::getPropertyDeclaringClass($trait->getProperty($prop->name)); + } + } + + return $prop->getDeclaringClass(); + } + + + /** + * Returns a reflection of a method that contains a declaration of $method. + * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. + */ + public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod + { + // file & line guessing as workaround for insufficient PHP reflection + $decl = $method->getDeclaringClass(); + if ($decl->getFileName() === $method->getFileName() + && $decl->getStartLine() <= $method->getStartLine() + && $decl->getEndLine() >= $method->getEndLine() + ) { + return $method; + } + + $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()]; + if (($alias = $decl->getTraitAliases()[$method->name] ?? null) + && ($m = new \ReflectionMethod($alias)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + + foreach ($decl->getTraits() as $trait) { + if ($trait->hasMethod($method->name) + && ($m = $trait->getMethod($method->name)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + } + + return $method; + } + + + /** + * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache. + */ + public static function areCommentsAvailable(): bool + { + static $res; + return $res ?? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment(); + } + + + public static function toString(\Reflector $ref): string + { + if ($ref instanceof \ReflectionClass) { + return $ref->name; + } elseif ($ref instanceof \ReflectionMethod) { + return $ref->getDeclaringClass()->name . '::' . $ref->name . '()'; + } elseif ($ref instanceof \ReflectionFunction) { + return $ref->name . '()'; + } elseif ($ref instanceof \ReflectionProperty) { + return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name; + } elseif ($ref instanceof \ReflectionParameter) { + return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()); + } else { + throw new Nette\InvalidArgumentException; + } + } + + + /** + * Expands the name of the class to full name in the given context of given class. + * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context. + * @throws Nette\InvalidArgumentException + */ + public static function expandClassName(string $name, \ReflectionClass $context): string + { + $lower = strtolower($name); + if (empty($name)) { + throw new Nette\InvalidArgumentException('Class name must not be empty.'); + + } elseif (Validators::isBuiltinType($lower)) { + return $lower; + + } elseif ($lower === 'self' || $lower === 'static') { + return $context->name; + + } elseif ($lower === 'parent') { + return $context->getParentClass() + ? $context->getParentClass()->name + : 'parent'; + + } elseif ($name[0] === '\\') { // fully qualified name + return ltrim($name, '\\'); + } + + $uses = self::getUseStatements($context); + $parts = explode('\\', $name, 2); + if (isset($uses[$parts[0]])) { + $parts[0] = $uses[$parts[0]]; + return implode('\\', $parts); + + } elseif ($context->inNamespace()) { + return $context->getNamespaceName() . '\\' . $name; + + } else { + return $name; + } + } + + + /** @return array of [alias => class] */ + public static function getUseStatements(\ReflectionClass $class): array + { + if ($class->isAnonymous()) { + throw new Nette\NotImplementedException('Anonymous classes are not supported.'); + } + + static $cache = []; + if (!isset($cache[$name = $class->name])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + + return $cache[$name]; + } + + + /** + * Parses PHP code to [class => [alias => class, ...]] + */ + private static function parseUseStatements(string $code, ?string $forClass = null): array + { + try { + $tokens = \PhpToken::tokenize($code, TOKEN_PARSE); + } catch (\ParseError $e) { + trigger_error($e->getMessage(), E_USER_NOTICE); + $tokens = []; + } + + $namespace = $class = $classLevel = $level = null; + $res = $uses = []; + + $nameTokens = [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; + + while ($token = current($tokens)) { + next($tokens); + switch ($token->id) { + case T_NAMESPACE: + $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); + $uses = []; + break; + + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + case PHP_VERSION_ID < 80100 + ? T_CLASS + : T_ENUM: + if ($name = self::fetch($tokens, T_STRING)) { + $class = $namespace . $name; + $classLevel = $level + 1; + $res[$class] = $uses; + if ($class === $forClass) { + return $res; + } + } + + break; + + case T_USE: + while (!$class && ($name = self::fetch($tokens, $nameTokens))) { + $name = ltrim($name, '\\'); + if (self::fetch($tokens, '{')) { + while ($suffix = self::fetch($tokens, $nameTokens)) { + if (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; + } else { + $tmp = explode('\\', $suffix); + $uses[end($tmp)] = $name . $suffix; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + } elseif (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name; + + } else { + $tmp = explode('\\', $name); + $uses[end($tmp)] = $name; + } + + if (!self::fetch($tokens, ',')) { + break; + } + } + + break; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case ord('{'): + $level++; + break; + + case ord('}'): + if ($level === $classLevel) { + $class = $classLevel = null; + } + + $level--; + } + } + + return $res; + } + + + private static function fetch(array &$tokens, string|int|array $take): ?string + { + $res = null; + while ($token = current($tokens)) { + if ($token->is($take)) { + $res .= $token->text; + } elseif (!$token->is([T_DOC_COMMENT, T_WHITESPACE, T_COMMENT])) { + break; + } + + next($tokens); + } + + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Strings.php b/vendor/nette/utils/src/Utils/Strings.php new file mode 100644 index 0000000..32732c1 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Strings.php @@ -0,0 +1,704 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use JetBrains\PhpStorm\Language; +use Nette; +use function is_array, is_object, strlen; + + +/** + * String tools library. + */ +class Strings +{ + use Nette\StaticClass; + + public const TrimCharacters = " \t\n\r\0\x0B\u{A0}"; + + /** @deprecated use Strings::TrimCharacters */ + public const TRIM_CHARACTERS = self::TrimCharacters; + + + /** + * @deprecated use Nette\Utils\Validator::isUnicode() + */ + public static function checkEncoding(string $s): bool + { + return $s === self::fixEncoding($s); + } + + + /** + * Removes all invalid UTF-8 characters from a string. + */ + public static function fixEncoding(string $s): string + { + // removes xD800-xDFFF, x110000 and higher + return htmlspecialchars_decode(htmlspecialchars($s, ENT_NOQUOTES | ENT_IGNORE, 'UTF-8'), ENT_NOQUOTES); + } + + + /** + * Returns a specific character in UTF-8 from code point (number in range 0x0000..D7FF or 0xE000..10FFFF). + * @throws Nette\InvalidArgumentException if code point is not in valid range + */ + public static function chr(int $code): string + { + if ($code < 0 || ($code >= 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } + + + /** + * Returns a code point of specific character in UTF-8 (number in range 0x0000..D7FF or 0xE000..10FFFF). + */ + public static function ord(string $c): int + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + $tmp = iconv('UTF-8', 'UTF-32BE//IGNORE', $c); + if (!$tmp) { + throw new Nette\InvalidArgumentException('Invalid UTF-8 character "' . ($c === '' ? '' : '\x' . strtoupper(bin2hex($c))) . '".'); + } + + return unpack('N', $tmp)[1]; + } + + + /** + * @deprecated use str_starts_with() + */ + public static function startsWith(string $haystack, string $needle): bool + { + return str_starts_with($haystack, $needle); + } + + + /** + * @deprecated use str_ends_with() + */ + public static function endsWith(string $haystack, string $needle): bool + { + return str_ends_with($haystack, $needle); + } + + + /** + * @deprecated use str_contains() + */ + public static function contains(string $haystack, string $needle): bool + { + return str_contains($haystack, $needle); + } + + + /** + * Returns a part of UTF-8 string specified by starting position and length. If start is negative, + * the returned string will start at the start'th character from the end of string. + */ + public static function substring(string $s, int $start, ?int $length = null): string + { + if (function_exists('mb_substr')) { + return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.'); + } elseif ($length === null) { + $length = self::length($s); + } elseif ($start < 0 && $length < 0) { + $start += self::length($s); // unifies iconv_substr behavior with mb_substr + } + + return iconv_substr($s, $start, $length, 'UTF-8'); + } + + + /** + * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, + * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. + */ + public static function normalize(string $s): string + { + // convert to compressed normal form (NFC) + if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { + $s = $n; + } + + $s = self::unixNewLines($s); + + // remove control characters; leave \t + \n + $s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]); + + // right trim + $s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]); + + // leading and trailing blank lines + $s = trim($s, "\n"); + + return $s; + } + + + /** @deprecated use Strings::unixNewLines() */ + public static function normalizeNewLines(string $s): string + { + return self::unixNewLines($s); + } + + + /** + * Converts line endings to \n used on Unix-like systems. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function unixNewLines(string $s): string + { + return preg_replace("~\r\n?|\u{2028}|\u{2029}~", "\n", $s); + } + + + /** + * Converts line endings to platform-specific, i.e. \r\n on Windows and \n elsewhere. + * Line endings are: \n, \r, \r\n, U+2028 line separator, U+2029 paragraph separator. + */ + public static function platformNewLines(string $s): string + { + return preg_replace("~\r\n?|\n|\u{2028}|\u{2029}~", PHP_EOL, $s); + } + + + /** + * Converts UTF-8 string to ASCII, ie removes diacritics etc. + */ + public static function toAscii(string $s): string + { + $iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null; + static $transliterator = null; + if ($transliterator === null) { + if (class_exists('Transliterator', false)) { + $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); + } else { + trigger_error(__METHOD__ . "(): it is recommended to enable PHP extensions 'intl'.", E_USER_NOTICE); + $transliterator = false; + } + } + + // remove control characters and check UTF-8 validity + $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); + + // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly + $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu', "\u{c4}" => 'Ae', "\u{d6}" => 'Oe', "\u{dc}" => 'Ue', "\u{1e9e}" => 'Ss', "\u{e4}" => 'ae', "\u{f6}" => 'oe', "\u{fc}" => 'ue', "\u{df}" => 'ss']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю Ä Ö Ü ẞ ä ö ü ß + if ($iconv !== 'libiconv') { + $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ + } + + if ($transliterator) { + $s = $transliterator->transliterate($s); + // use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ + if ($iconv === 'glibc') { + $s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + $s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters + } elseif ($iconv === 'libiconv') { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } else { // null or 'unknown' (#216) + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + } elseif ($iconv === 'glibc' || $iconv === 'libiconv') { + // temporarily hide these characters to distinguish them from the garbage that iconv creates + $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); + if ($iconv === 'glibc') { + // glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); + $s = strtr( + $s, + "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.', + ); + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); + } else { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } + + // remove garbage that iconv creates during transliteration (eg Ý -> Y') + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); + // restore temporarily hidden characters + $s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); + } else { + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + + return $s; + } + + + /** + * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters + * except letters of the English alphabet and numbers with a hyphens. + */ + public static function webalize(string $s, ?string $charlist = null, bool $lower = true): string + { + $s = self::toAscii($s); + if ($lower) { + $s = strtolower($s); + } + + $s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]); + $s = trim($s, '-'); + return $s; + } + + + /** + * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, + * an ellipsis (or something else set with third argument) is appended to the string. + */ + public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string + { + if (self::length($s) > $maxLen) { + $maxLen -= self::length($append); + if ($maxLen < 1) { + return $append; + + } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { + return $matches[0] . $append; + + } else { + return self::substring($s, 0, $maxLen) . $append; + } + } + + return $s; + } + + + /** + * Indents a multiline text from the left. Second argument sets how many indentation chars should be used, + * while the indent itself is the third argument (*tab* by default). + */ + public static function indent(string $s, int $level = 1, string $chars = "\t"): string + { + if ($level > 0) { + $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); + } + + return $s; + } + + + /** + * Converts all characters of UTF-8 string to lower case. + */ + public static function lower(string $s): string + { + return mb_strtolower($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. + */ + public static function firstLower(string $s): string + { + return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts all characters of a UTF-8 string to upper case. + */ + public static function upper(string $s): string + { + return mb_strtoupper($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. + */ + public static function firstUpper(string $s): string + { + return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. + */ + public static function capitalize(string $s): string + { + return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); + } + + + /** + * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared, + * if it is negative, the corresponding number of characters from the end of the strings is compared, + * otherwise the appropriate number of characters from the beginning is compared. + */ + public static function compare(string $left, string $right, ?int $length = null): bool + { + if (class_exists('Normalizer', false)) { + $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster + $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster + } + + if ($length < 0) { + $left = self::substring($left, $length, -$length); + $right = self::substring($right, $length, -$length); + } elseif ($length !== null) { + $left = self::substring($left, 0, $length); + $right = self::substring($right, 0, $length); + } + + return self::lower($left) === self::lower($right); + } + + + /** + * Finds the common prefix of strings or returns empty string if the prefix was not found. + * @param string[] $strings + */ + public static function findPrefix(array $strings): string + { + $first = array_shift($strings); + for ($i = 0; $i < strlen($first); $i++) { + foreach ($strings as $s) { + if (!isset($s[$i]) || $first[$i] !== $s[$i]) { + while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { + $i--; + } + + return substr($first, 0, $i); + } + } + } + + return $first; + } + + + /** + * Returns number of characters (not bytes) in UTF-8 string. + * That is the number of Unicode code points which may differ from the number of graphemes. + */ + public static function length(string $s): int + { + return function_exists('mb_strlen') + ? mb_strlen($s, 'UTF-8') + : strlen(utf8_decode($s)); + } + + + /** + * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. + */ + public static function trim(string $s, string $charlist = self::TrimCharacters): string + { + $charlist = preg_quote($charlist, '#'); + return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); + } + + + /** + * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. + */ + public static function padLeft(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; + } + + + /** + * Pads UTF-8 string to given length by appending the $pad string to the end. + */ + public static function padRight(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); + } + + + /** + * Reverses UTF-8 string. + */ + public static function reverse(string $s): string + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); + } + + + /** + * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function before(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, 0, $pos); + } + + + /** + * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function after(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, $pos + strlen($needle)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the $needle was not found. + * Negative value of `$nth` means searching from the end. + */ + public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position in characters of $nth occurence of $needle in $haystack or null if the needle was not found. + */ + private static function pos(string $haystack, string $needle, int $nth = 1): ?int + { + if (!$nth) { + return null; + } elseif ($nth > 0) { + if ($needle === '') { + return 0; + } + + $pos = 0; + while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { + $pos++; + } + } else { + $len = strlen($haystack); + if ($needle === '') { + return $len; + } elseif ($len === 0) { + return null; + } + + $pos = $len - 1; + while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { + $pos--; + } + } + + return Helpers::falseToNull($pos); + } + + + /** + * Divides the string into arrays according to the regular expression. Expressions in parentheses will be captured and returned as well. + */ + public static function split( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + bool $skipEmpty = false, + int $limit = -1, + bool $utf8 = false, + ): array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_SPLIT_OFFSET_CAPTURE : 0) | ($skipEmpty ? PREG_SPLIT_NO_EMPTY : 0); + + $pattern .= $utf8 ? 'u' : ''; + $m = self::pcre('preg_split', [$pattern, $subject, $limit, $flags | PREG_SPLIT_DELIM_CAPTURE]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, [$m])[0] + : $m; + + } + + + /** + * Searches the string for the part matching the regular expression and returns + * an array with the found expression and individual subexpressions, or `null`. + */ + public static function match( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): ?array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($offset > strlen($subject)) { + return null; + } elseif (!self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset])) { + return null; + } elseif ($utf8 && $captureOffset) { + return self::bytesToChars($subject, [$m])[0]; + } else { + return $m; + } + } + + + /** + * Searches the string for all occurrences matching the regular expression and + * returns an array of arrays containing the found expression and each subexpression. + */ + public static function matchAll( + string $subject, + #[Language('RegExp')] + string $pattern, + bool|int $captureOffset = false, + int $offset = 0, + bool $unmatchedAsNull = false, + bool $patternOrder = false, + bool $utf8 = false, + ): array + { + $flags = is_int($captureOffset) // back compatibility + ? $captureOffset + : ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0) | ($patternOrder ? PREG_PATTERN_ORDER : 0); + + if ($utf8) { + $offset = strlen(self::substring($subject, 0, $offset)); + $pattern .= 'u'; + } + + if ($offset > strlen($subject)) { + return []; + } + + self::pcre('preg_match_all', [ + $pattern, $subject, &$m, + ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), + $offset, + ]); + return $utf8 && $captureOffset + ? self::bytesToChars($subject, $m) + : $m; + + } + + + /** + * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. + */ + public static function replace( + string $subject, + #[Language('RegExp')] + string|array $pattern, + string|callable $replacement = '', + int $limit = -1, + bool $captureOffset = false, + bool $unmatchedAsNull = false, + bool $utf8 = false, + ): string + { + if (is_object($replacement) || is_array($replacement)) { + if (!is_callable($replacement, false, $textual)) { + throw new Nette\InvalidStateException("Callback '$textual' is not callable."); + } + + $flags = ($captureOffset ? PREG_OFFSET_CAPTURE : 0) | ($unmatchedAsNull ? PREG_UNMATCHED_AS_NULL : 0); + if ($utf8) { + $pattern .= 'u'; + if ($captureOffset) { + $replacement = fn($m) => $replacement(self::bytesToChars($subject, [$m])[0]); + } + } + + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit, 0, $flags]); + + } elseif (is_array($pattern) && is_string(key($pattern))) { + $replacement = array_values($pattern); + $pattern = array_keys($pattern); + } + + if ($utf8) { + $pattern = array_map(fn($item) => $item . 'u', (array) $pattern); + } + + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); + } + + + private static function bytesToChars(string $s, array $groups): array + { + $lastBytes = $lastChars = 0; + foreach ($groups as &$matches) { + foreach ($matches as &$match) { + if ($match[1] > $lastBytes) { + $lastChars += self::length(substr($s, $lastBytes, $match[1] - $lastBytes)); + } elseif ($match[1] < $lastBytes) { + $lastChars -= self::length(substr($s, $match[1], $lastBytes - $match[1])); + } + + $lastBytes = $match[1]; + $match[1] = $lastChars; + } + } + + return $groups; + } + + + /** @internal */ + public static function pcre(string $func, array $args) + { + $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { + // compile-time error, not detectable by preg_last_error + throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); + }); + + if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars + && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) + ) { + throw new RegexpException(preg_last_error_msg() + . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); + } + + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Type.php b/vendor/nette/utils/src/Utils/Type.php new file mode 100644 index 0000000..965977b --- /dev/null +++ b/vendor/nette/utils/src/Utils/Type.php @@ -0,0 +1,265 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * PHP type reflection. + */ +final class Type +{ + /** @var array<int, string|self> */ + private array $types; + private bool $simple; + private string $kind; // | & + + + /** + * Creates a Type object based on reflection. Resolves self, static and parent to the actual class name. + * If the subject has no type, it returns null. + */ + public static function fromReflection( + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $reflection, + ): ?self + { + $type = $reflection instanceof \ReflectionFunctionAbstract + ? $reflection->getReturnType() ?? (PHP_VERSION_ID >= 80100 && $reflection instanceof \ReflectionMethod ? $reflection->getTentativeReturnType() : null) + : $reflection->getType(); + + return $type ? self::fromReflectionType($type, $reflection, true) : null; + } + + + private static function fromReflectionType(\ReflectionType $type, $of, bool $asObject): self|string + { + if ($type instanceof \ReflectionNamedType) { + $name = self::resolve($type->getName(), $of); + return $asObject + ? new self($type->allowsNull() && $name !== 'mixed' ? [$name, 'null'] : [$name]) + : $name; + + } elseif ($type instanceof \ReflectionUnionType || $type instanceof \ReflectionIntersectionType) { + return new self( + array_map(fn($t) => self::fromReflectionType($t, $of, false), $type->getTypes()), + $type instanceof \ReflectionUnionType ? '|' : '&', + ); + + } else { + throw new Nette\InvalidStateException('Unexpected type of ' . Reflection::toString($of)); + } + } + + + /** + * Creates the Type object according to the text notation. + */ + public static function fromString(string $type): self + { + if (!Validators::isTypeDeclaration($type)) { + throw new Nette\InvalidArgumentException("Invalid type '$type'."); + } + + if ($type[0] === '?') { + return new self([substr($type, 1), 'null']); + } + + $unions = []; + foreach (explode('|', $type) as $part) { + $part = explode('&', trim($part, '()')); + $unions[] = count($part) === 1 ? $part[0] : new self($part, '&'); + } + + return count($unions) === 1 && $unions[0] instanceof self + ? $unions[0] + : new self($unions); + } + + + /** + * Resolves 'self', 'static' and 'parent' to the actual class name. + */ + public static function resolve( + string $type, + \ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionProperty $of, + ): string + { + $lower = strtolower($type); + if ($of instanceof \ReflectionFunction) { + return $type; + } elseif ($lower === 'self' || $lower === 'static') { + return $of->getDeclaringClass()->name; + } elseif ($lower === 'parent' && $of->getDeclaringClass()->getParentClass()) { + return $of->getDeclaringClass()->getParentClass()->name; + } else { + return $type; + } + } + + + private function __construct(array $types, string $kind = '|') + { + $o = array_search('null', $types, true); + if ($o !== false) { // null as last + array_splice($types, $o, 1); + $types[] = 'null'; + } + + $this->types = $types; + $this->simple = is_string($types[0]) && ($types[1] ?? 'null') === 'null'; + $this->kind = count($types) > 1 ? $kind : ''; + } + + + public function __toString(): string + { + $multi = count($this->types) > 1; + if ($this->simple) { + return ($multi ? '?' : '') . $this->types[0]; + } + + $res = []; + foreach ($this->types as $type) { + $res[] = $type instanceof self && $multi ? "($type)" : $type; + } + return implode($this->kind, $res); + } + + + /** + * Returns the array of subtypes that make up the compound type as strings. + * @return array<int, string|string[]> + */ + public function getNames(): array + { + return array_map(fn($t) => $t instanceof self ? $t->getNames() : $t, $this->types); + } + + + /** + * Returns the array of subtypes that make up the compound type as Type objects: + * @return self[] + */ + public function getTypes(): array + { + return array_map(fn($t) => $t instanceof self ? $t : new self([$t]), $this->types); + } + + + /** + * Returns the type name for simple types, otherwise null. + */ + public function getSingleName(): ?string + { + return $this->simple + ? $this->types[0] + : null; + } + + + /** + * Returns true whether it is a union type. + */ + public function isUnion(): bool + { + return $this->kind === '|'; + } + + + /** + * Returns true whether it is an intersection type. + */ + public function isIntersection(): bool + { + return $this->kind === '&'; + } + + + /** + * Returns true whether it is a simple type. Single nullable types are also considered to be simple types. + */ + public function isSimple(): bool + { + return $this->simple; + } + + + /** @deprecated use isSimple() */ + public function isSingle(): bool + { + return $this->simple; + } + + + /** + * Returns true whether the type is both a simple and a PHP built-in type. + */ + public function isBuiltin(): bool + { + return $this->simple && Validators::isBuiltinType($this->types[0]); + } + + + /** + * Returns true whether the type is both a simple and a class name. + */ + public function isClass(): bool + { + return $this->simple && !Validators::isBuiltinType($this->types[0]); + } + + + /** + * Determines if type is special class name self/parent/static. + */ + public function isClassKeyword(): bool + { + return $this->simple && Validators::isClassKeyword($this->types[0]); + } + + + /** + * Verifies type compatibility. For example, it checks if a value of a certain type could be passed as a parameter. + */ + public function allows(string $subtype): bool + { + if ($this->types === ['mixed']) { + return true; + } + + $subtype = self::fromString($subtype); + return $subtype->isUnion() + ? Arrays::every($subtype->types, fn($t) => $this->allows2($t instanceof self ? $t->types : [$t])) + : $this->allows2($subtype->types); + } + + + private function allows2(array $subtypes): bool + { + return $this->isUnion() + ? Arrays::some($this->types, fn($t) => $this->allows3($t instanceof self ? $t->types : [$t], $subtypes)) + : $this->allows3($this->types, $subtypes); + } + + + private function allows3(array $types, array $subtypes): bool + { + return Arrays::every( + $types, + fn($type) => Arrays::some( + $subtypes, + fn($subtype) => Validators::isBuiltinType($type) + ? strcasecmp($type, $subtype) === 0 + : is_a($subtype, $type, true) + ) + ); + } +} diff --git a/vendor/nette/utils/src/Utils/Validators.php b/vendor/nette/utils/src/Utils/Validators.php new file mode 100644 index 0000000..8e34fb0 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Validators.php @@ -0,0 +1,411 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + + +/** + * Validation utilities. + */ +class Validators +{ + use Nette\StaticClass; + + private const BuiltinTypes = [ + 'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, + 'never' => 1, 'true' => 1, + ]; + + /** @var array<string,?callable> */ + protected static $validators = [ + // PHP types + 'array' => 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'scalar' => 'is_scalar', + 'string' => 'is_string', + + // pseudo-types + 'callable' => [self::class, 'isCallable'], + 'iterable' => 'is_iterable', + 'list' => [Arrays::class, 'isList'], + 'mixed' => [self::class, 'isMixed'], + 'none' => [self::class, 'isNone'], + 'number' => [self::class, 'isNumber'], + 'numeric' => [self::class, 'isNumeric'], + 'numericint' => [self::class, 'isNumericInt'], + + // string patterns + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'digit' => 'ctype_digit', + 'lower' => 'ctype_lower', + 'pattern' => null, + 'space' => 'ctype_space', + 'unicode' => [self::class, 'isUnicode'], + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + + // syntax validation + 'email' => [self::class, 'isEmail'], + 'identifier' => [self::class, 'isPhpIdentifier'], + 'uri' => [self::class, 'isUri'], + 'url' => [self::class, 'isUrl'], + + // environment validation + 'class' => 'class_exists', + 'interface' => 'interface_exists', + 'directory' => 'is_dir', + 'file' => 'is_file', + 'type' => [self::class, 'isType'], + ]; + + /** @var array<string,callable> */ + protected static $counters = [ + 'string' => 'strlen', + 'unicode' => [Strings::class, 'length'], + 'array' => 'count', + 'list' => 'count', + 'alnum' => 'strlen', + 'alpha' => 'strlen', + 'digit' => 'strlen', + 'lower' => 'strlen', + 'space' => 'strlen', + 'upper' => 'strlen', + 'xdigit' => 'strlen', + ]; + + + /** + * Verifies that the value is of expected types separated by pipe. + * @throws AssertionException + */ + public static function assert(mixed $value, string $expected, string $label = 'variable'): void + { + if (!static::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; + $type = $translate[gettype($value)] ?? gettype($value); + if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { + $type .= ' ' . var_export($value, true); + } elseif (is_object($value)) { + $type .= ' ' . $value::class; + } + + throw new AssertionException("The $label expects to be $expected, $type given."); + } + } + + + /** + * Verifies that element $key in array is of expected types separated by pipe. + * @param mixed[] $array + * @throws AssertionException + */ + public static function assertField( + array $array, + $key, + ?string $expected = null, + string $label = "item '%' in array", + ): void + { + if (!array_key_exists($key, $array)) { + throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); + + } elseif ($expected) { + static::assert($array[$key], $expected, str_replace('%', $key, $label)); + } + } + + + /** + * Verifies that the value is of expected types separated by pipe. + */ + public static function is(mixed $value, string $expected): bool + { + foreach (explode('|', $expected) as $item) { + if (str_ends_with($item, '[]')) { + if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { + return true; + } + + continue; + } elseif (str_starts_with($item, '?')) { + $item = substr($item, 1); + if ($value === null) { + return true; + } + } + + [$type] = $item = explode(':', $item, 2); + if (isset(static::$validators[$type])) { + try { + if (!static::$validators[$type]($value)) { + continue; + } + } catch (\TypeError $e) { + continue; + } + } elseif ($type === 'pattern') { + if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { + return true; + } + + continue; + } elseif (!$value instanceof $type) { + continue; + } + + if (isset($item[1])) { + $length = $value; + if (isset(static::$counters[$type])) { + $length = static::$counters[$type]($value); + } + + $range = explode('..', $item[1]); + if (!isset($range[1])) { + $range[1] = $range[0]; + } + + if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { + continue; + } + } + + return true; + } + + return false; + } + + + /** + * Finds whether all values are of expected types separated by pipe. + * @param mixed[] $values + */ + public static function everyIs(iterable $values, string $expected): bool + { + foreach ($values as $value) { + if (!static::is($value, $expected)) { + return false; + } + } + + return true; + } + + + /** + * Checks if the value is an integer or a float. + */ + public static function isNumber(mixed $value): bool + { + return is_int($value) || is_float($value); + } + + + /** + * Checks if the value is an integer or a integer written in a string. + */ + public static function isNumericInt(mixed $value): bool + { + return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); + } + + + /** + * Checks if the value is a number or a number written in a string. + */ + public static function isNumeric(mixed $value): bool + { + return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?([0-9]++\.?[0-9]*|\.[0-9]+)$#D', $value)); + } + + + /** + * Checks if the value is a syntactically correct callback. + */ + public static function isCallable(mixed $value): bool + { + return $value && is_callable($value, true); + } + + + /** + * Checks if the value is a valid UTF-8 string. + */ + public static function isUnicode(mixed $value): bool + { + return is_string($value) && preg_match('##u', $value); + } + + + /** + * Checks if the value is 0, '', false or null. + */ + public static function isNone(mixed $value): bool + { + return $value == null; // intentionally == + } + + + /** @internal */ + public static function isMixed(): bool + { + return true; + } + + + /** + * Checks if a variable is a zero-based integer indexed array. + * @deprecated use Nette\Utils\Arrays::isList + */ + public static function isList(mixed $value): bool + { + return Arrays::isList($value); + } + + + /** + * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). + * Numbers, strings and DateTime objects can be compared. + */ + public static function isInRange(mixed $value, array $range): bool + { + if ($value === null || !(isset($range[0]) || isset($range[1]))) { + return false; + } + + $limit = $range[0] ?? $range[1]; + if (is_string($limit)) { + $value = (string) $value; + } elseif ($limit instanceof \DateTimeInterface) { + if (!$value instanceof \DateTimeInterface) { + return false; + } + } elseif (is_numeric($value)) { + $value *= 1; + } else { + return false; + } + + return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); + } + + + /** + * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. + */ + public static function isEmail(string $value): bool + { + $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part + $alpha = "a-z\x80-\xFF"; // superset of IDN + return (bool) preg_match(<<<XX + (^(?n) + ("([ !#-[\\]-~]*|\\\\[ -~])+"|$atom+(\\.$atom+)*) # quoted or unquoted + @ + ([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034 + [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain + $)Dix + XX, $value); + } + + + /** + * Checks if the value is a valid URL address. + */ + public static function isUrl(string $value): bool + { + $alpha = "a-z\x80-\xFF"; + return (bool) preg_match(<<<XX + (^(?n) + https?://( + (([-_0-9$alpha]+\\.)* # subdomain + [0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)? # domain + [$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain + |\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3} # IPv4 + |\\[[0-9a-f:]{3,39}\\] # IPv6 + )(:\\d{1,5})? # port + (/\\S*)? # path + (\\?\\S*)? # query + (\\#\\S*)? # fragment + $)Dix + XX, $value); + } + + + /** + * Checks if the value is a valid URI address, that is, actually a string beginning with a syntactically valid schema. + */ + public static function isUri(string $value): bool + { + return (bool) preg_match('#^[a-z\d+\.-]+:\S+$#Di', $value); + } + + + /** + * Checks whether the input is a class, interface or trait. + * @deprecated + */ + public static function isType(string $type): bool + { + return class_exists($type) || interface_exists($type) || trait_exists($type); + } + + + /** + * Checks whether the input is a valid PHP identifier. + */ + public static function isPhpIdentifier(string $value): bool + { + return preg_match('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$#D', $value) === 1; + } + + + /** + * Determines if type is PHP built-in type. Otherwise, it is the class name. + */ + public static function isBuiltinType(string $type): bool + { + return isset(self::BuiltinTypes[strtolower($type)]); + } + + + /** + * Determines if type is special class name self/parent/static. + */ + public static function isClassKeyword(string $name): bool + { + return (bool) preg_match('#^(self|parent|static)$#Di', $name); + } + + + /** + * Checks whether the given type declaration is syntactically valid. + */ + public static function isTypeDeclaration(string $type): bool + { + return (bool) preg_match(<<<'XX' + ~((?n) + \?? (?<type> \\? (?<name> [a-zA-Z_\x7f-\xff][\w\x7f-\xff]*) (\\ (?&name))* ) | + (?<intersection> (?&type) (& (?&type))+ ) | + (?<upart> (?&type) | \( (?&intersection) \) ) (\| (?&upart))+ + )$~xAD + XX, $type); + } +} diff --git a/vendor/nette/utils/src/Utils/exceptions.php b/vendor/nette/utils/src/Utils/exceptions.php new file mode 100644 index 0000000..af949ce --- /dev/null +++ b/vendor/nette/utils/src/Utils/exceptions.php @@ -0,0 +1,50 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + + +/** + * The exception that is thrown when an image error occurs. + */ +class ImageException extends \Exception +{ +} + + +/** + * The exception that indicates invalid image file. + */ +class UnknownImageFileException extends ImageException +{ +} + + +/** + * The exception that indicates error of JSON encoding/decoding. + */ +class JsonException extends \JsonException +{ +} + + +/** + * The exception that indicates error of the last Regexp execution. + */ +class RegexpException extends \Exception +{ +} + + +/** + * The exception that indicates assertion error. + */ +class AssertionException extends \Exception +{ +} diff --git a/vendor/nette/utils/src/compatibility.php b/vendor/nette/utils/src/compatibility.php new file mode 100644 index 0000000..9df5480 --- /dev/null +++ b/vendor/nette/utils/src/compatibility.php @@ -0,0 +1,32 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette\Utils; + +use Nette; + +if (false) { + /** @deprecated use Nette\HtmlStringable */ + interface IHtmlString extends Nette\HtmlStringable + { + } +} elseif (!interface_exists(IHtmlString::class)) { + class_alias(Nette\HtmlStringable::class, IHtmlString::class); +} + +namespace Nette\Localization; + +if (false) { + /** @deprecated use Nette\Localization\Translator */ + interface ITranslator extends Translator + { + } +} elseif (!interface_exists(ITranslator::class)) { + class_alias(Translator::class, ITranslator::class); +} diff --git a/vendor/nette/utils/src/exceptions.php b/vendor/nette/utils/src/exceptions.php new file mode 100644 index 0000000..bbda7dd --- /dev/null +++ b/vendor/nette/utils/src/exceptions.php @@ -0,0 +1,109 @@ +<?php + +/** + * This file is part of the Nette Framework (https://nette.org) + * Copyright (c) 2004 David Grudl (https://davidgrudl.com) + */ + +declare(strict_types=1); + +namespace Nette; + + +/** + * The exception that is thrown when the value of an argument is + * outside the allowable range of values as defined by the invoked method. + */ +class ArgumentOutOfRangeException extends \InvalidArgumentException +{ +} + + +/** + * The exception that is thrown when a method call is invalid for the object's + * current state, method has been invoked at an illegal or inappropriate time. + */ +class InvalidStateException extends \RuntimeException +{ +} + + +/** + * The exception that is thrown when a requested method or operation is not implemented. + */ +class NotImplementedException extends \LogicException +{ +} + + +/** + * The exception that is thrown when an invoked method is not supported. For scenarios where + * it is sometimes possible to perform the requested operation, see InvalidStateException. + */ +class NotSupportedException extends \LogicException +{ +} + + +/** + * The exception that is thrown when a requested method or operation is deprecated. + */ +class DeprecatedException extends NotSupportedException +{ +} + + +/** + * The exception that is thrown when accessing a class member (property or method) fails. + */ +class MemberAccessException extends \Error +{ +} + + +/** + * The exception that is thrown when an I/O error occurs. + */ +class IOException extends \RuntimeException +{ +} + + +/** + * The exception that is thrown when accessing a file that does not exist on disk. + */ +class FileNotFoundException extends IOException +{ +} + + +/** + * The exception that is thrown when part of a file or directory cannot be found. + */ +class DirectoryNotFoundException extends IOException +{ +} + + +/** + * The exception that is thrown when an argument does not match with the expected value. + */ +class InvalidArgumentException extends \InvalidArgumentException +{ +} + + +/** + * The exception that is thrown when an illegal index was requested. + */ +class OutOfRangeException extends \OutOfRangeException +{ +} + + +/** + * The exception that is thrown when a value (typically returned by function) does not match with the expected value. + */ +class UnexpectedValueException extends \UnexpectedValueException +{ +} diff --git a/vendor/psr/event-dispatcher/.editorconfig b/vendor/psr/event-dispatcher/.editorconfig new file mode 100644 index 0000000..164f092 --- /dev/null +++ b/vendor/psr/event-dispatcher/.editorconfig @@ -0,0 +1,15 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[Makefile] +indent_style = tab diff --git a/vendor/psr/event-dispatcher/.gitignore b/vendor/psr/event-dispatcher/.gitignore new file mode 100644 index 0000000..3a9875b --- /dev/null +++ b/vendor/psr/event-dispatcher/.gitignore @@ -0,0 +1,2 @@ +/vendor/ +composer.lock diff --git a/vendor/psr/event-dispatcher/LICENSE b/vendor/psr/event-dispatcher/LICENSE new file mode 100644 index 0000000..3f1559b --- /dev/null +++ b/vendor/psr/event-dispatcher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 PHP-FIG + +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. diff --git a/vendor/psr/event-dispatcher/README.md b/vendor/psr/event-dispatcher/README.md new file mode 100644 index 0000000..294214a --- /dev/null +++ b/vendor/psr/event-dispatcher/README.md @@ -0,0 +1,6 @@ +PSR Event Dispatcher +==================== + +This repository holds the interfaces related to [PSR-14](http://www.php-fig.org/psr/psr-14/). + +Note that this is not an Event Dispatcher implementation of its own. It is merely interfaces that describe the components of an Event Dispatcher. See the specification for more details. diff --git a/vendor/psr/event-dispatcher/composer.json b/vendor/psr/event-dispatcher/composer.json new file mode 100644 index 0000000..667a714 --- /dev/null +++ b/vendor/psr/event-dispatcher/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/event-dispatcher", + "description": "Standard interfaces for event handling.", + "type": "library", + "keywords": ["psr", "psr-14", "events"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=7.2.0" + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php new file mode 100644 index 0000000..4306fa9 --- /dev/null +++ b/vendor/psr/event-dispatcher/src/EventDispatcherInterface.php @@ -0,0 +1,21 @@ +<?php +declare(strict_types=1); + +namespace Psr\EventDispatcher; + +/** + * Defines a dispatcher for events. + */ +interface EventDispatcherInterface +{ + /** + * Provide all relevant listeners with an event to process. + * + * @param object $event + * The object to process. + * + * @return object + * The Event that was passed, now modified by listeners. + */ + public function dispatch(object $event); +} diff --git a/vendor/psr/event-dispatcher/src/ListenerProviderInterface.php b/vendor/psr/event-dispatcher/src/ListenerProviderInterface.php new file mode 100644 index 0000000..bdacb6e --- /dev/null +++ b/vendor/psr/event-dispatcher/src/ListenerProviderInterface.php @@ -0,0 +1,19 @@ +<?php +declare(strict_types=1); + +namespace Psr\EventDispatcher; + +/** + * Mapper from an event to the listeners that are applicable to that event. + */ +interface ListenerProviderInterface +{ + /** + * @param object $event + * An event for which to return the relevant listeners. + * @return iterable[callable] + * An iterable (array, iterator, or generator) of callables. Each + * callable MUST be type-compatible with $event. + */ + public function getListenersForEvent(object $event) : iterable; +} diff --git a/vendor/psr/event-dispatcher/src/StoppableEventInterface.php b/vendor/psr/event-dispatcher/src/StoppableEventInterface.php new file mode 100644 index 0000000..5955081 --- /dev/null +++ b/vendor/psr/event-dispatcher/src/StoppableEventInterface.php @@ -0,0 +1,26 @@ +<?php +declare(strict_types=1); + +namespace Psr\EventDispatcher; + +/** + * An Event whose processing may be interrupted when the event has been handled. + * + * A Dispatcher implementation MUST check to determine if an Event + * is marked as stopped after each listener is called. If it is then it should + * return immediately without calling any further Listeners. + */ +interface StoppableEventInterface +{ + /** + * Is propagation stopped? + * + * This will typically only be used by the Dispatcher to determine if the + * previous listener halted propagation. + * + * @return bool + * True if the Event is complete and no further listeners should be called. + * False to continue calling listeners. + */ + public function isPropagationStopped() : bool; +} diff --git a/vendor/symfony/deprecation-contracts/CHANGELOG.md b/vendor/symfony/deprecation-contracts/CHANGELOG.md new file mode 100644 index 0000000..7932e26 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/CHANGELOG.md @@ -0,0 +1,5 @@ +CHANGELOG +========= + +The changelog is maintained for all Symfony contracts at the following URL: +https://github.com/symfony/contracts/blob/main/CHANGELOG.md diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE new file mode 100644 index 0000000..0ed3a24 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020-present Fabien Potencier + +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. diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md new file mode 100644 index 0000000..9814864 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/README.md @@ -0,0 +1,26 @@ +Symfony Deprecation Contracts +============================= + +A generic function and convention to trigger deprecation notices. + +This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices. + +By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component, +the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments. + +The function requires at least 3 arguments: + - the name of the Composer package that is triggering the deprecation + - the version of the package that introduced the deprecation + - the message of the deprecation + - more arguments can be provided: they will be inserted in the message using `printf()` formatting + +Example: +```php +trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin'); +``` + +This will generate the following message: +`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.` + +While not recommended, the deprecation notices can be completely ignored by declaring an empty +`function trigger_deprecation() {}` in your application. diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json new file mode 100644 index 0000000..774200f --- /dev/null +++ b/vendor/symfony/deprecation-contracts/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/deprecation-contracts", + "type": "library", + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=8.1" + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "3.3-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + } +} diff --git a/vendor/symfony/deprecation-contracts/function.php b/vendor/symfony/deprecation-contracts/function.php new file mode 100644 index 0000000..2d56512 --- /dev/null +++ b/vendor/symfony/deprecation-contracts/function.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (!function_exists('trigger_deprecation')) { + /** + * Triggers a silenced deprecation notice. + * + * @param string $package The name of the Composer package that is triggering the deprecation + * @param string $version The version of the package that introduced the deprecation + * @param string $message The message of the deprecation + * @param mixed ...$args Values to insert in the message using printf() formatting + * + * @author Nicolas Grekas <p@tchwork.com> + */ + function trigger_deprecation(string $package, string $version, string $message, mixed ...$args): void + { + @trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED); + } +} diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php new file mode 100644 index 0000000..ba75a2c --- /dev/null +++ b/vendor/symfony/polyfill-ctype/Ctype.php @@ -0,0 +1,232 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Ctype; + +/** + * Ctype implementation through regex. + * + * @internal + * + * @author Gert de Pagter <BackEndTea@gmail.com> + */ +final class Ctype +{ + /** + * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise. + * + * @see https://php.net/ctype-alnum + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alnum($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is a letter, FALSE otherwise. + * + * @see https://php.net/ctype-alpha + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_alpha($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text); + } + + /** + * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise. + * + * @see https://php.net/ctype-cntrl + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_cntrl($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text); + } + + /** + * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise. + * + * @see https://php.net/ctype-digit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_digit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text); + } + + /** + * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise. + * + * @see https://php.net/ctype-graph + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_graph($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text); + } + + /** + * Returns TRUE if every character in text is a lowercase letter. + * + * @see https://php.net/ctype-lower + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_lower($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text); + } + + /** + * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all. + * + * @see https://php.net/ctype-print + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_print($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text); + } + + /** + * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise. + * + * @see https://php.net/ctype-punct + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_punct($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text); + } + + /** + * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters. + * + * @see https://php.net/ctype-space + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_space($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text); + } + + /** + * Returns TRUE if every character in text is an uppercase letter. + * + * @see https://php.net/ctype-upper + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_upper($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text); + } + + /** + * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise. + * + * @see https://php.net/ctype-xdigit + * + * @param mixed $text + * + * @return bool + */ + public static function ctype_xdigit($text) + { + $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__); + + return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text); + } + + /** + * Converts integers to their char versions according to normal ctype behaviour, if needed. + * + * If an integer between -128 and 255 inclusive is provided, + * it is interpreted as the ASCII value of a single character + * (negative values have 256 added in order to allow characters in the Extended ASCII range). + * Any other integer is interpreted as a string containing the decimal digits of the integer. + * + * @param mixed $int + * @param string $function + * + * @return mixed + */ + private static function convert_int_to_char_for_ctype($int, $function) + { + if (!\is_int($int)) { + return $int; + } + + if ($int < -128 || $int > 255) { + return (string) $int; + } + + if (\PHP_VERSION_ID >= 80100) { + @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED); + } + + if ($int < 0) { + $int += 256; + } + + return \chr($int); + } +} diff --git a/vendor/symfony/polyfill-ctype/LICENSE b/vendor/symfony/polyfill-ctype/LICENSE new file mode 100644 index 0000000..3f853aa --- /dev/null +++ b/vendor/symfony/polyfill-ctype/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018-2019 Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md new file mode 100644 index 0000000..b144d03 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/README.md @@ -0,0 +1,12 @@ +Symfony Polyfill / Ctype +======================== + +This component provides `ctype_*` functions to users who run php versions without the ctype extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php new file mode 100644 index 0000000..d54524b --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap.php @@ -0,0 +1,50 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('ctype_alnum')) { + function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit($text) { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph($text) { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower($text) { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print($text) { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct($text) { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space($text) { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper($text) { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php new file mode 100644 index 0000000..ab2f861 --- /dev/null +++ b/vendor/symfony/polyfill-ctype/bootstrap80.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Ctype as p; + +if (!function_exists('ctype_alnum')) { + function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } +} +if (!function_exists('ctype_alpha')) { + function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } +} +if (!function_exists('ctype_cntrl')) { + function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } +} +if (!function_exists('ctype_digit')) { + function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } +} +if (!function_exists('ctype_graph')) { + function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } +} +if (!function_exists('ctype_lower')) { + function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } +} +if (!function_exists('ctype_print')) { + function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } +} +if (!function_exists('ctype_punct')) { + function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } +} +if (!function_exists('ctype_space')) { + function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } +} +if (!function_exists('ctype_upper')) { + function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } +} +if (!function_exists('ctype_xdigit')) { + function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } +} diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json new file mode 100644 index 0000000..1b3efff --- /dev/null +++ b/vendor/symfony/polyfill-ctype/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-ctype", + "type": "library", + "description": "Symfony polyfill for ctype functions", + "keywords": ["polyfill", "compatibility", "portable", "ctype"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..bce5c4a --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,874 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class Mbstring +{ + public const MB_CASE_FOLD = \PHP_INT_MAX; + + private const CASE_FOLD = [ + ['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"], + ['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'], + ]; + + private static $encodingList = ['ASCII', 'UTF-8']; + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, \ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = self::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', \E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return self::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || (80000 > \PHP_VERSION_ID && !$convmap)) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (\MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, [__CLASS__, 'title_case'], $s); + } else { + if (\MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4]; + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $normalizedEncoding = self::getEncoding($encoding); + + if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) { + self::$internalEncoding = $normalizedEncoding; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($encoding) must be a valid encoding, "%s" given', $encoding)); + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($normalizedLang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $normalizedLang; + + return true; + } + + if (80000 > \PHP_VERSION_ID) { + return false; + } + + throw new \ValueError(sprintf('Argument #1 ($language) must be a valid language, "%s" given', $lang)); + } + + public static function mb_list_encodings() + { + return ['UTF-8']; + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return ['utf8']; + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + if (80000 > \PHP_VERSION_ID) { + trigger_error(__METHOD__.': Empty delimiter', \E_USER_WARNING); + + return false; + } + + return 0; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = '' !== $needle || 80000 > \PHP_VERSION_ID + ? iconv_strrpos($haystack, $needle, $encoding) + : self::mb_strlen($haystack, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + if (80000 > \PHP_VERSION_ID) { + trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING); + + return false; + } + + throw new \ValueError('Argument #2 ($length) must be greater than 0'); + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY); + } + + $result = []; + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, \MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (null === $c) { + return 'none'; + } + if (0 === strcasecmp($c, 'none')) { + return true; + } + if (80000 > \PHP_VERSION_ID) { + return false; + } + if (\is_int($c) || 'long' === $c || 'entity' === $c) { + return false; + } + + throw new \ValueError('Argument #1 ($substitute_character) must be "none", "long", "entity" or a valid codepoint'); + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = [ + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ]; + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], \ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], \MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], \MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..478b40d --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..fac60b0 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ +<?php + +return array ( + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i̇', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ +<?php + +// from Case_Ignorable in https://unicode.org/Public/UNIDATA/DerivedCoreProperties.txt + +return '/(?<![\x{0027}\x{002E}\x{003A}\x{005E}\x{0060}\x{00A8}\x{00AD}\x{00AF}\x{00B4}\x{00B7}\x{00B8}\x{02B0}-\x{02C1}\x{02C2}-\x{02C5}\x{02C6}-\x{02D1}\x{02D2}-\x{02DF}\x{02E0}-\x{02E4}\x{02E5}-\x{02EB}\x{02EC}\x{02ED}\x{02EE}\x{02EF}-\x{02FF}\x{0300}-\x{036F}\x{0374}\x{0375}\x{037A}\x{0384}-\x{0385}\x{0387}\x{0483}-\x{0487}\x{0488}-\x{0489}\x{0559}\x{0591}-\x{05BD}\x{05BF}\x{05C1}-\x{05C2}\x{05C4}-\x{05C5}\x{05C7}\x{05F4}\x{0600}-\x{0605}\x{0610}-\x{061A}\x{061C}\x{0640}\x{064B}-\x{065F}\x{0670}\x{06D6}-\x{06DC}\x{06DD}\x{06DF}-\x{06E4}\x{06E5}-\x{06E6}\x{06E7}-\x{06E8}\x{06EA}-\x{06ED}\x{070F}\x{0711}\x{0730}-\x{074A}\x{07A6}-\x{07B0}\x{07EB}-\x{07F3}\x{07F4}-\x{07F5}\x{07FA}\x{07FD}\x{0816}-\x{0819}\x{081A}\x{081B}-\x{0823}\x{0824}\x{0825}-\x{0827}\x{0828}\x{0829}-\x{082D}\x{0859}-\x{085B}\x{08D3}-\x{08E1}\x{08E2}\x{08E3}-\x{0902}\x{093A}\x{093C}\x{0941}-\x{0948}\x{094D}\x{0951}-\x{0957}\x{0962}-\x{0963}\x{0971}\x{0981}\x{09BC}\x{09C1}-\x{09C4}\x{09CD}\x{09E2}-\x{09E3}\x{09FE}\x{0A01}-\x{0A02}\x{0A3C}\x{0A41}-\x{0A42}\x{0A47}-\x{0A48}\x{0A4B}-\x{0A4D}\x{0A51}\x{0A70}-\x{0A71}\x{0A75}\x{0A81}-\x{0A82}\x{0ABC}\x{0AC1}-\x{0AC5}\x{0AC7}-\x{0AC8}\x{0ACD}\x{0AE2}-\x{0AE3}\x{0AFA}-\x{0AFF}\x{0B01}\x{0B3C}\x{0B3F}\x{0B41}-\x{0B44}\x{0B4D}\x{0B56}\x{0B62}-\x{0B63}\x{0B82}\x{0BC0}\x{0BCD}\x{0C00}\x{0C04}\x{0C3E}-\x{0C40}\x{0C46}-\x{0C48}\x{0C4A}-\x{0C4D}\x{0C55}-\x{0C56}\x{0C62}-\x{0C63}\x{0C81}\x{0CBC}\x{0CBF}\x{0CC6}\x{0CCC}-\x{0CCD}\x{0CE2}-\x{0CE3}\x{0D00}-\x{0D01}\x{0D3B}-\x{0D3C}\x{0D41}-\x{0D44}\x{0D4D}\x{0D62}-\x{0D63}\x{0DCA}\x{0DD2}-\x{0DD4}\x{0DD6}\x{0E31}\x{0E34}-\x{0E3A}\x{0E46}\x{0E47}-\x{0E4E}\x{0EB1}\x{0EB4}-\x{0EB9}\x{0EBB}-\x{0EBC}\x{0EC6}\x{0EC8}-\x{0ECD}\x{0F18}-\x{0F19}\x{0F35}\x{0F37}\x{0F39}\x{0F71}-\x{0F7E}\x{0F80}-\x{0F84}\x{0F86}-\x{0F87}\x{0F8D}-\x{0F97}\x{0F99}-\x{0FBC}\x{0FC6}\x{102D}-\x{1030}\x{1032}-\x{1037}\x{1039}-\x{103A}\x{103D}-\x{103E}\x{1058}-\x{1059}\x{105E}-\x{1060}\x{1071}-\x{1074}\x{1082}\x{1085}-\x{1086}\x{108D}\x{109D}\x{10FC}\x{135D}-\x{135F}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}-\x{1753}\x{1772}-\x{1773}\x{17B4}-\x{17B5}\x{17B7}-\x{17BD}\x{17C6}\x{17C9}-\x{17D3}\x{17D7}\x{17DD}\x{180B}-\x{180D}\x{180E}\x{1843}\x{1885}-\x{1886}\x{18A9}\x{1920}-\x{1922}\x{1927}-\x{1928}\x{1932}\x{1939}-\x{193B}\x{1A17}-\x{1A18}\x{1A1B}\x{1A56}\x{1A58}-\x{1A5E}\x{1A60}\x{1A62}\x{1A65}-\x{1A6C}\x{1A73}-\x{1A7C}\x{1A7F}\x{1AA7}\x{1AB0}-\x{1ABD}\x{1ABE}\x{1B00}-\x{1B03}\x{1B34}\x{1B36}-\x{1B3A}\x{1B3C}\x{1B42}\x{1B6B}-\x{1B73}\x{1B80}-\x{1B81}\x{1BA2}-\x{1BA5}\x{1BA8}-\x{1BA9}\x{1BAB}-\x{1BAD}\x{1BE6}\x{1BE8}-\x{1BE9}\x{1BED}\x{1BEF}-\x{1BF1}\x{1C2C}-\x{1C33}\x{1C36}-\x{1C37}\x{1C78}-\x{1C7D}\x{1CD0}-\x{1CD2}\x{1CD4}-\x{1CE0}\x{1CE2}-\x{1CE8}\x{1CED}\x{1CF4}\x{1CF8}-\x{1CF9}\x{1D2C}-\x{1D6A}\x{1D78}\x{1D9B}-\x{1DBF}\x{1DC0}-\x{1DF9}\x{1DFB}-\x{1DFF}\x{1FBD}\x{1FBF}-\x{1FC1}\x{1FCD}-\x{1FCF}\x{1FDD}-\x{1FDF}\x{1FED}-\x{1FEF}\x{1FFD}-\x{1FFE}\x{200B}-\x{200F}\x{2018}\x{2019}\x{2024}\x{2027}\x{202A}-\x{202E}\x{2060}-\x{2064}\x{2066}-\x{206F}\x{2071}\x{207F}\x{2090}-\x{209C}\x{20D0}-\x{20DC}\x{20DD}-\x{20E0}\x{20E1}\x{20E2}-\x{20E4}\x{20E5}-\x{20F0}\x{2C7C}-\x{2C7D}\x{2CEF}-\x{2CF1}\x{2D6F}\x{2D7F}\x{2DE0}-\x{2DFF}\x{2E2F}\x{3005}\x{302A}-\x{302D}\x{3031}-\x{3035}\x{303B}\x{3099}-\x{309A}\x{309B}-\x{309C}\x{309D}-\x{309E}\x{30FC}-\x{30FE}\x{A015}\x{A4F8}-\x{A4FD}\x{A60C}\x{A66F}\x{A670}-\x{A672}\x{A674}-\x{A67D}\x{A67F}\x{A69C}-\x{A69D}\x{A69E}-\x{A69F}\x{A6F0}-\x{A6F1}\x{A700}-\x{A716}\x{A717}-\x{A71F}\x{A720}-\x{A721}\x{A770}\x{A788}\x{A789}-\x{A78A}\x{A7F8}-\x{A7F9}\x{A802}\x{A806}\x{A80B}\x{A825}-\x{A826}\x{A8C4}-\x{A8C5}\x{A8E0}-\x{A8F1}\x{A8FF}\x{A926}-\x{A92D}\x{A947}-\x{A951}\x{A980}-\x{A982}\x{A9B3}\x{A9B6}-\x{A9B9}\x{A9BC}\x{A9CF}\x{A9E5}\x{A9E6}\x{AA29}-\x{AA2E}\x{AA31}-\x{AA32}\x{AA35}-\x{AA36}\x{AA43}\x{AA4C}\x{AA70}\x{AA7C}\x{AAB0}\x{AAB2}-\x{AAB4}\x{AAB7}-\x{AAB8}\x{AABE}-\x{AABF}\x{AAC1}\x{AADD}\x{AAEC}-\x{AAED}\x{AAF3}-\x{AAF4}\x{AAF6}\x{AB5B}\x{AB5C}-\x{AB5F}\x{ABE5}\x{ABE8}\x{ABED}\x{FB1E}\x{FBB2}-\x{FBC1}\x{FE00}-\x{FE0F}\x{FE13}\x{FE20}-\x{FE2F}\x{FE52}\x{FE55}\x{FEFF}\x{FF07}\x{FF0E}\x{FF1A}\x{FF3E}\x{FF40}\x{FF70}\x{FF9E}-\x{FF9F}\x{FFE3}\x{FFF9}-\x{FFFB}\x{101FD}\x{102E0}\x{10376}-\x{1037A}\x{10A01}-\x{10A03}\x{10A05}-\x{10A06}\x{10A0C}-\x{10A0F}\x{10A38}-\x{10A3A}\x{10A3F}\x{10AE5}-\x{10AE6}\x{10D24}-\x{10D27}\x{10F46}-\x{10F50}\x{11001}\x{11038}-\x{11046}\x{1107F}-\x{11081}\x{110B3}-\x{110B6}\x{110B9}-\x{110BA}\x{110BD}\x{110CD}\x{11100}-\x{11102}\x{11127}-\x{1112B}\x{1112D}-\x{11134}\x{11173}\x{11180}-\x{11181}\x{111B6}-\x{111BE}\x{111C9}-\x{111CC}\x{1122F}-\x{11231}\x{11234}\x{11236}-\x{11237}\x{1123E}\x{112DF}\x{112E3}-\x{112EA}\x{11300}-\x{11301}\x{1133B}-\x{1133C}\x{11340}\x{11366}-\x{1136C}\x{11370}-\x{11374}\x{11438}-\x{1143F}\x{11442}-\x{11444}\x{11446}\x{1145E}\x{114B3}-\x{114B8}\x{114BA}\x{114BF}-\x{114C0}\x{114C2}-\x{114C3}\x{115B2}-\x{115B5}\x{115BC}-\x{115BD}\x{115BF}-\x{115C0}\x{115DC}-\x{115DD}\x{11633}-\x{1163A}\x{1163D}\x{1163F}-\x{11640}\x{116AB}\x{116AD}\x{116B0}-\x{116B5}\x{116B7}\x{1171D}-\x{1171F}\x{11722}-\x{11725}\x{11727}-\x{1172B}\x{1182F}-\x{11837}\x{11839}-\x{1183A}\x{11A01}-\x{11A0A}\x{11A33}-\x{11A38}\x{11A3B}-\x{11A3E}\x{11A47}\x{11A51}-\x{11A56}\x{11A59}-\x{11A5B}\x{11A8A}-\x{11A96}\x{11A98}-\x{11A99}\x{11C30}-\x{11C36}\x{11C38}-\x{11C3D}\x{11C3F}\x{11C92}-\x{11CA7}\x{11CAA}-\x{11CB0}\x{11CB2}-\x{11CB3}\x{11CB5}-\x{11CB6}\x{11D31}-\x{11D36}\x{11D3A}\x{11D3C}-\x{11D3D}\x{11D3F}-\x{11D45}\x{11D47}\x{11D90}-\x{11D91}\x{11D95}\x{11D97}\x{11EF3}-\x{11EF4}\x{16AF0}-\x{16AF4}\x{16B30}-\x{16B36}\x{16B40}-\x{16B43}\x{16F8F}-\x{16F92}\x{16F93}-\x{16F9F}\x{16FE0}-\x{16FE1}\x{1BC9D}-\x{1BC9E}\x{1BCA0}-\x{1BCA3}\x{1D167}-\x{1D169}\x{1D173}-\x{1D17A}\x{1D17B}-\x{1D182}\x{1D185}-\x{1D18B}\x{1D1AA}-\x{1D1AD}\x{1D242}-\x{1D244}\x{1DA00}-\x{1DA36}\x{1DA3B}-\x{1DA6C}\x{1DA75}\x{1DA84}\x{1DA9B}-\x{1DA9F}\x{1DAA1}-\x{1DAAF}\x{1E000}-\x{1E006}\x{1E008}-\x{1E018}\x{1E01B}-\x{1E021}\x{1E023}-\x{1E024}\x{1E026}-\x{1E02A}\x{1E8D0}-\x{1E8D6}\x{1E944}-\x{1E94A}\x{1F3FB}-\x{1F3FF}\x{E0001}\x{E0020}-\x{E007F}\x{E0100}-\x{E01EF}])(\pL)(\pL*+)/u'; diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php new file mode 100644 index 0000000..56b9cb8 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/upperCase.php @@ -0,0 +1,1489 @@ +<?php + +return array ( + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ἈΙ', + 'ᾁ' => 'ἉΙ', + 'ᾂ' => 'ἊΙ', + 'ᾃ' => 'ἋΙ', + 'ᾄ' => 'ἌΙ', + 'ᾅ' => 'ἍΙ', + 'ᾆ' => 'ἎΙ', + 'ᾇ' => 'ἏΙ', + 'ᾐ' => 'ἨΙ', + 'ᾑ' => 'ἩΙ', + 'ᾒ' => 'ἪΙ', + 'ᾓ' => 'ἫΙ', + 'ᾔ' => 'ἬΙ', + 'ᾕ' => 'ἭΙ', + 'ᾖ' => 'ἮΙ', + 'ᾗ' => 'ἯΙ', + 'ᾠ' => 'ὨΙ', + 'ᾡ' => 'ὩΙ', + 'ᾢ' => 'ὪΙ', + 'ᾣ' => 'ὫΙ', + 'ᾤ' => 'ὬΙ', + 'ᾥ' => 'ὭΙ', + 'ᾦ' => 'ὮΙ', + 'ᾧ' => 'ὯΙ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ΑΙ', + 'ι' => 'Ι', + 'ῃ' => 'ΗΙ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ΩΙ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', + 'ß' => 'SS', + 'ff' => 'FF', + 'fi' => 'FI', + 'fl' => 'FL', + 'ffi' => 'FFI', + 'ffl' => 'FFL', + 'ſt' => 'ST', + 'st' => 'ST', + 'և' => 'ԵՒ', + 'ﬓ' => 'ՄՆ', + 'ﬔ' => 'ՄԵ', + 'ﬕ' => 'ՄԻ', + 'ﬖ' => 'ՎՆ', + 'ﬗ' => 'ՄԽ', + 'ʼn' => 'ʼN', + 'ΐ' => 'Ϊ́', + 'ΰ' => 'Ϋ́', + 'ǰ' => 'J̌', + 'ẖ' => 'H̱', + 'ẗ' => 'T̈', + 'ẘ' => 'W̊', + 'ẙ' => 'Y̊', + 'ẚ' => 'Aʾ', + 'ὐ' => 'Υ̓', + 'ὒ' => 'Υ̓̀', + 'ὔ' => 'Υ̓́', + 'ὖ' => 'Υ̓͂', + 'ᾶ' => 'Α͂', + 'ῆ' => 'Η͂', + 'ῒ' => 'Ϊ̀', + 'ΐ' => 'Ϊ́', + 'ῖ' => 'Ι͂', + 'ῗ' => 'Ϊ͂', + 'ῢ' => 'Ϋ̀', + 'ΰ' => 'Ϋ́', + 'ῤ' => 'Ρ̓', + 'ῦ' => 'Υ͂', + 'ῧ' => 'Ϋ͂', + 'ῶ' => 'Ω͂', + 'ᾈ' => 'ἈΙ', + 'ᾉ' => 'ἉΙ', + 'ᾊ' => 'ἊΙ', + 'ᾋ' => 'ἋΙ', + 'ᾌ' => 'ἌΙ', + 'ᾍ' => 'ἍΙ', + 'ᾎ' => 'ἎΙ', + 'ᾏ' => 'ἏΙ', + 'ᾘ' => 'ἨΙ', + 'ᾙ' => 'ἩΙ', + 'ᾚ' => 'ἪΙ', + 'ᾛ' => 'ἫΙ', + 'ᾜ' => 'ἬΙ', + 'ᾝ' => 'ἭΙ', + 'ᾞ' => 'ἮΙ', + 'ᾟ' => 'ἯΙ', + 'ᾨ' => 'ὨΙ', + 'ᾩ' => 'ὩΙ', + 'ᾪ' => 'ὪΙ', + 'ᾫ' => 'ὫΙ', + 'ᾬ' => 'ὬΙ', + 'ᾭ' => 'ὭΙ', + 'ᾮ' => 'ὮΙ', + 'ᾯ' => 'ὯΙ', + 'ᾼ' => 'ΑΙ', + 'ῌ' => 'ΗΙ', + 'ῼ' => 'ΩΙ', + 'ᾲ' => 'ᾺΙ', + 'ᾴ' => 'ΆΙ', + 'ῂ' => 'ῊΙ', + 'ῄ' => 'ΉΙ', + 'ῲ' => 'ῺΙ', + 'ῴ' => 'ΏΙ', + 'ᾷ' => 'Α͂Ι', + 'ῇ' => 'Η͂Ι', + 'ῷ' => 'Ω͂Ι', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..1fedd1f --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (\PHP_VERSION_ID >= 80000) { + return require __DIR__.'/bootstrap80.php'; +} + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = "\r\n", $indent = 0) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = null) { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/bootstrap80.php b/vendor/symfony/polyfill-mbstring/bootstrap80.php new file mode 100644 index 0000000..82f5ac4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap80.php @@ -0,0 +1,143 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader(?string $string): string { return p\Mbstring::mb_decode_mimeheader((string) $string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader(?string $string, ?string $charset = null, ?string $transfer_encoding = null, ?string $newline = "\r\n", ?int $indent = 0): string { return p\Mbstring::mb_encode_mimeheader((string) $string, $charset, $transfer_encoding, (string) $newline, (int) $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity(?string $string, array $map, ?string $encoding = null): string { return p\Mbstring::mb_decode_numericentity((string) $string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity(?string $string, array $map, ?string $encoding = null, ?bool $hex = false): string { return p\Mbstring::mb_encode_numericentity((string) $string, $map, $encoding, (bool) $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case(?string $string, ?int $mode, ?string $encoding = null): string { return p\Mbstring::mb_convert_case((string) $string, (int) $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding(?string $encoding = null): string|bool { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language(?string $language = null): string|bool { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings(): array { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases(?string $encoding): array { return p\Mbstring::mb_encoding_aliases((string) $encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding(array|string|null $value = null, ?string $encoding = null): bool { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; } +} +if (!function_exists('mb_strlen')) { + function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtolower((string) $string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper(?string $string, ?string $encoding = null): string { return p\Mbstring::mb_strtoupper((string) $string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character(string|int|null $substitute_character = null): string|int|bool { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr(?string $string, ?int $start, ?int $length = null, ?string $encoding = null): string { return p\Mbstring::mb_substr((string) $string, (int) $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_stripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strripos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos(?string $haystack, ?string $needle, ?int $offset = 0, ?string $encoding = null): int|false { return p\Mbstring::mb_strrpos((string) $haystack, (string) $needle, (int) $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strstr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info(?string $type = 'all'): array|string|int|false { return p\Mbstring::mb_get_info((string) $type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output(?string $encoding = null): string|bool { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strwidth((string) $string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count(?string $haystack, ?string $needle, ?string $encoding = null): int { return p\Mbstring::mb_substr_count((string) $haystack, (string) $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler(?string $string, ?int $status): string { return p\Mbstring::mb_output_handler((string) $string, (int) $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input(?string $type = null): array|string|false { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + function mb_convert_variables(?string $to_encoding, array|string|null $from_encoding, mixed &$var, mixed &...$vars): string|false { return p\Mbstring::mb_convert_variables((string) $to_encoding, $from_encoding ?? '', $var, ...$vars); } +} + +if (!function_exists('mb_ord')) { + function mb_ord(?string $string, ?string $encoding = null): int|false { return p\Mbstring::mb_ord((string) $string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr(?int $codepoint, ?string $encoding = null): string|false { return p\Mbstring::mb_chr((int) $codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub(?string $string, ?string $encoding = null): string { $encoding ??= mb_internal_encoding(); return mb_convert_encoding((string) $string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..4489553 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,41 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 Fabien Potencier + +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. diff --git a/vendor/symfony/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..362dd1a --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,115 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan <ion.bazan@gmail.com> + * @author Nico Oelgart <nicoswd@gmail.com> + * @author Nicolas Grekas <p@tchwork.com> + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case \PREG_INTERNAL_ERROR: + return 'Internal error'; + case \PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case \PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case \PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case \PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case \PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case \PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + if ('' === $needle || $needle === $haystack) { + return true; + } + + if ('' === $haystack) { + return false; + } + + $needleLength = \strlen($needle); + + return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength); + } +} diff --git a/vendor/symfony/polyfill-php80/PhpToken.php b/vendor/symfony/polyfill-php80/PhpToken.php new file mode 100644 index 0000000..fe6e691 --- /dev/null +++ b/vendor/symfony/polyfill-php80/PhpToken.php @@ -0,0 +1,103 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Fedonyuk Anton <info@ensostudio.ru> + * + * @internal + */ +class PhpToken implements \Stringable +{ + /** + * @var int + */ + public $id; + + /** + * @var string + */ + public $text; + + /** + * @var int + */ + public $line; + + /** + * @var int + */ + public $pos; + + public function __construct(int $id, string $text, int $line = -1, int $position = -1) + { + $this->id = $id; + $this->text = $text; + $this->line = $line; + $this->pos = $position; + } + + public function getTokenName(): ?string + { + if ('UNKNOWN' === $name = token_name($this->id)) { + $name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text; + } + + return $name; + } + + /** + * @param int|string|array $kind + */ + public function is($kind): bool + { + foreach ((array) $kind as $value) { + if (\in_array($value, [$this->id, $this->text], true)) { + return true; + } + } + + return false; + } + + public function isIgnorable(): bool + { + return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true); + } + + public function __toString(): string + { + return (string) $this->text; + } + + /** + * @return static[] + */ + public static function tokenize(string $code, int $flags = 0): array + { + $line = 1; + $position = 0; + $tokens = token_get_all($code, $flags); + foreach ($tokens as $index => $token) { + if (\is_string($token)) { + $id = \ord($token); + $text = $token; + } else { + [$id, $text, $line] = $token; + } + $tokens[$index] = new static($id, $text, $line, $position); + $position += \strlen($text); + } + + return $tokens; + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..3816c55 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,25 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- [`Stringable`](https://php.net/stringable) interface +- [`fdiv`](https://php.net/fdiv) +- [`ValueError`](https://php.net/valueerror) class +- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`PhpToken`](https://php.net/phptoken) class +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..2b95542 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,31 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#[Attribute(Attribute::TARGET_CLASS)] +final class Attribute +{ + public const TARGET_CLASS = 1; + public const TARGET_FUNCTION = 2; + public const TARGET_METHOD = 4; + public const TARGET_PROPERTY = 8; + public const TARGET_CLASS_CONSTANT = 16; + public const TARGET_PARAMETER = 32; + public const TARGET_ALL = 63; + public const IS_REPEATABLE = 64; + + /** @var int */ + public $flags; + + public function __construct(int $flags = self::TARGET_ALL) + { + $this->flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php new file mode 100644 index 0000000..bd1212f --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/PhpToken.php @@ -0,0 +1,16 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) { + class PhpToken extends Symfony\Polyfill\Php80\PhpToken + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..7c62d75 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,20 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + interface Stringable + { + /** + * @return string + */ + public function __toString(); + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php new file mode 100644 index 0000000..01c6c6c --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php @@ -0,0 +1,16 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class UnhandledMatchError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php new file mode 100644 index 0000000..783dbc2 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/ValueError.php @@ -0,0 +1,16 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +if (\PHP_VERSION_ID < 80000) { + class ValueError extends Error + { + } +} diff --git a/vendor/symfony/polyfill-php80/bootstrap.php b/vendor/symfony/polyfill-php80/bootstrap.php new file mode 100644 index 0000000..e5f7dbc --- /dev/null +++ b/vendor/symfony/polyfill-php80/bootstrap.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier <fabien@symfony.com> + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (\PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', \FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(?string $haystack, ?string $needle): bool { return p\Php80::str_contains($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(?string $haystack, ?string $needle): bool { return p\Php80::str_starts_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(?string $haystack, ?string $needle): bool { return p\Php80::str_ends_with($haystack ?? '', $needle ?? ''); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($resource): int { return p\Php80::get_resource_id($resource); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..bd9a326 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/twig/twig/CHANGELOG b/vendor/twig/twig/CHANGELOG new file mode 100644 index 0000000..fd2c9c6 --- /dev/null +++ b/vendor/twig/twig/CHANGELOG @@ -0,0 +1,167 @@ +# 3.5.1 (2023-02-08) + + * Arrow functions passed to the "reduce" filter now accept the current key as a third argument + * Restores the leniency of the matches twig comparison + * Fix error messages in sandboxed mode for "has some" and "has every" + +# 3.5.0 (2022-12-27) + + * Make Twig\ExpressionParser non-internal + * Add "has some" and "has every" operators + * Add Compile::reset() + * Throw a better runtime error when the "matches" regexp is not valid + * Add "twig *_names" intl functions + * Fix optimizing closures callbacks + * Add a better exception when getting an undefined constant via `constant` + * Fix `if` nodes when outside of a block and with an empty body + +# 3.4.3 (2022-09-28) + + * Fix a security issue on filesystem loader (possibility to load a template outside a configured directory) + +# 3.4.2 (2022-08-12) + + * Allow inherited magic method to still run with calling class + * Fix CallExpression::reflectCallable() throwing TypeError + * Fix typo in naming (currency_code) + +# 3.4.1 (2022-05-17) + +* Fix optimizing non-public named closures + +# 3.4.0 (2022-05-22) + + * Add support for named closures + +# 3.3.10 (2022-04-06) + + * Enable bytecode invalidation when auto_reload is enabled + +# 3.3.9 (2022-03-25) + + * Fix custom escapers when using multiple Twig environments + * Add support for "constant('class', object)" + * Do not reuse internally generated variable names during parsing + +# 3.3.8 (2022-02-04) + + * Fix a security issue when in a sandbox: the `sort` filter must require a Closure for the `arrow` parameter + * Fix deprecation notice on `round` + * Fix call to deprecated `convertToHtml` method + +# 3.3.7 (2022-01-03) + +* Allow more null support when Twig expects a string (for better 8.1 support) +* Only use Commonmark extensions if markdown enabled + +# 3.3.6 (2022-01-03) + +* Only use Commonmark extensions if markdown enabled + +# 3.3.5 (2022-01-03) + +* Allow CommonMark extensions to easily be added +* Allow null when Twig expects a string (for better 8.1 support) +* Make some performance optimizations +* Allow Symfony translation contract v3+ + +# 3.3.4 (2021-11-25) + + * Bump minimum supported Symfony component versions + * Fix a deprecated message + +# 3.3.3 (2021-09-17) + + * Allow Symfony 6 + * Improve compatibility with PHP 8.1 + * Explicitly specify the encoding for mb_ord in JS escaper + +# 3.3.2 (2021-05-16) + + * Revert "Throw a proper exception when a template name is an absolute path (as it has never been supported)" + +# 3.3.1 (2021-05-12) + + * Fix PHP 8.1 compatibility + * Throw a proper exception when a template name is an absolute path (as it has never been supported) + +# 3.3.0 (2021-02-08) + + * Fix macro calls in a "cache" tag + * Add the slug filter + * Allow extra bundle to be compatible with Twig 2 + +# 3.2.1 (2021-01-05) + + * Fix extra bundle compat with older versions of Symfony + +# 3.2.0 (2021-01-05) + + * Add the Cache extension in the "extra" repositories: "cache" tag + * Add "registerUndefinedTokenParserCallback" + * Mark built-in node visitors as @internal + * Fix "odd" not working for negative numbers + +# 3.1.1 (2020-10-27) + + * Fix "include(template_from_string())" + +# 3.1.0 (2020-10-21) + + * Fix sandbox support when using "include(template_from_string())" + * Make round brackets optional for one argument tests like "same as" or "divisible by" + * Add support for ES2015 style object initialisation shortcut { a } is the same as { 'a': a } + +# 3.0.5 (2020-08-05) + + * Fix twig_compare w.r.t. whitespace trimming + * Fix sandbox not disabled if syntax error occurs within {% sandbox %} tag + * Fix a regression when not using a space before an operator + * Restrict callables to closures in filters + * Allow trailing commas in argument lists (in calls as well as definitions) + +# 3.0.4 (2020-07-05) + + * Fix comparison operators + * Fix options not taken into account when using "Michelf\MarkdownExtra" + * Fix "Twig\Extra\Intl\IntlExtension::getCountryName()" to accept "null" as a first argument + * Throw exception in case non-Traversable data is passed to "filter" + * Fix context optimization on PHP 7.4 + * Fix PHP 8 compatibility + * Fix ambiguous syntax parsing + +# 3.0.3 (2020-02-11) + + * Add a check to ensure that iconv() is defined + +# 3.0.2 (2020-02-11) + + * Avoid exceptions when an intl resource is not found + * Fix implementation of case-insensitivity for method names + +# 3.0.1 (2019-12-28) + + * fixed Symfony 5.0 support for the HTML extra extension + +# 3.0.0 (2019-11-15) + + * fixed number formatter in Intl extra extension when using a formatter prototype + +# 3.0.0-BETA1 (2019-11-11) + + * removed the "if" condition support on the "for" tag + * made the in, <, >, <=, >=, ==, and != operators more strict when comparing strings and integers/floats + * removed the "filter" tag + * added type hints everywhere + * changed Environment::resolveTemplate() to always return a TemplateWrapper instance + * removed Template::__toString() + * removed Parser::isReservedMacroName() + * removed SanboxedPrintNode + * removed Node::setTemplateName() + * made classes maked as "@final" final + * removed InitRuntimeInterface, ExistsLoaderInterface, and SourceContextLoaderInterface + * removed the "spaceless" tag + * removed Twig\Environment::getBaseTemplateClass() and Twig\Environment::setBaseTemplateClass() + * removed the "base_template_class" option on Twig\Environment + * bumped minimum PHP version to 7.2 + * removed PSR-0 classes diff --git a/vendor/twig/twig/LICENSE b/vendor/twig/twig/LICENSE new file mode 100644 index 0000000..fd8234e --- /dev/null +++ b/vendor/twig/twig/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009-present by the Twig Team. + +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 Twig 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 OWNER 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. diff --git a/vendor/twig/twig/README.rst b/vendor/twig/twig/README.rst new file mode 100644 index 0000000..fbe7e9a --- /dev/null +++ b/vendor/twig/twig/README.rst @@ -0,0 +1,23 @@ +Twig, the flexible, fast, and secure template language for PHP +============================================================== + +Twig is a template language for PHP. + +Twig uses a syntax similar to the Django and Jinja template languages which +inspired the Twig runtime environment. + +Sponsors +-------- + +.. raw:: html + + <a href="https://blackfire.io/docs/introduction?utm_source=twig&utm_medium=github_readme&utm_campaign=logo"> + <img src="https://static.blackfire.io/assets/intemporals/logo/png/blackfire-io_secondary_horizontal_transparent.png?1" width="255px" alt="Blackfire.io"> + </a> + +More Information +---------------- + +Read the `documentation`_ for more information. + +.. _documentation: https://twig.symfony.com/documentation diff --git a/vendor/twig/twig/composer.json b/vendor/twig/twig/composer.json new file mode 100644 index 0000000..18d3135 --- /dev/null +++ b/vendor/twig/twig/composer.json @@ -0,0 +1,50 @@ +{ + "name": "twig/twig", + "type": "library", + "description": "Twig, the flexible, fast, and secure template language for PHP", + "keywords": ["templating"], + "homepage": "https://twig.symfony.com", + "license": "BSD-3-Clause", + "minimum-stability": "dev", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Twig Team", + "role": "Contributors" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + } + ], + "require": { + "php": ">=7.2.5", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-ctype": "^1.8" + }, + "require-dev": { + "symfony/phpunit-bridge": "^4.4.9|^5.0.9|^6.0", + "psr/container": "^1.0" + }, + "autoload": { + "psr-4" : { + "Twig\\" : "src/" + } + }, + "autoload-dev": { + "psr-4" : { + "Twig\\Tests\\" : "tests/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "3.5-dev" + } + } +} diff --git a/vendor/twig/twig/src/Cache/CacheInterface.php b/vendor/twig/twig/src/Cache/CacheInterface.php new file mode 100644 index 0000000..6e8c409 --- /dev/null +++ b/vendor/twig/twig/src/Cache/CacheInterface.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Cache; + +/** + * Interface implemented by cache classes. + * + * It is highly recommended to always store templates on the filesystem to + * benefit from the PHP opcode cache. This interface is mostly useful if you + * need to implement a custom strategy for storing templates on the filesystem. + * + * @author Andrew Tch <andrew@noop.lv> + */ +interface CacheInterface +{ + /** + * Generates a cache key for the given template class name. + */ + public function generateKey(string $name, string $className): string; + + /** + * Writes the compiled template to cache. + * + * @param string $content The template representation as a PHP class + */ + public function write(string $key, string $content): void; + + /** + * Loads a template from the cache. + */ + public function load(string $key): void; + + /** + * Returns the modification timestamp of a key. + */ + public function getTimestamp(string $key): int; +} diff --git a/vendor/twig/twig/src/Cache/FilesystemCache.php b/vendor/twig/twig/src/Cache/FilesystemCache.php new file mode 100644 index 0000000..e075563 --- /dev/null +++ b/vendor/twig/twig/src/Cache/FilesystemCache.php @@ -0,0 +1,87 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Cache; + +/** + * Implements a cache on the filesystem. + * + * @author Andrew Tch <andrew@noop.lv> + */ +class FilesystemCache implements CacheInterface +{ + public const FORCE_BYTECODE_INVALIDATION = 1; + + private $directory; + private $options; + + public function __construct(string $directory, int $options = 0) + { + $this->directory = rtrim($directory, '\/').'/'; + $this->options = $options; + } + + public function generateKey(string $name, string $className): string + { + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $className); + + return $this->directory.$hash[0].$hash[1].'/'.$hash.'.php'; + } + + public function load(string $key): void + { + if (is_file($key)) { + @include_once $key; + } + } + + public function write(string $key, string $content): void + { + $dir = \dirname($key); + if (!is_dir($dir)) { + if (false === @mkdir($dir, 0777, true)) { + clearstatcache(true, $dir); + if (!is_dir($dir)) { + throw new \RuntimeException(sprintf('Unable to create the cache directory (%s).', $dir)); + } + } + } elseif (!is_writable($dir)) { + throw new \RuntimeException(sprintf('Unable to write in the cache directory (%s).', $dir)); + } + + $tmpFile = tempnam($dir, basename($key)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $key)) { + @chmod($key, 0666 & ~umask()); + + if (self::FORCE_BYTECODE_INVALIDATION == ($this->options & self::FORCE_BYTECODE_INVALIDATION)) { + // Compile cached file into bytecode cache + if (\function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) { + @opcache_invalidate($key, true); + } elseif (\function_exists('apc_compile_file')) { + apc_compile_file($key); + } + } + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $key)); + } + + public function getTimestamp(string $key): int + { + if (!is_file($key)) { + return 0; + } + + return (int) @filemtime($key); + } +} diff --git a/vendor/twig/twig/src/Cache/NullCache.php b/vendor/twig/twig/src/Cache/NullCache.php new file mode 100644 index 0000000..8d20d59 --- /dev/null +++ b/vendor/twig/twig/src/Cache/NullCache.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Cache; + +/** + * Implements a no-cache strategy. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class NullCache implements CacheInterface +{ + public function generateKey(string $name, string $className): string + { + return ''; + } + + public function write(string $key, string $content): void + { + } + + public function load(string $key): void + { + } + + public function getTimestamp(string $key): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/Compiler.php b/vendor/twig/twig/src/Compiler.php new file mode 100644 index 0000000..eb652c6 --- /dev/null +++ b/vendor/twig/twig/src/Compiler.php @@ -0,0 +1,223 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Node\Node; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class Compiler +{ + private $lastLine; + private $source; + private $indentation; + private $env; + private $debugInfo = []; + private $sourceOffset; + private $sourceLine; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + public function getEnvironment(): Environment + { + return $this->env; + } + + public function getSource(): string + { + return $this->source; + } + + /** + * @return $this + */ + public function reset(int $indentation = 0) + { + $this->lastLine = null; + $this->source = ''; + $this->debugInfo = []; + $this->sourceOffset = 0; + // source code starts at 1 (as we then increment it when we encounter new lines) + $this->sourceLine = 1; + $this->indentation = $indentation; + $this->varNameSalt = 0; + + return $this; + } + + /** + * @return $this + */ + public function compile(Node $node, int $indentation = 0) + { + $this->reset($indentation); + $node->compile($this); + + return $this; + } + + /** + * @return $this + */ + public function subcompile(Node $node, bool $raw = true) + { + if (false === $raw) { + $this->source .= str_repeat(' ', $this->indentation * 4); + } + + $node->compile($this); + + return $this; + } + + /** + * Adds a raw string to the compiled code. + * + * @return $this + */ + public function raw(string $string) + { + $this->source .= $string; + + return $this; + } + + /** + * Writes a string to the compiled code by adding indentation. + * + * @return $this + */ + public function write(...$strings) + { + foreach ($strings as $string) { + $this->source .= str_repeat(' ', $this->indentation * 4).$string; + } + + return $this; + } + + /** + * Adds a quoted string to the compiled code. + * + * @return $this + */ + public function string(string $value) + { + $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\")); + + return $this; + } + + /** + * Returns a PHP representation of a given value. + * + * @return $this + */ + public function repr($value) + { + if (\is_int($value) || \is_float($value)) { + if (false !== $locale = setlocale(\LC_NUMERIC, '0')) { + setlocale(\LC_NUMERIC, 'C'); + } + + $this->raw(var_export($value, true)); + + if (false !== $locale) { + setlocale(\LC_NUMERIC, $locale); + } + } elseif (null === $value) { + $this->raw('null'); + } elseif (\is_bool($value)) { + $this->raw($value ? 'true' : 'false'); + } elseif (\is_array($value)) { + $this->raw('array('); + $first = true; + foreach ($value as $key => $v) { + if (!$first) { + $this->raw(', '); + } + $first = false; + $this->repr($key); + $this->raw(' => '); + $this->repr($v); + } + $this->raw(')'); + } else { + $this->string($value); + } + + return $this; + } + + /** + * @return $this + */ + public function addDebugInfo(Node $node) + { + if ($node->getTemplateLine() != $this->lastLine) { + $this->write(sprintf("// line %d\n", $node->getTemplateLine())); + + $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset); + $this->sourceOffset = \strlen($this->source); + $this->debugInfo[$this->sourceLine] = $node->getTemplateLine(); + + $this->lastLine = $node->getTemplateLine(); + } + + return $this; + } + + public function getDebugInfo(): array + { + ksort($this->debugInfo); + + return $this->debugInfo; + } + + /** + * @return $this + */ + public function indent(int $step = 1) + { + $this->indentation += $step; + + return $this; + } + + /** + * @return $this + * + * @throws \LogicException When trying to outdent too much so the indentation would become negative + */ + public function outdent(int $step = 1) + { + // can't outdent by more steps than the current indentation level + if ($this->indentation < $step) { + throw new \LogicException('Unable to call outdent() as the indentation would become negative.'); + } + + $this->indentation -= $step; + + return $this; + } + + public function getVarName(): string + { + return sprintf('__internal_compile_%d', $this->varNameSalt++); + } +} diff --git a/vendor/twig/twig/src/Environment.php b/vendor/twig/twig/src/Environment.php new file mode 100644 index 0000000..dd721b4 --- /dev/null +++ b/vendor/twig/twig/src/Environment.php @@ -0,0 +1,841 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Cache\CacheInterface; +use Twig\Cache\FilesystemCache; +use Twig\Cache\NullCache; +use Twig\Error\Error; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; +use Twig\Error\SyntaxError; +use Twig\Extension\CoreExtension; +use Twig\Extension\EscaperExtension; +use Twig\Extension\ExtensionInterface; +use Twig\Extension\OptimizerExtension; +use Twig\Loader\ArrayLoader; +use Twig\Loader\ChainLoader; +use Twig\Loader\LoaderInterface; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\TokenParser\TokenParserInterface; + +/** + * Stores the Twig configuration and renders templates. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Environment +{ + public const VERSION = '3.5.1'; + public const VERSION_ID = 30501; + public const MAJOR_VERSION = 3; + public const MINOR_VERSION = 5; + public const RELEASE_VERSION = 1; + public const EXTRA_VERSION = ''; + + private $charset; + private $loader; + private $debug; + private $autoReload; + private $cache; + private $lexer; + private $parser; + private $compiler; + /** @var array<string, mixed> */ + private $globals = []; + private $resolvedGlobals; + private $loadedTemplates; + private $strictVariables; + private $templateClassPrefix = '__TwigTemplate_'; + private $originalCache; + private $extensionSet; + private $runtimeLoaders = []; + private $runtimes = []; + private $optionsHash; + + /** + * Constructor. + * + * Available options: + * + * * debug: When set to true, it automatically set "auto_reload" to true as + * well (default to false). + * + * * charset: The charset used by the templates (default to UTF-8). + * + * * cache: An absolute path where to store the compiled templates, + * a \Twig\Cache\CacheInterface implementation, + * or false to disable compilation cache (default). + * + * * auto_reload: Whether to reload the template if the original source changed. + * If you don't provide the auto_reload option, it will be + * determined automatically based on the debug value. + * + * * strict_variables: Whether to ignore invalid variables in templates + * (default to false). + * + * * autoescape: Whether to enable auto-escaping (default to html): + * * false: disable auto-escaping + * * html, js: set the autoescaping to one of the supported strategies + * * name: set the autoescaping strategy based on the template name extension + * * PHP callback: a PHP callback that returns an escaping strategy based on the template "name" + * + * * optimizations: A flag that indicates which optimizations to apply + * (default to -1 which means that all optimizations are enabled; + * set it to 0 to disable). + */ + public function __construct(LoaderInterface $loader, $options = []) + { + $this->setLoader($loader); + + $options = array_merge([ + 'debug' => false, + 'charset' => 'UTF-8', + 'strict_variables' => false, + 'autoescape' => 'html', + 'cache' => false, + 'auto_reload' => null, + 'optimizations' => -1, + ], $options); + + $this->debug = (bool) $options['debug']; + $this->setCharset($options['charset'] ?? 'UTF-8'); + $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload']; + $this->strictVariables = (bool) $options['strict_variables']; + $this->setCache($options['cache']); + $this->extensionSet = new ExtensionSet(); + + $this->addExtension(new CoreExtension()); + $this->addExtension(new EscaperExtension($options['autoescape'])); + $this->addExtension(new OptimizerExtension($options['optimizations'])); + } + + /** + * Enables debugging mode. + */ + public function enableDebug() + { + $this->debug = true; + $this->updateOptionsHash(); + } + + /** + * Disables debugging mode. + */ + public function disableDebug() + { + $this->debug = false; + $this->updateOptionsHash(); + } + + /** + * Checks if debug mode is enabled. + * + * @return bool true if debug mode is enabled, false otherwise + */ + public function isDebug() + { + return $this->debug; + } + + /** + * Enables the auto_reload option. + */ + public function enableAutoReload() + { + $this->autoReload = true; + } + + /** + * Disables the auto_reload option. + */ + public function disableAutoReload() + { + $this->autoReload = false; + } + + /** + * Checks if the auto_reload option is enabled. + * + * @return bool true if auto_reload is enabled, false otherwise + */ + public function isAutoReload() + { + return $this->autoReload; + } + + /** + * Enables the strict_variables option. + */ + public function enableStrictVariables() + { + $this->strictVariables = true; + $this->updateOptionsHash(); + } + + /** + * Disables the strict_variables option. + */ + public function disableStrictVariables() + { + $this->strictVariables = false; + $this->updateOptionsHash(); + } + + /** + * Checks if the strict_variables option is enabled. + * + * @return bool true if strict_variables is enabled, false otherwise + */ + public function isStrictVariables() + { + return $this->strictVariables; + } + + /** + * Gets the current cache implementation. + * + * @param bool $original Whether to return the original cache option or the real cache instance + * + * @return CacheInterface|string|false A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function getCache($original = true) + { + return $original ? $this->originalCache : $this->cache; + } + + /** + * Sets the current cache implementation. + * + * @param CacheInterface|string|false $cache A Twig\Cache\CacheInterface implementation, + * an absolute path to the compiled templates, + * or false to disable cache + */ + public function setCache($cache) + { + if (\is_string($cache)) { + $this->originalCache = $cache; + $this->cache = new FilesystemCache($cache, $this->autoReload ? FilesystemCache::FORCE_BYTECODE_INVALIDATION : 0); + } elseif (false === $cache) { + $this->originalCache = $cache; + $this->cache = new NullCache(); + } elseif ($cache instanceof CacheInterface) { + $this->originalCache = $this->cache = $cache; + } else { + throw new \LogicException('Cache can only be a string, false, or a \Twig\Cache\CacheInterface implementation.'); + } + } + + /** + * Gets the template class associated with the given string. + * + * The generated template class is based on the following parameters: + * + * * The cache key for the given template; + * * The currently enabled extensions; + * * Whether the Twig C extension is available or not; + * * PHP version; + * * Twig version; + * * Options with what environment was created. + * + * @param string $name The name for which to calculate the template class name + * @param int|null $index The index if it is an embedded template + * + * @internal + */ + public function getTemplateClass(string $name, int $index = null): string + { + $key = $this->getLoader()->getCacheKey($name).$this->optionsHash; + + return $this->templateClassPrefix.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $key).(null === $index ? '' : '___'.$index); + } + + /** + * Renders a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function render($name, array $context = []): string + { + return $this->load($name)->render($context); + } + + /** + * Displays a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + * @throws RuntimeError When an error occurred during rendering + */ + public function display($name, array $context = []): void + { + $this->load($name)->display($context); + } + + /** + * Loads a template. + * + * @param string|TemplateWrapper $name The template name + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + */ + public function load($name): TemplateWrapper + { + if ($name instanceof TemplateWrapper) { + return $name; + } + + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } + + /** + * Loads a template internal representation. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The template name + * @param int $index The index if it is an embedded template + * + * @throws LoaderError When the template cannot be found + * @throws RuntimeError When a previously generated cache is corrupted + * @throws SyntaxError When an error occurred during compilation + * + * @internal + */ + public function loadTemplate(string $cls, string $name, int $index = null): Template + { + $mainCls = $cls; + if (null !== $index) { + $cls .= '___'.$index; + } + + if (isset($this->loadedTemplates[$cls])) { + return $this->loadedTemplates[$cls]; + } + + if (!class_exists($cls, false)) { + $key = $this->cache->generateKey($name, $mainCls); + + if (!$this->isAutoReload() || $this->isTemplateFresh($name, $this->cache->getTimestamp($key))) { + $this->cache->load($key); + } + + $source = null; + if (!class_exists($cls, false)) { + $source = $this->getLoader()->getSourceContext($name); + $content = $this->compileSource($source); + $this->cache->write($key, $content); + $this->cache->load($key); + + if (!class_exists($mainCls, false)) { + /* Last line of defense if either $this->bcWriteCacheFile was used, + * $this->cache is implemented as a no-op or we have a race condition + * where the cache was cleared between the above calls to write to and load from + * the cache. + */ + eval('?>'.$content); + } + + if (!class_exists($cls, false)) { + throw new RuntimeError(sprintf('Failed to load Twig template "%s", index "%s": cache might be corrupted.', $name, $index), -1, $source); + } + } + } + + $this->extensionSet->initRuntime(); + + return $this->loadedTemplates[$cls] = new $cls($this); + } + + /** + * Creates a template from source. + * + * This method should not be used as a generic way to load templates. + * + * @param string $template The template source + * @param string $name An optional name of the template to be used in error messages + * + * @throws LoaderError When the template cannot be found + * @throws SyntaxError When an error occurred during compilation + */ + public function createTemplate(string $template, string $name = null): TemplateWrapper + { + $hash = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $template, false); + if (null !== $name) { + $name = sprintf('%s (string template %s)', $name, $hash); + } else { + $name = sprintf('__string_template__%s', $hash); + } + + $loader = new ChainLoader([ + new ArrayLoader([$name => $template]), + $current = $this->getLoader(), + ]); + + $this->setLoader($loader); + try { + return new TemplateWrapper($this, $this->loadTemplate($this->getTemplateClass($name), $name)); + } finally { + $this->setLoader($current); + } + } + + /** + * Returns true if the template is still fresh. + * + * Besides checking the loader for freshness information, + * this method also checks if the enabled extensions have + * not changed. + * + * @param int $time The last modification time of the cached template + */ + public function isTemplateFresh(string $name, int $time): bool + { + return $this->extensionSet->getLastModified() <= $time && $this->getLoader()->isFresh($name, $time); + } + + /** + * Tries to load a template consecutively from an array. + * + * Similar to load() but it also accepts instances of \Twig\Template and + * \Twig\TemplateWrapper, and an array of templates where each is tried to be loaded. + * + * @param string|TemplateWrapper|array $names A template or an array of templates to try consecutively + * + * @throws LoaderError When none of the templates can be found + * @throws SyntaxError When an error occurred during compilation + */ + public function resolveTemplate($names): TemplateWrapper + { + if (!\is_array($names)) { + return $this->load($names); + } + + $count = \count($names); + foreach ($names as $name) { + if ($name instanceof Template) { + return $name; + } + if ($name instanceof TemplateWrapper) { + return $name; + } + + if (1 !== $count && !$this->getLoader()->exists($name)) { + continue; + } + + return $this->load($name); + } + + throw new LoaderError(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names))); + } + + public function setLexer(Lexer $lexer) + { + $this->lexer = $lexer; + } + + /** + * @throws SyntaxError When the code is syntactically wrong + */ + public function tokenize(Source $source): TokenStream + { + if (null === $this->lexer) { + $this->lexer = new Lexer($this); + } + + return $this->lexer->tokenize($source); + } + + public function setParser(Parser $parser) + { + $this->parser = $parser; + } + + /** + * Converts a token stream to a node tree. + * + * @throws SyntaxError When the token stream is syntactically or semantically wrong + */ + public function parse(TokenStream $stream): ModuleNode + { + if (null === $this->parser) { + $this->parser = new Parser($this); + } + + return $this->parser->parse($stream); + } + + public function setCompiler(Compiler $compiler) + { + $this->compiler = $compiler; + } + + /** + * Compiles a node and returns the PHP code. + */ + public function compile(Node $node): string + { + if (null === $this->compiler) { + $this->compiler = new Compiler($this); + } + + return $this->compiler->compile($node)->getSource(); + } + + /** + * Compiles a template source code. + * + * @throws SyntaxError When there was an error during tokenizing, parsing or compiling + */ + public function compileSource(Source $source): string + { + try { + return $this->compile($this->parse($this->tokenize($source))); + } catch (Error $e) { + $e->setSourceContext($source); + throw $e; + } catch (\Exception $e) { + throw new SyntaxError(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $source, $e); + } + } + + public function setLoader(LoaderInterface $loader) + { + $this->loader = $loader; + } + + public function getLoader(): LoaderInterface + { + return $this->loader; + } + + public function setCharset(string $charset) + { + if ('UTF8' === $charset = null === $charset ? null : strtoupper($charset)) { + // iconv on Windows requires "UTF-8" instead of "UTF8" + $charset = 'UTF-8'; + } + + $this->charset = $charset; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function hasExtension(string $class): bool + { + return $this->extensionSet->hasExtension($class); + } + + public function addRuntimeLoader(RuntimeLoaderInterface $loader) + { + $this->runtimeLoaders[] = $loader; + } + + /** + * @template TExtension of ExtensionInterface + * + * @param class-string<TExtension> $class + * + * @return TExtension + */ + public function getExtension(string $class): ExtensionInterface + { + return $this->extensionSet->getExtension($class); + } + + /** + * Returns the runtime implementation of a Twig element (filter/function/tag/test). + * + * @template TRuntime of object + * + * @param class-string<TRuntime> $class A runtime class name + * + * @return TRuntime The runtime implementation + * + * @throws RuntimeError When the template cannot be found + */ + public function getRuntime(string $class) + { + if (isset($this->runtimes[$class])) { + return $this->runtimes[$class]; + } + + foreach ($this->runtimeLoaders as $loader) { + if (null !== $runtime = $loader->load($class)) { + return $this->runtimes[$class] = $runtime; + } + } + + throw new RuntimeError(sprintf('Unable to load the "%s" runtime.', $class)); + } + + public function addExtension(ExtensionInterface $extension) + { + $this->extensionSet->addExtension($extension); + $this->updateOptionsHash(); + } + + /** + * @param ExtensionInterface[] $extensions An array of extensions + */ + public function setExtensions(array $extensions) + { + $this->extensionSet->setExtensions($extensions); + $this->updateOptionsHash(); + } + + /** + * @return ExtensionInterface[] An array of extensions (keys are for internal usage only and should not be relied on) + */ + public function getExtensions(): array + { + return $this->extensionSet->getExtensions(); + } + + public function addTokenParser(TokenParserInterface $parser) + { + $this->extensionSet->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + * + * @internal + */ + public function getTokenParsers(): array + { + return $this->extensionSet->getTokenParsers(); + } + + /** + * @internal + */ + public function getTokenParser(string $name): ?TokenParserInterface + { + return $this->extensionSet->getTokenParser($name); + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedTokenParserCallback($callable); + } + + public function addNodeVisitor(NodeVisitorInterface $visitor) + { + $this->extensionSet->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + * + * @internal + */ + public function getNodeVisitors(): array + { + return $this->extensionSet->getNodeVisitors(); + } + + public function addFilter(TwigFilter $filter) + { + $this->extensionSet->addFilter($filter); + } + + /** + * @internal + */ + public function getFilter(string $name): ?TwigFilter + { + return $this->extensionSet->getFilter($name); + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFilterCallback($callable); + } + + /** + * Gets the registered Filters. + * + * Be warned that this method cannot return filters defined with registerUndefinedFilterCallback. + * + * @return TwigFilter[] + * + * @see registerUndefinedFilterCallback + * + * @internal + */ + public function getFilters(): array + { + return $this->extensionSet->getFilters(); + } + + public function addTest(TwigTest $test) + { + $this->extensionSet->addTest($test); + } + + /** + * @return TwigTest[] + * + * @internal + */ + public function getTests(): array + { + return $this->extensionSet->getTests(); + } + + /** + * @internal + */ + public function getTest(string $name): ?TwigTest + { + return $this->extensionSet->getTest($name); + } + + public function addFunction(TwigFunction $function) + { + $this->extensionSet->addFunction($function); + } + + /** + * @internal + */ + public function getFunction(string $name): ?TwigFunction + { + return $this->extensionSet->getFunction($name); + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->extensionSet->registerUndefinedFunctionCallback($callable); + } + + /** + * Gets registered functions. + * + * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback. + * + * @return TwigFunction[] + * + * @see registerUndefinedFunctionCallback + * + * @internal + */ + public function getFunctions(): array + { + return $this->extensionSet->getFunctions(); + } + + /** + * Registers a Global. + * + * New globals can be added before compiling or rendering a template; + * but after, you can only update existing globals. + * + * @param mixed $value The global value + */ + public function addGlobal(string $name, $value) + { + if ($this->extensionSet->isInitialized() && !\array_key_exists($name, $this->getGlobals())) { + throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name)); + } + + if (null !== $this->resolvedGlobals) { + $this->resolvedGlobals[$name] = $value; + } else { + $this->globals[$name] = $value; + } + } + + /** + * @internal + * + * @return array<string, mixed> + */ + public function getGlobals(): array + { + if ($this->extensionSet->isInitialized()) { + if (null === $this->resolvedGlobals) { + $this->resolvedGlobals = array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + return $this->resolvedGlobals; + } + + return array_merge($this->extensionSet->getGlobals(), $this->globals); + } + + public function mergeGlobals(array $context): array + { + // we don't use array_merge as the context being generally + // bigger than globals, this code is faster. + foreach ($this->getGlobals() as $key => $value) { + if (!\array_key_exists($key, $context)) { + $context[$key] = $value; + } + } + + return $context; + } + + /** + * @internal + * + * @return array<string, array{precedence: int, class: class-string<AbstractUnary>}> + */ + public function getUnaryOperators(): array + { + return $this->extensionSet->getUnaryOperators(); + } + + /** + * @internal + * + * @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> + */ + public function getBinaryOperators(): array + { + return $this->extensionSet->getBinaryOperators(); + } + + private function updateOptionsHash(): void + { + $this->optionsHash = implode(':', [ + $this->extensionSet->getSignature(), + \PHP_MAJOR_VERSION, + \PHP_MINOR_VERSION, + self::VERSION, + (int) $this->debug, + (int) $this->strictVariables, + ]); + } +} diff --git a/vendor/twig/twig/src/Error/Error.php b/vendor/twig/twig/src/Error/Error.php new file mode 100644 index 0000000..a68be65 --- /dev/null +++ b/vendor/twig/twig/src/Error/Error.php @@ -0,0 +1,227 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Error; + +use Twig\Source; +use Twig\Template; + +/** + * Twig base exception. + * + * This exception class and its children must only be used when + * an error occurs during the loading of a template, when a syntax error + * is detected in a template, or when rendering a template. Other + * errors must use regular PHP exception classes (like when the template + * cache directory is not writable for instance). + * + * To help debugging template issues, this class tracks the original template + * name and line where the error occurred. + * + * Whenever possible, you must set these information (original template name + * and line number) yourself by passing them to the constructor. If some or all + * these information are not available from where you throw the exception, then + * this class will guess them automatically (when the line number is set to -1 + * and/or the name is set to null). As this is a costly operation, this + * can be disabled by passing false for both the name and the line number + * when creating a new instance of this class. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Error extends \Exception +{ + private $lineno; + private $name; + private $rawMessage; + private $sourcePath; + private $sourceCode; + + /** + * Constructor. + * + * By default, automatic guessing is enabled. + * + * @param string $message The error message + * @param int $lineno The template line where the error occurred + * @param Source|null $source The source context where the error occurred + */ + public function __construct(string $message, int $lineno = -1, Source $source = null, \Exception $previous = null) + { + parent::__construct('', 0, $previous); + + if (null === $source) { + $name = null; + } else { + $name = $source->getName(); + $this->sourceCode = $source->getCode(); + $this->sourcePath = $source->getPath(); + } + + $this->lineno = $lineno; + $this->name = $name; + $this->rawMessage = $message; + $this->updateRepr(); + } + + public function getRawMessage(): string + { + return $this->rawMessage; + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function setTemplateLine(int $lineno): void + { + $this->lineno = $lineno; + + $this->updateRepr(); + } + + public function getSourceContext(): ?Source + { + return $this->name ? new Source($this->sourceCode, $this->name, $this->sourcePath) : null; + } + + public function setSourceContext(Source $source = null): void + { + if (null === $source) { + $this->sourceCode = $this->name = $this->sourcePath = null; + } else { + $this->sourceCode = $source->getCode(); + $this->name = $source->getName(); + $this->sourcePath = $source->getPath(); + } + + $this->updateRepr(); + } + + public function guess(): void + { + $this->guessTemplateInfo(); + $this->updateRepr(); + } + + public function appendMessage($rawMessage): void + { + $this->rawMessage .= $rawMessage; + $this->updateRepr(); + } + + private function updateRepr(): void + { + $this->message = $this->rawMessage; + + if ($this->sourcePath && $this->lineno > 0) { + $this->file = $this->sourcePath; + $this->line = $this->lineno; + + return; + } + + $dot = false; + if ('.' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $dot = true; + } + + $questionMark = false; + if ('?' === substr($this->message, -1)) { + $this->message = substr($this->message, 0, -1); + $questionMark = true; + } + + if ($this->name) { + if (\is_string($this->name) || (\is_object($this->name) && method_exists($this->name, '__toString'))) { + $name = sprintf('"%s"', $this->name); + } else { + $name = json_encode($this->name); + } + $this->message .= sprintf(' in %s', $name); + } + + if ($this->lineno && $this->lineno >= 0) { + $this->message .= sprintf(' at line %d', $this->lineno); + } + + if ($dot) { + $this->message .= '.'; + } + + if ($questionMark) { + $this->message .= '?'; + } + } + + private function guessTemplateInfo(): void + { + $template = null; + $templateClass = null; + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS | \DEBUG_BACKTRACE_PROVIDE_OBJECT); + foreach ($backtrace as $trace) { + if (isset($trace['object']) && $trace['object'] instanceof Template) { + $currentClass = \get_class($trace['object']); + $isEmbedContainer = null === $templateClass ? false : 0 === strpos($templateClass, $currentClass); + if (null === $this->name || ($this->name == $trace['object']->getTemplateName() && !$isEmbedContainer)) { + $template = $trace['object']; + $templateClass = \get_class($trace['object']); + } + } + } + + // update template name + if (null !== $template && null === $this->name) { + $this->name = $template->getTemplateName(); + } + + // update template path if any + if (null !== $template && null === $this->sourcePath) { + $src = $template->getSourceContext(); + $this->sourceCode = $src->getCode(); + $this->sourcePath = $src->getPath(); + } + + if (null === $template || $this->lineno > -1) { + return; + } + + $r = new \ReflectionObject($template); + $file = $r->getFileName(); + + $exceptions = [$e = $this]; + while ($e = $e->getPrevious()) { + $exceptions[] = $e; + } + + while ($e = array_pop($exceptions)) { + $traces = $e->getTrace(); + array_unshift($traces, ['file' => $e->getFile(), 'line' => $e->getLine()]); + + while ($trace = array_shift($traces)) { + if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) { + continue; + } + + foreach ($template->getDebugInfo() as $codeLine => $templateLine) { + if ($codeLine <= $trace['line']) { + // update template line + $this->lineno = $templateLine; + + return; + } + } + } + } + } +} diff --git a/vendor/twig/twig/src/Error/LoaderError.php b/vendor/twig/twig/src/Error/LoaderError.php new file mode 100644 index 0000000..7c8c23c --- /dev/null +++ b/vendor/twig/twig/src/Error/LoaderError.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Error; + +/** + * Exception thrown when an error occurs during template loading. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class LoaderError extends Error +{ +} diff --git a/vendor/twig/twig/src/Error/RuntimeError.php b/vendor/twig/twig/src/Error/RuntimeError.php new file mode 100644 index 0000000..f6b8476 --- /dev/null +++ b/vendor/twig/twig/src/Error/RuntimeError.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Error; + +/** + * Exception thrown when an error occurs at runtime. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class RuntimeError extends Error +{ +} diff --git a/vendor/twig/twig/src/Error/SyntaxError.php b/vendor/twig/twig/src/Error/SyntaxError.php new file mode 100644 index 0000000..726b330 --- /dev/null +++ b/vendor/twig/twig/src/Error/SyntaxError.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Error; + +/** + * \Exception thrown when a syntax error occurs during lexing or parsing of a template. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SyntaxError extends Error +{ + /** + * Tweaks the error message to include suggestions. + * + * @param string $name The original name of the item that does not exist + * @param array $items An array of possible items + */ + public function addSuggestions(string $name, array $items): void + { + $alternatives = []; + foreach ($items as $item) { + $lev = levenshtein($name, $item); + if ($lev <= \strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = $lev; + } + } + + if (!$alternatives) { + return; + } + + asort($alternatives); + + $this->appendMessage(sprintf(' Did you mean "%s"?', implode('", "', array_keys($alternatives)))); + } +} diff --git a/vendor/twig/twig/src/ExpressionParser.php b/vendor/twig/twig/src/ExpressionParser.php new file mode 100644 index 0000000..2048c3c --- /dev/null +++ b/vendor/twig/twig/src/ExpressionParser.php @@ -0,0 +1,827 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Error\SyntaxError; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\ArrowFunctionExpression; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\Expression\BlockReferenceExpression; +use Twig\Node\Expression\ConditionalExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MethodCallExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\ParentExpression; +use Twig\Node\Expression\TestExpression; +use Twig\Node\Expression\Unary\AbstractUnary; +use Twig\Node\Expression\Unary\NegUnary; +use Twig\Node\Expression\Unary\NotUnary; +use Twig\Node\Expression\Unary\PosUnary; +use Twig\Node\Node; + +/** + * Parses expressions. + * + * This parser implements a "Precedence climbing" algorithm. + * + * @see https://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + * @see https://en.wikipedia.org/wiki/Operator-precedence_parser + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ExpressionParser +{ + public const OPERATOR_LEFT = 1; + public const OPERATOR_RIGHT = 2; + + private $parser; + private $env; + /** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */ + private $unaryOperators; + /** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: self::OPERATOR_*}> */ + private $binaryOperators; + + public function __construct(Parser $parser, Environment $env) + { + $this->parser = $parser; + $this->env = $env; + $this->unaryOperators = $env->getUnaryOperators(); + $this->binaryOperators = $env->getBinaryOperators(); + } + + public function parseExpression($precedence = 0, $allowArrow = false) + { + if ($allowArrow && $arrow = $this->parseArrow()) { + return $arrow; + } + + $expr = $this->getPrimary(); + $token = $this->parser->getCurrentToken(); + while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) { + $op = $this->binaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + + if ('is not' === $token->getValue()) { + $expr = $this->parseNotTestExpression($expr); + } elseif ('is' === $token->getValue()) { + $expr = $this->parseTestExpression($expr); + } elseif (isset($op['callable'])) { + $expr = $op['callable']($this->parser, $expr); + } else { + $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence'], true); + $class = $op['class']; + $expr = new $class($expr, $expr1, $token->getLine()); + } + + $token = $this->parser->getCurrentToken(); + } + + if (0 === $precedence) { + return $this->parseConditionalExpression($expr); + } + + return $expr; + } + + /** + * @return ArrowFunctionExpression|null + */ + private function parseArrow() + { + $stream = $this->parser->getStream(); + + // short array syntax (one argument, no parentheses)? + if ($stream->look(1)->test(/* Token::ARROW_TYPE */ 12)) { + $line = $stream->getCurrent()->getLine(); + $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $names = [new AssignNameExpression($token->getValue(), $token->getLine())]; + $stream->expect(/* Token::ARROW_TYPE */ 12); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + // first, determine if we are parsing an arrow function by finding => (long form) + $i = 0; + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + return null; + } + ++$i; + while (true) { + // variable name + ++$i; + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + ++$i; + } + if (!$stream->look($i)->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + return null; + } + ++$i; + if (!$stream->look($i)->test(/* Token::ARROW_TYPE */ 12)) { + return null; + } + + // yes, let's parse it properly + $token = $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '('); + $line = $token->getLine(); + + $names = []; + while (true) { + $token = $stream->expect(/* Token::NAME_TYPE */ 5); + $names[] = new AssignNameExpression($token->getValue(), $token->getLine()); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')'); + $stream->expect(/* Token::ARROW_TYPE */ 12); + + return new ArrowFunctionExpression($this->parseExpression(0), new Node($names), $line); + } + + private function getPrimary(): AbstractExpression + { + $token = $this->parser->getCurrentToken(); + + if ($this->isUnary($token)) { + $operator = $this->unaryOperators[$token->getValue()]; + $this->parser->getStream()->next(); + $expr = $this->parseExpression($operator['precedence']); + $class = $operator['class']; + + return $this->parsePostfixExpression(new $class($expr, $token->getLine())); + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $this->parser->getStream()->next(); + $expr = $this->parseExpression(); + $this->parser->getStream()->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'An opened parenthesis is not properly closed'); + + return $this->parsePostfixExpression($expr); + } + + return $this->parsePrimaryExpression(); + } + + private function parseConditionalExpression($expr): AbstractExpression + { + while ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, '?')) { + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $expr2 = $this->parseExpression(); + if ($this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $expr3 = $this->parseExpression(); + } else { + $expr3 = new ConstantExpression('', $this->parser->getCurrentToken()->getLine()); + } + } else { + $expr2 = $expr; + $expr3 = $this->parseExpression(); + } + + $expr = new ConditionalExpression($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine()); + } + + return $expr; + } + + private function isUnary(Token $token): bool + { + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->unaryOperators[$token->getValue()]); + } + + private function isBinary(Token $token): bool + { + return $token->test(/* Token::OPERATOR_TYPE */ 8) && isset($this->binaryOperators[$token->getValue()]); + } + + public function parsePrimaryExpression() + { + $token = $this->parser->getCurrentToken(); + switch ($token->getType()) { + case /* Token::NAME_TYPE */ 5: + $this->parser->getStream()->next(); + switch ($token->getValue()) { + case 'true': + case 'TRUE': + $node = new ConstantExpression(true, $token->getLine()); + break; + + case 'false': + case 'FALSE': + $node = new ConstantExpression(false, $token->getLine()); + break; + + case 'none': + case 'NONE': + case 'null': + case 'NULL': + $node = new ConstantExpression(null, $token->getLine()); + break; + + default: + if ('(' === $this->parser->getCurrentToken()->getValue()) { + $node = $this->getFunctionNode($token->getValue(), $token->getLine()); + } else { + $node = new NameExpression($token->getValue(), $token->getLine()); + } + } + break; + + case /* Token::NUMBER_TYPE */ 6: + $this->parser->getStream()->next(); + $node = new ConstantExpression($token->getValue(), $token->getLine()); + break; + + case /* Token::STRING_TYPE */ 7: + case /* Token::INTERPOLATION_START_TYPE */ 10: + $node = $this->parseStringExpression(); + break; + + case /* Token::OPERATOR_TYPE */ 8: + if (preg_match(Lexer::REGEX_NAME, $token->getValue(), $matches) && $matches[0] == $token->getValue()) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + $node = new NameExpression($token->getValue(), $token->getLine()); + break; + } + + if (isset($this->unaryOperators[$token->getValue()])) { + $class = $this->unaryOperators[$token->getValue()]['class']; + if (!\in_array($class, [NegUnary::class, PosUnary::class])) { + throw new SyntaxError(sprintf('Unexpected unary operator "%s".', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + + $this->parser->getStream()->next(); + $expr = $this->parsePrimaryExpression(); + + $node = new $class($expr, $token->getLine()); + break; + } + + // no break + default: + if ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '[')) { + $node = $this->parseArrayExpression(); + } elseif ($token->test(/* Token::PUNCTUATION_TYPE */ 9, '{')) { + $node = $this->parseHashExpression(); + } elseif ($token->test(/* Token::OPERATOR_TYPE */ 8, '=') && ('==' === $this->parser->getStream()->look(-1)->getValue() || '!=' === $this->parser->getStream()->look(-1)->getValue())) { + throw new SyntaxError(sprintf('Unexpected operator of value "%s". Did you try to use "===" or "!==" for strict comparison? Use "is same as(value)" instead.', $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } else { + throw new SyntaxError(sprintf('Unexpected token "%s" of value "%s".', Token::typeToEnglish($token->getType()), $token->getValue()), $token->getLine(), $this->parser->getStream()->getSourceContext()); + } + } + + return $this->parsePostfixExpression($node); + } + + public function parseStringExpression() + { + $stream = $this->parser->getStream(); + + $nodes = []; + // a string cannot be followed by another string in a single expression + $nextCanBeString = true; + while (true) { + if ($nextCanBeString && $token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) { + $nodes[] = new ConstantExpression($token->getValue(), $token->getLine()); + $nextCanBeString = false; + } elseif ($stream->nextIf(/* Token::INTERPOLATION_START_TYPE */ 10)) { + $nodes[] = $this->parseExpression(); + $stream->expect(/* Token::INTERPOLATION_END_TYPE */ 11); + $nextCanBeString = true; + } else { + break; + } + } + + $expr = array_shift($nodes); + foreach ($nodes as $node) { + $expr = new ConcatBinary($expr, $node, $node->getTemplateLine()); + } + + return $expr; + } + + public function parseArrayExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '[', 'An array element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + if (!$first) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'An array element must be followed by a comma'); + + // trailing ,? + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + break; + } + } + $first = false; + + $node->addElement($this->parseExpression()); + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']', 'An opened array is not properly closed'); + + return $node; + } + + public function parseHashExpression() + { + $stream = $this->parser->getStream(); + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '{', 'A hash element was expected'); + + $node = new ArrayExpression([], $stream->getCurrent()->getLine()); + $first = true; + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + if (!$first) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'A hash value must be followed by a comma'); + + // trailing ,? + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '}')) { + break; + } + } + $first = false; + + // a hash key can be: + // + // * a number -- 12 + // * a string -- 'a' + // * a name, which is equivalent to a string -- a + // * an expression, which must be enclosed in parentheses -- (1 + 2) + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + + // {a} is a shortcut for {a:a} + if ($stream->test(Token::PUNCTUATION_TYPE, [',', '}'])) { + $value = new NameExpression($key->getAttribute('value'), $key->getTemplateLine()); + $node->addElement($value, $key); + continue; + } + } elseif (($token = $stream->nextIf(/* Token::STRING_TYPE */ 7)) || $token = $stream->nextIf(/* Token::NUMBER_TYPE */ 6)) { + $key = new ConstantExpression($token->getValue(), $token->getLine()); + } elseif ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $key = $this->parseExpression(); + } else { + $current = $stream->getCurrent(); + + throw new SyntaxError(sprintf('A hash key must be a quoted string, a number, a name, or an expression enclosed in parentheses (unexpected token "%s" of value "%s".', Token::typeToEnglish($current->getType()), $current->getValue()), $current->getLine(), $stream->getSourceContext()); + } + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ':', 'A hash key must be followed by a colon (:)'); + $value = $this->parseExpression(); + + $node->addElement($value, $key); + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '}', 'An opened hash is not properly closed'); + + return $node; + } + + public function parsePostfixExpression($node) + { + while (true) { + $token = $this->parser->getCurrentToken(); + if (/* Token::PUNCTUATION_TYPE */ 9 == $token->getType()) { + if ('.' == $token->getValue() || '[' == $token->getValue()) { + $node = $this->parseSubscriptExpression($node); + } elseif ('|' == $token->getValue()) { + $node = $this->parseFilterExpression($node); + } else { + break; + } + } else { + break; + } + } + + return $node; + } + + public function getFunctionNode($name, $line) + { + switch ($name) { + case 'parent': + $this->parseArguments(); + if (!\count($this->parser->getBlockStack())) { + throw new SyntaxError('Calling "parent" outside a block is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + if (!$this->parser->getParent() && !$this->parser->hasTraits()) { + throw new SyntaxError('Calling "parent" on a template that does not extend nor "use" another template is forbidden.', $line, $this->parser->getStream()->getSourceContext()); + } + + return new ParentExpression($this->parser->peekBlockStack(), $line); + case 'block': + $args = $this->parseArguments(); + if (\count($args) < 1) { + throw new SyntaxError('The "block" function takes one argument (the block name).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new BlockReferenceExpression($args->getNode(0), \count($args) > 1 ? $args->getNode(1) : null, $line); + case 'attribute': + $args = $this->parseArguments(); + if (\count($args) < 2) { + throw new SyntaxError('The "attribute" function takes at least two arguments (the variable and the attributes).', $line, $this->parser->getStream()->getSourceContext()); + } + + return new GetAttrExpression($args->getNode(0), $args->getNode(1), \count($args) > 2 ? $args->getNode(2) : null, Template::ANY_CALL, $line); + default: + if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) { + $arguments = new ArrayExpression([], $line); + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + + $node = new MethodCallExpression($alias['node'], $alias['name'], $arguments, $line); + $node->setAttribute('safe', true); + + return $node; + } + + $args = $this->parseArguments(true); + $class = $this->getFunctionNodeClass($name, $line); + + return new $class($name, $args, $line); + } + } + + public function parseSubscriptExpression($node) + { + $stream = $this->parser->getStream(); + $token = $stream->next(); + $lineno = $token->getLine(); + $arguments = new ArrayExpression([], $lineno); + $type = Template::ANY_CALL; + if ('.' == $token->getValue()) { + $token = $stream->next(); + if ( + /* Token::NAME_TYPE */ 5 == $token->getType() + || + /* Token::NUMBER_TYPE */ 6 == $token->getType() + || + (/* Token::OPERATOR_TYPE */ 8 == $token->getType() && preg_match(Lexer::REGEX_NAME, $token->getValue())) + ) { + $arg = new ConstantExpression($token->getValue(), $lineno); + + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $type = Template::METHOD_CALL; + foreach ($this->parseArguments() as $n) { + $arguments->addElement($n); + } + } + } else { + throw new SyntaxError(sprintf('Expected name or number, got value "%s" of type %s.', $token->getValue(), Token::typeToEnglish($token->getType())), $lineno, $stream->getSourceContext()); + } + + if ($node instanceof NameExpression && null !== $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) { + if (!$arg instanceof ConstantExpression) { + throw new SyntaxError(sprintf('Dynamic macro names are not supported (called on "%s").', $node->getAttribute('name')), $token->getLine(), $stream->getSourceContext()); + } + + $name = $arg->getAttribute('value'); + + $node = new MethodCallExpression($node, 'macro_'.$name, $arguments, $lineno); + $node->setAttribute('safe', true); + + return $node; + } + } else { + $type = Template::ARRAY_CALL; + + // slice? + $slice = false; + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $slice = true; + $arg = new ConstantExpression(0, $token->getLine()); + } else { + $arg = $this->parseExpression(); + } + + if ($stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ':')) { + $slice = true; + } + + if ($slice) { + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ']')) { + $length = new ConstantExpression(null, $token->getLine()); + } else { + $length = $this->parseExpression(); + } + + $class = $this->getFilterNodeClass('slice', $token->getLine()); + $arguments = new Node([$arg, $length]); + $filter = new $class($node, new ConstantExpression('slice', $token->getLine()), $arguments, $token->getLine()); + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + + return $filter; + } + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ']'); + } + + return new GetAttrExpression($node, $arg, $arguments, $type, $lineno); + } + + public function parseFilterExpression($node) + { + $this->parser->getStream()->next(); + + return $this->parseFilterExpressionRaw($node); + } + + public function parseFilterExpressionRaw($node, $tag = null) + { + while (true) { + $token = $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5); + + $name = new ConstantExpression($token->getValue(), $token->getLine()); + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $arguments = new Node(); + } else { + $arguments = $this->parseArguments(true, false, true); + } + + $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine()); + + $node = new $class($node, $name, $arguments, $token->getLine(), $tag); + + if (!$this->parser->getStream()->test(/* Token::PUNCTUATION_TYPE */ 9, '|')) { + break; + } + + $this->parser->getStream()->next(); + } + + return $node; + } + + /** + * Parses arguments. + * + * @param bool $namedArguments Whether to allow named arguments or not + * @param bool $definition Whether we are parsing arguments for a function definition + * + * @return Node + * + * @throws SyntaxError + */ + public function parseArguments($namedArguments = false, $definition = false, $allowArrow = false) + { + $args = []; + $stream = $this->parser->getStream(); + + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, '(', 'A list of arguments must begin with an opening parenthesis'); + while (!$stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + if (!empty($args)) { + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ',', 'Arguments must be separated by a comma'); + + // if the comma above was a trailing comma, early exit the argument parse loop + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, ')')) { + break; + } + } + + if ($definition) { + $token = $stream->expect(/* Token::NAME_TYPE */ 5, null, 'An argument must be a name'); + $value = new NameExpression($token->getValue(), $this->parser->getCurrentToken()->getLine()); + } else { + $value = $this->parseExpression(0, $allowArrow); + } + + $name = null; + if ($namedArguments && $token = $stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + if (!$value instanceof NameExpression) { + throw new SyntaxError(sprintf('A parameter name must be a string, "%s" given.', \get_class($value)), $token->getLine(), $stream->getSourceContext()); + } + $name = $value->getAttribute('name'); + + if ($definition) { + $value = $this->parsePrimaryExpression(); + + if (!$this->checkConstantExpression($value)) { + throw new SyntaxError('A default value for an argument must be a constant (a boolean, a string, a number, or an array).', $token->getLine(), $stream->getSourceContext()); + } + } else { + $value = $this->parseExpression(0, $allowArrow); + } + } + + if ($definition) { + if (null === $name) { + $name = $value->getAttribute('name'); + $value = new ConstantExpression(null, $this->parser->getCurrentToken()->getLine()); + } + $args[$name] = $value; + } else { + if (null === $name) { + $args[] = $value; + } else { + $args[$name] = $value; + } + } + } + $stream->expect(/* Token::PUNCTUATION_TYPE */ 9, ')', 'A list of arguments must be closed by a parenthesis'); + + return new Node($args); + } + + public function parseAssignmentExpression() + { + $stream = $this->parser->getStream(); + $targets = []; + while (true) { + $token = $this->parser->getCurrentToken(); + if ($stream->test(/* Token::OPERATOR_TYPE */ 8) && preg_match(Lexer::REGEX_NAME, $token->getValue())) { + // in this context, string operators are variable names + $this->parser->getStream()->next(); + } else { + $stream->expect(/* Token::NAME_TYPE */ 5, null, 'Only variables can be assigned to'); + } + $value = $token->getValue(); + if (\in_array(strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), ['true', 'false', 'none', 'null'])) { + throw new SyntaxError(sprintf('You cannot assign a value to "%s".', $value), $token->getLine(), $stream->getSourceContext()); + } + $targets[] = new AssignNameExpression($value, $token->getLine()); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + + return new Node($targets); + } + + public function parseMultitargetExpression() + { + $targets = []; + while (true) { + $targets[] = $this->parseExpression(); + if (!$this->parser->getStream()->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } + + return new Node($targets); + } + + private function parseNotTestExpression(Node $node): NotUnary + { + return new NotUnary($this->parseTestExpression($node), $this->parser->getCurrentToken()->getLine()); + } + + private function parseTestExpression(Node $node): TestExpression + { + $stream = $this->parser->getStream(); + list($name, $test) = $this->getTest($node->getTemplateLine()); + + $class = $this->getTestNodeClass($test); + $arguments = null; + if ($stream->test(/* Token::PUNCTUATION_TYPE */ 9, '(')) { + $arguments = $this->parseArguments(true); + } elseif ($test->hasOneMandatoryArgument()) { + $arguments = new Node([0 => $this->parsePrimaryExpression()]); + } + + if ('defined' === $name && $node instanceof NameExpression && null !== $alias = $this->parser->getImportedSymbol('function', $node->getAttribute('name'))) { + $node = new MethodCallExpression($alias['node'], $alias['name'], new ArrayExpression([], $node->getTemplateLine()), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + + return new $class($node, $name, $arguments, $this->parser->getCurrentToken()->getLine()); + } + + private function getTest(int $line): array + { + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + if ($test = $this->env->getTest($name)) { + return [$name, $test]; + } + + if ($stream->test(/* Token::NAME_TYPE */ 5)) { + // try 2-words tests + $name = $name.' '.$this->parser->getCurrentToken()->getValue(); + + if ($test = $this->env->getTest($name)) { + $stream->next(); + + return [$name, $test]; + } + } + + $e = new SyntaxError(sprintf('Unknown "%s" test.', $name), $line, $stream->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getTests())); + + throw $e; + } + + private function getTestNodeClass(TwigTest $test): string + { + if ($test->isDeprecated()) { + $stream = $this->parser->getStream(); + $message = sprintf('Twig Test "%s" is deprecated', $test->getName()); + + if ($test->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); + } + if ($test->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $test->getAlternative()); + } + $src = $stream->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $stream->getCurrent()->getLine()); + + @trigger_error($message, \E_USER_DEPRECATED); + } + + return $test->getNodeClass(); + } + + private function getFunctionNodeClass(string $name, int $line): string + { + if (!$function = $this->env->getFunction($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" function.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFunctions())); + + throw $e; + } + + if ($function->isDeprecated()) { + $message = sprintf('Twig Function "%s" is deprecated', $function->getName()); + if ($function->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $function->getDeprecatedVersion()); + } + if ($function->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $function->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + @trigger_error($message, \E_USER_DEPRECATED); + } + + return $function->getNodeClass(); + } + + private function getFilterNodeClass(string $name, int $line): string + { + if (!$filter = $this->env->getFilter($name)) { + $e = new SyntaxError(sprintf('Unknown "%s" filter.', $name), $line, $this->parser->getStream()->getSourceContext()); + $e->addSuggestions($name, array_keys($this->env->getFilters())); + + throw $e; + } + + if ($filter->isDeprecated()) { + $message = sprintf('Twig Filter "%s" is deprecated', $filter->getName()); + if ($filter->getDeprecatedVersion()) { + $message .= sprintf(' since version %s', $filter->getDeprecatedVersion()); + } + if ($filter->getAlternative()) { + $message .= sprintf('. Use "%s" instead', $filter->getAlternative()); + } + $src = $this->parser->getStream()->getSourceContext(); + $message .= sprintf(' in %s at line %d.', $src->getPath() ?: $src->getName(), $line); + + @trigger_error($message, \E_USER_DEPRECATED); + } + + return $filter->getNodeClass(); + } + + // checks that the node only contains "constant" elements + private function checkConstantExpression(Node $node): bool + { + if (!($node instanceof ConstantExpression || $node instanceof ArrayExpression + || $node instanceof NegUnary || $node instanceof PosUnary + )) { + return false; + } + + foreach ($node as $n) { + if (!$this->checkConstantExpression($n)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/twig/twig/src/Extension/AbstractExtension.php b/vendor/twig/twig/src/Extension/AbstractExtension.php new file mode 100644 index 0000000..422925f --- /dev/null +++ b/vendor/twig/twig/src/Extension/AbstractExtension.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +abstract class AbstractExtension implements ExtensionInterface +{ + public function getTokenParsers() + { + return []; + } + + public function getNodeVisitors() + { + return []; + } + + public function getFilters() + { + return []; + } + + public function getTests() + { + return []; + } + + public function getFunctions() + { + return []; + } + + public function getOperators() + { + return []; + } +} diff --git a/vendor/twig/twig/src/Extension/CoreExtension.php b/vendor/twig/twig/src/Extension/CoreExtension.php new file mode 100644 index 0000000..f99adda --- /dev/null +++ b/vendor/twig/twig/src/Extension/CoreExtension.php @@ -0,0 +1,1750 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension { +use Twig\ExpressionParser; +use Twig\Node\Expression\Binary\AddBinary; +use Twig\Node\Expression\Binary\AndBinary; +use Twig\Node\Expression\Binary\BitwiseAndBinary; +use Twig\Node\Expression\Binary\BitwiseOrBinary; +use Twig\Node\Expression\Binary\BitwiseXorBinary; +use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\Expression\Binary\DivBinary; +use Twig\Node\Expression\Binary\EndsWithBinary; +use Twig\Node\Expression\Binary\EqualBinary; +use Twig\Node\Expression\Binary\FloorDivBinary; +use Twig\Node\Expression\Binary\GreaterBinary; +use Twig\Node\Expression\Binary\GreaterEqualBinary; +use Twig\Node\Expression\Binary\HasEveryBinary; +use Twig\Node\Expression\Binary\HasSomeBinary; +use Twig\Node\Expression\Binary\InBinary; +use Twig\Node\Expression\Binary\LessBinary; +use Twig\Node\Expression\Binary\LessEqualBinary; +use Twig\Node\Expression\Binary\MatchesBinary; +use Twig\Node\Expression\Binary\ModBinary; +use Twig\Node\Expression\Binary\MulBinary; +use Twig\Node\Expression\Binary\NotEqualBinary; +use Twig\Node\Expression\Binary\NotInBinary; +use Twig\Node\Expression\Binary\OrBinary; +use Twig\Node\Expression\Binary\PowerBinary; +use Twig\Node\Expression\Binary\RangeBinary; +use Twig\Node\Expression\Binary\SpaceshipBinary; +use Twig\Node\Expression\Binary\StartsWithBinary; +use Twig\Node\Expression\Binary\SubBinary; +use Twig\Node\Expression\Filter\DefaultFilter; +use Twig\Node\Expression\NullCoalesceExpression; +use Twig\Node\Expression\Test\ConstantTest; +use Twig\Node\Expression\Test\DefinedTest; +use Twig\Node\Expression\Test\DivisiblebyTest; +use Twig\Node\Expression\Test\EvenTest; +use Twig\Node\Expression\Test\NullTest; +use Twig\Node\Expression\Test\OddTest; +use Twig\Node\Expression\Test\SameasTest; +use Twig\Node\Expression\Unary\NegUnary; +use Twig\Node\Expression\Unary\NotUnary; +use Twig\Node\Expression\Unary\PosUnary; +use Twig\NodeVisitor\MacroAutoImportNodeVisitor; +use Twig\TokenParser\ApplyTokenParser; +use Twig\TokenParser\BlockTokenParser; +use Twig\TokenParser\DeprecatedTokenParser; +use Twig\TokenParser\DoTokenParser; +use Twig\TokenParser\EmbedTokenParser; +use Twig\TokenParser\ExtendsTokenParser; +use Twig\TokenParser\FlushTokenParser; +use Twig\TokenParser\ForTokenParser; +use Twig\TokenParser\FromTokenParser; +use Twig\TokenParser\IfTokenParser; +use Twig\TokenParser\ImportTokenParser; +use Twig\TokenParser\IncludeTokenParser; +use Twig\TokenParser\MacroTokenParser; +use Twig\TokenParser\SetTokenParser; +use Twig\TokenParser\UseTokenParser; +use Twig\TokenParser\WithTokenParser; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +final class CoreExtension extends AbstractExtension +{ + private $dateFormats = ['F j, Y H:i', '%d days']; + private $numberFormat = [0, '.', ',']; + private $timezone = null; + + /** + * Sets the default format to be used by the date filter. + * + * @param string $format The default date format string + * @param string $dateIntervalFormat The default date interval format string + */ + public function setDateFormat($format = null, $dateIntervalFormat = null) + { + if (null !== $format) { + $this->dateFormats[0] = $format; + } + + if (null !== $dateIntervalFormat) { + $this->dateFormats[1] = $dateIntervalFormat; + } + } + + /** + * Gets the default format to be used by the date filter. + * + * @return array The default date format string and the default date interval format string + */ + public function getDateFormat() + { + return $this->dateFormats; + } + + /** + * Sets the default timezone to be used by the date filter. + * + * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object + */ + public function setTimezone($timezone) + { + $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone); + } + + /** + * Gets the default timezone to be used by the date filter. + * + * @return \DateTimeZone The default timezone currently in use + */ + public function getTimezone() + { + if (null === $this->timezone) { + $this->timezone = new \DateTimeZone(date_default_timezone_get()); + } + + return $this->timezone; + } + + /** + * Sets the default format to be used by the number_format filter. + * + * @param int $decimal the number of decimal places to use + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + */ + public function setNumberFormat($decimal, $decimalPoint, $thousandSep) + { + $this->numberFormat = [$decimal, $decimalPoint, $thousandSep]; + } + + /** + * Get the default format used by the number_format filter. + * + * @return array The arguments for number_format() + */ + public function getNumberFormat() + { + return $this->numberFormat; + } + + public function getTokenParsers(): array + { + return [ + new ApplyTokenParser(), + new ForTokenParser(), + new IfTokenParser(), + new ExtendsTokenParser(), + new IncludeTokenParser(), + new BlockTokenParser(), + new UseTokenParser(), + new MacroTokenParser(), + new ImportTokenParser(), + new FromTokenParser(), + new SetTokenParser(), + new FlushTokenParser(), + new DoTokenParser(), + new EmbedTokenParser(), + new WithTokenParser(), + new DeprecatedTokenParser(), + ]; + } + + public function getFilters(): array + { + return [ + // formatting filters + new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]), + new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]), + new TwigFilter('format', 'twig_sprintf'), + new TwigFilter('replace', 'twig_replace_filter'), + new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]), + new TwigFilter('abs', 'abs'), + new TwigFilter('round', 'twig_round'), + + // encoding + new TwigFilter('url_encode', 'twig_urlencode_filter'), + new TwigFilter('json_encode', 'json_encode'), + new TwigFilter('convert_encoding', 'twig_convert_encoding'), + + // string filters + new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]), + new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]), + new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]), + new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]), + new TwigFilter('striptags', 'twig_striptags'), + new TwigFilter('trim', 'twig_trim_filter'), + new TwigFilter('nl2br', 'twig_nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]), + new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]), + + // array helpers + new TwigFilter('join', 'twig_join_filter'), + new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]), + new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]), + new TwigFilter('merge', 'twig_array_merge'), + new TwigFilter('batch', 'twig_array_batch'), + new TwigFilter('column', 'twig_array_column'), + new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]), + new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]), + new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]), + + // string/array filters + new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]), + new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]), + new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]), + new TwigFilter('first', 'twig_first', ['needs_environment' => true]), + new TwigFilter('last', 'twig_last', ['needs_environment' => true]), + + // iteration and runtime + new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]), + new TwigFilter('keys', 'twig_get_array_keys_filter'), + ]; + } + + public function getFunctions(): array + { + return [ + new TwigFunction('max', 'max'), + new TwigFunction('min', 'min'), + new TwigFunction('range', 'range'), + new TwigFunction('constant', 'twig_constant'), + new TwigFunction('cycle', 'twig_cycle'), + new TwigFunction('random', 'twig_random', ['needs_environment' => true]), + new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]), + new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]), + new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]), + ]; + } + + public function getTests(): array + { + return [ + new TwigTest('even', null, ['node_class' => EvenTest::class]), + new TwigTest('odd', null, ['node_class' => OddTest::class]), + new TwigTest('defined', null, ['node_class' => DefinedTest::class]), + new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]), + new TwigTest('none', null, ['node_class' => NullTest::class]), + new TwigTest('null', null, ['node_class' => NullTest::class]), + new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]), + new TwigTest('constant', null, ['node_class' => ConstantTest::class]), + new TwigTest('empty', 'twig_test_empty'), + new TwigTest('iterable', 'twig_test_iterable'), + ]; + } + + public function getNodeVisitors(): array + { + return [new MacroAutoImportNodeVisitor()]; + } + + public function getOperators(): array + { + return [ + [ + 'not' => ['precedence' => 50, 'class' => NotUnary::class], + '-' => ['precedence' => 500, 'class' => NegUnary::class], + '+' => ['precedence' => 500, 'class' => PosUnary::class], + ], + [ + 'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has some' => ['precedence' => 20, 'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'has every' => ['precedence' => 20, 'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT], + '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT], + ], + ]; + } +} +} + +namespace { + use Twig\Environment; + use Twig\Error\LoaderError; + use Twig\Error\RuntimeError; + use Twig\Extension\CoreExtension; + use Twig\Extension\SandboxExtension; + use Twig\Markup; + use Twig\Source; + use Twig\Template; + use Twig\TemplateWrapper; + +/** + * Cycles over a value. + * + * @param \ArrayAccess|array $values + * @param int $position The cycle position + * + * @return string The next value in the cycle + */ +function twig_cycle($values, $position) +{ + if (!\is_array($values) && !$values instanceof \ArrayAccess) { + return $values; + } + + return $values[$position % \count($values)]; +} + +/** + * Returns a random value depending on the supplied parameter type: + * - a random item from a \Traversable or array + * - a random character from a string + * - a random integer between 0 and the integer parameter. + * + * @param \Traversable|array|int|float|string $values The values to pick a random item from + * @param int|null $max Maximum value used when $values is an int + * + * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) + * + * @return mixed A random value from the given sequence + */ +function twig_random(Environment $env, $values = null, $max = null) +{ + if (null === $values) { + return null === $max ? mt_rand() : mt_rand(0, (int) $max); + } + + if (\is_int($values) || \is_float($values)) { + if (null === $max) { + if ($values < 0) { + $max = 0; + $min = $values; + } else { + $max = $values; + $min = 0; + } + } else { + $min = $values; + $max = $max; + } + + return mt_rand((int) $min, (int) $max); + } + + if (\is_string($values)) { + if ('' === $values) { + return ''; + } + + $charset = $env->getCharset(); + + if ('UTF-8' !== $charset) { + $values = twig_convert_encoding($values, 'UTF-8', $charset); + } + + // unicode version of str_split() + // split at all positions, but not after the start and not before the end + $values = preg_split('/(?<!^)(?!$)/u', $values); + + if ('UTF-8' !== $charset) { + foreach ($values as $i => $value) { + $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); + } + } + } + + if (!twig_test_iterable($values)) { + return $values; + } + + $values = twig_to_array($values); + + if (0 === \count($values)) { + throw new RuntimeError('The random function cannot pick from an empty array.'); + } + + return $values[array_rand($values, 1)]; +} + +/** + * Converts a date to the given format. + * + * {{ post.published_at|date("m/d/Y") }} + * + * @param \DateTimeInterface|\DateInterval|string $date A date + * @param string|null $format The target format, null to use the default + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return string The formatted date + */ +function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) +{ + if (null === $format) { + $formats = $env->getExtension(CoreExtension::class)->getDateFormat(); + $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; + } + + if ($date instanceof \DateInterval) { + return $date->format($format); + } + + return twig_date_converter($env, $date, $timezone)->format($format); +} + +/** + * Returns a new date object modified. + * + * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} + * + * @param \DateTimeInterface|string $date A date + * @param string $modifier A modifier string + * + * @return \DateTimeInterface + */ +function twig_date_modify_filter(Environment $env, $date, $modifier) +{ + $date = twig_date_converter($env, $date, false); + + return $date->modify($modifier); +} + +/** + * Returns a formatted string. + * + * @param string|null $format + * @param ...$values + * + * @return string + */ +function twig_sprintf($format, ...$values) +{ + return sprintf($format ?? '', ...$values); +} + +/** + * Converts an input to a \DateTime instance. + * + * {% if date(user.created_at) < date('+2days') %} + * {# do something #} + * {% endif %} + * + * @param \DateTimeInterface|string|null $date A date or null to use the current time + * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged + * + * @return \DateTimeInterface + */ +function twig_date_converter(Environment $env, $date = null, $timezone = null) +{ + // determine the timezone + if (false !== $timezone) { + if (null === $timezone) { + $timezone = $env->getExtension(CoreExtension::class)->getTimezone(); + } elseif (!$timezone instanceof \DateTimeZone) { + $timezone = new \DateTimeZone($timezone); + } + } + + // immutable dates + if ($date instanceof \DateTimeImmutable) { + return false !== $timezone ? $date->setTimezone($timezone) : $date; + } + + if ($date instanceof \DateTimeInterface) { + $date = clone $date; + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; + } + + if (null === $date || 'now' === $date) { + if (null === $date) { + $date = 'now'; + } + + return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); + } + + $asString = (string) $date; + if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { + $date = new \DateTime('@'.$date); + } else { + $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone()); + } + + if (false !== $timezone) { + $date->setTimezone($timezone); + } + + return $date; +} + +/** + * Replaces strings within a string. + * + * @param string|null $str String to replace in + * @param array|\Traversable $from Replace values + * + * @return string + */ +function twig_replace_filter($str, $from) +{ + if (!twig_test_iterable($from)) { + throw new RuntimeError(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from))); + } + + return strtr($str ?? '', twig_to_array($from)); +} + +/** + * Rounds a number. + * + * @param int|float|string|null $value The value to round + * @param int|float $precision The rounding precision + * @param string $method The method to use for rounding + * + * @return int|float The rounded number + */ +function twig_round($value, $precision = 0, $method = 'common') +{ + $value = (float) $value; + + if ('common' === $method) { + return round($value, $precision); + } + + if ('ceil' !== $method && 'floor' !== $method) { + throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); + } + + return $method($value * 10 ** $precision) / 10 ** $precision; +} + +/** + * Number format filter. + * + * All of the formatting options can be left null, in that case the defaults will + * be used. Supplying any of the parameters will override the defaults set in the + * environment object. + * + * @param mixed $number A float/int/string of the number to format + * @param int $decimal the number of decimal points to display + * @param string $decimalPoint the character(s) to use for the decimal point + * @param string $thousandSep the character(s) to use for the thousands separator + * + * @return string The formatted number + */ +function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) +{ + $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat(); + if (null === $decimal) { + $decimal = $defaults[0]; + } + + if (null === $decimalPoint) { + $decimalPoint = $defaults[1]; + } + + if (null === $thousandSep) { + $thousandSep = $defaults[2]; + } + + return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); +} + +/** + * URL encodes (RFC 3986) a string as a path segment or an array as a query string. + * + * @param string|array|null $url A URL or an array of query parameters + * + * @return string The URL encoded value + */ +function twig_urlencode_filter($url) +{ + if (\is_array($url)) { + return http_build_query($url, '', '&', \PHP_QUERY_RFC3986); + } + + return rawurlencode($url ?? ''); +} + +/** + * Merges an array with another one. + * + * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} + * + * {% set items = items|merge({ 'peugeot': 'car' }) %} + * + * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} + * + * @param array|\Traversable $arr1 An array + * @param array|\Traversable $arr2 An array + * + * @return array The merged array + */ +function twig_array_merge($arr1, $arr2) +{ + if (!twig_test_iterable($arr1)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); + } + + if (!twig_test_iterable($arr2)) { + throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); + } + + return array_merge(twig_to_array($arr1), twig_to_array($arr2)); +} + +/** + * Slices a variable. + * + * @param mixed $item A variable + * @param int $start Start of the slice + * @param int $length Size of the slice + * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) + * + * @return mixed The sliced variable + */ +function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + while ($item instanceof \IteratorAggregate) { + $item = $item->getIterator(); + } + + if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { + try { + return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); + } catch (\OutOfBoundsException $e) { + return []; + } + } + + $item = iterator_to_array($item, $preserveKeys); + } + + if (\is_array($item)) { + return \array_slice($item, $start, $length, $preserveKeys); + } + + return (string) mb_substr((string) $item, $start, $length, $env->getCharset()); +} + +/** + * Returns the first element of the item. + * + * @param mixed $item A variable + * + * @return mixed The first element of the item + */ +function twig_first(Environment $env, $item) +{ + $elements = twig_slice($env, $item, 0, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Returns the last element of the item. + * + * @param mixed $item A variable + * + * @return mixed The last element of the item + */ +function twig_last(Environment $env, $item) +{ + $elements = twig_slice($env, $item, -1, 1, false); + + return \is_string($elements) ? $elements : current($elements); +} + +/** + * Joins the values to a string. + * + * The separators between elements are empty strings per default, you can define them with the optional parameters. + * + * {{ [1, 2, 3]|join(', ', ' and ') }} + * {# returns 1, 2 and 3 #} + * + * {{ [1, 2, 3]|join('|') }} + * {# returns 1|2|3 #} + * + * {{ [1, 2, 3]|join }} + * {# returns 123 #} + * + * @param array $value An array + * @param string $glue The separator + * @param string|null $and The separator for the last pair + * + * @return string The concatenated string + */ +function twig_join_filter($value, $glue = '', $and = null) +{ + if (!twig_test_iterable($value)) { + $value = (array) $value; + } + + $value = twig_to_array($value, false); + + if (0 === \count($value)) { + return ''; + } + + if (null === $and || $and === $glue) { + return implode($glue, $value); + } + + if (1 === \count($value)) { + return $value[0]; + } + + return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; +} + +/** + * Splits the string into an array. + * + * {{ "one,two,three"|split(',') }} + * {# returns [one, two, three] #} + * + * {{ "one,two,three,four,five"|split(',', 3) }} + * {# returns [one, two, "three,four,five"] #} + * + * {{ "123"|split('') }} + * {# returns [1, 2, 3] #} + * + * {{ "aabbcc"|split('', 2) }} + * {# returns [aa, bb, cc] #} + * + * @param string|null $value A string + * @param string $delimiter The delimiter + * @param int $limit The limit + * + * @return array The split string as an array + */ +function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) +{ + $value = $value ?? ''; + + if (\strlen($delimiter) > 0) { + return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); + } + + if ($limit <= 1) { + return preg_split('/(?<!^)(?!$)/u', $value); + } + + $length = mb_strlen($value, $env->getCharset()); + if ($length < $limit) { + return [$value]; + } + + $r = []; + for ($i = 0; $i < $length; $i += $limit) { + $r[] = mb_substr($value, $i, $limit, $env->getCharset()); + } + + return $r; +} + +// The '_default' filter is used internally to avoid using the ternary operator +// which costs a lot for big contexts (before PHP 5.4). So, on average, +// a function call is cheaper. +/** + * @internal + */ +function _twig_default_filter($value, $default = '') +{ + if (twig_test_empty($value)) { + return $default; + } + + return $value; +} + +/** + * Returns the keys for the given array. + * + * It is useful when you want to iterate over the keys of an array: + * + * {% for key in array|keys %} + * {# ... #} + * {% endfor %} + * + * @param array $array An array + * + * @return array The keys + */ +function twig_get_array_keys_filter($array) +{ + if ($array instanceof \Traversable) { + while ($array instanceof \IteratorAggregate) { + $array = $array->getIterator(); + } + + $keys = []; + if ($array instanceof \Iterator) { + $array->rewind(); + while ($array->valid()) { + $keys[] = $array->key(); + $array->next(); + } + + return $keys; + } + + foreach ($array as $key => $item) { + $keys[] = $key; + } + + return $keys; + } + + if (!\is_array($array)) { + return []; + } + + return array_keys($array); +} + +/** + * Reverses a variable. + * + * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string + * @param bool $preserveKeys Whether to preserve key or not + * + * @return mixed The reversed input + */ +function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) +{ + if ($item instanceof \Traversable) { + return array_reverse(iterator_to_array($item), $preserveKeys); + } + + if (\is_array($item)) { + return array_reverse($item, $preserveKeys); + } + + $string = (string) $item; + + $charset = $env->getCharset(); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + preg_match_all('/./us', $string, $matches); + + $string = implode('', array_reverse($matches[0])); + + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, $charset, 'UTF-8'); + } + + return $string; +} + +/** + * Sorts an array. + * + * @param array|\Traversable $array + * + * @return array + */ +function twig_sort_filter(Environment $env, $array, $arrow = null) +{ + if ($array instanceof \Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); + } + + if (null !== $arrow) { + twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter'); + + uasort($array, $arrow); + } else { + asort($array); + } + + return $array; +} + +/** + * @internal + */ +function twig_in_filter($value, $compare) +{ + if ($value instanceof Markup) { + $value = (string) $value; + } + if ($compare instanceof Markup) { + $compare = (string) $compare; + } + + if (\is_string($compare)) { + if (\is_string($value) || \is_int($value) || \is_float($value)) { + return '' === $value || false !== strpos($compare, (string) $value); + } + + return false; + } + + if (!is_iterable($compare)) { + return false; + } + + if (\is_object($value) || \is_resource($value)) { + if (!\is_array($compare)) { + foreach ($compare as $item) { + if ($item === $value) { + return true; + } + } + + return false; + } + + return \in_array($value, $compare, true); + } + + foreach ($compare as $item) { + if (0 === twig_compare($value, $item)) { + return true; + } + } + + return false; +} + +/** + * Compares two values using a more strict version of the PHP non-strict comparison operator. + * + * @see https://wiki.php.net/rfc/string_to_number_comparison + * @see https://wiki.php.net/rfc/trailing_whitespace_numerics + * + * @internal + */ +function twig_compare($a, $b) +{ + // int <=> string + if (\is_int($a) && \is_string($b)) { + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + if ((int) $bTrim == $bTrim) { + return $a <=> (int) $bTrim; + } else { + return (float) $a <=> (float) $bTrim; + } + } + if (\is_string($a) && \is_int($b)) { + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + if ((int) $aTrim == $aTrim) { + return (int) $aTrim <=> $b; + } else { + return (float) $aTrim <=> (float) $b; + } + } + + // float <=> string + if (\is_float($a) && \is_string($b)) { + if (is_nan($a)) { + return 1; + } + $bTrim = trim($b, " \t\n\r\v\f"); + if (!is_numeric($bTrim)) { + return (string) $a <=> $b; + } + + return $a <=> (float) $bTrim; + } + if (\is_string($a) && \is_float($b)) { + if (is_nan($b)) { + return 1; + } + $aTrim = trim($a, " \t\n\r\v\f"); + if (!is_numeric($aTrim)) { + return $a <=> (string) $b; + } + + return (float) $aTrim <=> $b; + } + + // fallback to <=> + return $a <=> $b; +} + +/** + * @param string $pattern + * @param string|null $subject + * + * @return int + * + * @throws RuntimeError When an invalid pattern is used + */ +function twig_matches(string $regexp, ?string $str) +{ + set_error_handler(function ($t, $m) use ($regexp) { + throw new RuntimeError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); + }); + try { + return preg_match($regexp, $str ?? ''); + } finally { + restore_error_handler(); + } +} + +/** + * Returns a trimmed string. + * + * @param string|null $string + * @param string|null $characterMask + * @param string $side + * + * @return string + * + * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') + */ +function twig_trim_filter($string, $characterMask = null, $side = 'both') +{ + if (null === $characterMask) { + $characterMask = " \t\n\r\0\x0B"; + } + + switch ($side) { + case 'both': + return trim($string ?? '', $characterMask); + case 'left': + return ltrim($string ?? '', $characterMask); + case 'right': + return rtrim($string ?? '', $characterMask); + default: + throw new RuntimeError('Trimming side must be "left", "right" or "both".'); + } +} + +/** + * Inserts HTML line breaks before all newlines in a string. + * + * @param string|null $string + * + * @return string + */ +function twig_nl2br($string) +{ + return nl2br($string ?? ''); +} + +/** + * Removes whitespaces between HTML tags. + * + * @param string|null $string + * + * @return string + */ +function twig_spaceless($content) +{ + return trim(preg_replace('/>\s+</', '><', $content ?? '')); +} + +/** + * @param string|null $string + * @param string $to + * @param string $from + * + * @return string + */ +function twig_convert_encoding($string, $to, $from) +{ + if (!\function_exists('iconv')) { + throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + return iconv($from, $to, $string ?? ''); +} + +/** + * Returns the length of a variable. + * + * @param mixed $thing A variable + * + * @return int The length of the value + */ +function twig_length_filter(Environment $env, $thing) +{ + if (null === $thing) { + return 0; + } + + if (is_scalar($thing)) { + return mb_strlen($thing, $env->getCharset()); + } + + if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { + return \count($thing); + } + + if ($thing instanceof \Traversable) { + return iterator_count($thing); + } + + if (method_exists($thing, '__toString') && !$thing instanceof \Countable) { + return mb_strlen((string) $thing, $env->getCharset()); + } + + return 1; +} + +/** + * Converts a string to uppercase. + * + * @param string|null $string A string + * + * @return string The uppercased string + */ +function twig_upper_filter(Environment $env, $string) +{ + return mb_strtoupper($string ?? '', $env->getCharset()); +} + +/** + * Converts a string to lowercase. + * + * @param string|null $string A string + * + * @return string The lowercased string + */ +function twig_lower_filter(Environment $env, $string) +{ + return mb_strtolower($string ?? '', $env->getCharset()); +} + +/** + * Strips HTML and PHP tags from a string. + * + * @param string|null $string + * @param string[]|string|null $string + * + * @return string + */ +function twig_striptags($string, $allowable_tags = null) +{ + return strip_tags($string ?? '', $allowable_tags); +} + +/** + * Returns a titlecased string. + * + * @param string|null $string A string + * + * @return string The titlecased string + */ +function twig_title_string_filter(Environment $env, $string) +{ + if (null !== $charset = $env->getCharset()) { + return mb_convert_case($string ?? '', \MB_CASE_TITLE, $charset); + } + + return ucwords(strtolower($string ?? '')); +} + +/** + * Returns a capitalized string. + * + * @param string|null $string A string + * + * @return string The capitalized string + */ +function twig_capitalize_string_filter(Environment $env, $string) +{ + $charset = $env->getCharset(); + + return mb_strtoupper(mb_substr($string ?? '', 0, 1, $charset), $charset).mb_strtolower(mb_substr($string ?? '', 1, null, $charset), $charset); +} + +/** + * @internal + */ +function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) +{ + if (!method_exists($template, $method)) { + $parent = $template; + while ($parent = $parent->getParent($context)) { + if (method_exists($parent, $method)) { + return $parent->$method(...$args); + } + } + + throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); + } + + return $template->$method(...$args); +} + +/** + * @internal + */ +function twig_ensure_traversable($seq) +{ + if ($seq instanceof \Traversable || \is_array($seq)) { + return $seq; + } + + return []; +} + +/** + * @internal + */ +function twig_to_array($seq, $preserveKeys = true) +{ + if ($seq instanceof \Traversable) { + return iterator_to_array($seq, $preserveKeys); + } + + if (!\is_array($seq)) { + return $seq; + } + + return $preserveKeys ? $seq : array_values($seq); +} + +/** + * Checks if a variable is empty. + * + * {# evaluates to true if the foo variable is null, false, or the empty string #} + * {% if foo is empty %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is empty, false otherwise + */ +function twig_test_empty($value) +{ + if ($value instanceof \Countable) { + return 0 === \count($value); + } + + if ($value instanceof \Traversable) { + return !iterator_count($value); + } + + if (\is_object($value) && method_exists($value, '__toString')) { + return '' === (string) $value; + } + + return '' === $value || false === $value || null === $value || [] === $value; +} + +/** + * Checks if a variable is traversable. + * + * {# evaluates to true if the foo variable is an array or a traversable object #} + * {% if foo is iterable %} + * {# ... #} + * {% endif %} + * + * @param mixed $value A variable + * + * @return bool true if the value is traversable + */ +function twig_test_iterable($value) +{ + return $value instanceof \Traversable || \is_array($value); +} + +/** + * Renders a template. + * + * @param array $context + * @param string|array $template The template to render or an array of templates to try consecutively + * @param array $variables The variables to pass to the template + * @param bool $withContext + * @param bool $ignoreMissing Whether to ignore missing templates or not + * @param bool $sandboxed Whether to sandbox the template or not + * + * @return string The rendered template + */ +function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) +{ + $alreadySandboxed = false; + $sandbox = null; + if ($withContext) { + $variables = array_merge($context, $variables); + } + + if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { + $sandbox = $env->getExtension(SandboxExtension::class); + if (!$alreadySandboxed = $sandbox->isSandboxed()) { + $sandbox->enableSandbox(); + } + + foreach ((\is_array($template) ? $template : [$template]) as $name) { + // if a Template instance is passed, it might have been instantiated outside of a sandbox, check security + if ($name instanceof TemplateWrapper || $name instanceof Template) { + $name->unwrap()->checkSecurity(); + } + } + } + + try { + $loaded = null; + try { + $loaded = $env->resolveTemplate($template); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } + + return $loaded ? $loaded->render($variables) : ''; + } finally { + if ($isSandboxed && !$alreadySandboxed) { + $sandbox->disableSandbox(); + } + } +} + +/** + * Returns a template content without rendering it. + * + * @param string $name The template name + * @param bool $ignoreMissing Whether to ignore missing templates or not + * + * @return string The template source + */ +function twig_source(Environment $env, $name, $ignoreMissing = false) +{ + $loader = $env->getLoader(); + try { + return $loader->getSourceContext($name)->getCode(); + } catch (LoaderError $e) { + if (!$ignoreMissing) { + throw $e; + } + } +} + +/** + * Provides the ability to get constants from instances as well as class/global constants. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return string + */ +function twig_constant($constant, $object = null) +{ + if (null !== $object) { + if ('class' === $constant) { + return \get_class($object); + } + + $constant = \get_class($object).'::'.$constant; + } + + if (!\defined($constant)) { + throw new RuntimeError(sprintf('Constant "%s" is undefined.', $constant)); + } + + return \constant($constant); +} + +/** + * Checks if a constant exists. + * + * @param string $constant The name of the constant + * @param object|null $object The object to get the constant from + * + * @return bool + */ +function twig_constant_is_defined($constant, $object = null) +{ + if (null !== $object) { + if ('class' === $constant) { + return true; + } + + $constant = \get_class($object).'::'.$constant; + } + + return \defined($constant); +} + +/** + * Batches item. + * + * @param array $items An array of items + * @param int $size The size of the batch + * @param mixed $fill A value used to fill missing items + * + * @return array + */ +function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) +{ + if (!twig_test_iterable($items)) { + throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); + } + + $size = ceil($size); + + $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); + + if (null !== $fill && $result) { + $last = \count($result) - 1; + if ($fillCount = $size - \count($result[$last])) { + for ($i = 0; $i < $fillCount; ++$i) { + $result[$last][] = $fill; + } + } + } + + return $result; +} + +/** + * Returns the attribute value for a given array/object. + * + * @param mixed $object The object or array from where to get the item + * @param mixed $item The item to get from the array or object + * @param array $arguments An array of arguments to pass if the item is an object method + * @param string $type The type of attribute (@see \Twig\Template constants) + * @param bool $isDefinedTest Whether this is only a defined check + * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not + * @param int $lineno The template line where the attribute was called + * + * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true + * + * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false + * + * @internal + */ +function twig_get_attribute(Environment $env, Source $source, $object, $item, array $arguments = [], $type = /* Template::ANY_CALL */ 'any', $isDefinedTest = false, $ignoreStrictCheck = false, $sandboxed = false, int $lineno = -1) +{ + // array + if (/* Template::METHOD_CALL */ 'method' !== $type) { + $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; + + if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) + || ($object instanceof ArrayAccess && isset($object[$arrayItem])) + ) { + if ($isDefinedTest) { + return true; + } + + return $object[$arrayItem]; + } + + if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if ($object instanceof ArrayAccess) { + $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); + } elseif (\is_object($object)) { + $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); + } elseif (\is_array($object)) { + if (empty($object)) { + $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); + } else { + $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); + } + } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { + if (null === $object) { + $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + } elseif (null === $object) { + $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); + } else { + $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + } + + if (!\is_object($object)) { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + if (null === $object) { + $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); + } elseif (\is_array($object)) { + $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); + } else { + $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); + } + + throw new RuntimeError($message, $lineno, $source); + } + + if ($object instanceof Template) { + throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); + } + + // object property + if (/* Template::METHOD_CALL */ 'method' !== $type) { + if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); + } + + return $object->$item; + } + } + + static $cache = []; + + $class = \get_class($object); + + // object method + // precedence: getXxx() > isXxx() > hasXxx() + if (!isset($cache[$class])) { + $methods = get_class_methods($object); + sort($methods); + $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); + $classCache = []; + foreach ($methods as $i => $method) { + $classCache[$method] = $method; + $classCache[$lcName = $lcMethods[$i]] = $method; + + if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { + $name = substr($method, 2); + $lcName = substr($lcName, 2); + } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { + $name = substr($method, 3); + $lcName = substr($lcName, 3); + if (\in_array('is'.$lcName, $lcMethods)) { + continue; + } + } else { + continue; + } + + // skip get() and is() methods (in which case, $name is empty) + if ($name) { + if (!isset($classCache[$name])) { + $classCache[$name] = $method; + } + + if (!isset($classCache[$lcName])) { + $classCache[$lcName] = $method; + } + } + } + $cache[$class] = $classCache; + } + + $call = false; + if (isset($cache[$class][$item])) { + $method = $cache[$class][$item]; + } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { + $method = $cache[$class][$lcItem]; + } elseif (isset($cache[$class]['__call'])) { + $method = $item; + $call = true; + } else { + if ($isDefinedTest) { + return false; + } + + if ($ignoreStrictCheck || !$env->isStrictVariables()) { + return; + } + + throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), $lineno, $source); + } + + if ($isDefinedTest) { + return true; + } + + if ($sandboxed) { + $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); + } + + // Some objects throw exceptions when they have __call, and the method we try + // to call is not supported. If ignoreStrictCheck is true, we should return null. + try { + $ret = $object->$method(...$arguments); + } catch (\BadMethodCallException $e) { + if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { + return; + } + throw $e; + } + + return $ret; +} + +/** + * Returns the values from a single column in the input array. + * + * <pre> + * {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %} + * + * {% set fruits = items|column('fruit') %} + * + * {# fruits now contains ['apple', 'orange'] #} + * </pre> + * + * @param array|Traversable $array An array + * @param mixed $name The column name + * @param mixed $index The column to use as the index/keys for the returned array + * + * @return array The array of values + */ +function twig_array_column($array, $name, $index = null): array +{ + if ($array instanceof Traversable) { + $array = iterator_to_array($array); + } elseif (!\is_array($array)) { + throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + return array_column($array, $name, $index); +} + +function twig_array_filter(Environment $env, $array, $arrow) +{ + if (!twig_test_iterable($array)) { + throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); + } + + twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter'); + + if (\is_array($array)) { + return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); + } + + // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator + return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); +} + +function twig_array_map(Environment $env, $array, $arrow) +{ + twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter'); + + $r = []; + foreach ($array as $k => $v) { + $r[$k] = $arrow($v, $k); + } + + return $r; +} + +function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) +{ + twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter'); + + if (!\is_array($array) && !$array instanceof \Traversable) { + throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); + } + + $accumulator = $initial; + foreach ($array as $key => $value) { + $accumulator = $arrow($accumulator, $value, $key); + } + + return $accumulator; +} + +function twig_array_some(Environment $env, $array, $arrow) +{ + twig_check_arrow_in_sandbox($env, $arrow, 'has some', 'operator'); + + foreach ($array as $k => $v) { + if ($arrow($v, $k)) { + return true; + } + } + + return false; +} + +function twig_array_every(Environment $env, $array, $arrow) +{ + twig_check_arrow_in_sandbox($env, $arrow, 'has every', 'operator'); + + foreach ($array as $k => $v) { + if (!$arrow($v, $k)) { + return false; + } + } + + return true; +} + +function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type) +{ + if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { + throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type)); + } +} +} diff --git a/vendor/twig/twig/src/Extension/DebugExtension.php b/vendor/twig/twig/src/Extension/DebugExtension.php new file mode 100644 index 0000000..bfb23d7 --- /dev/null +++ b/vendor/twig/twig/src/Extension/DebugExtension.php @@ -0,0 +1,64 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension { +use Twig\TwigFunction; + +final class DebugExtension extends AbstractExtension +{ + public function getFunctions(): array + { + // dump is safe if var_dump is overridden by xdebug + $isDumpOutputHtmlSafe = \extension_loaded('xdebug') + // false means that it was not set (and the default is on) or it explicitly enabled + && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump')) + // false means that it was not set (and the default is on) or it explicitly enabled + // xdebug.overload_var_dump produces HTML only when html_errors is also enabled + && (false === ini_get('html_errors') || ini_get('html_errors')) + || 'cli' === \PHP_SAPI + ; + + return [ + new TwigFunction('dump', 'twig_var_dump', ['is_safe' => $isDumpOutputHtmlSafe ? ['html'] : [], 'needs_context' => true, 'needs_environment' => true, 'is_variadic' => true]), + ]; + } +} +} + +namespace { +use Twig\Environment; +use Twig\Template; +use Twig\TemplateWrapper; + +function twig_var_dump(Environment $env, $context, ...$vars) +{ + if (!$env->isDebug()) { + return; + } + + ob_start(); + + if (!$vars) { + $vars = []; + foreach ($context as $key => $value) { + if (!$value instanceof Template && !$value instanceof TemplateWrapper) { + $vars[$key] = $value; + } + } + + var_dump($vars); + } else { + var_dump(...$vars); + } + + return ob_get_clean(); +} +} diff --git a/vendor/twig/twig/src/Extension/EscaperExtension.php b/vendor/twig/twig/src/Extension/EscaperExtension.php new file mode 100644 index 0000000..9d2251d --- /dev/null +++ b/vendor/twig/twig/src/Extension/EscaperExtension.php @@ -0,0 +1,416 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension { +use Twig\FileExtensionEscapingStrategy; +use Twig\NodeVisitor\EscaperNodeVisitor; +use Twig\TokenParser\AutoEscapeTokenParser; +use Twig\TwigFilter; + +final class EscaperExtension extends AbstractExtension +{ + private $defaultStrategy; + private $escapers = []; + + /** @internal */ + public $safeClasses = []; + + /** @internal */ + public $safeLookup = []; + + /** + * @param string|false|callable $defaultStrategy An escaping strategy + * + * @see setDefaultStrategy() + */ + public function __construct($defaultStrategy = 'html') + { + $this->setDefaultStrategy($defaultStrategy); + } + + public function getTokenParsers(): array + { + return [new AutoEscapeTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new EscaperNodeVisitor()]; + } + + public function getFilters(): array + { + return [ + new TwigFilter('escape', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('e', 'twig_escape_filter', ['needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe']), + new TwigFilter('raw', 'twig_raw_filter', ['is_safe' => ['all']]), + ]; + } + + /** + * Sets the default strategy to use when not defined by the user. + * + * The strategy can be a valid PHP callback that takes the template + * name as an argument and returns the strategy to use. + * + * @param string|false|callable $defaultStrategy An escaping strategy + */ + public function setDefaultStrategy($defaultStrategy): void + { + if ('name' === $defaultStrategy) { + $defaultStrategy = [FileExtensionEscapingStrategy::class, 'guess']; + } + + $this->defaultStrategy = $defaultStrategy; + } + + /** + * Gets the default strategy to use when not defined by the user. + * + * @param string $name The template name + * + * @return string|false The default strategy to use for the template + */ + public function getDefaultStrategy(string $name) + { + // disable string callables to avoid calling a function named html or js, + // or any other upcoming escaping strategy + if (!\is_string($this->defaultStrategy) && false !== $this->defaultStrategy) { + return \call_user_func($this->defaultStrategy, $name); + } + + return $this->defaultStrategy; + } + + /** + * Defines a new escaper to be used via the escape filter. + * + * @param string $strategy The strategy name that should be used as a strategy in the escape call + * @param callable $callable A valid PHP callable + */ + public function setEscaper($strategy, callable $callable) + { + $this->escapers[$strategy] = $callable; + } + + /** + * Gets all defined escapers. + * + * @return callable[] An array of escapers + */ + public function getEscapers() + { + return $this->escapers; + } + + public function setSafeClasses(array $safeClasses = []) + { + $this->safeClasses = []; + $this->safeLookup = []; + foreach ($safeClasses as $class => $strategies) { + $this->addSafeClass($class, $strategies); + } + } + + public function addSafeClass(string $class, array $strategies) + { + $class = ltrim($class, '\\'); + if (!isset($this->safeClasses[$class])) { + $this->safeClasses[$class] = []; + } + $this->safeClasses[$class] = array_merge($this->safeClasses[$class], $strategies); + + foreach ($strategies as $strategy) { + $this->safeLookup[$strategy][$class] = true; + } + } +} +} + +namespace { +use Twig\Environment; +use Twig\Error\RuntimeError; +use Twig\Extension\EscaperExtension; +use Twig\Markup; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; + +/** + * Marks a variable as being safe. + * + * @param string $string A PHP variable + */ +function twig_raw_filter($string) +{ + return $string; +} + +/** + * Escapes a string. + * + * @param mixed $string The value to be escaped + * @param string $strategy The escaping strategy + * @param string $charset The charset + * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) + * + * @return string + */ +function twig_escape_filter(Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) +{ + if ($autoescape && $string instanceof Markup) { + return $string; + } + + if (!\is_string($string)) { + if (\is_object($string) && method_exists($string, '__toString')) { + if ($autoescape) { + $c = \get_class($string); + $ext = $env->getExtension(EscaperExtension::class); + if (!isset($ext->safeClasses[$c])) { + $ext->safeClasses[$c] = []; + foreach (class_parents($string) + class_implements($string) as $class) { + if (isset($ext->safeClasses[$class])) { + $ext->safeClasses[$c] = array_unique(array_merge($ext->safeClasses[$c], $ext->safeClasses[$class])); + foreach ($ext->safeClasses[$class] as $s) { + $ext->safeLookup[$s][$c] = true; + } + } + } + } + if (isset($ext->safeLookup[$strategy][$c]) || isset($ext->safeLookup['all'][$c])) { + return (string) $string; + } + } + + $string = (string) $string; + } elseif (\in_array($strategy, ['html', 'js', 'css', 'html_attr', 'url'])) { + return $string; + } + } + + if ('' === $string) { + return ''; + } + + if (null === $charset) { + $charset = $env->getCharset(); + } + + switch ($strategy) { + case 'html': + // see https://www.php.net/htmlspecialchars + + // Using a static variable to avoid initializing the array + // each time the function is called. Moving the declaration on the + // top of the function slow downs other escaping strategies. + static $htmlspecialcharsCharsets = [ + 'ISO-8859-1' => true, 'ISO8859-1' => true, + 'ISO-8859-15' => true, 'ISO8859-15' => true, + 'utf-8' => true, 'UTF-8' => true, + 'CP866' => true, 'IBM866' => true, '866' => true, + 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, + '1251' => true, + 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, + 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, + 'BIG5' => true, '950' => true, + 'GB2312' => true, '936' => true, + 'BIG5-HKSCS' => true, + 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, + 'EUC-JP' => true, 'EUCJP' => true, + 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, + ]; + + if (isset($htmlspecialcharsCharsets[$charset])) { + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { + // cache the lowercase variant for future iterations + $htmlspecialcharsCharsets[$charset] = true; + + return htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, $charset); + } + + $string = twig_convert_encoding($string, 'UTF-8', $charset); + $string = htmlspecialchars($string, \ENT_QUOTES | \ENT_SUBSTITUTE, 'UTF-8'); + + return iconv('UTF-8', $charset, $string); + + case 'js': + // escape all non-alphanumeric characters + // into their \x or \uHHHH representations + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) { + $char = $matches[0]; + + /* + * A few characters have short escape sequences in JSON and JavaScript. + * Escape sequences supported only by JavaScript, not JSON, are omitted. + * \" is also supported but omitted, because the resulting string is not HTML safe. + */ + static $shortMap = [ + '\\' => '\\\\', + '/' => '\\/', + "\x08" => '\b', + "\x0C" => '\f', + "\x0A" => '\n', + "\x0D" => '\r', + "\x09" => '\t', + ]; + + if (isset($shortMap[$char])) { + return $shortMap[$char]; + } + + $codepoint = mb_ord($char, 'UTF-8'); + if (0x10000 > $codepoint) { + return sprintf('\u%04X', $codepoint); + } + + // Split characters outside the BMP into surrogate pairs + // https://tools.ietf.org/html/rfc2781.html#section-2.1 + $u = $codepoint - 0x10000; + $high = 0xD800 | ($u >> 10); + $low = 0xDC00 | ($u & 0x3FF); + + return sprintf('\u%04X\u%04X', $high, $low); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'css': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) { + $char = $matches[0]; + + return sprintf('\\%X ', 1 === \strlen($char) ? \ord($char) : mb_ord($char, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'html_attr': + if ('UTF-8' !== $charset) { + $string = twig_convert_encoding($string, 'UTF-8', $charset); + } + + if (!preg_match('//u', $string)) { + throw new RuntimeError('The string to escape is not a valid UTF-8 string.'); + } + + $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) { + /** + * This function is adapted from code coming from Zend Framework. + * + * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com) + * @license https://framework.zend.com/license/new-bsd New BSD License + */ + $chr = $matches[0]; + $ord = \ord($chr); + + /* + * The following replaces characters undefined in HTML with the + * hex entity for the Unicode replacement character. + */ + if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) { + return '�'; + } + + /* + * Check if the current character to escape has a name entity we should + * replace it with while grabbing the hex value of the character. + */ + if (1 === \strlen($chr)) { + /* + * While HTML supports far more named entities, the lowest common denominator + * has become HTML5's XML Serialisation which is restricted to the those named + * entities that XML supports. Using HTML entities would result in this error: + * XML Parsing Error: undefined entity + */ + static $entityMap = [ + 34 => '"', /* quotation mark */ + 38 => '&', /* ampersand */ + 60 => '<', /* less-than sign */ + 62 => '>', /* greater-than sign */ + ]; + + if (isset($entityMap[$ord])) { + return $entityMap[$ord]; + } + + return sprintf('&#x%02X;', $ord); + } + + /* + * Per OWASP recommendations, we'll use hex entities for any other + * characters where a named entity does not exist. + */ + return sprintf('&#x%04X;', mb_ord($chr, 'UTF-8')); + }, $string); + + if ('UTF-8' !== $charset) { + $string = iconv('UTF-8', $charset, $string); + } + + return $string; + + case 'url': + return rawurlencode($string); + + default: + $escapers = $env->getExtension(EscaperExtension::class)->getEscapers(); + if (array_key_exists($strategy, $escapers)) { + return $escapers[$strategy]($env, $string, $charset); + } + + $validStrategies = implode(', ', array_merge(['html', 'js', 'url', 'css', 'html_attr'], array_keys($escapers))); + + throw new RuntimeError(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); + } +} + +/** + * @internal + */ +function twig_escape_filter_is_safe(Node $filterArgs) +{ + foreach ($filterArgs as $arg) { + if ($arg instanceof ConstantExpression) { + return [$arg->getAttribute('value')]; + } + + return []; + } + + return ['html']; +} +} diff --git a/vendor/twig/twig/src/Extension/ExtensionInterface.php b/vendor/twig/twig/src/Extension/ExtensionInterface.php new file mode 100644 index 0000000..ab9c2c3 --- /dev/null +++ b/vendor/twig/twig/src/Extension/ExtensionInterface.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +use Twig\ExpressionParser; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\TokenParserInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +/** + * Interface implemented by extension classes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface ExtensionInterface +{ + /** + * Returns the token parser instances to add to the existing list. + * + * @return TokenParserInterface[] + */ + public function getTokenParsers(); + + /** + * Returns the node visitor instances to add to the existing list. + * + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(); + + /** + * Returns a list of filters to add to the existing list. + * + * @return TwigFilter[] + */ + public function getFilters(); + + /** + * Returns a list of tests to add to the existing list. + * + * @return TwigTest[] + */ + public function getTests(); + + /** + * Returns a list of functions to add to the existing list. + * + * @return TwigFunction[] + */ + public function getFunctions(); + + /** + * Returns a list of operators to add to the existing list. + * + * @return array<array> First array of unary operators, second array of binary operators + * + * @psalm-return array{ + * array<string, array{precedence: int, class: class-string<AbstractUnary>}>, + * array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> + * } + */ + public function getOperators(); +} diff --git a/vendor/twig/twig/src/Extension/GlobalsInterface.php b/vendor/twig/twig/src/Extension/GlobalsInterface.php new file mode 100644 index 0000000..6f1dfe8 --- /dev/null +++ b/vendor/twig/twig/src/Extension/GlobalsInterface.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +/** + * Enables usage of the deprecated Twig\Extension\AbstractExtension::getGlobals() method. + * + * Explicitly implement this interface if you really need to implement the + * deprecated getGlobals() method in your extensions. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface GlobalsInterface +{ + /** + * @return array<string, mixed> + */ + public function getGlobals(): array; +} diff --git a/vendor/twig/twig/src/Extension/OptimizerExtension.php b/vendor/twig/twig/src/Extension/OptimizerExtension.php new file mode 100644 index 0000000..965bfdb --- /dev/null +++ b/vendor/twig/twig/src/Extension/OptimizerExtension.php @@ -0,0 +1,29 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +use Twig\NodeVisitor\OptimizerNodeVisitor; + +final class OptimizerExtension extends AbstractExtension +{ + private $optimizers; + + public function __construct(int $optimizers = -1) + { + $this->optimizers = $optimizers; + } + + public function getNodeVisitors(): array + { + return [new OptimizerNodeVisitor($this->optimizers)]; + } +} diff --git a/vendor/twig/twig/src/Extension/ProfilerExtension.php b/vendor/twig/twig/src/Extension/ProfilerExtension.php new file mode 100644 index 0000000..43e4a44 --- /dev/null +++ b/vendor/twig/twig/src/Extension/ProfilerExtension.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +use Twig\Profiler\NodeVisitor\ProfilerNodeVisitor; +use Twig\Profiler\Profile; + +class ProfilerExtension extends AbstractExtension +{ + private $actives = []; + + public function __construct(Profile $profile) + { + $this->actives[] = $profile; + } + + /** + * @return void + */ + public function enter(Profile $profile) + { + $this->actives[0]->addProfile($profile); + array_unshift($this->actives, $profile); + } + + /** + * @return void + */ + public function leave(Profile $profile) + { + $profile->leave(); + array_shift($this->actives); + + if (1 === \count($this->actives)) { + $this->actives[0]->leave(); + } + } + + public function getNodeVisitors(): array + { + return [new ProfilerNodeVisitor(static::class)]; + } +} diff --git a/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php new file mode 100644 index 0000000..63bc3b1 --- /dev/null +++ b/vendor/twig/twig/src/Extension/RuntimeExtensionInterface.php @@ -0,0 +1,19 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +/** + * @author Grégoire Pineau <lyrixx@lyrixx.info> + */ +interface RuntimeExtensionInterface +{ +} diff --git a/vendor/twig/twig/src/Extension/SandboxExtension.php b/vendor/twig/twig/src/Extension/SandboxExtension.php new file mode 100644 index 0000000..c861159 --- /dev/null +++ b/vendor/twig/twig/src/Extension/SandboxExtension.php @@ -0,0 +1,123 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +use Twig\NodeVisitor\SandboxNodeVisitor; +use Twig\Sandbox\SecurityNotAllowedMethodError; +use Twig\Sandbox\SecurityNotAllowedPropertyError; +use Twig\Sandbox\SecurityPolicyInterface; +use Twig\Source; +use Twig\TokenParser\SandboxTokenParser; + +final class SandboxExtension extends AbstractExtension +{ + private $sandboxedGlobally; + private $sandboxed; + private $policy; + + public function __construct(SecurityPolicyInterface $policy, $sandboxed = false) + { + $this->policy = $policy; + $this->sandboxedGlobally = $sandboxed; + } + + public function getTokenParsers(): array + { + return [new SandboxTokenParser()]; + } + + public function getNodeVisitors(): array + { + return [new SandboxNodeVisitor()]; + } + + public function enableSandbox(): void + { + $this->sandboxed = true; + } + + public function disableSandbox(): void + { + $this->sandboxed = false; + } + + public function isSandboxed(): bool + { + return $this->sandboxedGlobally || $this->sandboxed; + } + + public function isSandboxedGlobally(): bool + { + return $this->sandboxedGlobally; + } + + public function setSecurityPolicy(SecurityPolicyInterface $policy) + { + $this->policy = $policy; + } + + public function getSecurityPolicy(): SecurityPolicyInterface + { + return $this->policy; + } + + public function checkSecurity($tags, $filters, $functions): void + { + if ($this->isSandboxed()) { + $this->policy->checkSecurity($tags, $filters, $functions); + } + } + + public function checkMethodAllowed($obj, $method, int $lineno = -1, Source $source = null): void + { + if ($this->isSandboxed()) { + try { + $this->policy->checkMethodAllowed($obj, $method); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function checkPropertyAllowed($obj, $property, int $lineno = -1, Source $source = null): void + { + if ($this->isSandboxed()) { + try { + $this->policy->checkPropertyAllowed($obj, $property); + } catch (SecurityNotAllowedPropertyError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + } + + public function ensureToStringAllowed($obj, int $lineno = -1, Source $source = null) + { + if ($this->isSandboxed() && \is_object($obj) && method_exists($obj, '__toString')) { + try { + $this->policy->checkMethodAllowed($obj, '__toString'); + } catch (SecurityNotAllowedMethodError $e) { + $e->setSourceContext($source); + $e->setTemplateLine($lineno); + + throw $e; + } + } + + return $obj; + } +} diff --git a/vendor/twig/twig/src/Extension/StagingExtension.php b/vendor/twig/twig/src/Extension/StagingExtension.php new file mode 100644 index 0000000..0ea47f9 --- /dev/null +++ b/vendor/twig/twig/src/Extension/StagingExtension.php @@ -0,0 +1,100 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension; + +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\TokenParserInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +/** + * Used by \Twig\Environment as a staging area. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class StagingExtension extends AbstractExtension +{ + private $functions = []; + private $filters = []; + private $visitors = []; + private $tokenParsers = []; + private $tests = []; + + public function addFunction(TwigFunction $function): void + { + if (isset($this->functions[$function->getName()])) { + throw new \LogicException(sprintf('Function "%s" is already registered.', $function->getName())); + } + + $this->functions[$function->getName()] = $function; + } + + public function getFunctions(): array + { + return $this->functions; + } + + public function addFilter(TwigFilter $filter): void + { + if (isset($this->filters[$filter->getName()])) { + throw new \LogicException(sprintf('Filter "%s" is already registered.', $filter->getName())); + } + + $this->filters[$filter->getName()] = $filter; + } + + public function getFilters(): array + { + return $this->filters; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[] = $visitor; + } + + public function getNodeVisitors(): array + { + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if (isset($this->tokenParsers[$parser->getTag()])) { + throw new \LogicException(sprintf('Tag "%s" is already registered.', $parser->getTag())); + } + + $this->tokenParsers[$parser->getTag()] = $parser; + } + + public function getTokenParsers(): array + { + return $this->tokenParsers; + } + + public function addTest(TwigTest $test): void + { + if (isset($this->tests[$test->getName()])) { + throw new \LogicException(sprintf('Test "%s" is already registered.', $test->getName())); + } + + $this->tests[$test->getName()] = $test; + } + + public function getTests(): array + { + return $this->tests; + } +} diff --git a/vendor/twig/twig/src/Extension/StringLoaderExtension.php b/vendor/twig/twig/src/Extension/StringLoaderExtension.php new file mode 100644 index 0000000..7b45147 --- /dev/null +++ b/vendor/twig/twig/src/Extension/StringLoaderExtension.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Extension { +use Twig\TwigFunction; + +final class StringLoaderExtension extends AbstractExtension +{ + public function getFunctions(): array + { + return [ + new TwigFunction('template_from_string', 'twig_template_from_string', ['needs_environment' => true]), + ]; + } +} +} + +namespace { +use Twig\Environment; +use Twig\TemplateWrapper; + +/** + * Loads a template from a string. + * + * {{ include(template_from_string("Hello {{ name }}")) }} + * + * @param string $template A template as a string or object implementing __toString() + * @param string $name An optional name of the template to be used in error messages + */ +function twig_template_from_string(Environment $env, $template, string $name = null): TemplateWrapper +{ + return $env->createTemplate((string) $template, $name); +} +} diff --git a/vendor/twig/twig/src/ExtensionSet.php b/vendor/twig/twig/src/ExtensionSet.php new file mode 100644 index 0000000..d32200c --- /dev/null +++ b/vendor/twig/twig/src/ExtensionSet.php @@ -0,0 +1,480 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Error\RuntimeError; +use Twig\Extension\ExtensionInterface; +use Twig\Extension\GlobalsInterface; +use Twig\Extension\StagingExtension; +use Twig\Node\Expression\Binary\AbstractBinary; +use Twig\Node\Expression\Unary\AbstractUnary; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\TokenParser\TokenParserInterface; + +/** + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class ExtensionSet +{ + private $extensions; + private $initialized = false; + private $runtimeInitialized = false; + private $staging; + private $parsers; + private $visitors; + /** @var array<string, TwigFilter> */ + private $filters; + /** @var array<string, TwigTest> */ + private $tests; + /** @var array<string, TwigFunction> */ + private $functions; + /** @var array<string, array{precedence: int, class: class-string<AbstractUnary>}> */ + private $unaryOperators; + /** @var array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> */ + private $binaryOperators; + /** @var array<string, mixed> */ + private $globals; + private $functionCallbacks = []; + private $filterCallbacks = []; + private $parserCallbacks = []; + private $lastModified = 0; + + public function __construct() + { + $this->staging = new StagingExtension(); + } + + public function initRuntime() + { + $this->runtimeInitialized = true; + } + + public function hasExtension(string $class): bool + { + return isset($this->extensions[ltrim($class, '\\')]); + } + + public function getExtension(string $class): ExtensionInterface + { + $class = ltrim($class, '\\'); + + if (!isset($this->extensions[$class])) { + throw new RuntimeError(sprintf('The "%s" extension is not enabled.', $class)); + } + + return $this->extensions[$class]; + } + + /** + * @param ExtensionInterface[] $extensions + */ + public function setExtensions(array $extensions): void + { + foreach ($extensions as $extension) { + $this->addExtension($extension); + } + } + + /** + * @return ExtensionInterface[] + */ + public function getExtensions(): array + { + return $this->extensions; + } + + public function getSignature(): string + { + return json_encode(array_keys($this->extensions)); + } + + public function isInitialized(): bool + { + return $this->initialized || $this->runtimeInitialized; + } + + public function getLastModified(): int + { + if (0 !== $this->lastModified) { + return $this->lastModified; + } + + foreach ($this->extensions as $extension) { + $r = new \ReflectionObject($extension); + if (is_file($r->getFileName()) && ($extensionTime = filemtime($r->getFileName())) > $this->lastModified) { + $this->lastModified = $extensionTime; + } + } + + return $this->lastModified; + } + + public function addExtension(ExtensionInterface $extension): void + { + $class = \get_class($extension); + + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $class)); + } + + if (isset($this->extensions[$class])) { + throw new \LogicException(sprintf('Unable to register extension "%s" as it is already registered.', $class)); + } + + $this->extensions[$class] = $extension; + } + + public function addFunction(TwigFunction $function): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $function->getName())); + } + + $this->staging->addFunction($function); + } + + /** + * @return TwigFunction[] + */ + public function getFunctions(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->functions; + } + + public function getFunction(string $name): ?TwigFunction + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->functions[$name])) { + return $this->functions[$name]; + } + + foreach ($this->functions as $pattern => $function) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $function->setArguments($matches); + + return $function; + } + } + + foreach ($this->functionCallbacks as $callback) { + if (false !== $function = $callback($name)) { + return $function; + } + } + + return null; + } + + public function registerUndefinedFunctionCallback(callable $callable): void + { + $this->functionCallbacks[] = $callable; + } + + public function addFilter(TwigFilter $filter): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $filter->getName())); + } + + $this->staging->addFilter($filter); + } + + /** + * @return TwigFilter[] + */ + public function getFilters(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->filters; + } + + public function getFilter(string $name): ?TwigFilter + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->filters[$name])) { + return $this->filters[$name]; + } + + foreach ($this->filters as $pattern => $filter) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count && preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $filter->setArguments($matches); + + return $filter; + } + } + + foreach ($this->filterCallbacks as $callback) { + if (false !== $filter = $callback($name)) { + return $filter; + } + } + + return null; + } + + public function registerUndefinedFilterCallback(callable $callable): void + { + $this->filterCallbacks[] = $callable; + } + + public function addNodeVisitor(NodeVisitorInterface $visitor): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a node visitor as extensions have already been initialized.'); + } + + $this->staging->addNodeVisitor($visitor); + } + + /** + * @return NodeVisitorInterface[] + */ + public function getNodeVisitors(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->visitors; + } + + public function addTokenParser(TokenParserInterface $parser): void + { + if ($this->initialized) { + throw new \LogicException('Unable to add a token parser as extensions have already been initialized.'); + } + + $this->staging->addTokenParser($parser); + } + + /** + * @return TokenParserInterface[] + */ + public function getTokenParsers(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->parsers; + } + + public function getTokenParser(string $name): ?TokenParserInterface + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->parsers[$name])) { + return $this->parsers[$name]; + } + + foreach ($this->parserCallbacks as $callback) { + if (false !== $parser = $callback($name)) { + return $parser; + } + } + + return null; + } + + public function registerUndefinedTokenParserCallback(callable $callable): void + { + $this->parserCallbacks[] = $callable; + } + + /** + * @return array<string, mixed> + */ + public function getGlobals(): array + { + if (null !== $this->globals) { + return $this->globals; + } + + $globals = []; + foreach ($this->extensions as $extension) { + if (!$extension instanceof GlobalsInterface) { + continue; + } + + $extGlobals = $extension->getGlobals(); + if (!\is_array($extGlobals)) { + throw new \UnexpectedValueException(sprintf('"%s::getGlobals()" must return an array of globals.', \get_class($extension))); + } + + $globals = array_merge($globals, $extGlobals); + } + + if ($this->initialized) { + $this->globals = $globals; + } + + return $globals; + } + + public function addTest(TwigTest $test): void + { + if ($this->initialized) { + throw new \LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $test->getName())); + } + + $this->staging->addTest($test); + } + + /** + * @return TwigTest[] + */ + public function getTests(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->tests; + } + + public function getTest(string $name): ?TwigTest + { + if (!$this->initialized) { + $this->initExtensions(); + } + + if (isset($this->tests[$name])) { + return $this->tests[$name]; + } + + foreach ($this->tests as $pattern => $test) { + $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count); + + if ($count) { + if (preg_match('#^'.$pattern.'$#', $name, $matches)) { + array_shift($matches); + $test->setArguments($matches); + + return $test; + } + } + } + + return null; + } + + /** + * @return array<string, array{precedence: int, class: class-string<AbstractUnary>}> + */ + public function getUnaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->unaryOperators; + } + + /** + * @return array<string, array{precedence: int, class: class-string<AbstractBinary>, associativity: ExpressionParser::OPERATOR_*}> + */ + public function getBinaryOperators(): array + { + if (!$this->initialized) { + $this->initExtensions(); + } + + return $this->binaryOperators; + } + + private function initExtensions(): void + { + $this->parsers = []; + $this->filters = []; + $this->functions = []; + $this->tests = []; + $this->visitors = []; + $this->unaryOperators = []; + $this->binaryOperators = []; + + foreach ($this->extensions as $extension) { + $this->initExtension($extension); + } + $this->initExtension($this->staging); + // Done at the end only, so that an exception during initialization does not mark the environment as initialized when catching the exception + $this->initialized = true; + } + + private function initExtension(ExtensionInterface $extension): void + { + // filters + foreach ($extension->getFilters() as $filter) { + $this->filters[$filter->getName()] = $filter; + } + + // functions + foreach ($extension->getFunctions() as $function) { + $this->functions[$function->getName()] = $function; + } + + // tests + foreach ($extension->getTests() as $test) { + $this->tests[$test->getName()] = $test; + } + + // token parsers + foreach ($extension->getTokenParsers() as $parser) { + if (!$parser instanceof TokenParserInterface) { + throw new \LogicException('getTokenParsers() must return an array of \Twig\TokenParser\TokenParserInterface.'); + } + + $this->parsers[$parser->getTag()] = $parser; + } + + // node visitors + foreach ($extension->getNodeVisitors() as $visitor) { + $this->visitors[] = $visitor; + } + + // operators + if ($operators = $extension->getOperators()) { + if (!\is_array($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array with operators, got "%s".', \get_class($extension), \is_object($operators) ? \get_class($operators) : \gettype($operators).(\is_resource($operators) ? '' : '#'.$operators))); + } + + if (2 !== \count($operators)) { + throw new \InvalidArgumentException(sprintf('"%s::getOperators()" must return an array of 2 elements, got %d.', \get_class($extension), \count($operators))); + } + + $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]); + $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]); + } + } +} diff --git a/vendor/twig/twig/src/FileExtensionEscapingStrategy.php b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php new file mode 100644 index 0000000..65198bb --- /dev/null +++ b/vendor/twig/twig/src/FileExtensionEscapingStrategy.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +/** + * Default autoescaping strategy based on file names. + * + * This strategy sets the HTML as the default autoescaping strategy, + * but changes it based on the template name. + * + * Note that there is no runtime performance impact as the + * default autoescaping strategy is set at compilation time. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FileExtensionEscapingStrategy +{ + /** + * Guesses the best autoescaping strategy based on the file name. + * + * @param string $name The template name + * + * @return string|false The escaping strategy name to use or false to disable + */ + public static function guess(string $name) + { + if (\in_array(substr($name, -1), ['/', '\\'])) { + return 'html'; // return html for directories + } + + if ('.twig' === substr($name, -5)) { + $name = substr($name, 0, -5); + } + + $extension = pathinfo($name, \PATHINFO_EXTENSION); + + switch ($extension) { + case 'js': + return 'js'; + + case 'css': + return 'css'; + + case 'txt': + return false; + + default: + return 'html'; + } + } +} diff --git a/vendor/twig/twig/src/Lexer.php b/vendor/twig/twig/src/Lexer.php new file mode 100644 index 0000000..9ff028c --- /dev/null +++ b/vendor/twig/twig/src/Lexer.php @@ -0,0 +1,501 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Error\SyntaxError; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class Lexer +{ + private $tokens; + private $code; + private $cursor; + private $lineno; + private $end; + private $state; + private $states; + private $brackets; + private $env; + private $source; + private $options; + private $regexes; + private $position; + private $positions; + private $currentVarBlockLine; + + public const STATE_DATA = 0; + public const STATE_BLOCK = 1; + public const STATE_VAR = 2; + public const STATE_STRING = 3; + public const STATE_INTERPOLATION = 4; + + public const REGEX_NAME = '/[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/A'; + public const REGEX_NUMBER = '/[0-9]+(?:\.[0-9]+)?([Ee][\+\-][0-9]+)?/A'; + public const REGEX_STRING = '/"([^#"\\\\]*(?:\\\\.[^#"\\\\]*)*)"|\'([^\'\\\\]*(?:\\\\.[^\'\\\\]*)*)\'/As'; + public const REGEX_DQ_STRING_DELIM = '/"/A'; + public const REGEX_DQ_STRING_PART = '/[^#"\\\\]*(?:(?:\\\\.|#(?!\{))[^#"\\\\]*)*/As'; + public const PUNCTUATION = '()[]{}?:.,|'; + + public function __construct(Environment $env, array $options = []) + { + $this->env = $env; + + $this->options = array_merge([ + 'tag_comment' => ['{#', '#}'], + 'tag_block' => ['{%', '%}'], + 'tag_variable' => ['{{', '}}'], + 'whitespace_trim' => '-', + 'whitespace_line_trim' => '~', + 'whitespace_line_chars' => ' \t\0\x0B', + 'interpolation' => ['#{', '}'], + ], $options); + + // when PHP 7.3 is the min version, we will be able to remove the '#' part in preg_quote as it's part of the default + $this->regexes = [ + // }} + 'lex_var' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_variable'][1], '#').'\s*'. // -}}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_variable'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~}}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_variable'][1], '#'). // }} + ') + }Ax', + + // %} + 'lex_block' => '{ + \s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*\n?'. // -%}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#').'\n?'. // %}\n? + ') + }Ax', + + // {% endverbatim %} + 'lex_raw_data' => '{'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '('. + $this->options['whitespace_trim']. // - + '|'. + $this->options['whitespace_line_trim']. // ~ + ')?\s*endverbatim\s*'. + '(?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%} + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }sx', + + 'operator' => $this->getOperatorRegex(), + + // #} + 'lex_comment' => '{ + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_comment'][1], '#').'\s*\n?'. // -#}\s*\n? + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_comment'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~#}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_comment'][1], '#').'\n?'. // #}\n? + ') + }sx', + + // verbatim %} + 'lex_block_raw' => '{ + \s*verbatim\s* + (?:'. + preg_quote($this->options['whitespace_trim'].$this->options['tag_block'][1], '#').'\s*'. // -%}\s* + '|'. + preg_quote($this->options['whitespace_line_trim'].$this->options['tag_block'][1], '#').'['.$this->options['whitespace_line_chars'].']*'. // ~%}[ \t\0\x0B]* + '|'. + preg_quote($this->options['tag_block'][1], '#'). // %} + ') + }Asx', + + 'lex_block_line' => '{\s*line\s+(\d+)\s*'.preg_quote($this->options['tag_block'][1], '#').'}As', + + // {{ or {% or {# + 'lex_tokens_start' => '{ + ('. + preg_quote($this->options['tag_variable'][0], '#'). // {{ + '|'. + preg_quote($this->options['tag_block'][0], '#'). // {% + '|'. + preg_quote($this->options['tag_comment'][0], '#'). // {# + ')('. + preg_quote($this->options['whitespace_trim'], '#'). // - + '|'. + preg_quote($this->options['whitespace_line_trim'], '#'). // ~ + ')? + }sx', + 'interpolation_start' => '{'.preg_quote($this->options['interpolation'][0], '#').'\s*}A', + 'interpolation_end' => '{\s*'.preg_quote($this->options['interpolation'][1], '#').'}A', + ]; + } + + public function tokenize(Source $source): TokenStream + { + $this->source = $source; + $this->code = str_replace(["\r\n", "\r"], "\n", $source->getCode()); + $this->cursor = 0; + $this->lineno = 1; + $this->end = \strlen($this->code); + $this->tokens = []; + $this->state = self::STATE_DATA; + $this->states = []; + $this->brackets = []; + $this->position = -1; + + // find all token starts in one go + preg_match_all($this->regexes['lex_tokens_start'], $this->code, $matches, \PREG_OFFSET_CAPTURE); + $this->positions = $matches; + + while ($this->cursor < $this->end) { + // dispatch to the lexing functions depending + // on the current state + switch ($this->state) { + case self::STATE_DATA: + $this->lexData(); + break; + + case self::STATE_BLOCK: + $this->lexBlock(); + break; + + case self::STATE_VAR: + $this->lexVar(); + break; + + case self::STATE_STRING: + $this->lexString(); + break; + + case self::STATE_INTERPOLATION: + $this->lexInterpolation(); + break; + } + } + + $this->pushToken(/* Token::EOF_TYPE */ -1); + + if (!empty($this->brackets)) { + list($expect, $lineno) = array_pop($this->brackets); + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + return new TokenStream($this->tokens, $this->source); + } + + private function lexData(): void + { + // if no matches are left we return the rest of the template as simple text token + if ($this->position == \count($this->positions[0]) - 1) { + $this->pushToken(/* Token::TEXT_TYPE */ 0, substr($this->code, $this->cursor)); + $this->cursor = $this->end; + + return; + } + + // Find the first token after the current cursor + $position = $this->positions[0][++$this->position]; + while ($position[1] < $this->cursor) { + if ($this->position == \count($this->positions[0]) - 1) { + return; + } + $position = $this->positions[0][++$this->position]; + } + + // push the template text first + $text = $textContent = substr($this->code, $this->cursor, $position[1] - $this->cursor); + + // trim? + if (isset($this->positions[2][$this->position][0])) { + if ($this->options['whitespace_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } elseif ($this->options['whitespace_line_trim'] === $this->positions[2][$this->position][0]) { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + $this->moveCursor($textContent.$position[0]); + + switch ($this->positions[1][$this->position][0]) { + case $this->options['tag_comment'][0]: + $this->lexComment(); + break; + + case $this->options['tag_block'][0]: + // raw data? + if (preg_match($this->regexes['lex_block_raw'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lexRawData(); + // {% line \d+ %} + } elseif (preg_match($this->regexes['lex_block_line'], $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + $this->lineno = (int) $match[1]; + } else { + $this->pushToken(/* Token::BLOCK_START_TYPE */ 1); + $this->pushState(self::STATE_BLOCK); + $this->currentVarBlockLine = $this->lineno; + } + break; + + case $this->options['tag_variable'][0]: + $this->pushToken(/* Token::VAR_START_TYPE */ 2); + $this->pushState(self::STATE_VAR); + $this->currentVarBlockLine = $this->lineno; + break; + } + } + + private function lexBlock(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::BLOCK_END_TYPE */ 3); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexVar(): void + { + if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::VAR_END_TYPE */ 4); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function lexExpression(): void + { + // whitespace + if (preg_match('/\s+/A', $this->code, $match, 0, $this->cursor)) { + $this->moveCursor($match[0]); + + if ($this->cursor >= $this->end) { + throw new SyntaxError(sprintf('Unclosed "%s".', self::STATE_BLOCK === $this->state ? 'block' : 'variable'), $this->currentVarBlockLine, $this->source); + } + } + + // arrow function + if ('=' === $this->code[$this->cursor] && '>' === $this->code[$this->cursor + 1]) { + $this->pushToken(Token::ARROW_TYPE, '=>'); + $this->moveCursor('=>'); + } + // operators + elseif (preg_match($this->regexes['operator'], $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::OPERATOR_TYPE */ 8, preg_replace('/\s+/', ' ', $match[0])); + $this->moveCursor($match[0]); + } + // names + elseif (preg_match(self::REGEX_NAME, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::NAME_TYPE */ 5, $match[0]); + $this->moveCursor($match[0]); + } + // numbers + elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, 0, $this->cursor)) { + $number = (float) $match[0]; // floats + if (ctype_digit($match[0]) && $number <= \PHP_INT_MAX) { + $number = (int) $match[0]; // integers lower than the maximum + } + $this->pushToken(/* Token::NUMBER_TYPE */ 6, $number); + $this->moveCursor($match[0]); + } + // punctuation + elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) { + // opening bracket + if (false !== strpos('([{', $this->code[$this->cursor])) { + $this->brackets[] = [$this->code[$this->cursor], $this->lineno]; + } + // closing bracket + elseif (false !== strpos(')]}', $this->code[$this->cursor])) { + if (empty($this->brackets)) { + throw new SyntaxError(sprintf('Unexpected "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + + list($expect, $lineno) = array_pop($this->brackets); + if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + } + + $this->pushToken(/* Token::PUNCTUATION_TYPE */ 9, $this->code[$this->cursor]); + ++$this->cursor; + } + // strings + elseif (preg_match(self::REGEX_STRING, $this->code, $match, 0, $this->cursor)) { + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes(substr($match[0], 1, -1))); + $this->moveCursor($match[0]); + } + // opening double quoted string + elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = ['"', $this->lineno]; + $this->pushState(self::STATE_STRING); + $this->moveCursor($match[0]); + } + // unlexable + else { + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function lexRawData(): void + { + if (!preg_match($this->regexes['lex_raw_data'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unexpected end of file: Unclosed "verbatim" block.', $this->lineno, $this->source); + } + + $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor); + $this->moveCursor($text.$match[0][0]); + + // trim? + if (isset($match[1][0])) { + if ($this->options['whitespace_trim'] === $match[1][0]) { + // whitespace_trim detected ({%-, {{- or {#-) + $text = rtrim($text); + } else { + // whitespace_line_trim detected ({%~, {{~ or {#~) + // don't trim \r and \n + $text = rtrim($text, " \t\0\x0B"); + } + } + + $this->pushToken(/* Token::TEXT_TYPE */ 0, $text); + } + + private function lexComment(): void + { + if (!preg_match($this->regexes['lex_comment'], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) { + throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source); + } + + $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]); + } + + private function lexString(): void + { + if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) { + $this->brackets[] = [$this->options['interpolation'][0], $this->lineno]; + $this->pushToken(/* Token::INTERPOLATION_START_TYPE */ 10); + $this->moveCursor($match[0]); + $this->pushState(self::STATE_INTERPOLATION); + } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, 0, $this->cursor) && \strlen($match[0]) > 0) { + $this->pushToken(/* Token::STRING_TYPE */ 7, stripcslashes($match[0])); + $this->moveCursor($match[0]); + } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, 0, $this->cursor)) { + list($expect, $lineno) = array_pop($this->brackets); + if ('"' != $this->code[$this->cursor]) { + throw new SyntaxError(sprintf('Unclosed "%s".', $expect), $lineno, $this->source); + } + + $this->popState(); + ++$this->cursor; + } else { + // unlexable + throw new SyntaxError(sprintf('Unexpected character "%s".', $this->code[$this->cursor]), $this->lineno, $this->source); + } + } + + private function lexInterpolation(): void + { + $bracket = end($this->brackets); + if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, 0, $this->cursor)) { + array_pop($this->brackets); + $this->pushToken(/* Token::INTERPOLATION_END_TYPE */ 11); + $this->moveCursor($match[0]); + $this->popState(); + } else { + $this->lexExpression(); + } + } + + private function pushToken($type, $value = ''): void + { + // do not push empty text tokens + if (/* Token::TEXT_TYPE */ 0 === $type && '' === $value) { + return; + } + + $this->tokens[] = new Token($type, $value, $this->lineno); + } + + private function moveCursor($text): void + { + $this->cursor += \strlen($text); + $this->lineno += substr_count($text, "\n"); + } + + private function getOperatorRegex(): string + { + $operators = array_merge( + ['='], + array_keys($this->env->getUnaryOperators()), + array_keys($this->env->getBinaryOperators()) + ); + + $operators = array_combine($operators, array_map('strlen', $operators)); + arsort($operators); + + $regex = []; + foreach ($operators as $operator => $length) { + // an operator that ends with a character must be followed by + // a whitespace, a parenthesis, an opening map [ or sequence { + $r = preg_quote($operator, '/'); + if (ctype_alpha($operator[$length - 1])) { + $r .= '(?=[\s()\[{])'; + } + + // an operator that begins with a character must not have a dot or pipe before + if (ctype_alpha($operator[0])) { + $r = '(?<![\.\|])'.$r; + } + + // an operator with a space can be any amount of whitespaces + $r = preg_replace('/\s+/', '\s+', $r); + + $regex[] = $r; + } + + return '/'.implode('|', $regex).'/A'; + } + + private function pushState($state): void + { + $this->states[] = $this->state; + $this->state = $state; + } + + private function popState(): void + { + if (0 === \count($this->states)) { + throw new \LogicException('Cannot pop state without a previous state.'); + } + + $this->state = array_pop($this->states); + } +} diff --git a/vendor/twig/twig/src/Loader/ArrayLoader.php b/vendor/twig/twig/src/Loader/ArrayLoader.php new file mode 100644 index 0000000..5d726c3 --- /dev/null +++ b/vendor/twig/twig/src/Loader/ArrayLoader.php @@ -0,0 +1,77 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Loader; + +use Twig\Error\LoaderError; +use Twig\Source; + +/** + * Loads a template from an array. + * + * When using this loader with a cache mechanism, you should know that a new cache + * key is generated each time a template content "changes" (the cache key being the + * source code of the template). If you don't want to see your cache grows out of + * control, you need to take care of clearing the old cache file by yourself. + * + * This loader should only be used for unit testing. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class ArrayLoader implements LoaderInterface +{ + private $templates = []; + + /** + * @param array $templates An array of templates (keys are the names, and values are the source code) + */ + public function __construct(array $templates = []) + { + $this->templates = $templates; + } + + public function setTemplate(string $name, string $template): void + { + $this->templates[$name] = $template; + } + + public function getSourceContext(string $name): Source + { + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return new Source($this->templates[$name], $name); + } + + public function exists(string $name): bool + { + return isset($this->templates[$name]); + } + + public function getCacheKey(string $name): string + { + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return $name.':'.$this->templates[$name]; + } + + public function isFresh(string $name, int $time): bool + { + if (!isset($this->templates[$name])) { + throw new LoaderError(sprintf('Template "%s" is not defined.', $name)); + } + + return true; + } +} diff --git a/vendor/twig/twig/src/Loader/ChainLoader.php b/vendor/twig/twig/src/Loader/ChainLoader.php new file mode 100644 index 0000000..fbf4f3a --- /dev/null +++ b/vendor/twig/twig/src/Loader/ChainLoader.php @@ -0,0 +1,119 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Loader; + +use Twig\Error\LoaderError; +use Twig\Source; + +/** + * Loads templates from other loaders. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class ChainLoader implements LoaderInterface +{ + private $hasSourceCache = []; + private $loaders = []; + + /** + * @param LoaderInterface[] $loaders + */ + public function __construct(array $loaders = []) + { + foreach ($loaders as $loader) { + $this->addLoader($loader); + } + } + + public function addLoader(LoaderInterface $loader): void + { + $this->loaders[] = $loader; + $this->hasSourceCache = []; + } + + /** + * @return LoaderInterface[] + */ + public function getLoaders(): array + { + return $this->loaders; + } + + public function getSourceContext(string $name): Source + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getSourceContext($name); + } catch (LoaderError $e) { + $exceptions[] = $e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function exists(string $name): bool + { + if (isset($this->hasSourceCache[$name])) { + return $this->hasSourceCache[$name]; + } + + foreach ($this->loaders as $loader) { + if ($loader->exists($name)) { + return $this->hasSourceCache[$name] = true; + } + } + + return $this->hasSourceCache[$name] = false; + } + + public function getCacheKey(string $name): string + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->getCacheKey($name); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } + + public function isFresh(string $name, int $time): bool + { + $exceptions = []; + foreach ($this->loaders as $loader) { + if (!$loader->exists($name)) { + continue; + } + + try { + return $loader->isFresh($name, $time); + } catch (LoaderError $e) { + $exceptions[] = \get_class($loader).': '.$e->getMessage(); + } + } + + throw new LoaderError(sprintf('Template "%s" is not defined%s.', $name, $exceptions ? ' ('.implode(', ', $exceptions).')' : '')); + } +} diff --git a/vendor/twig/twig/src/Loader/FilesystemLoader.php b/vendor/twig/twig/src/Loader/FilesystemLoader.php new file mode 100644 index 0000000..62267a1 --- /dev/null +++ b/vendor/twig/twig/src/Loader/FilesystemLoader.php @@ -0,0 +1,283 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Loader; + +use Twig\Error\LoaderError; +use Twig\Source; + +/** + * Loads template from the filesystem. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FilesystemLoader implements LoaderInterface +{ + /** Identifier of the main namespace. */ + public const MAIN_NAMESPACE = '__main__'; + + protected $paths = []; + protected $cache = []; + protected $errorCache = []; + + private $rootPath; + + /** + * @param string|array $paths A path or an array of paths where to look for templates + * @param string|null $rootPath The root path common to all relative paths (null for getcwd()) + */ + public function __construct($paths = [], string $rootPath = null) + { + $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR; + if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) { + $this->rootPath = $realPath.\DIRECTORY_SEPARATOR; + } + + if ($paths) { + $this->setPaths($paths); + } + } + + /** + * Returns the paths to the templates. + */ + public function getPaths(string $namespace = self::MAIN_NAMESPACE): array + { + return $this->paths[$namespace] ?? []; + } + + /** + * Returns the path namespaces. + * + * The main namespace is always defined. + */ + public function getNamespaces(): array + { + return array_keys($this->paths); + } + + /** + * @param string|array $paths A path or an array of paths where to look for templates + */ + public function setPaths($paths, string $namespace = self::MAIN_NAMESPACE): void + { + if (!\is_array($paths)) { + $paths = [$paths]; + } + + $this->paths[$namespace] = []; + foreach ($paths as $path) { + $this->addPath($path, $namespace); + } + } + + /** + * @throws LoaderError + */ + public function addPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $this->paths[$namespace][] = rtrim($path, '/\\'); + } + + /** + * @throws LoaderError + */ + public function prependPath(string $path, string $namespace = self::MAIN_NAMESPACE): void + { + // invalidate the cache + $this->cache = $this->errorCache = []; + + $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path; + if (!is_dir($checkPath)) { + throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath)); + } + + $path = rtrim($path, '/\\'); + + if (!isset($this->paths[$namespace])) { + $this->paths[$namespace][] = $path; + } else { + array_unshift($this->paths[$namespace], $path); + } + } + + public function getSourceContext(string $name): Source + { + if (null === $path = $this->findTemplate($name)) { + return new Source('', $name, ''); + } + + return new Source(file_get_contents($path), $name, $path); + } + + public function getCacheKey(string $name): string + { + if (null === $path = $this->findTemplate($name)) { + return ''; + } + $len = \strlen($this->rootPath); + if (0 === strncmp($this->rootPath, $path, $len)) { + return substr($path, $len); + } + + return $path; + } + + /** + * @return bool + */ + public function exists(string $name) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return true; + } + + return null !== $this->findTemplate($name, false); + } + + public function isFresh(string $name, int $time): bool + { + // false support to be removed in 3.0 + if (null === $path = $this->findTemplate($name)) { + return false; + } + + return filemtime($path) < $time; + } + + /** + * @return string|null + */ + protected function findTemplate(string $name, bool $throw = true) + { + $name = $this->normalizeName($name); + + if (isset($this->cache[$name])) { + return $this->cache[$name]; + } + + if (isset($this->errorCache[$name])) { + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + try { + list($namespace, $shortname) = $this->parseName($name); + + $this->validateName($shortname); + } catch (LoaderError $e) { + if (!$throw) { + return null; + } + + throw $e; + } + + if (!isset($this->paths[$namespace])) { + $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + foreach ($this->paths[$namespace] as $path) { + if (!$this->isAbsolutePath($path)) { + $path = $this->rootPath.$path; + } + + if (is_file($path.'/'.$shortname)) { + if (false !== $realpath = realpath($path.'/'.$shortname)) { + return $this->cache[$name] = $realpath; + } + + return $this->cache[$name] = $path.'/'.$shortname; + } + } + + $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])); + + if (!$throw) { + return null; + } + + throw new LoaderError($this->errorCache[$name]); + } + + private function normalizeName(string $name): string + { + return preg_replace('#/{2,}#', '/', str_replace('\\', '/', $name)); + } + + private function parseName(string $name, string $default = self::MAIN_NAMESPACE): array + { + if (isset($name[0]) && '@' == $name[0]) { + if (false === $pos = strpos($name, '/')) { + throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name)); + } + + $namespace = substr($name, 1, $pos - 1); + $shortname = substr($name, $pos + 1); + + return [$namespace, $shortname]; + } + + return [$default, $name]; + } + + private function validateName(string $name): void + { + if (false !== strpos($name, "\0")) { + throw new LoaderError('A template name cannot contain NUL bytes.'); + } + + $name = ltrim($name, '/'); + $parts = explode('/', $name); + $level = 0; + foreach ($parts as $part) { + if ('..' === $part) { + --$level; + } elseif ('.' !== $part) { + ++$level; + } + + if ($level < 0) { + throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name)); + } + } + } + + private function isAbsolutePath(string $file): bool + { + return strspn($file, '/\\', 0, 1) + || (\strlen($file) > 3 && ctype_alpha($file[0]) + && ':' === $file[1] + && strspn($file, '/\\', 2, 1) + ) + || null !== parse_url($file, \PHP_URL_SCHEME) + ; + } +} diff --git a/vendor/twig/twig/src/Loader/LoaderInterface.php b/vendor/twig/twig/src/Loader/LoaderInterface.php new file mode 100644 index 0000000..fec7e85 --- /dev/null +++ b/vendor/twig/twig/src/Loader/LoaderInterface.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Loader; + +use Twig\Error\LoaderError; +use Twig\Source; + +/** + * Interface all loaders must implement. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface LoaderInterface +{ + /** + * Returns the source context for a given template logical name. + * + * @throws LoaderError When $name is not found + */ + public function getSourceContext(string $name): Source; + + /** + * Gets the cache key to use for the cache for a given template name. + * + * @throws LoaderError When $name is not found + */ + public function getCacheKey(string $name): string; + + /** + * @param int $time Timestamp of the last modification time of the cached template + * + * @throws LoaderError When $name is not found + */ + public function isFresh(string $name, int $time): bool; + + /** + * @return bool + */ + public function exists(string $name); +} diff --git a/vendor/twig/twig/src/Markup.php b/vendor/twig/twig/src/Markup.php new file mode 100644 index 0000000..1788acc --- /dev/null +++ b/vendor/twig/twig/src/Markup.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +/** + * Marks a content as safe. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Markup implements \Countable, \JsonSerializable +{ + private $content; + private $charset; + + public function __construct($content, $charset) + { + $this->content = (string) $content; + $this->charset = $charset; + } + + public function __toString() + { + return $this->content; + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return mb_strlen($this->content, $this->charset); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->content; + } +} diff --git a/vendor/twig/twig/src/Node/AutoEscapeNode.php b/vendor/twig/twig/src/Node/AutoEscapeNode.php new file mode 100644 index 0000000..cd97041 --- /dev/null +++ b/vendor/twig/twig/src/Node/AutoEscapeNode.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents an autoescape node. + * + * The value is the escaping strategy (can be html, js, ...) + * + * The true value is equivalent to html. + * + * If autoescaping is disabled, then the value is false. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class AutoEscapeNode extends Node +{ + public function __construct($value, Node $body, int $lineno, string $tag = 'autoescape') + { + parent::__construct(['body' => $body], ['value' => $value], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('body')); + } +} diff --git a/vendor/twig/twig/src/Node/BlockNode.php b/vendor/twig/twig/src/Node/BlockNode.php new file mode 100644 index 0000000..0632ba7 --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockNode.php @@ -0,0 +1,44 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a block node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class BlockNode extends Node +{ + public function __construct(string $name, Node $body, int $lineno, string $tag = null) + { + parent::__construct(['body' => $body], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("public function block_%s(\$context, array \$blocks = [])\n", $this->getAttribute('name')), "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ; + + $compiler + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/BlockReferenceNode.php b/vendor/twig/twig/src/Node/BlockReferenceNode.php new file mode 100644 index 0000000..cc8af5b --- /dev/null +++ b/vendor/twig/twig/src/Node/BlockReferenceNode.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a block call node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class BlockReferenceNode extends Node implements NodeOutputInterface +{ + public function __construct(string $name, int $lineno, string $tag = null) + { + parent::__construct([], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name'))) + ; + } +} diff --git a/vendor/twig/twig/src/Node/BodyNode.php b/vendor/twig/twig/src/Node/BodyNode.php new file mode 100644 index 0000000..041cbf6 --- /dev/null +++ b/vendor/twig/twig/src/Node/BodyNode.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +/** + * Represents a body node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class BodyNode extends Node +{ +} diff --git a/vendor/twig/twig/src/Node/CheckSecurityCallNode.php b/vendor/twig/twig/src/Node/CheckSecurityCallNode.php new file mode 100644 index 0000000..a78a38d --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckSecurityCallNode.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class CheckSecurityCallNode extends Node +{ + public function compile(Compiler $compiler) + { + $compiler + ->write("\$this->sandbox = \$this->env->getExtension('\Twig\Extension\SandboxExtension');\n") + ->write("\$this->checkSecurity();\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/CheckSecurityNode.php b/vendor/twig/twig/src/Node/CheckSecurityNode.php new file mode 100644 index 0000000..4727327 --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckSecurityNode.php @@ -0,0 +1,88 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class CheckSecurityNode extends Node +{ + private $usedFilters; + private $usedTags; + private $usedFunctions; + + public function __construct(array $usedFilters, array $usedTags, array $usedFunctions) + { + $this->usedFilters = $usedFilters; + $this->usedTags = $usedTags; + $this->usedFunctions = $usedFunctions; + + parent::__construct(); + } + + public function compile(Compiler $compiler): void + { + $tags = $filters = $functions = []; + foreach (['tags', 'filters', 'functions'] as $type) { + foreach ($this->{'used'.ucfirst($type)} as $name => $node) { + if ($node instanceof Node) { + ${$type}[$name] = $node->getTemplateLine(); + } else { + ${$type}[$node] = null; + } + } + } + + $compiler + ->write("\n") + ->write("public function checkSecurity()\n") + ->write("{\n") + ->indent() + ->write('static $tags = ')->repr(array_filter($tags))->raw(";\n") + ->write('static $filters = ')->repr(array_filter($filters))->raw(";\n") + ->write('static $functions = ')->repr(array_filter($functions))->raw(";\n\n") + ->write("try {\n") + ->indent() + ->write("\$this->sandbox->checkSecurity(\n") + ->indent() + ->write(!$tags ? "[],\n" : "['".implode("', '", array_keys($tags))."'],\n") + ->write(!$filters ? "[],\n" : "['".implode("', '", array_keys($filters))."'],\n") + ->write(!$functions ? "[]\n" : "['".implode("', '", array_keys($functions))."']\n") + ->outdent() + ->write(");\n") + ->outdent() + ->write("} catch (SecurityError \$e) {\n") + ->indent() + ->write("\$e->setSourceContext(\$this->source);\n\n") + ->write("if (\$e instanceof SecurityNotAllowedTagError && isset(\$tags[\$e->getTagName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$tags[\$e->getTagName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFilterError && isset(\$filters[\$e->getFilterName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$filters[\$e->getFilterName()]);\n") + ->outdent() + ->write("} elseif (\$e instanceof SecurityNotAllowedFunctionError && isset(\$functions[\$e->getFunctionName()])) {\n") + ->indent() + ->write("\$e->setTemplateLine(\$functions[\$e->getFunctionName()]);\n") + ->outdent() + ->write("}\n\n") + ->write("throw \$e;\n") + ->outdent() + ->write("}\n\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/CheckToStringNode.php b/vendor/twig/twig/src/Node/CheckToStringNode.php new file mode 100644 index 0000000..c7a9d69 --- /dev/null +++ b/vendor/twig/twig/src/Node/CheckToStringNode.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; + +/** + * Checks if casting an expression to __toString() is allowed by the sandbox. + * + * For instance, when there is a simple Print statement, like {{ article }}, + * and if the sandbox is enabled, we need to check that the __toString() + * method is allowed if 'article' is an object. The same goes for {{ article|upper }} + * or {{ random(article) }} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class CheckToStringNode extends AbstractExpression +{ + public function __construct(AbstractExpression $expr) + { + parent::__construct(['expr' => $expr], [], $expr->getTemplateLine(), $expr->getNodeTag()); + } + + public function compile(Compiler $compiler): void + { + $expr = $this->getNode('expr'); + $compiler + ->raw('$this->sandbox->ensureToStringAllowed(') + ->subcompile($expr) + ->raw(', ') + ->repr($expr->getTemplateLine()) + ->raw(', $this->source)') + ; + } +} diff --git a/vendor/twig/twig/src/Node/DeprecatedNode.php b/vendor/twig/twig/src/Node/DeprecatedNode.php new file mode 100644 index 0000000..5ff4430 --- /dev/null +++ b/vendor/twig/twig/src/Node/DeprecatedNode.php @@ -0,0 +1,53 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ConstantExpression; + +/** + * Represents a deprecated node. + * + * @author Yonel Ceruto <yonelceruto@gmail.com> + */ +class DeprecatedNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $expr = $this->getNode('expr'); + + if ($expr instanceof ConstantExpression) { + $compiler->write('@trigger_error(') + ->subcompile($expr); + } else { + $varName = $compiler->getVarName(); + $compiler->write(sprintf('$%s = ', $varName)) + ->subcompile($expr) + ->raw(";\n") + ->write(sprintf('@trigger_error($%s', $varName)); + } + + $compiler + ->raw('.') + ->string(sprintf(' ("%s" at line %d).', $this->getTemplateName(), $this->getTemplateLine())) + ->raw(", E_USER_DEPRECATED);\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/DoNode.php b/vendor/twig/twig/src/Node/DoNode.php new file mode 100644 index 0000000..f7783d1 --- /dev/null +++ b/vendor/twig/twig/src/Node/DoNode.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; + +/** + * Represents a do node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DoNode extends Node +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/EmbedNode.php b/vendor/twig/twig/src/Node/EmbedNode.php new file mode 100644 index 0000000..903c3f6 --- /dev/null +++ b/vendor/twig/twig/src/Node/EmbedNode.php @@ -0,0 +1,48 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ConstantExpression; + +/** + * Represents an embed node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class EmbedNode extends IncludeNode +{ + // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module) + public function __construct(string $name, int $index, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + { + parent::__construct(new ConstantExpression('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag); + + $this->setAttribute('name', $name); + $this->setAttribute('index', $index); + } + + protected function addGetTemplate(Compiler $compiler): void + { + $compiler + ->write('$this->loadTemplate(') + ->string($this->getAttribute('name')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(', ') + ->string($this->getAttribute('index')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/AbstractExpression.php b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php new file mode 100644 index 0000000..42da055 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AbstractExpression.php @@ -0,0 +1,24 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Node\Node; + +/** + * Abstract class for all nodes that represents an expression. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class AbstractExpression extends Node +{ +} diff --git a/vendor/twig/twig/src/Node/Expression/ArrayExpression.php b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php new file mode 100644 index 0000000..0e25fe4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ArrayExpression.php @@ -0,0 +1,85 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class ArrayExpression extends AbstractExpression +{ + private $index; + + public function __construct(array $elements, int $lineno) + { + parent::__construct($elements, [], $lineno); + + $this->index = -1; + foreach ($this->getKeyValuePairs() as $pair) { + if ($pair['key'] instanceof ConstantExpression && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) { + $this->index = $pair['key']->getAttribute('value'); + } + } + } + + public function getKeyValuePairs(): array + { + $pairs = []; + foreach (array_chunk($this->nodes, 2) as $pair) { + $pairs[] = [ + 'key' => $pair[0], + 'value' => $pair[1], + ]; + } + + return $pairs; + } + + public function hasElement(AbstractExpression $key): bool + { + foreach ($this->getKeyValuePairs() as $pair) { + // we compare the string representation of the keys + // to avoid comparing the line numbers which are not relevant here. + if ((string) $key === (string) $pair['key']) { + return true; + } + } + + return false; + } + + public function addElement(AbstractExpression $value, AbstractExpression $key = null): void + { + if (null === $key) { + $key = new ConstantExpression(++$this->index, $value->getTemplateLine()); + } + + array_push($this->nodes, $key, $value); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw('['); + $first = true; + foreach ($this->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler + ->subcompile($pair['key']) + ->raw(' => ') + ->subcompile($pair['value']) + ; + } + $compiler->raw(']'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php b/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php new file mode 100644 index 0000000..eaad03c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ArrowFunctionExpression.php @@ -0,0 +1,64 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * Represents an arrow function. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ArrowFunctionExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $expr, Node $names, $lineno, $tag = null) + { + parent::__construct(['expr' => $expr, 'names' => $names], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->raw('function (') + ; + foreach ($this->getNode('names') as $i => $name) { + if ($i) { + $compiler->raw(', '); + } + + $compiler + ->raw('$__') + ->raw($name->getAttribute('name')) + ->raw('__') + ; + } + $compiler + ->raw(') use ($context, $macros) { ') + ; + foreach ($this->getNode('names') as $name) { + $compiler + ->raw('$context["') + ->raw($name->getAttribute('name')) + ->raw('"] = $__') + ->raw($name->getAttribute('name')) + ->raw('__; ') + ; + } + $compiler + ->raw('return ') + ->subcompile($this->getNode('expr')) + ->raw('; }') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php new file mode 100644 index 0000000..7dd1bc4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/AssignNameExpression.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class AssignNameExpression extends NameExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('$context[') + ->string($this->getAttribute('name')) + ->raw(']') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php new file mode 100644 index 0000000..c424e5c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AbstractBinary.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; + +abstract class AbstractBinary extends AbstractExpression +{ + public function __construct(Node $left, Node $right, int $lineno) + { + parent::__construct(['left' => $left, 'right' => $right], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('left')) + ->raw(' ') + ; + $this->operator($compiler); + $compiler + ->raw(' ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php new file mode 100644 index 0000000..ee4307e --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AddBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class AddBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('+'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php new file mode 100644 index 0000000..5f2380d --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/AndBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class AndBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('&&'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php new file mode 100644 index 0000000..db7d6d6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseAndBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class BitwiseAndBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('&'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php new file mode 100644 index 0000000..ce803dd --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseOrBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class BitwiseOrBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('|'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php new file mode 100644 index 0000000..5c29785 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/BitwiseXorBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class BitwiseXorBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('^'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php new file mode 100644 index 0000000..f825ab8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ConcatBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class ConcatBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('.'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php new file mode 100644 index 0000000..e3817d1 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/DivBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class DivBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('/'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php new file mode 100644 index 0000000..c3516b8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EndsWithBinary.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class EndsWithBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $left = $compiler->getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || $%2$s === substr($%1$s, -strlen($%2$s))))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php new file mode 100644 index 0000000..6b48549 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/EqualBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class EqualBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('=='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php new file mode 100644 index 0000000..d7e7980 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/FloorDivBinary.php @@ -0,0 +1,29 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class FloorDivBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler->raw('(int) floor('); + parent::compile($compiler); + $compiler->raw(')'); + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('/'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php new file mode 100644 index 0000000..e1dd067 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class GreaterBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php new file mode 100644 index 0000000..df9bfcf --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/GreaterEqualBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class GreaterEqualBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 <= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('>='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php new file mode 100644 index 0000000..adfabd4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/HasEveryBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class HasEveryBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('twig_array_every($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php new file mode 100644 index 0000000..270da36 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/HasSomeBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class HasSomeBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('twig_array_some($this->env, ') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php new file mode 100644 index 0000000..6dbfa97 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/InBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class InBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('in'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php new file mode 100644 index 0000000..598e629 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class LessBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(-1 === twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php new file mode 100644 index 0000000..e3c4af5 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/LessEqualBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class LessEqualBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 >= twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php new file mode 100644 index 0000000..a8bce6f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MatchesBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class MatchesBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('twig_matches(') + ->subcompile($this->getNode('right')) + ->raw(', ') + ->subcompile($this->getNode('left')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php new file mode 100644 index 0000000..271b45c --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/ModBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class ModBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('%'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php new file mode 100644 index 0000000..6d4c1e0 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/MulBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class MulBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('*'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php new file mode 100644 index 0000000..db47a28 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotEqualBinary.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class NotEqualBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + if (\PHP_VERSION_ID >= 80000) { + parent::compile($compiler); + + return; + } + + $compiler + ->raw('(0 !== twig_compare(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw('))') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('!='); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php new file mode 100644 index 0000000..fcba6cc --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/NotInBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class NotInBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('!twig_in_filter(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('not in'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php new file mode 100644 index 0000000..21f87c9 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/OrBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class OrBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('||'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php new file mode 100644 index 0000000..c9f4c66 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/PowerBinary.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class PowerBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('**'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php new file mode 100644 index 0000000..55982c8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/RangeBinary.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class RangeBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('range(') + ->subcompile($this->getNode('left')) + ->raw(', ') + ->subcompile($this->getNode('right')) + ->raw(')') + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('..'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php new file mode 100644 index 0000000..ae5a4a4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/SpaceshipBinary.php @@ -0,0 +1,22 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class SpaceshipBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('<=>'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php new file mode 100644 index 0000000..d0df1c4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/StartsWithBinary.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class StartsWithBinary extends AbstractBinary +{ + public function compile(Compiler $compiler): void + { + $left = $compiler->getVarName(); + $right = $compiler->getVarName(); + $compiler + ->raw(sprintf('(is_string($%s = ', $left)) + ->subcompile($this->getNode('left')) + ->raw(sprintf(') && is_string($%s = ', $right)) + ->subcompile($this->getNode('right')) + ->raw(sprintf(') && (\'\' === $%2$s || 0 === strpos($%1$s, $%2$s)))', $left, $right)) + ; + } + + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw(''); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php new file mode 100644 index 0000000..eeb87fa --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Binary/SubBinary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Binary; + +use Twig\Compiler; + +class SubBinary extends AbstractBinary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('-'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php new file mode 100644 index 0000000..b1e2a8f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/BlockReferenceExpression.php @@ -0,0 +1,86 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * Represents a block call node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class BlockReferenceExpression extends AbstractExpression +{ + public function __construct(Node $name, ?Node $template, int $lineno, string $tag = null) + { + $nodes = ['name' => $name]; + if (null !== $template) { + $nodes['template'] = $template; + } + + parent::__construct($nodes, ['is_defined_test' => false, 'output' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $this->compileTemplateCall($compiler, 'hasBlock'); + } else { + if ($this->getAttribute('output')) { + $compiler->addDebugInfo($this); + + $this + ->compileTemplateCall($compiler, 'displayBlock') + ->raw(";\n"); + } else { + $this->compileTemplateCall($compiler, 'renderBlock'); + } + } + } + + private function compileTemplateCall(Compiler $compiler, string $method): Compiler + { + if (!$this->hasNode('template')) { + $compiler->write('$this'); + } else { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('template')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + $compiler->raw(sprintf('->%s', $method)); + + return $this->compileBlockArguments($compiler); + } + + private function compileBlockArguments(Compiler $compiler): Compiler + { + $compiler + ->raw('(') + ->subcompile($this->getNode('name')) + ->raw(', $context'); + + if (!$this->hasNode('template')) { + $compiler->raw(', $blocks'); + } + + return $compiler->raw(')'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/CallExpression.php b/vendor/twig/twig/src/Node/Expression/CallExpression.php new file mode 100644 index 0000000..11a6b1a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/CallExpression.php @@ -0,0 +1,321 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Extension\ExtensionInterface; +use Twig\Node\Node; + +abstract class CallExpression extends AbstractExpression +{ + private $reflector; + + protected function compileCallable(Compiler $compiler) + { + $callable = $this->getAttribute('callable'); + + if (\is_string($callable) && false === strpos($callable, '::')) { + $compiler->raw($callable); + } else { + [$r, $callable] = $this->reflectCallable($callable); + + if (\is_string($callable)) { + $compiler->raw($callable); + } elseif (\is_array($callable) && \is_string($callable[0])) { + if (!$r instanceof \ReflectionMethod || $r->isStatic()) { + $compiler->raw(sprintf('%s::%s', $callable[0], $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->getRuntime(\'%s\')->%s', $callable[0], $callable[1])); + } + } elseif (\is_array($callable) && $callable[0] instanceof ExtensionInterface) { + $class = \get_class($callable[0]); + if (!$compiler->getEnvironment()->hasExtension($class)) { + // Compile a non-optimized call to trigger a \Twig\Error\RuntimeError, which cannot be a compile-time error + $compiler->raw(sprintf('$this->env->getExtension(\'%s\')', $class)); + } else { + $compiler->raw(sprintf('$this->extensions[\'%s\']', ltrim($class, '\\'))); + } + + $compiler->raw(sprintf('->%s', $callable[1])); + } else { + $compiler->raw(sprintf('$this->env->get%s(\'%s\')->getCallable()', ucfirst($this->getAttribute('type')), $this->getAttribute('name'))); + } + } + + $this->compileArguments($compiler); + } + + protected function compileArguments(Compiler $compiler, $isArray = false): void + { + $compiler->raw($isArray ? '[' : '('); + + $first = true; + + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + $compiler->raw('$this->env'); + $first = false; + } + + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->raw('$context'); + $first = false; + } + + if ($this->hasAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->string($argument); + $first = false; + } + } + + if ($this->hasNode('node')) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($this->getNode('node')); + $first = false; + } + + if ($this->hasNode('arguments')) { + $callable = $this->getAttribute('callable'); + $arguments = $this->getArguments($callable, $this->getNode('arguments')); + foreach ($arguments as $node) { + if (!$first) { + $compiler->raw(', '); + } + $compiler->subcompile($node); + $first = false; + } + } + + $compiler->raw($isArray ? ']' : ')'); + } + + protected function getArguments($callable, $arguments) + { + $callType = $this->getAttribute('type'); + $callName = $this->getAttribute('name'); + + $parameters = []; + $named = false; + foreach ($arguments as $name => $node) { + if (!\is_int($name)) { + $named = true; + $name = $this->normalizeName($name); + } elseif ($named) { + throw new SyntaxError(sprintf('Positional arguments cannot be used after named arguments for %s "%s".', $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + $parameters[$name] = $node; + } + + $isVariadic = $this->hasAttribute('is_variadic') && $this->getAttribute('is_variadic'); + if (!$named && !$isVariadic) { + return $parameters; + } + + if (!$callable) { + if ($named) { + $message = sprintf('Named arguments are not supported for %s "%s".', $callType, $callName); + } else { + $message = sprintf('Arbitrary positional arguments are not supported for %s "%s".', $callType, $callName); + } + + throw new \LogicException($message); + } + + list($callableParameters, $isPhpVariadic) = $this->getCallableParameters($callable, $isVariadic); + $arguments = []; + $names = []; + $missingArguments = []; + $optionalArguments = []; + $pos = 0; + foreach ($callableParameters as $callableParameter) { + $name = $this->normalizeName($callableParameter->name); + if (\PHP_VERSION_ID >= 80000 && 'range' === $callable) { + if ('start' === $name) { + $name = 'low'; + } elseif ('end' === $name) { + $name = 'high'; + } + } + + $names[] = $name; + + if (\array_key_exists($name, $parameters)) { + if (\array_key_exists($pos, $parameters)) { + throw new SyntaxError(sprintf('Argument "%s" is defined twice for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + + if (\count($missingArguments)) { + throw new SyntaxError(sprintf( + 'Argument "%s" could not be assigned for %s "%s(%s)" because it is mapped to an internal PHP function which cannot determine default value for optional argument%s "%s".', + $name, $callType, $callName, implode(', ', $names), \count($missingArguments) > 1 ? 's' : '', implode('", "', $missingArguments) + ), $this->getTemplateLine(), $this->getSourceContext()); + } + + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$name]; + unset($parameters[$name]); + $optionalArguments = []; + } elseif (\array_key_exists($pos, $parameters)) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $parameters[$pos]; + unset($parameters[$pos]); + $optionalArguments = []; + ++$pos; + } elseif ($callableParameter->isDefaultValueAvailable()) { + $optionalArguments[] = new ConstantExpression($callableParameter->getDefaultValue(), -1); + } elseif ($callableParameter->isOptional()) { + if (empty($parameters)) { + break; + } else { + $missingArguments[] = $name; + } + } else { + throw new SyntaxError(sprintf('Value for argument "%s" is required for %s "%s".', $name, $callType, $callName), $this->getTemplateLine(), $this->getSourceContext()); + } + } + + if ($isVariadic) { + $arbitraryArguments = $isPhpVariadic ? new VariadicExpression([], -1) : new ArrayExpression([], -1); + foreach ($parameters as $key => $value) { + if (\is_int($key)) { + $arbitraryArguments->addElement($value); + } else { + $arbitraryArguments->addElement($value, new ConstantExpression($key, -1)); + } + unset($parameters[$key]); + } + + if ($arbitraryArguments->count()) { + $arguments = array_merge($arguments, $optionalArguments); + $arguments[] = $arbitraryArguments; + } + } + + if (!empty($parameters)) { + $unknownParameter = null; + foreach ($parameters as $parameter) { + if ($parameter instanceof Node) { + $unknownParameter = $parameter; + break; + } + } + + throw new SyntaxError( + sprintf( + 'Unknown argument%s "%s" for %s "%s(%s)".', + \count($parameters) > 1 ? 's' : '', implode('", "', array_keys($parameters)), $callType, $callName, implode(', ', $names) + ), + $unknownParameter ? $unknownParameter->getTemplateLine() : $this->getTemplateLine(), + $unknownParameter ? $unknownParameter->getSourceContext() : $this->getSourceContext() + ); + } + + return $arguments; + } + + protected function normalizeName(string $name): string + { + return strtolower(preg_replace(['/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'], ['\\1_\\2', '\\1_\\2'], $name)); + } + + private function getCallableParameters($callable, bool $isVariadic): array + { + [$r, , $callableName] = $this->reflectCallable($callable); + + $parameters = $r->getParameters(); + if ($this->hasNode('node')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) { + array_shift($parameters); + } + if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) { + array_shift($parameters); + } + if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) { + foreach ($this->getAttribute('arguments') as $argument) { + array_shift($parameters); + } + } + $isPhpVariadic = false; + if ($isVariadic) { + $argument = end($parameters); + $isArray = $argument && $argument->hasType() && 'array' === $argument->getType()->getName(); + if ($isArray && $argument->isDefaultValueAvailable() && [] === $argument->getDefaultValue()) { + array_pop($parameters); + } elseif ($argument && $argument->isVariadic()) { + array_pop($parameters); + $isPhpVariadic = true; + } else { + throw new \LogicException(sprintf('The last parameter of "%s" for %s "%s" must be an array with default value, eg. "array $arg = []".', $callableName, $this->getAttribute('type'), $this->getAttribute('name'))); + } + } + + return [$parameters, $isPhpVariadic]; + } + + private function reflectCallable($callable) + { + if (null !== $this->reflector) { + return $this->reflector; + } + + if (\is_string($callable) && false !== $pos = strpos($callable, '::')) { + $callable = [substr($callable, 0, $pos), substr($callable, 2 + $pos)]; + } + + if (\is_array($callable) && method_exists($callable[0], $callable[1])) { + $r = new \ReflectionMethod($callable[0], $callable[1]); + + return $this->reflector = [$r, $callable, $r->class.'::'.$r->name]; + } + + $checkVisibility = $callable instanceof \Closure; + try { + $closure = \Closure::fromCallable($callable); + } catch (\TypeError $e) { + throw new \LogicException(sprintf('Callback for %s "%s" is not callable in the current scope.', $this->getAttribute('type'), $this->getAttribute('name')), 0, $e); + } + $r = new \ReflectionFunction($closure); + + if (false !== strpos($r->name, '{closure}')) { + return $this->reflector = [$r, $callable, 'Closure']; + } + + if ($object = $r->getClosureThis()) { + $callable = [$object, $r->name]; + $callableName = (\function_exists('get_debug_type') ? get_debug_type($object) : \get_class($object)).'::'.$r->name; + } elseif (\PHP_VERSION_ID >= 80111 && $class = $r->getClosureCalledClass()) { + $callableName = $class->name.'::'.$r->name; + } elseif (\PHP_VERSION_ID < 80111 && $class = $r->getClosureScopeClass()) { + $callableName = (\is_array($callable) ? $callable[0] : $class->name).'::'.$r->name; + } else { + $callable = $callableName = $r->name; + } + + if ($checkVisibility && \is_array($callable) && method_exists(...$callable) && !(new \ReflectionMethod(...$callable))->isPublic()) { + $callable = $r->getClosure(); + } + + return $this->reflector = [$r, $callable, $callableName]; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php new file mode 100644 index 0000000..2c7bd0a --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConditionalExpression.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class ConditionalExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $expr1, AbstractExpression $expr2, AbstractExpression $expr3, int $lineno) + { + parent::__construct(['expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('((') + ->subcompile($this->getNode('expr1')) + ->raw(') ? (') + ->subcompile($this->getNode('expr2')) + ->raw(') : (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ConstantExpression.php b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php new file mode 100644 index 0000000..7ddbcc6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ConstantExpression.php @@ -0,0 +1,28 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class ConstantExpression extends AbstractExpression +{ + public function __construct($value, int $lineno) + { + parent::__construct([], ['value' => $value], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->repr($this->getAttribute('value')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php new file mode 100644 index 0000000..6a572d4 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Filter/DefaultFilter.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Filter; + +use Twig\Compiler; +use Twig\Node\Expression\ConditionalExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\Test\DefinedTest; +use Twig\Node\Node; + +/** + * Returns the value or the default value when it is undefined or empty. + * + * {{ var.foo|default('foo item on var is not defined') }} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DefaultFilter extends FilterExpression +{ + public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) + { + $default = new FilterExpression($node, new ConstantExpression('default', $node->getTemplateLine()), $arguments, $node->getTemplateLine()); + + if ('default' === $filterName->getAttribute('value') && ($node instanceof NameExpression || $node instanceof GetAttrExpression)) { + $test = new DefinedTest(clone $node, 'defined', new Node(), $node->getTemplateLine()); + $false = \count($arguments) ? $arguments->getNode(0) : new ConstantExpression('', $node->getTemplateLine()); + + $node = new ConditionalExpression($test, $default, $false, $node->getTemplateLine()); + } else { + $node = $default; + } + + parent::__construct($node, $filterName, $arguments, $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/FilterExpression.php b/vendor/twig/twig/src/Node/Expression/FilterExpression.php new file mode 100644 index 0000000..0fc1588 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FilterExpression.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +class FilterExpression extends CallExpression +{ + public function __construct(Node $node, ConstantExpression $filterName, Node $arguments, int $lineno, string $tag = null) + { + parent::__construct(['node' => $node, 'filter' => $filterName, 'arguments' => $arguments], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getNode('filter')->getAttribute('value'); + $filter = $compiler->getEnvironment()->getFilter($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'filter'); + $this->setAttribute('needs_environment', $filter->needsEnvironment()); + $this->setAttribute('needs_context', $filter->needsContext()); + $this->setAttribute('arguments', $filter->getArguments()); + $this->setAttribute('callable', $filter->getCallable()); + $this->setAttribute('is_variadic', $filter->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/FunctionExpression.php b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php new file mode 100644 index 0000000..7126977 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/FunctionExpression.php @@ -0,0 +1,43 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +class FunctionExpression extends CallExpression +{ + public function __construct(string $name, Node $arguments, int $lineno) + { + parent::__construct(['arguments' => $arguments], ['name' => $name, 'is_defined_test' => false], $lineno); + } + + public function compile(Compiler $compiler) + { + $name = $this->getAttribute('name'); + $function = $compiler->getEnvironment()->getFunction($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'function'); + $this->setAttribute('needs_environment', $function->needsEnvironment()); + $this->setAttribute('needs_context', $function->needsContext()); + $this->setAttribute('arguments', $function->getArguments()); + $callable = $function->getCallable(); + if ('constant' === $name && $this->getAttribute('is_defined_test')) { + $callable = 'twig_constant_is_defined'; + } + $this->setAttribute('callable', $callable); + $this->setAttribute('is_variadic', $function->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php new file mode 100644 index 0000000..e6a75ce --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/GetAttrExpression.php @@ -0,0 +1,87 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Extension\SandboxExtension; +use Twig\Template; + +class GetAttrExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $node, AbstractExpression $attribute, ?AbstractExpression $arguments, string $type, int $lineno) + { + $nodes = ['node' => $node, 'attribute' => $attribute]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'optimizable' => true], $lineno); + } + + public function compile(Compiler $compiler): void + { + $env = $compiler->getEnvironment(); + + // optimize array calls + if ( + $this->getAttribute('optimizable') + && (!$env->isStrictVariables() || $this->getAttribute('ignore_strict_check')) + && !$this->getAttribute('is_defined_test') + && Template::ARRAY_CALL === $this->getAttribute('type') + ) { + $var = '$'.$compiler->getVarName(); + $compiler + ->raw('(('.$var.' = ') + ->subcompile($this->getNode('node')) + ->raw(') && is_array(') + ->raw($var) + ->raw(') || ') + ->raw($var) + ->raw(' instanceof ArrayAccess ? (') + ->raw($var) + ->raw('[') + ->subcompile($this->getNode('attribute')) + ->raw('] ?? null) : null)') + ; + + return; + } + + $compiler->raw('twig_get_attribute($this->env, $this->source, '); + + if ($this->getAttribute('ignore_strict_check')) { + $this->getNode('node')->setAttribute('ignore_strict_check', true); + } + + $compiler + ->subcompile($this->getNode('node')) + ->raw(', ') + ->subcompile($this->getNode('attribute')) + ; + + if ($this->hasNode('arguments')) { + $compiler->raw(', ')->subcompile($this->getNode('arguments')); + } else { + $compiler->raw(', []'); + } + + $compiler->raw(', ') + ->repr($this->getAttribute('type')) + ->raw(', ')->repr($this->getAttribute('is_defined_test')) + ->raw(', ')->repr($this->getAttribute('ignore_strict_check')) + ->raw(', ')->repr($env->hasExtension(SandboxExtension::class)) + ->raw(', ')->repr($this->getNode('node')->getTemplateLine()) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/InlinePrint.php b/vendor/twig/twig/src/Node/Expression/InlinePrint.php new file mode 100644 index 0000000..1ad4751 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/InlinePrint.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * @internal + */ +final class InlinePrint extends AbstractExpression +{ + public function __construct(Node $node, int $lineno) + { + parent::__construct(['node' => $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('print (') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php new file mode 100644 index 0000000..d5ec0b6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/MethodCallExpression.php @@ -0,0 +1,62 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class MethodCallExpression extends AbstractExpression +{ + public function __construct(AbstractExpression $node, string $method, ArrayExpression $arguments, int $lineno) + { + parent::__construct(['node' => $node, 'arguments' => $arguments], ['method' => $method, 'safe' => false, 'is_defined_test' => false], $lineno); + + if ($node instanceof NameExpression) { + $node->setAttribute('always_defined', true); + } + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('is_defined_test')) { + $compiler + ->raw('method_exists($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(')') + ; + + return; + } + + $compiler + ->raw('twig_call_macro($macros[') + ->repr($this->getNode('node')->getAttribute('name')) + ->raw('], ') + ->repr($this->getAttribute('method')) + ->raw(', [') + ; + $first = true; + foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) { + if (!$first) { + $compiler->raw(', '); + } + $first = false; + + $compiler->subcompile($pair['value']); + } + $compiler + ->raw('], ') + ->repr($this->getTemplateLine()) + ->raw(', $context, $this->getSourceContext())'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/NameExpression.php b/vendor/twig/twig/src/Node/Expression/NameExpression.php new file mode 100644 index 0000000..c3563f0 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NameExpression.php @@ -0,0 +1,97 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class NameExpression extends AbstractExpression +{ + private $specialVars = [ + '_self' => '$this->getTemplateName()', + '_context' => '$context', + '_charset' => '$this->env->getCharset()', + ]; + + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false], $lineno); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + + $compiler->addDebugInfo($this); + + if ($this->getAttribute('is_defined_test')) { + if ($this->isSpecial()) { + $compiler->repr(true); + } elseif (\PHP_VERSION_ID >= 70400) { + $compiler + ->raw('array_key_exists(') + ->string($name) + ->raw(', $context)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context))') + ; + } + } elseif ($this->isSpecial()) { + $compiler->raw($this->specialVars[$name]); + } elseif ($this->getAttribute('always_defined')) { + $compiler + ->raw('$context[') + ->string($name) + ->raw(']') + ; + } else { + if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) { + $compiler + ->raw('($context[') + ->string($name) + ->raw('] ?? null)') + ; + } else { + $compiler + ->raw('(isset($context[') + ->string($name) + ->raw(']) || array_key_exists(') + ->string($name) + ->raw(', $context) ? $context[') + ->string($name) + ->raw('] : (function () { throw new RuntimeError(\'Variable ') + ->string($name) + ->raw(' does not exist.\', ') + ->repr($this->lineno) + ->raw(', $this->source); })()') + ->raw(')') + ; + } + } + } + + public function isSpecial() + { + return isset($this->specialVars[$this->getAttribute('name')]); + } + + public function isSimple() + { + return !$this->isSpecial() && !$this->getAttribute('is_defined_test'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php new file mode 100644 index 0000000..a72bc4f --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/NullCoalesceExpression.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Expression\Binary\AndBinary; +use Twig\Node\Expression\Test\DefinedTest; +use Twig\Node\Expression\Test\NullTest; +use Twig\Node\Expression\Unary\NotUnary; +use Twig\Node\Node; + +class NullCoalesceExpression extends ConditionalExpression +{ + public function __construct(Node $left, Node $right, int $lineno) + { + $test = new DefinedTest(clone $left, 'defined', new Node(), $left->getTemplateLine()); + // for "block()", we don't need the null test as the return value is always a string + if (!$left instanceof BlockReferenceExpression) { + $test = new AndBinary( + $test, + new NotUnary(new NullTest($left, 'null', new Node(), $left->getTemplateLine()), $left->getTemplateLine()), + $left->getTemplateLine() + ); + } + + parent::__construct($test, $left, $right, $lineno); + } + + public function compile(Compiler $compiler): void + { + /* + * This optimizes only one case. PHP 7 also supports more complex expressions + * that can return null. So, for instance, if log is defined, log("foo") ?? "..." works, + * but log($a["foo"]) ?? "..." does not if $a["foo"] is not defined. More advanced + * cases might be implemented as an optimizer node visitor, but has not been done + * as benefits are probably not worth the added complexity. + */ + if ($this->getNode('expr2') instanceof NameExpression) { + $this->getNode('expr2')->setAttribute('always_defined', true); + $compiler + ->raw('((') + ->subcompile($this->getNode('expr2')) + ->raw(') ?? (') + ->subcompile($this->getNode('expr3')) + ->raw('))') + ; + } else { + parent::compile($compiler); + } + } +} diff --git a/vendor/twig/twig/src/Node/Expression/ParentExpression.php b/vendor/twig/twig/src/Node/Expression/ParentExpression.php new file mode 100644 index 0000000..2549197 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/ParentExpression.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +/** + * Represents a parent node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ParentExpression extends AbstractExpression +{ + public function __construct(string $name, int $lineno, string $tag = null) + { + parent::__construct([], ['output' => false, 'name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('output')) { + $compiler + ->addDebugInfo($this) + ->write('$this->displayParentBlock(') + ->string($this->getAttribute('name')) + ->raw(", \$context, \$blocks);\n") + ; + } else { + $compiler + ->raw('$this->renderParentBlock(') + ->string($this->getAttribute('name')) + ->raw(', $context, $blocks)') + ; + } + } +} diff --git a/vendor/twig/twig/src/Node/Expression/TempNameExpression.php b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php new file mode 100644 index 0000000..004c704 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TempNameExpression.php @@ -0,0 +1,31 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class TempNameExpression extends AbstractExpression +{ + public function __construct(string $name, int $lineno) + { + parent::__construct([], ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->raw('$_') + ->raw($this->getAttribute('name')) + ->raw('_') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php new file mode 100644 index 0000000..57e9319 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/ConstantTest.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks if a variable is the exact same value as a constant. + * + * {% if post.status is constant('Post::PUBLISHED') %} + * the status attribute is exactly the same as Post::PUBLISHED + * {% endif %} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ConstantTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === constant(') + ; + + if ($this->getNode('arguments')->hasNode(1)) { + $compiler + ->raw('get_class(') + ->subcompile($this->getNode('arguments')->getNode(1)) + ->raw(')."::".') + ; + } + + $compiler + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw('))') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php new file mode 100644 index 0000000..3953bbb --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DefinedTest.php @@ -0,0 +1,74 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Error\SyntaxError; +use Twig\Node\Expression\ArrayExpression; +use Twig\Node\Expression\BlockReferenceExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MethodCallExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\TestExpression; +use Twig\Node\Node; + +/** + * Checks if a variable is defined in the current context. + * + * {# defined works with variable names and variable attributes #} + * {% if foo is defined %} + * {# ... #} + * {% endif %} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DefinedTest extends TestExpression +{ + public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) + { + if ($node instanceof NameExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof GetAttrExpression) { + $node->setAttribute('is_defined_test', true); + $this->changeIgnoreStrictCheck($node); + } elseif ($node instanceof BlockReferenceExpression) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof FunctionExpression && 'constant' === $node->getAttribute('name')) { + $node->setAttribute('is_defined_test', true); + } elseif ($node instanceof ConstantExpression || $node instanceof ArrayExpression) { + $node = new ConstantExpression(true, $node->getTemplateLine()); + } elseif ($node instanceof MethodCallExpression) { + $node->setAttribute('is_defined_test', true); + } else { + throw new SyntaxError('The "defined" test only works with simple variables.', $lineno); + } + + parent::__construct($node, $name, $arguments, $lineno); + } + + private function changeIgnoreStrictCheck(GetAttrExpression $node) + { + $node->setAttribute('optimizable', false); + $node->setAttribute('ignore_strict_check', true); + + if ($node->getNode('node') instanceof GetAttrExpression) { + $this->changeIgnoreStrictCheck($node->getNode('node')); + } + } + + public function compile(Compiler $compiler): void + { + $compiler->subcompile($this->getNode('node')); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php new file mode 100644 index 0000000..4cb3ee0 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/DivisiblebyTest.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks if a variable is divisible by a number. + * + * {% if loop.index is divisible by(3) %} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class DivisiblebyTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(0 == ') + ->subcompile($this->getNode('node')) + ->raw(' % ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php new file mode 100644 index 0000000..a0e3ed6 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/EvenTest.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks if a number is even. + * + * {{ var is even }} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class EvenTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 == 0') + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/NullTest.php b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php new file mode 100644 index 0000000..45b54ae --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/NullTest.php @@ -0,0 +1,34 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks that a variable is null. + * + * {{ var is none }} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class NullTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(null === ') + ->subcompile($this->getNode('node')) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/OddTest.php b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php new file mode 100644 index 0000000..d56c711 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/OddTest.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks if a number is odd. + * + * {{ var is odd }} + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class OddTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' % 2 != 0') + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php new file mode 100644 index 0000000..c96d2bc --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Test/SameasTest.php @@ -0,0 +1,34 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Test; + +use Twig\Compiler; +use Twig\Node\Expression\TestExpression; + +/** + * Checks if a variable is the same as another one (=== in PHP). + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SameasTest extends TestExpression +{ + public function compile(Compiler $compiler): void + { + $compiler + ->raw('(') + ->subcompile($this->getNode('node')) + ->raw(' === ') + ->subcompile($this->getNode('arguments')->getNode(0)) + ->raw(')') + ; + } +} diff --git a/vendor/twig/twig/src/Node/Expression/TestExpression.php b/vendor/twig/twig/src/Node/Expression/TestExpression.php new file mode 100644 index 0000000..e518bd8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/TestExpression.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; +use Twig\Node\Node; + +class TestExpression extends CallExpression +{ + public function __construct(Node $node, string $name, ?Node $arguments, int $lineno) + { + $nodes = ['node' => $node]; + if (null !== $arguments) { + $nodes['arguments'] = $arguments; + } + + parent::__construct($nodes, ['name' => $name], $lineno); + } + + public function compile(Compiler $compiler): void + { + $name = $this->getAttribute('name'); + $test = $compiler->getEnvironment()->getTest($name); + + $this->setAttribute('name', $name); + $this->setAttribute('type', 'test'); + $this->setAttribute('arguments', $test->getArguments()); + $this->setAttribute('callable', $test->getCallable()); + $this->setAttribute('is_variadic', $test->isVariadic()); + + $this->compileCallable($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php new file mode 100644 index 0000000..e31e3f8 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/AbstractUnary.php @@ -0,0 +1,34 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Unary; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Node; + +abstract class AbstractUnary extends AbstractExpression +{ + public function __construct(Node $node, int $lineno) + { + parent::__construct(['node' => $node], [], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler->raw(' '); + $this->operator($compiler); + $compiler->subcompile($this->getNode('node')); + } + + abstract public function operator(Compiler $compiler): Compiler; +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php new file mode 100644 index 0000000..dc2f235 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NegUnary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Unary; + +use Twig\Compiler; + +class NegUnary extends AbstractUnary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('-'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php new file mode 100644 index 0000000..55c11ba --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/NotUnary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Unary; + +use Twig\Compiler; + +class NotUnary extends AbstractUnary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('!'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php new file mode 100644 index 0000000..4b0a062 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/Unary/PosUnary.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression\Unary; + +use Twig\Compiler; + +class PosUnary extends AbstractUnary +{ + public function operator(Compiler $compiler): Compiler + { + return $compiler->raw('+'); + } +} diff --git a/vendor/twig/twig/src/Node/Expression/VariadicExpression.php b/vendor/twig/twig/src/Node/Expression/VariadicExpression.php new file mode 100644 index 0000000..a1bdb48 --- /dev/null +++ b/vendor/twig/twig/src/Node/Expression/VariadicExpression.php @@ -0,0 +1,24 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node\Expression; + +use Twig\Compiler; + +class VariadicExpression extends ArrayExpression +{ + public function compile(Compiler $compiler): void + { + $compiler->raw('...'); + + parent::compile($compiler); + } +} diff --git a/vendor/twig/twig/src/Node/FlushNode.php b/vendor/twig/twig/src/Node/FlushNode.php new file mode 100644 index 0000000..fa50a88 --- /dev/null +++ b/vendor/twig/twig/src/Node/FlushNode.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a flush node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class FlushNode extends Node +{ + public function __construct(int $lineno, string $tag) + { + parent::__construct([], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("flush();\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/ForLoopNode.php b/vendor/twig/twig/src/Node/ForLoopNode.php new file mode 100644 index 0000000..d5ce845 --- /dev/null +++ b/vendor/twig/twig/src/Node/ForLoopNode.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Internal node used by the for node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ForLoopNode extends Node +{ + public function __construct(int $lineno, string $tag = null) + { + parent::__construct([], ['with_loop' => false, 'ifexpr' => false, 'else' => false], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + if ($this->getAttribute('else')) { + $compiler->write("\$context['_iterated'] = true;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("++\$context['loop']['index0'];\n") + ->write("++\$context['loop']['index'];\n") + ->write("\$context['loop']['first'] = false;\n") + ->write("if (isset(\$context['loop']['length'])) {\n") + ->indent() + ->write("--\$context['loop']['revindex0'];\n") + ->write("--\$context['loop']['revindex'];\n") + ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n") + ->outdent() + ->write("}\n") + ; + } + } +} diff --git a/vendor/twig/twig/src/Node/ForNode.php b/vendor/twig/twig/src/Node/ForNode.php new file mode 100644 index 0000000..04addfb --- /dev/null +++ b/vendor/twig/twig/src/Node/ForNode.php @@ -0,0 +1,107 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\AssignNameExpression; + +/** + * Represents a for node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ForNode extends Node +{ + private $loop; + + public function __construct(AssignNameExpression $keyTarget, AssignNameExpression $valueTarget, AbstractExpression $seq, ?Node $ifexpr, Node $body, ?Node $else, int $lineno, string $tag = null) + { + $body = new Node([$body, $this->loop = new ForLoopNode($lineno, $tag)]); + + $nodes = ['key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, ['with_loop' => true], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("\$context['_parent'] = \$context;\n") + ->write("\$context['_seq'] = twig_ensure_traversable(") + ->subcompile($this->getNode('seq')) + ->raw(");\n") + ; + + if ($this->hasNode('else')) { + $compiler->write("\$context['_iterated'] = false;\n"); + } + + if ($this->getAttribute('with_loop')) { + $compiler + ->write("\$context['loop'] = [\n") + ->write(" 'parent' => \$context['_parent'],\n") + ->write(" 'index0' => 0,\n") + ->write(" 'index' => 1,\n") + ->write(" 'first' => true,\n") + ->write("];\n") + ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof \Countable)) {\n") + ->indent() + ->write("\$length = count(\$context['_seq']);\n") + ->write("\$context['loop']['revindex0'] = \$length - 1;\n") + ->write("\$context['loop']['revindex'] = \$length;\n") + ->write("\$context['loop']['length'] = \$length;\n") + ->write("\$context['loop']['last'] = 1 === \$length;\n") + ->outdent() + ->write("}\n") + ; + } + + $this->loop->setAttribute('else', $this->hasNode('else')); + $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop')); + + $compiler + ->write("foreach (\$context['_seq'] as ") + ->subcompile($this->getNode('key_target')) + ->raw(' => ') + ->subcompile($this->getNode('value_target')) + ->raw(") {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("}\n") + ; + + if ($this->hasNode('else')) { + $compiler + ->write("if (!\$context['_iterated']) {\n") + ->indent() + ->subcompile($this->getNode('else')) + ->outdent() + ->write("}\n") + ; + } + + $compiler->write("\$_parent = \$context['_parent'];\n"); + + // remove some "private" loop variables (needed for nested loops) + $compiler->write('unset($context[\'_seq\'], $context[\'_iterated\'], $context[\''.$this->getNode('key_target')->getAttribute('name').'\'], $context[\''.$this->getNode('value_target')->getAttribute('name').'\'], $context[\'_parent\'], $context[\'loop\']);'."\n"); + + // keep the values set in the inner context for variables defined in the outer context + $compiler->write("\$context = array_intersect_key(\$context, \$_parent) + \$_parent;\n"); + } +} diff --git a/vendor/twig/twig/src/Node/IfNode.php b/vendor/twig/twig/src/Node/IfNode.php new file mode 100644 index 0000000..569ab79 --- /dev/null +++ b/vendor/twig/twig/src/Node/IfNode.php @@ -0,0 +1,73 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents an if node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class IfNode extends Node +{ + public function __construct(Node $tests, ?Node $else, int $lineno, string $tag = null) + { + $nodes = ['tests' => $tests]; + if (null !== $else) { + $nodes['else'] = $else; + } + + parent::__construct($nodes, [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + for ($i = 0, $count = \count($this->getNode('tests')); $i < $count; $i += 2) { + if ($i > 0) { + $compiler + ->outdent() + ->write('} elseif (') + ; + } else { + $compiler + ->write('if (') + ; + } + + $compiler + ->subcompile($this->getNode('tests')->getNode($i)) + ->raw(") {\n") + ->indent() + ; + // The node might not exists if the content is empty + if ($this->getNode('tests')->hasNode($i + 1)) { + $compiler->subcompile($this->getNode('tests')->getNode($i + 1)); + } + } + + if ($this->hasNode('else')) { + $compiler + ->outdent() + ->write("} else {\n") + ->indent() + ->subcompile($this->getNode('else')) + ; + } + + $compiler + ->outdent() + ->write("}\n"); + } +} diff --git a/vendor/twig/twig/src/Node/ImportNode.php b/vendor/twig/twig/src/Node/ImportNode.php new file mode 100644 index 0000000..5378d79 --- /dev/null +++ b/vendor/twig/twig/src/Node/ImportNode.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\NameExpression; + +/** + * Represents an import node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class ImportNode extends Node +{ + public function __construct(AbstractExpression $expr, AbstractExpression $var, int $lineno, string $tag = null, bool $global = true) + { + parent::__construct(['expr' => $expr, 'var' => $var], ['global' => $global], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('$macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + + if ($this->getAttribute('global')) { + $compiler + ->raw('$this->macros[') + ->repr($this->getNode('var')->getAttribute('name')) + ->raw('] = ') + ; + } + + if ($this->getNode('expr') instanceof NameExpression && '_self' === $this->getNode('expr')->getAttribute('name')) { + $compiler->raw('$this'); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')->unwrap()') + ; + } + + $compiler->raw(";\n"); + } +} diff --git a/vendor/twig/twig/src/Node/IncludeNode.php b/vendor/twig/twig/src/Node/IncludeNode.php new file mode 100644 index 0000000..d540d6b --- /dev/null +++ b/vendor/twig/twig/src/Node/IncludeNode.php @@ -0,0 +1,106 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; + +/** + * Represents an include node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class IncludeNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, ?AbstractExpression $variables, bool $only, bool $ignoreMissing, int $lineno, string $tag = null) + { + $nodes = ['expr' => $expr]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => $only, 'ignore_missing' => $ignoreMissing], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if ($this->getAttribute('ignore_missing')) { + $template = $compiler->getVarName(); + + $compiler + ->write(sprintf("$%s = null;\n", $template)) + ->write("try {\n") + ->indent() + ->write(sprintf('$%s = ', $template)) + ; + + $this->addGetTemplate($compiler); + + $compiler + ->raw(";\n") + ->outdent() + ->write("} catch (LoaderError \$e) {\n") + ->indent() + ->write("// ignore missing template\n") + ->outdent() + ->write("}\n") + ->write(sprintf("if ($%s) {\n", $template)) + ->indent() + ->write(sprintf('$%s->display(', $template)) + ; + $this->addTemplateArguments($compiler); + $compiler + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } else { + $this->addGetTemplate($compiler); + $compiler->raw('->display('); + $this->addTemplateArguments($compiler); + $compiler->raw(");\n"); + } + } + + protected function addGetTemplate(Compiler $compiler) + { + $compiler + ->write('$this->loadTemplate(') + ->subcompile($this->getNode('expr')) + ->raw(', ') + ->repr($this->getTemplateName()) + ->raw(', ') + ->repr($this->getTemplateLine()) + ->raw(')') + ; + } + + protected function addTemplateArguments(Compiler $compiler) + { + if (!$this->hasNode('variables')) { + $compiler->raw(false === $this->getAttribute('only') ? '$context' : '[]'); + } elseif (false === $this->getAttribute('only')) { + $compiler + ->raw('twig_array_merge($context, ') + ->subcompile($this->getNode('variables')) + ->raw(')') + ; + } else { + $compiler->raw('twig_to_array('); + $compiler->subcompile($this->getNode('variables')); + $compiler->raw(')'); + } + } +} diff --git a/vendor/twig/twig/src/Node/MacroNode.php b/vendor/twig/twig/src/Node/MacroNode.php new file mode 100644 index 0000000..7f1b24d --- /dev/null +++ b/vendor/twig/twig/src/Node/MacroNode.php @@ -0,0 +1,113 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Error\SyntaxError; + +/** + * Represents a macro node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class MacroNode extends Node +{ + public const VARARGS_NAME = 'varargs'; + + public function __construct(string $name, Node $body, Node $arguments, int $lineno, string $tag = null) + { + foreach ($arguments as $argumentName => $argument) { + if (self::VARARGS_NAME === $argumentName) { + throw new SyntaxError(sprintf('The argument "%s" in macro "%s" cannot be defined because the variable "%s" is reserved for arbitrary arguments.', self::VARARGS_NAME, $name, self::VARARGS_NAME), $argument->getTemplateLine(), $argument->getSourceContext()); + } + } + + parent::__construct(['body' => $body, 'arguments' => $arguments], ['name' => $name], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write(sprintf('public function macro_%s(', $this->getAttribute('name'))) + ; + + $count = \count($this->getNode('arguments')); + $pos = 0; + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->raw('$__'.$name.'__ = ') + ->subcompile($default) + ; + + if (++$pos < $count) { + $compiler->raw(', '); + } + } + + if ($count) { + $compiler->raw(', '); + } + + $compiler + ->raw('...$__varargs__') + ->raw(")\n") + ->write("{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->write("\$context = \$this->env->mergeGlobals([\n") + ->indent() + ; + + foreach ($this->getNode('arguments') as $name => $default) { + $compiler + ->write('') + ->string($name) + ->raw(' => $__'.$name.'__') + ->raw(",\n") + ; + } + + $compiler + ->write('') + ->string(self::VARARGS_NAME) + ->raw(' => ') + ; + + $compiler + ->raw("\$__varargs__,\n") + ->outdent() + ->write("]);\n\n") + ->write("\$blocks = [];\n\n") + ; + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->raw("\n") + ->write("return ('' === \$tmp = ob_get_contents()) ? '' : new Markup(\$tmp, \$this->env->getCharset());\n") + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("ob_end_clean();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/ModuleNode.php b/vendor/twig/twig/src/Node/ModuleNode.php new file mode 100644 index 0000000..e972b6b --- /dev/null +++ b/vendor/twig/twig/src/Node/ModuleNode.php @@ -0,0 +1,464 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Source; + +/** + * Represents a module node. + * + * Consider this class as being final. If you need to customize the behavior of + * the generated class, consider adding nodes to the following nodes: display_start, + * display_end, constructor_start, constructor_end, and class_end. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class ModuleNode extends Node +{ + public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source) + { + $nodes = [ + 'body' => $body, + 'blocks' => $blocks, + 'macros' => $macros, + 'traits' => $traits, + 'display_start' => new Node(), + 'display_end' => new Node(), + 'constructor_start' => new Node(), + 'constructor_end' => new Node(), + 'class_end' => new Node(), + ]; + if (null !== $parent) { + $nodes['parent'] = $parent; + } + + // embedded templates are set as attributes so that they are only visited once by the visitors + parent::__construct($nodes, [ + 'index' => null, + 'embedded_templates' => $embeddedTemplates, + ], 1); + + // populate the template name of all node children + $this->setSourceContext($source); + } + + public function setIndex($index) + { + $this->setAttribute('index', $index); + } + + public function compile(Compiler $compiler): void + { + $this->compileTemplate($compiler); + + foreach ($this->getAttribute('embedded_templates') as $template) { + $compiler->subcompile($template); + } + } + + protected function compileTemplate(Compiler $compiler) + { + if (!$this->getAttribute('index')) { + $compiler->write('<?php'); + } + + $this->compileClassHeader($compiler); + + $this->compileConstructor($compiler); + + $this->compileGetParent($compiler); + + $this->compileDisplay($compiler); + + $compiler->subcompile($this->getNode('blocks')); + + $this->compileMacros($compiler); + + $this->compileGetTemplateName($compiler); + + $this->compileIsTraitable($compiler); + + $this->compileDebugInfo($compiler); + + $this->compileGetSourceContext($compiler); + + $this->compileClassFooter($compiler); + } + + protected function compileGetParent(Compiler $compiler) + { + if (!$this->hasNode('parent')) { + return; + } + $parent = $this->getNode('parent'); + + $compiler + ->write("protected function doGetParent(array \$context)\n", "{\n") + ->indent() + ->addDebugInfo($parent) + ->write('return ') + ; + + if ($parent instanceof ConstantExpression) { + $compiler->subcompile($parent); + } else { + $compiler + ->raw('$this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(')') + ; + } + + $compiler + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassHeader(Compiler $compiler) + { + $compiler + ->write("\n\n") + ; + if (!$this->getAttribute('index')) { + $compiler + ->write("use Twig\Environment;\n") + ->write("use Twig\Error\LoaderError;\n") + ->write("use Twig\Error\RuntimeError;\n") + ->write("use Twig\Extension\SandboxExtension;\n") + ->write("use Twig\Markup;\n") + ->write("use Twig\Sandbox\SecurityError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n") + ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n") + ->write("use Twig\Source;\n") + ->write("use Twig\Template;\n\n") + ; + } + $compiler + // if the template name contains */, add a blank to avoid a PHP parse error + ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n") + ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index'))) + ->raw(" extends Template\n") + ->write("{\n") + ->indent() + ->write("private \$source;\n") + ->write("private \$macros = [];\n\n") + ; + } + + protected function compileConstructor(Compiler $compiler) + { + $compiler + ->write("public function __construct(Environment \$env)\n", "{\n") + ->indent() + ->subcompile($this->getNode('constructor_start')) + ->write("parent::__construct(\$env);\n\n") + ->write("\$this->source = \$this->getSourceContext();\n\n") + ; + + // parent + if (!$this->hasNode('parent')) { + $compiler->write("\$this->parent = false;\n\n"); + } + + $countTraits = \count($this->getNode('traits')); + if ($countTraits) { + // traits + foreach ($this->getNode('traits') as $i => $trait) { + $node = $trait->getNode('template'); + + $compiler + ->addDebugInfo($node) + ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i)) + ->indent() + ->write("throw new RuntimeError('Template \"'.") + ->subcompile($trait->getNode('template')) + ->raw(".'\" cannot be used as a trait.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i)) + ; + + foreach ($trait->getNode('targets') as $key => $value) { + $compiler + ->write(sprintf('if (!isset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("])) {\n") + ->indent() + ->write("throw new RuntimeError('Block ") + ->string($key) + ->raw(' is not defined in trait ') + ->subcompile($trait->getNode('template')) + ->raw(".', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->source);\n") + ->outdent() + ->write("}\n\n") + + ->write(sprintf('$_trait_%s_blocks[', $i)) + ->subcompile($value) + ->raw(sprintf('] = $_trait_%s_blocks[', $i)) + ->string($key) + ->raw(sprintf(']; unset($_trait_%s_blocks[', $i)) + ->string($key) + ->raw("]);\n\n") + ; + } + } + + if ($countTraits > 1) { + $compiler + ->write("\$this->traits = array_merge(\n") + ->indent() + ; + + for ($i = 0; $i < $countTraits; ++$i) { + $compiler + ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i)) + ; + } + + $compiler + ->outdent() + ->write(");\n\n") + ; + } else { + $compiler + ->write("\$this->traits = \$_trait_0_blocks;\n\n") + ; + } + + $compiler + ->write("\$this->blocks = array_merge(\n") + ->indent() + ->write("\$this->traits,\n") + ->write("[\n") + ; + } else { + $compiler + ->write("\$this->blocks = [\n") + ; + } + + // blocks + $compiler + ->indent() + ; + + foreach ($this->getNode('blocks') as $name => $node) { + $compiler + ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name)) + ; + } + + if ($countTraits) { + $compiler + ->outdent() + ->write("]\n") + ->outdent() + ->write(");\n") + ; + } else { + $compiler + ->outdent() + ->write("];\n") + ; + } + + $compiler + ->subcompile($this->getNode('constructor_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDisplay(Compiler $compiler) + { + $compiler + ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n") + ->indent() + ->write("\$macros = \$this->macros;\n") + ->subcompile($this->getNode('display_start')) + ->subcompile($this->getNode('body')) + ; + + if ($this->hasNode('parent')) { + $parent = $this->getNode('parent'); + + $compiler->addDebugInfo($parent); + if ($parent instanceof ConstantExpression) { + $compiler + ->write('$this->parent = $this->loadTemplate(') + ->subcompile($parent) + ->raw(', ') + ->repr($this->getSourceContext()->getName()) + ->raw(', ') + ->repr($parent->getTemplateLine()) + ->raw(");\n") + ; + $compiler->write('$this->parent'); + } else { + $compiler->write('$this->getParent($context)'); + } + $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n"); + } + + $compiler + ->subcompile($this->getNode('display_end')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileClassFooter(Compiler $compiler) + { + $compiler + ->subcompile($this->getNode('class_end')) + ->outdent() + ->write("}\n") + ; + } + + protected function compileMacros(Compiler $compiler) + { + $compiler->subcompile($this->getNode('macros')); + } + + protected function compileGetTemplateName(Compiler $compiler) + { + $compiler + ->write("public function getTemplateName()\n", "{\n") + ->indent() + ->write('return ') + ->repr($this->getSourceContext()->getName()) + ->raw(";\n") + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileIsTraitable(Compiler $compiler) + { + // A template can be used as a trait if: + // * it has no parent + // * it has no macros + // * it has no body + // + // Put another way, a template can be used as a trait if it + // only contains blocks and use statements. + $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros')); + if ($traitable) { + if ($this->getNode('body') instanceof BodyNode) { + $nodes = $this->getNode('body')->getNode(0); + } else { + $nodes = $this->getNode('body'); + } + + if (!\count($nodes)) { + $nodes = new Node([$nodes]); + } + + foreach ($nodes as $node) { + if (!\count($node)) { + continue; + } + + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if ($node instanceof BlockReferenceNode) { + continue; + } + + $traitable = false; + break; + } + } + + if ($traitable) { + return; + } + + $compiler + ->write("public function isTraitable()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false')) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileDebugInfo(Compiler $compiler) + { + $compiler + ->write("public function getDebugInfo()\n", "{\n") + ->indent() + ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true)))) + ->outdent() + ->write("}\n\n") + ; + } + + protected function compileGetSourceContext(Compiler $compiler) + { + $compiler + ->write("public function getSourceContext()\n", "{\n") + ->indent() + ->write('return new Source(') + ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '') + ->raw(', ') + ->string($this->getSourceContext()->getName()) + ->raw(', ') + ->string($this->getSourceContext()->getPath()) + ->raw(");\n") + ->outdent() + ->write("}\n") + ; + } + + protected function compileLoadTemplate(Compiler $compiler, $node, $var) + { + if ($node instanceof ConstantExpression) { + $compiler + ->write(sprintf('%s = $this->loadTemplate(', $var)) + ->subcompile($node) + ->raw(', ') + ->repr($node->getTemplateName()) + ->raw(', ') + ->repr($node->getTemplateLine()) + ->raw(");\n") + ; + } else { + throw new \LogicException('Trait templates can only be constant nodes.'); + } + } +} diff --git a/vendor/twig/twig/src/Node/Node.php b/vendor/twig/twig/src/Node/Node.php new file mode 100644 index 0000000..c0558b9 --- /dev/null +++ b/vendor/twig/twig/src/Node/Node.php @@ -0,0 +1,179 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Source; + +/** + * Represents a node in the AST. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class Node implements \Countable, \IteratorAggregate +{ + protected $nodes; + protected $attributes; + protected $lineno; + protected $tag; + + private $name; + private $sourceContext; + + /** + * @param array $nodes An array of named nodes + * @param array $attributes An array of attributes (should not be nodes) + * @param int $lineno The line number + * @param string $tag The tag name associated with the Node + */ + public function __construct(array $nodes = [], array $attributes = [], int $lineno = 0, string $tag = null) + { + foreach ($nodes as $name => $node) { + if (!$node instanceof self) { + throw new \InvalidArgumentException(sprintf('Using "%s" for the value of node "%s" of "%s" is not supported. You must pass a \Twig\Node\Node instance.', \is_object($node) ? \get_class($node) : (null === $node ? 'null' : \gettype($node)), $name, static::class)); + } + } + $this->nodes = $nodes; + $this->attributes = $attributes; + $this->lineno = $lineno; + $this->tag = $tag; + } + + public function __toString() + { + $attributes = []; + foreach ($this->attributes as $name => $value) { + $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true))); + } + + $repr = [static::class.'('.implode(', ', $attributes)]; + + if (\count($this->nodes)) { + foreach ($this->nodes as $name => $node) { + $len = \strlen($name) + 4; + $noderepr = []; + foreach (explode("\n", (string) $node) as $line) { + $noderepr[] = str_repeat(' ', $len).$line; + } + + $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr))); + } + + $repr[] = ')'; + } else { + $repr[0] .= ')'; + } + + return implode("\n", $repr); + } + + /** + * @return void + */ + public function compile(Compiler $compiler) + { + foreach ($this->nodes as $node) { + $node->compile($compiler); + } + } + + public function getTemplateLine(): int + { + return $this->lineno; + } + + public function getNodeTag(): ?string + { + return $this->tag; + } + + public function hasAttribute(string $name): bool + { + return \array_key_exists($name, $this->attributes); + } + + public function getAttribute(string $name) + { + if (!\array_key_exists($name, $this->attributes)) { + throw new \LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, static::class)); + } + + return $this->attributes[$name]; + } + + public function setAttribute(string $name, $value): void + { + $this->attributes[$name] = $value; + } + + public function removeAttribute(string $name): void + { + unset($this->attributes[$name]); + } + + public function hasNode(string $name): bool + { + return isset($this->nodes[$name]); + } + + public function getNode(string $name): self + { + if (!isset($this->nodes[$name])) { + throw new \LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, static::class)); + } + + return $this->nodes[$name]; + } + + public function setNode(string $name, self $node): void + { + $this->nodes[$name] = $node; + } + + public function removeNode(string $name): void + { + unset($this->nodes[$name]); + } + + /** + * @return int + */ + #[\ReturnTypeWillChange] + public function count() + { + return \count($this->nodes); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->nodes); + } + + public function getTemplateName(): ?string + { + return $this->sourceContext ? $this->sourceContext->getName() : null; + } + + public function setSourceContext(Source $source): void + { + $this->sourceContext = $source; + foreach ($this->nodes as $node) { + $node->setSourceContext($source); + } + } + + public function getSourceContext(): ?Source + { + return $this->sourceContext; + } +} diff --git a/vendor/twig/twig/src/Node/NodeCaptureInterface.php b/vendor/twig/twig/src/Node/NodeCaptureInterface.php new file mode 100644 index 0000000..9fb6a0c --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeCaptureInterface.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +/** + * Represents a node that captures any nested displayable nodes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface NodeCaptureInterface +{ +} diff --git a/vendor/twig/twig/src/Node/NodeOutputInterface.php b/vendor/twig/twig/src/Node/NodeOutputInterface.php new file mode 100644 index 0000000..5e35b40 --- /dev/null +++ b/vendor/twig/twig/src/Node/NodeOutputInterface.php @@ -0,0 +1,21 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +/** + * Represents a displayable node in the AST. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface NodeOutputInterface +{ +} diff --git a/vendor/twig/twig/src/Node/PrintNode.php b/vendor/twig/twig/src/Node/PrintNode.php new file mode 100644 index 0000000..60386d2 --- /dev/null +++ b/vendor/twig/twig/src/Node/PrintNode.php @@ -0,0 +1,39 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\AbstractExpression; + +/** + * Represents a node that outputs an expression. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class PrintNode extends Node implements NodeOutputInterface +{ + public function __construct(AbstractExpression $expr, int $lineno, string $tag = null) + { + parent::__construct(['expr' => $expr], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->subcompile($this->getNode('expr')) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/SandboxNode.php b/vendor/twig/twig/src/Node/SandboxNode.php new file mode 100644 index 0000000..4d5666b --- /dev/null +++ b/vendor/twig/twig/src/Node/SandboxNode.php @@ -0,0 +1,52 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a sandbox node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SandboxNode extends Node +{ + public function __construct(Node $body, int $lineno, string $tag = null) + { + parent::__construct(['body' => $body], [], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write("if (!\$alreadySandboxed = \$this->sandbox->isSandboxed()) {\n") + ->indent() + ->write("\$this->sandbox->enableSandbox();\n") + ->outdent() + ->write("}\n") + ->write("try {\n") + ->indent() + ->subcompile($this->getNode('body')) + ->outdent() + ->write("} finally {\n") + ->indent() + ->write("if (!\$alreadySandboxed) {\n") + ->indent() + ->write("\$this->sandbox->disableSandbox();\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/SetNode.php b/vendor/twig/twig/src/Node/SetNode.php new file mode 100644 index 0000000..96b6bd8 --- /dev/null +++ b/vendor/twig/twig/src/Node/SetNode.php @@ -0,0 +1,105 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; +use Twig\Node\Expression\ConstantExpression; + +/** + * Represents a set node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SetNode extends Node implements NodeCaptureInterface +{ + public function __construct(bool $capture, Node $names, Node $values, int $lineno, string $tag = null) + { + parent::__construct(['names' => $names, 'values' => $values], ['capture' => $capture, 'safe' => false], $lineno, $tag); + + /* + * Optimizes the node when capture is used for a large block of text. + * + * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig\Markup("foo"); + */ + if ($this->getAttribute('capture')) { + $this->setAttribute('safe', true); + + $values = $this->getNode('values'); + if ($values instanceof TextNode) { + $this->setNode('values', new ConstantExpression($values->getAttribute('data'), $values->getTemplateLine())); + $this->setAttribute('capture', false); + } + } + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('list('); + foreach ($this->getNode('names') as $idx => $node) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($node); + } + $compiler->raw(')'); + } else { + if ($this->getAttribute('capture')) { + if ($compiler->getEnvironment()->isDebug()) { + $compiler->write("ob_start();\n"); + } else { + $compiler->write("ob_start(function () { return ''; });\n"); + } + $compiler + ->subcompile($this->getNode('values')) + ; + } + + $compiler->subcompile($this->getNode('names'), false); + + if ($this->getAttribute('capture')) { + $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Markup(\$tmp, \$this->env->getCharset())"); + } + } + + if (!$this->getAttribute('capture')) { + $compiler->raw(' = '); + + if (\count($this->getNode('names')) > 1) { + $compiler->write('['); + foreach ($this->getNode('values') as $idx => $value) { + if ($idx) { + $compiler->raw(', '); + } + + $compiler->subcompile($value); + } + $compiler->raw(']'); + } else { + if ($this->getAttribute('safe')) { + $compiler + ->raw("('' === \$tmp = ") + ->subcompile($this->getNode('values')) + ->raw(") ? '' : new Markup(\$tmp, \$this->env->getCharset())") + ; + } else { + $compiler->subcompile($this->getNode('values')); + } + } + } + + $compiler->raw(";\n"); + } +} diff --git a/vendor/twig/twig/src/Node/TextNode.php b/vendor/twig/twig/src/Node/TextNode.php new file mode 100644 index 0000000..d74ebe6 --- /dev/null +++ b/vendor/twig/twig/src/Node/TextNode.php @@ -0,0 +1,38 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a text node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class TextNode extends Node implements NodeOutputInterface +{ + public function __construct(string $data, int $lineno) + { + parent::__construct([], ['data' => $data], $lineno); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->addDebugInfo($this) + ->write('echo ') + ->string($this->getAttribute('data')) + ->raw(";\n") + ; + } +} diff --git a/vendor/twig/twig/src/Node/WithNode.php b/vendor/twig/twig/src/Node/WithNode.php new file mode 100644 index 0000000..56a3344 --- /dev/null +++ b/vendor/twig/twig/src/Node/WithNode.php @@ -0,0 +1,70 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Node; + +use Twig\Compiler; + +/** + * Represents a nested "with" scope. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class WithNode extends Node +{ + public function __construct(Node $body, ?Node $variables, bool $only, int $lineno, string $tag = null) + { + $nodes = ['body' => $body]; + if (null !== $variables) { + $nodes['variables'] = $variables; + } + + parent::__construct($nodes, ['only' => $only], $lineno, $tag); + } + + public function compile(Compiler $compiler): void + { + $compiler->addDebugInfo($this); + + $parentContextName = $compiler->getVarName(); + + $compiler->write(sprintf("\$%s = \$context;\n", $parentContextName)); + + if ($this->hasNode('variables')) { + $node = $this->getNode('variables'); + $varsName = $compiler->getVarName(); + $compiler + ->write(sprintf('$%s = ', $varsName)) + ->subcompile($node) + ->raw(";\n") + ->write(sprintf("if (!twig_test_iterable(\$%s)) {\n", $varsName)) + ->indent() + ->write("throw new RuntimeError('Variables passed to the \"with\" tag must be a hash.', ") + ->repr($node->getTemplateLine()) + ->raw(", \$this->getSourceContext());\n") + ->outdent() + ->write("}\n") + ->write(sprintf("\$%s = twig_to_array(\$%s);\n", $varsName, $varsName)) + ; + + if ($this->getAttribute('only')) { + $compiler->write("\$context = [];\n"); + } + + $compiler->write(sprintf("\$context = \$this->env->mergeGlobals(array_merge(\$context, \$%s));\n", $varsName)); + } + + $compiler + ->subcompile($this->getNode('body')) + ->write(sprintf("\$context = \$%s;\n", $parentContextName)) + ; + } +} diff --git a/vendor/twig/twig/src/NodeTraverser.php b/vendor/twig/twig/src/NodeTraverser.php new file mode 100644 index 0000000..47a2d5c --- /dev/null +++ b/vendor/twig/twig/src/NodeTraverser.php @@ -0,0 +1,76 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; + +/** + * A node traverser. + * + * It visits all nodes and their children and calls the given visitor for each. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class NodeTraverser +{ + private $env; + private $visitors = []; + + /** + * @param NodeVisitorInterface[] $visitors + */ + public function __construct(Environment $env, array $visitors = []) + { + $this->env = $env; + foreach ($visitors as $visitor) { + $this->addVisitor($visitor); + } + } + + public function addVisitor(NodeVisitorInterface $visitor): void + { + $this->visitors[$visitor->getPriority()][] = $visitor; + } + + /** + * Traverses a node and calls the registered visitors. + */ + public function traverse(Node $node): Node + { + ksort($this->visitors); + foreach ($this->visitors as $visitors) { + foreach ($visitors as $visitor) { + $node = $this->traverseForVisitor($visitor, $node); + } + } + + return $node; + } + + private function traverseForVisitor(NodeVisitorInterface $visitor, Node $node): ?Node + { + $node = $visitor->enterNode($node, $this->env); + + foreach ($node as $k => $n) { + if (null !== $m = $this->traverseForVisitor($visitor, $n)) { + if ($m !== $n) { + $node->setNode($k, $m); + } + } else { + $node->removeNode($k); + } + } + + return $visitor->leaveNode($node, $this->env); + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php new file mode 100644 index 0000000..d7036ae --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/AbstractNodeVisitor.php @@ -0,0 +1,49 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\Node; + +/** + * Used to make node visitors compatible with Twig 1.x and 2.x. + * + * To be removed in Twig 3.1. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class AbstractNodeVisitor implements NodeVisitorInterface +{ + final public function enterNode(Node $node, Environment $env): Node + { + return $this->doEnterNode($node, $env); + } + + final public function leaveNode(Node $node, Environment $env): ?Node + { + return $this->doLeaveNode($node, $env); + } + + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + abstract protected function doEnterNode(Node $node, Environment $env); + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + abstract protected function doLeaveNode(Node $node, Environment $env); +} diff --git a/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php new file mode 100644 index 0000000..fe56ea3 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/EscaperNodeVisitor.php @@ -0,0 +1,208 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Extension\EscaperExtension; +use Twig\Node\AutoEscapeNode; +use Twig\Node\BlockNode; +use Twig\Node\BlockReferenceNode; +use Twig\Node\DoNode; +use Twig\Node\Expression\ConditionalExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\InlinePrint; +use Twig\Node\ImportNode; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\PrintNode; +use Twig\NodeTraverser; + +/** + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class EscaperNodeVisitor implements NodeVisitorInterface +{ + private $statusStack = []; + private $blocks = []; + private $safeAnalysis; + private $traverser; + private $defaultStrategy = false; + private $safeVars = []; + + public function __construct() + { + $this->safeAnalysis = new SafeAnalysisNodeVisitor(); + } + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + if ($env->hasExtension(EscaperExtension::class) && $defaultStrategy = $env->getExtension(EscaperExtension::class)->getDefaultStrategy($node->getTemplateName())) { + $this->defaultStrategy = $defaultStrategy; + } + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof AutoEscapeNode) { + $this->statusStack[] = $node->getAttribute('value'); + } elseif ($node instanceof BlockNode) { + $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env); + } elseif ($node instanceof ImportNode) { + $this->safeVars[] = $node->getNode('var')->getAttribute('name'); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->defaultStrategy = false; + $this->safeVars = []; + $this->blocks = []; + } elseif ($node instanceof FilterExpression) { + return $this->preEscapeFilterNode($node, $env); + } elseif ($node instanceof PrintNode && false !== $type = $this->needEscaping($env)) { + $expression = $node->getNode('expr'); + if ($expression instanceof ConditionalExpression && $this->shouldUnwrapConditional($expression, $env, $type)) { + return new DoNode($this->unwrapConditional($expression, $env, $type), $expression->getTemplateLine()); + } + + return $this->escapePrintNode($node, $env, $type); + } + + if ($node instanceof AutoEscapeNode || $node instanceof BlockNode) { + array_pop($this->statusStack); + } elseif ($node instanceof BlockReferenceNode) { + $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env); + } + + return $node; + } + + private function shouldUnwrapConditional(ConditionalExpression $expression, Environment $env, string $type): bool + { + $expr2Safe = $this->isSafeFor($type, $expression->getNode('expr2'), $env); + $expr3Safe = $this->isSafeFor($type, $expression->getNode('expr3'), $env); + + return $expr2Safe !== $expr3Safe; + } + + private function unwrapConditional(ConditionalExpression $expression, Environment $env, string $type): ConditionalExpression + { + // convert "echo a ? b : c" to "a ? echo b : echo c" recursively + $expr2 = $expression->getNode('expr2'); + if ($expr2 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr2, $env, $type)) { + $expr2 = $this->unwrapConditional($expr2, $env, $type); + } else { + $expr2 = $this->escapeInlinePrintNode(new InlinePrint($expr2, $expr2->getTemplateLine()), $env, $type); + } + $expr3 = $expression->getNode('expr3'); + if ($expr3 instanceof ConditionalExpression && $this->shouldUnwrapConditional($expr3, $env, $type)) { + $expr3 = $this->unwrapConditional($expr3, $env, $type); + } else { + $expr3 = $this->escapeInlinePrintNode(new InlinePrint($expr3, $expr3->getTemplateLine()), $env, $type); + } + + return new ConditionalExpression($expression->getNode('expr1'), $expr2, $expr3, $expression->getTemplateLine()); + } + + private function escapeInlinePrintNode(InlinePrint $node, Environment $env, string $type): Node + { + $expression = $node->getNode('node'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + return new InlinePrint($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + private function escapePrintNode(PrintNode $node, Environment $env, string $type): Node + { + if (false === $type) { + return $node; + } + + $expression = $node->getNode('expr'); + + if ($this->isSafeFor($type, $expression, $env)) { + return $node; + } + + $class = \get_class($node); + + return new $class($this->getEscaperFilter($type, $expression), $node->getTemplateLine()); + } + + private function preEscapeFilterNode(FilterExpression $filter, Environment $env): FilterExpression + { + $name = $filter->getNode('filter')->getAttribute('value'); + + $type = $env->getFilter($name)->getPreEscape(); + if (null === $type) { + return $filter; + } + + $node = $filter->getNode('node'); + if ($this->isSafeFor($type, $node, $env)) { + return $filter; + } + + $filter->setNode('node', $this->getEscaperFilter($type, $node)); + + return $filter; + } + + private function isSafeFor(string $type, Node $expression, Environment $env): bool + { + $safe = $this->safeAnalysis->getSafe($expression); + + if (null === $safe) { + if (null === $this->traverser) { + $this->traverser = new NodeTraverser($env, [$this->safeAnalysis]); + } + + $this->safeAnalysis->setSafeVars($this->safeVars); + + $this->traverser->traverse($expression); + $safe = $this->safeAnalysis->getSafe($expression); + } + + return \in_array($type, $safe) || \in_array('all', $safe); + } + + private function needEscaping(Environment $env) + { + if (\count($this->statusStack)) { + return $this->statusStack[\count($this->statusStack) - 1]; + } + + return $this->defaultStrategy ? $this->defaultStrategy : false; + } + + private function getEscaperFilter(string $type, Node $node): FilterExpression + { + $line = $node->getTemplateLine(); + $name = new ConstantExpression('escape', $line); + $args = new Node([new ConstantExpression($type, $line), new ConstantExpression(null, $line), new ConstantExpression(true, $line)]); + + return new FilterExpression($node, $name, $args, $line); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php new file mode 100644 index 0000000..af477e6 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/MacroAutoImportNodeVisitor.php @@ -0,0 +1,74 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\Expression\AssignNameExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MethodCallExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\ImportNode; +use Twig\Node\ModuleNode; +use Twig\Node\Node; + +/** + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class MacroAutoImportNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $hasMacroCalls = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->hasMacroCalls = false; + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + if ($this->hasMacroCalls) { + $node->getNode('constructor_end')->setNode('_auto_macro_import', new ImportNode(new NameExpression('_self', 0), new AssignNameExpression('_self', 0), 0, 'import', true)); + } + } elseif ($this->inAModule) { + if ( + $node instanceof GetAttrExpression && + $node->getNode('node') instanceof NameExpression && + '_self' === $node->getNode('node')->getAttribute('name') && + $node->getNode('attribute') instanceof ConstantExpression + ) { + $this->hasMacroCalls = true; + + $name = $node->getNode('attribute')->getAttribute('value'); + $node = new MethodCallExpression($node->getNode('node'), 'macro_'.$name, $node->getNode('arguments'), $node->getTemplateLine()); + $node->setAttribute('safe', true); + } + } + + return $node; + } + + public function getPriority(): int + { + // we must be ran before auto-escaping + return -10; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php new file mode 100644 index 0000000..59e836d --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/NodeVisitorInterface.php @@ -0,0 +1,46 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\Node; + +/** + * Interface for node visitor classes. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface NodeVisitorInterface +{ + /** + * Called before child nodes are visited. + * + * @return Node The modified node + */ + public function enterNode(Node $node, Environment $env): Node; + + /** + * Called after child nodes are visited. + * + * @return Node|null The modified node or null if the node must be removed + */ + public function leaveNode(Node $node, Environment $env): ?Node; + + /** + * Returns the priority for this visitor. + * + * Priority should be between -10 and 10 (0 is the default). + * + * @return int The priority level + */ + public function getPriority(); +} diff --git a/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php new file mode 100644 index 0000000..7ac75e4 --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/OptimizerNodeVisitor.php @@ -0,0 +1,217 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\BlockReferenceNode; +use Twig\Node\Expression\BlockReferenceExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\ParentExpression; +use Twig\Node\ForNode; +use Twig\Node\IncludeNode; +use Twig\Node\Node; +use Twig\Node\PrintNode; + +/** + * Tries to optimize the AST. + * + * This visitor is always the last registered one. + * + * You can configure which optimizations you want to activate via the + * optimizer mode. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class OptimizerNodeVisitor implements NodeVisitorInterface +{ + public const OPTIMIZE_ALL = -1; + public const OPTIMIZE_NONE = 0; + public const OPTIMIZE_FOR = 2; + public const OPTIMIZE_RAW_FILTER = 4; + + private $loops = []; + private $loopsTargets = []; + private $optimizers; + + /** + * @param int $optimizers The optimizer mode + */ + public function __construct(int $optimizers = -1) + { + if ($optimizers > (self::OPTIMIZE_FOR | self::OPTIMIZE_RAW_FILTER)) { + throw new \InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers)); + } + + $this->optimizers = $optimizers; + } + + public function enterNode(Node $node, Environment $env): Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->enterOptimizeFor($node, $env); + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) { + $this->leaveOptimizeFor($node, $env); + } + + if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) { + $node = $this->optimizeRawFilter($node, $env); + } + + $node = $this->optimizePrintNode($node, $env); + + return $node; + } + + /** + * Optimizes print nodes. + * + * It replaces: + * + * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()" + */ + private function optimizePrintNode(Node $node, Environment $env): Node + { + if (!$node instanceof PrintNode) { + return $node; + } + + $exprNode = $node->getNode('expr'); + if ( + $exprNode instanceof BlockReferenceExpression || + $exprNode instanceof ParentExpression + ) { + $exprNode->setAttribute('output', true); + + return $exprNode; + } + + return $node; + } + + /** + * Removes "raw" filters. + */ + private function optimizeRawFilter(Node $node, Environment $env): Node + { + if ($node instanceof FilterExpression && 'raw' == $node->getNode('filter')->getAttribute('value')) { + return $node->getNode('node'); + } + + return $node; + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function enterOptimizeFor(Node $node, Environment $env): void + { + if ($node instanceof ForNode) { + // disable the loop variable by default + $node->setAttribute('with_loop', false); + array_unshift($this->loops, $node); + array_unshift($this->loopsTargets, $node->getNode('value_target')->getAttribute('name')); + array_unshift($this->loopsTargets, $node->getNode('key_target')->getAttribute('name')); + } elseif (!$this->loops) { + // we are outside a loop + return; + } + + // when do we need to add the loop variable back? + + // the loop variable is referenced for the current loop + elseif ($node instanceof NameExpression && 'loop' === $node->getAttribute('name')) { + $node->setAttribute('always_defined', true); + $this->addLoopToCurrent(); + } + + // optimize access to loop targets + elseif ($node instanceof NameExpression && \in_array($node->getAttribute('name'), $this->loopsTargets)) { + $node->setAttribute('always_defined', true); + } + + // block reference + elseif ($node instanceof BlockReferenceNode || $node instanceof BlockReferenceExpression) { + $this->addLoopToCurrent(); + } + + // include without the only attribute + elseif ($node instanceof IncludeNode && !$node->getAttribute('only')) { + $this->addLoopToAll(); + } + + // include function without the with_context=false parameter + elseif ($node instanceof FunctionExpression + && 'include' === $node->getAttribute('name') + && (!$node->getNode('arguments')->hasNode('with_context') + || false !== $node->getNode('arguments')->getNode('with_context')->getAttribute('value') + ) + ) { + $this->addLoopToAll(); + } + + // the loop variable is referenced via an attribute + elseif ($node instanceof GetAttrExpression + && (!$node->getNode('attribute') instanceof ConstantExpression + || 'parent' === $node->getNode('attribute')->getAttribute('value') + ) + && (true === $this->loops[0]->getAttribute('with_loop') + || ($node->getNode('node') instanceof NameExpression + && 'loop' === $node->getNode('node')->getAttribute('name') + ) + ) + ) { + $this->addLoopToAll(); + } + } + + /** + * Optimizes "for" tag by removing the "loop" variable creation whenever possible. + */ + private function leaveOptimizeFor(Node $node, Environment $env): void + { + if ($node instanceof ForNode) { + array_shift($this->loops); + array_shift($this->loopsTargets); + array_shift($this->loopsTargets); + } + } + + private function addLoopToCurrent(): void + { + $this->loops[0]->setAttribute('with_loop', true); + } + + private function addLoopToAll(): void + { + foreach ($this->loops as $loop) { + $loop->setAttribute('with_loop', true); + } + } + + public function getPriority(): int + { + return 255; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php new file mode 100644 index 0000000..90d6f2e --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SafeAnalysisNodeVisitor.php @@ -0,0 +1,160 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\Expression\BlockReferenceExpression; +use Twig\Node\Expression\ConditionalExpression; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\MethodCallExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\Expression\ParentExpression; +use Twig\Node\Node; + +/** + * @internal + */ +final class SafeAnalysisNodeVisitor implements NodeVisitorInterface +{ + private $data = []; + private $safeVars = []; + + public function setSafeVars(array $safeVars): void + { + $this->safeVars = $safeVars; + } + + public function getSafe(Node $node) + { + $hash = spl_object_hash($node); + if (!isset($this->data[$hash])) { + return; + } + + foreach ($this->data[$hash] as $bucket) { + if ($bucket['key'] !== $node) { + continue; + } + + if (\in_array('html_attr', $bucket['value'])) { + $bucket['value'][] = 'html'; + } + + return $bucket['value']; + } + } + + private function setSafe(Node $node, array $safe): void + { + $hash = spl_object_hash($node); + if (isset($this->data[$hash])) { + foreach ($this->data[$hash] as &$bucket) { + if ($bucket['key'] === $node) { + $bucket['value'] = $safe; + + return; + } + } + } + $this->data[$hash][] = [ + 'key' => $node, + 'value' => $safe, + ]; + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ConstantExpression) { + // constants are marked safe for all + $this->setSafe($node, ['all']); + } elseif ($node instanceof BlockReferenceExpression) { + // blocks are safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ParentExpression) { + // parent block is safe by definition + $this->setSafe($node, ['all']); + } elseif ($node instanceof ConditionalExpression) { + // intersect safeness of both operands + $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3'))); + $this->setSafe($node, $safe); + } elseif ($node instanceof FilterExpression) { + // filter expression is safe when the filter is safe + $name = $node->getNode('filter')->getAttribute('value'); + $args = $node->getNode('arguments'); + if ($filter = $env->getFilter($name)) { + $safe = $filter->getSafe($args); + if (null === $safe) { + $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety()); + } + $this->setSafe($node, $safe); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof FunctionExpression) { + // function expression is safe when the function is safe + $name = $node->getAttribute('name'); + $args = $node->getNode('arguments'); + if ($function = $env->getFunction($name)) { + $this->setSafe($node, $function->getSafe($args)); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof MethodCallExpression) { + if ($node->getAttribute('safe')) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } elseif ($node instanceof GetAttrExpression && $node->getNode('node') instanceof NameExpression) { + $name = $node->getNode('node')->getAttribute('name'); + if (\in_array($name, $this->safeVars)) { + $this->setSafe($node, ['all']); + } else { + $this->setSafe($node, []); + } + } else { + $this->setSafe($node, []); + } + + return $node; + } + + private function intersectSafe(array $a = null, array $b = null): array + { + if (null === $a || null === $b) { + return []; + } + + if (\in_array('all', $a)) { + return $b; + } + + if (\in_array('all', $b)) { + return $a; + } + + return array_intersect($a, $b); + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php new file mode 100644 index 0000000..1446cee --- /dev/null +++ b/vendor/twig/twig/src/NodeVisitor/SandboxNodeVisitor.php @@ -0,0 +1,136 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\NodeVisitor; + +use Twig\Environment; +use Twig\Node\CheckSecurityCallNode; +use Twig\Node\CheckSecurityNode; +use Twig\Node\CheckToStringNode; +use Twig\Node\Expression\Binary\ConcatBinary; +use Twig\Node\Expression\Binary\RangeBinary; +use Twig\Node\Expression\FilterExpression; +use Twig\Node\Expression\FunctionExpression; +use Twig\Node\Expression\GetAttrExpression; +use Twig\Node\Expression\NameExpression; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\PrintNode; +use Twig\Node\SetNode; + +/** + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +final class SandboxNodeVisitor implements NodeVisitorInterface +{ + private $inAModule = false; + private $tags; + private $filters; + private $functions; + private $needsToStringWrap = false; + + public function enterNode(Node $node, Environment $env): Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = true; + $this->tags = []; + $this->filters = []; + $this->functions = []; + + return $node; + } elseif ($this->inAModule) { + // look for tags + if ($node->getNodeTag() && !isset($this->tags[$node->getNodeTag()])) { + $this->tags[$node->getNodeTag()] = $node; + } + + // look for filters + if ($node instanceof FilterExpression && !isset($this->filters[$node->getNode('filter')->getAttribute('value')])) { + $this->filters[$node->getNode('filter')->getAttribute('value')] = $node; + } + + // look for functions + if ($node instanceof FunctionExpression && !isset($this->functions[$node->getAttribute('name')])) { + $this->functions[$node->getAttribute('name')] = $node; + } + + // the .. operator is equivalent to the range() function + if ($node instanceof RangeBinary && !isset($this->functions['range'])) { + $this->functions['range'] = $node; + } + + if ($node instanceof PrintNode) { + $this->needsToStringWrap = true; + $this->wrapNode($node, 'expr'); + } + + if ($node instanceof SetNode && !$node->getAttribute('capture')) { + $this->needsToStringWrap = true; + } + + // wrap outer nodes that can implicitly call __toString() + if ($this->needsToStringWrap) { + if ($node instanceof ConcatBinary) { + $this->wrapNode($node, 'left'); + $this->wrapNode($node, 'right'); + } + if ($node instanceof FilterExpression) { + $this->wrapNode($node, 'node'); + $this->wrapArrayNode($node, 'arguments'); + } + if ($node instanceof FunctionExpression) { + $this->wrapArrayNode($node, 'arguments'); + } + } + } + + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $this->inAModule = false; + + $node->setNode('constructor_end', new Node([new CheckSecurityCallNode(), $node->getNode('constructor_end')])); + $node->setNode('class_end', new Node([new CheckSecurityNode($this->filters, $this->tags, $this->functions), $node->getNode('class_end')])); + } elseif ($this->inAModule) { + if ($node instanceof PrintNode || $node instanceof SetNode) { + $this->needsToStringWrap = false; + } + } + + return $node; + } + + private function wrapNode(Node $node, string $name): void + { + $expr = $node->getNode($name); + if ($expr instanceof NameExpression || $expr instanceof GetAttrExpression) { + $node->setNode($name, new CheckToStringNode($expr)); + } + } + + private function wrapArrayNode(Node $node, string $name): void + { + $args = $node->getNode($name); + foreach ($args as $name => $_) { + $this->wrapNode($args, $name); + } + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/Parser.php b/vendor/twig/twig/src/Parser.php new file mode 100644 index 0000000..4428208 --- /dev/null +++ b/vendor/twig/twig/src/Parser.php @@ -0,0 +1,348 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Error\SyntaxError; +use Twig\Node\BlockNode; +use Twig\Node\BlockReferenceNode; +use Twig\Node\BodyNode; +use Twig\Node\Expression\AbstractExpression; +use Twig\Node\MacroNode; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\Node\NodeCaptureInterface; +use Twig\Node\NodeOutputInterface; +use Twig\Node\PrintNode; +use Twig\Node\TextNode; +use Twig\TokenParser\TokenParserInterface; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +class Parser +{ + private $stack = []; + private $stream; + private $parent; + private $visitors; + private $expressionParser; + private $blocks; + private $blockStack; + private $macros; + private $env; + private $importedSymbols; + private $traits; + private $embeddedTemplates = []; + private $varNameSalt = 0; + + public function __construct(Environment $env) + { + $this->env = $env; + } + + public function getVarName(): string + { + return sprintf('__internal_parse_%d', $this->varNameSalt++); + } + + public function parse(TokenStream $stream, $test = null, bool $dropNeedle = false): ModuleNode + { + $vars = get_object_vars($this); + unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser'], $vars['reservedMacroNames'], $vars['varNameSalt']); + $this->stack[] = $vars; + + // node visitors + if (null === $this->visitors) { + $this->visitors = $this->env->getNodeVisitors(); + } + + if (null === $this->expressionParser) { + $this->expressionParser = new ExpressionParser($this, $this->env); + } + + $this->stream = $stream; + $this->parent = null; + $this->blocks = []; + $this->macros = []; + $this->traits = []; + $this->blockStack = []; + $this->importedSymbols = [[]]; + $this->embeddedTemplates = []; + + try { + $body = $this->subparse($test, $dropNeedle); + + if (null !== $this->parent && null === $body = $this->filterBodyNodes($body)) { + $body = new Node(); + } + } catch (SyntaxError $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->stream->getSourceContext()); + } + + if (!$e->getTemplateLine()) { + $e->setTemplateLine($this->stream->getCurrent()->getLine()); + } + + throw $e; + } + + $node = new ModuleNode(new BodyNode([$body]), $this->parent, new Node($this->blocks), new Node($this->macros), new Node($this->traits), $this->embeddedTemplates, $stream->getSourceContext()); + + $traverser = new NodeTraverser($this->env, $this->visitors); + + $node = $traverser->traverse($node); + + // restore previous stack so previous parse() call can resume working + foreach (array_pop($this->stack) as $key => $val) { + $this->$key = $val; + } + + return $node; + } + + public function subparse($test, bool $dropNeedle = false): Node + { + $lineno = $this->getCurrentToken()->getLine(); + $rv = []; + while (!$this->stream->isEOF()) { + switch ($this->getCurrentToken()->getType()) { + case /* Token::TEXT_TYPE */ 0: + $token = $this->stream->next(); + $rv[] = new TextNode($token->getValue(), $token->getLine()); + break; + + case /* Token::VAR_START_TYPE */ 2: + $token = $this->stream->next(); + $expr = $this->expressionParser->parseExpression(); + $this->stream->expect(/* Token::VAR_END_TYPE */ 4); + $rv[] = new PrintNode($expr, $token->getLine()); + break; + + case /* Token::BLOCK_START_TYPE */ 1: + $this->stream->next(); + $token = $this->getCurrentToken(); + + if (/* Token::NAME_TYPE */ 5 !== $token->getType()) { + throw new SyntaxError('A block must start with a tag name.', $token->getLine(), $this->stream->getSourceContext()); + } + + if (null !== $test && $test($token)) { + if ($dropNeedle) { + $this->stream->next(); + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + if (!$subparser = $this->env->getTokenParser($token->getValue())) { + if (null !== $test) { + $e = new SyntaxError(sprintf('Unexpected "%s" tag', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + + if (\is_array($test) && isset($test[0]) && $test[0] instanceof TokenParserInterface) { + $e->appendMessage(sprintf(' (expecting closing tag for the "%s" tag defined near line %s).', $test[0]->getTag(), $lineno)); + } + } else { + $e = new SyntaxError(sprintf('Unknown "%s" tag.', $token->getValue()), $token->getLine(), $this->stream->getSourceContext()); + $e->addSuggestions($token->getValue(), array_keys($this->env->getTokenParsers())); + } + + throw $e; + } + + $this->stream->next(); + + $subparser->setParser($this); + $node = $subparser->parse($token); + if (null !== $node) { + $rv[] = $node; + } + break; + + default: + throw new SyntaxError('Lexer or parser ended up in unsupported state.', $this->getCurrentToken()->getLine(), $this->stream->getSourceContext()); + } + } + + if (1 === \count($rv)) { + return $rv[0]; + } + + return new Node($rv, [], $lineno); + } + + public function getBlockStack(): array + { + return $this->blockStack; + } + + public function peekBlockStack() + { + return $this->blockStack[\count($this->blockStack) - 1] ?? null; + } + + public function popBlockStack(): void + { + array_pop($this->blockStack); + } + + public function pushBlockStack($name): void + { + $this->blockStack[] = $name; + } + + public function hasBlock(string $name): bool + { + return isset($this->blocks[$name]); + } + + public function getBlock(string $name): Node + { + return $this->blocks[$name]; + } + + public function setBlock(string $name, BlockNode $value): void + { + $this->blocks[$name] = new BodyNode([$value], [], $value->getTemplateLine()); + } + + public function hasMacro(string $name): bool + { + return isset($this->macros[$name]); + } + + public function setMacro(string $name, MacroNode $node): void + { + $this->macros[$name] = $node; + } + + public function addTrait($trait): void + { + $this->traits[] = $trait; + } + + public function hasTraits(): bool + { + return \count($this->traits) > 0; + } + + public function embedTemplate(ModuleNode $template) + { + $template->setIndex(mt_rand()); + + $this->embeddedTemplates[] = $template; + } + + public function addImportedSymbol(string $type, string $alias, string $name = null, AbstractExpression $node = null): void + { + $this->importedSymbols[0][$type][$alias] = ['name' => $name, 'node' => $node]; + } + + public function getImportedSymbol(string $type, string $alias) + { + // if the symbol does not exist in the current scope (0), try in the main/global scope (last index) + return $this->importedSymbols[0][$type][$alias] ?? ($this->importedSymbols[\count($this->importedSymbols) - 1][$type][$alias] ?? null); + } + + public function isMainScope(): bool + { + return 1 === \count($this->importedSymbols); + } + + public function pushLocalScope(): void + { + array_unshift($this->importedSymbols, []); + } + + public function popLocalScope(): void + { + array_shift($this->importedSymbols); + } + + public function getExpressionParser(): ExpressionParser + { + return $this->expressionParser; + } + + public function getParent(): ?Node + { + return $this->parent; + } + + public function setParent(?Node $parent): void + { + $this->parent = $parent; + } + + public function getStream(): TokenStream + { + return $this->stream; + } + + public function getCurrentToken(): Token + { + return $this->stream->getCurrent(); + } + + private function filterBodyNodes(Node $node, bool $nested = false): ?Node + { + // check that the body does not contain non-empty output nodes + if ( + ($node instanceof TextNode && !ctype_space($node->getAttribute('data'))) + || + (!$node instanceof TextNode && !$node instanceof BlockReferenceNode && $node instanceof NodeOutputInterface) + ) { + if (false !== strpos((string) $node, \chr(0xEF).\chr(0xBB).\chr(0xBF))) { + $t = substr($node->getAttribute('data'), 3); + if ('' === $t || ctype_space($t)) { + // bypass empty nodes starting with a BOM + return null; + } + } + + throw new SyntaxError('A template that extends another one cannot include content outside Twig blocks. Did you forget to put the content inside a {% block %} tag?', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + // bypass nodes that "capture" the output + if ($node instanceof NodeCaptureInterface) { + // a "block" tag in such a node will serve as a block definition AND be displayed in place as well + return $node; + } + + // "block" tags that are not captured (see above) are only used for defining + // the content of the block. In such a case, nesting it does not work as + // expected as the definition is not part of the default template code flow. + if ($nested && $node instanceof BlockReferenceNode) { + throw new SyntaxError('A block definition cannot be nested under non-capturing nodes.', $node->getTemplateLine(), $this->stream->getSourceContext()); + } + + if ($node instanceof NodeOutputInterface) { + return null; + } + + // here, $nested means "being at the root level of a child template" + // we need to discard the wrapping "Node" for the "body" node + $nested = $nested || Node::class !== \get_class($node); + foreach ($node as $k => $n) { + if (null !== $n && null === $this->filterBodyNodes($n, $nested)) { + $node->removeNode($k); + } + } + + return $node; + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php new file mode 100644 index 0000000..4da43e4 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BaseDumper.php @@ -0,0 +1,63 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Dumper; + +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class BaseDumper +{ + private $root; + + public function dump(Profile $profile): string + { + return $this->dumpProfile($profile); + } + + abstract protected function formatTemplate(Profile $profile, $prefix): string; + + abstract protected function formatNonTemplate(Profile $profile, $prefix): string; + + abstract protected function formatTime(Profile $profile, $percent): string; + + private function dumpProfile(Profile $profile, $prefix = '', $sibling = false): string + { + if ($profile->isRoot()) { + $this->root = $profile->getDuration(); + $start = $profile->getName(); + } else { + if ($profile->isTemplate()) { + $start = $this->formatTemplate($profile, $prefix); + } else { + $start = $this->formatNonTemplate($profile, $prefix); + } + $prefix .= $sibling ? '│ ' : ' '; + } + + $percent = $this->root ? $profile->getDuration() / $this->root * 100 : 0; + + if ($profile->getDuration() * 1000 < 1) { + $str = $start."\n"; + } else { + $str = sprintf("%s %s\n", $start, $this->formatTime($profile, $percent)); + } + + $nCount = \count($profile->getProfiles()); + foreach ($profile as $i => $p) { + $str .= $this->dumpProfile($p, $prefix, $i + 1 !== $nCount); + } + + return $str; + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php new file mode 100644 index 0000000..03abe0f --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/BlackfireDumper.php @@ -0,0 +1,72 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Dumper; + +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class BlackfireDumper +{ + public function dump(Profile $profile): string + { + $data = []; + $this->dumpProfile('main()', $profile, $data); + $this->dumpChildren('main()', $profile, $data); + + $start = sprintf('%f', microtime(true)); + $str = <<<EOF +file-format: BlackfireProbe +cost-dimensions: wt mu pmu +request-start: $start + + +EOF; + + foreach ($data as $name => $values) { + $str .= "$name//{$values['ct']} {$values['wt']} {$values['mu']} {$values['pmu']}\n"; + } + + return $str; + } + + private function dumpChildren(string $parent, Profile $profile, &$data) + { + foreach ($profile as $p) { + if ($p->isTemplate()) { + $name = $p->getTemplate(); + } else { + $name = sprintf('%s::%s(%s)', $p->getTemplate(), $p->getType(), $p->getName()); + } + $this->dumpProfile(sprintf('%s==>%s', $parent, $name), $p, $data); + $this->dumpChildren($name, $p, $data); + } + } + + private function dumpProfile(string $edge, Profile $profile, &$data) + { + if (isset($data[$edge])) { + ++$data[$edge]['ct']; + $data[$edge]['wt'] += floor($profile->getDuration() * 1000000); + $data[$edge]['mu'] += $profile->getMemoryUsage(); + $data[$edge]['pmu'] += $profile->getPeakMemoryUsage(); + } else { + $data[$edge] = [ + 'ct' => 1, + 'wt' => floor($profile->getDuration() * 1000000), + 'mu' => $profile->getMemoryUsage(), + 'pmu' => $profile->getPeakMemoryUsage(), + ]; + } + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php new file mode 100644 index 0000000..1f2433b --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/HtmlDumper.php @@ -0,0 +1,47 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Dumper; + +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class HtmlDumper extends BaseDumper +{ + private static $colors = [ + 'block' => '#dfd', + 'macro' => '#ddf', + 'template' => '#ffd', + 'big' => '#d44', + ]; + + public function dump(Profile $profile): string + { + return '<pre>'.parent::dump($profile).'</pre>'; + } + + protected function formatTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ <span style="background-color: %s">%s</span>', $prefix, self::$colors['template'], $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s::%s(<span style="background-color: %s">%s</span>)', $prefix, $profile->getTemplate(), $profile->getType(), isset(self::$colors[$profile->getType()]) ? self::$colors[$profile->getType()] : 'auto', $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return sprintf('<span style="color: %s">%.2fms/%.0f%%</span>', $percent > 20 ? self::$colors['big'] : 'auto', $profile->getDuration() * 1000, $percent); + } +} diff --git a/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php new file mode 100644 index 0000000..31561c4 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Dumper/TextDumper.php @@ -0,0 +1,35 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Dumper; + +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class TextDumper extends BaseDumper +{ + protected function formatTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s', $prefix, $profile->getTemplate()); + } + + protected function formatNonTemplate(Profile $profile, $prefix): string + { + return sprintf('%s└ %s::%s(%s)', $prefix, $profile->getTemplate(), $profile->getType(), $profile->getName()); + } + + protected function formatTime(Profile $profile, $percent): string + { + return sprintf('%.2fms/%.0f%%', $profile->getDuration() * 1000, $percent); + } +} diff --git a/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php new file mode 100644 index 0000000..1494baf --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/EnterProfileNode.php @@ -0,0 +1,42 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Node; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * Represents a profile enter node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class EnterProfileNode extends Node +{ + public function __construct(string $extensionName, string $type, string $name, string $varName) + { + parent::__construct([], ['extension_name' => $extensionName, 'name' => $name, 'type' => $type, 'var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write(sprintf('$%s = $this->extensions[', $this->getAttribute('var_name'))) + ->repr($this->getAttribute('extension_name')) + ->raw("];\n") + ->write(sprintf('$%s->enter($%s = new \Twig\Profiler\Profile($this->getTemplateName(), ', $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ->repr($this->getAttribute('type')) + ->raw(', ') + ->repr($this->getAttribute('name')) + ->raw("));\n\n") + ; + } +} diff --git a/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php new file mode 100644 index 0000000..94cebba --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Node/LeaveProfileNode.php @@ -0,0 +1,36 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\Node; + +use Twig\Compiler; +use Twig\Node\Node; + +/** + * Represents a profile leave node. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class LeaveProfileNode extends Node +{ + public function __construct(string $varName) + { + parent::__construct([], ['var_name' => $varName]); + } + + public function compile(Compiler $compiler): void + { + $compiler + ->write("\n") + ->write(sprintf("\$%s->leave(\$%s);\n\n", $this->getAttribute('var_name'), $this->getAttribute('var_name').'_prof')) + ; + } +} diff --git a/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php new file mode 100644 index 0000000..91abee8 --- /dev/null +++ b/vendor/twig/twig/src/Profiler/NodeVisitor/ProfilerNodeVisitor.php @@ -0,0 +1,70 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler\NodeVisitor; + +use Twig\Environment; +use Twig\Node\BlockNode; +use Twig\Node\BodyNode; +use Twig\Node\MacroNode; +use Twig\Node\ModuleNode; +use Twig\Node\Node; +use Twig\NodeVisitor\NodeVisitorInterface; +use Twig\Profiler\Node\EnterProfileNode; +use Twig\Profiler\Node\LeaveProfileNode; +use Twig\Profiler\Profile; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class ProfilerNodeVisitor implements NodeVisitorInterface +{ + private $extensionName; + private $varName; + + public function __construct(string $extensionName) + { + $this->extensionName = $extensionName; + $this->varName = sprintf('__internal_%s', hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', $extensionName)); + } + + public function enterNode(Node $node, Environment $env): Node + { + return $node; + } + + public function leaveNode(Node $node, Environment $env): ?Node + { + if ($node instanceof ModuleNode) { + $node->setNode('display_start', new Node([new EnterProfileNode($this->extensionName, Profile::TEMPLATE, $node->getTemplateName(), $this->varName), $node->getNode('display_start')])); + $node->setNode('display_end', new Node([new LeaveProfileNode($this->varName), $node->getNode('display_end')])); + } elseif ($node instanceof BlockNode) { + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::BLOCK, $node->getAttribute('name'), $this->varName), + $node->getNode('body'), + new LeaveProfileNode($this->varName), + ])); + } elseif ($node instanceof MacroNode) { + $node->setNode('body', new BodyNode([ + new EnterProfileNode($this->extensionName, Profile::MACRO, $node->getAttribute('name'), $this->varName), + $node->getNode('body'), + new LeaveProfileNode($this->varName), + ])); + } + + return $node; + } + + public function getPriority(): int + { + return 0; + } +} diff --git a/vendor/twig/twig/src/Profiler/Profile.php b/vendor/twig/twig/src/Profiler/Profile.php new file mode 100644 index 0000000..252ca9b --- /dev/null +++ b/vendor/twig/twig/src/Profiler/Profile.php @@ -0,0 +1,181 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Profiler; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class Profile implements \IteratorAggregate, \Serializable +{ + public const ROOT = 'ROOT'; + public const BLOCK = 'block'; + public const TEMPLATE = 'template'; + public const MACRO = 'macro'; + + private $template; + private $name; + private $type; + private $starts = []; + private $ends = []; + private $profiles = []; + + public function __construct(string $template = 'main', string $type = self::ROOT, string $name = 'main') + { + $this->template = $template; + $this->type = $type; + $this->name = 0 === strpos($name, '__internal_') ? 'INTERNAL' : $name; + $this->enter(); + } + + public function getTemplate(): string + { + return $this->template; + } + + public function getType(): string + { + return $this->type; + } + + public function getName(): string + { + return $this->name; + } + + public function isRoot(): bool + { + return self::ROOT === $this->type; + } + + public function isTemplate(): bool + { + return self::TEMPLATE === $this->type; + } + + public function isBlock(): bool + { + return self::BLOCK === $this->type; + } + + public function isMacro(): bool + { + return self::MACRO === $this->type; + } + + /** + * @return Profile[] + */ + public function getProfiles(): array + { + return $this->profiles; + } + + public function addProfile(self $profile): void + { + $this->profiles[] = $profile; + } + + /** + * Returns the duration in microseconds. + */ + public function getDuration(): float + { + if ($this->isRoot() && $this->profiles) { + // for the root node with children, duration is the sum of all child durations + $duration = 0; + foreach ($this->profiles as $profile) { + $duration += $profile->getDuration(); + } + + return $duration; + } + + return isset($this->ends['wt']) && isset($this->starts['wt']) ? $this->ends['wt'] - $this->starts['wt'] : 0; + } + + /** + * Returns the memory usage in bytes. + */ + public function getMemoryUsage(): int + { + return isset($this->ends['mu']) && isset($this->starts['mu']) ? $this->ends['mu'] - $this->starts['mu'] : 0; + } + + /** + * Returns the peak memory usage in bytes. + */ + public function getPeakMemoryUsage(): int + { + return isset($this->ends['pmu']) && isset($this->starts['pmu']) ? $this->ends['pmu'] - $this->starts['pmu'] : 0; + } + + /** + * Starts the profiling. + */ + public function enter(): void + { + $this->starts = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + /** + * Stops the profiling. + */ + public function leave(): void + { + $this->ends = [ + 'wt' => microtime(true), + 'mu' => memory_get_usage(), + 'pmu' => memory_get_peak_usage(), + ]; + } + + public function reset(): void + { + $this->starts = $this->ends = $this->profiles = []; + $this->enter(); + } + + public function getIterator(): \Traversable + { + return new \ArrayIterator($this->profiles); + } + + public function serialize(): string + { + return serialize($this->__serialize()); + } + + public function unserialize($data): void + { + $this->__unserialize(unserialize($data)); + } + + /** + * @internal + */ + public function __serialize(): array + { + return [$this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles]; + } + + /** + * @internal + */ + public function __unserialize(array $data): void + { + list($this->template, $this->name, $this->type, $this->starts, $this->ends, $this->profiles) = $data; + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php new file mode 100644 index 0000000..b360d7b --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/ContainerRuntimeLoader.php @@ -0,0 +1,37 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\RuntimeLoader; + +use Psr\Container\ContainerInterface; + +/** + * Lazily loads Twig runtime implementations from a PSR-11 container. + * + * Note that the runtime services MUST use their class names as identifiers. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class ContainerRuntimeLoader implements RuntimeLoaderInterface +{ + private $container; + + public function __construct(ContainerInterface $container) + { + $this->container = $container; + } + + public function load(string $class) + { + return $this->container->has($class) ? $this->container->get($class) : null; + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php new file mode 100644 index 0000000..1306483 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/FactoryRuntimeLoader.php @@ -0,0 +1,41 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\RuntimeLoader; + +/** + * Lazy loads the runtime implementations for a Twig element. + * + * @author Robin Chalas <robin.chalas@gmail.com> + */ +class FactoryRuntimeLoader implements RuntimeLoaderInterface +{ + private $map; + + /** + * @param array $map An array where keys are class names and values factory callables + */ + public function __construct(array $map = []) + { + $this->map = $map; + } + + public function load(string $class) + { + if (!isset($this->map[$class])) { + return null; + } + + $runtimeFactory = $this->map[$class]; + + return $runtimeFactory(); + } +} diff --git a/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php new file mode 100644 index 0000000..9e5b204 --- /dev/null +++ b/vendor/twig/twig/src/RuntimeLoader/RuntimeLoaderInterface.php @@ -0,0 +1,27 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\RuntimeLoader; + +/** + * Creates runtime implementations for Twig elements (filters/functions/tests). + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface RuntimeLoaderInterface +{ + /** + * Creates the runtime implementation of a Twig element (filter/function/test). + * + * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class + */ + public function load(string $class); +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityError.php b/vendor/twig/twig/src/Sandbox/SecurityError.php new file mode 100644 index 0000000..30a404f --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityError.php @@ -0,0 +1,23 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +use Twig\Error\Error; + +/** + * Exception thrown when a security error occurs at runtime. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +class SecurityError extends Error +{ +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php new file mode 100644 index 0000000..02d3063 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFilterError.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Exception thrown when a not allowed filter is used in a template. + * + * @author Martin Hasoň <martin.hason@gmail.com> + */ +final class SecurityNotAllowedFilterError extends SecurityError +{ + private $filterName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->filterName = $functionName; + } + + public function getFilterName(): string + { + return $this->filterName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php new file mode 100644 index 0000000..4f76dc6 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedFunctionError.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Exception thrown when a not allowed function is used in a template. + * + * @author Martin Hasoň <martin.hason@gmail.com> + */ +final class SecurityNotAllowedFunctionError extends SecurityError +{ + private $functionName; + + public function __construct(string $message, string $functionName) + { + parent::__construct($message); + $this->functionName = $functionName; + } + + public function getFunctionName(): string + { + return $this->functionName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php new file mode 100644 index 0000000..8df9d0b --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedMethodError.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Exception thrown when a not allowed class method is used in a template. + * + * @author Kit Burton-Senior <mail@kitbs.com> + */ +final class SecurityNotAllowedMethodError extends SecurityError +{ + private $className; + private $methodName; + + public function __construct(string $message, string $className, string $methodName) + { + parent::__construct($message); + $this->className = $className; + $this->methodName = $methodName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getMethodName() + { + return $this->methodName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php new file mode 100644 index 0000000..42ec4f3 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedPropertyError.php @@ -0,0 +1,40 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Exception thrown when a not allowed class property is used in a template. + * + * @author Kit Burton-Senior <mail@kitbs.com> + */ +final class SecurityNotAllowedPropertyError extends SecurityError +{ + private $className; + private $propertyName; + + public function __construct(string $message, string $className, string $propertyName) + { + parent::__construct($message); + $this->className = $className; + $this->propertyName = $propertyName; + } + + public function getClassName(): string + { + return $this->className; + } + + public function getPropertyName() + { + return $this->propertyName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php new file mode 100644 index 0000000..4522150 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityNotAllowedTagError.php @@ -0,0 +1,33 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Exception thrown when a not allowed tag is used in a template. + * + * @author Martin Hasoň <martin.hason@gmail.com> + */ +final class SecurityNotAllowedTagError extends SecurityError +{ + private $tagName; + + public function __construct(string $message, string $tagName) + { + parent::__construct($message); + $this->tagName = $tagName; + } + + public function getTagName(): string + { + return $this->tagName; + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicy.php b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php new file mode 100644 index 0000000..2fc0d01 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicy.php @@ -0,0 +1,126 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +use Twig\Markup; +use Twig\Template; + +/** + * Represents a security policy which need to be enforced when sandbox mode is enabled. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class SecurityPolicy implements SecurityPolicyInterface +{ + private $allowedTags; + private $allowedFilters; + private $allowedMethods; + private $allowedProperties; + private $allowedFunctions; + + public function __construct(array $allowedTags = [], array $allowedFilters = [], array $allowedMethods = [], array $allowedProperties = [], array $allowedFunctions = []) + { + $this->allowedTags = $allowedTags; + $this->allowedFilters = $allowedFilters; + $this->setAllowedMethods($allowedMethods); + $this->allowedProperties = $allowedProperties; + $this->allowedFunctions = $allowedFunctions; + } + + public function setAllowedTags(array $tags): void + { + $this->allowedTags = $tags; + } + + public function setAllowedFilters(array $filters): void + { + $this->allowedFilters = $filters; + } + + public function setAllowedMethods(array $methods): void + { + $this->allowedMethods = []; + foreach ($methods as $class => $m) { + $this->allowedMethods[$class] = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, \is_array($m) ? $m : [$m]); + } + } + + public function setAllowedProperties(array $properties): void + { + $this->allowedProperties = $properties; + } + + public function setAllowedFunctions(array $functions): void + { + $this->allowedFunctions = $functions; + } + + public function checkSecurity($tags, $filters, $functions): void + { + foreach ($tags as $tag) { + if (!\in_array($tag, $this->allowedTags)) { + throw new SecurityNotAllowedTagError(sprintf('Tag "%s" is not allowed.', $tag), $tag); + } + } + + foreach ($filters as $filter) { + if (!\in_array($filter, $this->allowedFilters)) { + throw new SecurityNotAllowedFilterError(sprintf('Filter "%s" is not allowed.', $filter), $filter); + } + } + + foreach ($functions as $function) { + if (!\in_array($function, $this->allowedFunctions)) { + throw new SecurityNotAllowedFunctionError(sprintf('Function "%s" is not allowed.', $function), $function); + } + } + } + + public function checkMethodAllowed($obj, $method): void + { + if ($obj instanceof Template || $obj instanceof Markup) { + return; + } + + $allowed = false; + $method = strtr($method, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); + foreach ($this->allowedMethods as $class => $methods) { + if ($obj instanceof $class) { + $allowed = \in_array($method, $methods); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedMethodError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, $class), $class, $method); + } + } + + public function checkPropertyAllowed($obj, $property): void + { + $allowed = false; + foreach ($this->allowedProperties as $class => $properties) { + if ($obj instanceof $class) { + $allowed = \in_array($property, \is_array($properties) ? $properties : [$properties]); + + break; + } + } + + if (!$allowed) { + $class = \get_class($obj); + throw new SecurityNotAllowedPropertyError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, $class), $class, $property); + } + } +} diff --git a/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php new file mode 100644 index 0000000..36471c5 --- /dev/null +++ b/vendor/twig/twig/src/Sandbox/SecurityPolicyInterface.php @@ -0,0 +1,45 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Sandbox; + +/** + * Interface that all security policy classes must implements. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +interface SecurityPolicyInterface +{ + /** + * @param string[] $tags + * @param string[] $filters + * @param string[] $functions + * + * @throws SecurityError + */ + public function checkSecurity($tags, $filters, $functions): void; + + /** + * @param object $obj + * @param string $method + * + * @throws SecurityNotAllowedMethodError + */ + public function checkMethodAllowed($obj, $method): void; + + /** + * @param object $obj + * @param string $property + * + * @throws SecurityNotAllowedPropertyError + */ + public function checkPropertyAllowed($obj, $property): void; +} diff --git a/vendor/twig/twig/src/Source.php b/vendor/twig/twig/src/Source.php new file mode 100644 index 0000000..3cb0240 --- /dev/null +++ b/vendor/twig/twig/src/Source.php @@ -0,0 +1,51 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +/** + * Holds information about a non-compiled Twig template. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class Source +{ + private $code; + private $name; + private $path; + + /** + * @param string $code The template source code + * @param string $name The template logical name + * @param string $path The filesystem path of the template if any + */ + public function __construct(string $code, string $name, string $path = '') + { + $this->code = $code; + $this->name = $name; + $this->path = $path; + } + + public function getCode(): string + { + return $this->code; + } + + public function getName(): string + { + return $this->name; + } + + public function getPath(): string + { + return $this->path; + } +} diff --git a/vendor/twig/twig/src/Template.php b/vendor/twig/twig/src/Template.php new file mode 100644 index 0000000..e04bd04 --- /dev/null +++ b/vendor/twig/twig/src/Template.php @@ -0,0 +1,422 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +use Twig\Error\Error; +use Twig\Error\LoaderError; +use Twig\Error\RuntimeError; + +/** + * Default base class for compiled templates. + * + * This class is an implementation detail of how template compilation currently + * works, which might change. It should never be used directly. Use $twig->load() + * instead, which returns an instance of \Twig\TemplateWrapper. + * + * @author Fabien Potencier <fabien@symfony.com> + * + * @internal + */ +abstract class Template +{ + public const ANY_CALL = 'any'; + public const ARRAY_CALL = 'array'; + public const METHOD_CALL = 'method'; + + protected $parent; + protected $parents = []; + protected $env; + protected $blocks = []; + protected $traits = []; + protected $extensions = []; + protected $sandbox; + + public function __construct(Environment $env) + { + $this->env = $env; + $this->extensions = $env->getExtensions(); + } + + /** + * Returns the template name. + * + * @return string The template name + */ + abstract public function getTemplateName(); + + /** + * Returns debug information about the template. + * + * @return array Debug information + */ + abstract public function getDebugInfo(); + + /** + * Returns information about the original template source code. + * + * @return Source + */ + abstract public function getSourceContext(); + + /** + * Returns the parent template. + * + * This method is for internal use only and should never be called + * directly. + * + * @return Template|TemplateWrapper|false The parent template or false if there is no parent + */ + public function getParent(array $context) + { + if (null !== $this->parent) { + return $this->parent; + } + + try { + $parent = $this->doGetParent($context); + + if (false === $parent) { + return false; + } + + if ($parent instanceof self || $parent instanceof TemplateWrapper) { + return $this->parents[$parent->getSourceContext()->getName()] = $parent; + } + + if (!isset($this->parents[$parent])) { + $this->parents[$parent] = $this->loadTemplate($parent); + } + } catch (LoaderError $e) { + $e->setSourceContext(null); + $e->guess(); + + throw $e; + } + + return $this->parents[$parent]; + } + + protected function doGetParent(array $context) + { + return false; + } + + public function isTraitable() + { + return true; + } + + /** + * Displays a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + */ + public function displayParentBlock($name, array $context, array $blocks = []) + { + if (isset($this->traits[$name])) { + $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, $blocks, false); + } else { + throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); + } + } + + /** + * Displays a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to display + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + */ + public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null) + { + if ($useBlocks && isset($blocks[$name])) { + $template = $blocks[$name][0]; + $block = $blocks[$name][1]; + } elseif (isset($this->blocks[$name])) { + $template = $this->blocks[$name][0]; + $block = $this->blocks[$name][1]; + } else { + $template = null; + $block = null; + } + + // avoid RCEs when sandbox is enabled + if (null !== $template && !$template instanceof self) { + throw new \LogicException('A block must be a method on a \Twig\Template instance.'); + } + + if (null !== $template) { + try { + $template->$block($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($template->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } elseif (false !== $parent = $this->getParent($context)) { + $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this); + } elseif (isset($blocks[$name])) { + throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext()); + } else { + throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext()); + } + } + + /** + * Renders a parent block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render from the parent + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return string The rendered block + */ + public function renderParentBlock($name, array $context, array $blocks = []) + { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayParentBlock($name, $context, $blocks); + + return ob_get_clean(); + } + + /** + * Renders a block. + * + * This method is for internal use only and should never be called + * directly. + * + * @param string $name The block name to render + * @param array $context The context + * @param array $blocks The current set of blocks + * @param bool $useBlocks Whether to use the current set of blocks + * + * @return string The rendered block + */ + public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true) + { + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + $this->displayBlock($name, $context, $blocks, $useBlocks); + + return ob_get_clean(); + } + + /** + * Returns whether a block exists or not in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param string $name The block name + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return bool true if the block exists, false otherwise + */ + public function hasBlock($name, array $context, array $blocks = []) + { + if (isset($blocks[$name])) { + return $blocks[$name][0] instanceof self; + } + + if (isset($this->blocks[$name])) { + return true; + } + + if (false !== $parent = $this->getParent($context)) { + return $parent->hasBlock($name, $context); + } + + return false; + } + + /** + * Returns all block names in the current context of the template. + * + * This method checks blocks defined in the current template + * or defined in "used" traits or defined in parent templates. + * + * @param array $context The context + * @param array $blocks The current set of blocks + * + * @return array An array of block names + */ + public function getBlockNames(array $context, array $blocks = []) + { + $names = array_merge(array_keys($blocks), array_keys($this->blocks)); + + if (false !== $parent = $this->getParent($context)) { + $names = array_merge($names, $parent->getBlockNames($context)); + } + + return array_unique($names); + } + + /** + * @return Template|TemplateWrapper + */ + protected function loadTemplate($template, $templateName = null, $line = null, $index = null) + { + try { + if (\is_array($template)) { + return $this->env->resolveTemplate($template); + } + + if ($template instanceof self || $template instanceof TemplateWrapper) { + return $template; + } + + if ($template === $this->getTemplateName()) { + $class = static::class; + if (false !== $pos = strrpos($class, '___', -1)) { + $class = substr($class, 0, $pos); + } + } else { + $class = $this->env->getTemplateClass($template); + } + + return $this->env->loadTemplate($class, $template, $index); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); + } + + if ($e->getTemplateLine() > 0) { + throw $e; + } + + if (!$line) { + $e->guess(); + } else { + $e->setTemplateLine($line); + } + + throw $e; + } + } + + /** + * @internal + * + * @return Template + */ + public function unwrap() + { + return $this; + } + + /** + * Returns all blocks. + * + * This method is for internal use only and should never be called + * directly. + * + * @return array An array of blocks + */ + public function getBlocks() + { + return $this->blocks; + } + + public function display(array $context, array $blocks = []) + { + $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); + } + + public function render(array $context) + { + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->display($context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + protected function displayWithErrorHandling(array $context, array $blocks = []) + { + try { + $this->doDisplay($context, $blocks); + } catch (Error $e) { + if (!$e->getSourceContext()) { + $e->setSourceContext($this->getSourceContext()); + } + + // this is mostly useful for \Twig\Error\LoaderError exceptions + // see \Twig\Error\LoaderError + if (-1 === $e->getTemplateLine()) { + $e->guess(); + } + + throw $e; + } catch (\Exception $e) { + $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); + $e->guess(); + + throw $e; + } + } + + /** + * Auto-generated method to display the template with the given context. + * + * @param array $context An array of parameters to pass to the template + * @param array $blocks An array of blocks to pass to the template + */ + abstract protected function doDisplay(array $context, array $blocks = []); +} diff --git a/vendor/twig/twig/src/TemplateWrapper.php b/vendor/twig/twig/src/TemplateWrapper.php new file mode 100644 index 0000000..c9c6b07 --- /dev/null +++ b/vendor/twig/twig/src/TemplateWrapper.php @@ -0,0 +1,109 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +/** + * Exposes a template to userland. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +final class TemplateWrapper +{ + private $env; + private $template; + + /** + * This method is for internal use only and should never be called + * directly (use Twig\Environment::load() instead). + * + * @internal + */ + public function __construct(Environment $env, Template $template) + { + $this->env = $env; + $this->template = $template; + } + + public function render(array $context = []): string + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + return $this->template->render($context, \func_get_args()[1] ?? []); + } + + public function display(array $context = []) + { + // using func_get_args() allows to not expose the blocks argument + // as it should only be used by internal code + $this->template->display($context, \func_get_args()[1] ?? []); + } + + public function hasBlock(string $name, array $context = []): bool + { + return $this->template->hasBlock($name, $context); + } + + /** + * @return string[] An array of defined template block names + */ + public function getBlockNames(array $context = []): array + { + return $this->template->getBlockNames($context); + } + + public function renderBlock(string $name, array $context = []): string + { + $context = $this->env->mergeGlobals($context); + $level = ob_get_level(); + if ($this->env->isDebug()) { + ob_start(); + } else { + ob_start(function () { return ''; }); + } + try { + $this->template->displayBlock($name, $context); + } catch (\Throwable $e) { + while (ob_get_level() > $level) { + ob_end_clean(); + } + + throw $e; + } + + return ob_get_clean(); + } + + public function displayBlock(string $name, array $context = []) + { + $this->template->displayBlock($name, $this->env->mergeGlobals($context)); + } + + public function getSourceContext(): Source + { + return $this->template->getSourceContext(); + } + + public function getTemplateName(): string + { + return $this->template->getTemplateName(); + } + + /** + * @internal + * + * @return Template + */ + public function unwrap() + { + return $this->template; + } +} diff --git a/vendor/twig/twig/src/Test/IntegrationTestCase.php b/vendor/twig/twig/src/Test/IntegrationTestCase.php new file mode 100644 index 0000000..307302b --- /dev/null +++ b/vendor/twig/twig/src/Test/IntegrationTestCase.php @@ -0,0 +1,265 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Test; + +use PHPUnit\Framework\TestCase; +use Twig\Environment; +use Twig\Error\Error; +use Twig\Extension\ExtensionInterface; +use Twig\Loader\ArrayLoader; +use Twig\RuntimeLoader\RuntimeLoaderInterface; +use Twig\TwigFilter; +use Twig\TwigFunction; +use Twig\TwigTest; + +/** + * Integration test helper. + * + * @author Fabien Potencier <fabien@symfony.com> + * @author Karma Dordrak <drak@zikula.org> + */ +abstract class IntegrationTestCase extends TestCase +{ + /** + * @return string + */ + abstract protected function getFixturesDir(); + + /** + * @return RuntimeLoaderInterface[] + */ + protected function getRuntimeLoaders() + { + return []; + } + + /** + * @return ExtensionInterface[] + */ + protected function getExtensions() + { + return []; + } + + /** + * @return TwigFilter[] + */ + protected function getTwigFilters() + { + return []; + } + + /** + * @return TwigFunction[] + */ + protected function getTwigFunctions() + { + return []; + } + + /** + * @return TwigTest[] + */ + protected function getTwigTests() + { + return []; + } + + /** + * @dataProvider getTests + */ + public function testIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + /** + * @dataProvider getLegacyTests + * @group legacy + */ + public function testLegacyIntegration($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation); + } + + public function getTests($name, $legacyTests = false) + { + $fixturesDir = realpath($this->getFixturesDir()); + $tests = []; + + foreach (new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($fixturesDir), \RecursiveIteratorIterator::LEAVES_ONLY) as $file) { + if (!preg_match('/\.test$/', $file)) { + continue; + } + + if ($legacyTests xor false !== strpos($file->getRealpath(), '.legacy.test')) { + continue; + } + + $test = file_get_contents($file->getRealpath()); + + if (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = $match[6]; + $outputs = [[null, $match[5], null, '']]; + } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*(?:--DEPRECATION--\s*(.*?))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) { + $message = $match[1]; + $condition = $match[2]; + $deprecation = $match[3]; + $templates = self::parseTemplates($match[4]); + $exception = false; + preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, \PREG_SET_ORDER); + } else { + throw new \InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file))); + } + + $tests[] = [str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs, $deprecation]; + } + + if ($legacyTests && empty($tests)) { + // add a dummy test to avoid a PHPUnit message + return [['not', '-', '', [], '', []]]; + } + + return $tests; + } + + public function getLegacyTests() + { + return $this->getTests('testLegacyIntegration', true); + } + + protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs, $deprecation = '') + { + if (!$outputs) { + $this->markTestSkipped('no tests to run'); + } + + if ($condition) { + eval('$ret = '.$condition.';'); + if (!$ret) { + $this->markTestSkipped($condition); + } + } + + $loader = new ArrayLoader($templates); + + foreach ($outputs as $i => $match) { + $config = array_merge([ + 'cache' => false, + 'strict_variables' => true, + ], $match[2] ? eval($match[2].';') : []); + $twig = new Environment($loader, $config); + $twig->addGlobal('global', 'global'); + foreach ($this->getRuntimeLoaders() as $runtimeLoader) { + $twig->addRuntimeLoader($runtimeLoader); + } + + foreach ($this->getExtensions() as $extension) { + $twig->addExtension($extension); + } + + foreach ($this->getTwigFilters() as $filter) { + $twig->addFilter($filter); + } + + foreach ($this->getTwigTests() as $test) { + $twig->addTest($test); + } + + foreach ($this->getTwigFunctions() as $function) { + $twig->addFunction($function); + } + + // avoid using the same PHP class name for different cases + $p = new \ReflectionProperty($twig, 'templateClassPrefix'); + $p->setAccessible(true); + $p->setValue($twig, '__TwigTemplate_'.hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', uniqid(mt_rand(), true), false).'_'); + + $deprecations = []; + try { + $prevHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) use (&$deprecations, &$prevHandler) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + + return true; + } + + return $prevHandler ? $prevHandler($type, $msg, $file, $line, $context) : false; + }); + + $template = $twig->load('index.twig'); + } catch (\Exception $e) { + if (false !== $exception) { + $message = $e->getMessage(); + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $message))); + $last = substr($message, \strlen($message) - 1); + $this->assertTrue('.' === $last || '?' === $last, 'Exception message must end with a dot or a question mark.'); + + return; + } + + throw new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + } finally { + restore_error_handler(); + } + + $this->assertSame($deprecation, implode("\n", $deprecations)); + + try { + $output = trim($template->render(eval($match[1].';')), "\n "); + } catch (\Exception $e) { + if (false !== $exception) { + $this->assertSame(trim($exception), trim(sprintf('%s: %s', \get_class($e), $e->getMessage()))); + + return; + } + + $e = new Error(sprintf('%s: %s', \get_class($e), $e->getMessage()), -1, null, $e); + + $output = trim(sprintf('%s: %s', \get_class($e), $e->getMessage())); + } + + if (false !== $exception) { + list($class) = explode(':', $exception); + $constraintClass = class_exists('PHPUnit\Framework\Constraint\Exception') ? 'PHPUnit\Framework\Constraint\Exception' : 'PHPUnit_Framework_Constraint_Exception'; + $this->assertThat(null, new $constraintClass($class)); + } + + $expected = trim($match[3], "\n "); + + if ($expected !== $output) { + printf("Compiled templates that failed on case %d:\n", $i + 1); + + foreach (array_keys($templates) as $name) { + echo "Template: $name\n"; + echo $twig->compile($twig->parse($twig->tokenize($twig->getLoader()->getSourceContext($name)))); + } + } + $this->assertEquals($expected, $output, $message.' (in '.$file.')'); + } + } + + protected static function parseTemplates($test) + { + $templates = []; + preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, \PREG_SET_ORDER); + foreach ($matches as $match) { + $templates[($match[1] ?: 'index.twig')] = $match[2]; + } + + return $templates; + } +} diff --git a/vendor/twig/twig/src/Test/NodeTestCase.php b/vendor/twig/twig/src/Test/NodeTestCase.php new file mode 100644 index 0000000..3b8b2c8 --- /dev/null +++ b/vendor/twig/twig/src/Test/NodeTestCase.php @@ -0,0 +1,65 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\Test; + +use PHPUnit\Framework\TestCase; +use Twig\Compiler; +use Twig\Environment; +use Twig\Loader\ArrayLoader; +use Twig\Node\Node; + +abstract class NodeTestCase extends TestCase +{ + abstract public function getTests(); + + /** + * @dataProvider getTests + */ + public function testCompile($node, $source, $environment = null, $isPattern = false) + { + $this->assertNodeCompilation($source, $node, $environment, $isPattern); + } + + public function assertNodeCompilation($source, Node $node, Environment $environment = null, $isPattern = false) + { + $compiler = $this->getCompiler($environment); + $compiler->compile($node); + + if ($isPattern) { + $this->assertStringMatchesFormat($source, trim($compiler->getSource())); + } else { + $this->assertEquals($source, trim($compiler->getSource())); + } + } + + protected function getCompiler(Environment $environment = null) + { + return new Compiler(null === $environment ? $this->getEnvironment() : $environment); + } + + protected function getEnvironment() + { + return new Environment(new ArrayLoader([])); + } + + protected function getVariableGetter($name, $line = false) + { + $line = $line > 0 ? "// line $line\n" : ''; + + return sprintf('%s($context["%s"] ?? null)', $line, $name); + } + + protected function getAttributeGetter() + { + return 'twig_get_attribute($this->env, $this->source, '; + } +} diff --git a/vendor/twig/twig/src/Token.php b/vendor/twig/twig/src/Token.php new file mode 100644 index 0000000..53a6caf --- /dev/null +++ b/vendor/twig/twig/src/Token.php @@ -0,0 +1,178 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig; + +/** + * @author Fabien Potencier <fabien@symfony.com> + */ +final class Token +{ + private $value; + private $type; + private $lineno; + + public const EOF_TYPE = -1; + public const TEXT_TYPE = 0; + public const BLOCK_START_TYPE = 1; + public const VAR_START_TYPE = 2; + public const BLOCK_END_TYPE = 3; + public const VAR_END_TYPE = 4; + public const NAME_TYPE = 5; + public const NUMBER_TYPE = 6; + public const STRING_TYPE = 7; + public const OPERATOR_TYPE = 8; + public const PUNCTUATION_TYPE = 9; + public const INTERPOLATION_START_TYPE = 10; + public const INTERPOLATION_END_TYPE = 11; + public const ARROW_TYPE = 12; + + public function __construct(int $type, $value, int $lineno) + { + $this->type = $type; + $this->value = $value; + $this->lineno = $lineno; + } + + public function __toString() + { + return sprintf('%s(%s)', self::typeToString($this->type, true), $this->value); + } + + /** + * Tests the current token for a type and/or a value. + * + * Parameters may be: + * * just type + * * type and value (or array of possible values) + * * just value (or array of possible values) (NAME_TYPE is used as type) + * + * @param array|string|int $type The type to test + * @param array|string|null $values The token value + */ + public function test($type, $values = null): bool + { + if (null === $values && !\is_int($type)) { + $values = $type; + $type = self::NAME_TYPE; + } + + return ($this->type === $type) && ( + null === $values || + (\is_array($values) && \in_array($this->value, $values)) || + $this->value == $values + ); + } + + public function getLine(): int + { + return $this->lineno; + } + + public function getType(): int + { + return $this->type; + } + + public function getValue() + { + return $this->value; + } + + public static function typeToString(int $type, bool $short = false): string + { + switch ($type) { + case self::EOF_TYPE: + $name = 'EOF_TYPE'; + break; + case self::TEXT_TYPE: + $name = 'TEXT_TYPE'; + break; + case self::BLOCK_START_TYPE: + $name = 'BLOCK_START_TYPE'; + break; + case self::VAR_START_TYPE: + $name = 'VAR_START_TYPE'; + break; + case self::BLOCK_END_TYPE: + $name = 'BLOCK_END_TYPE'; + break; + case self::VAR_END_TYPE: + $name = 'VAR_END_TYPE'; + break; + case self::NAME_TYPE: + $name = 'NAME_TYPE'; + break; + case self::NUMBER_TYPE: + $name = 'NUMBER_TYPE'; + break; + case self::STRING_TYPE: + $name = 'STRING_TYPE'; + break; + case self::OPERATOR_TYPE: + $name = 'OPERATOR_TYPE'; + break; + case self::PUNCTUATION_TYPE: + $name = 'PUNCTUATION_TYPE'; + break; + case self::INTERPOLATION_START_TYPE: + $name = 'INTERPOLATION_START_TYPE'; + break; + case self::INTERPOLATION_END_TYPE: + $name = 'INTERPOLATION_END_TYPE'; + break; + case self::ARROW_TYPE: + $name = 'ARROW_TYPE'; + break; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + + return $short ? $name : 'Twig\Token::'.$name; + } + + public static function typeToEnglish(int $type): string + { + switch ($type) { + case self::EOF_TYPE: + return 'end of template'; + case self::TEXT_TYPE: + return 'text'; + case self::BLOCK_START_TYPE: + return 'begin of statement block'; + case self::VAR_START_TYPE: + return 'begin of print statement'; + case self::BLOCK_END_TYPE: + return 'end of statement block'; + case self::VAR_END_TYPE: + return 'end of print statement'; + case self::NAME_TYPE: + return 'name'; + case self::NUMBER_TYPE: + return 'number'; + case self::STRING_TYPE: + return 'string'; + case self::OPERATOR_TYPE: + return 'operator'; + case self::PUNCTUATION_TYPE: + return 'punctuation'; + case self::INTERPOLATION_START_TYPE: + return 'begin of string interpolation'; + case self::INTERPOLATION_END_TYPE: + return 'end of string interpolation'; + case self::ARROW_TYPE: + return 'arrow function'; + default: + throw new \LogicException(sprintf('Token of type "%s" does not exist.', $type)); + } + } +} diff --git a/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php new file mode 100644 index 0000000..720ea67 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AbstractTokenParser.php @@ -0,0 +1,32 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\TokenParser; + +use Twig\Parser; + +/** + * Base class for all token parsers. + * + * @author Fabien Potencier <fabien@symfony.com> + */ +abstract class AbstractTokenParser implements TokenParserInterface +{ + /** + * @var Parser + */ + protected $parser; + + public function setParser(Parser $parser): void + { + $this->parser = $parser; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php b/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php new file mode 100644 index 0000000..4dbf304 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ApplyTokenParser.php @@ -0,0 +1,60 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\TokenParser; + +use Twig\Node\Expression\TempNameExpression; +use Twig\Node\Node; +use Twig\Node\PrintNode; +use Twig\Node\SetNode; +use Twig\Token; + +/** + * Applies filters on a section of a template. + * + * {% apply upper %} + * This text becomes uppercase + * {% endapply %} + * + * @internal + */ +final class ApplyTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $name = $this->parser->getVarName(); + + $ref = new TempNameExpression($name, $lineno); + $ref->setAttribute('always_defined', true); + + $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag()); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + $body = $this->parser->subparse([$this, 'decideApplyEnd'], true); + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new Node([ + new SetNode(true, $ref, $body, $lineno, $this->getTag()), + new PrintNode($filter, $lineno, $this->getTag()), + ]); + } + + public function decideApplyEnd(Token $token): bool + { + return $token->test('endapply'); + } + + public function getTag(): string + { + return 'apply'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php new file mode 100644 index 0000000..b674bea --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/AutoEscapeTokenParser.php @@ -0,0 +1,58 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\TokenParser; + +use Twig\Error\SyntaxError; +use Twig\Node\AutoEscapeNode; +use Twig\Node\Expression\ConstantExpression; +use Twig\Node\Node; +use Twig\Token; + +/** + * Marks a section of a template to be escaped or not. + * + * @internal + */ +final class AutoEscapeTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + + if ($stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + $value = 'html'; + } else { + $expr = $this->parser->getExpressionParser()->parseExpression(); + if (!$expr instanceof ConstantExpression) { + throw new SyntaxError('An escaping strategy must be a string or false.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $value = $expr->getAttribute('value'); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new AutoEscapeNode($value, $body, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endautoescape'); + } + + public function getTag(): string + { + return 'autoescape'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/BlockTokenParser.php b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php new file mode 100644 index 0000000..5878131 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/BlockTokenParser.php @@ -0,0 +1,78 @@ +<?php + +/* + * This file is part of Twig. + * + * (c) Fabien Potencier + * (c) Armin Ronacher + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Twig\TokenParser; + +use Twig\Error\SyntaxError; +use Twig\Node\BlockNode; +use Twig\Node\BlockReferenceNode; +use Twig\Node\Node; +use Twig\Node\PrintNode; +use Twig\Token; + +/** + * Marks a section of a template as being reusable. + * + * {% block head %} + * <link rel="stylesheet" href="style.css" /> + * <title>{% block title %}{% endblock %} - My Webpage + * {% endblock %} + * + * @internal + */ +final class BlockTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + if ($this->parser->hasBlock($name)) { + throw new SyntaxError(sprintf("The block '%s' has already been defined line %d.", $name, $this->parser->getBlock($name)->getTemplateLine()), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + $this->parser->setBlock($name, $block = new BlockNode($name, new Node([]), $lineno)); + $this->parser->pushLocalScope(); + $this->parser->pushBlockStack($name); + + if ($stream->nextIf(/* Token::BLOCK_END_TYPE */ 3)) { + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endblock for block "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + } else { + $body = new Node([ + new PrintNode($this->parser->getExpressionParser()->parseExpression(), $lineno), + ]); + } + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $block->setNode('body', $body); + $this->parser->popBlockStack(); + $this->parser->popLocalScope(); + + return new BlockReferenceNode($name, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endblock'); + } + + public function getTag(): string + { + return 'block'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php new file mode 100644 index 0000000..31416c7 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DeprecatedTokenParser.php @@ -0,0 +1,43 @@ + + * + * @internal + */ +final class DeprecatedTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $expr = $this->parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); + + return new DeprecatedNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'deprecated'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/DoTokenParser.php b/vendor/twig/twig/src/TokenParser/DoTokenParser.php new file mode 100644 index 0000000..32c8f12 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/DoTokenParser.php @@ -0,0 +1,38 @@ +parser->getExpressionParser()->parseExpression(); + + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new DoNode($expr, $token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'do'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php new file mode 100644 index 0000000..64b4f29 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/EmbedTokenParser.php @@ -0,0 +1,73 @@ +parser->getStream(); + + $parent = $this->parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + $parentToken = $fakeParentToken = new Token(/* Token::STRING_TYPE */ 7, '__parent__', $token->getLine()); + if ($parent instanceof ConstantExpression) { + $parentToken = new Token(/* Token::STRING_TYPE */ 7, $parent->getAttribute('value'), $token->getLine()); + } elseif ($parent instanceof NameExpression) { + $parentToken = new Token(/* Token::NAME_TYPE */ 5, $parent->getAttribute('name'), $token->getLine()); + } + + // inject a fake parent to make the parent() function work + $stream->injectTokens([ + new Token(/* Token::BLOCK_START_TYPE */ 1, '', $token->getLine()), + new Token(/* Token::NAME_TYPE */ 5, 'extends', $token->getLine()), + $parentToken, + new Token(/* Token::BLOCK_END_TYPE */ 3, '', $token->getLine()), + ]); + + $module = $this->parser->parse($stream, [$this, 'decideBlockEnd'], true); + + // override the parent with the correct one + if ($fakeParentToken === $parentToken) { + $module->setNode('parent', $parent); + } + + $this->parser->embedTemplate($module); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new EmbedNode($module->getTemplateName(), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endembed'); + } + + public function getTag(): string + { + return 'embed'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php new file mode 100644 index 0000000..0ca46dd --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ExtendsTokenParser.php @@ -0,0 +1,52 @@ +parser->getStream(); + + if ($this->parser->peekBlockStack()) { + throw new SyntaxError('Cannot use "extend" in a block.', $token->getLine(), $stream->getSourceContext()); + } elseif (!$this->parser->isMainScope()) { + throw new SyntaxError('Cannot use "extend" in a macro.', $token->getLine(), $stream->getSourceContext()); + } + + if (null !== $this->parser->getParent()) { + throw new SyntaxError('Multiple extends tags are forbidden.', $token->getLine(), $stream->getSourceContext()); + } + $this->parser->setParent($this->parser->getExpressionParser()->parseExpression()); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new Node(); + } + + public function getTag(): string + { + return 'extends'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/FlushTokenParser.php b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php new file mode 100644 index 0000000..02c74aa --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FlushTokenParser.php @@ -0,0 +1,38 @@ +parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new FlushNode($token->getLine(), $this->getTag()); + } + + public function getTag(): string + { + return 'flush'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ForTokenParser.php b/vendor/twig/twig/src/TokenParser/ForTokenParser.php new file mode 100644 index 0000000..bac8ba2 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ForTokenParser.php @@ -0,0 +1,78 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * + * @internal + */ +final class ForTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $targets = $this->parser->getExpressionParser()->parseAssignmentExpression(); + $stream->expect(/* Token::OPERATOR_TYPE */ 8, 'in'); + $seq = $this->parser->getExpressionParser()->parseExpression(); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideForFork']); + if ('else' == $stream->next()->getValue()) { + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $else = $this->parser->subparse([$this, 'decideForEnd'], true); + } else { + $else = null; + } + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + if (\count($targets) > 1) { + $keyTarget = $targets->getNode(0); + $keyTarget = new AssignNameExpression($keyTarget->getAttribute('name'), $keyTarget->getTemplateLine()); + $valueTarget = $targets->getNode(1); + } else { + $keyTarget = new AssignNameExpression('_key', $lineno); + $valueTarget = $targets->getNode(0); + } + $valueTarget = new AssignNameExpression($valueTarget->getAttribute('name'), $valueTarget->getTemplateLine()); + + return new ForNode($keyTarget, $valueTarget, $seq, null, $body, $else, $lineno, $this->getTag()); + } + + public function decideForFork(Token $token): bool + { + return $token->test(['else', 'endfor']); + } + + public function decideForEnd(Token $token): bool + { + return $token->test('endfor'); + } + + public function getTag(): string + { + return 'for'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/FromTokenParser.php b/vendor/twig/twig/src/TokenParser/FromTokenParser.php new file mode 100644 index 0000000..35098c2 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/FromTokenParser.php @@ -0,0 +1,66 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(/* Token::NAME_TYPE */ 5, 'import'); + + $targets = []; + do { + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + } + + $targets[$name] = $alias; + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } while (true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $var = new AssignNameExpression($this->parser->getVarName(), $token->getLine()); + $node = new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + + foreach ($targets as $name => $alias) { + $this->parser->addImportedSymbol('function', $alias, 'macro_'.$name, $var); + } + + return $node; + } + + public function getTag(): string + { + return 'from'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/IfTokenParser.php b/vendor/twig/twig/src/TokenParser/IfTokenParser.php new file mode 100644 index 0000000..c0fe6df --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IfTokenParser.php @@ -0,0 +1,89 @@ + + * {% for user in users %} + *
  • {{ user.username|e }}
  • + * {% endfor %} + * + * {% endif %} + * + * @internal + */ +final class IfTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests = [$expr, $body]; + $else = null; + + $end = false; + while (!$end) { + switch ($stream->next()->getValue()) { + case 'else': + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $else = $this->parser->subparse([$this, 'decideIfEnd']); + break; + + case 'elseif': + $expr = $this->parser->getExpressionParser()->parseExpression(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideIfFork']); + $tests[] = $expr; + $tests[] = $body; + break; + + case 'endif': + $end = true; + break; + + default: + throw new SyntaxError(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d).', $lineno), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new IfNode(new Node($tests), $else, $lineno, $this->getTag()); + } + + public function decideIfFork(Token $token): bool + { + return $token->test(['elseif', 'else', 'endif']); + } + + public function decideIfEnd(Token $token): bool + { + return $token->test(['endif']); + } + + public function getTag(): string + { + return 'if'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/ImportTokenParser.php b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php new file mode 100644 index 0000000..44cb4da --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/ImportTokenParser.php @@ -0,0 +1,44 @@ +parser->getExpressionParser()->parseExpression(); + $this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5, 'as'); + $var = new AssignNameExpression($this->parser->getStream()->expect(/* Token::NAME_TYPE */ 5)->getValue(), $token->getLine()); + $this->parser->getStream()->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->addImportedSymbol('template', $var->getAttribute('name')); + + return new ImportNode($macro, $var, $token->getLine(), $this->getTag(), $this->parser->isMainScope()); + } + + public function getTag(): string + { + return 'import'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php new file mode 100644 index 0000000..28beb8a --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/IncludeTokenParser.php @@ -0,0 +1,69 @@ +parser->getExpressionParser()->parseExpression(); + + list($variables, $only, $ignoreMissing) = $this->parseArguments(); + + return new IncludeNode($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag()); + } + + protected function parseArguments() + { + $stream = $this->parser->getStream(); + + $ignoreMissing = false; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'ignore')) { + $stream->expect(/* Token::NAME_TYPE */ 5, 'missing'); + + $ignoreMissing = true; + } + + $variables = null; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'with')) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + } + + $only = false; + if ($stream->nextIf(/* Token::NAME_TYPE */ 5, 'only')) { + $only = true; + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return [$variables, $only, $ignoreMissing]; + } + + public function getTag(): string + { + return 'include'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/MacroTokenParser.php b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php new file mode 100644 index 0000000..f584927 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/MacroTokenParser.php @@ -0,0 +1,66 @@ + + * {% endmacro %} + * + * @internal + */ +final class MacroTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $lineno = $token->getLine(); + $stream = $this->parser->getStream(); + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $arguments = $this->parser->getExpressionParser()->parseArguments(true, true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $this->parser->pushLocalScope(); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + if ($token = $stream->nextIf(/* Token::NAME_TYPE */ 5)) { + $value = $token->getValue(); + + if ($value != $name) { + throw new SyntaxError(sprintf('Expected endmacro for macro "%s" (but "%s" given).', $name, $value), $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } + $this->parser->popLocalScope(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->setMacro($name, new MacroNode($name, new BodyNode([$body]), $arguments, $lineno, $this->getTag())); + + return new Node(); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endmacro'); + } + + public function getTag(): string + { + return 'macro'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php new file mode 100644 index 0000000..c919556 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SandboxTokenParser.php @@ -0,0 +1,66 @@ +parser->getStream(); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + $body = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + // in a sandbox tag, only include tags are allowed + if (!$body instanceof IncludeNode) { + foreach ($body as $node) { + if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) { + continue; + } + + if (!$node instanceof IncludeNode) { + throw new SyntaxError('Only "include" tags are allowed within a "sandbox" section.', $node->getTemplateLine(), $stream->getSourceContext()); + } + } + } + + return new SandboxNode($body, $token->getLine(), $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endsandbox'); + } + + public function getTag(): string + { + return 'sandbox'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/SetTokenParser.php b/vendor/twig/twig/src/TokenParser/SetTokenParser.php new file mode 100644 index 0000000..2fbdfe0 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/SetTokenParser.php @@ -0,0 +1,73 @@ +getLine(); + $stream = $this->parser->getStream(); + $names = $this->parser->getExpressionParser()->parseAssignmentExpression(); + + $capture = false; + if ($stream->nextIf(/* Token::OPERATOR_TYPE */ 8, '=')) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + if (\count($names) !== \count($values)) { + throw new SyntaxError('When using set, you must have the same number of variables and assignments.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + } else { + $capture = true; + + if (\count($names) > 1) { + throw new SyntaxError('When using set with a block, you cannot have a multi-target.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $values = $this->parser->subparse([$this, 'decideBlockEnd'], true); + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + } + + return new SetNode($capture, $names, $values, $lineno, $this->getTag()); + } + + public function decideBlockEnd(Token $token): bool + { + return $token->test('endset'); + } + + public function getTag(): string + { + return 'set'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/TokenParserInterface.php b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php new file mode 100644 index 0000000..bb8db3e --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/TokenParserInterface.php @@ -0,0 +1,46 @@ + + */ +interface TokenParserInterface +{ + /** + * Sets the parser associated with this token parser. + */ + public function setParser(Parser $parser): void; + + /** + * Parses a token and returns a node. + * + * @return Node + * + * @throws SyntaxError + */ + public function parse(Token $token); + + /** + * Gets the tag name associated with this token parser. + * + * @return string + */ + public function getTag(); +} diff --git a/vendor/twig/twig/src/TokenParser/UseTokenParser.php b/vendor/twig/twig/src/TokenParser/UseTokenParser.php new file mode 100644 index 0000000..d0a2de4 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/UseTokenParser.php @@ -0,0 +1,73 @@ +parser->getExpressionParser()->parseExpression(); + $stream = $this->parser->getStream(); + + if (!$template instanceof ConstantExpression) { + throw new SyntaxError('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getSourceContext()); + } + + $targets = []; + if ($stream->nextIf('with')) { + do { + $name = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + + $alias = $name; + if ($stream->nextIf('as')) { + $alias = $stream->expect(/* Token::NAME_TYPE */ 5)->getValue(); + } + + $targets[$name] = new ConstantExpression($alias, -1); + + if (!$stream->nextIf(/* Token::PUNCTUATION_TYPE */ 9, ',')) { + break; + } + } while (true); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $this->parser->addTrait(new Node(['template' => $template, 'targets' => new Node($targets)])); + + return new Node(); + } + + public function getTag(): string + { + return 'use'; + } +} diff --git a/vendor/twig/twig/src/TokenParser/WithTokenParser.php b/vendor/twig/twig/src/TokenParser/WithTokenParser.php new file mode 100644 index 0000000..7d8cbe2 --- /dev/null +++ b/vendor/twig/twig/src/TokenParser/WithTokenParser.php @@ -0,0 +1,56 @@ + + * + * @internal + */ +final class WithTokenParser extends AbstractTokenParser +{ + public function parse(Token $token): Node + { + $stream = $this->parser->getStream(); + + $variables = null; + $only = false; + if (!$stream->test(/* Token::BLOCK_END_TYPE */ 3)) { + $variables = $this->parser->getExpressionParser()->parseExpression(); + $only = (bool) $stream->nextIf(/* Token::NAME_TYPE */ 5, 'only'); + } + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + $body = $this->parser->subparse([$this, 'decideWithEnd'], true); + + $stream->expect(/* Token::BLOCK_END_TYPE */ 3); + + return new WithNode($body, $variables, $only, $token->getLine(), $this->getTag()); + } + + public function decideWithEnd(Token $token): bool + { + return $token->test('endwith'); + } + + public function getTag(): string + { + return 'with'; + } +} diff --git a/vendor/twig/twig/src/TokenStream.php b/vendor/twig/twig/src/TokenStream.php new file mode 100644 index 0000000..1eac11a --- /dev/null +++ b/vendor/twig/twig/src/TokenStream.php @@ -0,0 +1,132 @@ + + */ +final class TokenStream +{ + private $tokens; + private $current = 0; + private $source; + + public function __construct(array $tokens, Source $source = null) + { + $this->tokens = $tokens; + $this->source = $source ?: new Source('', ''); + } + + public function __toString() + { + return implode("\n", $this->tokens); + } + + public function injectTokens(array $tokens) + { + $this->tokens = array_merge(\array_slice($this->tokens, 0, $this->current), $tokens, \array_slice($this->tokens, $this->current)); + } + + /** + * Sets the pointer to the next token and returns the old one. + */ + public function next(): Token + { + if (!isset($this->tokens[++$this->current])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current - 1]; + } + + /** + * Tests a token, sets the pointer to the next one and returns it or throws a syntax error. + * + * @return Token|null The next token if the condition is true, null otherwise + */ + public function nextIf($primary, $secondary = null) + { + if ($this->tokens[$this->current]->test($primary, $secondary)) { + return $this->next(); + } + } + + /** + * Tests a token and returns it or throws a syntax error. + */ + public function expect($type, $value = null, string $message = null): Token + { + $token = $this->tokens[$this->current]; + if (!$token->test($type, $value)) { + $line = $token->getLine(); + throw new SyntaxError(sprintf('%sUnexpected token "%s"%s ("%s" expected%s).', + $message ? $message.'. ' : '', + Token::typeToEnglish($token->getType()), + $token->getValue() ? sprintf(' of value "%s"', $token->getValue()) : '', + Token::typeToEnglish($type), $value ? sprintf(' with value "%s"', $value) : ''), + $line, + $this->source + ); + } + $this->next(); + + return $token; + } + + /** + * Looks at the next token. + */ + public function look(int $number = 1): Token + { + if (!isset($this->tokens[$this->current + $number])) { + throw new SyntaxError('Unexpected end of template.', $this->tokens[$this->current + $number - 1]->getLine(), $this->source); + } + + return $this->tokens[$this->current + $number]; + } + + /** + * Tests the current token. + */ + public function test($primary, $secondary = null): bool + { + return $this->tokens[$this->current]->test($primary, $secondary); + } + + /** + * Checks if end of stream was reached. + */ + public function isEOF(): bool + { + return /* Token::EOF_TYPE */ -1 === $this->tokens[$this->current]->getType(); + } + + public function getCurrent(): Token + { + return $this->tokens[$this->current]; + } + + /** + * Gets the source associated with this stream. + * + * @internal + */ + public function getSourceContext(): Source + { + return $this->source; + } +} diff --git a/vendor/twig/twig/src/TwigFilter.php b/vendor/twig/twig/src/TwigFilter.php new file mode 100644 index 0000000..94e5f9b --- /dev/null +++ b/vendor/twig/twig/src/TwigFilter.php @@ -0,0 +1,134 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#filters + */ +final class TwigFilter +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the filter. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'pre_escape' => null, + 'preserves_safety' => null, + 'node_class' => FilterExpression::class, + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this filter. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function getSafe(Node $filterArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($filterArgs); + } + + return null; + } + + public function getPreservesSafety(): ?array + { + return $this->options['preserves_safety']; + } + + public function getPreEscape(): ?string + { + return $this->options['pre_escape']; + } + + public function isVariadic(): bool + { + return $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } +} diff --git a/vendor/twig/twig/src/TwigFunction.php b/vendor/twig/twig/src/TwigFunction.php new file mode 100644 index 0000000..494d45b --- /dev/null +++ b/vendor/twig/twig/src/TwigFunction.php @@ -0,0 +1,122 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#functions + */ +final class TwigFunction +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the function. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'needs_environment' => false, + 'needs_context' => false, + 'is_variadic' => false, + 'is_safe' => null, + 'is_safe_callback' => null, + 'node_class' => FunctionExpression::class, + 'deprecated' => false, + 'alternative' => null, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this function. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function needsEnvironment(): bool + { + return $this->options['needs_environment']; + } + + public function needsContext(): bool + { + return $this->options['needs_context']; + } + + public function getSafe(Node $functionArgs): ?array + { + if (null !== $this->options['is_safe']) { + return $this->options['is_safe']; + } + + if (null !== $this->options['is_safe_callback']) { + return $this->options['is_safe_callback']($functionArgs); + } + + return []; + } + + public function isVariadic(): bool + { + return (bool) $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } +} diff --git a/vendor/twig/twig/src/TwigTest.php b/vendor/twig/twig/src/TwigTest.php new file mode 100644 index 0000000..4c18632 --- /dev/null +++ b/vendor/twig/twig/src/TwigTest.php @@ -0,0 +1,100 @@ + + * + * @see https://twig.symfony.com/doc/templates.html#test-operator + */ +final class TwigTest +{ + private $name; + private $callable; + private $options; + private $arguments = []; + + /** + * @param callable|null $callable A callable implementing the test. If null, you need to overwrite the "node_class" option to customize compilation. + */ + public function __construct(string $name, $callable = null, array $options = []) + { + $this->name = $name; + $this->callable = $callable; + $this->options = array_merge([ + 'is_variadic' => false, + 'node_class' => TestExpression::class, + 'deprecated' => false, + 'alternative' => null, + 'one_mandatory_argument' => false, + ], $options); + } + + public function getName(): string + { + return $this->name; + } + + /** + * Returns the callable to execute for this test. + * + * @return callable|null + */ + public function getCallable() + { + return $this->callable; + } + + public function getNodeClass(): string + { + return $this->options['node_class']; + } + + public function setArguments(array $arguments): void + { + $this->arguments = $arguments; + } + + public function getArguments(): array + { + return $this->arguments; + } + + public function isVariadic(): bool + { + return (bool) $this->options['is_variadic']; + } + + public function isDeprecated(): bool + { + return (bool) $this->options['deprecated']; + } + + public function getDeprecatedVersion(): string + { + return \is_bool($this->options['deprecated']) ? '' : $this->options['deprecated']; + } + + public function getAlternative(): ?string + { + return $this->options['alternative']; + } + + public function hasOneMandatoryArgument(): bool + { + return (bool) $this->options['one_mandatory_argument']; + } +} diff --git a/vendor/twig/twig/src/Util/DeprecationCollector.php b/vendor/twig/twig/src/Util/DeprecationCollector.php new file mode 100644 index 0000000..378b666 --- /dev/null +++ b/vendor/twig/twig/src/Util/DeprecationCollector.php @@ -0,0 +1,77 @@ + + */ +final class DeprecationCollector +{ + private $twig; + + public function __construct(Environment $twig) + { + $this->twig = $twig; + } + + /** + * Returns deprecations for templates contained in a directory. + * + * @param string $dir A directory where templates are stored + * @param string $ext Limit the loaded templates by extension + * + * @return array An array of deprecations + */ + public function collectDir(string $dir, string $ext = '.twig'): array + { + $iterator = new \RegexIterator( + new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator($dir), \RecursiveIteratorIterator::LEAVES_ONLY + ), '{'.preg_quote($ext).'$}' + ); + + return $this->collect(new TemplateDirIterator($iterator)); + } + + /** + * Returns deprecations for passed templates. + * + * @param \Traversable $iterator An iterator of templates (where keys are template names and values the contents of the template) + * + * @return array An array of deprecations + */ + public function collect(\Traversable $iterator): array + { + $deprecations = []; + set_error_handler(function ($type, $msg) use (&$deprecations) { + if (\E_USER_DEPRECATED === $type) { + $deprecations[] = $msg; + } + }); + + foreach ($iterator as $name => $contents) { + try { + $this->twig->parse($this->twig->tokenize(new Source($contents, $name))); + } catch (SyntaxError $e) { + // ignore templates containing syntax errors + } + } + + restore_error_handler(); + + return $deprecations; + } +} diff --git a/vendor/twig/twig/src/Util/TemplateDirIterator.php b/vendor/twig/twig/src/Util/TemplateDirIterator.php new file mode 100644 index 0000000..3bef14b --- /dev/null +++ b/vendor/twig/twig/src/Util/TemplateDirIterator.php @@ -0,0 +1,36 @@ + + */ +class TemplateDirIterator extends \IteratorIterator +{ + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function current() + { + return file_get_contents(parent::current()); + } + + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function key() + { + return (string) parent::key(); + } +}