├── .wp-env.json ├── tests ├── data │ └── blue.png ├── mocks │ └── graphql-relay-mock.php ├── parser │ ├── blocks │ │ ├── test-image-block.php │ │ └── test-unregistered-block.php │ └── sources │ │ ├── test-source-node.php │ │ ├── test-source-delimiter.php │ │ ├── test-source-meta.php │ │ ├── test-source-tag.php │ │ ├── test-source-attribute.php │ │ ├── test-source-html.php │ │ └── test-source-text.php ├── bootstrap.php └── registry-test-case.php ├── .github ├── CODEOWNERS ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── phpcs.yml │ └── phpunit.yml ├── .gitignore ├── vendor ├── composer │ ├── autoload_namespaces.php │ ├── autoload_classmap.php │ ├── autoload_files.php │ ├── autoload_psr4.php │ ├── platform_check.php │ ├── LICENSE │ ├── autoload_real.php │ ├── autoload_static.php │ └── installed.php ├── masterminds │ └── html5 │ │ ├── src │ │ └── HTML5 │ │ │ ├── Exception.php │ │ │ ├── Parser │ │ │ ├── ParseError.php │ │ │ ├── FileInputStream.php │ │ │ ├── CharacterReference.php │ │ │ ├── README.md │ │ │ ├── InputStream.php │ │ │ ├── TreeBuildingRules.php │ │ │ └── EventHandler.php │ │ │ ├── Serializer │ │ │ ├── README.md │ │ │ └── RulesInterface.php │ │ │ └── InstructionProcessor.php │ │ ├── UPGRADING.md │ │ ├── CREDITS │ │ ├── bin │ │ └── entities.php │ │ ├── composer.json │ │ └── LICENSE.txt ├── symfony │ ├── css-selector │ │ ├── CHANGELOG.md │ │ ├── Exception │ │ │ ├── ExceptionInterface.php │ │ │ ├── ParseException.php │ │ │ ├── InternalErrorException.php │ │ │ ├── ExpressionErrorException.php │ │ │ └── SyntaxErrorException.php │ │ ├── README.md │ │ ├── Node │ │ │ ├── NodeInterface.php │ │ │ ├── AbstractNode.php │ │ │ ├── HashNode.php │ │ │ ├── ClassNode.php │ │ │ ├── PseudoNode.php │ │ │ ├── ElementNode.php │ │ │ ├── NegationNode.php │ │ │ ├── SelectorNode.php │ │ │ ├── CombinedSelectorNode.php │ │ │ ├── FunctionNode.php │ │ │ ├── Specificity.php │ │ │ └── AttributeNode.php │ │ ├── Parser │ │ │ ├── Handler │ │ │ │ ├── HandlerInterface.php │ │ │ │ ├── CommentHandler.php │ │ │ │ ├── WhitespaceHandler.php │ │ │ │ ├── NumberHandler.php │ │ │ │ ├── HashHandler.php │ │ │ │ ├── IdentifierHandler.php │ │ │ │ └── StringHandler.php │ │ │ ├── ParserInterface.php │ │ │ ├── Shortcut │ │ │ │ ├── EmptyStringParser.php │ │ │ │ ├── ElementParser.php │ │ │ │ ├── HashParser.php │ │ │ │ └── ClassParser.php │ │ │ ├── Tokenizer │ │ │ │ ├── TokenizerEscaping.php │ │ │ │ ├── Tokenizer.php │ │ │ │ └── TokenizerPatterns.php │ │ │ ├── Reader.php │ │ │ ├── Token.php │ │ │ └── TokenStream.php │ │ ├── composer.json │ │ ├── LICENSE │ │ ├── XPath │ │ │ ├── TranslatorInterface.php │ │ │ ├── Extension │ │ │ │ ├── AbstractExtension.php │ │ │ │ ├── ExtensionInterface.php │ │ │ │ ├── CombinationExtension.php │ │ │ │ └── PseudoClassExtension.php │ │ │ └── XPathExpr.php │ │ └── CssSelectorConverter.php │ ├── polyfill-ctype │ │ ├── README.md │ │ ├── composer.json │ │ ├── LICENSE │ │ ├── bootstrap.php │ │ └── bootstrap80.php │ ├── polyfill-mbstring │ │ ├── README.md │ │ ├── LICENSE │ │ ├── composer.json │ │ └── Resources │ │ │ └── unidata │ │ │ └── caseFolding.php │ └── dom-crawler │ │ ├── README.md │ │ ├── composer.json │ │ ├── Link.php │ │ ├── Image.php │ │ ├── LICENSE │ │ ├── Field │ │ ├── TextareaFormField.php │ │ ├── InputFormField.php │ │ ├── FormField.php │ │ └── FileFormField.php │ │ ├── Test │ │ └── Constraint │ │ │ ├── CrawlerSelectorExists.php │ │ │ ├── CrawlerSelectorCount.php │ │ │ ├── CrawlerSelectorTextSame.php │ │ │ ├── CrawlerSelectorAttributeValueSame.php │ │ │ ├── CrawlerSelectorTextContains.php │ │ │ ├── CrawlerAnySelectorTextSame.php │ │ │ └── CrawlerAnySelectorTextContains.php │ │ ├── AbstractUriElement.php │ │ └── UriResolver.php └── autoload.php ├── phpunit.xml.dist ├── .editorconfig ├── composer.json ├── vip-block-data-api.php ├── src └── parser │ └── block-additions │ └── core-image.php ├── RELEASE.md └── phpcs.xml.dist /.wp-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "." 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /tests/data/blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/vip-block-data-api/HEAD/tests/data/blue.png -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 2 | * @Automattic/vip-bistro 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vip-block-data-api*.zip 2 | 3 | # Ignore non-production composer dependencies 4 | vendor/* 5 | !vendor/autoload.php 6 | !vendor/composer/ 7 | !vendor/masterminds/ 8 | !vendor/symfony/ 9 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_files.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', 10 | '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 6.3 5 | ----- 6 | 7 | * Add support for `:scope` 8 | 9 | 4.4.0 10 | ----- 11 | 12 | * Added support for `*:only-of-type` 13 | 14 | 2.8.0 15 | ----- 16 | 17 | * Added the `CssSelectorConverter` class as a non-static API for the component. 18 | * Deprecated the `CssSelector` static API of the component. 19 | 20 | 2.1.0 21 | ----- 22 | 23 | * none 24 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/README.md: -------------------------------------------------------------------------------- 1 | Symfony Polyfill / Ctype 2 | ======================== 3 | 4 | This component provides `ctype_*` functions to users who run php versions without the ctype extension. 5 | 6 | More information can be found in the 7 | [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). 8 | 9 | License 10 | ======= 11 | 12 | This library is released under the [MIT license](LICENSE). 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | A few sentences describing the overall goals of the Pull Request. 4 | 5 | Should include any special considerations, decisions, and links to relevant GitHub issues. 6 | 7 | ## Steps to Test 8 | 9 | Outline the steps to test and verify the PR here. 10 | 11 | Example: 12 | 13 | 1. Check out PR. 14 | 1. Run `npm run build`. 15 | 1. Test the thing.` 16 | 1. Verify cookies are delicious. 17 | 18 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-mbstring/README.md: -------------------------------------------------------------------------------- 1 | Symfony Polyfill / Mbstring 2 | =========================== 3 | 4 | This component provides a partial, native PHP implementation for the 5 | [Mbstring](https://php.net/mbstring) extension. 6 | 7 | More information can be found in the 8 | [main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md). 9 | 10 | License 11 | ======= 12 | 13 | This library is released under the [MIT license](LICENSE). 14 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/README.md: -------------------------------------------------------------------------------- 1 | DomCrawler Component 2 | ==================== 3 | 4 | The DomCrawler component eases DOM navigation for HTML and XML documents. 5 | 6 | Resources 7 | --------- 8 | 9 | * [Documentation](https://symfony.com/doc/current/components/dom_crawler.html) 10 | * [Contributing](https://symfony.com/doc/current/contributing/index.html) 11 | * [Report issues](https://github.com/symfony/symfony/issues) and 12 | [send Pull Requests](https://github.com/symfony/symfony/pulls) 13 | in the [main Symfony repository](https://github.com/symfony/symfony) 14 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/UPGRADING.md: -------------------------------------------------------------------------------- 1 | From 1.x to 2.x 2 | ================= 3 | 4 | - All classes uses `Masterminds` namespace. 5 | - All public static methods has been removed from `HTML5` class and the general API to access the HTML5 functionalities has changed. 6 | 7 | Before: 8 | 9 | $dom = \HTML5::loadHTML('....'); 10 | \HTML5::saveHTML($dom); 11 | 12 | After: 13 | 14 | use Masterminds\HTML5; 15 | 16 | $html5 = new HTML5(); 17 | 18 | $dom = $html5->loadHTML('....'); 19 | echo $html5->saveHTML($dom); 20 | 21 | 22 | -------------------------------------------------------------------------------- /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/symfony/polyfill-mbstring'), 10 | 'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'), 11 | 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), 12 | 'Symfony\\Component\\CssSelector\\' => array($vendorDir . '/symfony/css-selector'), 13 | 'Masterminds\\' => array($vendorDir . '/masterminds/html5/src'), 14 | ); 15 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/CREDITS: -------------------------------------------------------------------------------- 1 | Matt Butcher [technosophos] (lead) 2 | Matt Farina [mattfarina] (lead) 3 | Asmir Mustafic [goetas] (contributor) 4 | Edward Z. Yang [ezyang] (contributor) 5 | Geoffrey Sneddon [gsnedders] (contributor) 6 | Kukhar Vasily [ngreduce] (contributor) 7 | Rune Christensen [MrElectronic] (contributor) 8 | Mišo Belica [miso-belica] (contributor) 9 | Asmir Mustafic [goetas] (contributor) 10 | KITAITI Makoto [KitaitiMakoto] (contributor) 11 | Jacob Floyd [cognifloyd] (contributor) 12 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Exception; 13 | 14 | /** 15 | * Interface for exceptions. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | */ 22 | interface ExceptionInterface extends \Throwable 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # It is based on https://core.trac.wordpress.org/browser/trunk/.editorconfig 3 | # See https://editorconfig.org for more information about the standard. 4 | # WordPress VIP documentation: https://docs.wpvip.com/technical-references/vip-codebase/editorconfig/ 5 | 6 | # WordPress Coding Standards 7 | # https://make.wordpress.org/core/handbook/coding-standards/ 8 | 9 | root = true 10 | 11 | [*] 12 | charset = utf-8 13 | end_of_line = lf 14 | insert_final_newline = true 15 | trim_trailing_whitespace = true 16 | indent_style = tab 17 | 18 | [*.yml] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false 24 | 25 | [*.txt] 26 | end_of_line = crlf 27 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/bin/entities.php: -------------------------------------------------------------------------------- 1 | $obj) { 14 | $sname = substr($name, 1, -1); 15 | $table[$sname] = $obj->characters; 16 | } 17 | 18 | echo ' 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Exception; 13 | 14 | /** 15 | * ParseException is thrown when a CSS selector syntax is not valid. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Fabien Potencier 21 | */ 22 | class ParseException extends \Exception implements ExceptionInterface 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Exception/InternalErrorException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Exception; 13 | 14 | /** 15 | * ParseException is thrown when a CSS selector syntax is not valid. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | */ 22 | class InternalErrorException extends ParseException 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Exception/ExpressionErrorException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Exception; 13 | 14 | /** 15 | * ParseException is thrown when a CSS selector syntax is not valid. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | */ 22 | class ExpressionErrorException extends ParseException 23 | { 24 | } 25 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/README.md: -------------------------------------------------------------------------------- 1 | CssSelector Component 2 | ===================== 3 | 4 | The CssSelector component converts CSS selectors to XPath expressions. 5 | 6 | Resources 7 | --------- 8 | 9 | * [Documentation](https://symfony.com/doc/current/components/css_selector.html) 10 | * [Contributing](https://symfony.com/doc/current/contributing/index.html) 11 | * [Report issues](https://github.com/symfony/symfony/issues) and 12 | [send Pull Requests](https://github.com/symfony/symfony/pulls) 13 | in the [main Symfony repository](https://github.com/symfony/symfony) 14 | 15 | Credits 16 | ------- 17 | 18 | This component is a port of the Python cssselect library 19 | [v0.7.1](https://github.com/SimonSapin/cssselect/releases/tag/v0.7.1), 20 | which is distributed under the BSD license. 21 | -------------------------------------------------------------------------------- /tests/mocks/graphql-relay-mock.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Interface for nodes. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | interface NodeInterface extends \Stringable 25 | { 26 | public function getNodeName(): string; 27 | 28 | public function getSpecificity(): Specificity; 29 | } 30 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/HandlerInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\TokenStream; 16 | 17 | /** 18 | * CSS selector handler interface. 19 | * 20 | * This component is a port of the Python cssselect library, 21 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 22 | * 23 | * @author Jean-François Simon 24 | * 25 | * @internal 26 | */ 27 | interface HandlerInterface 28 | { 29 | public function handle(Reader $reader, TokenStream $stream): bool; 30 | } 31 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/AbstractNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Abstract base node class. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | abstract class AbstractNode implements NodeInterface 25 | { 26 | private string $nodeName; 27 | 28 | public function getNodeName(): string 29 | { 30 | return $this->nodeName ??= preg_replace('~.*\\\\([^\\\\]+)Node$~', '$1', static::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/css-selector", 3 | "type": "library", 4 | "description": "Converts CSS selectors to XPath expressions", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Jean-François Simon", 15 | "email": "jeanfrancois.simon@sensiolabs.com" 16 | }, 17 | { 18 | "name": "Symfony Community", 19 | "homepage": "https://symfony.com/contributors" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=8.1" 24 | }, 25 | "autoload": { 26 | "psr-4": { "Symfony\\Component\\CssSelector\\": "" }, 27 | "exclude-from-classmap": [ 28 | "/Tests/" 29 | ] 30 | }, 31 | "minimum-stability": "dev" 32 | } 33 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser; 13 | 14 | use Symfony\Component\CssSelector\Node\SelectorNode; 15 | 16 | /** 17 | * CSS selector parser interface. 18 | * 19 | * This component is a port of the Python cssselect library, 20 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 21 | * 22 | * @author Jean-François Simon 23 | * 24 | * @internal 25 | */ 26 | interface ParserInterface 27 | { 28 | /** 29 | * Parses given selector source into an array of tokens. 30 | * 31 | * @return SelectorNode[] 32 | */ 33 | public function parse(string $source): array; 34 | } 35 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: ingeniumed, smithjw1, alecgeatches 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | - Include what kind of a post has caused this problem 15 | - Include any custom blocks that have caused this problem 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Actual behavior** 21 | A clear and concise description of what actually happened. 22 | 23 | **Block Data API URL** 24 | 25 | If publicly accessible, please share the block data API URL used to reproduce this problem. 26 | 27 | e.g. `https://my.site/wp-json/vip-block-data-api/v1/posts/139/blocks` 28 | 29 | **Version of the plugin** 30 | Version of the plugin 31 | 32 | **Additional context** 33 | Add any other context about the problem here. Please be careful to not share any confidential information here. 34 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 80100)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | 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; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/dom-crawler", 3 | "type": "library", 4 | "description": "Eases DOM navigation for HTML and XML documents", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.1", 20 | "symfony/polyfill-ctype": "~1.8", 21 | "symfony/polyfill-mbstring": "~1.0", 22 | "masterminds/html5": "^2.6" 23 | }, 24 | "require-dev": { 25 | "symfony/css-selector": "^5.4|^6.0|^7.0" 26 | }, 27 | "autoload": { 28 | "psr-4": { "Symfony\\Component\\DomCrawler\\": "" }, 29 | "exclude-from-classmap": [ 30 | "/Tests/" 31 | ] 32 | }, 33 | "minimum-stability": "dev" 34 | } 35 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Link.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler; 13 | 14 | /** 15 | * Link represents an HTML link (an HTML a, area or link tag). 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | class Link extends AbstractUriElement 20 | { 21 | protected function getRawUri(): string 22 | { 23 | return $this->node->getAttribute('href'); 24 | } 25 | 26 | /** 27 | * @return void 28 | */ 29 | protected function setNode(\DOMElement $node) 30 | { 31 | if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { 32 | throw new \LogicException(\sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); 33 | } 34 | 35 | $this->node = $node; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Serializer/README.md: -------------------------------------------------------------------------------- 1 | # The Serializer (Writer) Model 2 | 3 | The serializer roughly follows sections _8.1 Writing HTML documents_ and section 4 | _8.3 Serializing HTML fragments_ by converting DOMDocument, DOMDocumentFragment, 5 | and DOMNodeList into HTML5. 6 | 7 | [ HTML5 ] // Interface for saving. 8 | || 9 | [ Traverser ] // Walk the DOM 10 | || 11 | [ Rules ] // Convert DOM elements into strings. 12 | || 13 | [ HTML5 ] // HTML5 document or fragment in text. 14 | 15 | 16 | ## HTML5 Class 17 | 18 | Provides the top level interface for saving. 19 | 20 | ## The Traverser 21 | 22 | Walks the DOM finding each element and passing it off to the output rules to 23 | convert to HTML5. 24 | 25 | ## Output Rules 26 | 27 | The output rules are defined in the RulesInterface which can have multiple 28 | implementations. Currently, the OutputRules is the default implementation that 29 | converts a DOM as is into HTML5. 30 | 31 | ## HTML5 String 32 | 33 | The output of the process it HTML5 as a string or saved to a file. -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Image.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler; 13 | 14 | /** 15 | * Image represents an HTML image (an HTML img tag). 16 | */ 17 | class Image extends AbstractUriElement 18 | { 19 | public function __construct(\DOMElement $node, ?string $currentUri = null) 20 | { 21 | parent::__construct($node, $currentUri, 'GET'); 22 | } 23 | 24 | protected function getRawUri(): string 25 | { 26 | return $this->node->getAttribute('src'); 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | protected function setNode(\DOMElement $node) 33 | { 34 | if ('img' !== $node->nodeName) { 35 | throw new \LogicException(\sprintf('Unable to visualize a "%s" tag.', $node->nodeName)); 36 | } 37 | 38 | $this->node = $node; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/polyfill-ctype", 3 | "type": "library", 4 | "description": "Symfony polyfill for ctype functions", 5 | "keywords": ["polyfill", "compatibility", "portable", "ctype"], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Gert de Pagter", 11 | "email": "BackEndTea@gmail.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.2" 20 | }, 21 | "provide": { 22 | "ext-ctype": "*" 23 | }, 24 | "autoload": { 25 | "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" }, 26 | "files": [ "bootstrap.php" ] 27 | }, 28 | "suggest": { 29 | "ext-ctype": "For best performance" 30 | }, 31 | "minimum-stability": "dev", 32 | "extra": { 33 | "thanks": { 34 | "name": "symfony/polyfill", 35 | "url": "https://github.com/symfony/polyfill" 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/phpcs.yml: -------------------------------------------------------------------------------- 1 | name: PHP CodeSniffer 2 | 3 | on: pull_request 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | php-versions: 16 | - 8.1 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - name: Setup PHP 22 | uses: shivammathur/setup-php@v2 23 | with: 24 | tools: composer 25 | php-version: ${{ matrix.php-versions }} 26 | 27 | - name: Validate composer.json and composer.lock 28 | run: composer validate --strict 29 | 30 | - name: Cache Composer packages 31 | id: composer-cache 32 | uses: actions/cache@v4 33 | with: 34 | path: vendor 35 | key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 36 | restore-keys: | 37 | ${{ runner.os }}-php- 38 | 39 | - name: Install dependencies 40 | run: composer install --prefer-dist --no-progress 41 | 42 | - name: PHPCS 43 | run: composer run-script phpcs 44 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-mbstring/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-mbstring/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/polyfill-mbstring", 3 | "type": "library", 4 | "description": "Symfony polyfill for the Mbstring extension", 5 | "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Nicolas Grekas", 11 | "email": "p@tchwork.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.2", 20 | "ext-iconv": "*" 21 | }, 22 | "provide": { 23 | "ext-mbstring": "*" 24 | }, 25 | "autoload": { 26 | "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, 27 | "files": [ "bootstrap.php" ] 28 | }, 29 | "suggest": { 30 | "ext-mbstring": "For best performance" 31 | }, 32 | "minimum-stability": "dev", 33 | "extra": { 34 | "thanks": { 35 | "name": "symfony/polyfill", 36 | "url": "https://github.com/symfony/polyfill" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Field/TextareaFormField.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Field; 13 | 14 | /** 15 | * TextareaFormField represents a textarea form field (an HTML textarea tag). 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | class TextareaFormField extends FormField 20 | { 21 | /** 22 | * Initializes the form field. 23 | * 24 | * @return void 25 | * 26 | * @throws \LogicException When node type is incorrect 27 | */ 28 | protected function initialize() 29 | { 30 | if ('textarea' !== $this->node->nodeName) { 31 | throw new \LogicException(\sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); 32 | } 33 | 34 | $this->value = ''; 35 | foreach ($this->node->childNodes as $node) { 36 | $this->value .= $node->wholeText; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/TranslatorInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath; 13 | 14 | use Symfony\Component\CssSelector\Node\SelectorNode; 15 | 16 | /** 17 | * XPath expression translator interface. 18 | * 19 | * This component is a port of the Python cssselect library, 20 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 21 | * 22 | * @author Jean-François Simon 23 | * 24 | * @internal 25 | */ 26 | interface TranslatorInterface 27 | { 28 | /** 29 | * Translates a CSS selector to an XPath expression. 30 | */ 31 | public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string; 32 | 33 | /** 34 | * Translates a parsed selector node to an XPath expression. 35 | */ 36 | public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string; 37 | } 38 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "masterminds/html5", 3 | "description": "An HTML5 parser and serializer.", 4 | "type": "library", 5 | "homepage": "http://masterminds.github.io/html5-php", 6 | "license": "MIT", 7 | "keywords": ["xml", "html", "html5", "dom", "parser", "serializer", "querypath"], 8 | "authors": [ 9 | { 10 | "name": "Matt Butcher", 11 | "email": "technosophos@gmail.com" 12 | }, 13 | { 14 | "name": "Matt Farina", 15 | "email": "matt@mattfarina.com" 16 | }, 17 | { 18 | "name": "Asmir Mustafic", 19 | "email": "goetas@gmail.com" 20 | } 21 | ], 22 | "require" : { 23 | "ext-dom": "*", 24 | "php" : ">=5.3.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit" : "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" 28 | }, 29 | "autoload": { 30 | "psr-4": {"Masterminds\\": "src"} 31 | }, 32 | "autoload-dev": { 33 | "psr-4": {"Masterminds\\HTML5\\Tests\\": "test/HTML5"} 34 | }, 35 | "extra": { 36 | "branch-alias": { 37 | "dev-master": "2.7-dev" 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorExists.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerSelectorExists extends Constraint 18 | { 19 | private string $selector; 20 | 21 | public function __construct(string $selector) 22 | { 23 | $this->selector = $selector; 24 | } 25 | 26 | public function toString(): string 27 | { 28 | return \sprintf('matches selector "%s"', $this->selector); 29 | } 30 | 31 | /** 32 | * @param Crawler $crawler 33 | */ 34 | protected function matches($crawler): bool 35 | { 36 | return 0 < \count($crawler->filter($this->selector)); 37 | } 38 | 39 | /** 40 | * @param Crawler $crawler 41 | */ 42 | protected function failureDescription($crawler): string 43 | { 44 | return 'the Crawler '.$this->toString(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Parser/FileInputStream.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\TokenStream; 16 | 17 | /** 18 | * CSS selector comment handler. 19 | * 20 | * This component is a port of the Python cssselect library, 21 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 22 | * 23 | * @author Jean-François Simon 24 | * 25 | * @internal 26 | */ 27 | class CommentHandler implements HandlerInterface 28 | { 29 | public function handle(Reader $reader, TokenStream $stream): bool 30 | { 31 | if ('/*' !== $reader->getSubstring(2)) { 32 | return false; 33 | } 34 | 35 | $offset = $reader->getOffset('*/'); 36 | 37 | if (false === $offset) { 38 | $reader->moveToEnd(); 39 | } else { 40 | $reader->moveForward($offset + 2); 41 | } 42 | 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/Extension/AbstractExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath\Extension; 13 | 14 | /** 15 | * XPath expression translator abstract extension. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | abstract class AbstractExtension implements ExtensionInterface 25 | { 26 | public function getNodeTranslators(): array 27 | { 28 | return []; 29 | } 30 | 31 | public function getCombinationTranslators(): array 32 | { 33 | return []; 34 | } 35 | 36 | public function getFunctionTranslators(): array 37 | { 38 | return []; 39 | } 40 | 41 | public function getPseudoClassTranslators(): array 42 | { 43 | return []; 44 | } 45 | 46 | public function getAttributeMatchingTranslators(): array 47 | { 48 | return []; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "automattic/vip-block-data-api", 3 | "description": "A WordPress plugin that provides an API to retrieve Gutenberg content as structured JSON", 4 | "type": "wordpress-plugin", 5 | "license": "GPL-2.0-or-later", 6 | "scripts": { 7 | "phpcs": "phpcs", 8 | "phpcs-fix": "phpcbf", 9 | "test": "wp-env run tests-cli --env-cwd=wp-content/plugins/vip-block-data-api ./vendor/bin/phpunit", 10 | "test-multisite": "wp-env run tests-cli --env-cwd=wp-content/plugins/vip-block-data-api /bin/bash -c 'WP_MULTISITE=1 ./vendor/bin/phpunit'", 11 | "test-watch": [ 12 | "Composer\\Config::disableProcessTimeout", 13 | "nodemon -w ./ --ignore vendor/ -e php --exec 'composer run test'" 14 | ] 15 | }, 16 | "require": { 17 | "php": ">=8.1", 18 | "masterminds/html5": "^2.8", 19 | "symfony/dom-crawler": "^6.0", 20 | "symfony/css-selector": "^6.0" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "^9.5", 24 | "wp-phpunit/wp-phpunit": "^6.3", 25 | "phpcompatibility/phpcompatibility-wp": "^2.1", 26 | "automattic/vipwpcs": "^3.0", 27 | "yoast/phpunit-polyfills": "^2.0", 28 | "dms/phpunit-arraysubset-asserts": "^0.5.0" 29 | }, 30 | "config": { 31 | "allow-plugins": { 32 | "dealerdirect/phpcodesniffer-composer-installer": true, 33 | "mnsami/composer-custom-directory-installer": false 34 | }, 35 | "sort-packages": true 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/WhitespaceHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\Token; 16 | use Symfony\Component\CssSelector\Parser\TokenStream; 17 | 18 | /** 19 | * CSS selector whitespace handler. 20 | * 21 | * This component is a port of the Python cssselect library, 22 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 23 | * 24 | * @author Jean-François Simon 25 | * 26 | * @internal 27 | */ 28 | class WhitespaceHandler implements HandlerInterface 29 | { 30 | public function handle(Reader $reader, TokenStream $stream): bool 31 | { 32 | $match = $reader->findPattern('~^[ \t\r\n\f]+~'); 33 | 34 | if (false === $match) { 35 | return false; 36 | } 37 | 38 | $stream->push(new Token(Token::TYPE_WHITESPACE, $match[0], $reader->getPosition())); 39 | $reader->moveForward(\strlen($match[0])); 40 | 41 | return true; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Shortcut/EmptyStringParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Shortcut; 13 | 14 | use Symfony\Component\CssSelector\Node\ElementNode; 15 | use Symfony\Component\CssSelector\Node\SelectorNode; 16 | use Symfony\Component\CssSelector\Parser\ParserInterface; 17 | 18 | /** 19 | * CSS selector class parser shortcut. 20 | * 21 | * This shortcut ensure compatibility with previous version. 22 | * - The parser fails to parse an empty string. 23 | * - In the previous version, an empty string matches each tags. 24 | * 25 | * This component is a port of the Python cssselect library, 26 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 27 | * 28 | * @author Jean-François Simon 29 | * 30 | * @internal 31 | */ 32 | class EmptyStringParser implements ParserInterface 33 | { 34 | public function parse(string $source): array 35 | { 36 | // Matches an empty string 37 | if ('' == $source) { 38 | return [new SelectorNode(new ElementNode(null, '*'))]; 39 | } 40 | 41 | return []; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/parser/blocks/test-image-block.php: -------------------------------------------------------------------------------- 1 | factory()->attachment->create_upload_object( WPCOMVIP__BLOCK_DATA_API__TEST_DATA . '/blue.png' ); 22 | $attachment_url = wp_get_attachment_url( $attachment_id ); 23 | 24 | $html = ' 25 | 26 |
27 | 28 |
29 | 30 | '; 31 | 32 | $expected_blocks = [ 33 | [ 34 | 'name' => 'core/image', 35 | 'attributes' => [ 36 | 'width' => 800, 37 | 'height' => 450, 38 | ], 39 | ], 40 | ]; 41 | 42 | $content_parser = new ContentParser(); 43 | $blocks = $content_parser->parse( $html ); 44 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 45 | $this->assertArraySubset( $expected_blocks, $expected_blocks, true ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorCount.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerSelectorCount extends Constraint 18 | { 19 | public function __construct( 20 | private readonly int $count, 21 | private readonly string $selector, 22 | ) { 23 | } 24 | 25 | public function toString(): string 26 | { 27 | return \sprintf('selector "%s" count is "%d"', $this->selector, $this->count); 28 | } 29 | 30 | /** 31 | * @param Crawler $crawler 32 | */ 33 | protected function matches($crawler): bool 34 | { 35 | return $this->count === \count($crawler->filter($this->selector)); 36 | } 37 | 38 | /** 39 | * @param Crawler $crawler 40 | */ 41 | protected function failureDescription($crawler): string 42 | { 43 | return \sprintf('the Crawler selector "%s" was expected to be found %d time(s) but was found %d time(s)', $this->selector, $this->count, \count($crawler->filter($this->selector))); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-node.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/custom-block', [ 17 | 'description' => [ 18 | 'type' => 'object', 19 | 'source' => 'node', 20 | 'selector' => '.description p', 21 | ], 22 | ] ); 23 | 24 | $html = ' 25 | 26 |
27 |

