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 |
--------------------------------------------------------------------------------