Description text

28 |
29 | 30 | '; 31 | 32 | $expected_blocks = [ 33 | [ 34 | 'name' => 'test/custom-block', 35 | 'attributes' => [ 36 | 'description' => [ 37 | 'type' => 'p', 38 | 'children' => [ 39 | 'Description text', 40 | ], 41 | ], 42 | ], 43 | ], 44 | ]; 45 | 46 | $content_parser = new ContentParser( $this->get_block_registry() ); 47 | $blocks = $content_parser->parse( $html ); 48 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 49 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/parser/blocks/test-unregistered-block.php: -------------------------------------------------------------------------------- 1 | 17 |

Unknown block content

18 | 19 | '; 20 | 21 | $expected_blocks = [ 22 | [ 23 | 'name' => 'test/unknown-block', 24 | 'attributes' => [ 25 | 'delimiter-attribute' => 'delimiter-value', 26 | ], 27 | ], 28 | ]; 29 | 30 | $expected_warnings = [ 31 | 'Block type "test/unknown-block" is not server-side registered. Sourced block attributes will not be available.', 32 | ]; 33 | 34 | $content_parser = new ContentParser( $this->get_block_registry() ); 35 | $blocks = $content_parser->parse( $html ); 36 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 37 | $this->assertArrayHasKey( 'warnings', $blocks, sprintf( 'Expected parser to have warnings, none received: %s', wp_json_encode( $blocks ) ) ); 38 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 39 | $this->assertEqualSets( $expected_warnings, $blocks['warnings'] ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/HashNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a "#" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class HashNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private string $id; 28 | 29 | public function __construct(NodeInterface $selector, string $id) 30 | { 31 | $this->selector = $selector; 32 | $this->id = $id; 33 | } 34 | 35 | public function getSelector(): NodeInterface 36 | { 37 | return $this->selector; 38 | } 39 | 40 | public function getId(): string 41 | { 42 | return $this->id; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return $this->selector->getSpecificity()->plus(new Specificity(1, 0, 0)); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | return \sprintf('%s[%s#%s]', $this->getNodeName(), $this->selector, $this->id); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/ClassNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a "." node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class ClassNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private string $name; 28 | 29 | public function __construct(NodeInterface $selector, string $name) 30 | { 31 | $this->selector = $selector; 32 | $this->name = $name; 33 | } 34 | 35 | public function getSelector(): NodeInterface 36 | { 37 | return $this->selector; 38 | } 39 | 40 | public function getName(): string 41 | { 42 | return $this->name; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | return \sprintf('%s[%s.%s]', $this->getNodeName(), $this->selector, $this->name); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Shortcut/ElementParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Shortcut; 13 | 14 | use Symfony\Component\CssSelector\Node\ElementNode; 15 | use Symfony\Component\CssSelector\Node\SelectorNode; 16 | use Symfony\Component\CssSelector\Parser\ParserInterface; 17 | 18 | /** 19 | * CSS selector element parser shortcut. 20 | * 21 | * This component is a port of the Python cssselect library, 22 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 23 | * 24 | * @author Jean-François Simon 25 | * 26 | * @internal 27 | */ 28 | class ElementParser implements ParserInterface 29 | { 30 | public function parse(string $source): array 31 | { 32 | // Matches an optional namespace, required element or `*` 33 | // $source = 'testns|testel'; 34 | // $matches = array (size=3) 35 | // 0 => string 'testns|testel' (length=13) 36 | // 1 => string 'testns' (length=6) 37 | // 2 => string 'testel' (length=6) 38 | if (preg_match('/^(?:([a-z]++)\|)?([\w-]++|\*)$/i', trim($source), $matches)) { 39 | return [new SelectorNode(new ElementNode($matches[1] ?: null, $matches[2]))]; 40 | } 41 | 42 | return []; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorTextSame.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerSelectorTextSame extends Constraint 18 | { 19 | private string $selector; 20 | private string $expectedText; 21 | 22 | public function __construct(string $selector, string $expectedText) 23 | { 24 | $this->selector = $selector; 25 | $this->expectedText = $expectedText; 26 | } 27 | 28 | public function toString(): string 29 | { 30 | return \sprintf('has a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); 31 | } 32 | 33 | /** 34 | * @param Crawler $crawler 35 | */ 36 | protected function matches($crawler): bool 37 | { 38 | $crawler = $crawler->filter($this->selector); 39 | if (!\count($crawler)) { 40 | return false; 41 | } 42 | 43 | return $this->expectedText === trim($crawler->text(null, true)); 44 | } 45 | 46 | /** 47 | * @param Crawler $crawler 48 | */ 49 | protected function failureDescription($crawler): string 50 | { 51 | return 'the Crawler '.$this->toString(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/PseudoNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a ":" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class PseudoNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private string $identifier; 28 | 29 | public function __construct(NodeInterface $selector, string $identifier) 30 | { 31 | $this->selector = $selector; 32 | $this->identifier = strtolower($identifier); 33 | } 34 | 35 | public function getSelector(): NodeInterface 36 | { 37 | return $this->selector; 38 | } 39 | 40 | public function getIdentifier(): string 41 | { 42 | return $this->identifier; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | return \sprintf('%s[%s:%s]', $this->getNodeName(), $this->selector, $this->identifier); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/ElementNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a "|" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class ElementNode extends AbstractNode 25 | { 26 | private ?string $namespace; 27 | private ?string $element; 28 | 29 | public function __construct(?string $namespace = null, ?string $element = null) 30 | { 31 | $this->namespace = $namespace; 32 | $this->element = $element; 33 | } 34 | 35 | public function getNamespace(): ?string 36 | { 37 | return $this->namespace; 38 | } 39 | 40 | public function getElement(): ?string 41 | { 42 | return $this->element; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return new Specificity(0, 0, $this->element ? 1 : 0); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | $element = $this->element ?: '*'; 53 | 54 | return \sprintf('%s[%s]', $this->getNodeName(), $this->namespace ? $this->namespace.'|'.$element : $element); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/NumberHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\Token; 16 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; 17 | use Symfony\Component\CssSelector\Parser\TokenStream; 18 | 19 | /** 20 | * CSS selector comment handler. 21 | * 22 | * This component is a port of the Python cssselect library, 23 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 24 | * 25 | * @author Jean-François Simon 26 | * 27 | * @internal 28 | */ 29 | class NumberHandler implements HandlerInterface 30 | { 31 | private TokenizerPatterns $patterns; 32 | 33 | public function __construct(TokenizerPatterns $patterns) 34 | { 35 | $this->patterns = $patterns; 36 | } 37 | 38 | public function handle(Reader $reader, TokenStream $stream): bool 39 | { 40 | $match = $reader->findPattern($this->patterns->getNumberPattern()); 41 | 42 | if (!$match) { 43 | return false; 44 | } 45 | 46 | $stream->push(new Token(Token::TYPE_NUMBER, $match[0], $reader->getPosition())); 47 | $reader->moveForward(\strlen($match[0])); 48 | 49 | return true; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/NegationNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a ":not()" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class NegationNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private NodeInterface $subSelector; 28 | 29 | public function __construct(NodeInterface $selector, NodeInterface $subSelector) 30 | { 31 | $this->selector = $selector; 32 | $this->subSelector = $subSelector; 33 | } 34 | 35 | public function getSelector(): NodeInterface 36 | { 37 | return $this->selector; 38 | } 39 | 40 | public function getSubSelector(): NodeInterface 41 | { 42 | return $this->subSelector; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | return \sprintf('%s[%s:not(%s)]', $this->getNodeName(), $this->selector, $this->subSelector); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Field; 13 | 14 | /** 15 | * InputFormField represents an input form field (an HTML input tag). 16 | * 17 | * For inputs with type of file, checkbox, or radio, there are other more 18 | * specialized classes (cf. FileFormField and ChoiceFormField). 19 | * 20 | * @author Fabien Potencier 21 | */ 22 | class InputFormField extends FormField 23 | { 24 | /** 25 | * Initializes the form field. 26 | * 27 | * @return void 28 | * 29 | * @throws \LogicException When node type is incorrect 30 | */ 31 | protected function initialize() 32 | { 33 | if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { 34 | throw new \LogicException(\sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); 35 | } 36 | 37 | $type = strtolower($this->node->getAttribute('type')); 38 | if ('checkbox' === $type) { 39 | throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); 40 | } 41 | 42 | if ('file' === $type) { 43 | throw new \LogicException('File inputs should be instances of FileFormField.'); 44 | } 45 | 46 | $this->value = $this->node->getAttribute('value'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/SelectorNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a "(::|:)" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class SelectorNode extends AbstractNode 25 | { 26 | private NodeInterface $tree; 27 | private ?string $pseudoElement; 28 | 29 | public function __construct(NodeInterface $tree, ?string $pseudoElement = null) 30 | { 31 | $this->tree = $tree; 32 | $this->pseudoElement = $pseudoElement ? strtolower($pseudoElement) : null; 33 | } 34 | 35 | public function getTree(): NodeInterface 36 | { 37 | return $this->tree; 38 | } 39 | 40 | public function getPseudoElement(): ?string 41 | { 42 | return $this->pseudoElement; 43 | } 44 | 45 | public function getSpecificity(): Specificity 46 | { 47 | return $this->tree->getSpecificity()->plus(new Specificity(0, 0, $this->pseudoElement ? 1 : 0)); 48 | } 49 | 50 | public function __toString(): string 51 | { 52 | return \sprintf('%s[%s%s]', $this->getNodeName(), $this->tree, $this->pseudoElement ? '::'.$this->pseudoElement : ''); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerSelectorAttributeValueSame.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerSelectorAttributeValueSame extends Constraint 18 | { 19 | private string $selector; 20 | private string $attribute; 21 | private string $expectedText; 22 | 23 | public function __construct(string $selector, string $attribute, string $expectedText) 24 | { 25 | $this->selector = $selector; 26 | $this->attribute = $attribute; 27 | $this->expectedText = $expectedText; 28 | } 29 | 30 | public function toString(): string 31 | { 32 | return \sprintf('has a node matching selector "%s" with attribute "%s" of value "%s"', $this->selector, $this->attribute, $this->expectedText); 33 | } 34 | 35 | /** 36 | * @param Crawler $crawler 37 | */ 38 | protected function matches($crawler): bool 39 | { 40 | $crawler = $crawler->filter($this->selector); 41 | if (!\count($crawler)) { 42 | return false; 43 | } 44 | 45 | return $this->expectedText === trim($crawler->attr($this->attribute) ?? ''); 46 | } 47 | 48 | /** 49 | * @param Crawler $crawler 50 | */ 51 | protected function failureDescription($crawler): string 52 | { 53 | return 'the Crawler '.$this->toString(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Shortcut/HashParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Shortcut; 13 | 14 | use Symfony\Component\CssSelector\Node\ElementNode; 15 | use Symfony\Component\CssSelector\Node\HashNode; 16 | use Symfony\Component\CssSelector\Node\SelectorNode; 17 | use Symfony\Component\CssSelector\Parser\ParserInterface; 18 | 19 | /** 20 | * CSS selector hash parser shortcut. 21 | * 22 | * This component is a port of the Python cssselect library, 23 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 24 | * 25 | * @author Jean-François Simon 26 | * 27 | * @internal 28 | */ 29 | class HashParser implements ParserInterface 30 | { 31 | public function parse(string $source): array 32 | { 33 | // Matches an optional namespace, optional element, and required id 34 | // $source = 'test|input#ab6bd_field'; 35 | // $matches = array (size=4) 36 | // 0 => string 'test|input#ab6bd_field' (length=22) 37 | // 1 => string 'test' (length=4) 38 | // 2 => string 'input' (length=5) 39 | // 3 => string 'ab6bd_field' (length=11) 40 | if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+#([\w-]++)$/i', trim($source), $matches)) { 41 | return [ 42 | new SelectorNode(new HashNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), 43 | ]; 44 | } 45 | 46 | return []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Shortcut/ClassParser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Shortcut; 13 | 14 | use Symfony\Component\CssSelector\Node\ClassNode; 15 | use Symfony\Component\CssSelector\Node\ElementNode; 16 | use Symfony\Component\CssSelector\Node\SelectorNode; 17 | use Symfony\Component\CssSelector\Parser\ParserInterface; 18 | 19 | /** 20 | * CSS selector class parser shortcut. 21 | * 22 | * This component is a port of the Python cssselect library, 23 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 24 | * 25 | * @author Jean-François Simon 26 | * 27 | * @internal 28 | */ 29 | class ClassParser implements ParserInterface 30 | { 31 | public function parse(string $source): array 32 | { 33 | // Matches an optional namespace, optional element, and required class 34 | // $source = 'test|input.ab6bd_field'; 35 | // $matches = array (size=4) 36 | // 0 => string 'test|input.ab6bd_field' (length=22) 37 | // 1 => string 'test' (length=4) 38 | // 2 => string 'input' (length=5) 39 | // 3 => string 'ab6bd_field' (length=11) 40 | if (preg_match('/^(?:([a-z]++)\|)?+([\w-]++|\*)?+\.([\w-]++)$/i', trim($source), $matches)) { 41 | return [ 42 | new SelectorNode(new ClassNode(new ElementNode($matches[1] ?: null, $matches[2] ?: null), $matches[3])), 43 | ]; 44 | } 45 | 46 | return []; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-ctype/bootstrap.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Symfony\Polyfill\Ctype as p; 13 | 14 | if (\PHP_VERSION_ID >= 80000) { 15 | return require __DIR__.'/bootstrap80.php'; 16 | } 17 | 18 | if (!function_exists('ctype_alnum')) { 19 | function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); } 20 | } 21 | if (!function_exists('ctype_alpha')) { 22 | function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); } 23 | } 24 | if (!function_exists('ctype_cntrl')) { 25 | function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); } 26 | } 27 | if (!function_exists('ctype_digit')) { 28 | function ctype_digit($text) { return p\Ctype::ctype_digit($text); } 29 | } 30 | if (!function_exists('ctype_graph')) { 31 | function ctype_graph($text) { return p\Ctype::ctype_graph($text); } 32 | } 33 | if (!function_exists('ctype_lower')) { 34 | function ctype_lower($text) { return p\Ctype::ctype_lower($text); } 35 | } 36 | if (!function_exists('ctype_print')) { 37 | function ctype_print($text) { return p\Ctype::ctype_print($text); } 38 | } 39 | if (!function_exists('ctype_punct')) { 40 | function ctype_punct($text) { return p\Ctype::ctype_punct($text); } 41 | } 42 | if (!function_exists('ctype_space')) { 43 | function ctype_space($text) { return p\Ctype::ctype_space($text); } 44 | } 45 | if (!function_exists('ctype_upper')) { 46 | function ctype_upper($text) { return p\Ctype::ctype_upper($text); } 47 | } 48 | if (!function_exists('ctype_xdigit')) { 49 | function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Parser/CharacterReference.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Symfony\Polyfill\Ctype as p; 13 | 14 | if (!function_exists('ctype_alnum')) { 15 | function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); } 16 | } 17 | if (!function_exists('ctype_alpha')) { 18 | function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); } 19 | } 20 | if (!function_exists('ctype_cntrl')) { 21 | function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); } 22 | } 23 | if (!function_exists('ctype_digit')) { 24 | function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); } 25 | } 26 | if (!function_exists('ctype_graph')) { 27 | function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); } 28 | } 29 | if (!function_exists('ctype_lower')) { 30 | function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); } 31 | } 32 | if (!function_exists('ctype_print')) { 33 | function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); } 34 | } 35 | if (!function_exists('ctype_punct')) { 36 | function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); } 37 | } 38 | if (!function_exists('ctype_space')) { 39 | function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); } 40 | } 41 | if (!function_exists('ctype_upper')) { 42 | function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); } 43 | } 44 | if (!function_exists('ctype_xdigit')) { 45 | function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/HashHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\Token; 16 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; 17 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; 18 | use Symfony\Component\CssSelector\Parser\TokenStream; 19 | 20 | /** 21 | * CSS selector comment handler. 22 | * 23 | * This component is a port of the Python cssselect library, 24 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 25 | * 26 | * @author Jean-François Simon 27 | * 28 | * @internal 29 | */ 30 | class HashHandler implements HandlerInterface 31 | { 32 | private TokenizerPatterns $patterns; 33 | private TokenizerEscaping $escaping; 34 | 35 | public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) 36 | { 37 | $this->patterns = $patterns; 38 | $this->escaping = $escaping; 39 | } 40 | 41 | public function handle(Reader $reader, TokenStream $stream): bool 42 | { 43 | $match = $reader->findPattern($this->patterns->getHashPattern()); 44 | 45 | if (!$match) { 46 | return false; 47 | } 48 | 49 | $value = $this->escaping->escapeUnicode($match[1]); 50 | $stream->push(new Token(Token::TYPE_HASH, $value, $reader->getPosition())); 51 | $reader->moveForward(\strlen($match[0])); 52 | 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/Extension/ExtensionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath\Extension; 13 | 14 | /** 15 | * XPath expression translator extension interface. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | interface ExtensionInterface 25 | { 26 | /** 27 | * Returns node translators. 28 | * 29 | * These callables will receive the node as first argument and the translator as second argument. 30 | * 31 | * @return callable[] 32 | */ 33 | public function getNodeTranslators(): array; 34 | 35 | /** 36 | * Returns combination translators. 37 | * 38 | * @return callable[] 39 | */ 40 | public function getCombinationTranslators(): array; 41 | 42 | /** 43 | * Returns function translators. 44 | * 45 | * @return callable[] 46 | */ 47 | public function getFunctionTranslators(): array; 48 | 49 | /** 50 | * Returns pseudo-class translators. 51 | * 52 | * @return callable[] 53 | */ 54 | public function getPseudoClassTranslators(): array; 55 | 56 | /** 57 | * Returns attribute operation translators. 58 | * 59 | * @return callable[] 60 | */ 61 | public function getAttributeMatchingTranslators(): array; 62 | 63 | /** 64 | * Returns extension name. 65 | */ 66 | public function getName(): string; 67 | } 68 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | $filesToLoad = \Composer\Autoload\ComposerStaticInit0e98fdf7ca952c8f4cb3c82e525d431a::$files; 37 | $requireFile = \Closure::bind(static function ($fileIdentifier, $file) { 38 | if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { 39 | $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; 40 | 41 | require $file; 42 | } 43 | }, null, null); 44 | foreach ($filesToLoad as $fileIdentifier => $file) { 45 | $requireFile($fileIdentifier, $file); 46 | } 47 | 48 | return $loader; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/IdentifierHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Parser\Reader; 15 | use Symfony\Component\CssSelector\Parser\Token; 16 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; 17 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; 18 | use Symfony\Component\CssSelector\Parser\TokenStream; 19 | 20 | /** 21 | * CSS selector comment handler. 22 | * 23 | * This component is a port of the Python cssselect library, 24 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 25 | * 26 | * @author Jean-François Simon 27 | * 28 | * @internal 29 | */ 30 | class IdentifierHandler implements HandlerInterface 31 | { 32 | private TokenizerPatterns $patterns; 33 | private TokenizerEscaping $escaping; 34 | 35 | public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) 36 | { 37 | $this->patterns = $patterns; 38 | $this->escaping = $escaping; 39 | } 40 | 41 | public function handle(Reader $reader, TokenStream $stream): bool 42 | { 43 | $match = $reader->findPattern($this->patterns->getIdentifierPattern()); 44 | 45 | if (!$match) { 46 | return false; 47 | } 48 | 49 | $value = $this->escaping->escapeUnicode($match[0]); 50 | $stream->push(new Token(Token::TYPE_IDENTIFIER, $value, $reader->getPosition())); 51 | $reader->moveForward(\strlen($match[0])); 52 | 53 | return true; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name: PHPUnit 2 | 3 | on: pull_request 4 | 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.ref }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | test: 11 | # Alias 'master' to 'latest' 12 | name: WP ${{ matrix.wp == 'master' && 'latest' || matrix.wp }} and PHP ${{ matrix.php }} 13 | runs-on: ubuntu-latest 14 | continue-on-error: ${{ matrix.allowed_failure }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | # Check lowest supported WP version, with the lowest supported PHP. 21 | - php: "8.1" 22 | wp: "6.0" 23 | allowed_failure: false 24 | # Check latest WP with the lowest supported PHP. 25 | - php: "8.1" 26 | wp: "master" 27 | allowed_failure: false 28 | # Check latest WP with the highest supported PHP. 29 | - php: "8.3" 30 | wp: "master" 31 | allowed_failure: false 32 | 33 | steps: 34 | - name: Checkout code 35 | uses: actions/checkout@v4 36 | 37 | - name: Install wordpress environment 38 | run: npm -g install @wordpress/env 39 | 40 | - name: Setup PHP ${{ matrix.php }} 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: ${{ matrix.php }} 44 | tools: composer 45 | 46 | - name: Install Composer dependencies 47 | uses: ramsey/composer-install@v3 48 | with: 49 | composer-options: --prefer-dist --no-progress 50 | 51 | - name: Setup wp-env 52 | run: wp-env start 53 | env: 54 | WP_ENV_CORE: WordPress/WordPress#${{ matrix.wp }} 55 | WP_ENV_PHP_VERSION: ${{ matrix.php }} 56 | 57 | - name: PHPUnit 58 | run: composer test 59 | 60 | - name: PHPUnit multisite 61 | run: composer run-script test-multisite 62 | -------------------------------------------------------------------------------- /tests/registry-test-case.php: -------------------------------------------------------------------------------- 1 | get_all_registered() as $block_type ) { 20 | if ( 'core/' === substr( $block_type->name, 0, 5 ) ) { 21 | continue; 22 | } 23 | 24 | $block_registry->unregister( $block_type->name ); 25 | } 26 | 27 | if ( class_exists( 'WP_Block_Bindings_Registry' ) ) { 28 | // Unregister non-core block bindings. 29 | $block_bindings_registry = WP_Block_Bindings_Registry::get_instance(); 30 | foreach ( $block_bindings_registry->get_all_registered() as $source ) { 31 | if ( 'core/' === substr( $source->name, 0, 5 ) ) { 32 | continue; 33 | } 34 | 35 | $block_bindings_registry->unregister( $source->name ); 36 | } 37 | } 38 | 39 | parent::tearDown(); 40 | } 41 | 42 | /* Helper methods */ 43 | 44 | protected function get_block_registry(): WP_Block_Type_Registry { 45 | return WP_Block_Type_Registry::get_instance(); 46 | } 47 | 48 | protected function register_block_with_attributes( string $block_name, array $attributes, array $additional_args = [] ): void { 49 | $block_type_args = array_merge( [ 50 | 'apiVersion' => 2, 51 | 'attributes' => $attributes, 52 | ], $additional_args ); 53 | 54 | $this->get_block_registry()->register( $block_name, $block_type_args ); 55 | } 56 | 57 | protected function register_block_bindings_source( string $source, array $args ): void { 58 | WP_Block_Bindings_Registry::get_instance()->register( $source, $args ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Parser/README.md: -------------------------------------------------------------------------------- 1 | # The Parser Model 2 | 3 | The parser model here follows the model in section 4 | [8.2.1](http://www.w3.org/TR/2012/CR-html5-20121217/syntax.html#parsing) 5 | of the HTML5 specification, though we do not assume a networking layer. 6 | 7 | [ InputStream ] // Generic support for reading input. 8 | || 9 | [ Scanner ] // Breaks down the stream into characters. 10 | || 11 | [ Tokenizer ] // Groups characters into syntactic 12 | || 13 | [ Tree Builder ] // Organizes units into a tree of objects 14 | || 15 | [ DOM Document ] // The final state of the parsed document. 16 | 17 | 18 | ## InputStream 19 | 20 | This is an interface with at least two concrete implementations: 21 | 22 | - StringInputStream: Reads an HTML5 string. 23 | - FileInputStream: Reads an HTML5 file. 24 | 25 | ## Scanner 26 | 27 | This is a mechanical piece of the parser. 28 | 29 | ## Tokenizer 30 | 31 | This follows section 8.4 of the HTML5 spec. It is (roughly) a recursive 32 | descent parser. (Though there are plenty of optimizations that are less 33 | than purely functional. 34 | 35 | ## EventHandler and DOMTree 36 | 37 | EventHandler is the interface for tree builders. Since not all 38 | implementations will necessarily build trees, we've chosen a more 39 | generic name. 40 | 41 | The event handler emits tokens during tokenization. 42 | 43 | The DOMTree is an event handler that builds a DOM tree. The output of 44 | the DOMTree builder is a DOMDocument. 45 | 46 | ## DOMDocument 47 | 48 | PHP has a DOMDocument class built-in (technically, it's part of libxml.) 49 | We use that, thus rendering the output of this process compatible with 50 | SimpleXML, QueryPath, and many other XML/HTML processing tools. 51 | 52 | For cases where the HTML5 is a fragment of a HTML5 document a 53 | DOMDocumentFragment is returned instead. This is another built-in class. 54 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/CombinedSelectorNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a combined node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class CombinedSelectorNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private string $combinator; 28 | private NodeInterface $subSelector; 29 | 30 | public function __construct(NodeInterface $selector, string $combinator, NodeInterface $subSelector) 31 | { 32 | $this->selector = $selector; 33 | $this->combinator = $combinator; 34 | $this->subSelector = $subSelector; 35 | } 36 | 37 | public function getSelector(): NodeInterface 38 | { 39 | return $this->selector; 40 | } 41 | 42 | public function getCombinator(): string 43 | { 44 | return $this->combinator; 45 | } 46 | 47 | public function getSubSelector(): NodeInterface 48 | { 49 | return $this->subSelector; 50 | } 51 | 52 | public function getSpecificity(): Specificity 53 | { 54 | return $this->selector->getSpecificity()->plus($this->subSelector->getSpecificity()); 55 | } 56 | 57 | public function __toString(): string 58 | { 59 | $combinator = ' ' === $this->combinator ? '' : $this->combinator; 60 | 61 | return \sprintf('%s[%s %s %s]', $this->getNodeName(), $this->selector, $combinator, $this->subSelector); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/InstructionProcessor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerSelectorTextContains extends Constraint 18 | { 19 | private string $selector; 20 | private string $expectedText; 21 | private bool $hasNode = false; 22 | private string $nodeText; 23 | 24 | public function __construct(string $selector, string $expectedText) 25 | { 26 | $this->selector = $selector; 27 | $this->expectedText = $expectedText; 28 | } 29 | 30 | public function toString(): string 31 | { 32 | if ($this->hasNode) { 33 | return \sprintf('the text "%s" of the node matching selector "%s" contains "%s"', $this->nodeText, $this->selector, $this->expectedText); 34 | } 35 | 36 | return \sprintf('the Crawler has a node matching selector "%s"', $this->selector); 37 | } 38 | 39 | /** 40 | * @param Crawler $crawler 41 | */ 42 | protected function matches($crawler): bool 43 | { 44 | $crawler = $crawler->filter($this->selector); 45 | if (!\count($crawler)) { 46 | $this->hasNode = false; 47 | 48 | return false; 49 | } 50 | 51 | $this->hasNode = true; 52 | $this->nodeText = $crawler->text(null, true); 53 | 54 | return str_contains($this->nodeText, $this->expectedText); 55 | } 56 | 57 | /** 58 | * @param Crawler $crawler 59 | */ 60 | protected function failureDescription($crawler): string 61 | { 62 | return $this->toString(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Exception/SyntaxErrorException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Exception; 13 | 14 | use Symfony\Component\CssSelector\Parser\Token; 15 | 16 | /** 17 | * ParseException is thrown when a CSS selector syntax is not valid. 18 | * 19 | * This component is a port of the Python cssselect library, 20 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 21 | * 22 | * @author Jean-François Simon 23 | */ 24 | class SyntaxErrorException extends ParseException 25 | { 26 | public static function unexpectedToken(string $expectedValue, Token $foundToken): self 27 | { 28 | return new self(\sprintf('Expected %s, but %s found.', $expectedValue, $foundToken)); 29 | } 30 | 31 | public static function pseudoElementFound(string $pseudoElement, string $unexpectedLocation): self 32 | { 33 | return new self(\sprintf('Unexpected pseudo-element "::%s" found %s.', $pseudoElement, $unexpectedLocation)); 34 | } 35 | 36 | public static function unclosedString(int $position): self 37 | { 38 | return new self(\sprintf('Unclosed/invalid string at %s.', $position)); 39 | } 40 | 41 | public static function nestedNot(): self 42 | { 43 | return new self('Got nested ::not().'); 44 | } 45 | 46 | public static function notAtTheStartOfASelector(string $pseudoElement): self 47 | { 48 | return new self(\sprintf('Got immediate child pseudo-element ":%s" not at the start of a selector', $pseudoElement)); 49 | } 50 | 51 | public static function stringAsFunctionArgument(): self 52 | { 53 | return new self('String not allowed as function argument.'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextSame.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerAnySelectorTextSame extends Constraint 18 | { 19 | private string $selector; 20 | private string $expectedText; 21 | 22 | public function __construct(string $selector, string $expectedText) 23 | { 24 | $this->selector = $selector; 25 | $this->expectedText = $expectedText; 26 | } 27 | 28 | public function toString(): string 29 | { 30 | return \sprintf('has at least a node matching selector "%s" with content "%s"', $this->selector, $this->expectedText); 31 | } 32 | 33 | protected function matches($other): bool 34 | { 35 | if (!$other instanceof Crawler) { 36 | throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); 37 | } 38 | 39 | $other = $other->filter($this->selector); 40 | if (!\count($other)) { 41 | return false; 42 | } 43 | 44 | $nodes = $other->each(fn (Crawler $node) => trim($node->text(null, true))); 45 | 46 | return \in_array($this->expectedText, $nodes, true); 47 | } 48 | 49 | protected function failureDescription($other): string 50 | { 51 | if (!$other instanceof Crawler) { 52 | throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); 53 | } 54 | 55 | return 'the Crawler '.$this->toString(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Tokenizer/TokenizerEscaping.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Tokenizer; 13 | 14 | /** 15 | * CSS selector tokenizer escaping applier. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class TokenizerEscaping 25 | { 26 | private TokenizerPatterns $patterns; 27 | 28 | public function __construct(TokenizerPatterns $patterns) 29 | { 30 | $this->patterns = $patterns; 31 | } 32 | 33 | public function escapeUnicode(string $value): string 34 | { 35 | $value = $this->replaceUnicodeSequences($value); 36 | 37 | return preg_replace($this->patterns->getSimpleEscapePattern(), '$1', $value); 38 | } 39 | 40 | public function escapeUnicodeAndNewLine(string $value): string 41 | { 42 | $value = preg_replace($this->patterns->getNewLineEscapePattern(), '', $value); 43 | 44 | return $this->escapeUnicode($value); 45 | } 46 | 47 | private function replaceUnicodeSequences(string $value): string 48 | { 49 | return preg_replace_callback($this->patterns->getUnicodeEscapePattern(), function ($match) { 50 | $c = hexdec($match[1]); 51 | 52 | if (0x80 > $c %= 0x200000) { 53 | return \chr($c); 54 | } 55 | if (0x800 > $c) { 56 | return \chr(0xC0 | $c >> 6).\chr(0x80 | $c & 0x3F); 57 | } 58 | if (0x10000 > $c) { 59 | return \chr(0xE0 | $c >> 12).\chr(0x80 | $c >> 6 & 0x3F).\chr(0x80 | $c & 0x3F); 60 | } 61 | 62 | return ''; 63 | }, $value); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/FunctionNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | use Symfony\Component\CssSelector\Parser\Token; 15 | 16 | /** 17 | * Represents a ":()" node. 18 | * 19 | * This component is a port of the Python cssselect library, 20 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 21 | * 22 | * @author Jean-François Simon 23 | * 24 | * @internal 25 | */ 26 | class FunctionNode extends AbstractNode 27 | { 28 | private NodeInterface $selector; 29 | private string $name; 30 | private array $arguments; 31 | 32 | /** 33 | * @param Token[] $arguments 34 | */ 35 | public function __construct(NodeInterface $selector, string $name, array $arguments = []) 36 | { 37 | $this->selector = $selector; 38 | $this->name = strtolower($name); 39 | $this->arguments = $arguments; 40 | } 41 | 42 | public function getSelector(): NodeInterface 43 | { 44 | return $this->selector; 45 | } 46 | 47 | public function getName(): string 48 | { 49 | return $this->name; 50 | } 51 | 52 | /** 53 | * @return Token[] 54 | */ 55 | public function getArguments(): array 56 | { 57 | return $this->arguments; 58 | } 59 | 60 | public function getSpecificity(): Specificity 61 | { 62 | return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); 63 | } 64 | 65 | public function __toString(): string 66 | { 67 | $arguments = implode(', ', array_map(fn (Token $token) => "'".$token->getValue()."'", $this->arguments)); 68 | 69 | return \sprintf('%s[%s:%s(%s)]', $this->getNodeName(), $this->selector, $this->name, $arguments ? '['.$arguments.']' : ''); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/Specificity.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a node specificity. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @see http://www.w3.org/TR/selectors/#specificity 21 | * 22 | * @author Jean-François Simon 23 | * 24 | * @internal 25 | */ 26 | class Specificity 27 | { 28 | public const A_FACTOR = 100; 29 | public const B_FACTOR = 10; 30 | public const C_FACTOR = 1; 31 | 32 | private int $a; 33 | private int $b; 34 | private int $c; 35 | 36 | public function __construct(int $a, int $b, int $c) 37 | { 38 | $this->a = $a; 39 | $this->b = $b; 40 | $this->c = $c; 41 | } 42 | 43 | public function plus(self $specificity): self 44 | { 45 | return new self($this->a + $specificity->a, $this->b + $specificity->b, $this->c + $specificity->c); 46 | } 47 | 48 | public function getValue(): int 49 | { 50 | return $this->a * self::A_FACTOR + $this->b * self::B_FACTOR + $this->c * self::C_FACTOR; 51 | } 52 | 53 | /** 54 | * Returns -1 if the object specificity is lower than the argument, 55 | * 0 if they are equal, and 1 if the argument is lower. 56 | */ 57 | public function compareTo(self $specificity): int 58 | { 59 | if ($this->a !== $specificity->a) { 60 | return $this->a > $specificity->a ? 1 : -1; 61 | } 62 | 63 | if ($this->b !== $specificity->b) { 64 | return $this->b > $specificity->b ? 1 : -1; 65 | } 66 | 67 | if ($this->c !== $specificity->c) { 68 | return $this->c > $specificity->c ? 1 : -1; 69 | } 70 | 71 | return 0; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/Extension/CombinationExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath\Extension; 13 | 14 | use Symfony\Component\CssSelector\XPath\XPathExpr; 15 | 16 | /** 17 | * XPath expression translator combination extension. 18 | * 19 | * This component is a port of the Python cssselect library, 20 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 21 | * 22 | * @author Jean-François Simon 23 | * 24 | * @internal 25 | */ 26 | class CombinationExtension extends AbstractExtension 27 | { 28 | public function getCombinationTranslators(): array 29 | { 30 | return [ 31 | ' ' => $this->translateDescendant(...), 32 | '>' => $this->translateChild(...), 33 | '+' => $this->translateDirectAdjacent(...), 34 | '~' => $this->translateIndirectAdjacent(...), 35 | ]; 36 | } 37 | 38 | public function translateDescendant(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr 39 | { 40 | return $xpath->join('/descendant-or-self::*/', $combinedXpath); 41 | } 42 | 43 | public function translateChild(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr 44 | { 45 | return $xpath->join('/', $combinedXpath); 46 | } 47 | 48 | public function translateDirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr 49 | { 50 | return $xpath 51 | ->join('/following-sibling::', $combinedXpath) 52 | ->addNameTest() 53 | ->addCondition('position() = 1'); 54 | } 55 | 56 | public function translateIndirectAdjacent(XPathExpr $xpath, XPathExpr $combinedXpath): XPathExpr 57 | { 58 | return $xpath->join('/following-sibling::', $combinedXpath); 59 | } 60 | 61 | public function getName(): string 62 | { 63 | return 'combination'; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vip-block-data-api.php: -------------------------------------------------------------------------------- 1 | 28 |
29 |

30 |
31 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser; 13 | 14 | /** 15 | * CSS selector reader. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class Reader 25 | { 26 | private string $source; 27 | private int $length; 28 | private int $position = 0; 29 | 30 | public function __construct(string $source) 31 | { 32 | $this->source = $source; 33 | $this->length = \strlen($source); 34 | } 35 | 36 | public function isEOF(): bool 37 | { 38 | return $this->position >= $this->length; 39 | } 40 | 41 | public function getPosition(): int 42 | { 43 | return $this->position; 44 | } 45 | 46 | public function getRemainingLength(): int 47 | { 48 | return $this->length - $this->position; 49 | } 50 | 51 | public function getSubstring(int $length, int $offset = 0): string 52 | { 53 | return substr($this->source, $this->position + $offset, $length); 54 | } 55 | 56 | /** 57 | * @return int|false 58 | */ 59 | public function getOffset(string $string): int|bool 60 | { 61 | $position = strpos($this->source, $string, $this->position); 62 | 63 | return false === $position ? false : $position - $this->position; 64 | } 65 | 66 | public function findPattern(string $pattern): array|false 67 | { 68 | $source = substr($this->source, $this->position); 69 | 70 | if (preg_match($pattern, $source, $matches)) { 71 | return $matches; 72 | } 73 | 74 | return false; 75 | } 76 | 77 | public function moveForward(int $length): void 78 | { 79 | $this->position += $length; 80 | } 81 | 82 | public function moveToEnd(): void 83 | { 84 | $this->position = $this->length; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Tokenizer/Tokenizer.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Tokenizer; 13 | 14 | use Symfony\Component\CssSelector\Parser\Handler; 15 | use Symfony\Component\CssSelector\Parser\Reader; 16 | use Symfony\Component\CssSelector\Parser\Token; 17 | use Symfony\Component\CssSelector\Parser\TokenStream; 18 | 19 | /** 20 | * CSS selector tokenizer. 21 | * 22 | * This component is a port of the Python cssselect library, 23 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 24 | * 25 | * @author Jean-François Simon 26 | * 27 | * @internal 28 | */ 29 | class Tokenizer 30 | { 31 | /** 32 | * @var Handler\HandlerInterface[] 33 | */ 34 | private array $handlers; 35 | 36 | public function __construct() 37 | { 38 | $patterns = new TokenizerPatterns(); 39 | $escaping = new TokenizerEscaping($patterns); 40 | 41 | $this->handlers = [ 42 | new Handler\WhitespaceHandler(), 43 | new Handler\IdentifierHandler($patterns, $escaping), 44 | new Handler\HashHandler($patterns, $escaping), 45 | new Handler\StringHandler($patterns, $escaping), 46 | new Handler\NumberHandler($patterns), 47 | new Handler\CommentHandler(), 48 | ]; 49 | } 50 | 51 | /** 52 | * Tokenize selector source code. 53 | */ 54 | public function tokenize(Reader $reader): TokenStream 55 | { 56 | $stream = new TokenStream(); 57 | 58 | while (!$reader->isEOF()) { 59 | foreach ($this->handlers as $handler) { 60 | if ($handler->handle($reader, $stream)) { 61 | continue 2; 62 | } 63 | } 64 | 65 | $stream->push(new Token(Token::TYPE_DELIMITER, $reader->getSubstring(1), $reader->getPosition())); 66 | $reader->moveForward(1); 67 | } 68 | 69 | return $stream 70 | ->push(new Token(Token::TYPE_FILE_END, null, $reader->getPosition())) 71 | ->freeze(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', 11 | '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', 12 | ); 13 | 14 | public static $prefixLengthsPsr4 = array ( 15 | 'S' => 16 | array ( 17 | 'Symfony\\Polyfill\\Mbstring\\' => 26, 18 | 'Symfony\\Polyfill\\Ctype\\' => 23, 19 | 'Symfony\\Component\\DomCrawler\\' => 29, 20 | 'Symfony\\Component\\CssSelector\\' => 30, 21 | ), 22 | 'M' => 23 | array ( 24 | 'Masterminds\\' => 12, 25 | ), 26 | ); 27 | 28 | public static $prefixDirsPsr4 = array ( 29 | 'Symfony\\Polyfill\\Mbstring\\' => 30 | array ( 31 | 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', 32 | ), 33 | 'Symfony\\Polyfill\\Ctype\\' => 34 | array ( 35 | 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', 36 | ), 37 | 'Symfony\\Component\\DomCrawler\\' => 38 | array ( 39 | 0 => __DIR__ . '/..' . '/symfony/dom-crawler', 40 | ), 41 | 'Symfony\\Component\\CssSelector\\' => 42 | array ( 43 | 0 => __DIR__ . '/..' . '/symfony/css-selector', 44 | ), 45 | 'Masterminds\\' => 46 | array ( 47 | 0 => __DIR__ . '/..' . '/masterminds/html5/src', 48 | ), 49 | ); 50 | 51 | public static $classMap = array ( 52 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 53 | ); 54 | 55 | public static function getInitializer(ClassLoader $loader) 56 | { 57 | return \Closure::bind(function () use ($loader) { 58 | $loader->prefixLengthsPsr4 = ComposerStaticInit0e98fdf7ca952c8f4cb3c82e525d431a::$prefixLengthsPsr4; 59 | $loader->prefixDirsPsr4 = ComposerStaticInit0e98fdf7ca952c8f4cb3c82e525d431a::$prefixDirsPsr4; 60 | $loader->classMap = ComposerStaticInit0e98fdf7ca952c8f4cb3c82e525d431a::$classMap; 61 | 62 | }, null, ClassLoader::class); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/CssSelectorConverter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector; 13 | 14 | use Symfony\Component\CssSelector\Parser\Shortcut\ClassParser; 15 | use Symfony\Component\CssSelector\Parser\Shortcut\ElementParser; 16 | use Symfony\Component\CssSelector\Parser\Shortcut\EmptyStringParser; 17 | use Symfony\Component\CssSelector\Parser\Shortcut\HashParser; 18 | use Symfony\Component\CssSelector\XPath\Extension\HtmlExtension; 19 | use Symfony\Component\CssSelector\XPath\Translator; 20 | 21 | /** 22 | * CssSelectorConverter is the main entry point of the component and can convert CSS 23 | * selectors to XPath expressions. 24 | * 25 | * @author Christophe Coevoet 26 | */ 27 | class CssSelectorConverter 28 | { 29 | private Translator $translator; 30 | private array $cache; 31 | 32 | private static array $xmlCache = []; 33 | private static array $htmlCache = []; 34 | 35 | /** 36 | * @param bool $html Whether HTML support should be enabled. Disable it for XML documents 37 | */ 38 | public function __construct(bool $html = true) 39 | { 40 | $this->translator = new Translator(); 41 | 42 | if ($html) { 43 | $this->translator->registerExtension(new HtmlExtension($this->translator)); 44 | $this->cache = &self::$htmlCache; 45 | } else { 46 | $this->cache = &self::$xmlCache; 47 | } 48 | 49 | $this->translator 50 | ->registerParserShortcut(new EmptyStringParser()) 51 | ->registerParserShortcut(new ElementParser()) 52 | ->registerParserShortcut(new ClassParser()) 53 | ->registerParserShortcut(new HashParser()) 54 | ; 55 | } 56 | 57 | /** 58 | * Translates a CSS expression to its XPath equivalent. 59 | * 60 | * Optionally, a prefix can be added to the resulting XPath 61 | * expression with the $prefix parameter. 62 | */ 63 | public function toXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string 64 | { 65 | return $this->cache[$prefix][$cssExpr] ??= $this->translator->cssToXPath($cssExpr, $prefix); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Test/Constraint/CrawlerAnySelectorTextContains.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Test\Constraint; 13 | 14 | use PHPUnit\Framework\Constraint\Constraint; 15 | use Symfony\Component\DomCrawler\Crawler; 16 | 17 | final class CrawlerAnySelectorTextContains extends Constraint 18 | { 19 | private string $selector; 20 | private string $expectedText; 21 | private bool $hasNode = false; 22 | 23 | public function __construct(string $selector, string $expectedText) 24 | { 25 | $this->selector = $selector; 26 | $this->expectedText = $expectedText; 27 | } 28 | 29 | public function toString(): string 30 | { 31 | if ($this->hasNode) { 32 | return \sprintf('the text of any node matching selector "%s" contains "%s"', $this->selector, $this->expectedText); 33 | } 34 | 35 | return \sprintf('the Crawler has a node matching selector "%s"', $this->selector); 36 | } 37 | 38 | protected function matches($other): bool 39 | { 40 | if (!$other instanceof Crawler) { 41 | throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); 42 | } 43 | 44 | $other = $other->filter($this->selector); 45 | if (!\count($other)) { 46 | $this->hasNode = false; 47 | 48 | return false; 49 | } 50 | 51 | $this->hasNode = true; 52 | 53 | $nodes = $other->each(fn (Crawler $node) => $node->text(null, true)); 54 | $matches = array_filter($nodes, function (string $node): bool { 55 | return str_contains($node, $this->expectedText); 56 | }); 57 | 58 | return 0 < \count($matches); 59 | } 60 | 61 | protected function failureDescription($other): string 62 | { 63 | if (!$other instanceof Crawler) { 64 | throw new \InvalidArgumentException(\sprintf('"%s" constraint expected an argument of type "%s", got "%s".', self::class, Crawler::class, get_debug_type($other))); 65 | } 66 | 67 | return $this->toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Node/AttributeNode.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Node; 13 | 14 | /** 15 | * Represents a "[| ]" node. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class AttributeNode extends AbstractNode 25 | { 26 | private NodeInterface $selector; 27 | private ?string $namespace; 28 | private string $attribute; 29 | private string $operator; 30 | private ?string $value; 31 | 32 | public function __construct(NodeInterface $selector, ?string $namespace, string $attribute, string $operator, ?string $value) 33 | { 34 | $this->selector = $selector; 35 | $this->namespace = $namespace; 36 | $this->attribute = $attribute; 37 | $this->operator = $operator; 38 | $this->value = $value; 39 | } 40 | 41 | public function getSelector(): NodeInterface 42 | { 43 | return $this->selector; 44 | } 45 | 46 | public function getNamespace(): ?string 47 | { 48 | return $this->namespace; 49 | } 50 | 51 | public function getAttribute(): string 52 | { 53 | return $this->attribute; 54 | } 55 | 56 | public function getOperator(): string 57 | { 58 | return $this->operator; 59 | } 60 | 61 | public function getValue(): ?string 62 | { 63 | return $this->value; 64 | } 65 | 66 | public function getSpecificity(): Specificity 67 | { 68 | return $this->selector->getSpecificity()->plus(new Specificity(0, 1, 0)); 69 | } 70 | 71 | public function __toString(): string 72 | { 73 | $attribute = $this->namespace ? $this->namespace.'|'.$this->attribute : $this->attribute; 74 | 75 | return 'exists' === $this->operator 76 | ? \sprintf('%s[%s[%s]]', $this->getNodeName(), $this->selector, $attribute) 77 | : \sprintf("%s[%s[%s %s '%s']]", $this->getNodeName(), $this->selector, $attribute, $this->operator, $this->value); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-delimiter.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/custom-block', [ 16 | 'data-1' => [ 17 | 'type' => 'string', 18 | ], 19 | 'data-2' => [ 20 | 'type' => 'number', 21 | ], 22 | 'data-3' => [ 23 | 'type' => 'string', 24 | 'default' => 'default-data-3-value', 25 | ], 26 | 'data-4' => [ 27 | 'type' => 'number', 28 | ], 29 | ] ); 30 | 31 | $html = ' 32 | 33 |
Custom block content here
34 | 35 | '; 36 | 37 | $expected_blocks = [ 38 | [ 39 | 'name' => 'test/custom-block', 40 | 'attributes' => [ 41 | 'data-1' => 'data-1-value', 42 | 'data-2' => 123, 43 | 'data-3' => 'default-data-3-value', 44 | ], 45 | ], 46 | ]; 47 | 48 | $content_parser = new ContentParser( $this->get_block_registry() ); 49 | $blocks = $content_parser->parse( $html ); 50 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 51 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 52 | } 53 | 54 | public function test_parse_block_delimiter_attributes__are_overridden_by_sourced_attributes() { 55 | $this->register_block_with_attributes( 'test/paragraph', [ 56 | 'content' => [ 57 | 'type' => 'string', 58 | 'source' => 'html', 59 | 'selector' => 'p', 60 | ], 61 | ] ); 62 | 63 | $html = ' 64 | 65 |

Test content

66 | 67 | '; 68 | 69 | $expected_blocks = [ 70 | [ 71 | 'name' => 'test/paragraph', 72 | 'attributes' => [ 73 | 'content' => 'Test content', 74 | ], 75 | ], 76 | ]; 77 | 78 | $content_parser = new ContentParser( $this->get_block_registry() ); 79 | $blocks = $content_parser->parse( $html ); 80 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 81 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-meta.php: -------------------------------------------------------------------------------- 1 | factory()->post->create(); 17 | update_post_meta( $post_id, 'test_meta_key', 'test_meta_value' ); 18 | 19 | $this->register_block_with_attributes( 'test/block-with-meta', [ 20 | 'test_meta_attribute' => [ 21 | 'type' => 'string', 22 | 'source' => 'meta', 23 | 'meta' => 'test_meta_key', 24 | ], 25 | ] ); 26 | 27 | $html = ''; 28 | 29 | $expected_blocks = [ 30 | [ 31 | 'name' => 'test/block-with-meta', 32 | 'attributes' => [ 33 | 'test_meta_attribute' => 'test_meta_value', 34 | ], 35 | ], 36 | ]; 37 | 38 | $meta_source_function = function () use ( $post_id ) { 39 | return $post_id; 40 | }; 41 | 42 | $content_parser = new ContentParser( $this->get_block_registry() ); 43 | $blocks = $content_parser->parse( $html, $post_id ); 44 | 45 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 46 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 47 | } 48 | 49 | public function test_parse_meta_source__with_default_value() { 50 | $post_id = $this->factory->post->create(); 51 | 52 | $this->register_block_with_attributes( 'test/block-with-missing-meta', [ 53 | 'test_meta_attribute' => [ 54 | 'type' => 'string', 55 | 'source' => 'meta', 56 | 'meta' => 'missing_meta_key', 57 | 'default' => 'default_value', 58 | ], 59 | ] ); 60 | 61 | $html = ''; 62 | 63 | $expected_blocks = [ 64 | [ 65 | 'name' => 'test/block-with-missing-meta', 66 | 'attributes' => [ 67 | 'test_meta_attribute' => 'default_value', 68 | ], 69 | ], 70 | ]; 71 | 72 | $content_parser = new ContentParser( $this->get_block_registry() ); 73 | $blocks = $content_parser->parse( $html, $post_id ); 74 | 75 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 76 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /vendor/symfony/polyfill-mbstring/Resources/unidata/caseFolding.php: -------------------------------------------------------------------------------- 1 | 'i̇', 5 | 'µ' => 'μ', 6 | 'ſ' => 's', 7 | 'ͅ' => 'ι', 8 | 'ς' => 'σ', 9 | 'ϐ' => 'β', 10 | 'ϑ' => 'θ', 11 | 'ϕ' => 'φ', 12 | 'ϖ' => 'π', 13 | 'ϰ' => 'κ', 14 | 'ϱ' => 'ρ', 15 | 'ϵ' => 'ε', 16 | 'ẛ' => 'ṡ', 17 | 'ι' => 'ι', 18 | 'ß' => 'ss', 19 | 'ʼn' => 'ʼn', 20 | 'ǰ' => 'ǰ', 21 | 'ΐ' => 'ΐ', 22 | 'ΰ' => 'ΰ', 23 | 'և' => 'եւ', 24 | 'ẖ' => 'ẖ', 25 | 'ẗ' => 'ẗ', 26 | 'ẘ' => 'ẘ', 27 | 'ẙ' => 'ẙ', 28 | 'ẚ' => 'aʾ', 29 | 'ẞ' => 'ss', 30 | 'ὐ' => 'ὐ', 31 | 'ὒ' => 'ὒ', 32 | 'ὔ' => 'ὔ', 33 | 'ὖ' => 'ὖ', 34 | 'ᾀ' => 'ἀι', 35 | 'ᾁ' => 'ἁι', 36 | 'ᾂ' => 'ἂι', 37 | 'ᾃ' => 'ἃι', 38 | 'ᾄ' => 'ἄι', 39 | 'ᾅ' => 'ἅι', 40 | 'ᾆ' => 'ἆι', 41 | 'ᾇ' => 'ἇι', 42 | 'ᾈ' => 'ἀι', 43 | 'ᾉ' => 'ἁι', 44 | 'ᾊ' => 'ἂι', 45 | 'ᾋ' => 'ἃι', 46 | 'ᾌ' => 'ἄι', 47 | 'ᾍ' => 'ἅι', 48 | 'ᾎ' => 'ἆι', 49 | 'ᾏ' => 'ἇι', 50 | 'ᾐ' => 'ἠι', 51 | 'ᾑ' => 'ἡι', 52 | 'ᾒ' => 'ἢι', 53 | 'ᾓ' => 'ἣι', 54 | 'ᾔ' => 'ἤι', 55 | 'ᾕ' => 'ἥι', 56 | 'ᾖ' => 'ἦι', 57 | 'ᾗ' => 'ἧι', 58 | 'ᾘ' => 'ἠι', 59 | 'ᾙ' => 'ἡι', 60 | 'ᾚ' => 'ἢι', 61 | 'ᾛ' => 'ἣι', 62 | 'ᾜ' => 'ἤι', 63 | 'ᾝ' => 'ἥι', 64 | 'ᾞ' => 'ἦι', 65 | 'ᾟ' => 'ἧι', 66 | 'ᾠ' => 'ὠι', 67 | 'ᾡ' => 'ὡι', 68 | 'ᾢ' => 'ὢι', 69 | 'ᾣ' => 'ὣι', 70 | 'ᾤ' => 'ὤι', 71 | 'ᾥ' => 'ὥι', 72 | 'ᾦ' => 'ὦι', 73 | 'ᾧ' => 'ὧι', 74 | 'ᾨ' => 'ὠι', 75 | 'ᾩ' => 'ὡι', 76 | 'ᾪ' => 'ὢι', 77 | 'ᾫ' => 'ὣι', 78 | 'ᾬ' => 'ὤι', 79 | 'ᾭ' => 'ὥι', 80 | 'ᾮ' => 'ὦι', 81 | 'ᾯ' => 'ὧι', 82 | 'ᾲ' => 'ὰι', 83 | 'ᾳ' => 'αι', 84 | 'ᾴ' => 'άι', 85 | 'ᾶ' => 'ᾶ', 86 | 'ᾷ' => 'ᾶι', 87 | 'ᾼ' => 'αι', 88 | 'ῂ' => 'ὴι', 89 | 'ῃ' => 'ηι', 90 | 'ῄ' => 'ήι', 91 | 'ῆ' => 'ῆ', 92 | 'ῇ' => 'ῆι', 93 | 'ῌ' => 'ηι', 94 | 'ῒ' => 'ῒ', 95 | 'ῖ' => 'ῖ', 96 | 'ῗ' => 'ῗ', 97 | 'ῢ' => 'ῢ', 98 | 'ῤ' => 'ῤ', 99 | 'ῦ' => 'ῦ', 100 | 'ῧ' => 'ῧ', 101 | 'ῲ' => 'ὼι', 102 | 'ῳ' => 'ωι', 103 | 'ῴ' => 'ώι', 104 | 'ῶ' => 'ῶ', 105 | 'ῷ' => 'ῶι', 106 | 'ῼ' => 'ωι', 107 | 'ff' => 'ff', 108 | 'fi' => 'fi', 109 | 'fl' => 'fl', 110 | 'ffi' => 'ffi', 111 | 'ffl' => 'ffl', 112 | 'ſt' => 'st', 113 | 'st' => 'st', 114 | 'ﬓ' => 'մն', 115 | 'ﬔ' => 'մե', 116 | 'ﬕ' => 'մի', 117 | 'ﬖ' => 'վն', 118 | 'ﬗ' => 'մխ', 119 | ]; 120 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Handler/StringHandler.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Handler; 13 | 14 | use Symfony\Component\CssSelector\Exception\InternalErrorException; 15 | use Symfony\Component\CssSelector\Exception\SyntaxErrorException; 16 | use Symfony\Component\CssSelector\Parser\Reader; 17 | use Symfony\Component\CssSelector\Parser\Token; 18 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerEscaping; 19 | use Symfony\Component\CssSelector\Parser\Tokenizer\TokenizerPatterns; 20 | use Symfony\Component\CssSelector\Parser\TokenStream; 21 | 22 | /** 23 | * CSS selector comment handler. 24 | * 25 | * This component is a port of the Python cssselect library, 26 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 27 | * 28 | * @author Jean-François Simon 29 | * 30 | * @internal 31 | */ 32 | class StringHandler implements HandlerInterface 33 | { 34 | private TokenizerPatterns $patterns; 35 | private TokenizerEscaping $escaping; 36 | 37 | public function __construct(TokenizerPatterns $patterns, TokenizerEscaping $escaping) 38 | { 39 | $this->patterns = $patterns; 40 | $this->escaping = $escaping; 41 | } 42 | 43 | public function handle(Reader $reader, TokenStream $stream): bool 44 | { 45 | $quote = $reader->getSubstring(1); 46 | 47 | if (!\in_array($quote, ["'", '"'])) { 48 | return false; 49 | } 50 | 51 | $reader->moveForward(1); 52 | $match = $reader->findPattern($this->patterns->getQuotedStringPattern($quote)); 53 | 54 | if (!$match) { 55 | throw new InternalErrorException(\sprintf('Should have found at least an empty match at %d.', $reader->getPosition())); 56 | } 57 | 58 | // check unclosed strings 59 | if (\strlen($match[0]) === $reader->getRemainingLength()) { 60 | throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); 61 | } 62 | 63 | // check quotes pairs validity 64 | if ($quote !== $reader->getSubstring(1, \strlen($match[0]))) { 65 | throw SyntaxErrorException::unclosedString($reader->getPosition() - 1); 66 | } 67 | 68 | $string = $this->escaping->escapeUnicodeAndNewLine($match[0]); 69 | $stream->push(new Token(Token::TYPE_STRING, $string, $reader->getPosition())); 70 | $reader->moveForward(\strlen($match[0]) + 1); 71 | 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/parser/block-additions/core-image.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'automattic/vip-block-data-api', 4 | 'pretty_version' => 'dev-trunk', 5 | 'version' => 'dev-trunk', 6 | 'reference' => '203b0abed8739e0a1e7923f226b2eb92794fd988', 7 | 'type' => 'wordpress-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => false, 11 | ), 12 | 'versions' => array( 13 | 'automattic/vip-block-data-api' => array( 14 | 'pretty_version' => 'dev-trunk', 15 | 'version' => 'dev-trunk', 16 | 'reference' => '203b0abed8739e0a1e7923f226b2eb92794fd988', 17 | 'type' => 'wordpress-plugin', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'masterminds/html5' => array( 23 | 'pretty_version' => '2.10.0', 24 | 'version' => '2.10.0.0', 25 | 'reference' => 'fcf91eb64359852f00d921887b219479b4f21251', 26 | 'type' => 'library', 27 | 'install_path' => __DIR__ . '/../masterminds/html5', 28 | 'aliases' => array(), 29 | 'dev_requirement' => false, 30 | ), 31 | 'symfony/css-selector' => array( 32 | 'pretty_version' => 'v6.4.24', 33 | 'version' => '6.4.24.0', 34 | 'reference' => '9b784413143701aa3c94ac1869a159a9e53e8761', 35 | 'type' => 'library', 36 | 'install_path' => __DIR__ . '/../symfony/css-selector', 37 | 'aliases' => array(), 38 | 'dev_requirement' => false, 39 | ), 40 | 'symfony/dom-crawler' => array( 41 | 'pretty_version' => 'v6.4.25', 42 | 'version' => '6.4.25.0', 43 | 'reference' => '976302990f9f2a6d4c07206836dd4ca77cae9524', 44 | 'type' => 'library', 45 | 'install_path' => __DIR__ . '/../symfony/dom-crawler', 46 | 'aliases' => array(), 47 | 'dev_requirement' => false, 48 | ), 49 | 'symfony/polyfill-ctype' => array( 50 | 'pretty_version' => 'v1.33.0', 51 | 'version' => '1.33.0.0', 52 | 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 53 | 'type' => 'library', 54 | 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', 55 | 'aliases' => array(), 56 | 'dev_requirement' => false, 57 | ), 58 | 'symfony/polyfill-mbstring' => array( 59 | 'pretty_version' => 'v1.33.0', 60 | 'version' => '1.33.0.0', 61 | 'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493', 62 | 'type' => 'library', 63 | 'install_path' => __DIR__ . '/../symfony/polyfill-mbstring', 64 | 'aliases' => array(), 65 | 'dev_requirement' => false, 66 | ), 67 | ), 68 | ); 69 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/XPathExpr.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath; 13 | 14 | /** 15 | * XPath expression translator interface. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class XPathExpr 25 | { 26 | private string $path; 27 | private string $element; 28 | private string $condition; 29 | 30 | public function __construct(string $path = '', string $element = '*', string $condition = '', bool $starPrefix = false) 31 | { 32 | $this->path = $path; 33 | $this->element = $element; 34 | $this->condition = $condition; 35 | 36 | if ($starPrefix) { 37 | $this->addStarPrefix(); 38 | } 39 | } 40 | 41 | public function getElement(): string 42 | { 43 | return $this->element; 44 | } 45 | 46 | /** 47 | * @return $this 48 | */ 49 | public function addCondition(string $condition): static 50 | { 51 | $this->condition = $this->condition ? \sprintf('(%s) and (%s)', $this->condition, $condition) : $condition; 52 | 53 | return $this; 54 | } 55 | 56 | public function getCondition(): string 57 | { 58 | return $this->condition; 59 | } 60 | 61 | /** 62 | * @return $this 63 | */ 64 | public function addNameTest(): static 65 | { 66 | if ('*' !== $this->element) { 67 | $this->addCondition('name() = '.Translator::getXpathLiteral($this->element)); 68 | $this->element = '*'; 69 | } 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @return $this 76 | */ 77 | public function addStarPrefix(): static 78 | { 79 | $this->path .= '*/'; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * Joins another XPathExpr with a combiner. 86 | * 87 | * @return $this 88 | */ 89 | public function join(string $combiner, self $expr): static 90 | { 91 | $path = $this->__toString().$combiner; 92 | 93 | if ('*/' !== $expr->path) { 94 | $path .= $expr->path; 95 | } 96 | 97 | $this->path = $path; 98 | $this->element = $expr->element; 99 | $this->condition = $expr->condition; 100 | 101 | return $this; 102 | } 103 | 104 | public function __toString(): string 105 | { 106 | $path = $this->path.$this->element; 107 | $condition = null === $this->condition || '' === $this->condition ? '' : '['.$this->condition.']'; 108 | 109 | return $path.$condition; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/Token.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser; 13 | 14 | /** 15 | * CSS selector token. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class Token 25 | { 26 | public const TYPE_FILE_END = 'eof'; 27 | public const TYPE_DELIMITER = 'delimiter'; 28 | public const TYPE_WHITESPACE = 'whitespace'; 29 | public const TYPE_IDENTIFIER = 'identifier'; 30 | public const TYPE_HASH = 'hash'; 31 | public const TYPE_NUMBER = 'number'; 32 | public const TYPE_STRING = 'string'; 33 | 34 | private ?string $type; 35 | private ?string $value; 36 | private ?int $position; 37 | 38 | public function __construct(?string $type, ?string $value, ?int $position) 39 | { 40 | $this->type = $type; 41 | $this->value = $value; 42 | $this->position = $position; 43 | } 44 | 45 | public function getType(): ?int 46 | { 47 | return $this->type; 48 | } 49 | 50 | public function getValue(): ?string 51 | { 52 | return $this->value; 53 | } 54 | 55 | public function getPosition(): ?int 56 | { 57 | return $this->position; 58 | } 59 | 60 | public function isFileEnd(): bool 61 | { 62 | return self::TYPE_FILE_END === $this->type; 63 | } 64 | 65 | public function isDelimiter(array $values = []): bool 66 | { 67 | if (self::TYPE_DELIMITER !== $this->type) { 68 | return false; 69 | } 70 | 71 | if (!$values) { 72 | return true; 73 | } 74 | 75 | return \in_array($this->value, $values); 76 | } 77 | 78 | public function isWhitespace(): bool 79 | { 80 | return self::TYPE_WHITESPACE === $this->type; 81 | } 82 | 83 | public function isIdentifier(): bool 84 | { 85 | return self::TYPE_IDENTIFIER === $this->type; 86 | } 87 | 88 | public function isHash(): bool 89 | { 90 | return self::TYPE_HASH === $this->type; 91 | } 92 | 93 | public function isNumber(): bool 94 | { 95 | return self::TYPE_NUMBER === $this->type; 96 | } 97 | 98 | public function isString(): bool 99 | { 100 | return self::TYPE_STRING === $this->type; 101 | } 102 | 103 | public function __toString(): string 104 | { 105 | if ($this->value) { 106 | return \sprintf('<%s "%s" at %s>', $this->type, $this->value, $this->position); 107 | } 108 | 109 | return \sprintf('<%s at %s>', $this->type, $this->position); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Serializer/RulesInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser\Tokenizer; 13 | 14 | /** 15 | * CSS selector tokenizer patterns builder. 16 | * 17 | * This component is a port of the Python cssselect library, 18 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 19 | * 20 | * @author Jean-François Simon 21 | * 22 | * @internal 23 | */ 24 | class TokenizerPatterns 25 | { 26 | private string $unicodeEscapePattern; 27 | private string $simpleEscapePattern; 28 | private string $newLineEscapePattern; 29 | private string $escapePattern; 30 | private string $stringEscapePattern; 31 | private string $nonAsciiPattern; 32 | private string $nmCharPattern; 33 | private string $nmStartPattern; 34 | private string $identifierPattern; 35 | private string $hashPattern; 36 | private string $numberPattern; 37 | private string $quotedStringPattern; 38 | 39 | public function __construct() 40 | { 41 | $this->unicodeEscapePattern = '\\\\([0-9a-f]{1,6})(?:\r\n|[ \n\r\t\f])?'; 42 | $this->simpleEscapePattern = '\\\\(.)'; 43 | $this->newLineEscapePattern = '\\\\(?:\n|\r\n|\r|\f)'; 44 | $this->escapePattern = $this->unicodeEscapePattern.'|\\\\[^\n\r\f0-9a-f]'; 45 | $this->stringEscapePattern = $this->newLineEscapePattern.'|'.$this->escapePattern; 46 | $this->nonAsciiPattern = '[^\x00-\x7F]'; 47 | $this->nmCharPattern = '[_a-z0-9-]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; 48 | $this->nmStartPattern = '[_a-z]|'.$this->escapePattern.'|'.$this->nonAsciiPattern; 49 | $this->identifierPattern = '-?(?:'.$this->nmStartPattern.')(?:'.$this->nmCharPattern.')*'; 50 | $this->hashPattern = '#((?:'.$this->nmCharPattern.')+)'; 51 | $this->numberPattern = '[+-]?(?:[0-9]*\.[0-9]+|[0-9]+)'; 52 | $this->quotedStringPattern = '([^\n\r\f\\\\%s]|'.$this->stringEscapePattern.')*'; 53 | } 54 | 55 | public function getNewLineEscapePattern(): string 56 | { 57 | return '~'.$this->newLineEscapePattern.'~'; 58 | } 59 | 60 | public function getSimpleEscapePattern(): string 61 | { 62 | return '~'.$this->simpleEscapePattern.'~'; 63 | } 64 | 65 | public function getUnicodeEscapePattern(): string 66 | { 67 | return '~'.$this->unicodeEscapePattern.'~i'; 68 | } 69 | 70 | public function getIdentifierPattern(): string 71 | { 72 | return '~^'.$this->identifierPattern.'~i'; 73 | } 74 | 75 | public function getHashPattern(): string 76 | { 77 | return '~^'.$this->hashPattern.'~i'; 78 | } 79 | 80 | public function getNumberPattern(): string 81 | { 82 | return '~^'.$this->numberPattern.'~'; 83 | } 84 | 85 | public function getQuotedStringPattern(string $quote): string 86 | { 87 | return '~^'.\sprintf($this->quotedStringPattern, $quote).'~i'; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Field/FormField.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Field; 13 | 14 | /** 15 | * FormField is the abstract class for all form fields. 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | abstract class FormField 20 | { 21 | /** 22 | * @var \DOMElement 23 | */ 24 | protected $node; 25 | /** 26 | * @var string 27 | */ 28 | protected $name; 29 | /** 30 | * @var string 31 | */ 32 | protected $value; 33 | /** 34 | * @var \DOMDocument 35 | */ 36 | protected $document; 37 | /** 38 | * @var \DOMXPath 39 | */ 40 | protected $xpath; 41 | /** 42 | * @var bool 43 | */ 44 | protected $disabled; 45 | 46 | /** 47 | * @param \DOMElement $node The node associated with this field 48 | */ 49 | public function __construct(\DOMElement $node) 50 | { 51 | $this->node = $node; 52 | $this->name = $node->getAttribute('name'); 53 | $this->xpath = new \DOMXPath($node->ownerDocument); 54 | 55 | $this->initialize(); 56 | } 57 | 58 | /** 59 | * Returns the label tag associated to the field or null if none. 60 | */ 61 | public function getLabel(): ?\DOMElement 62 | { 63 | $xpath = new \DOMXPath($this->node->ownerDocument); 64 | 65 | if ($this->node->hasAttribute('id')) { 66 | $labels = $xpath->query(\sprintf('descendant::label[@for="%s"]', $this->node->getAttribute('id'))); 67 | if ($labels->length > 0) { 68 | return $labels->item(0); 69 | } 70 | } 71 | 72 | $labels = $xpath->query('ancestor::label[1]', $this->node); 73 | 74 | return $labels->length > 0 ? $labels->item(0) : null; 75 | } 76 | 77 | /** 78 | * Returns the name of the field. 79 | */ 80 | public function getName(): string 81 | { 82 | return $this->name; 83 | } 84 | 85 | /** 86 | * Gets the value of the field. 87 | */ 88 | public function getValue(): string|array|null 89 | { 90 | return $this->value; 91 | } 92 | 93 | /** 94 | * Sets the value of the field. 95 | * 96 | * @return void 97 | */ 98 | public function setValue(?string $value) 99 | { 100 | $this->value = $value ?? ''; 101 | } 102 | 103 | /** 104 | * Returns true if the field should be included in the submitted values. 105 | */ 106 | public function hasValue(): bool 107 | { 108 | return true; 109 | } 110 | 111 | /** 112 | * Check if the current field is disabled. 113 | */ 114 | public function isDisabled(): bool 115 | { 116 | return $this->node->hasAttribute('disabled'); 117 | } 118 | 119 | /** 120 | * Initializes the form field. 121 | * 122 | * @return void 123 | */ 124 | abstract protected function initialize(); 125 | } 126 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release steps 2 | 3 | ## 1. Create a release branch 4 | 5 | 1. Before merging a feature, create a release branch for the next target version, e.g. 6 | 7 | ```bash 8 | git checkout trunk 9 | git checkout -b planned-release/0.2.1 10 | ``` 11 | 12 | 2. In GitHub, select the base branch as the `planned-release/...` branch. 13 | 3. Merge feature branches into the `planned-release/...` branch. 14 | 15 | ## 2. Bump plugin version 16 | 17 | 1. When the version is ready for release, inside the `planned-release/...` branch, bump the version number in `vip-block-data-api.php`. Change plugin header and `WPCOMVIP__BLOCK_DATA_API__PLUGIN_VERSION` to match new version. 18 | 2. Push the `planned-release/...` branch to GitHub. 19 | 3. PR version changes with feature changes and merge to `trunk`. 20 | 21 | ## 3. Tag branch for release 22 | 23 | 1. In `trunk`, add a signed tag for the release: 24 | 25 | ```bash 26 | git checkout trunk 27 | git pull 28 | git tag -s -a -m "Release " 29 | 30 | # e.g. git tag -s -a 1.0.2 -m "Release 1.0.2" 31 | ``` 32 | 33 | 2. Run `git push --tags`. 34 | 35 | ## 4. Create a release 36 | 37 | 1. In the `vip-block-data-api` folder, run this command to create a plugin ZIP: 38 | 39 | ```bash 40 | git archive --prefix "vip-block-data-api/" -o vip-block-data-api-.zip 41 | 42 | # e.g. git archive --prefix "vip-block-data-api/" 1.0.2 -o vip-block-data-api-1.0.2.zip 43 | # 44 | # Creates a ZIP archive with the prefix folder "vip-block-data-api/" containing files from tag 1.0.2 45 | ``` 46 | 47 | 2. Visit the [vip-block-data-api create release page](https://github.com/Automattic/vip-block-data-api/releases/new). 48 | 3. Select the newly created version tag in the dropdown. 49 | 4. For the title, enter the release version name (e.g. `1.0.2`) 50 | 5. Add a description of release changes. 51 | 6. Attach the plugin ZIP. 52 | 7. Click "Publish release." 53 | 54 | ## 5. Update integrations 55 | 56 | Patch updates (e.g. `1.2.3` -> `1.2.4`) do not require any additional steps. 57 | 58 | This section applies if the plugin has increased by a minor (e.g. `1.2` -> `1.3`) or major (e.g. `1.2` -> `2.0`) version. 59 | 60 | For an example updating an integration version, [see this mu-plugins PR](https://github.com/Automattic/vip-go-mu-plugins/pull/5409). 61 | 62 | 1. Ensure that the latest release of the Block Data API plugin has been [pulled in `vip-go-mu-plugins-ext`](https://github.com/Automattic/vip-go-mu-plugins-ext/tree/trunk/vip-integrations). Updates are synced by minor version, so a patch update of `1.2.3` will be pulled into `vip-integrations/vip-block-data-api-1.2`. If it's not, wait for the [**Update versioned external dependencies** workflow](https://github.com/Automattic/vip-go-mu-plugins-ext/actions/workflows/update-deps.yml) to pull in the latest changes, or run it manually. 63 | 64 | 2. Create a branch on [vip-go-mu-plugins](https://github.com/Automattic/vip-go-mu-plugins). 65 | 3. Update the `integrations/block-data-api.php` version to match the minor version of the plugin, e.g. `1.2`. This will correspond with the folder path for the plugin [in `vip-go-mu-plugins-ext`](https://github.com/Automattic/vip-go-mu-plugins-ext/tree/trunk/vip-integrations). 66 | 4. Submit the PR, get it approved, and merge. 67 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-tag.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/header', [ 16 | 'header-tag' => [ 17 | 'type' => 'string', 18 | 'source' => 'tag', 19 | 'selector' => 'h1,h2,h3', 20 | ], 21 | ] ); 22 | 23 | $html = ' 24 | 25 |

Article title

26 | 27 | '; 28 | 29 | $expected_blocks = [ 30 | [ 31 | 'name' => 'test/header', 32 | 'attributes' => [ 33 | 'header-tag' => 'h1', 34 | ], 35 | ], 36 | ]; 37 | 38 | $content_parser = new ContentParser( $this->get_block_registry() ); 39 | $blocks = $content_parser->parse( $html ); 40 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 41 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 42 | } 43 | 44 | public function test_parse_tag_source__in_query() { 45 | $this->register_block_with_attributes( 'test/headers', [ 46 | 'header-tags' => [ 47 | 'type' => 'array', 48 | 'source' => 'query', 49 | 'selector' => 'h1,h2,h3', 50 | 'query' => [ 51 | 'tag-name' => [ 52 | 'type' => 'string', 53 | 'source' => 'tag', 54 | ], 55 | ], 56 | ], 57 | ] ); 58 | 59 | $html = ' 60 | 61 |

Article subtitle

62 |

Subsection title

63 | 64 | '; 65 | 66 | $expected_blocks = [ 67 | [ 68 | 'name' => 'test/headers', 69 | 'attributes' => [ 70 | 'header-tags' => [ 71 | [ 72 | 'tag-name' => 'h2', 73 | ], 74 | [ 75 | 'tag-name' => 'h3', 76 | ], 77 | ], 78 | ], 79 | ], 80 | ]; 81 | 82 | $content_parser = new ContentParser( $this->get_block_registry() ); 83 | $blocks = $content_parser->parse( $html ); 84 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 85 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 86 | } 87 | 88 | public function test_parse_tag_source__with_default_value() { 89 | $this->register_block_with_attributes( 'test/cell', [ 90 | 'cell-tag' => [ 91 | 'type' => 'string', 92 | 'source' => 'tag', 93 | 'selector' => 'th,td', 94 | 'default' => 'td', 95 | ], 96 | ] ); 97 | 98 | $html = ''; 99 | 100 | $expected_blocks = [ 101 | [ 102 | 'name' => 'test/cell', 103 | 'attributes' => [ 104 | 'cell-tag' => 'td', 105 | ], 106 | ], 107 | ]; 108 | 109 | $content_parser = new ContentParser( $this->get_block_registry() ); 110 | $blocks = $content_parser->parse( $html ); 111 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 112 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-attribute.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/image', [ 17 | 'url' => [ 18 | 'type' => 'string', 19 | 'source' => 'attribute', 20 | 'selector' => 'img', 21 | 'attribute' => 'src', 22 | ], 23 | ] ); 24 | 25 | $html = ' 26 | 27 | 28 | 29 | '; 30 | 31 | $expected_blocks = [ 32 | [ 33 | 'name' => 'test/image', 34 | 'attributes' => [ 35 | 'url' => '/image.jpg', 36 | ], 37 | ], 38 | ]; 39 | 40 | $content_parser = new ContentParser( $this->get_block_registry() ); 41 | $blocks = $content_parser->parse( $html ); 42 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 43 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 44 | } 45 | 46 | public function test_parse_attribute_source__with_default_value() { 47 | $this->register_block_with_attributes( 'test/image', [ 48 | 'alt' => [ 49 | 'type' => 'string', 50 | 'source' => 'attribute', 51 | 'selector' => 'img', 52 | 'attribute' => 'alt', 53 | 'default' => 'Default alt text', 54 | ], 55 | ] ); 56 | 57 | $html = ' 58 | 59 | 60 | 61 | '; 62 | 63 | $expected_blocks = [ 64 | [ 65 | 'name' => 'test/image', 66 | 'attributes' => [ 67 | 'alt' => 'Default alt text', 68 | ], 69 | ], 70 | ]; 71 | 72 | $content_parser = new ContentParser( $this->get_block_registry() ); 73 | $blocks = $content_parser->parse( $html ); 74 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 75 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 76 | } 77 | 78 | public function test_parse_attribute_source__with_asterisk_selector() { 79 | $this->register_block_with_attributes( 'test/image', [ 80 | 'anchor' => [ 81 | 'type' => 'string', 82 | 'source' => 'attribute', 83 | 'selector' => '*', 84 | 'attribute' => 'id', 85 | ], 86 | ] ); 87 | 88 | $html = ' 89 | 90 | 91 | 92 | '; 93 | 94 | $expected_blocks = [ 95 | [ 96 | 'name' => 'test/image', 97 | 'attributes' => [ 98 | 'anchor' => 'anchor123', 99 | ], 100 | ], 101 | ]; 102 | 103 | $content_parser = new ContentParser( $this->get_block_registry() ); 104 | $blocks = $content_parser->parse( $html ); 105 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 106 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Custom ruleset for VIP Block Data API 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | . 15 | 16 | 18 | 19 | 20 | \.git/* 21 | /vendor/* 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 37 | 38 | 39 | 40 | /tests 41 | 42 | 43 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-html.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/paragraph', [ 17 | 'content' => [ 18 | 'type' => 'string', 19 | 'source' => 'html', 20 | 'selector' => 'p', 21 | ], 22 | ] ); 23 | 24 | $html = ' 25 | 26 |

Test paragraph with HTML

27 | 28 | '; 29 | 30 | $expected_blocks = [ 31 | [ 32 | 'name' => 'test/paragraph', 33 | 'attributes' => [ 34 | 'content' => 'Test paragraph with HTML', 35 | ], 36 | ], 37 | ]; 38 | 39 | $content_parser = new ContentParser( $this->get_block_registry() ); 40 | $blocks = $content_parser->parse( $html ); 41 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 42 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 43 | } 44 | 45 | public function test_parse_html_source__with_multiline_selector() { 46 | $this->register_block_with_attributes( 'test/quote', [ 47 | 'content' => [ 48 | 'type' => 'string', 49 | 'source' => 'html', 50 | 'selector' => 'blockquote', 51 | 'multiline' => 'p', 52 | ], 53 | ] ); 54 | 55 | $html = ' 56 | 57 |
58 |
59 |

Line 1

60 |

Line 2

61 |
62 |
63 | '; 64 | 65 | $expected_blocks = [ 66 | [ 67 | 'name' => 'test/quote', 68 | 'attributes' => [ 69 | 'content' => '

Line 1

Line 2

', 70 | ], 71 | ], 72 | ]; 73 | 74 | $content_parser = new ContentParser( $this->get_block_registry() ); 75 | $blocks = $content_parser->parse( $html ); 76 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 77 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 78 | } 79 | 80 | public function test_parse_html_source__with_default_value() { 81 | $this->register_block_with_attributes( 'test/image', [ 82 | 'caption' => [ 83 | 'type' => 'string', 84 | 'source' => 'html', 85 | 'selector' => 'figcaption', 86 | 'default' => 'Default image caption', 87 | ], 88 | ] ); 89 | 90 | $html = ' 91 | 92 | 93 | 94 | '; 95 | 96 | $expected_blocks = [ 97 | [ 98 | 'name' => 'test/image', 99 | 'attributes' => [ 100 | 'caption' => 'Default image caption', 101 | ], 102 | ], 103 | ]; 104 | 105 | $content_parser = new ContentParser( $this->get_block_registry() ); 106 | $blocks = $content_parser->parse( $html ); 107 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 108 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /tests/parser/sources/test-source-text.php: -------------------------------------------------------------------------------- 1 | register_block_with_attributes( 'test/figure', [ 17 | 'content' => [ 18 | 'type' => 'string', 19 | 'source' => 'text', 20 | 'selector' => 'figcaption', 21 | ], 22 | ] ); 23 | 24 | $html = ' 25 | 26 |
27 | 28 |
The inner text of the figcaption element
29 |
30 | '; 31 | 32 | $expected_blocks = [ 33 | [ 34 | 'name' => 'test/figure', 35 | 'attributes' => [ 36 | 'content' => 'The inner text of the figcaption element', 37 | ], 38 | ], 39 | ]; 40 | 41 | $content_parser = new ContentParser( $this->get_block_registry() ); 42 | $blocks = $content_parser->parse( $html ); 43 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 44 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 45 | } 46 | 47 | public function test_parse_text_source__with_html_tags() { 48 | $this->register_block_with_attributes( 'test/figure', [ 49 | 'content' => [ 50 | 'type' => 'string', 51 | 'source' => 'text', 52 | 'selector' => 'figcaption', 53 | ], 54 | ] ); 55 | 56 | $html = ' 57 | 58 |
59 | 60 |
61 | HTML tags should be ignored in text attributes 62 |
63 |
64 | '; 65 | 66 | $expected_blocks = [ 67 | [ 68 | 'name' => 'test/figure', 69 | 'attributes' => [ 70 | 'content' => 'HTML tags should be ignored in text attributes', 71 | ], 72 | ], 73 | ]; 74 | 75 | $content_parser = new ContentParser( $this->get_block_registry() ); 76 | $blocks = $content_parser->parse( $html ); 77 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 78 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 79 | } 80 | 81 | public function test_parse_text_source__with_default_value() { 82 | $this->register_block_with_attributes( 'test/figure', [ 83 | 'caption' => [ 84 | 'type' => 'string', 85 | 'source' => 'text', 86 | 'selector' => 'figcaption', 87 | 'default' => 'Default caption', 88 | ], 89 | ] ); 90 | 91 | $html = ' 92 | 93 |
94 | 95 |
96 | '; 97 | 98 | $expected_blocks = [ 99 | [ 100 | 'name' => 'test/figure', 101 | 'attributes' => [ 102 | 'caption' => 'Default caption', 103 | ], 104 | ], 105 | ]; 106 | 107 | $content_parser = new ContentParser( $this->get_block_registry() ); 108 | $blocks = $content_parser->parse( $html ); 109 | $this->assertArrayHasKey( 'blocks', $blocks, sprintf( 'Unexpected parser output: %s', wp_json_encode( $blocks ) ) ); 110 | $this->assertArraySubset( $expected_blocks, $blocks['blocks'], true ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Parser/TreeBuildingRules.php: -------------------------------------------------------------------------------- 1 | 1, 20 | 'dd' => 1, 21 | 'dt' => 1, 22 | 'rt' => 1, 23 | 'rp' => 1, 24 | 'tr' => 1, 25 | 'th' => 1, 26 | 'td' => 1, 27 | 'thead' => 1, 28 | 'tfoot' => 1, 29 | 'tbody' => 1, 30 | 'table' => 1, 31 | 'optgroup' => 1, 32 | 'option' => 1, 33 | ); 34 | 35 | /** 36 | * Returns true if the given tagname has special processing rules. 37 | */ 38 | public function hasRules($tagname) 39 | { 40 | return isset(static::$tags[$tagname]); 41 | } 42 | 43 | /** 44 | * Evaluate the rule for the current tag name. 45 | * 46 | * This may modify the existing DOM. 47 | * 48 | * @return \DOMElement The new Current DOM element. 49 | */ 50 | public function evaluate($new, $current) 51 | { 52 | switch ($new->tagName) { 53 | case 'li': 54 | return $this->handleLI($new, $current); 55 | case 'dt': 56 | case 'dd': 57 | return $this->handleDT($new, $current); 58 | case 'rt': 59 | case 'rp': 60 | return $this->handleRT($new, $current); 61 | case 'optgroup': 62 | return $this->closeIfCurrentMatches($new, $current, array( 63 | 'optgroup', 64 | )); 65 | case 'option': 66 | return $this->closeIfCurrentMatches($new, $current, array( 67 | 'option', 68 | )); 69 | case 'tr': 70 | return $this->closeIfCurrentMatches($new, $current, array( 71 | 'tr', 72 | )); 73 | case 'td': 74 | case 'th': 75 | return $this->closeIfCurrentMatches($new, $current, array( 76 | 'th', 77 | 'td', 78 | )); 79 | case 'tbody': 80 | case 'thead': 81 | case 'tfoot': 82 | case 'table': // Spec isn't explicit about this, but it's necessary. 83 | return $this->closeIfCurrentMatches($new, $current, array( 84 | 'thead', 85 | 'tfoot', 86 | 'tbody', 87 | )); 88 | } 89 | 90 | return $current; 91 | } 92 | 93 | protected function handleLI($ele, $current) 94 | { 95 | return $this->closeIfCurrentMatches($ele, $current, array( 96 | 'li', 97 | )); 98 | } 99 | 100 | protected function handleDT($ele, $current) 101 | { 102 | return $this->closeIfCurrentMatches($ele, $current, array( 103 | 'dt', 104 | 'dd', 105 | )); 106 | } 107 | 108 | protected function handleRT($ele, $current) 109 | { 110 | return $this->closeIfCurrentMatches($ele, $current, array( 111 | 'rt', 112 | 'rp', 113 | )); 114 | } 115 | 116 | protected function closeIfCurrentMatches($ele, $current, $match) 117 | { 118 | if (in_array($current->tagName, $match, true)) { 119 | $current->parentNode->appendChild($ele); 120 | } else { 121 | $current->appendChild($ele); 122 | } 123 | 124 | return $ele; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/AbstractUriElement.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler; 13 | 14 | /** 15 | * Any HTML element that can link to an URI. 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | abstract class AbstractUriElement 20 | { 21 | /** 22 | * @var \DOMElement 23 | */ 24 | protected $node; 25 | 26 | /** 27 | * @var string|null The method to use for the element 28 | */ 29 | protected $method; 30 | 31 | /** 32 | * @var string The URI of the page where the element is embedded (or the base href) 33 | */ 34 | protected $currentUri; 35 | 36 | /** 37 | * @param \DOMElement $node A \DOMElement instance 38 | * @param string|null $currentUri The URI of the page where the link is embedded (or the base href) 39 | * @param string|null $method The method to use for the link (GET by default) 40 | * 41 | * @throws \InvalidArgumentException if the node is not a link 42 | */ 43 | public function __construct(\DOMElement $node, ?string $currentUri = null, ?string $method = 'GET') 44 | { 45 | $this->setNode($node); 46 | $this->method = $method ? strtoupper($method) : null; 47 | $this->currentUri = $currentUri; 48 | 49 | $elementUriIsRelative = !parse_url(trim($this->getRawUri()), \PHP_URL_SCHEME); 50 | $baseUriIsAbsolute = null !== $this->currentUri && \in_array(strtolower(substr($this->currentUri, 0, 4)), ['http', 'file']); 51 | if ($elementUriIsRelative && !$baseUriIsAbsolute) { 52 | throw new \InvalidArgumentException(\sprintf('The URL of the element is relative, so you must define its base URI passing an absolute URL to the constructor of the "%s" class ("%s" was passed).', __CLASS__, $this->currentUri)); 53 | } 54 | } 55 | 56 | /** 57 | * Gets the node associated with this link. 58 | */ 59 | public function getNode(): \DOMElement 60 | { 61 | return $this->node; 62 | } 63 | 64 | /** 65 | * Gets the method associated with this link. 66 | */ 67 | public function getMethod(): string 68 | { 69 | return $this->method ?? 'GET'; 70 | } 71 | 72 | /** 73 | * Gets the URI associated with this link. 74 | */ 75 | public function getUri(): string 76 | { 77 | return UriResolver::resolve($this->getRawUri(), $this->currentUri); 78 | } 79 | 80 | /** 81 | * Returns raw URI data. 82 | */ 83 | abstract protected function getRawUri(): string; 84 | 85 | /** 86 | * Returns the canonicalized URI path (see RFC 3986, section 5.2.4). 87 | * 88 | * @param string $path URI path 89 | */ 90 | protected function canonicalizePath(string $path): string 91 | { 92 | if ('' === $path || '/' === $path) { 93 | return $path; 94 | } 95 | 96 | if (str_ends_with($path, '.')) { 97 | $path .= '/'; 98 | } 99 | 100 | $output = []; 101 | 102 | foreach (explode('/', $path) as $segment) { 103 | if ('..' === $segment) { 104 | array_pop($output); 105 | } elseif ('.' !== $segment) { 106 | $output[] = $segment; 107 | } 108 | } 109 | 110 | return implode('/', $output); 111 | } 112 | 113 | /** 114 | * Sets current \DOMElement instance. 115 | * 116 | * @param \DOMElement $node A \DOMElement instance 117 | * 118 | * @return void 119 | * 120 | * @throws \LogicException If given node is not an anchor 121 | */ 122 | abstract protected function setNode(\DOMElement $node); 123 | } 124 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/Field/FileFormField.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler\Field; 13 | 14 | /** 15 | * FileFormField represents a file form field (an HTML file input tag). 16 | * 17 | * @author Fabien Potencier 18 | */ 19 | class FileFormField extends FormField 20 | { 21 | /** 22 | * Sets the PHP error code associated with the field. 23 | * 24 | * @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) 25 | * 26 | * @return void 27 | * 28 | * @throws \InvalidArgumentException When error code doesn't exist 29 | */ 30 | public function setErrorCode(int $error) 31 | { 32 | $codes = [\UPLOAD_ERR_INI_SIZE, \UPLOAD_ERR_FORM_SIZE, \UPLOAD_ERR_PARTIAL, \UPLOAD_ERR_NO_FILE, \UPLOAD_ERR_NO_TMP_DIR, \UPLOAD_ERR_CANT_WRITE, \UPLOAD_ERR_EXTENSION]; 33 | if (!\in_array($error, $codes)) { 34 | throw new \InvalidArgumentException(\sprintf('The error code "%s" is not valid.', $error)); 35 | } 36 | 37 | $this->value = ['name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0]; 38 | } 39 | 40 | /** 41 | * Sets the value of the field. 42 | * 43 | * @return void 44 | */ 45 | public function upload(?string $value) 46 | { 47 | $this->setValue($value); 48 | } 49 | 50 | /** 51 | * Sets the value of the field. 52 | * 53 | * @return void 54 | */ 55 | public function setValue(?string $value) 56 | { 57 | if (null !== $value && is_readable($value)) { 58 | $error = \UPLOAD_ERR_OK; 59 | $size = filesize($value); 60 | $info = pathinfo($value); 61 | $name = $info['basename']; 62 | 63 | // copy to a tmp location 64 | $tmp = sys_get_temp_dir().'/'.strtr(substr(base64_encode(hash('sha256', uniqid(mt_rand(), true), true)), 0, 7), '/', '_'); 65 | if (\array_key_exists('extension', $info)) { 66 | $tmp .= '.'.$info['extension']; 67 | } 68 | if (is_file($tmp)) { 69 | unlink($tmp); 70 | } 71 | copy($value, $tmp); 72 | $value = $tmp; 73 | } else { 74 | $error = \UPLOAD_ERR_NO_FILE; 75 | $size = 0; 76 | $name = ''; 77 | $value = ''; 78 | } 79 | 80 | $this->value = ['name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size]; 81 | } 82 | 83 | /** 84 | * Sets path to the file as string for simulating HTTP request. 85 | * 86 | * @return void 87 | */ 88 | public function setFilePath(string $path) 89 | { 90 | parent::setValue($path); 91 | } 92 | 93 | /** 94 | * Initializes the form field. 95 | * 96 | * @return void 97 | * 98 | * @throws \LogicException When node type is incorrect 99 | */ 100 | protected function initialize() 101 | { 102 | if ('input' !== $this->node->nodeName) { 103 | throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); 104 | } 105 | 106 | if ('file' !== strtolower($this->node->getAttribute('type'))) { 107 | throw new \LogicException(\sprintf('A FileFormField can only be created from an input tag with a type of file (given type is "%s").', $this->node->getAttribute('type'))); 108 | } 109 | 110 | $this->setValue(null); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/Parser/TokenStream.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\Parser; 13 | 14 | use Symfony\Component\CssSelector\Exception\InternalErrorException; 15 | use Symfony\Component\CssSelector\Exception\SyntaxErrorException; 16 | 17 | /** 18 | * CSS selector token stream. 19 | * 20 | * This component is a port of the Python cssselect library, 21 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 22 | * 23 | * @author Jean-François Simon 24 | * 25 | * @internal 26 | */ 27 | class TokenStream 28 | { 29 | /** 30 | * @var Token[] 31 | */ 32 | private array $tokens = []; 33 | 34 | /** 35 | * @var Token[] 36 | */ 37 | private array $used = []; 38 | 39 | private int $cursor = 0; 40 | private ?Token $peeked; 41 | private bool $peeking = false; 42 | 43 | /** 44 | * Pushes a token. 45 | * 46 | * @return $this 47 | */ 48 | public function push(Token $token): static 49 | { 50 | $this->tokens[] = $token; 51 | 52 | return $this; 53 | } 54 | 55 | /** 56 | * Freezes stream. 57 | * 58 | * @return $this 59 | */ 60 | public function freeze(): static 61 | { 62 | return $this; 63 | } 64 | 65 | /** 66 | * Returns next token. 67 | * 68 | * @throws InternalErrorException If there is no more token 69 | */ 70 | public function getNext(): Token 71 | { 72 | if ($this->peeking) { 73 | $this->peeking = false; 74 | $this->used[] = $this->peeked; 75 | 76 | return $this->peeked; 77 | } 78 | 79 | if (!isset($this->tokens[$this->cursor])) { 80 | throw new InternalErrorException('Unexpected token stream end.'); 81 | } 82 | 83 | return $this->tokens[$this->cursor++]; 84 | } 85 | 86 | /** 87 | * Returns peeked token. 88 | */ 89 | public function getPeek(): Token 90 | { 91 | if (!$this->peeking) { 92 | $this->peeked = $this->getNext(); 93 | $this->peeking = true; 94 | } 95 | 96 | return $this->peeked; 97 | } 98 | 99 | /** 100 | * Returns used tokens. 101 | * 102 | * @return Token[] 103 | */ 104 | public function getUsed(): array 105 | { 106 | return $this->used; 107 | } 108 | 109 | /** 110 | * Returns next identifier token. 111 | * 112 | * @throws SyntaxErrorException If next token is not an identifier 113 | */ 114 | public function getNextIdentifier(): string 115 | { 116 | $next = $this->getNext(); 117 | 118 | if (!$next->isIdentifier()) { 119 | throw SyntaxErrorException::unexpectedToken('identifier', $next); 120 | } 121 | 122 | return $next->getValue(); 123 | } 124 | 125 | /** 126 | * Returns next identifier or null if star delimiter token is found. 127 | * 128 | * @throws SyntaxErrorException If next token is not an identifier or a star delimiter 129 | */ 130 | public function getNextIdentifierOrStar(): ?string 131 | { 132 | $next = $this->getNext(); 133 | 134 | if ($next->isIdentifier()) { 135 | return $next->getValue(); 136 | } 137 | 138 | if ($next->isDelimiter(['*'])) { 139 | return null; 140 | } 141 | 142 | throw SyntaxErrorException::unexpectedToken('identifier or "*"', $next); 143 | } 144 | 145 | /** 146 | * Skips next whitespace if any. 147 | */ 148 | public function skipWhitespace(): void 149 | { 150 | $peek = $this->getPeek(); 151 | 152 | if ($peek->isWhitespace()) { 153 | $this->getNext(); 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /vendor/symfony/css-selector/XPath/Extension/PseudoClassExtension.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\CssSelector\XPath\Extension; 13 | 14 | use Symfony\Component\CssSelector\Exception\ExpressionErrorException; 15 | use Symfony\Component\CssSelector\XPath\XPathExpr; 16 | 17 | /** 18 | * XPath expression translator pseudo-class extension. 19 | * 20 | * This component is a port of the Python cssselect library, 21 | * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. 22 | * 23 | * @author Jean-François Simon 24 | * 25 | * @internal 26 | */ 27 | class PseudoClassExtension extends AbstractExtension 28 | { 29 | public function getPseudoClassTranslators(): array 30 | { 31 | return [ 32 | 'root' => $this->translateRoot(...), 33 | 'scope' => $this->translateScopePseudo(...), 34 | 'first-child' => $this->translateFirstChild(...), 35 | 'last-child' => $this->translateLastChild(...), 36 | 'first-of-type' => $this->translateFirstOfType(...), 37 | 'last-of-type' => $this->translateLastOfType(...), 38 | 'only-child' => $this->translateOnlyChild(...), 39 | 'only-of-type' => $this->translateOnlyOfType(...), 40 | 'empty' => $this->translateEmpty(...), 41 | ]; 42 | } 43 | 44 | public function translateRoot(XPathExpr $xpath): XPathExpr 45 | { 46 | return $xpath->addCondition('not(parent::*)'); 47 | } 48 | 49 | public function translateScopePseudo(XPathExpr $xpath): XPathExpr 50 | { 51 | return $xpath->addCondition('1'); 52 | } 53 | 54 | public function translateFirstChild(XPathExpr $xpath): XPathExpr 55 | { 56 | return $xpath 57 | ->addStarPrefix() 58 | ->addNameTest() 59 | ->addCondition('position() = 1'); 60 | } 61 | 62 | public function translateLastChild(XPathExpr $xpath): XPathExpr 63 | { 64 | return $xpath 65 | ->addStarPrefix() 66 | ->addNameTest() 67 | ->addCondition('position() = last()'); 68 | } 69 | 70 | /** 71 | * @throws ExpressionErrorException 72 | */ 73 | public function translateFirstOfType(XPathExpr $xpath): XPathExpr 74 | { 75 | if ('*' === $xpath->getElement()) { 76 | throw new ExpressionErrorException('"*:first-of-type" is not implemented.'); 77 | } 78 | 79 | return $xpath 80 | ->addStarPrefix() 81 | ->addCondition('position() = 1'); 82 | } 83 | 84 | /** 85 | * @throws ExpressionErrorException 86 | */ 87 | public function translateLastOfType(XPathExpr $xpath): XPathExpr 88 | { 89 | if ('*' === $xpath->getElement()) { 90 | throw new ExpressionErrorException('"*:last-of-type" is not implemented.'); 91 | } 92 | 93 | return $xpath 94 | ->addStarPrefix() 95 | ->addCondition('position() = last()'); 96 | } 97 | 98 | public function translateOnlyChild(XPathExpr $xpath): XPathExpr 99 | { 100 | return $xpath 101 | ->addStarPrefix() 102 | ->addNameTest() 103 | ->addCondition('last() = 1'); 104 | } 105 | 106 | public function translateOnlyOfType(XPathExpr $xpath): XPathExpr 107 | { 108 | $element = $xpath->getElement(); 109 | 110 | return $xpath->addCondition(\sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element)); 111 | } 112 | 113 | public function translateEmpty(XPathExpr $xpath): XPathExpr 114 | { 115 | return $xpath->addCondition('not(*) and not(string-length())'); 116 | } 117 | 118 | public function getName(): string 119 | { 120 | return 'pseudo-class'; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /vendor/masterminds/html5/src/HTML5/Parser/EventHandler.php: -------------------------------------------------------------------------------- 1 | ). 65 | * 66 | * @return int one of the Tokenizer::TEXTMODE_* constants 67 | */ 68 | public function startTag($name, $attributes = array(), $selfClosing = false); 69 | 70 | /** 71 | * An end-tag. 72 | */ 73 | public function endTag($name); 74 | 75 | /** 76 | * A comment section (unparsed character data). 77 | */ 78 | public function comment($cdata); 79 | 80 | /** 81 | * A unit of parsed character data. 82 | * 83 | * Entities in this text are *already decoded*. 84 | */ 85 | public function text($cdata); 86 | 87 | /** 88 | * Indicates that the document has been entirely processed. 89 | */ 90 | public function eof(); 91 | 92 | /** 93 | * Emitted when the parser encounters an error condition. 94 | */ 95 | public function parseError($msg, $line, $col); 96 | 97 | /** 98 | * A CDATA section. 99 | * 100 | * @param string $data 101 | * The unparsed character data 102 | */ 103 | public function cdata($data); 104 | 105 | /** 106 | * This is a holdover from the XML spec. 107 | * 108 | * While user agents don't get PIs, server-side does. 109 | * 110 | * @param string $name The name of the processor (e.g. 'php'). 111 | * @param string $data The unparsed data. 112 | */ 113 | public function processingInstruction($name, $data = null); 114 | } 115 | -------------------------------------------------------------------------------- /vendor/symfony/dom-crawler/UriResolver.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\DomCrawler; 13 | 14 | /** 15 | * The UriResolver class takes an URI (relative, absolute, fragment, etc.) 16 | * and turns it into an absolute URI against another given base URI. 17 | * 18 | * @author Fabien Potencier 19 | * @author Grégoire Pineau 20 | */ 21 | class UriResolver 22 | { 23 | /** 24 | * Resolves a URI according to a base URI. 25 | * 26 | * For example if $uri=/foo/bar and $baseUri=https://symfony.com it will 27 | * return https://symfony.com/foo/bar 28 | * 29 | * If the $uri is not absolute you must pass an absolute $baseUri 30 | */ 31 | public static function resolve(string $uri, ?string $baseUri): string 32 | { 33 | $uri = trim($uri); 34 | 35 | // absolute URL? 36 | if (null !== parse_url(\strlen($uri) !== strcspn($uri, '?#') ? $uri : $uri.'#', \PHP_URL_SCHEME)) { 37 | return $uri; 38 | } 39 | 40 | if (null === $baseUri) { 41 | throw new \InvalidArgumentException('The URI is relative, so you must define its base URI passing an absolute URL.'); 42 | } 43 | 44 | // empty URI 45 | if (!$uri) { 46 | return $baseUri; 47 | } 48 | 49 | // an anchor 50 | if ('#' === $uri[0]) { 51 | return self::cleanupAnchor($baseUri).$uri; 52 | } 53 | 54 | $baseUriCleaned = self::cleanupUri($baseUri); 55 | 56 | if ('?' === $uri[0]) { 57 | return $baseUriCleaned.$uri; 58 | } 59 | 60 | // absolute URL with relative schema 61 | if (str_starts_with($uri, '//')) { 62 | return preg_replace('#^([^/]*)//.*$#', '$1', $baseUriCleaned).$uri; 63 | } 64 | 65 | $baseUriCleaned = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUriCleaned); 66 | 67 | // absolute path 68 | if ('/' === $uri[0]) { 69 | return $baseUriCleaned.$uri; 70 | } 71 | 72 | // relative path 73 | $path = parse_url(substr($baseUri, \strlen($baseUriCleaned)), \PHP_URL_PATH) ?? ''; 74 | $path = self::canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); 75 | 76 | return $baseUriCleaned.('' === $path || '/' !== $path[0] ? '/' : '').$path; 77 | } 78 | 79 | /** 80 | * Returns the canonicalized URI path (see RFC 3986, section 5.2.4). 81 | */ 82 | private static function canonicalizePath(string $path): string 83 | { 84 | if ('' === $path || '/' === $path) { 85 | return $path; 86 | } 87 | 88 | if (str_ends_with($path, '.')) { 89 | $path .= '/'; 90 | } 91 | 92 | $output = []; 93 | 94 | foreach (explode('/', $path) as $segment) { 95 | if ('..' === $segment) { 96 | array_pop($output); 97 | } elseif ('.' !== $segment) { 98 | $output[] = $segment; 99 | } 100 | } 101 | 102 | return implode('/', $output); 103 | } 104 | 105 | /** 106 | * Removes the query string and the anchor from the given uri. 107 | */ 108 | private static function cleanupUri(string $uri): string 109 | { 110 | return self::cleanupQuery(self::cleanupAnchor($uri)); 111 | } 112 | 113 | /** 114 | * Removes the query string from the uri. 115 | */ 116 | private static function cleanupQuery(string $uri): string 117 | { 118 | if (false !== $pos = strpos($uri, '?')) { 119 | return substr($uri, 0, $pos); 120 | } 121 | 122 | return $uri; 123 | } 124 | 125 | /** 126 | * Removes the anchor from the uri. 127 | */ 128 | private static function cleanupAnchor(string $uri): string 129 | { 130 | if (false !== $pos = strpos($uri, '#')) { 131 | return substr($uri, 0, $pos); 132 | } 133 | 134 | return $uri; 135 | } 136 | } 137 | --------------------------------------------------------------------------------