From 43d0cd0b4c0c3e06b751b630ca2e79353915a2f3 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 7 Jun 2026 16:28:31 -0400 Subject: [PATCH] vendor: use composer for html block conversion --- .gitignore | 8 +- composer.json | 5 +- composer.lock | 80 +- library.php | 8 +- vendor_prefixed/autoload.php | 82 - .../chubes4/html-to-blocks-converter/LICENSE | 339 -- .../html-to-blocks-converter/composer.json | 21 - .../html-to-blocks-converter.php | 38 - .../includes/class-attribute-parser.php | 373 -- .../includes/class-block-factory.php | 604 --- .../includes/class-html-element.php | 425 -- .../class-html-to-blocks-versions.php | 123 - .../includes/class-svg-icon-classifier.php | 233 - .../includes/class-transform-registry.php | 3897 ----------------- .../includes/hooks.php | 193 - .../includes/svg-icon-functions.php | 24 - .../html-to-blocks-converter/library.php | 61 - .../html-to-blocks-converter/raw-handler.php | 895 ---- .../tests/CoreBlockInventoryUnitTest.php | 99 - .../GutenbergRawHandlerParityUnitTest.php | 106 - .../tests/RawHandlerFixturesUnitTest.php | 152 - .../tests/core-block-coverage-docs-smoke.php | 172 - .../tests/smoke-action-text-transforms.php | 311 -- .../tests/smoke-address-inline-strong-br.php | 142 - .../smoke-block-serialization-fidelity.php | 115 - .../tests/smoke-block-supports.php | 194 - .../tests/smoke-branded-link-spans.php | 164 - .../tests/smoke-buttons-justify-content.php | 149 - ...smoke-code-chrome-decorative-fallbacks.php | 139 - .../tests/smoke-code-demo-pre-svg.php | 175 - .../tests/smoke-code-display-divs.php | 187 - .../smoke-code-window-fallback-scope.php | 373 -- .../tests/smoke-core-block-inventory.php | 108 - .../smoke-core-block-transform-matrix.php | 98 - .../smoke-decorative-cta-wrapper-divs.php | 137 - .../tests/smoke-decorative-div-fallbacks.php | 140 - .../tests/smoke-decorative-strip-marquee.php | 186 - .../smoke-decorative-visual-clusters.php | 233 - .../smoke-definition-list-transforms.php | 149 - .../smoke-detail-br-wrapper-fallback.php | 158 - .../smoke-div-code-snippet-linebreaks.php | 218 - .../tests/smoke-ember-rye-media-collage.php | 160 - .../tests/smoke-empty-bem-decorative-divs.php | 156 - ...oke-empty-decorative-icon-placeholders.php | 134 - .../smoke-empty-glow-decorative-divs.php | 182 - .../smoke-extrachill-edge-shell-hero.php | 162 - .../tests/smoke-form-fallback-scope.php | 244 -- .../tests/smoke-group-section-anchor.php | 123 - .../smoke-info-address-contact-blocks.php | 165 - .../smoke-inline-script-fallback-scope.php | 186 - .../tests/smoke-inline-svg-fallback-scope.php | 152 - .../smoke-inline-svg-icon-classification.php | 164 - .../tests/smoke-layout-transforms.php | 240 - .../tests/smoke-loom-larder-fallbacks.php | 181 - .../tests/smoke-media-embed-transforms.php | 225 - .../smoke-nested-landing-layout-content.php | 196 - .../tests/smoke-no-duplicate-descendants.php | 168 - .../tests/smoke-php-scoper-callback.php | 108 - .../smoke-preformatted-class-fidelity.php | 105 - .../tests/smoke-product-card-body-price.php | 167 - ...e-product-card-decorative-placeholders.php | 183 - .../tests/smoke-product-grid-context-gate.php | 214 - .../tests/smoke-progress-fill-divs.php | 161 - .../tests/smoke-project-card-status-divs.php | 160 - .../tests/smoke-quote-attribution-wrapper.php | 132 - .../tests/smoke-quote-author-avatar-meta.php | 163 - .../tests/smoke-raw-handler-context.php | 110 - .../smoke-repeated-card-grid-transforms.php | 280 -- .../smoke-resized-svg-image-serialization.php | 123 - .../tests/smoke-rich-ui-clusters.php | 144 - .../tests/smoke-scoped-rest-callback.php | 116 - .../smoke-site-editor-marker-transforms.php | 156 - .../smoke-static-navigation-transforms.php | 187 - .../tests/smoke-static-site-chrome.php | 305 -- .../tests/smoke-step-timeline-connectors.php | 179 - .../tests/smoke-task-check-divs.php | 173 - .../smoke-terminal-blank-spacer-spans.php | 205 - .../smoke-testimonial-figure-blockquote.php | 170 - .../tests/smoke-text-metric-stat-cards.php | 168 - .../smoke-traffic-light-decorative-dots.php | 171 - .../smoke-trivial-theme-part-fragments.php | 160 - .../smoke-unsupported-html-fallback-hook.php | 94 - .../tests/smoke-visual-list-groups.php | 148 - .../traces/large-html-raw-handler.trace.php | 168 - .../tools/generate-core-block-inventory.php | 46 - .../dflydev/dot-access-data/LICENSE | 19 - .../dflydev/dot-access-data/composer.json | 72 - .../dflydev/dot-access-data/src/Data.php | 246 -- .../dot-access-data/src/DataInterface.php | 119 - .../src/Exception/DataException.php | 19 - .../src/Exception/InvalidPathException.php | 19 - .../src/Exception/MissingPathException.php | 31 - .../dflydev/dot-access-data/src/Util.php | 71 - vendor_prefixed/league/commonmark/LICENSE | 28 - .../league/commonmark/composer.json | 138 - .../commonmark/src/CommonMarkConverter.php | 40 - .../commonmark/src/ConverterInterface.php | 27 - .../commonmark/src/Delimiter/Bracket.php | 71 - .../commonmark/src/Delimiter/Delimiter.php | 107 - .../src/Delimiter/DelimiterInterface.php | 40 - .../src/Delimiter/DelimiterParser.php | 84 - .../src/Delimiter/DelimiterStack.php | 330 -- .../CacheableDelimiterProcessorInterface.php | 43 - .../DelimiterProcessorCollection.php | 77 - .../DelimiterProcessorCollectionInterface.php | 41 - .../Processor/DelimiterProcessorInterface.php | 74 - .../Processor/StaggeredDelimiterProcessor.php | 94 - .../src/Environment/Environment.php | 354 -- .../Environment/EnvironmentAwareInterface.php | 17 - .../EnvironmentBuilderInterface.php | 89 - .../src/Environment/EnvironmentInterface.php | 47 - .../commonmark/src/Event/AbstractEvent.php | 49 - .../src/Event/DocumentParsedEvent.php | 30 - .../src/Event/DocumentPreParsedEvent.php | 41 - .../src/Event/DocumentPreRenderEvent.php | 37 - .../src/Event/DocumentRenderedEvent.php | 36 - .../commonmark/src/Event/ListenerData.php | 44 - .../Exception/AlreadyInitializedException.php | 16 - .../src/Exception/CommonMarkException.php | 19 - .../commonmark/src/Exception/IOException.php | 16 - .../Exception/InvalidArgumentException.php | 16 - .../src/Exception/LogicException.php | 16 - .../Exception/MissingDependencyException.php | 16 - .../Exception/UnexpectedEncodingException.php | 16 - .../Attributes/AttributesExtension.php | 37 - .../Attributes/Event/AttributesListener.php | 126 - .../Extension/Attributes/Node/Attributes.php | 54 - .../Attributes/Node/AttributesInline.php | 48 - .../Parser/AttributesBlockContinueParser.php | 73 - .../Parser/AttributesBlockStartParser.php | 34 - .../Parser/AttributesInlineParser.php | 45 - .../Attributes/Util/AttributesHelper.php | 147 - .../Extension/Autolink/AutolinkExtension.php | 29 - .../Autolink/EmailAutolinkParser.php | 40 - .../Extension/Autolink/UrlAutolinkParser.php | 131 - .../CommonMark/CommonMarkCoreExtension.php | 42 - .../Processor/EmphasisDelimiterProcessor.php | 95 - .../CommonMark/Node/Block/BlockQuote.php | 17 - .../CommonMark/Node/Block/FencedCode.php | 80 - .../CommonMark/Node/Block/Heading.php | 34 - .../CommonMark/Node/Block/HtmlBlock.php | 68 - .../CommonMark/Node/Block/IndentedCode.php | 27 - .../CommonMark/Node/Block/ListBlock.php | 46 - .../CommonMark/Node/Block/ListData.php | 37 - .../CommonMark/Node/Block/ListItem.php | 31 - .../CommonMark/Node/Block/ThematicBreak.php | 17 - .../Node/Inline/AbstractWebResource.php | 34 - .../Extension/CommonMark/Node/Inline/Code.php | 20 - .../CommonMark/Node/Inline/Emphasis.php | 35 - .../CommonMark/Node/Inline/HtmlInline.php | 21 - .../CommonMark/Node/Inline/Image.php | 40 - .../Extension/CommonMark/Node/Inline/Link.php | 40 - .../CommonMark/Node/Inline/Strong.php | 35 - .../Parser/Block/BlockQuoteParser.php | 50 - .../Parser/Block/BlockQuoteStartParser.php | 33 - .../Parser/Block/FencedCodeParser.php | 71 - .../Parser/Block/FencedCodeStartParser.php | 34 - .../CommonMark/Parser/Block/HeadingParser.php | 43 - .../Parser/Block/HeadingStartParser.php | 64 - .../Parser/Block/HtmlBlockParser.php | 68 - .../Parser/Block/HtmlBlockStartParser.php | 50 - .../Parser/Block/IndentedCodeParser.php | 62 - .../Parser/Block/IndentedCodeStartParser.php | 35 - .../Parser/Block/ListBlockParser.php | 78 - .../Parser/Block/ListBlockStartParser.php | 129 - .../Parser/Block/ListItemParser.php | 68 - .../Parser/Block/ThematicBreakParser.php | 36 - .../Parser/Block/ThematicBreakStartParser.php | 34 - .../Parser/Inline/AutolinkParser.php | 45 - .../Parser/Inline/BacktickParser.php | 104 - .../CommonMark/Parser/Inline/BangParser.php | 37 - .../Parser/Inline/CloseBracketParser.php | 175 - .../CommonMark/Parser/Inline/EntityParser.php | 36 - .../Parser/Inline/EscapableParser.php | 47 - .../Parser/Inline/HtmlInlineParser.php | 35 - .../Parser/Inline/OpenBracketParser.php | 36 - .../Renderer/Block/BlockQuoteRenderer.php | 58 - .../Renderer/Block/FencedCodeRenderer.php | 66 - .../Renderer/Block/HeadingRenderer.php | 55 - .../Renderer/Block/HtmlBlockRenderer.php | 57 - .../Renderer/Block/IndentedCodeRenderer.php | 50 - .../Renderer/Block/ListBlockRenderer.php | 64 - .../Renderer/Block/ListItemRenderer.php | 67 - .../Renderer/Block/ThematicBreakRenderer.php | 49 - .../Renderer/Inline/CodeRenderer.php | 50 - .../Renderer/Inline/EmphasisRenderer.php | 49 - .../Renderer/Inline/HtmlInlineRenderer.php | 57 - .../Renderer/Inline/ImageRenderer.php | 88 - .../Renderer/Inline/LinkRenderer.php | 73 - .../Renderer/Inline/StrongRenderer.php | 49 - .../ConfigurableExtensionInterface.php | 18 - .../ApplyDefaultAttributesProcessor.php | 56 - .../DefaultAttributesExtension.php | 33 - .../DescriptionListExtension.php | 37 - .../ConsecutiveDescriptionListMerger.php | 35 - .../Event/LooseDescriptionHandler.php | 57 - .../DescriptionList/Node/Description.php | 32 - .../DescriptionList/Node/DescriptionList.php | 17 - .../DescriptionList/Node/DescriptionTerm.php | 17 - .../Parser/DescriptionContinueParser.php | 57 - .../Parser/DescriptionListContinueParser.php | 45 - .../Parser/DescriptionStartParser.php | 59 - .../Parser/DescriptionTermContinueParser.php | 44 - .../Renderer/DescriptionListRenderer.php | 34 - .../Renderer/DescriptionRenderer.php | 33 - .../Renderer/DescriptionTermRenderer.php | 33 - .../DisallowedRawHtmlExtension.php | 34 - .../DisallowedRawHtmlRenderer.php | 50 - .../Embed/Bridge/OscaroteroEmbedAdapter.php | 43 - .../Embed/DomainFilteringAdapter.php | 53 - .../commonmark/src/Extension/Embed/Embed.php | 41 - .../Extension/Embed/EmbedAdapterInterface.php | 23 - .../src/Extension/Embed/EmbedExtension.php | 35 - .../src/Extension/Embed/EmbedParser.php | 51 - .../src/Extension/Embed/EmbedProcessor.php | 62 - .../src/Extension/Embed/EmbedRenderer.php | 31 - .../src/Extension/Embed/EmbedStartParser.php | 44 - .../src/Extension/ExtensionInterface.php | 21 - .../ExternalLink/ExternalLinkExtension.php | 30 - .../ExternalLink/ExternalLinkProcessor.php | 97 - .../Event/AnonymousFootnotesListener.php | 49 - .../FixOrphanedFootnotesAndRefsListener.php | 54 - .../Event/GatherFootnotesListener.php | 87 - .../Event/NumberFootnotesListener.php | 60 - .../Extension/Footnote/FootnoteExtension.php | 55 - .../src/Extension/Footnote/Node/Footnote.php | 31 - .../Footnote/Node/FootnoteBackref.php | 34 - .../Footnote/Node/FootnoteContainer.php | 18 - .../Extension/Footnote/Node/FootnoteRef.php | 47 - .../Parser/AnonymousFootnoteRefParser.php | 51 - .../Footnote/Parser/FootnoteParser.php | 56 - .../Footnote/Parser/FootnoteRefParser.php | 44 - .../Footnote/Parser/FootnoteStartParser.php | 43 - .../Renderer/FootnoteBackrefRenderer.php | 66 - .../Renderer/FootnoteContainerRenderer.php | 60 - .../Footnote/Renderer/FootnoteRefRenderer.php | 63 - .../Footnote/Renderer/FootnoteRenderer.php | 62 - .../Data/FrontMatterDataParserInterface.php | 23 - .../Data/LibYamlFrontMatterParser.php | 39 - .../Data/SymfonyYamlFrontMatterParser.php | 35 - .../Exception/InvalidFrontMatterException.php | 21 - .../FrontMatter/FrontMatterExtension.php | 40 - .../FrontMatter/FrontMatterParser.php | 52 - .../FrontMatterParserInterface.php | 18 - .../FrontMatterProviderInterface.php | 20 - .../Input/MarkdownInputWithFrontMatter.php | 37 - .../FrontMatterPostRenderListener.php | 26 - .../Listener/FrontMatterPreParser.php | 30 - .../Output/RenderedContentWithFrontMatter.php | 45 - .../GithubFlavoredMarkdownExtension.php | 30 - .../HeadingPermalink/HeadingPermalink.php | 31 - .../HeadingPermalinkExtension.php | 33 - .../HeadingPermalinkProcessor.php | 82 - .../HeadingPermalinkRenderer.php | 84 - .../Highlight/HighlightExtension.php | 23 - .../src/Extension/Highlight/Mark.php | 32 - .../Highlight/MarkDelimiterProcessor.php | 57 - .../src/Extension/Highlight/MarkRenderer.php | 44 - .../Extension/InlinesOnly/ChildRenderer.php | 31 - .../InlinesOnly/InlinesOnlyExtension.php | 39 - .../Mention/Generator/CallbackGenerator.php | 46 - .../Generator/MentionGeneratorInterface.php | 19 - .../Generator/StringTemplateLinkGenerator.php | 28 - .../src/Extension/Mention/Mention.php | 75 - .../Extension/Mention/MentionExtension.php | 45 - .../src/Extension/Mention/MentionParser.php | 67 - .../src/Extension/SmartPunct/DashParser.php | 53 - .../Extension/SmartPunct/EllipsesParser.php | 33 - .../src/Extension/SmartPunct/Quote.php | 26 - .../src/Extension/SmartPunct/QuoteParser.php | 75 - .../Extension/SmartPunct/QuoteProcessor.php | 69 - .../ReplaceUnpairedQuotesListener.php | 38 - .../SmartPunct/SmartPunctExtension.php | 37 - .../Extension/Strikethrough/Strikethrough.php | 32 - .../StrikethroughDelimiterProcessor.php | 57 - .../Strikethrough/StrikethroughExtension.php | 23 - .../Strikethrough/StrikethroughRenderer.php | 44 - .../commonmark/src/Extension/Table/Table.php | 19 - .../src/Extension/Table/TableCell.php | 87 - .../src/Extension/Table/TableCellRenderer.php | 71 - .../src/Extension/Table/TableExtension.php | 40 - .../src/Extension/Table/TableParser.php | 178 - .../src/Extension/Table/TableRenderer.php | 49 - .../src/Extension/Table/TableRow.php | 19 - .../src/Extension/Table/TableRowRenderer.php | 48 - .../src/Extension/Table/TableSection.php | 55 - .../Extension/Table/TableSectionRenderer.php | 57 - .../src/Extension/Table/TableStartParser.php | 137 - .../TableOfContents/Node/TableOfContents.php | 17 - .../Node/TableOfContentsPlaceholder.php | 17 - .../Normalizer/AsIsNormalizerStrategy.php | 59 - .../Normalizer/FlatNormalizerStrategy.php | 28 - .../NormalizerStrategyInterface.php | 18 - .../Normalizer/RelativeNormalizerStrategy.php | 57 - .../TableOfContentsBuilder.php | 85 - .../TableOfContentsExtension.php | 40 - .../TableOfContentsGenerator.php | 137 - .../TableOfContentsGeneratorInterface.php | 19 - .../TableOfContentsPlaceholderParser.php | 64 - .../TableOfContentsPlaceholderRenderer.php | 35 - .../TableOfContentsRenderer.php | 49 - .../Extension/TaskList/TaskListExtension.php | 23 - .../Extension/TaskList/TaskListItemMarker.php | 32 - .../TaskList/TaskListItemMarkerParser.php | 43 - .../TaskList/TaskListItemMarkerRenderer.php | 59 - .../src/GithubFlavoredMarkdownConverter.php | 39 - .../commonmark/src/Input/MarkdownInput.php | 81 - .../src/Input/MarkdownInputInterface.php | 22 - .../commonmark/src/MarkdownConverter.php | 80 - .../src/MarkdownConverterInterface.php | 31 - .../src/Node/Block/AbstractBlock.php | 54 - .../commonmark/src/Node/Block/Document.php | 46 - .../commonmark/src/Node/Block/Paragraph.php | 21 - .../src/Node/Block/TightBlockInterface.php | 18 - .../src/Node/Inline/AbstractInline.php | 20 - .../Node/Inline/AbstractStringContainer.php | 40 - .../src/Node/Inline/AdjacentTextMerger.php | 87 - .../src/Node/Inline/DelimitedInterface.php | 18 - .../commonmark/src/Node/Inline/Newline.php | 34 - .../commonmark/src/Node/Inline/Text.php | 23 - .../league/commonmark/src/Node/Node.php | 218 - .../commonmark/src/Node/NodeIterator.php | 48 - .../league/commonmark/src/Node/NodeWalker.php | 70 - .../commonmark/src/Node/NodeWalkerEvent.php | 36 - .../league/commonmark/src/Node/Query.php | 117 - .../commonmark/src/Node/Query/AndExpr.php | 48 - .../src/Node/Query/ExpressionInterface.php | 18 - .../commonmark/src/Node/Query/OrExpr.php | 48 - .../src/Node/RawMarkupContainerInterface.php | 19 - .../src/Node/StringContainerHelper.php | 48 - .../src/Node/StringContainerInterface.php | 24 - .../src/Normalizer/SlugNormalizer.php | 51 - .../src/Normalizer/TextNormalizer.php | 40 - .../Normalizer/TextNormalizerInterface.php | 31 - .../src/Normalizer/UniqueSlugNormalizer.php | 47 - .../UniqueSlugNormalizerInterface.php | 25 - .../commonmark/src/Output/RenderedContent.php | 41 - .../src/Output/RenderedContentInterface.php | 25 - .../Block/AbstractBlockContinueParser.php | 40 - .../src/Parser/Block/BlockContinue.php | 63 - .../Block/BlockContinueParserInterface.php | 55 - ...lockContinueParserWithInlinesInterface.php | 21 - .../src/Parser/Block/BlockStart.php | 105 - .../Block/BlockStartParserInterface.php | 30 - .../src/Parser/Block/DocumentBlockParser.php | 68 - .../src/Parser/Block/ParagraphParser.php | 71 - .../SkipLinesStartingWithLettersParser.php | 40 - .../league/commonmark/src/Parser/Cursor.php | 389 -- .../commonmark/src/Parser/CursorState.php | 52 - .../Parser/Inline/InlineParserInterface.php | 19 - .../src/Parser/Inline/InlineParserMatch.php | 73 - .../src/Parser/Inline/NewlineParser.php | 46 - .../src/Parser/InlineParserContext.php | 101 - .../src/Parser/InlineParserEngine.php | 148 - .../Parser/InlineParserEngineInterface.php | 24 - .../commonmark/src/Parser/MarkdownParser.php | 293 -- .../src/Parser/MarkdownParserInterface.php | 22 - .../src/Parser/MarkdownParserState.php | 47 - .../Parser/MarkdownParserStateInterface.php | 31 - .../src/Parser/ParserLogicException.php | 17 - .../Reference/MemoryLimitedReferenceMap.php | 56 - .../commonmark/src/Reference/Reference.php | 46 - .../src/Reference/ReferenceInterface.php | 25 - .../commonmark/src/Reference/ReferenceMap.php | 71 - .../src/Reference/ReferenceMapInterface.php | 27 - .../src/Reference/ReferenceParser.php | 262 -- .../src/Reference/ReferenceableInterface.php | 17 - .../src/Renderer/Block/DocumentRenderer.php | 48 - .../src/Renderer/Block/ParagraphRenderer.php | 64 - .../Renderer/ChildNodeRendererInterface.php | 26 - .../Renderer/DocumentRendererInterface.php | 25 - .../commonmark/src/Renderer/HtmlDecorator.php | 40 - .../commonmark/src/Renderer/HtmlRenderer.php | 81 - .../src/Renderer/Inline/NewlineRenderer.php | 66 - .../src/Renderer/Inline/TextRenderer.php | 48 - .../Renderer/MarkdownRendererInterface.php | 27 - .../Renderer/NoMatchingRendererException.php | 17 - .../src/Renderer/NodeRendererInterface.php | 24 - .../commonmark/src/Util/ArrayCollection.php | 159 - .../src/Util/Html5EntityDecoder.php | 64 - .../commonmark/src/Util/HtmlElement.php | 137 - .../league/commonmark/src/Util/HtmlFilter.php | 51 - .../commonmark/src/Util/LinkParserHelper.php | 135 - .../commonmark/src/Util/PrioritizedList.php | 65 - .../commonmark/src/Util/RegexHelper.php | 217 - .../league/commonmark/src/Util/SpecReader.php | 57 - .../league/commonmark/src/Util/UrlEncoder.php | 58 - .../league/commonmark/src/Util/Xml.php | 31 - .../src/Xml/FallbackNodeXmlRenderer.php | 74 - .../src/Xml/MarkdownToXmlConverter.php | 52 - .../src/Xml/XmlNodeRendererInterface.php | 24 - .../league/commonmark/src/Xml/XmlRenderer.php | 106 - vendor_prefixed/league/config/LICENSE.md | 28 - vendor_prefixed/league/config/composer.json | 77 - .../league/config/src/Configuration.php | 217 - .../src/ConfigurationAwareInterface.php | 20 - .../src/ConfigurationBuilderInterface.php | 19 - .../config/src/ConfigurationInterface.php | 42 - .../src/ConfigurationProviderInterface.php | 20 - .../ConfigurationExceptionInterface.php | 19 - .../InvalidConfigurationException.php | 41 - .../src/Exception/UnknownOptionException.php | 27 - .../src/Exception/ValidationException.php | 31 - .../src/MutableConfigurationInterface.php | 30 - .../config/src/ReadOnlyConfiguration.php | 35 - .../config/src/SchemaBuilderInterface.php | 24 - .../league/html-to-markdown/LICENSE | 20 - .../league/html-to-markdown/composer.json | 73 - .../league/html-to-markdown/src/Coerce.php | 33 - .../html-to-markdown/src/Configuration.php | 70 - .../src/ConfigurationAwareInterface.php | 9 - .../src/Converter/BlockquoteConverter.php | 33 - .../src/Converter/CodeConverter.php | 57 - .../src/Converter/CommentConverter.php | 43 - .../src/Converter/ConverterInterface.php | 14 - .../src/Converter/DefaultConverter.php | 40 - .../src/Converter/DivConverter.php | 31 - .../src/Converter/EmphasisConverter.php | 59 - .../src/Converter/HardBreakConverter.php | 41 - .../src/Converter/HeaderConverter.php | 49 - .../src/Converter/HorizontalRuleConverter.php | 20 - .../src/Converter/ImageConverter.php | 27 - .../src/Converter/LinkConverter.php | 64 - .../src/Converter/ListBlockConverter.php | 20 - .../src/Converter/ListItemConverter.php | 57 - .../src/Converter/ParagraphConverter.php | 78 - .../src/Converter/PreformattedConverter.php | 48 - .../src/Converter/TableConverter.php | 91 - .../src/Converter/TextConverter.php | 39 - .../league/html-to-markdown/src/Element.php | 190 - .../html-to-markdown/src/ElementInterface.php | 32 - .../html-to-markdown/src/Environment.php | 80 - .../html-to-markdown/src/HtmlConverter.php | 248 -- .../src/HtmlConverterInterface.php | 25 - .../src/PreConverterInterface.php | 9 - vendor_prefixed/nette/schema/composer.json | 58 - .../nette/schema/src/Schema/Context.php | 40 - .../schema/src/Schema/DynamicParameter.php | 12 - .../schema/src/Schema/Elements/AnyOf.php | 106 - .../nette/schema/src/Schema/Elements/Base.php | 123 - .../schema/src/Schema/Elements/Structure.php | 161 - .../nette/schema/src/Schema/Elements/Type.php | 159 - .../nette/schema/src/Schema/Expect.php | 96 - .../nette/schema/src/Schema/Helpers.php | 130 - .../nette/schema/src/Schema/Message.php | 66 - .../nette/schema/src/Schema/Processor.php | 78 - .../nette/schema/src/Schema/Schema.php | 31 - .../schema/src/Schema/ValidationException.php | 38 - vendor_prefixed/nette/utils/composer.json | 82 - .../nette/utils/src/HtmlStringable.php | 20 - .../utils/src/Iterators/CachingIterator.php | 103 - .../nette/utils/src/Iterators/Mapper.php | 25 - .../nette/utils/src/SmartObject.php | 121 - .../nette/utils/src/StaticClass.php | 21 - .../nette/utils/src/Translator.php | 20 - .../nette/utils/src/Utils/ArrayHash.php | 83 - .../nette/utils/src/Utils/ArrayList.php | 107 - .../nette/utils/src/Utils/Arrays.php | 478 -- .../nette/utils/src/Utils/Callback.php | 114 - .../nette/utils/src/Utils/DateTime.php | 146 - .../nette/utils/src/Utils/FileInfo.php | 53 - .../nette/utils/src/Utils/FileSystem.php | 253 -- .../nette/utils/src/Utils/Finder.php | 428 -- .../nette/utils/src/Utils/Floats.php | 84 - .../nette/utils/src/Utils/Helpers.php | 105 - .../nette/utils/src/Utils/Html.php | 703 --- .../nette/utils/src/Utils/Image.php | 648 --- .../nette/utils/src/Utils/ImageColor.php | 51 - .../nette/utils/src/Utils/ImageType.php | 18 - .../nette/utils/src/Utils/Iterables.php | 246 -- .../nette/utils/src/Utils/Json.php | 60 - .../nette/utils/src/Utils/ObjectHelpers.php | 170 - .../nette/utils/src/Utils/Paginator.php | 152 - .../nette/utils/src/Utils/Random.php | 42 - .../nette/utils/src/Utils/Reflection.php | 258 -- .../utils/src/Utils/ReflectionMethod.php | 33 - .../nette/utils/src/Utils/Strings.php | 585 --- .../nette/utils/src/Utils/Type.php | 228 - .../nette/utils/src/Utils/Validators.php | 337 -- .../nette/utils/src/Utils/exceptions.php | 39 - .../nette/utils/src/compatibility.php | 28 - .../nette/utils/src/exceptions.php | 87 - vendor_prefixed/psr/event-dispatcher/LICENSE | 21 - .../psr/event-dispatcher/composer.json | 30 - .../src/EventDispatcherInterface.php | 21 - .../src/ListenerProviderInterface.php | 19 - .../src/StoppableEventInterface.php | 26 - .../symfony/deprecation-contracts/LICENSE | 19 - .../deprecation-contracts/composer.json | 35 - .../deprecation-contracts/function.php | 28 - .../symfony/polyfill-php80/LICENSE | 19 - .../symfony/polyfill-php80/Php80.php | 107 - .../symfony/polyfill-php80/PhpToken.php | 93 - .../Resources/stubs/Attribute.php | 39 - .../Resources/stubs/PhpToken.php | 18 - .../Resources/stubs/Stringable.php | 22 - .../Resources/stubs/UnhandledMatchError.php | 18 - .../Resources/stubs/ValueError.php | 18 - .../symfony/polyfill-php80/bootstrap.php | 61 - .../symfony/polyfill-php80/composer.json | 48 - 500 files changed, 48 insertions(+), 45638 deletions(-) delete mode 100644 vendor_prefixed/autoload.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/LICENSE delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/composer.json delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/html-to-blocks-converter.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-attribute-parser.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-block-factory.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-html-element.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-html-to-blocks-versions.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-svg-icon-classifier.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-transform-registry.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/hooks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/includes/svg-icon-functions.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/library.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/raw-handler.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/CoreBlockInventoryUnitTest.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/GutenbergRawHandlerParityUnitTest.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/RawHandlerFixturesUnitTest.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/core-block-coverage-docs-smoke.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-action-text-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-address-inline-strong-br.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-serialization-fidelity.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-supports.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-branded-link-spans.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-buttons-justify-content.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-chrome-decorative-fallbacks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-demo-pre-svg.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-display-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-window-fallback-scope.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-core-block-inventory.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-core-block-transform-matrix.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-cta-wrapper-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-div-fallbacks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-strip-marquee.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-visual-clusters.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-definition-list-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-detail-br-wrapper-fallback.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-div-code-snippet-linebreaks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-ember-rye-media-collage.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-bem-decorative-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-decorative-icon-placeholders.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-glow-decorative-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-extrachill-edge-shell-hero.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-form-fallback-scope.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-group-section-anchor.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-info-address-contact-blocks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-inline-script-fallback-scope.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-inline-svg-fallback-scope.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-inline-svg-icon-classification.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-layout-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-loom-larder-fallbacks.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-media-embed-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-nested-landing-layout-content.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-no-duplicate-descendants.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-php-scoper-callback.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-preformatted-class-fidelity.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-product-card-body-price.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-product-card-decorative-placeholders.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-product-grid-context-gate.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-progress-fill-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-project-card-status-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-quote-attribution-wrapper.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-quote-author-avatar-meta.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-raw-handler-context.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-repeated-card-grid-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-resized-svg-image-serialization.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-rich-ui-clusters.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-scoped-rest-callback.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-site-editor-marker-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-static-navigation-transforms.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-static-site-chrome.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-step-timeline-connectors.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-task-check-divs.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-terminal-blank-spacer-spans.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-testimonial-figure-blockquote.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-text-metric-stat-cards.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-traffic-light-decorative-dots.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-trivial-theme-part-fragments.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-unsupported-html-fallback-hook.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-visual-list-groups.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tests/traces/large-html-raw-handler.trace.php delete mode 100644 vendor_prefixed/chubes4/html-to-blocks-converter/tools/generate-core-block-inventory.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/LICENSE delete mode 100644 vendor_prefixed/dflydev/dot-access-data/composer.json delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/Data.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/DataInterface.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/Exception/DataException.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/Exception/InvalidPathException.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/Exception/MissingPathException.php delete mode 100644 vendor_prefixed/dflydev/dot-access-data/src/Util.php delete mode 100644 vendor_prefixed/league/commonmark/LICENSE delete mode 100644 vendor_prefixed/league/commonmark/composer.json delete mode 100644 vendor_prefixed/league/commonmark/src/CommonMarkConverter.php delete mode 100644 vendor_prefixed/league/commonmark/src/ConverterInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Bracket.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Delimiter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/DelimiterInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/DelimiterParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/DelimiterStack.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Processor/CacheableDelimiterProcessorInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollection.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Processor/DelimiterProcessorCollectionInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Processor/DelimiterProcessorInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Delimiter/Processor/StaggeredDelimiterProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Environment/Environment.php delete mode 100644 vendor_prefixed/league/commonmark/src/Environment/EnvironmentAwareInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Environment/EnvironmentBuilderInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Environment/EnvironmentInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/AbstractEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/DocumentParsedEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/DocumentPreParsedEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/DocumentPreRenderEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/DocumentRenderedEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Event/ListenerData.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/AlreadyInitializedException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/CommonMarkException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/IOException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/InvalidArgumentException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/LogicException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/MissingDependencyException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Exception/UnexpectedEncodingException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/AttributesExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Event/AttributesListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Node/Attributes.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Node/AttributesInline.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockContinueParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Parser/AttributesBlockStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Parser/AttributesInlineParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Attributes/Util/AttributesHelper.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Autolink/AutolinkExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Autolink/EmailAutolinkParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Autolink/UrlAutolinkParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/CommonMarkCoreExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Delimiter/Processor/EmphasisDelimiterProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/BlockQuote.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/FencedCode.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/Heading.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/HtmlBlock.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/IndentedCode.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/ListBlock.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/ListData.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/ListItem.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Block/ThematicBreak.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/AbstractWebResource.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/Code.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/Emphasis.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/HtmlInline.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/Image.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/Link.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Node/Inline/Strong.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/BlockQuoteStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/FencedCodeStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/HeadingStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/HtmlBlockStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/IndentedCodeStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/ListBlockStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/ListItemParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Block/ThematicBreakStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/AutolinkParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/BacktickParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/BangParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/CloseBracketParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/EntityParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/EscapableParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/HtmlInlineParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Parser/Inline/OpenBracketParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Block/ThematicBreakRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/CodeRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/EmphasisRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/HtmlInlineRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/ImageRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/LinkRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/CommonMark/Renderer/Inline/StrongRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/ConfigurableExtensionInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DefaultAttributes/ApplyDefaultAttributesProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DefaultAttributes/DefaultAttributesExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/DescriptionListExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Event/ConsecutiveDescriptionListMerger.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Event/LooseDescriptionHandler.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Node/Description.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Node/DescriptionList.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Node/DescriptionTerm.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionContinueParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionListContinueParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Parser/DescriptionTermContinueParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionListRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DescriptionList/Renderer/DescriptionTermRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/DisallowedRawHtml/DisallowedRawHtmlRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/Bridge/OscaroteroEmbedAdapter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/DomainFilteringAdapter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/Embed.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedAdapterInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Embed/EmbedStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/ExtensionInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/ExternalLink/ExternalLinkExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/ExternalLink/ExternalLinkProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Event/AnonymousFootnotesListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Event/FixOrphanedFootnotesAndRefsListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Event/GatherFootnotesListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Event/NumberFootnotesListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/FootnoteExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Node/Footnote.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Node/FootnoteBackref.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Node/FootnoteContainer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Node/FootnoteRef.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Parser/AnonymousFootnoteRefParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Parser/FootnoteParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Parser/FootnoteRefParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Parser/FootnoteStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Renderer/FootnoteBackrefRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Renderer/FootnoteContainerRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRefRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Footnote/Renderer/FootnoteRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Data/FrontMatterDataParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Data/LibYamlFrontMatterParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Data/SymfonyYamlFrontMatterParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Exception/InvalidFrontMatterException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/FrontMatterExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/FrontMatterParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/FrontMatterParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/FrontMatterProviderInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Input/MarkdownInputWithFrontMatter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPostRenderListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Listener/FrontMatterPreParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/FrontMatter/Output/RenderedContentWithFrontMatter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/GithubFlavoredMarkdownExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalink.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/HeadingPermalink/HeadingPermalinkRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Highlight/HighlightExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Highlight/Mark.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Highlight/MarkDelimiterProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Highlight/MarkRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/InlinesOnly/ChildRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/InlinesOnly/InlinesOnlyExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/Generator/CallbackGenerator.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/Generator/MentionGeneratorInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/Generator/StringTemplateLinkGenerator.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/Mention.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/MentionExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Mention/MentionParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/DashParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/EllipsesParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/Quote.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/QuoteParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/QuoteProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/ReplaceUnpairedQuotesListener.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/SmartPunct/SmartPunctExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Strikethrough/Strikethrough.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Strikethrough/StrikethroughDelimiterProcessor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Strikethrough/StrikethroughExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Strikethrough/StrikethroughRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/Table.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableCell.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableCellRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableRow.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableRowRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableSection.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableSectionRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/Table/TableStartParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Node/TableOfContents.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Node/TableOfContentsPlaceholder.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Normalizer/AsIsNormalizerStrategy.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Normalizer/FlatNormalizerStrategy.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Normalizer/NormalizerStrategyInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/Normalizer/RelativeNormalizerStrategy.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsBuilder.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsGenerator.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsGeneratorInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsPlaceholderRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TableOfContents/TableOfContentsRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TaskList/TaskListExtension.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TaskList/TaskListItemMarker.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TaskList/TaskListItemMarkerParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Extension/TaskList/TaskListItemMarkerRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/GithubFlavoredMarkdownConverter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Input/MarkdownInput.php delete mode 100644 vendor_prefixed/league/commonmark/src/Input/MarkdownInputInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/MarkdownConverter.php delete mode 100644 vendor_prefixed/league/commonmark/src/MarkdownConverterInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Block/AbstractBlock.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Block/Document.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Block/Paragraph.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Block/TightBlockInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/AbstractInline.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/AbstractStringContainer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/AdjacentTextMerger.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/DelimitedInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/Newline.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Inline/Text.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Node.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/NodeIterator.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/NodeWalker.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/NodeWalkerEvent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Query.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Query/AndExpr.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Query/ExpressionInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/Query/OrExpr.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/RawMarkupContainerInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/StringContainerHelper.php delete mode 100644 vendor_prefixed/league/commonmark/src/Node/StringContainerInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Normalizer/SlugNormalizer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Normalizer/TextNormalizer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Normalizer/TextNormalizerInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Normalizer/UniqueSlugNormalizer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Normalizer/UniqueSlugNormalizerInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Output/RenderedContent.php delete mode 100644 vendor_prefixed/league/commonmark/src/Output/RenderedContentInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/AbstractBlockContinueParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/BlockContinue.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/BlockContinueParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/BlockContinueParserWithInlinesInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/BlockStart.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/BlockStartParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/DocumentBlockParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/ParagraphParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Block/SkipLinesStartingWithLettersParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Cursor.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/CursorState.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Inline/InlineParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Inline/InlineParserMatch.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/Inline/NewlineParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/InlineParserContext.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/InlineParserEngine.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/InlineParserEngineInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/MarkdownParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/MarkdownParserInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/MarkdownParserState.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/MarkdownParserStateInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Parser/ParserLogicException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/MemoryLimitedReferenceMap.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/Reference.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/ReferenceInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/ReferenceMap.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/ReferenceMapInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/ReferenceParser.php delete mode 100644 vendor_prefixed/league/commonmark/src/Reference/ReferenceableInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/Block/DocumentRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/Block/ParagraphRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/ChildNodeRendererInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/DocumentRendererInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/HtmlDecorator.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/HtmlRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/Inline/NewlineRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/Inline/TextRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/MarkdownRendererInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/NoMatchingRendererException.php delete mode 100644 vendor_prefixed/league/commonmark/src/Renderer/NodeRendererInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/ArrayCollection.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/Html5EntityDecoder.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/HtmlElement.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/HtmlFilter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/LinkParserHelper.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/PrioritizedList.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/RegexHelper.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/SpecReader.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/UrlEncoder.php delete mode 100644 vendor_prefixed/league/commonmark/src/Util/Xml.php delete mode 100644 vendor_prefixed/league/commonmark/src/Xml/FallbackNodeXmlRenderer.php delete mode 100644 vendor_prefixed/league/commonmark/src/Xml/MarkdownToXmlConverter.php delete mode 100644 vendor_prefixed/league/commonmark/src/Xml/XmlNodeRendererInterface.php delete mode 100644 vendor_prefixed/league/commonmark/src/Xml/XmlRenderer.php delete mode 100644 vendor_prefixed/league/config/LICENSE.md delete mode 100644 vendor_prefixed/league/config/composer.json delete mode 100644 vendor_prefixed/league/config/src/Configuration.php delete mode 100644 vendor_prefixed/league/config/src/ConfigurationAwareInterface.php delete mode 100644 vendor_prefixed/league/config/src/ConfigurationBuilderInterface.php delete mode 100644 vendor_prefixed/league/config/src/ConfigurationInterface.php delete mode 100644 vendor_prefixed/league/config/src/ConfigurationProviderInterface.php delete mode 100644 vendor_prefixed/league/config/src/Exception/ConfigurationExceptionInterface.php delete mode 100644 vendor_prefixed/league/config/src/Exception/InvalidConfigurationException.php delete mode 100644 vendor_prefixed/league/config/src/Exception/UnknownOptionException.php delete mode 100644 vendor_prefixed/league/config/src/Exception/ValidationException.php delete mode 100644 vendor_prefixed/league/config/src/MutableConfigurationInterface.php delete mode 100644 vendor_prefixed/league/config/src/ReadOnlyConfiguration.php delete mode 100644 vendor_prefixed/league/config/src/SchemaBuilderInterface.php delete mode 100644 vendor_prefixed/league/html-to-markdown/LICENSE delete mode 100644 vendor_prefixed/league/html-to-markdown/composer.json delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Coerce.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Configuration.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/ConfigurationAwareInterface.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/BlockquoteConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/CodeConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/CommentConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/ConverterInterface.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/DefaultConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/DivConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/EmphasisConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/HardBreakConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/HeaderConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/HorizontalRuleConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/ImageConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/LinkConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/ListBlockConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/ListItemConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/ParagraphConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/PreformattedConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/TableConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Converter/TextConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Element.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/ElementInterface.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/Environment.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/HtmlConverter.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/HtmlConverterInterface.php delete mode 100644 vendor_prefixed/league/html-to-markdown/src/PreConverterInterface.php delete mode 100644 vendor_prefixed/nette/schema/composer.json delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Context.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/DynamicParameter.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Elements/AnyOf.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Elements/Base.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Elements/Structure.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Elements/Type.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Expect.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Helpers.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Message.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Processor.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/Schema.php delete mode 100644 vendor_prefixed/nette/schema/src/Schema/ValidationException.php delete mode 100644 vendor_prefixed/nette/utils/composer.json delete mode 100644 vendor_prefixed/nette/utils/src/HtmlStringable.php delete mode 100644 vendor_prefixed/nette/utils/src/Iterators/CachingIterator.php delete mode 100644 vendor_prefixed/nette/utils/src/Iterators/Mapper.php delete mode 100644 vendor_prefixed/nette/utils/src/SmartObject.php delete mode 100644 vendor_prefixed/nette/utils/src/StaticClass.php delete mode 100644 vendor_prefixed/nette/utils/src/Translator.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ArrayHash.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ArrayList.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Arrays.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Callback.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/DateTime.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/FileInfo.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/FileSystem.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Finder.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Floats.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Helpers.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Html.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Image.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ImageColor.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ImageType.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Iterables.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Json.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ObjectHelpers.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Paginator.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Random.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Reflection.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/ReflectionMethod.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Strings.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Type.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/Validators.php delete mode 100644 vendor_prefixed/nette/utils/src/Utils/exceptions.php delete mode 100644 vendor_prefixed/nette/utils/src/compatibility.php delete mode 100644 vendor_prefixed/nette/utils/src/exceptions.php delete mode 100644 vendor_prefixed/psr/event-dispatcher/LICENSE delete mode 100644 vendor_prefixed/psr/event-dispatcher/composer.json delete mode 100644 vendor_prefixed/psr/event-dispatcher/src/EventDispatcherInterface.php delete mode 100644 vendor_prefixed/psr/event-dispatcher/src/ListenerProviderInterface.php delete mode 100644 vendor_prefixed/psr/event-dispatcher/src/StoppableEventInterface.php delete mode 100644 vendor_prefixed/symfony/deprecation-contracts/LICENSE delete mode 100644 vendor_prefixed/symfony/deprecation-contracts/composer.json delete mode 100644 vendor_prefixed/symfony/deprecation-contracts/function.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/LICENSE delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Php80.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/PhpToken.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Resources/stubs/Attribute.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Resources/stubs/PhpToken.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Resources/stubs/Stringable.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/Resources/stubs/ValueError.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/bootstrap.php delete mode 100644 vendor_prefixed/symfony/polyfill-php80/composer.json diff --git a/.gitignore b/.gitignore index a65caa2..8a270c3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,10 @@ vendor/ node_modules/ -# vendor_prefixed/ is intentionally NOT excluded: it's the php-scoper'd -# distribution artifact that ships to Composer consumers so they get the -# isolated dependency tree the architecture promises. Regenerated from -# `composer install && composer build`. +# vendor_prefixed/ is a generated distribution artifact. Release/plugin builds +# may include it, but source consumers should resolve dependencies through +# Composer instead of committed dependency snapshots. +vendor_prefixed/ # Build artifacts build/ diff --git a/composer.json b/composer.json index fe84d8b..4c1e881 100644 --- a/composer.json +++ b/composer.json @@ -19,12 +19,12 @@ ] }, "require": { + "chubes4/html-to-blocks-converter": "dev-main", "php": "^8.1", "league/commonmark": "^2.5", "league/html-to-markdown": "^5.1" }, "require-dev": { - "chubes4/html-to-blocks-converter": "dev-main", "humbug/php-scoper": "^0.18.19" }, "minimum-stability": "dev", @@ -36,7 +36,8 @@ }, { "type": "vcs", - "url": "https://github.com/chubes4/html-to-blocks-converter" + "url": "https://github.com/chubes4/html-to-blocks-converter", + "no-api": true } ], "config": { diff --git a/composer.lock b/composer.lock index 24a79fe..4f78e7c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,45 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "17bb65e574fb5f8bb9fff608767bf4a3", + "content-hash": "fa21093e6f55653057bedf2e6a1e1b99", "packages": [ + { + "name": "chubes4/html-to-blocks-converter", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/chubes4/html-to-blocks-converter", + "reference": "94977024d4b676c723232d0a7c3b01c8563c224a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/chubes4/html-to-blocks-converter/zipball/94977024d4b676c723232d0a7c3b01c8563c224a", + "reference": "94977024d4b676c723232d0a7c3b01c8563c224a", + "shasum": "" + }, + "require": { + "php": ">=7.4" + }, + "default-branch": true, + "type": "wordpress-plugin", + "autoload": { + "files": [ + "library.php" + ] + }, + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Chris Huber", + "homepage": "https://chubes.net" + } + ], + "description": "Convert raw HTML into Gutenberg block arrays using WordPress' HTML API.", + "homepage": "https://github.com/chubes4/html-to-blocks-converter", + "time": "2026-06-07T20:27:36+00:00" + }, { "name": "dflydev/dot-access-data", "version": "v3.0.3", @@ -720,47 +757,6 @@ } ], "packages-dev": [ - { - "name": "chubes4/html-to-blocks-converter", - "version": "dev-main", - "source": { - "type": "git", - "url": "https://github.com/chubes4/html-to-blocks-converter.git", - "reference": "c4f9418c8fbf5d13ebf619f654a7209ea05f7989" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/chubes4/html-to-blocks-converter/zipball/c4f9418c8fbf5d13ebf619f654a7209ea05f7989", - "reference": "c4f9418c8fbf5d13ebf619f654a7209ea05f7989", - "shasum": "" - }, - "require": { - "php": ">=7.4" - }, - "default-branch": true, - "type": "wordpress-plugin", - "autoload": { - "files": [ - "library.php" - ] - }, - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "Chris Huber", - "homepage": "https://chubes.net" - } - ], - "description": "Convert raw HTML into Gutenberg block arrays using WordPress' HTML API.", - "homepage": "https://github.com/chubes4/html-to-blocks-converter", - "support": { - "source": "https://github.com/chubes4/html-to-blocks-converter/tree/main", - "issues": "https://github.com/chubes4/html-to-blocks-converter/issues" - }, - "time": "2026-06-07T19:43:57+00:00" - }, { "name": "fidry/console", "version": "0.6.11", diff --git a/library.php b/library.php index 0d33cf7..bce1325 100644 --- a/library.php +++ b/library.php @@ -20,11 +20,9 @@ $bfb_library_path = __DIR__; $bfb_library_version = '0.8.1'; -// Load Composer/php-scoper dependencies as soon as the bridge package is -// included, not when the winning BFB version initializes on -// `plugins_loaded:1`. Some dependencies (notably html-to-blocks-converter) -// register their own Action-Scheduler-style version callbacks at -// `plugins_loaded:0`; loading them from BFB's initializer would be too late. +// Load built/php-scoper dependencies when present. Composer consumers usually +// load dependencies through the root autoloader before this file runs, while +// standalone release builds can still ship a scoped vendor_prefixed/ artifact. if ( file_exists( $bfb_library_path . '/vendor_prefixed/autoload.php' ) ) { require_once $bfb_library_path . '/vendor_prefixed/autoload.php'; } elseif ( file_exists( $bfb_library_path . '/vendor/autoload.php' ) ) { diff --git a/vendor_prefixed/autoload.php b/vendor_prefixed/autoload.php deleted file mode 100644 index 9b4ea42..0000000 --- a/vendor_prefixed/autoload.php +++ /dev/null @@ -1,82 +0,0 @@ - array( - 'league/config/src', - ), - 'BlockFormatBridge\\Vendor\\League\\HTMLToMarkdown\\' => array( - 'league/html-to-markdown/src', - ), - 'BlockFormatBridge\\Vendor\\League\\CommonMark\\' => array( - 'league/commonmark/src', - ), - 'BlockFormatBridge\\Vendor\\Nette\\' => array( - 'nette/utils/src', - 'nette/schema/src', - ), - 'BlockFormatBridge\\Vendor\\Dflydev\\DotAccessData\\' => array( - 'dflydev/dot-access-data/src', - ), - 'BlockFormatBridge\\Vendor\\Psr\\EventDispatcher\\' => array( - 'psr/event-dispatcher/src', - ), - 'BlockFormatBridge\\Vendor\\Symfony\\Polyfill\\Php80\\' => array( - 'symfony/polyfill-php80/', - ), -); - -$bfb_prefixed_psr0 = array(); - -spl_autoload_register( function ( $class ) use ( $bfb_prefixed_root, $bfb_prefixed_psr4, $bfb_prefixed_psr0 ) { - // PSR-4 - foreach ( $bfb_prefixed_psr4 as $prefix => $dirs ) { - if ( $prefix !== '' && strpos( $class, $prefix ) !== 0 ) { - continue; - } - $relative = substr( $class, strlen( $prefix ) ); - $relative = str_replace( '\\', '/', $relative ) . '.php'; - foreach ( $dirs as $dir ) { - $candidate = $bfb_prefixed_root . '/' . $dir . '/' . $relative; - if ( is_file( $candidate ) ) { - require_once $candidate; - return; - } - } - } - - // PSR-0 (fallback for older packages, e.g. nette). - foreach ( $bfb_prefixed_psr0 as $prefix => $dirs ) { - if ( $prefix !== '' && strpos( $class, $prefix ) !== 0 ) { - continue; - } - // PSR-0 underscore convention applies to the class portion only. - $relative_namespace = ''; - $relative_class = $class; - if ( false !== ( $pos = strrpos( $class, '\\' ) ) ) { - $relative_namespace = substr( $class, 0, $pos ); - $relative_class = substr( $class, $pos + 1 ); - } - $relative = str_replace( '\\', '/', $relative_namespace ) . '/' . str_replace( '_', '/', $relative_class ) . '.php'; - $relative = ltrim( $relative, '/' ); - foreach ( $dirs as $dir ) { - $candidate = $bfb_prefixed_root . '/' . $dir . '/' . $relative; - if ( is_file( $candidate ) ) { - require_once $candidate; - return; - } - } - } -} ); - -// Eagerly loaded files (composer 'files' autoload entries). -require_once $bfb_prefixed_root . '/chubes4/html-to-blocks-converter/library.php'; -require_once $bfb_prefixed_root . '/symfony/polyfill-php80/bootstrap.php'; -require_once $bfb_prefixed_root . '/symfony/deprecation-contracts/function.php'; diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/LICENSE b/vendor_prefixed/chubes4/html-to-blocks-converter/LICENSE deleted file mode 100644 index d159169..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/LICENSE +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/composer.json b/vendor_prefixed/chubes4/html-to-blocks-converter/composer.json deleted file mode 100644 index 7a0487b..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/composer.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "chubes4\/html-to-blocks-converter", - "type": "wordpress-plugin", - "description": "Convert raw HTML into Gutenberg block arrays using WordPress' HTML API.", - "license": "GPL-2.0-or-later", - "homepage": "https:\/\/github.com\/chubes4\/html-to-blocks-converter", - "authors": [ - { - "name": "Chris Huber", - "homepage": "https:\/\/chubes.net" - } - ], - "require": { - "php": ">=7.4" - }, - "autoload": { - "files": [ - "library.php" - ] - } -} \ No newline at end of file diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/html-to-blocks-converter.php b/vendor_prefixed/chubes4/html-to-blocks-converter/html-to-blocks-converter.php deleted file mode 100644 index 9b049fe..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/html-to-blocks-converter.php +++ /dev/null @@ -1,38 +0,0 @@ -

'; - \printf( - /* translators: %s: minimum WordPress version */ - esc_html__('HTML to Blocks Converter requires WordPress %s or higher.', 'html-to-blocks-converter'), - esc_html(\HTML_TO_BLOCKS_CONVERTER_MIN_WP) - ); - echo '

'; - }); - return; -} -require_once \HTML_TO_BLOCKS_CONVERTER_PATH . 'library.php'; diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-attribute-parser.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-attribute-parser.php deleted file mode 100644 index 20a74cd..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-attribute-parser.php +++ /dev/null @@ -1,373 +0,0 @@ -get_registered($block_name); - if (!$block_type || empty($block_type->attributes)) { - return $overrides; - } - $doc = self::parse_html($html); - if (!$doc) { - return $overrides; - } - $attributes = array(); - foreach ($block_type->attributes as $key => $schema) { - $value = self::parse_attribute($doc, $schema, $html); - if (null !== $value) { - $attributes[$key] = $value; - } - } - return \array_merge($attributes, $overrides); - } - /** - * Parses HTML string into HTML_Element wrapper rooted at a container div - * - * @param string $html HTML string - * @return HTML_To_Blocks_HTML_Element|null - */ - private static function parse_html($html) - { - if (empty($html)) { - return null; - } - return HTML_To_Blocks_HTML_Element::from_html('
' . $html . '
'); - } - /** - * Parses a single attribute based on schema - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param array $schema Attribute schema - * @param string $html Original HTML - * @return mixed Parsed value or null - */ - private static function parse_attribute($element, $schema, $html) - { - $source = $schema['source'] ?? null; - $selector = $schema['selector'] ?? null; - switch ($source) { - case 'html': - return self::get_inner_html($element, $selector); - case 'text': - return self::get_text_content($element, $selector); - case 'attribute': - return self::get_dom_attribute($element, $selector, $schema['attribute'] ?? ''); - case 'raw': - return $html; - case 'query': - return self::query_elements($element, $selector, $schema['query'] ?? array()); - case 'tag': - return self::get_tag_name($element, $selector); - default: - return $schema['default'] ?? null; - } - } - /** - * Gets inner HTML of an element matching selector - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @return string|null - */ - private static function get_inner_html($element, $selector) - { - $node = self::query_selector($element, $selector); - if (!$node) { - return null; - } - return \trim($node->get_inner_html()); - } - /** - * Gets text content of an element matching selector - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @return string|null - */ - private static function get_text_content($element, $selector) - { - $node = self::query_selector($element, $selector); - if (!$node) { - return null; - } - return \trim($node->get_text_content()); - } - /** - * Gets an attribute value from an element matching selector - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @param string $attribute Attribute name - * @return string|null - */ - private static function get_dom_attribute($element, $selector, $attribute) - { - $node = self::query_selector($element, $selector); - if (!$node) { - return null; - } - if (!$node->has_attribute($attribute)) { - return null; - } - return $node->get_attribute($attribute); - } - /** - * Gets the tag name of an element matching selector - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @return string|null - */ - private static function get_tag_name($element, $selector) - { - $node = self::query_selector($element, $selector); - if (!$node) { - return null; - } - return \strtolower($node->get_tag_name()); - } - /** - * Queries multiple elements and extracts data based on sub-schema - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @param array $query Query schema for each element - * @return array - */ - private static function query_elements($element, $selector, $query) - { - $nodes = self::query_selector_all($element, $selector); - $results = array(); - foreach ($nodes as $node) { - $item = array(); - foreach ($query as $key => $sub_schema) { - $item[$key] = self::parse_attribute($node, $sub_schema, $node->get_outer_html()); - } - $results[] = $item; - } - return $results; - } - /** - * Simple CSS selector query (supports tag, .class, #id, and combinations) - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @return HTML_To_Blocks_HTML_Element|null - */ - public static function query_selector($element, $selector) - { - // @phpstan-ignore-next-line booleanNot.alwaysFalse -- Defensive public API guard for untyped external callers. - if (!$element) { - return null; - } - if (empty($selector)) { - return $element; - } - $matches = self::query_selector_all($element, $selector); - return $matches[0] ?? null; - } - /** - * Query all elements matching selector - * - * @param HTML_To_Blocks_HTML_Element $element HTML element wrapper - * @param string|null $selector CSS-like selector - * @return array - */ - public static function query_selector_all($element, $selector) - { - // @phpstan-ignore-next-line booleanNot.alwaysFalse -- Defensive public API guard for untyped external callers. - if (!$element || empty($selector)) { - return array(); - } - $cache_key = \md5($element->get_outer_html() . '|' . $selector); - if (isset(self::$selector_chain_cache[$cache_key])) { - return self::$selector_chain_cache[$cache_key]; - } - $selector_groups = \preg_split('/\s*,\s*/', \trim($selector)); - if (\false === $selector_groups) { - return array(); - } - $all_matches = array(); - foreach ($selector_groups as $group) { - if ('' === $group) { - continue; - } - $group_matches = self::query_selector_chain($element, $group); - foreach ($group_matches as $match) { - $key = \md5($match->get_outer_html()); - if (!isset($all_matches[$key])) { - $all_matches[$key] = $match; - } - } - } - self::$selector_chain_cache[$cache_key] = \array_values($all_matches); - return self::$selector_chain_cache[$cache_key]; - } - /** - * Queries selector chain with child/descendant combinators - * - * @param HTML_To_Blocks_HTML_Element $root Root element - * @param string $selector Selector chain - * @return array - */ - private static function query_selector_chain($root, $selector) - { - $tokens = \preg_split('/\s+/', \trim(\str_replace('>', ' > ', $selector))); - if (\false === $tokens) { - return array(); - } - $tokens = \array_values(\array_filter($tokens, static function ($token) { - return '' !== $token; - })); - if (empty($tokens)) { - return array(); - } - $steps = array(); - $next_combinator = 'descendant'; - foreach ($tokens as $token) { - if ('>' === $token) { - $next_combinator = 'child'; - continue; - } - $steps[] = array('combinator' => $next_combinator, 'selector' => $token); - $next_combinator = 'descendant'; - } - $current = array($root); - foreach ($steps as $step) { - $next = array(); - foreach ($current as $context) { - $base_selector = self::extract_base_selector($step['selector']); - if (null === $base_selector) { - continue; - } - // WP HTML API adapter currently supports descendant matching only, - // so child combinators are approximated as descendant matching. - $candidates = $context->query_selector_all($base_selector); - foreach ($candidates as $candidate) { - if (self::matches_simple_selector($candidate, $step['selector'])) { - $next[] = $candidate; - } - } - } - if (empty($next)) { - return array(); - } - $current = $next; - } - return $current; - } - /** - * Extracts base selector supported by HTML element adapter - * - * @param string $selector Selector token - * @return string|null - */ - private static function extract_base_selector($selector) - { - $selector = \trim($selector); - $pattern = '/^([a-z0-9*]+)?(?:\.([a-z0-9_-]+))?(?:#([a-z0-9_-]+))?(?:\[[^\]]+\])?(?::not\(\[[^\]]+\]\))?$/i'; - if (!\preg_match($pattern, $selector, $matches)) { - return null; - } - $tag = $matches[1] ?? ''; - $class = $matches[2] ?? ''; - $id = $matches[3] ?? ''; - if ('*' === $tag || '' === $tag) { - if ($class) { - return '.' . $class; - } - if ($id) { - return '#' . $id; - } - return null; - } - return $tag . ($class ? '.' . $class : '') . ($id ? '#' . $id : ''); - } - /** - * Matches a simple selector against one element - * - * Supports: tag, .class, #id, [attr], [attr=value], :not([attr]), and combinations. - * - * @param HTML_To_Blocks_HTML_Element $element Candidate element - * @param string $selector Simple selector - * @return bool - */ - private static function matches_simple_selector($element, $selector) - { - $selector = \trim($selector); - if ('*' === $selector) { - return \true; - } - $pattern = '/^([a-z0-9*]+)?(?:\.([a-z0-9_-]+))?(?:#([a-z0-9_-]+))?(?:\[([a-z0-9_-]+)(?:=["\']?([^"\'\]]+)["\']?)?\])?(?::not\(\[([a-z0-9_-]+)(?:=["\']?([^"\'\]]+)["\']?)?\]\))?$/i'; - if (!\preg_match($pattern, $selector, $matches)) { - return \false; - } - $tag = $matches[1] ?? null; - $class = $matches[2] ?? null; - $id = $matches[3] ?? null; - $attr_name = $matches[4] ?? null; - $attr_value = $matches[5] ?? null; - $not_attr_name = $matches[6] ?? null; - $not_attr_value = $matches[7] ?? null; - if ($tag && '*' !== $tag && \strtoupper($tag) !== $element->get_tag_name()) { - return \false; - } - if ($class) { - $class_attr = $element->get_attribute('class'); - if (!$class_attr || !\preg_match('/(?:^|\s)' . \preg_quote($class, '/') . '(?:$|\s)/', $class_attr)) { - return \false; - } - } - if ($id) { - if ($element->get_attribute('id') !== $id) { - return \false; - } - } - if ($attr_name) { - if (!$element->has_attribute($attr_name)) { - return \false; - } - if (null !== $attr_value && (string) $element->get_attribute($attr_name) !== $attr_value) { - return \false; - } - } - if ($not_attr_name) { - if (!$element->has_attribute($not_attr_name)) { - return \true; - } - if (null === $not_attr_value) { - return \false; - } - if ((string) $element->get_attribute($not_attr_name) === $not_attr_value) { - return \false; - } - } - return \true; - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-block-factory.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-block-factory.php deleted file mode 100644 index b1ca3d6..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-block-factory.php +++ /dev/null @@ -1,604 +0,0 @@ -is_registered($name)) { - return self::create_block('core/html', array('content' => '')); - } - $block_type = $registry->get_registered($name); - $inner_html = ''; - $inner_content = array(); - $html_parts = self::generate_wrapper_html($name, $attributes); - if ('' !== $html_parts['opening'] || '' !== $html_parts['closing']) { - $inner_html = $html_parts['opening'] . $html_parts['closing']; - if (!empty($inner_blocks)) { - $inner_content[] = $html_parts['opening']; - foreach ($inner_blocks as $index => $inner_block) { - $inner_content[] = null; - } - $inner_content[] = $html_parts['closing']; - } else { - $inner_content[] = $inner_html; - } - } else { - $block_html = self::generate_block_html($name, $attributes); - if (!empty($block_html)) { - $inner_html = $block_html; - $inner_content[] = $block_html; - } - } - $sanitized_attributes = self::sanitize_attributes($block_type, $attributes); - return array('blockName' => $name, 'attrs' => $sanitized_attributes, 'innerBlocks' => $inner_blocks, 'innerHTML' => $inner_html, 'innerContent' => $inner_content); - } - /** - * Merges a block's base class with the stored custom className attribute. - * - * @param string $base Base class list required by the core block. - * @param array $attributes Block attributes. - * @return string Merged class list. - */ - private static function merge_block_class(string $base, array $attributes): string - { - $classes = \preg_split('/\s+/', \trim($base . ' ' . ($attributes['className'] ?? ''))); - $classes = \is_array($classes) ? \array_filter($classes) : array(); - $style = $attributes['style'] ?? array(); - if (\is_array($style)) { - if (!empty($style['color']['text'])) { - $classes[] = 'has-text-color'; - } - if (!empty($style['color']['background']) || !empty($style['color']['gradient'])) { - $classes[] = 'has-background'; - } - } - return \implode(' ', \array_values(\array_unique($classes))); - } - /** - * Resolves an allowed HTML tagName attribute with a safe default. - * - * @param string $default_value Default tag name. - * @param array $attributes Block attributes. - * @param array $allowed Allowed lowercase tag names. - * @return string Safe tag name. - */ - private static function tag_name(string $default_value, array $attributes, array $allowed): string - { - $tag_name = \strtolower((string) ($attributes['tagName'] ?? $default_value)); - return \in_array($tag_name, $allowed, \true) ? $tag_name : $default_value; - } - /** - * Converts an associative attribute map to escaped HTML attributes. - * - * @param array $attrs HTML attribute map. - * @return string Leading-space-prefixed HTML attributes. - */ - private static function html_attrs(array $attrs, array $include_empty = array()): string - { - $html = ''; - foreach ($attrs as $name => $value) { - if (null === $value || '' === $value && !\in_array($name, $include_empty, \true) || \false === $value) { - continue; - } - if (\true === $value) { - $html .= ' ' . $name; - continue; - } - $html .= ' ' . $name . '="' . esc_attr($value) . '"'; - } - return $html; - } - /** - * Serializes core block support style attributes into inline CSS. - * - * @param array $attributes Block attributes. - * @return string Inline CSS declaration list. - */ - private static function style_attr(array $attributes): string - { - $style = $attributes['style'] ?? array(); - $declarations = array(); - if (!\is_array($style)) { - return ''; - } - $border = $style['border'] ?? array(); - if (\is_array($border)) { - foreach (array('color', 'style', 'width', 'radius') as $part) { - $value = $border[$part] ?? null; - if (\is_string($value) && '' !== $value) { - $declarations[] = 'border-' . $part . ':' . $value; - } - } - } - $text_color = $style['color']['text'] ?? null; - if (\is_string($text_color) && '' !== $text_color) { - $declarations[] = 'color:' . $text_color; - } - $background_color = $style['color']['background'] ?? null; - if (\is_string($background_color) && '' !== $background_color) { - $declarations[] = 'background-color:' . $background_color; - } - $background_gradient = $style['color']['gradient'] ?? null; - if (\is_string($background_gradient) && '' !== $background_gradient) { - $declarations[] = 'background:' . $background_gradient; - } - $spacing = $style['spacing'] ?? array(); - if (\is_array($spacing)) { - foreach (array('margin', 'padding') as $property) { - $value = $spacing[$property] ?? null; - if (\is_string($value) && '' !== $value) { - $declarations[] = $property . ':' . $value; - continue; - } - if (!\is_array($value)) { - continue; - } - foreach (array('top', 'right', 'bottom', 'left') as $side) { - $side_value = $value[$side] ?? null; - if (\is_string($side_value) && '' !== $side_value) { - $declarations[] = $property . '-' . $side . ':' . $side_value; - } - } - } - } - $min_height = $style['dimensions']['minHeight'] ?? null; - if (\is_string($min_height) && '' !== $min_height) { - $declarations[] = 'min-height:' . $min_height; - } - $width = $style['dimensions']['width'] ?? null; - if (\is_string($width) && '' !== $width) { - $declarations[] = 'width:' . $width; - } - return \implode(';', $declarations); - } - /** - * Generates the complete HTML for a block without inner blocks - * - * @param string $name Block name - * @param array $attributes Block attributes - * @return string Block HTML - */ - private static function generate_block_html($name, $attributes) - { - switch ($name) { - case 'core/paragraph': - $content = $attributes['content'] ?? ''; - $html_attrs = array('class' => self::merge_block_class('', $attributes)); - $style = self::style_attr($attributes); - if (!empty($attributes['align'])) { - $style = \trim($style . ';text-align:' . $attributes['align'], ';'); - } - if ('' !== $style) { - $html_attrs['style'] = $style; - } - return '' . $content . '

'; - case 'core/heading': - $level = $attributes['level'] ?? 2; - $content = $attributes['content'] ?? ''; - return ' self::merge_block_class('wp-block-heading', $attributes))) . '>' . $content . ''; - case 'core/list-item': - $content = $attributes['content'] ?? ''; - return '
  • ' . $content . '
  • '; - case 'core/button': - return self::generate_button_html($attributes); - case 'core/pullquote': - return self::generate_pullquote_html($attributes); - case 'core/verse': - $content = $attributes['content'] ?? ''; - return ' self::merge_block_class('wp-block-verse', $attributes))) . '>' . $content . ''; - case 'core/image': - return self::generate_image_html($attributes); - case 'core/code': - $content = esc_html($attributes['content'] ?? ''); - return ' self::merge_block_class('wp-block-code', $attributes))) . '>' . $content . ''; - case 'core/preformatted': - $content = $attributes['content'] ?? ''; - return ' self::merge_block_class('wp-block-preformatted', $attributes))) . '>' . $content . ''; - case 'core/separator': - return ' self::merge_block_class('wp-block-separator has-css-opacity', $attributes))) . ' />'; - case 'core/table': - return self::generate_table_html($attributes); - case 'core/video': - return self::generate_video_html($attributes); - case 'core/audio': - return self::generate_audio_html($attributes); - case 'core/file': - return self::generate_file_html($attributes); - case 'core/embed': - return self::generate_embed_html($attributes); - case 'core/shortcode': - return $attributes['text'] ?? ''; - case 'core/html': - return $attributes['content'] ?? ''; - default: - return ''; - } - } - /** - * Generates HTML for button block. - * - * @param array $attributes Block attributes. - * @return string Button HTML. - */ - private static function generate_button_html($attributes) - { - $text = $attributes['text'] ?? ''; - $url = $attributes['url'] ?? ''; - $rel = !empty($attributes['rel']) ? ' rel="' . esc_attr($attributes['rel']) . '"' : ''; - $target = !empty($attributes['linkTarget']) ? ' target="' . esc_attr($attributes['linkTarget']) . '"' : ''; - $class_name = self::merge_block_class('wp-block-button', $attributes); - return ''; - } - /** - * Generates HTML for pullquote block. - * - * @param array $attributes Block attributes. - * @return string Pullquote HTML. - */ - private static function generate_pullquote_html($attributes) - { - $value = $attributes['value'] ?? ''; - $citation = !empty($attributes['citation']) ? '' . $attributes['citation'] . '' : ''; - return ' self::merge_block_class('wp-block-pullquote', $attributes))) . '>
    ' . $value . $citation . '
    '; - } - /** - * Generates HTML for image block - * - * @param array $attributes Block attributes - * @return string Image HTML - */ - private static function generate_image_html($attributes) - { - $url = $attributes['url'] ?? ''; - if (empty($url)) { - return ''; - } - $img_attrs = array('src' => esc_url($url), 'alt' => $attributes['alt'] ?? '', 'title' => $attributes['title'] ?? null, 'srcset' => $attributes['srcset'] ?? null, 'sizes' => $attributes['sizes'] ?? null, 'width' => $attributes['width'] ?? null, 'height' => $attributes['height'] ?? null); - $is_svg_image = \preg_match('/\.svg(?:$|[?#])/i', (string) $url) === 1; - $is_resized = $is_svg_image && !empty($attributes['width']) && !empty($attributes['height']); - if ($is_resized) { - $width = self::image_style_dimension($attributes['width']); - $height = self::image_style_dimension($attributes['height']); - $img_attrs['style'] = 'width:' . $width . ';height:' . $height; - $img_attrs['width'] = null; - $img_attrs['height'] = null; - } - $img = ''; - if (!empty($attributes['href'])) { - $rel = !empty($attributes['rel']) ? ' rel="' . esc_attr($attributes['rel']) . '"' : ''; - $img = '' . $img . ''; - } - $figcaption = ''; - if (!empty($attributes['caption'])) { - $figcaption = '
    ' . $attributes['caption'] . '
    '; - } - $class = self::merge_block_class($is_resized ? 'wp-block-image is-resized' : 'wp-block-image', $attributes); - if (!empty($attributes['align'])) { - $class .= ' align' . $attributes['align']; - } - return '
    ' . $img . $figcaption . '
    '; - } - /** - * Normalizes image dimension attributes for CSS style declarations. - * - * @param mixed $value Dimension attribute value. - * @return string CSS dimension value. - */ - private static function image_style_dimension($value): string - { - $value = \trim((string) $value); - if (\preg_match('/^-?(?:\d+|\d*\.\d+)$/', $value)) { - return $value . 'px'; - } - return $value; - } - /** - * Generates HTML for table block - * - * @param array $attributes Block attributes - * @return string Table HTML - */ - private static function generate_table_html($attributes) - { - $html = ' self::merge_block_class('wp-block-table', $attributes))) . '>'; - if (!empty($attributes['head'])) { - $html .= ''; - foreach ($attributes['head'] as $row) { - $html .= ''; - foreach ($row['cells'] ?? array() as $cell) { - $tag = $cell['tag'] ?? 'th'; - $html .= "<{$tag}>" . ($cell['content'] ?? '') . ""; - } - $html .= ''; - } - $html .= ''; - } - if (!empty($attributes['body'])) { - $html .= ''; - foreach ($attributes['body'] as $row) { - $html .= ''; - foreach ($row['cells'] ?? array() as $cell) { - $tag = $cell['tag'] ?? 'td'; - $html .= "<{$tag}>" . ($cell['content'] ?? '') . ""; - } - $html .= ''; - } - $html .= ''; - } - if (!empty($attributes['foot'])) { - $html .= ''; - foreach ($attributes['foot'] as $row) { - $html .= ''; - foreach ($row['cells'] ?? array() as $cell) { - $tag = $cell['tag'] ?? 'td'; - $html .= "<{$tag}>" . ($cell['content'] ?? '') . ""; - } - $html .= ''; - } - $html .= ''; - } - $html .= '
    '; - if (!empty($attributes['caption'])) { - $html .= '
    ' . $attributes['caption'] . '
    '; - } - $html .= ''; - return $html; - } - /** - * Generates HTML for a video block. - * - * @param array $attributes Block attributes. - * @return string Block HTML. - */ - private static function generate_video_html($attributes) - { - $src = $attributes['src'] ?? ''; - if ('' === $src) { - return ''; - } - $attrs = ' controls'; - foreach (array('autoplay', 'loop', 'muted', 'playsInline') as $flag) { - if (!empty($attributes[$flag])) { - $attrs .= ' ' . \strtolower('playsInline' === $flag ? 'playsinline' : $flag); - } - } - foreach (array('poster', 'preload') as $key) { - if (!empty($attributes[$key])) { - $attrs .= ' ' . $key . '="' . esc_attr($attributes[$key]) . '"'; - } - } - $html = ' self::merge_block_class('wp-block-video', $attributes))) . '>'; - if (!empty($attributes['caption'])) { - $html .= '
    ' . $attributes['caption'] . '
    '; - } - $html .= ''; - return $html; - } - /** - * Generates HTML for an audio block. - * - * @param array $attributes Block attributes. - * @return string Block HTML. - */ - private static function generate_audio_html($attributes) - { - $src = $attributes['src'] ?? ''; - if ('' === $src) { - return ''; - } - $attrs = ' controls'; - foreach (array('autoplay', 'loop') as $flag) { - if (!empty($attributes[$flag])) { - $attrs .= ' ' . $flag; - } - } - if (!empty($attributes['preload'])) { - $attrs .= ' preload="' . esc_attr($attributes['preload']) . '"'; - } - $html = ' self::merge_block_class('wp-block-audio', $attributes))) . '>'; - if (!empty($attributes['caption'])) { - $html .= '
    ' . $attributes['caption'] . '
    '; - } - $html .= ''; - return $html; - } - /** - * Generates HTML for a file block. - * - * @param array $attributes Block attributes. - * @return string Block HTML. - */ - private static function generate_file_html($attributes) - { - $href = $attributes['href'] ?? $attributes['textLinkHref'] ?? ''; - if ('' === $href) { - return ''; - } - $name = $attributes['fileName'] ?? \basename(\strtok($href, '?#')); - $target = !empty($attributes['textLinkTarget']) ? ' target="' . esc_attr($attributes['textLinkTarget']) . '"' : ''; - $html = ' self::merge_block_class('wp-block-file', $attributes))) . '>' . $name . ''; - if (!isset($attributes['showDownloadButton']) || $attributes['showDownloadButton']) { - $html .= 'Download'; - } - $html .= ''; - return $html; - } - /** - * Generates HTML for an embed block. - * - * @param array $attributes Block attributes. - * @return string Block HTML. - */ - private static function generate_embed_html($attributes) - { - $url = $attributes['url'] ?? ''; - if ('' === $url) { - return ''; - } - $provider = $attributes['providerNameSlug'] ?? ''; - $class = self::merge_block_class('wp-block-embed', $attributes); - if ('' !== $provider) { - $class .= ' is-provider-' . sanitize_html_class($provider) . ' wp-block-embed-' . sanitize_html_class($provider); - } - return '
    ' . esc_url($url) . '
    '; - } - /** - * Generates wrapper HTML for blocks with inner blocks - * - * @param string $name Block name - * @param array $attributes Block attributes - * @return array Opening and closing HTML tags - */ - private static function generate_wrapper_html($name, $attributes) - { - switch ($name) { - case 'core/list': - $tag = !empty($attributes['ordered']) ? 'ol' : 'ul'; - return array('opening' => '<' . $tag . self::html_attrs(array('class' => self::merge_block_class('wp-block-list', $attributes))) . '>', 'closing' => ""); - case 'core/list-item': - $content = $attributes['content'] ?? ''; - return array('opening' => '
  • ' . $content, 'closing' => '
  • '); - case 'core/quote': - return array('opening' => ' self::merge_block_class('wp-block-quote', $attributes))) . '>', 'closing' => ''); - case 'core/buttons': - return array('opening' => ' self::merge_block_class('wp-block-buttons', $attributes))) . '>', 'closing' => ''); - case 'core/details': - $summary = $attributes['summary'] ?? ''; - return array('opening' => ' self::merge_block_class('wp-block-details', $attributes))) . '>' . $summary . '', 'closing' => ''); - case 'core/group': - $tag = self::tag_name('div', $attributes, array('div', 'section', 'main', 'article', 'aside', 'header', 'footer', 'nav')); - return array('opening' => '<' . $tag . self::html_attrs(array('id' => $attributes['anchor'] ?? null, 'class' => self::merge_block_class('wp-block-group', $attributes), 'style' => self::style_attr($attributes), 'aria-label' => $attributes['ariaLabel'] ?? null)) . '>', 'closing' => ''); - case 'core/column': - return array('opening' => ' self::merge_block_class('wp-block-column', $attributes))) . '>', 'closing' => ''); - case 'core/columns': - return array('opening' => ' self::merge_block_class('wp-block-columns', $attributes))) . '>', 'closing' => ''); - case 'core/gallery': - $class = self::merge_block_class('wp-block-gallery has-nested-images columns-default is-cropped', $attributes); - if (!empty($attributes['columns'])) { - $class = self::merge_block_class('wp-block-gallery has-nested-images columns-' . (int) $attributes['columns'] . ' is-cropped', $attributes); - } - return array('opening' => '
    ', 'closing' => '
    '); - case 'core/media-text': - $media_url = $attributes['mediaUrl'] ?? ''; - $media_type = $attributes['mediaType'] ?? 'image'; - $media_alt = esc_attr($attributes['mediaAlt'] ?? ''); - $media_html = 'video' === $media_type ? '' : '' . $media_alt . ''; - $class = self::merge_block_class('wp-block-media-text is-stacked-on-mobile', $attributes); - if (($attributes['mediaPosition'] ?? 'left') === 'right') { - $class .= ' has-media-on-the-right'; - } - return array('opening' => '
    ' . $media_html . '
    ', 'closing' => '
    '); - default: - return array('opening' => '', 'closing' => ''); - } - } - /** - * Sanitizes block attributes against the block type schema - * Excludes attributes with source types (rich-text, html, text) as those are derived from HTML - * - * @param WP_Block_Type $block_type Block type object - * @param array $attributes Raw attributes - * @return array Sanitized attributes for JSON serialization - */ - private static function sanitize_attributes($block_type, $attributes) - { - if (empty($block_type->attributes)) { - return $attributes; - } - $sanitized = array(); - foreach ($attributes as $key => $value) { - if (!isset($block_type->attributes[$key])) { - if ('anchor' === $key) { - $sanitized[$key] = $value; - } - continue; - } - $schema = $block_type->attributes[$key]; - if (isset($schema['source'])) { - continue; - } - $type = $schema['type'] ?? null; - if (null === $value || '' === $value) { - continue; - } - if ('rich-text' === $type) { - continue; - } - switch ($type) { - case 'string': - $sanitized[$key] = (string) $value; - break; - case 'number': - case 'integer': - $sanitized[$key] = \is_numeric($value) ? (int) $value : null; - break; - case 'boolean': - $sanitized[$key] = (bool) $value; - break; - case 'array': - $sanitized[$key] = \is_array($value) ? $value : array($value); - break; - case 'object': - $sanitized[$key] = \is_array($value) ? $value : array(); - break; - default: - $sanitized[$key] = $value; - } - } - return $sanitized; - } - /** - * Gets the inner HTML content from an element - * - * @param mixed $element Element or HTML string - * @return string Inner HTML - */ - public static function get_inner_html($element) - { - if ($element instanceof HTML_To_Blocks_HTML_Element) { - return $element->get_inner_html(); - } - if (\is_string($element)) { - $parsed = HTML_To_Blocks_HTML_Element::from_html($element); - return $parsed ? $parsed->get_inner_html() : ''; - } - return ''; - } - /** - * Gets text content from an element - * - * @param mixed $element Element or HTML string - * @return string Text content - */ - public static function get_text_content($element) - { - if ($element instanceof HTML_To_Blocks_HTML_Element) { - return $element->get_text_content(); - } - if (\is_string($element)) { - $parsed = HTML_To_Blocks_HTML_Element::from_html($element); - return $parsed ? $parsed->get_text_content() : ''; - } - return ''; - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-html-element.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-html-element.php deleted file mode 100644 index b52b8c6..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-html-element.php +++ /dev/null @@ -1,425 +0,0 @@ -tag_name = $tag_name; - $this->attributes = $attributes; - $this->outer_html = $outer_html; - $this->inner_html = $inner_html; - } - /** - * Creates an HTML_Element from raw HTML string representing a single element - * - * @param string $html HTML string containing a single root element - * @return self|null Element instance or null if parsing fails - */ - public static function from_html(string $html): ?self - { - $html = \trim($html); - if (empty($html)) { - return null; - } - if (\preg_match('/^<\s*(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)\b([^>]*)\/?>\s*$/i', $html, $matches)) { - return new self($matches[1], self::parse_attribute_string($matches[2]), $html, ''); - } - $processor = \WP_HTML_Processor::create_fragment($html); - if (!$processor) { - return null; - } - if (!$processor->next_token()) { - return self::from_table_scoped_html($html); - } - $tag_name = $processor->get_tag(); - if (!$tag_name) { - return self::from_table_scoped_html($html); - } - $attributes = self::extract_attributes($processor); - $inner_html = self::extract_inner_html($html, $tag_name); - return new self($tag_name, $attributes, $html, $inner_html); - } - /** - * Creates an HTML_Element for table-scoped tags that cannot be parsed standalone - * - * @param string $html Raw HTML - * @return self|null - */ - private static function from_table_scoped_html(string $html): ?self - { - if (!\preg_match('/^<\s*([a-z0-9:-]+)/i', $html, $matches)) { - return null; - } - $tag = \strtolower($matches[1]); - $wrappers = array('thead' => '%s
    ', 'tbody' => '%s
    ', 'tfoot' => '%s
    ', 'caption' => '%s
    ', 'colgroup' => '%s
    ', 'col' => '%s
    ', 'tr' => '%s
    ', 'td' => '%s
    ', 'th' => '%s
    '); - if (!isset($wrappers[$tag])) { - return null; - } - $wrapped_html = \sprintf($wrappers[$tag], $html); - $processor = \WP_HTML_Processor::create_fragment($wrapped_html); - if (!$processor) { - return null; - } - while ($processor->next_tag()) { - if ($processor->is_tag_closer()) { - continue; - } - if (\strtolower($processor->get_tag()) !== $tag) { - continue; - } - $attributes = self::extract_attributes($processor); - $inner_html = self::extract_inner_html($html, $processor->get_tag()); - return new self($processor->get_tag(), $attributes, $html, $inner_html); - } - return null; - } - /** - * Extracts all attributes from the current processor position - * - * @param WP_HTML_Processor $processor HTML processor at an element - * @return array Associative array of attribute name => value - */ - private static function extract_attributes(\WP_HTML_Processor $processor): array - { - $attributes = array(); - $names = $processor->get_attribute_names_with_prefix(''); - if ($names) { - foreach ($names as $name) { - $attributes[$name] = $processor->get_attribute($name); - } - } - return $attributes; - } - /** - * Parses attributes from a raw opening tag string. - * - * @param string $attribute_string Raw attribute markup. - * @return array Attribute map. - */ - private static function parse_attribute_string(string $attribute_string): array - { - $attributes = array(); - if (\preg_match_all('/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|\'([^\']*)\'|([^\s"\'>]+))/', $attribute_string, $matches, \PREG_SET_ORDER)) { - foreach ($matches as $match) { - $value = ''; - if (isset($match[3]) && '' !== $match[3]) { - $value = $match[3]; - } elseif (isset($match[4]) && '' !== $match[4]) { - $value = $match[4]; - } elseif (isset($match[5])) { - $value = $match[5]; - } - $attributes[\strtolower($match[1])] = \html_entity_decode($value, \ENT_QUOTES, 'UTF-8'); - } - } - return $attributes; - } - /** - * Extracts inner HTML from an element string - * - * @param string $html Full element HTML - * @param string $tag_name Tag name to find closing tag - * @return string Inner HTML content - */ - private static function extract_inner_html(string $html, string $tag_name): string - { - $tag_lower = \strtolower($tag_name); - $void_elements = array('area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'); - if (\in_array($tag_lower, $void_elements, \true)) { - return ''; - } - $pattern = '/^<' . \preg_quote($tag_name, '/') . '(?:\s[^>]*)?>(.*)$/is'; - if (!\preg_match($pattern, $html, $matches)) { - return ''; - } - $content = $matches[1]; - $close_pattern = '/<\/' . \preg_quote($tag_name, '/') . '>\s*$/i'; - $content = \preg_replace($close_pattern, '', $content); - return \trim($content); - } - /** - * Gets the tag name (uppercase) - * - * @return string Tag name - */ - public function get_tag_name(): string - { - return \strtoupper($this->tag_name); - } - /** - * Alias for get_tag_name() to match DOMNode interface - * - * @return string Tag name - */ - public function get_node_name(): string - { - return $this->get_tag_name(); - } - /** - * Gets an attribute value - * - * @param string $name Attribute name - * @return string|null Attribute value or null if not present - */ - public function get_attribute(string $name): ?string - { - $value = $this->attributes[\strtolower($name)] ?? null; - if (\true === $value) { - return ''; - } - return $value; - } - /** - * Checks if an attribute exists - * - * @param string $name Attribute name - * @return bool True if attribute exists - */ - public function has_attribute(string $name): bool - { - return isset($this->attributes[\strtolower($name)]); - } - /** - * Gets all attributes - * - * @return array Associative array of attributes - */ - public function get_attributes(): array - { - return $this->attributes; - } - /** - * Gets the inner HTML content - * - * @return string Inner HTML - */ - public function get_inner_html(): string - { - return $this->inner_html; - } - /** - * Gets the outer HTML (full element including tags) - * - * @return string Outer HTML - */ - public function get_outer_html(): string - { - return $this->outer_html; - } - /** - * Gets the text content (strips all HTML tags) - * - * @return string Text content - */ - public function get_text_content(): string - { - return \trim(wp_strip_all_tags($this->inner_html)); - } - /** - * Queries for a descendant element matching a simple selector - * - * @param string $selector Simple CSS selector (tag, .class, #id) - * @return self|null Matching element or null - */ - public function query_selector(string $selector): ?self - { - $results = $this->query_selector_all($selector); - return $results[0] ?? null; - } - /** - * Queries for all descendant elements matching a simple selector - * - * @param string $selector Simple CSS selector (tag, .class, #id) - * @return array Array of matching elements - */ - public function query_selector_all(string $selector): array - { - $processor = \WP_HTML_Processor::create_fragment($this->outer_html); - if (!$processor) { - return array(); - } - $selector = \trim($selector); - $results = array(); - $tag_match = null; - $class_match = null; - $id_match = null; - $occurrence_counters = array(); - if (\preg_match('/^([a-z0-9]+)?(?:\.([a-z0-9_-]+))?(?:#([a-z0-9_-]+))?$/i', $selector, $matches)) { - $tag_match = !empty($matches[1]) ? \strtoupper($matches[1]) : null; - $class_match = $matches[2] ?? null; - $id_match = $matches[3] ?? null; - } - $root_depth = null; - while ($processor->next_tag()) { - if ($processor->is_tag_closer()) { - continue; - } - $tag = $processor->get_tag(); - $tag_lower = \strtolower($tag); - $occurrence_counters[$tag_lower] = ($occurrence_counters[$tag_lower] ?? 0) + 1; - $occurrence = $occurrence_counters[$tag_lower] - 1; - if (null === $root_depth) { - $root_depth = $processor->get_current_depth(); - continue; - } - if ($processor->get_current_depth() <= $root_depth) { - continue; - } - if ($tag_match && \strtoupper($tag) !== $tag_match) { - continue; - } - if ($class_match) { - $class_attr = $processor->get_attribute('class'); - if (!\is_string($class_attr) || !\preg_match('/(?:^|\s)' . \preg_quote($class_match, '/') . '(?:$|\s)/', $class_attr)) { - continue; - } - } - if ($id_match) { - $id_attr = $processor->get_attribute('id'); - if ($id_attr !== $id_match) { - continue; - } - } - $element_html = self::extract_element_html_at_occurrence($this->outer_html, $tag_lower, $occurrence); - if ($element_html) { - $element = self::from_html($element_html); - if ($element) { - $results[] = $element; - } - } - } - return $results; - } - /** - * Gets child elements (direct descendants only) - * - * @return array Array of child elements - */ - public function get_child_elements(): array - { - $processor = \WP_HTML_Processor::create_fragment($this->outer_html); - if (!$processor) { - return array(); - } - $children = array(); - $root_depth = null; - $occurrence_counters = array(); - $tag_positions = array(); - while ($processor->next_tag()) { - if ($processor->is_tag_closer()) { - continue; - } - $tag_name = $processor->get_tag(); - $tag_lower = \strtolower($tag_name); - $occurrence_counters[$tag_lower] = ($occurrence_counters[$tag_lower] ?? 0) + 1; - $occurrence = $occurrence_counters[$tag_lower] - 1; - if (null === $root_depth) { - $root_depth = $processor->get_current_depth(); - continue; - } - $depth = $processor->get_current_depth(); - if ($depth !== $root_depth + 1) { - continue; - } - if (!isset($tag_positions[$tag_lower])) { - $tag_positions[$tag_lower] = self::find_tag_positions($this->outer_html, $tag_lower); - } - $element_html = self::extract_element_html_at_occurrence($this->outer_html, $tag_lower, $occurrence, $tag_positions[$tag_lower]); - if ($element_html) { - $element = self::from_html($element_html); - if ($element) { - $children[] = $element; - } - } - } - return $children; - } - /** - * Extracts element HTML at a specific occurrence in source HTML - * - * @param string $html Source HTML - * @param string $tag_name Tag name (lowercase) - * @param int $occurrence Which occurrence (0-based) - * @return string|null - */ - private static function extract_element_html_at_occurrence(string $html, string $tag_name, int $occurrence, ?array $positions = null): ?string - { - $positions = $positions ?? self::find_tag_positions($html, $tag_name); - if (!isset($positions[$occurrence])) { - return null; - } - $start_pos = $positions[$occurrence]; - $html_from_start = \substr($html, $start_pos); - $void_elements = array('area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'); - if (\in_array(\strtolower($tag_name), $void_elements, \true)) { - $pattern = '/<' . \preg_quote($tag_name, '/') . '(?:\s[^>]*)?\/?>/i'; - if (\preg_match($pattern, $html_from_start, $matches)) { - return $matches[0]; - } - return null; - } - return self::extract_balanced_element($html_from_start, $tag_name); - } - /** - * Finds all positions of a tag's opening tags in HTML - * - * @param string $html Source HTML - * @param string $tag_name Tag name - * @return array - */ - private static function find_tag_positions(string $html, string $tag_name): array - { - $positions = array(); - $offset = 0; - $pattern = '/<' . \preg_quote($tag_name, '/') . '(?:\s|>)/i'; - while (\preg_match($pattern, $html, $matches, \PREG_OFFSET_CAPTURE, $offset)) { - $positions[] = $matches[0][1]; - $offset = $matches[0][1] + 1; - } - return $positions; - } - /** - * Extracts a balanced element including nested elements of same type - * - * @param string $html HTML starting with opening tag - * @param string $tag_name Tag name - * @return string|null - */ - private static function extract_balanced_element(string $html, string $tag_name): ?string - { - $depth = 0; - $tag_pattern = '/<\/?' . \preg_quote($tag_name, '/') . '(?:\s[^>]*)?>/i'; - $matched_count = \preg_match_all($tag_pattern, $html, $matches, \PREG_OFFSET_CAPTURE); - if (\false === $matched_count || 0 === $matched_count) { - return null; - } - foreach ($matches[0] as $match) { - $tag_markup = $match[0]; - $offset = $match[1]; - if (0 === \strpos($tag_markup, ' initializer callback. - * - * @var array - */ - private $versions = array(); - /** - * Whether the winning version has already initialized. - * - * @var bool - */ - private $initialized = \false; - /** - * Whether the plugins_loaded hook has been registered. - * - * @var bool - */ - private static $hooked = \false; - /** - * Get singleton. - * - * @return self - */ - public static function instance(): self - { - if (null === self::$instance) { - self::$instance = new self(); - } - return self::$instance; - } - /** - * Ensure the latest-version initializer runs once per request. - * - * @return void - */ - public static function register_hooks(): void - { - if (self::$hooked || !\function_exists('add_action')) { - return; - } - \add_action('plugins_loaded', array(__CLASS__, 'initialize_latest_version'), 1); - self::$hooked = \true; - } - /** - * Register one copy of the library. - * - * @param string $version Semantic version string. - * @param callable $initializer Initializer that loads this copy's files. - * @return void - */ - public function register(string $version, callable $initializer): void - { - if ($this->initialized) { - return; - } - $this->versions[$version] = $initializer; - } - /** - * Initialize the highest registered version. - * - * @return void - */ - public static function initialize_latest_version(): void - { - self::instance()->initialize_latest(); - } - /** - * Initialize the highest registered version. - * - * @return void - */ - public function initialize_latest(): void - { - if ($this->initialized || empty($this->versions)) { - return; - } - \uksort($this->versions, 'version_compare'); - $version = \array_key_last($this->versions); - $initializer = $this->versions[$version]; - $this->initialized = \true; - $initializer(); - /** - * Fires after the winning html-to-blocks-converter version loads. - * - * @param string $version Loaded version. - */ - \do_action('html_to_blocks_loaded', $version); - } - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-svg-icon-classifier.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-svg-icon-classifier.php deleted file mode 100644 index f4946cb..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-svg-icon-classifier.php +++ /dev/null @@ -1,233 +0,0 @@ - \false, 'svg' => '', 'metadata' => array(), 'reason' => ''); - if ('' === $svg || \strlen($svg) > self::MAX_BYTES) { - $result['reason'] = 'size_limit'; - return $result; - } - if (!\class_exists('DOMDocument', \false)) { - $result['reason'] = 'dom_unavailable'; - return $result; - } - $document = new \DOMDocument(); - $previous = \libxml_use_internal_errors(\true); - $loaded = $document->loadXML($svg, \LIBXML_NONET | \LIBXML_NOERROR | \LIBXML_NOWARNING); - \libxml_clear_errors(); - \libxml_use_internal_errors($previous); - if (!$loaded || !$document->documentElement || \strtolower($document->documentElement->tagName) !== 'svg') { - $result['reason'] = 'invalid_svg'; - return $result; - } - $state = array('nodes' => 0, 'max_depth' => 0, 'tags' => array(), 'reason' => '', 'local_refs' => self::collect_local_reference_ids($document->documentElement)); - $sanitized = self::sanitize_element($document->documentElement, $document, 1, $state); - if (!$sanitized) { - $result['reason'] = $state['reason'] ? $state['reason'] : 'unsafe_svg'; - return $result; - } - $view_box = $sanitized->getAttribute('viewBox'); - $width = $sanitized->getAttribute('width'); - $height = $sanitized->getAttribute('height'); - if (!self::has_bounded_graphic_dimensions($view_box, $width, $height)) { - $result['reason'] = 'dimension_limit'; - return $result; - } - $sanitized_svg = $document->saveXML($sanitized); - if (!\is_string($sanitized_svg) || '' === $sanitized_svg) { - $result['reason'] = 'serialization_failed'; - return $result; - } - $is_icon_sized = self::is_icon_sized_graphic($view_box, $width, $height); - $result['is_safe'] = \true; - $result['svg'] = $sanitized_svg; - $result['reason'] = $is_icon_sized ? 'safe_svg_icon' : 'safe_inline_svg_illustration'; - $result['metadata'] = array('kind' => $is_icon_sized ? 'inline-svg-icon' : 'inline-svg-illustration', 'viewBox' => $view_box, 'width' => $width, 'height' => $height, 'className' => $sanitized->getAttribute('class'), 'ariaLabel' => $sanitized->getAttribute('aria-label'), 'nodeCount' => $state['nodes'], 'maxDepth' => $state['max_depth'], 'tags' => \array_values(\array_unique($state['tags']))); - return $result; - } - /** - * Recursively validates and copies an allowed SVG element into the sanitizer document. - * - * @param DOMElement $source Source element. - * @param DOMDocument $document Sanitizer document. - * @param int $depth Current element depth. - * @param array $state Mutable traversal state. - * @return DOMElement|null Sanitized element or null when unsafe. - */ - private static function sanitize_element(\DOMElement $source, \DOMDocument $document, int $depth, array &$state): ?\DOMElement - { - $tag = \strtolower($source->tagName); - if (!\in_array($tag, self::ALLOWED_TAGS, \true) || \preg_match('/^animate/i', $tag) === 1) { - $state['reason'] = 'disallowed_tag'; - return null; - } - ++$state['nodes']; - $state['max_depth'] = \max($state['max_depth'], $depth); - $state['tags'][] = $tag; - if ($state['nodes'] > self::MAX_NODES || $depth > self::MAX_DEPTH) { - $state['reason'] = 'complexity_limit'; - return null; - } - $target = $document->createElement($tag); - foreach (\iterator_to_array($source->attributes) as $attribute) { - $name = \strtolower($attribute->name); - $value = \trim($attribute->value); - if (!self::is_allowed_attribute($name, $value, $state)) { - $state['reason'] = 'disallowed_attribute'; - return null; - } - $target->setAttribute('viewbox' === $name ? 'viewBox' : $name, $value); - } - foreach (\iterator_to_array($source->childNodes) as $child) { - if ($child instanceof \DOMText) { - $text = \trim($child->textContent); - if ('' !== $text) { - $target->appendChild($document->createTextNode($text)); - } - continue; - } - if ($child instanceof \DOMElement) { - $child_element = self::sanitize_element($child, $document, $depth + 1, $state); - if (!$child_element) { - return null; - } - $target->appendChild($child_element); - continue; - } - if (\in_array($child->nodeType, array(\XML_COMMENT_NODE, \XML_CDATA_SECTION_NODE, \XML_PI_NODE), \true)) { - $state['reason'] = 'disallowed_node'; - return null; - } - } - return $target; - } - /** - * Checks whether an SVG attribute is allowed and reference-free. - * - * @param string $name Attribute name. - * @param string $value Attribute value. - * @return bool True when safe. - */ - private static function is_allowed_attribute(string $name, string $value, array $state): bool - { - if (\strpos($name, 'on') === 0 || \in_array($name, array('href', 'xlink:href', 'src', 'style'), \true)) { - return \false; - } - if ('id' === $name) { - return \preg_match('/^[A-Za-z][A-Za-z0-9_-]*$/', $value) === 1; - } - if ('xmlns' === $name && 'http://www.w3.org/2000/svg' === $value) { - return \true; - } - if (!\in_array($name, self::ALLOWED_ATTRIBUTES, \true)) { - return \false; - } - if (\preg_match('/url\s*\(/i', $value) === 1) { - if (\preg_match('/^url\(#([A-Za-z][A-Za-z0-9_-]*)\)$/', $value, $matches) !== 1) { - return \false; - } - return \in_array($matches[1], $state['local_refs'] ?? array(), \true); - } - return \preg_match('/(?:https?:)?\/\/|data:/i', $value) !== 1; - } - /** - * Collects local paint server IDs that are safe to reference via url(#id). - * - * @param DOMElement $root Root SVG element. - * @return string[] Local reference IDs. - */ - private static function collect_local_reference_ids(\DOMElement $root): array - { - $ids = array(); - foreach ($root->getElementsByTagName('pattern') as $pattern) { - if ($pattern instanceof \DOMElement && $pattern->hasAttribute('id')) { - $id = \trim($pattern->getAttribute('id')); - if (\preg_match('/^[A-Za-z][A-Za-z0-9_-]*$/', $id) === 1) { - $ids[] = $id; - } - } - } - return \array_values(\array_unique($ids)); - } - /** - * Applies bounded inline graphic dimension limits. - * - * @param string $view_box SVG viewBox attribute. - * @param string $width SVG width attribute. - * @param string $height SVG height attribute. - * @return bool True when dimensions look bounded. - */ - private static function has_bounded_graphic_dimensions(string $view_box, string $width, string $height): bool - { - if ('' !== $view_box) { - $parts = \preg_split('/[\s,]+/', \trim($view_box)); - if (!\is_array($parts)) { - return \false; - } - if (\count($parts) !== 4 || !\is_numeric($parts[2]) || !\is_numeric($parts[3])) { - return \false; - } - if ((float) $parts[2] <= 0 || (float) $parts[3] <= 0 || (float) $parts[2] > self::MAX_GRAPHIC_SIZE || (float) $parts[3] > self::MAX_GRAPHIC_SIZE) { - return \false; - } - } - foreach (array($width, $height) as $dimension) { - if ('' === $dimension) { - continue; - } - if (\preg_match('/^([0-9]+(?:\.[0-9]+)?)(?:px)?$/', $dimension, $matches) !== 1 || (float) $matches[1] > self::MAX_GRAPHIC_SIZE) { - return \false; - } - } - return '' !== $view_box || '' !== $width || '' !== $height; - } - /** - * Checks whether a bounded SVG is small enough to keep icon metadata. - * - * @param string $view_box SVG viewBox attribute. - * @param string $width SVG width attribute. - * @param string $height SVG height attribute. - * @return bool True when dimensions look icon-sized. - */ - private static function is_icon_sized_graphic(string $view_box, string $width, string $height): bool - { - if ('' !== $view_box) { - $parts = \preg_split('/[\s,]+/', \trim($view_box)); - return \is_array($parts) && \count($parts) === 4 && \is_numeric($parts[2]) && \is_numeric($parts[3]) && (float) $parts[2] <= 256 && (float) $parts[3] <= 256; - } - foreach (array($width, $height) as $dimension) { - if ('' === $dimension) { - continue; - } - if (\preg_match('/^([0-9]+(?:\.[0-9]+)?)(?:px)?$/', $dimension, $matches) !== 1 || (float) $matches[1] > 256) { - return \false; - } - } - return '' !== $view_box || '' !== $width || '' !== $height; - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-transform-registry.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-transform-registry.php deleted file mode 100644 index 8db4398..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/class-transform-registry.php +++ /dev/null @@ -1,3897 +0,0 @@ -> - */ - private static array $repeated_card_grid_children = array(); - /** - * Gets all raw transforms for core blocks - * Sorted by priority (lower = higher priority) - * - * @return array Array of transform definitions - */ - public static function get_raw_transforms() - { - if (null !== self::$transforms) { - return self::$transforms; - } - self::$transforms = \array_merge(self::get_site_editor_marker_transforms(), self::get_svg_icon_transforms(), self::get_heading_transforms(), self::get_list_transforms(), self::get_button_transforms(), self::get_media_transforms(), self::get_image_transforms(), self::get_details_transforms(), self::get_pullquote_transforms(), self::get_quote_transforms(), self::get_code_transforms(), self::get_code_window_transforms(), self::get_verse_transforms(), self::get_preformatted_transforms(), self::get_separator_transforms(), self::get_table_transforms(), self::get_form_transforms(), self::get_layout_transforms(), self::get_paragraph_transforms()); - \usort(self::$transforms, function ($a, $b) { - return ($a['priority'] ?? 10) - ($b['priority'] ?? 10); - }); - return self::$transforms; - } - /** - * Safe inline SVG icon transform. - * - * h2bc exposes these as explicit placeholders rather than final core/html so - * stricter downstream pipelines can replace them with native assets. - * - * @return array Transform definitions - */ - private static function get_svg_icon_transforms() - { - return array(array('blockName' => 'html-to-blocks/svg-icon', 'priority' => 2, 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'SVG' || !\class_exists('BlockFormatBridge\Vendor\HTML_To_Blocks_SVG_Icon_Classifier', \false)) { - return \false; - } - $classification = HTML_To_Blocks_SVG_Icon_Classifier::classify($element->get_outer_html()); - return !empty($classification['is_safe']); - }, 'transform' => function ($element) { - $classification = HTML_To_Blocks_SVG_Icon_Classifier::classify($element->get_outer_html()); - $svg = $classification['svg'] ?? ''; - $metadata = $classification['metadata'] ?? array(); - if (\function_exists('do_action')) { - \do_action('html_to_blocks_safe_inline_svg_icon', $svg, $metadata, $classification); - } - return array('blockName' => 'html-to-blocks/svg-icon', 'attrs' => array('svg' => $svg, 'metadata' => $metadata), 'innerBlocks' => array(), 'innerHTML' => $svg, 'innerContent' => array($svg)); - })); - } - /** - * Explicit Site Editor primitive marker transforms. - * - * @return array Transform definitions - */ - private static function get_site_editor_marker_transforms() - { - return array(array('blockName' => 'core/pattern', 'priority' => 1, 'isMatch' => function ($element) { - return self::get_pattern_marker_slug($element) !== ''; - }, 'transform' => function ($element) { - return HTML_To_Blocks_Block_Factory::create_block('core/pattern', array('slug' => self::get_pattern_marker_slug($element))); - }), array('blockName' => 'core/template-part', 'priority' => 1, 'isMatch' => function ($element) { - return self::get_template_part_marker_slug($element) !== ''; - }, 'transform' => function ($element) { - $slug = self::get_template_part_marker_slug($element); - $attributes = array('slug' => $slug); - if (\in_array($slug, array('header', 'footer', 'sidebar'), \true)) { - $attributes['area'] = $slug; - } - return HTML_To_Blocks_Block_Factory::create_block('core/template-part', $attributes); - })); - } - /** - * Gets a valid explicit pattern marker slug. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return string Pattern slug or empty string. - */ - private static function get_pattern_marker_slug($element): string - { - if (!$element->has_attribute('data-bfb-pattern')) { - return ''; - } - $slug = \trim((string) $element->get_attribute('data-bfb-pattern')); - return \preg_match('/^[a-z0-9_.-]+\/[a-z0-9_.\/-]+$/i', $slug) === 1 ? $slug : ''; - } - /** - * Gets a valid explicit template-part marker slug. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return string Template-part slug or empty string. - */ - private static function get_template_part_marker_slug($element): string - { - if (!$element->has_attribute('data-bfb-template-part')) { - return ''; - } - $slug = \trim((string) $element->get_attribute('data-bfb-template-part')); - return \preg_match('/^[a-z0-9_.-]+$/i', $slug) === 1 ? $slug : ''; - } - /** - * Media and embed transforms for high-confidence static HTML patterns. - * - * @return array Transform definitions - */ - private static function get_media_transforms() - { - return array(array('blockName' => 'core/gallery', 'priority' => 8, 'isMatch' => function ($element) { - return self::is_gallery_element($element); - }, 'transform' => function ($element, $handler = null, array $args = array()) { - return self::create_gallery_block($element, $args); - }), array('blockName' => 'core/media-text', 'priority' => 8, 'isMatch' => function ($element) { - $class = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - return \preg_match('/(?:^|\s)(?:wp-block-media-text|media-text)(?:$|\s)/i', $class) === 1 && ($element->query_selector('img') || $element->query_selector('video')); - }, 'transform' => function ($element, $handler, array $args = array()) { - return self::create_media_text_block($element, $handler, $args); - }), array('blockName' => 'core/video', 'priority' => 9, 'isMatch' => function ($element) { - $video = $element->get_tag_name() === 'VIDEO' ? $element : $element->query_selector('video'); - return $video && self::get_media_src($video) !== ''; - }, 'transform' => function ($element) { - $video = $element->get_tag_name() === 'VIDEO' ? $element : $element->query_selector('video'); - $attributes = self::get_media_attributes($video, array('src', 'poster', 'preload', 'autoplay', 'controls', 'loop', 'muted', 'playsInline')); - if ($element->get_tag_name() === 'FIGURE') { - $caption = $element->query_selector('figcaption'); - if ($caption) { - $attributes['caption'] = $caption->get_inner_html(); - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/video', $attributes); - }), array('blockName' => 'core/audio', 'priority' => 9, 'isMatch' => function ($element) { - $audio = $element->get_tag_name() === 'AUDIO' ? $element : $element->query_selector('audio'); - return $audio && self::get_media_src($audio) !== ''; - }, 'transform' => function ($element) { - $audio = $element->get_tag_name() === 'AUDIO' ? $element : $element->query_selector('audio'); - $attributes = self::get_media_attributes($audio, array('src', 'preload', 'autoplay', 'loop')); - if ($element->get_tag_name() === 'FIGURE') { - $caption = $element->query_selector('figcaption'); - if ($caption) { - $attributes['caption'] = $caption->get_inner_html(); - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/audio', $attributes); - }), array('blockName' => 'core/file', 'priority' => 9, 'isMatch' => function ($element) { - return $element->get_tag_name() === 'A' && $element->has_attribute('href') && self::is_file_link($element); - }, 'transform' => function ($element) { - return self::create_file_block_from_anchor($element); - }), array('blockName' => 'core/file', 'priority' => 9, 'isMatch' => function ($element) { - $anchor = $element->get_tag_name() === 'P' ? $element->query_selector('a') : null; - return $anchor && self::is_file_link($anchor) && \trim($element->get_inner_html()) === \trim($anchor->get_outer_html()); - }, 'transform' => function ($element) { - return self::create_file_block_from_anchor($element->query_selector('a')); - }), array('blockName' => 'core/embed', 'priority' => 9, 'isMatch' => function ($element) { - return $element->get_tag_name() === 'IFRAME' && $element->has_attribute('src') && self::get_embed_provider_slug($element->get_attribute('src')) !== ''; - }, 'transform' => function ($element) { - $src = $element->get_attribute('src'); - $attributes = array('url' => self::normalise_embed_url($src), 'type' => 'rich', 'providerNameSlug' => self::get_embed_provider_slug($src), 'responsive' => \true); - return HTML_To_Blocks_Block_Factory::create_block('core/embed', $attributes); - })); - } - /** - * Checks whether an element is a high-confidence gallery wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool - */ - private static function is_gallery_element($element): bool - { - $class = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - if (\preg_match('/(?:^|\s)(?:wp-block-gallery|blocks-gallery-grid|gallery|image-grid)(?:$|\s)/i', $class) !== 1) { - return \false; - } - return \count($element->query_selector_all('img')) > 1; - } - /** - * Creates a gallery block containing image inner blocks. - * - * @param HTML_To_Blocks_HTML_Element $element Gallery wrapper. - * @return array Block array. - */ - private static function create_gallery_block($element, array $args = array()): array - { - $images = $element->query_selector_all('img'); - $captions = $element->query_selector_all('figcaption'); - $inner_blocks = array(); - $ids = array(); - foreach ($images as $index => $img) { - $caption = isset($captions[$index]) ? $captions[$index]->get_inner_html() : ''; - $image_block = self::create_image_block_from_img($img, $caption, $args); - $inner_blocks[] = $image_block; - if (isset($image_block['attrs']['id'])) { - $ids[] = $image_block['attrs']['id']; - } - } - $attributes = array(); - if (!empty($ids)) { - $attributes['ids'] = $ids; - } - if (\preg_match('/(?:^|\s)columns-(\d+)(?:$|\s)/', $element->get_attribute('class') ?? '', $matches)) { - $attributes['columns'] = \min(8, \max(1, (int) $matches[1])); - } - return HTML_To_Blocks_Block_Factory::create_block('core/gallery', $attributes, $inner_blocks); - } - /** - * Creates an image block from an img element. - * - * @param HTML_To_Blocks_HTML_Element $img Image element. - * @param string $caption Optional caption HTML. - * @return array Block array. - */ - private static function create_image_block_from_img($img, string $caption = '', array $args = array()): array - { - $attributes = array('url' => $img->get_attribute('src') ?? ''); - self::apply_image_element_attributes($attributes, $img); - if ('' !== $caption) { - $attributes['caption'] = $caption; - } - if ($img->has_attribute('class') && \preg_match('/(?:^|\s)wp-image-(\d+)(?:$|\s)/', $img->get_attribute('class'), $matches)) { - $attributes['id'] = (int) $matches[1]; - } - self::apply_image_asset_metadata($attributes, $args); - return HTML_To_Blocks_Block_Factory::create_block('core/image', $attributes); - } - /** - * Preserves direct img attributes that are safe and useful in static snapshots. - * - * @param array $attributes Block attributes. - * @param HTML_To_Blocks_HTML_Element $img Source img element. - */ - private static function apply_image_element_attributes(array &$attributes, $img): void - { - foreach (array('alt', 'title', 'srcset', 'sizes', 'width', 'height') as $attribute) { - if ($img->has_attribute($attribute)) { - $attributes[$attribute] = $img->get_attribute($attribute); - } - } - } - /** - * Applies caller-resolved asset metadata to image block attributes. - * - * @param array $attributes Block attributes. - * @param array $args Raw handler arguments. - */ - private static function apply_image_asset_metadata(array &$attributes, array $args): void - { - $metadata = self::get_asset_metadata_for_source($args, (string) ($attributes['url'] ?? '')); - if (empty($metadata)) { - return; - } - if (isset($metadata['url']) && \is_scalar($metadata['url']) && '' !== (string) $metadata['url']) { - $attributes['url'] = (string) $metadata['url']; - } - if (isset($metadata['id']) && \is_numeric($metadata['id']) && (int) $metadata['id'] > 0) { - $attributes['id'] = (int) $metadata['id']; - } - foreach (array('alt', 'width', 'height') as $attribute) { - if (!isset($attributes[$attribute]) && isset($metadata[$attribute]) && \is_scalar($metadata[$attribute])) { - $attributes[$attribute] = (string) $metadata[$attribute]; - } - } - } - /** - * Returns caller-provided asset metadata for a source reference. - * - * @param array $args Raw handler arguments. - * @param string $source Source URL/path from the HTML. - * @return array - */ - private static function get_asset_metadata_for_source(array $args, string $source): array - { - $context = isset($args['context']) && \is_array($args['context']) ? $args['context'] : array(); - $maps = array('asset_metadata', 'resolved_assets', 'assets'); - foreach ($maps as $map_key) { - if (empty($context[$map_key]) || !\is_array($context[$map_key])) { - continue; - } - $map = $context[$map_key]; - if (isset($map[$source]) && \is_array($map[$source])) { - return $map[$source]; - } - foreach ($map as $metadata) { - if (!\is_array($metadata)) { - continue; - } - foreach (array('source', 'src', 'path', 'original') as $source_key) { - if (isset($metadata[$source_key]) && (string) $metadata[$source_key] === $source) { - return $metadata; - } - } - } - } - return array(); - } - /** - * Creates a media-text block from a recognized two-column wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element Media-text wrapper. - * @param callable $handler Recursive raw handler. - * @return array Block array. - */ - private static function create_media_text_block($element, $handler, array $args = array()): array - { - $media = $element->query_selector('img') ? $element->query_selector('img') : $element->query_selector('video'); - $content = $element->query_selector('.wp-block-media-text__content'); - $media_type = $media && $media->get_tag_name() === 'VIDEO' ? 'video' : 'image'; - $attributes = array('mediaUrl' => self::get_media_src($media), 'mediaType' => $media_type, 'mediaPosition' => 'left', 'mediaWidth' => 50, 'isStackedOnMobile' => \true); - if ($media && $media->has_attribute('alt')) { - $attributes['mediaAlt'] = $media->get_attribute('alt'); - } - if ($media && $media->has_attribute('class') && \preg_match('/(?:^|\s)wp-image-(\d+)(?:$|\s)/', $media->get_attribute('class'), $matches)) { - $attributes['mediaId'] = (int) $matches[1]; - } - $metadata = self::get_asset_metadata_for_source($args, (string) $attributes['mediaUrl']); - if (!empty($metadata)) { - if (isset($metadata['url']) && \is_scalar($metadata['url']) && '' !== (string) $metadata['url']) { - $attributes['mediaUrl'] = (string) $metadata['url']; - } - if (isset($metadata['id']) && \is_numeric($metadata['id']) && (int) $metadata['id'] > 0) { - $attributes['mediaId'] = (int) $metadata['id']; - } - if (!isset($attributes['mediaAlt']) && isset($metadata['alt']) && \is_scalar($metadata['alt'])) { - $attributes['mediaAlt'] = (string) $metadata['alt']; - } - } - if (\preg_match('/(?:^|\s)has-media-on-the-right(?:$|\s)/', $element->get_attribute('class') ?? '')) { - $attributes['mediaPosition'] = 'right'; - } - if (!$content) { - $children = $element->get_child_elements(); - foreach ($children as $child) { - if (!$child->query_selector('img') && !$child->query_selector('video')) { - $content = $child; - break; - } - } - } - $inner_blocks = $content ? $handler(array('HTML' => $content->get_inner_html())) : array(); - return HTML_To_Blocks_Block_Factory::create_block('core/media-text', $attributes, $inner_blocks); - } - /** - * Extracts the best media source from src or nested source tags. - * - * @param HTML_To_Blocks_HTML_Element|null $element Media element. - * @return string - */ - private static function get_media_src($element): string - { - if (!$element) { - return ''; - } - if ($element->has_attribute('src') && $element->get_attribute('src') !== '') { - return $element->get_attribute('src'); - } - $source = $element->query_selector('source'); - if ($source && $source->has_attribute('src')) { - return $source->get_attribute('src'); - } - return ''; - } - /** - * Extracts media attributes from a video/audio element. - * - * @param HTML_To_Blocks_HTML_Element $element Media element. - * @param array $keys Allowed attributes. - * @return array - */ - private static function get_media_attributes($element, array $keys): array - { - $attributes = array('src' => self::get_media_src($element)); - foreach ($keys as $key) { - $html_key = 'playsInline' === $key ? 'playsinline' : $key; - if ('src' === $key || !$element->has_attribute($html_key)) { - continue; - } - $value = $element->get_attribute($html_key); - if (\in_array($key, array('autoplay', 'controls', 'loop', 'muted', 'playsInline'), \true)) { - $attributes[$key] = \true; - } else { - $attributes[$key] = $value; - } - } - return $attributes; - } - /** - * Checks whether a link points to a document/archive download. - * - * @param HTML_To_Blocks_HTML_Element $element Link element. - * @return bool - */ - private static function is_file_link($element): bool - { - $href = (string) $element->get_attribute('href'); - $href_path = \strtok($href, '?#'); - $href_path = \false === $href_path ? '' : \strtolower($href_path); - if (\preg_match('/\.(?:pdf|docx?|pptx?|xlsx?|zip|rar|7z|txt|csv|ics|epub)$/', $href_path)) { - return \true; - } - $class = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - return $element->has_attribute('download') && \preg_match('/(?:^|\s)(?:download|file)(?:$|\s)/i', $class) === 1; - } - /** - * Creates a file block from a downloadable anchor. - * - * @param HTML_To_Blocks_HTML_Element $anchor Link element. - * @return array Block array. - */ - private static function create_file_block_from_anchor($anchor): array - { - $attributes = array('href' => $anchor->get_attribute('href'), 'textLinkHref' => $anchor->get_attribute('href'), 'fileName' => $anchor->get_inner_html(), 'showDownloadButton' => \true); - if ($anchor->has_attribute('target')) { - $attributes['textLinkTarget'] = $anchor->get_attribute('target'); - } - return HTML_To_Blocks_Block_Factory::create_block('core/file', $attributes); - } - /** - * Infers a conservative core/embed provider slug from a URL. - * - * @param string $url Embed URL. - * @return string - */ - private static function get_embed_provider_slug(string $url): string - { - $host = wp_parse_url($url, \PHP_URL_HOST); - $host = $host ? \strtolower(\preg_replace('/^www\./', '', $host)) : ''; - $providers = array('youtube.com' => 'youtube', 'youtu.be' => 'youtube', 'vimeo.com' => 'vimeo', 'soundcloud.com' => 'soundcloud', 'spotify.com' => 'spotify', 'twitter.com' => 'twitter', 'x.com' => 'twitter', 'instagram.com' => 'instagram', 'tiktok.com' => 'tiktok'); - foreach ($providers as $needle => $slug) { - if ($host === $needle || \substr($host, -\strlen('.' . $needle)) === '.' . $needle) { - return $slug; - } - } - return ''; - } - /** - * Converts common iframe embed URLs back to their public oEmbed URL. - * - * @param string $url Iframe URL. - * @return string - */ - private static function normalise_embed_url(string $url): string - { - if (\preg_match('#youtube\.com/embed/([^?&/]+)#i', $url, $matches)) { - return 'https://www.youtube.com/watch?v=' . $matches[1]; - } - if (\preg_match('#player\.vimeo\.com/video/(\d+)#i', $url, $matches)) { - return 'https://vimeo.com/' . $matches[1]; - } - return $url; - } - /** - * core/heading transforms - h1-h6 elements - * - * @return array Transform definitions - */ - private static function get_heading_transforms() - { - return array(array('blockName' => 'core/heading', 'priority' => 10, 'selector' => 'h1,h2,h3,h4,h5,h6', 'isMatch' => function ($element) { - return \preg_match('/^H[1-6]$/i', $element->get_tag_name()); - }, 'transform' => function ($element) { - $level = (int) \substr($element->get_tag_name(), 1); - $content = $element->get_inner_html(); - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'align' => \true, 'text_align' => \true, 'colors' => \true, 'typography' => \true, 'spacing' => \true, 'border' => \true, 'class_name' => \true)); - $attributes = \array_merge($attributes, array('level' => $level, 'content' => $content)); - return HTML_To_Blocks_Block_Factory::create_block('core/heading', $attributes); - })); - } - /** - * core/list transforms - ol and ul elements - * - * @return array Transform definitions - */ - private static function get_list_transforms() - { - return array(array('blockName' => 'core/list', 'priority' => 9, 'selector' => 'dl', 'isMatch' => function ($element) { - return 'DL' === $element->get_tag_name() && !empty(self::get_definition_list_pairs($element)); - }, 'transform' => function ($element) { - return self::create_definition_list_block_from_element($element); - }), array('blockName' => 'core/group', 'priority' => 9, 'selector' => 'ol,ul', 'isMatch' => function ($element) { - return \in_array($element->get_tag_name(), array('OL', 'UL'), \true) && self::is_visual_list_element($element); - }, 'transform' => function ($element, $handler) { - return self::create_visual_list_group_from_element($element, $handler); - }), array('blockName' => 'core/list', 'priority' => 10, 'selector' => 'ol,ul', 'isMatch' => function ($element) { - return \in_array($element->get_tag_name(), array('OL', 'UL'), \true); - }, 'transform' => function ($element) { - return self::create_list_block_from_element($element); - })); - } - /** - * Creates blocks from a simple definition list. - * - * @param HTML_To_Blocks_HTML_Element $definition_list The dl element. - * @return array Block array. - */ - private static function create_definition_list_block_from_element($definition_list): array - { - $wrapper_pairs = self::get_definition_list_wrapper_pairs($definition_list); - if (!empty($wrapper_pairs)) { - return self::create_visual_definition_list_group_from_pairs($definition_list, $wrapper_pairs); - } - $list_attributes = self::get_block_support_attributes($definition_list, array('anchor' => \true, 'class_name' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - $list_attributes = \array_merge($list_attributes, array('ordered' => \false)); - $inner_blocks = array(); - foreach (self::get_definition_list_pairs($definition_list) as $pair) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/list-item', array('content' => $pair['term'] . ': ' . $pair['description'])); - } - return HTML_To_Blocks_Block_Factory::create_block('core/list', $list_attributes, $inner_blocks); - } - /** - * Creates grouped native blocks for generated visual metadata definition lists. - * - * @param HTML_To_Blocks_HTML_Element $definition_list The dl element. - * @param array $wrapper_pairs - * Definition wrapper pairs. - * @return array Block array. - */ - private static function create_visual_definition_list_group_from_pairs($definition_list, array $wrapper_pairs): array - { - $inner_blocks = array(); - foreach ($wrapper_pairs as $pair) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_visual_definition_list_group_attributes($pair['wrapper']), array(self::create_definition_list_paragraph_block($pair['term']), self::create_definition_list_paragraph_block($pair['description']))); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_visual_definition_list_group_attributes($definition_list), $inner_blocks); - } - /** - * Creates a paragraph block for a definition term or description. - * - * @param HTML_To_Blocks_HTML_Element $element Source dt or dd element. - * @return array Block array. - */ - private static function create_definition_list_paragraph_block($element): array - { - $attributes = self::get_block_support_attributes($element, array('class_name' => \true, 'colors' => \true, 'typography' => \true, 'spacing' => \true, 'text_align' => \true)); - $attributes['content'] = \trim($element->get_inner_html()); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Gets wrapper-safe attributes for visual definition-list groups. - * - * @param HTML_To_Blocks_HTML_Element $element Source dl or wrapper element. - * @return array Block attributes. - */ - private static function get_visual_definition_list_group_attributes($element): array - { - return self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true, 'dimensions' => \true, 'layout' => \true, 'aria_label' => \true)); - } - /** - * Gets simple term/description pairs from a dl element. - * - * Supports generated fragments shaped as dl > div > dt + dd and plain - * dl > dt + dd. More complex definition lists intentionally keep falling - * through so unsupported structure remains visible to diagnostics. - * - * @param HTML_To_Blocks_HTML_Element $definition_list The dl element. - * @return array Definition pairs. - */ - private static function get_definition_list_pairs($definition_list): array - { - $children = $definition_list->get_child_elements(); - if (empty($children)) { - return array(); - } - if (!empty(self::get_definition_list_wrapper_pairs($definition_list))) { - $pairs = array(); - foreach ($children as $child) { - $pairs[] = self::get_definition_pair_from_wrapper($child); - } - return \array_values(\array_filter($pairs)); - } - return self::get_direct_definition_list_pairs($children); - } - /** - * Gets simple wrapper pairs from generated dl > div > dt + dd markup. - * - * @param HTML_To_Blocks_HTML_Element $definition_list The dl element. - * @return array Wrapper - * pair data. - */ - private static function get_definition_list_wrapper_pairs($definition_list): array - { - $children = $definition_list->get_child_elements(); - if (empty($children) || !self::definition_list_has_only_wrapper_pairs($children)) { - return array(); - } - $pairs = array(); - foreach ($children as $child) { - $wrapper_children = $child->get_child_elements(); - $pairs[] = array('wrapper' => $child, 'term' => $wrapper_children[0], 'description' => $wrapper_children[1]); - } - return $pairs; - } - /** - * Checks whether all direct dl children are div wrappers around dt/dd pairs. - * - * @param array $children Direct dl children. - * @return bool True when every direct child is a simple pair wrapper. - */ - private static function definition_list_has_only_wrapper_pairs(array $children): bool - { - foreach ($children as $child) { - if ('DIV' !== $child->get_tag_name() || !self::get_definition_pair_from_wrapper($child)) { - return \false; - } - } - return \true; - } - /** - * Gets a term/description pair from a simple wrapper element. - * - * @param HTML_To_Blocks_HTML_Element $wrapper Candidate wrapper element. - * @return array{term:string,description:string}|null Definition pair or null. - */ - private static function get_definition_pair_from_wrapper($wrapper): ?array - { - $children = $wrapper->get_child_elements(); - if (\count($children) !== 2 || 'DT' !== $children[0]->get_tag_name() || 'DD' !== $children[1]->get_tag_name()) { - return null; - } - return self::create_definition_pair($children[0], $children[1]); - } - /** - * Gets term/description pairs from a dl with direct dt/dd children. - * - * @param array $children Direct dl children. - * @return array Definition pairs. - */ - private static function get_direct_definition_list_pairs(array $children): array - { - $pairs = array(); - $count = \count($children); - for ($index = 0; $index < $count; $index += 2) { - $term = $children[$index] ?? null; - $description = $children[$index + 1] ?? null; - if (!$term || !$description || 'DT' !== $term->get_tag_name() || 'DD' !== $description->get_tag_name()) { - return array(); - } - $pair = self::create_definition_pair($term, $description); - if (!$pair) { - return array(); - } - $pairs[] = $pair; - } - return $pairs; - } - /** - * Creates a sanitized definition pair from dt and dd elements. - * - * @param HTML_To_Blocks_HTML_Element $term The dt element. - * @param HTML_To_Blocks_HTML_Element $description The dd element. - * @return array{term:string,description:string}|null Definition pair or null. - */ - private static function create_definition_pair($term, $description): ?array - { - $pair = array('term' => \trim($term->get_inner_html()), 'description' => \trim($description->get_inner_html())); - return '' !== wp_strip_all_tags($pair['term']) && '' !== wp_strip_all_tags($pair['description']) ? $pair : null; - } - /** - * Creates a list block from an HTML element (recursive for nested lists) - * - * @param HTML_To_Blocks_HTML_Element $list_element The ol/ul element - * @return array Block array - */ - private static function create_list_block_from_element($list_element) - { - $ordered = $list_element->get_tag_name() === 'OL'; - $list_attributes = self::get_block_support_attributes($list_element, array('anchor' => \true, 'class_name' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - $list_attributes = \array_merge($list_attributes, array('ordered' => $ordered)); - if ($list_element->has_attribute('start')) { - $list_attributes['start'] = (int) $list_element->get_attribute('start'); - } - if ($list_element->has_attribute('reversed')) { - $list_attributes['reversed'] = \true; - } - if ($list_element->has_attribute('type')) { - $type = $list_element->get_attribute('type'); - $type_map = array('A' => 'upper-alpha', 'a' => 'lower-alpha', 'I' => 'upper-roman', 'i' => 'lower-roman'); - if (isset($type_map[$type])) { - $list_attributes['type'] = $type_map[$type]; - } - } - $inner_blocks = array(); - $li_elements = self::get_direct_li_children($list_element->get_inner_html()); - foreach ($li_elements as $li_html) { - $li = HTML_To_Blocks_HTML_Element::from_html($li_html); - if ($li) { - $list_item_block = self::create_list_item_block($li); - if ($list_item_block) { - $inner_blocks[] = $list_item_block; - } - } - } - $block = HTML_To_Blocks_Block_Factory::create_block('core/list', $list_attributes, $inner_blocks); - // The source class is already preserved in the static list wrapper markup. - unset($block['attrs']['className']); - return $block; - } - /** - * Checks whether a list is being used as card/timeline layout scaffolding. - * - * @param HTML_To_Blocks_HTML_Element $list_element The ol/ul element. - * @return bool True when the list should become editable group blocks. - */ - private static function is_visual_list_element($list_element): bool - { - foreach (self::get_direct_li_children($list_element->get_inner_html()) as $li_html) { - $li = HTML_To_Blocks_HTML_Element::from_html($li_html); - if (!$li) { - continue; - } - foreach ($li->get_child_elements() as $child) { - if (self::is_visual_list_item_child($child)) { - return \true; - } - } - } - return \false; - } - /** - * Checks whether a direct li child indicates layout content instead of prose. - * - * @param HTML_To_Blocks_HTML_Element $child Direct child element of an li. - * @return bool True when the child should be editable outside core/list-item content. - */ - private static function is_visual_list_item_child($child): bool - { - return \in_array($child->get_tag_name(), array('DIV', 'SECTION', 'ARTICLE', 'MAIN', 'ASIDE', 'HEADER', 'FOOTER', 'NAV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'P', 'FIGURE', 'TABLE', 'PRE', 'BLOCKQUOTE', 'DETAILS'), \true); - } - /** - * Creates editable group blocks for visual ol/ul card or timeline layouts. - * - * @param HTML_To_Blocks_HTML_Element $list_element The ol/ul element. - * @param callable $handler Raw handler callback. - * @return array Block array. - */ - private static function create_visual_list_group_from_element($list_element, callable $handler): array - { - $inner_blocks = array(); - foreach (self::get_direct_li_children($list_element->get_inner_html()) as $li_html) { - $li = HTML_To_Blocks_HTML_Element::from_html($li_html); - if (!$li) { - continue; - } - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_visual_list_group_attributes($li), $handler(array('HTML' => $li->get_inner_html()))); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_visual_list_group_attributes($list_element), $inner_blocks); - } - /** - * Gets wrapper-safe attributes for visual list groups. - * - * @param HTML_To_Blocks_HTML_Element $element Source ol/ul/li element. - * @return array Block attributes. - */ - private static function get_visual_list_group_attributes($element): array - { - return self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - } - /** - * Creates a list-item block from an li element - * - * @param HTML_To_Blocks_HTML_Element $li_element The li element - * @return array Block array - */ - private static function create_list_item_block($li_element) - { - $inner_html = $li_element->get_inner_html(); - $nested_list = null; - $nested_ol = $li_element->query_selector('ol'); - $nested_ul = $li_element->query_selector('ul'); - if ($nested_ol) { - $nested_list = $nested_ol; - $inner_html = \preg_replace('/]*>.*<\/ol>/is', '', $inner_html); - } elseif ($nested_ul) { - $nested_list = $nested_ul; - $inner_html = \preg_replace('/]*>.*<\/ul>/is', '', $inner_html); - } - $content = \trim($inner_html); - $inner_blocks = array(); - if ($nested_list) { - $inner_blocks[] = self::create_list_block_from_element($nested_list); - } - return HTML_To_Blocks_Block_Factory::create_block('core/list-item', array('content' => $content), $inner_blocks); - } - /** - * Gets direct
  • children from list inner HTML - * - * @param string $inner_html The inner HTML of an ol/ul element - * @return array Array of li element HTML strings - */ - private static function get_direct_li_children(string $inner_html): array - { - $results = array(); - $len = \strlen($inner_html); - $i = 0; - $list_depth = 0; - while ($i < $len) { - $remaining = \substr($inner_html, $i); - if (\preg_match('/^<(ul|ol)(?:\s|>)/i', $remaining)) { - ++$list_depth; - ++$i; - continue; - } - if (\preg_match('/^<\/(ul|ol)\s*>/i', $remaining)) { - --$list_depth; - ++$i; - continue; - } - if (0 === $list_depth && \preg_match('/^]*)?>/i', $remaining)) { - $li_html = self::extract_balanced_li($remaining); - if ($li_html) { - $results[] = $li_html; - $i += \strlen($li_html); - continue; - } - } - ++$i; - } - return $results; - } - /** - * Extracts a balanced
  • element including nested lists - * - * @param string $html HTML starting with
  • )/i', $remaining)) { - ++$li_depth; - } elseif (\preg_match('/^<\/li\s*>/i', $remaining, $close_match)) { - --$li_depth; - if (0 === $li_depth) { - return \substr($html, 0, $i + \strlen($close_match[0])); - } - } - ++$i; - } - return null; - } - /** - * core/buttons and core/button transforms - native WordPress button anchors. - * - * @return array Transform definitions - */ - private static function get_button_transforms() - { - return array(array('blockName' => 'core/paragraph', 'priority' => 8, 'selector' => 'div', 'isMatch' => function ($element) { - return self::is_aria_hidden_inline_span_container($element); - }, 'transform' => function ($element) { - return self::create_aria_hidden_inline_span_paragraph($element); - }), array('blockName' => 'core/paragraph', 'priority' => 8, 'selector' => 'div', 'isMatch' => function ($element) { - return self::is_visual_diagram_span_container($element); - }, 'transform' => function ($element) { - return self::create_aria_hidden_inline_span_paragraph($element); - }), array('blockName' => 'core/paragraph', 'priority' => 8, 'selector' => 'span', 'isMatch' => function ($element) { - return self::is_inline_span_label($element); - }, 'transform' => function ($element) { - return self::create_inline_span_label_paragraph($element); - }), array('blockName' => 'core/group', 'priority' => 8, 'selector' => 'div,p', 'isMatch' => function ($element) { - return self::is_static_visual_button_container($element); - }, 'transform' => function ($element) { - return self::create_static_visual_button_group($element); - }), array('blockName' => 'core/group', 'priority' => 8, 'selector' => 'button', 'isMatch' => function ($element) { - return self::is_static_toggle_button_chrome($element); - }, 'transform' => function ($element) { - return self::create_static_toggle_button_group($element); - }), array('blockName' => 'core/paragraph', 'priority' => 8, 'selector' => 'button', 'isMatch' => function ($element) { - return self::is_static_navigation_toggle_button($element); - }, 'transform' => function ($element) { - return self::create_static_navigation_toggle_paragraph($element); - }), array('blockName' => 'core/paragraph', 'priority' => 8, 'selector' => 'button', 'isMatch' => function ($element) { - return self::is_static_visual_button($element); - }, 'transform' => function ($element) { - return self::create_static_visual_button_paragraph($element); - }), array('blockName' => 'core/html', 'priority' => 8, 'selector' => 'div,p', 'isMatch' => function ($element) { - return self::is_class_sensitive_action_link_container($element); - }, 'transform' => function ($element) { - return HTML_To_Blocks_Block_Factory::create_block('core/html', array('content' => $element->get_outer_html())); - }), array('blockName' => 'core/buttons', 'priority' => 8, 'selector' => 'div,p', 'isMatch' => function ($element) { - return self::is_button_anchor_container($element) || self::is_single_button_anchor_wrapper($element); - }, 'transform' => function ($element) { - return self::create_buttons_block_from_container($element); - }), array('blockName' => 'core/paragraph', 'priority' => 9, 'selector' => 'a', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'A' && self::is_branded_inline_anchor($element); - }, 'transform' => function ($element) { - return self::create_branded_inline_anchor_paragraph($element); - }), array('blockName' => 'core/buttons', 'priority' => 9, 'selector' => 'a', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'A' && self::is_button_like_anchor($element); - }, 'transform' => function ($element) { - return self::create_buttons_block_from_anchor($element); - }), array('blockName' => 'core/buttons', 'priority' => 9, 'selector' => 'p', 'isMatch' => function ($element) { - $anchor = self::get_single_anchor_from_html($element->get_inner_html()); - return $element->get_tag_name() === 'P' && $anchor && self::is_button_like_anchor($anchor); - }, 'transform' => function ($element) { - $anchor = self::get_single_anchor_from_html($element->get_inner_html()); - return self::create_buttons_block_from_anchor($anchor); - })); - } - /** - * Checks whether a button is static responsive-navigation chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the button can be represented as native editable text. - */ - private static function is_static_navigation_toggle_button($element): bool - { - if ('BUTTON' !== $element->get_tag_name() || !$element->has_attribute('class')) { - return \false; - } - if (!self::class_matches($element, '/(?:^|[-_\s])(?:nav|menu)[-_\s]?toggle(?:$|[-_\s])/i')) { - return \false; - } - if ($element->has_attribute('form') || $element->has_attribute('name') || $element->has_attribute('value')) { - return \false; - } - $type = \strtolower(\trim((string) ($element->get_attribute('type') ?? ''))); - if (\in_array($type, array('submit', 'reset'), \true)) { - return \false; - } - return '' !== \trim($element->get_text_content()); - } - /** - * Creates editable text for static responsive-navigation chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Button element. - * @return array Block array. - */ - private static function create_static_navigation_toggle_paragraph($element): array - { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attributes['content'] = esc_html(\trim($element->get_text_content())); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Checks whether an element is a simple row/container of button-like anchors. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the container can safely become core/buttons. - */ - private static function is_button_anchor_container($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'P'), \true)) { - return \false; - } - $children = self::get_direct_anchor_children_from_html($element->get_inner_html()); - if (\count($children) < 2) { - return \false; - } - if (self::is_action_link_container($element)) { - foreach ($children as $child) { - if (self::is_exact_cta_token_container($element) && self::is_generic_button_variant_anchor($child)) { - return \false; - } - if (self::is_class_sensitive_cta_anchor($child) && !self::is_generic_button_variant_anchor($child)) { - return \false; - } - } - return \true; - } - foreach ($children as $child) { - if (!self::is_button_like_anchor($child)) { - return \false; - } - } - return \true; - } - /** - * Checks whether an alignment/action wrapper contains one button-like CTA anchor. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the wrapper can safely become core/buttons. - */ - private static function is_single_button_anchor_wrapper($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'P'), \true)) { - return \false; - } - $children = self::get_direct_anchor_children_from_html($element->get_inner_html()); - if (\count($children) !== 1 || !self::is_button_like_anchor($children[0])) { - return \false; - } - return self::is_action_link_container($element) || self::is_alignment_button_wrapper($element); - } - /** - * Checks for explicit alignment wrapper classes commonly used around CTAs. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the wrapper class signals visual alignment. - */ - private static function is_alignment_button_wrapper($element): bool - { - return self::class_matches($element, '/(?:^|[-_\s])(?:center|centered|text[-_]?center|aligncenter|align[-_]?center)(?:$|[-_\s])/i'); - } - /** - * Checks whether a wrapper contains only static visual button controls. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when direct button children can become native text blocks. - */ - private static function is_static_visual_button_container($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'P'), \true)) { - return \false; - } - $buttons = self::get_direct_static_visual_button_children_from_html($element->get_inner_html()); - return \count($buttons) >= 2; - } - /** - * Gets direct static visual button children when no other content is present. - * - * @param string $html Inner HTML to inspect. - * @return array Static visual button elements. - */ - private static function get_direct_static_visual_button_children_from_html(string $html): array - { - $remaining = $html; - $buttons = array(); - if (!\preg_match_all('/]*)>(.*?)<\/button>/is', $html, $matches, \PREG_SET_ORDER)) { - return array(); - } - foreach ($matches as $match) { - $outer = $match[0]; - $attributes = self::parse_attribute_string($match[1]); - $button = new HTML_To_Blocks_HTML_Element('button', $attributes, $outer, \trim($match[2])); - if (!self::is_static_visual_button($button)) { - return array(); - } - $buttons[] = $button; - $remaining = \str_replace($outer, '', $remaining); - } - return \trim($remaining) === '' ? $buttons : array(); - } - /** - * Checks whether a button is static visual UI text rather than a form control. - * - * Inline `on*` event handlers (e.g. `onclick`) do not disqualify the button: - * the block factory only carries class-based block support attributes, so the - * handler is dropped from the output instead of being preserved as raw HTML. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the button can safely become editable paragraph text. - */ - private static function is_static_visual_button($element): bool - { - if ('BUTTON' !== $element->get_tag_name()) { - return \false; - } - if ($element->has_attribute('form') || $element->has_attribute('name') || $element->has_attribute('value')) { - return \false; - } - $type = \strtolower(\trim((string) ($element->get_attribute('type') ?? ''))); - if (\in_array($type, array('submit', 'reset'), \true)) { - return \false; - } - if (\preg_match('/<\s*[a-z][^>]*>/i', $element->get_inner_html()) === 1 || \trim($element->get_text_content()) === '') { - return \false; - } - $class_name = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - return \preg_match('/(?:^|[-_\s])(?:tabs?|chips?|filters?|pills?|segmented|selector|use[-_]?case)(?:$|[-_\s])/i', $class_name) === 1; - } - /** - * Checks whether a button is inert navigation toggle chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the button can safely become native visual chrome. - */ - private static function is_static_toggle_button_chrome($element): bool - { - if ('BUTTON' !== $element->get_tag_name()) { - return \false; - } - if ($element->has_attribute('form') || $element->has_attribute('name') || $element->has_attribute('value')) { - return \false; - } - $type = \strtolower(\trim((string) ($element->get_attribute('type') ?? ''))); - if ('' !== $type && 'button' !== $type) { - return \false; - } - $class_name = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - if (\preg_match('/(?:^|[-_\s])(?:nav[-_\s]?toggle|menu[-_\s]?toggle|hamburger)(?:$|[-_\s])/i', $class_name) !== 1) { - return \false; - } - $children = $element->get_child_elements(); - if (empty($children)) { - return \trim($element->get_text_content()) === ''; - } - foreach ($children as $child) { - if (!\in_array($child->get_tag_name(), array('SPAN', 'DIV'), \true)) { - return \false; - } - if (self::is_empty_element($child)) { - continue; - } - if (!self::class_matches($child, '/(?:^|\s)sr-only(?:\s|$)/i') || array() !== $child->get_child_elements()) { - return \false; - } - } - return \true; - } - /** - * Creates native visual chrome for an inert navigation toggle button. - * - * @param HTML_To_Blocks_HTML_Element $element Button element. - * @return array Block array. - */ - private static function create_static_toggle_button_group($element): array - { - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element)); - } - /** - * Creates a native group from a static visual button row. - * - * @param HTML_To_Blocks_HTML_Element $element Button row wrapper. - * @return array Block array. - */ - private static function create_static_visual_button_group($element): array - { - $buttons = \array_map(array(__CLASS__, 'create_static_visual_button_paragraph'), self::get_direct_static_visual_button_children_from_html($element->get_inner_html())); - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $buttons); - } - /** - * Creates an editable paragraph block from a static visual button. - * - * @param HTML_To_Blocks_HTML_Element $element Button element. - * @return array Block array. - */ - private static function create_static_visual_button_paragraph($element): array - { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'colors' => \true, 'typography' => \true, 'spacing' => \true, 'border' => \true)); - $attributes['content'] = $element->get_inner_html(); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Checks whether a standalone span is a visual label. - * - * Number-only and tag label spans are commonly used as service, step, index, - * and card badges. Keep the span markup inside a native paragraph so - * class-based presentation survives without falling back to core/html. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the span can safely become paragraph markup. - */ - private static function is_inline_span_label($element): bool - { - if ('SPAN' !== $element->get_tag_name() || !$element->has_attribute('class')) { - return \false; - } - if (\preg_match('/<\s*[a-z][^>]*>/i', $element->get_inner_html()) === 1) { - return \false; - } - if (\preg_match('/^\d+$/', \trim($element->get_text_content())) === 1) { - return \true; - } - return self::class_matches($element, '/(?:^|\s)tag(?:\s|$)/i'); - } - /** - * Creates a paragraph preserving inline label span markup. - * - * @param HTML_To_Blocks_HTML_Element $element Inline label span. - * @return array Block array. - */ - private static function create_inline_span_label_paragraph($element): array - { - $attributes = array('content' => \trim($element->get_outer_html())); - self::apply_inline_preservation_paragraph_spacing($attributes); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Removes default paragraph margins from paragraphs standing in for inline markup. - * - * @param array $attributes Paragraph block attributes. - */ - private static function apply_inline_preservation_paragraph_spacing(array &$attributes): void - { - if (!isset($attributes['style']) || !\is_array($attributes['style'])) { - $attributes['style'] = array(); - } - if (!isset($attributes['style']['spacing']) || !\is_array($attributes['style']['spacing'])) { - $attributes['style']['spacing'] = array(); - } - if (!isset($attributes['style']['spacing']['margin']) || !\is_array($attributes['style']['spacing']['margin'])) { - $attributes['style']['spacing']['margin'] = array(); - } - foreach (array('top', 'right', 'bottom', 'left') as $side) { - if (!isset($attributes['style']['spacing']['margin'][$side])) { - $attributes['style']['spacing']['margin'][$side] = '0'; - } - } - } - /** - * Checks whether an aria-hidden div is only inline span labels. - * - * Decorative rulers and visual scales commonly use direct span ticks inside an - * aria-hidden div. Convert those wrappers to a paragraph so Gutenberg does not - * insert an extra paragraph inside a group and break span-level CSS layouts. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the wrapper can safely become paragraph markup. - */ - private static function is_aria_hidden_inline_span_container($element): bool - { - if ('DIV' !== $element->get_tag_name() || !$element->has_attribute('aria-hidden')) { - return \false; - } - if ('true' !== \strtolower(\trim((string) $element->get_attribute('aria-hidden')))) { - return \false; - } - if ('' === \trim($element->get_text_content())) { - return \false; - } - return self::html_contains_only_direct_spans($element->get_inner_html()); - } - /** - * Checks whether a visible diagram div contains only inline span labels. - * - * Diagram layers commonly position direct span labels. Keep those labels as - * direct paragraph content instead of wrapping them in an inserted paragraph - * inside a group, which changes diagram sizing and vertical rhythm. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the wrapper can safely become paragraph markup. - */ - private static function is_visual_diagram_span_container($element): bool - { - if ('DIV' !== $element->get_tag_name() || !self::class_matches($element, '/(?:^|[-_\s])diagram(?:$|[-_\s])/i')) { - return \false; - } - if ('' === \trim($element->get_text_content())) { - return \false; - } - return self::html_contains_only_direct_spans($element->get_inner_html()); - } - /** - * Creates a paragraph preserving direct span markup from an aria-hidden div. - * - * @param HTML_To_Blocks_HTML_Element $element Span-only wrapper. - * @return array Block array. - */ - private static function create_aria_hidden_inline_span_paragraph($element): array - { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'colors' => \true, 'typography' => \true, 'spacing' => \true, 'border' => \true)); - self::apply_inline_preservation_paragraph_spacing($attributes); - $attributes['content'] = \trim($element->get_inner_html()); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Checks whether HTML contains only sibling span elements and whitespace. - * - * @param string $html Inner HTML to inspect. - * @return bool True when no non-span sibling markup or text remains. - */ - private static function html_contains_only_direct_spans(string $html): bool - { - $remaining = $html; - if (!\preg_match_all('/]*>.*?<\/span>/is', $html, $matches)) { - return \false; - } - foreach ($matches[0] as $span_html) { - $remaining = \str_replace($span_html, '', $remaining); - } - return '' === \trim($remaining); - } - /** - * Checks whether an anchor is a branded inline link with nested visual spans. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the branded link can safely become paragraph markup. - */ - private static function is_branded_inline_anchor($element): bool - { - if ('A' !== $element->get_tag_name()) { - return \false; - } - $href = \trim((string) ($element->get_attribute('href') ?? '')); - if ('' === $href || \preg_match('/^(?:https?:|mailto:|tel:|javascript:)/i', $href) === 1) { - return \false; - } - if (!self::class_matches($element, '/(?:^|[-_\s])brand(?:$|[-_\s])/i')) { - return \false; - } - return \preg_match('/<\s*span\b/i', $element->get_inner_html()) === 1; - } - /** - * Creates a paragraph preserving a branded inline anchor as editable markup. - * - * @param HTML_To_Blocks_HTML_Element $element Branded anchor element. - * @return array Block array. - */ - private static function create_branded_inline_anchor_paragraph($element): array - { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true)); - self::apply_inline_preservation_paragraph_spacing($attributes); - $attributes['content'] = \trim($element->get_outer_html()); - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - /** - * Checks whether a container is explicitly an action/link row. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when direct anchors should remain separate action blocks. - */ - private static function is_action_link_container($element): bool - { - return self::class_matches($element, '/(?:^|[-_\s])(?:actions?|buttons?|cta)(?:$|[-_\s])/i'); - } - /** - * Checks whether an action wrapper should preserve class-sensitive anchors as raw HTML. - * - * Some action rows use direct anchors whose source CSS targets the anchor - * classes themselves. Converting those wrappers to core/buttons or flowing the - * anchors through a paragraph moves the effective styling target and changes - * visual rhythm. Keep only explicit action containers with class-sensitive CTA - * anchors as raw HTML; simpler action rows continue through native buttons. - * - * @param HTML_To_Blocks_HTML_Element $element Element to inspect. - * @return bool True when the wrapper should remain raw HTML. - */ - private static function is_class_sensitive_action_link_container($element): bool - { - if (!self::is_action_link_container($element)) { - return \false; - } - $children = self::get_direct_anchor_children_from_html($element->get_inner_html()); - if (\count($children) < 1) { - return \false; - } - foreach ($children as $child) { - if (self::is_class_sensitive_cta_anchor($child) && !self::is_generic_button_variant_anchor($child)) { - return \true; - } - } - return \false; - } - /** - * Gets direct anchor children when the HTML contains only sibling anchors and whitespace. - * - * @param string $html Inner HTML to inspect. - * @return array Anchor elements. - */ - private static function get_direct_anchor_children_from_html(string $html): array - { - $remaining = $html; - $anchors = array(); - if (!\preg_match_all('/]*)>(.*?)<\/a>/is', $html, $matches, \PREG_SET_ORDER)) { - return array(); - } - foreach ($matches as $match) { - $outer = $match[0]; - $attributes = self::parse_attribute_string($match[1]); - $anchors[] = new HTML_To_Blocks_HTML_Element('a', $attributes, $outer, \trim($match[2])); - $remaining = \str_replace($outer, '', $remaining); - } - return \trim($remaining) === '' ? $anchors : array(); - } - /** - * Gets a single anchor element when the HTML is only one anchor. - * - * @param string $html Inner HTML to inspect. - * @return HTML_To_Blocks_HTML_Element|null Anchor element or null. - */ - private static function get_single_anchor_from_html(string $html): ?HTML_To_Blocks_HTML_Element - { - $html = \trim($html); - if (!\preg_match('/^]*)>(.*)<\/a>$/is', $html, $matches)) { - return null; - } - $attributes = self::parse_attribute_string($matches[1]); - return new HTML_To_Blocks_HTML_Element('a', $attributes, $html, \trim($matches[2])); - } - /** - * Parses an HTML attribute string into an associative array. - * - * @param string $attribute_string Raw attribute string. - * @return array Parsed attributes. - */ - private static function parse_attribute_string(string $attribute_string): array - { - $attributes = array(); - if (\preg_match_all('/([a-zA-Z_:][-a-zA-Z0-9_:.]*)\s*=\s*("([^"]*)"|\'([^\']*)\'|([^\s"\'>]+))/', $attribute_string, $matches, \PREG_SET_ORDER)) { - foreach ($matches as $match) { - $value = ''; - if (isset($match[3]) && '' !== $match[3]) { - $value = $match[3]; - } elseif (isset($match[4]) && '' !== $match[4]) { - $value = $match[4]; - } elseif (isset($match[5])) { - $value = $match[5]; - } - $attributes[\strtolower($match[1])] = \html_entity_decode($value, \ENT_QUOTES, 'UTF-8'); - } - } - return $attributes; - } - /** - * Checks if an anchor already carries native WordPress button markup. - * - * @param HTML_To_Blocks_HTML_Element $element Anchor element. - * @return bool True when the anchor is already WordPress-button-shaped. - */ - private static function is_button_like_anchor($element): bool - { - // @phpstan-ignore-next-line booleanNot.alwaysFalse -- Defensive public API guard for untyped external callers. - if (!$element || $element->get_tag_name() !== 'A') { - return \false; - } - if (self::is_class_sensitive_cta_anchor($element)) { - return \false; - } - $class_name = $element->get_attribute('class') ?? ''; - if (\preg_match('/(?:^|\s)(?:wp-block-button__link|wp-element-button)(?:$|\s)/i', $class_name) === 1) { - return \true; - } - if (\preg_match('/(?:^|\s)btn(?:$|\s)/i', $class_name) === 1) { - return \true; - } - if (\preg_match('/(?:^|\s)btn-(?!cta(?:$|\s))[A-Za-z0-9_-]+(?:$|\s)/i', $class_name) === 1) { - return \true; - } - return \preg_match('/(?:^|\s)[A-Za-z0-9]+-btn(?:-[A-Za-z0-9_-]+)?(?:$|\s)/i', $class_name) === 1; - } - /** - * Checks for CTA link classes whose selectors must remain anchor-owned. - * - * Gutenberg core/button serializes custom className on the wrapper div, not - * the inner link. Keep these anchors as inline paragraph content instead of - * silently changing selectors like `.cta-btn:hover` or `a.cta-link`. - * - * @param HTML_To_Blocks_HTML_Element $element Anchor element. - * @return bool True when class ownership is more important than button shape. - */ - private static function is_class_sensitive_cta_anchor($element): bool - { - // @phpstan-ignore-next-line booleanNot.alwaysFalse -- Defensive public API guard for untyped external callers. - if (!$element || $element->get_tag_name() !== 'A') { - return \false; - } - $class_name = $element->get_attribute('class') ?? ''; - if (\preg_match('/(?:^|\s)(?:wp-block-button__link|wp-element-button)(?:$|\s)/i', $class_name) === 1) { - return \false; - } - return \preg_match('/(?:^|\s)(?:cta-(?:btn|link)|(?:btn|link)-cta)(?:$|\s)/i', $class_name) === 1; - } - /** - * Checks whether a CTA/action container uses an explicit CTA class token. - * - * @param HTML_To_Blocks_HTML_Element $element Container element. - * @return bool True when the container should keep CTA-owned anchor classes. - */ - private static function is_exact_cta_token_container($element): bool - { - $class_name = $element->get_attribute('class') ?? ''; - return \preg_match('/(?:^|\s)(?:cta|cta[-_]?actions)(?:$|\s)/i', $class_name) === 1; - } - /** - * Checks whether an anchor has the generic button class plus visual variants. - * - * @param HTML_To_Blocks_HTML_Element $element Anchor element. - * @return bool True when the class shape is safe to treat as a native button. - */ - private static function is_generic_button_variant_anchor($element): bool - { - // @phpstan-ignore-next-line booleanNot.alwaysFalse -- Defensive public API guard for untyped external callers. - if (!$element || $element->get_tag_name() !== 'A') { - return \false; - } - $class_name = \trim((string) ($element->get_attribute('class') ?? '')); - if (\preg_match('/(?:^|\s)button(?:$|\s)/i', $class_name) !== 1) { - return \false; - } - return \preg_match('/(?:^|\s)(?:primary|secondary)(?:$|\s)/i', $class_name) === 1; - } - /** - * Creates a buttons wrapper with one button child from an anchor. - * - * @param HTML_To_Blocks_HTML_Element $anchor Anchor element. - * @return array Block array. - */ - private static function create_buttons_block_from_anchor($anchor): array - { - return self::create_buttons_block_from_anchors(array($anchor)); - } - /** - * Creates a buttons wrapper with one button child per direct anchor. - * - * @param HTML_To_Blocks_HTML_Element $element Container element. - * @return array Block array. - */ - private static function create_buttons_block_from_container($element): array - { - $attributes = array(); - if ($element->has_attribute('class')) { - $attributes['className'] = $element->get_attribute('class'); - } - $layout = self::extract_buttons_layout_attributes($element); - if (!empty($layout)) { - $attributes['layout'] = $layout; - } - return self::create_buttons_block_from_anchors(self::get_direct_anchor_children_from_html($element->get_inner_html()), $attributes); - } - /** - * Extracts core/buttons layout attributes from a source container. - * - * Maps inline horizontal alignment to Gutenberg-compatible flex layout - * attributes so the buttons block round-trips through Gutenberg with the - * source horizontal alignment intact. core/buttons defaults to a flex - * layout, so the layout type is fixed to flex whenever any horizontal - * alignment intent is detected. - * - * @param HTML_To_Blocks_HTML_Element $element Container element. - * @return array Layout attribute map (possibly empty). - */ - private static function extract_buttons_layout_attributes($element): array - { - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - $classes = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - $justify = ''; - if ('' !== $style) { - $justify = \strtolower(\trim(self::extract_css_property($style, 'justify-content'))); - } - // Map common CSS justify-content values to Gutenberg's supported values. - $mapped_justify = ''; - switch ($justify) { - case 'center': - $mapped_justify = 'center'; - break; - case 'flex-start': - case 'start': - case 'left': - $mapped_justify = 'left'; - break; - case 'flex-end': - case 'end': - case 'right': - $mapped_justify = 'right'; - break; - case 'space-between': - $mapped_justify = 'space-between'; - break; - } - if ('' === $mapped_justify && '' !== $classes) { - if (\preg_match('/(?:^|\s)is-content-justification-(left|right|center|space-between)(?:\s|$)/i', $classes, $matches)) { - $mapped_justify = \strtolower($matches[1]); - } - } - if ('' === $mapped_justify) { - return array(); - } - return array('type' => 'flex', 'justifyContent' => $mapped_justify); - } - /** - * Creates a buttons wrapper with button children from anchors. - * - * @param array $anchors Anchor elements. - * @param array $wrapper_attributes Wrapper block attributes. - * @return array Block array. - */ - private static function create_buttons_block_from_anchors(array $anchors, array $wrapper_attributes = array()): array - { - $buttons = array(); - foreach ($anchors as $anchor) { - $buttons[] = self::create_button_block_from_anchor($anchor); - } - return HTML_To_Blocks_Block_Factory::create_block('core/buttons', $wrapper_attributes, $buttons); - } - /** - * Creates one button block from an anchor. - * - * @param HTML_To_Blocks_HTML_Element $anchor Anchor element. - * @return array Block array. - */ - private static function create_button_block_from_anchor($anchor): array - { - $attributes = array('text' => $anchor->get_inner_html()); - if ($anchor->has_attribute('href')) { - $attributes['url'] = $anchor->get_attribute('href'); - } - if ($anchor->has_attribute('target')) { - $attributes['linkTarget'] = $anchor->get_attribute('target'); - } - if ($anchor->has_attribute('rel')) { - $attributes['rel'] = $anchor->get_attribute('rel'); - } - if ($anchor->has_attribute('class')) { - $attributes['className'] = self::button_block_class_name($anchor->get_attribute('class')); - } - return HTML_To_Blocks_Block_Factory::create_block('core/button', $attributes); - } - /** - * Converts anchor classes to button block classes. - * - * @param string $class_name Anchor class attribute. - * @return string Button block class name. - */ - private static function button_block_class_name(string $class_name): string - { - $classes = \preg_split('/\s+/', \trim($class_name)); - if (\false === $classes) { - return ''; - } - $classes = \array_filter($classes, function ($class_name) { - return !\in_array($class_name, array('wp-block-button__link', 'wp-element-button'), \true); - }); - return \trim(\implode(' ', $classes)); - } - /** - * core/image transforms - figure with img - * - * @return array Transform definitions - */ - private static function get_image_transforms() - { - return array(array('blockName' => 'core/image', 'priority' => 10, 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'FIGURE') { - return \false; - } - $img = $element->query_selector('img'); - return null !== $img; - }, 'transform' => function ($element, $handler = null, array $args = array()) { - $img = $element->query_selector('img'); - $figcaption = $element->query_selector('figcaption'); - $attributes = array('url' => $img->get_attribute('src') ?? ''); - self::apply_image_element_attributes($attributes, $img); - if ($figcaption) { - $attributes['caption'] = $figcaption->get_inner_html(); - } - $class_name = ''; - if ($element->has_attribute('class')) { - $class_name .= $element->get_attribute('class') . ' '; - } - if ($img->has_attribute('class')) { - $class_name .= $img->get_attribute('class'); - } - $class_name = \trim($class_name); - if (\preg_match('/(?:^|\s)align(left|center|right)(?:$|\s)/', $class_name, $matches)) { - $attributes['align'] = $matches[1]; - } - if (\preg_match('/(?:^|\s)wp-image-(\d+)(?:$|\s)/', $class_name, $matches)) { - $attributes['id'] = (int) $matches[1]; - } - if ($element->has_attribute('id') && $element->get_attribute('id') !== '') { - $attributes['anchor'] = $element->get_attribute('id'); - } - self::apply_image_asset_metadata($attributes, $args); - $anchor_element = $element->query_selector('a'); - if ($anchor_element && $anchor_element->has_attribute('href')) { - $attributes['href'] = $anchor_element->get_attribute('href'); - $attributes['linkDestination'] = 'custom'; - if ($anchor_element->has_attribute('rel')) { - $attributes['rel'] = $anchor_element->get_attribute('rel'); - } - if ($anchor_element->has_attribute('class')) { - $attributes['linkClass'] = $anchor_element->get_attribute('class'); - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/image', $attributes); - }), array('blockName' => 'core/image', 'priority' => 15, 'isMatch' => function ($element) { - return $element->get_tag_name() === 'IMG'; - }, 'transform' => function ($element, $handler = null, array $args = array()) { - $attributes = array('url' => $element->get_attribute('src') ?? ''); - self::apply_image_element_attributes($attributes, $element); - $class_name = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - if (\preg_match('/(?:^|\s)align(left|center|right)(?:$|\s)/', $class_name, $matches)) { - $attributes['align'] = $matches[1]; - } - if (\preg_match('/(?:^|\s)wp-image-(\d+)(?:$|\s)/', $class_name, $matches)) { - $attributes['id'] = (int) $matches[1]; - } - self::apply_image_asset_metadata($attributes, $args); - return HTML_To_Blocks_Block_Factory::create_block('core/image', $attributes); - })); - } - /** - * core/details transforms - details elements with a summary. - * - * @return array Transform definitions - */ - private static function get_details_transforms() - { - return array(array('blockName' => 'core/details', 'priority' => 10, 'selector' => 'details', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'DETAILS' && \preg_match('/]*)?>.*?<\/summary>/is', $element->get_inner_html()) === 1; - }, 'transform' => function ($element, $handler) { - $inner_html = $element->get_inner_html(); - \preg_match('/]*)?>(.*?)<\/summary>/is', $inner_html, $summary_matches); - $summary = \trim($summary_matches[1] ?? ''); - $content_html = \trim(\preg_replace('/]*)?>.*?<\/summary>/is', '', $inner_html, 1)); - $inner_blocks = '' !== $content_html ? $handler(array('HTML' => $content_html)) : array(); - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attributes['summary'] = $summary; - if ($element->has_attribute('open')) { - $attributes['showContent'] = \true; - } - return HTML_To_Blocks_Block_Factory::create_block('core/details', $attributes, $inner_blocks); - })); - } - /** - * core/pullquote transforms - explicit pullquote blockquotes. - * - * @return array Transform definitions - */ - private static function get_pullquote_transforms() - { - return array(array('blockName' => 'core/pullquote', 'priority' => 9, 'selector' => 'blockquote', 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'BLOCKQUOTE' || !$element->has_attribute('class')) { - return \false; - } - $class_name = $element->get_attribute('class'); - return \preg_match('/(?:^|\s)(?:wp-block-pullquote|pullquote|is-style-pullquote)(?:$|\s)/i', $class_name) === 1; - }, 'transform' => function ($element) { - $value = \trim($element->get_inner_html()); - $citation = ''; - if (\preg_match('/]*)?>(.*?)<\/cite>/is', $value, $matches)) { - $citation = \trim($matches[1]); - $value = \trim(\preg_replace('/]*)?>.*?<\/cite>/is', '', $value, 1)); - } - $attributes = array('value' => $value); - if ('' !== $citation) { - $attributes['citation'] = $citation; - } - return HTML_To_Blocks_Block_Factory::create_block('core/pullquote', $attributes); - })); - } - /** - * core/quote transforms - blockquote elements - * - * @return array Transform definitions - */ - private static function get_quote_transforms() - { - return array(array('blockName' => 'core/group', 'priority' => 9, 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'FIGURE') { - return \false; - } - $children = $element->get_child_elements(); - return \count($children) === 2 && $children[0]->get_tag_name() === 'BLOCKQUOTE' && $children[1]->get_tag_name() === 'FIGCAPTION'; - }, 'transform' => function ($element, $handler) { - $children = $element->get_child_elements(); - $blockquote = $children[0]; - $figcaption = $children[1]; - $inner_blocks = $handler(array('HTML' => $blockquote->get_outer_html())); - $caption_attrs = self::get_block_support_attributes($figcaption, array('class_name' => \true)); - $caption_markup = \trim($figcaption->get_inner_html()); - if ('' !== $caption_markup) { - $caption_attrs['content'] = $caption_markup; - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $caption_attrs); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - }), array('blockName' => 'core/quote', 'priority' => 10, 'selector' => 'blockquote', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'BLOCKQUOTE'; - }, 'transform' => function ($element, $handler) { - $inner_html = $element->get_inner_html(); - $inner_blocks = $handler(array('HTML' => $inner_html)); - $attributes = array(); - if ($element->has_attribute('id') && $element->get_attribute('id') !== '') { - $attributes['anchor'] = $element->get_attribute('id'); - } - return HTML_To_Blocks_Block_Factory::create_block('core/quote', $attributes, $inner_blocks); - })); - } - /** - * core/code transforms - pre > code elements - * - * @return array Transform definitions - */ - private static function get_code_transforms() - { - return array(array('blockName' => 'core/code', 'priority' => 10, 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'PRE') { - return \false; - } - $code = $element->query_selector('code'); - if (!$code) { - return \false; - } - $inner_html = $element->get_inner_html(); - $stripped = \preg_replace('/]*>.*<\/code>/is', '', $inner_html); - $has_only_code = empty(\trim(wp_strip_all_tags($stripped))); - return $has_only_code; - }, 'transform' => function ($element) { - $code = $element->query_selector('code'); - if ($code && array() !== $code->get_child_elements()) { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attributes['content'] = $code->get_inner_html(); - if ($code->has_attribute('class')) { - $attributes['className'] = self::merge_class_names($attributes['className'] ?? '', $code->get_attribute('class')); - } - return HTML_To_Blocks_Block_Factory::create_block('core/preformatted', $attributes); - } - $content = $code ? $code->get_text_content() : $element->get_text_content(); - $attributes = array('content' => $content); - // Preserve language class for syntax highlighting - if ($code && $code->has_attribute('class')) { - $class = $code->get_attribute('class'); - if (\preg_match('/language-(\S+)/', $class, $matches)) { - $attributes['className'] = 'language-' . $matches[1]; - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/code', $attributes); - })); - } - /** - * Visual code-window/demo transforms. - * - * @return array Transform definitions - */ - private static function get_code_window_transforms() - { - return array(array('blockName' => 'core/group', 'priority' => 8, 'isMatch' => function ($element) { - return self::is_decorative_code_chrome_element($element); - }, 'transform' => function ($element, $handler) { - $inner_blocks = array() !== $element->get_child_elements() ? $handler(array('HTML' => $element->get_inner_html())) : array(); - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - }), array('blockName' => 'core/group', 'priority' => 9, 'isMatch' => function ($element) { - return self::is_code_window_text_chrome_element($element); - }, 'transform' => function ($element) { - return self::create_code_window_text_group($element); - }), array('blockName' => 'core/preformatted', 'priority' => 9, 'isMatch' => function ($element) { - return self::is_div_line_code_panel($element); - }, 'transform' => function ($element) { - return self::create_code_window_body_block($element); - }), array('blockName' => 'core/group', 'priority' => 9, 'isMatch' => function ($element) { - if ('DIV' !== $element->get_tag_name()) { - return \false; - } - if (self::has_unsafe_code_display_markup($element)) { - return \false; - } - if (self::class_matches($element, '/(?:^|\s)(?:[A-Za-z0-9_-]*code[-_]?(?:window|preview|panel|comparison)[A-Za-z0-9_-]*|code[-_]?pane)(?:$|\s)/i')) { - return \true; - } - if (self::is_code_window_part_container($element)) { - return \true; - } - return self::class_matches($element, '/(?:^|[-_\s])(?:code|workflow[-_\s]?code)(?:$|[-_\s])/i') && 1 === \preg_match('/class=["\'][^"\']*[A-Za-z0-9_-]*code[-_]?window[A-Za-z0-9_-]*[^"\']*["\']/i', $element->get_inner_html()); - }, 'transform' => function ($element, $handler) { - return self::create_code_window_block($element, $handler); - })); - } - /** - * Creates a native group for a visual code-window wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element Code-window wrapper. - * @param callable $handler Recursive raw handler. - * @return array Block array. - */ - private static function create_code_window_block($element, $handler): array - { - $inner_blocks = array(); - foreach ($element->get_child_elements() as $child) { - $class_name = $child->has_attribute('class') ? $child->get_attribute('class') : ''; - if ('PRE' === $child->get_tag_name()) { - $inner_blocks[] = self::create_pre_code_preformatted_block($child); - continue; - } - if (self::class_matches($child, '/(?:^|[-_\s])code[-_]?block(?:$|[-_\s])/i') && \preg_match('/get_inner_html()) === 1) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($child), $handler(array('HTML' => $child->get_inner_html()))); - continue; - } - if (\preg_match('/(?:^|\s)[A-Za-z0-9_-]*code[-_]?(?:body|block)[A-Za-z0-9_-]*(?:$|\s)/i', $class_name) === 1) { - $inner_blocks[] = self::create_code_window_body_block($child); - continue; - } - if (\preg_match('/(?:^|\s)[A-Za-z0-9_-]*(?:code[-_]?(?:bar|titlebar|header|pane[-_]?header|panel[-_]?label|badge)|code[-_]?preview[-_]?(?:header|tab[-_]?bar)|window[-_]?(?:bar|title|header)|arrow[-_]?row)[A-Za-z0-9_-]*(?:$|\s)/i', $class_name) === 1) { - $inner_blocks[] = self::create_code_window_text_group($child); - continue; - } - $inner_blocks = \array_merge($inner_blocks, $handler(array('HTML' => $child->get_outer_html()))); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - } - /** - * Creates a preformatted block for code panel pre/code bodies. - * - * @param HTML_To_Blocks_HTML_Element $element Pre element. - * @return array Block array. - */ - private static function create_pre_code_preformatted_block($element): array - { - $code = $element->query_selector('code'); - $content = $code ? $code->get_inner_html() : $element->get_inner_html(); - $attrs = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attrs['content'] = $content; - if ($code && $code->has_attribute('class')) { - $attrs['className'] = self::merge_class_names($attrs['className'] ?? '', $code->get_attribute('class')); - } - return HTML_To_Blocks_Block_Factory::create_block('core/preformatted', $attrs); - } - /** - * Creates a preformatted block from code-window line wrappers. - * - * @param HTML_To_Blocks_HTML_Element $element Code body element. - * @return array Block array. - */ - private static function create_code_window_body_block($element): array - { - $lines = array(); - foreach ($element->get_child_elements() as $child) { - if ($child->get_tag_name() === 'DIV') { - $line_html = $child->get_inner_html(); - if ($child->has_attribute('class')) { - $line_class = self::safe_block_class_name($child->get_attribute('class')); - if ('' !== $line_class) { - $line_html = '' . $line_html . ''; - } - } - $lines[] = $line_html; - } - } - $content = !empty($lines) ? \implode("\n", $lines) : $element->get_inner_html(); - $attrs = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attrs['content'] = $content; - return HTML_To_Blocks_Block_Factory::create_block('core/preformatted', $attrs); - } - /** - * Creates a native group for non-code chrome around a code-window. - * - * @param HTML_To_Blocks_HTML_Element $element Chrome element. - * @return array Block array. - */ - private static function create_code_window_text_group($element): array - { - $content = self::get_code_window_chrome_content($element); - $attrs = self::get_common_layout_attributes($element); - if ('' === $content) { - return HTML_To_Blocks_Block_Factory::create_block('core/group', $attrs); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', $attrs, array(HTML_To_Blocks_Block_Factory::create_block('core/paragraph', array('className' => $attrs['className'] ?? '', 'content' => $content)))); - } - /** - * Extracts meaningful visible text from code-window chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Chrome element. - * @return string Editable chrome text, excluding decorative dots. - */ - private static function get_code_window_chrome_content($element): string - { - $content = $element->get_inner_html(); - foreach ($element->get_child_elements() as $child) { - if (self::class_matches($child, '/(?:^|\s)[A-Za-z0-9_-]*code[-_]?dot[A-Za-z0-9_-]*(?:$|\s)/i') || self::is_empty_decorative_element($child)) { - $content = \str_replace($child->get_outer_html(), '', $content); - } - } - return \trim($content); - } - /** - * Checks whether an element is empty/decorative code-window chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the element can safely become native non-content chrome. - */ - private static function is_decorative_code_chrome_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - if (self::has_unsafe_code_display_markup($element) || !self::has_decorative_code_chrome_class($element)) { - return \false; - } - $text = \trim(wp_strip_all_tags($element->get_inner_html())); - if (!self::is_decorative_text_content($text)) { - return \false; - } - foreach ($element->get_child_elements() as $child) { - if (!self::is_decorative_code_chrome_element($child)) { - return \false; - } - } - return \true; - } - /** - * Checks whether an element is textual code-window chrome such as title bars. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when chrome can safely become a native group. - */ - private static function is_code_window_text_chrome_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - if (self::has_unsafe_code_display_markup($element)) { - return \false; - } - return self::class_matches($element, '/(?:^|\s)[A-Za-z0-9_-]*(?:code[-_]?(?:bar|titlebar|header|pane[-_]?header|panel[-_]?label|badge)|code[-_]?preview[-_]?(?:header|tab[-_]?bar)|window[-_]?(?:bar|title|header)|arrow[-_]?row)[A-Za-z0-9_-]*(?:$|\s)/i'); - } - /** - * Checks whether a generic code-labeled wrapper contains code-window parts. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when a wrapper can be decomposed into native code-window blocks. - */ - private static function is_code_window_part_container($element): bool - { - if ('DIV' !== $element->get_tag_name() || !self::class_matches($element, '/(?:^|[-_\s])code(?:$|[-_\s])/i')) { - return \false; - } - $has_header = \false; - $has_body = \false; - foreach ($element->get_child_elements() as $child) { - $class_name = $child->has_attribute('class') ? $child->get_attribute('class') : ''; - if (\preg_match('/(?:^|\s)[A-Za-z0-9_-]*(?:code[-_]?(?:bar|titlebar|header|pane[-_]?header|panel[-_]?label)|code[-_]?preview[-_]?header)[A-Za-z0-9_-]*(?:$|\s)/i', $class_name) === 1) { - $has_header = \true; - } - if ('PRE' === $child->get_tag_name() || \preg_match('/(?:^|\s)[A-Za-z0-9_-]*(?:code[-_]?(?:body|block|output|panel)|code[-_]?preview[-_]?body)[A-Za-z0-9_-]*(?:$|\s)/i', $class_name) === 1) { - $has_body = \true; - } - } - $inner_html = $element->get_inner_html(); - if (!$has_header && \preg_match('/class=["\'][^"\']*[A-Za-z0-9_-]*code[-_]?(?:bar|titlebar|header|pane[-_]?header|panel[-_]?label)[A-Za-z0-9_-]*[^"\']*["\']/i', $inner_html) === 1) { - $has_header = \true; - } - if (!$has_body && \preg_match('/get_tag_name() || !self::class_matches($element, '/(?:^|[-_\s])(?:code|terminal|snippet)(?:$|[-_\s])/i')) { - return \false; - } - if (self::class_matches($element, '/(?:^|\s)[A-Za-z0-9_-]*code[-_]?window[A-Za-z0-9_-]*(?:$|\s)/i')) { - return \false; - } - if (self::has_unsafe_code_display_markup($element)) { - return \false; - } - $children = $element->get_child_elements(); - if (\count($children) < 2) { - return \false; - } - $has_text = \false; - foreach ($children as $child) { - if ('DIV' !== $child->get_tag_name()) { - return \false; - } - foreach ($child->get_child_elements() as $inline_child) { - if (!\in_array($inline_child->get_tag_name(), array('SPAN', 'BR', 'CODE', 'STRONG', 'B', 'EM', 'I', 'SMALL'), \true)) { - return \false; - } - } - $text = \str_replace(" ", ' ', \html_entity_decode(\trim($child->get_text_content()), \ENT_QUOTES, 'UTF-8')); - if (\trim($text) !== '') { - $has_text = \true; - } - } - return $has_text; - } - /** - * Checks for generic code-window chrome class names. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when class names describe decorative code chrome. - */ - private static function has_decorative_code_chrome_class($element): bool - { - $class_name = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - if ('' === $class_name) { - return \false; - } - $chrome_part = '(?:dot|line|divider|separator|rule|arrow|chrome|bar|titlebar)'; - return \preg_match('/(?:^|[\s_-])code[\w-]*[\s_-]?' . $chrome_part . '(?:$|[\s_-]|\d)/i', $class_name) === 1 || \preg_match('/(?:^|[\s_-])' . $chrome_part . '[\w-]*[\s_-]?code(?:$|[\s_-]|\d)/i', $class_name) === 1; - } - /** - * Checks whether text is empty or purely visual punctuation/chrome. - * - * @param string $text Text content to inspect. - * @return bool True when text carries no semantic content. - */ - private static function is_decorative_text_content(string $text): bool - { - $text = \trim(\html_entity_decode($text, \ENT_QUOTES, 'UTF-8')); - if ('' === $text) { - return \true; - } - return \preg_match('/^[\s\-_=|:;.,•·*+<>›»«‹→←↑↓↔⇒⇐⇑⇓➜➔➝⟶⟵⟷]+$/u', $text) === 1; - } - /** - * core/verse transforms - explicit verse/preformatted poetry classes. - * - * @return array Transform definitions - */ - private static function get_verse_transforms() - { - return array(array('blockName' => 'core/verse', 'priority' => 10, 'selector' => 'pre', 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'PRE' || !$element->has_attribute('class')) { - return \false; - } - $class_name = $element->get_attribute('class'); - return \preg_match('/(?:^|\s)(?:wp-block-verse|verse)(?:$|\s)/i', $class_name) === 1; - }, 'transform' => function ($element) { - return HTML_To_Blocks_Block_Factory::create_block('core/verse', array('content' => $element->get_inner_html())); - })); - } - /** - * core/preformatted transforms - pre elements (not containing code) - * - * @return array Transform definitions - */ - private static function get_preformatted_transforms() - { - return array(array('blockName' => 'core/preformatted', 'priority' => 9, 'isMatch' => function ($element) { - return self::is_div_code_snippet_element($element); - }, 'transform' => function ($element) { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attributes['content'] = self::normalise_code_snippet_content($element); - return HTML_To_Blocks_Block_Factory::create_block('core/preformatted', $attributes); - }), array('blockName' => 'core/preformatted', 'priority' => 11, 'isMatch' => function ($element) { - if ($element->get_tag_name() !== 'PRE') { - return \false; - } - $code = $element->query_selector('code'); - if (!$code) { - return \true; - } - $inner_html = $element->get_inner_html(); - $stripped = \preg_replace('/]*>.*<\/code>/is', '', $inner_html); - $has_only_code = empty(\trim(wp_strip_all_tags($stripped))); - return !$has_only_code; - }, 'transform' => function ($element) { - $content = $element->get_inner_html(); - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true)); - $attributes['content'] = $content; - return HTML_To_Blocks_Block_Factory::create_block('core/preformatted', $attributes); - })); - } - /** - * Checks whether a div is a styled code snippet using inline syntax spans and br line breaks. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the element should become core/preformatted. - */ - private static function is_div_code_snippet_element($element): bool - { - if ($element->get_tag_name() !== 'DIV') { - return \false; - } - if (self::class_matches($element, '/(?:^|\s)[A-Za-z0-9_-]*code[-_]?window[A-Za-z0-9_-]*(?:$|\s)/i')) { - return \false; - } - if (self::has_unsafe_code_display_markup($element)) { - return \false; - } - if (self::class_matches($element, '/(?:^|[-_\s])ws[-_]?code(?:$|[-_\s])/i')) { - return \trim($element->get_text_content()) !== ''; - } - if (!self::class_matches($element, '/(?:^|[-_\s])(?:step[-_\s]?code|workflow[-_\s]?code|code[-_\s]?(?:snippet|block|body|output))(?:$|[-_\s])/i')) { - return \false; - } - $inner_html = $element->get_inner_html(); - $has_br_lines = \preg_match('//i', $inner_html) === 1; - $has_display_block_lines = self::has_display_block_code_lines($element); - if (!$has_br_lines && !$has_display_block_lines) { - return \false; - } - if (self::class_matches($element, '/(?:^|[-_\s])(?:step[-_\s]?code|workflow[-_\s]?code|code[-_\s]?(?:body|output))(?:$|[-_\s])/i')) { - return \trim($element->get_text_content()) !== ''; - } - return \preg_match('/(?:<|>|\/\/|\x{2192}|→|="|=\")/iu', $inner_html) === 1; - } - /** - * Converts snippet div line-break markup into preformatted content. - * - * @param HTML_To_Blocks_HTML_Element $element Source snippet element. - * @return string Preformatted block content. - */ - private static function normalise_code_snippet_content($element): string - { - $display_block_lines = self::get_display_block_code_lines($element); - if (\count($display_block_lines) >= 2) { - return \trim(\implode("\n", $display_block_lines)); - } - $inner_html = $element->get_inner_html(); - $content = \preg_replace('//i', "\n", $inner_html); - return \trim($content); - } - /** - * Checks whether a code snippet uses direct display:block spans as line wrappers. - * - * @param HTML_To_Blocks_HTML_Element $element Source snippet element. - * @return bool True when the snippet has display:block line spans. - */ - private static function has_display_block_code_lines($element): bool - { - return \count(self::get_display_block_code_lines($element)) >= 2; - } - /** - * Extracts line contents from direct display:block span wrappers. - * - * @param HTML_To_Blocks_HTML_Element $element Source snippet element. - * @return array Line HTML fragments. - */ - private static function get_display_block_code_lines($element): array - { - $lines = array(); - foreach ($element->get_child_elements() as $child) { - if ('SPAN' !== $child->get_tag_name()) { - continue; - } - $style = $child->has_attribute('style') ? $child->get_attribute('style') : ''; - if (\preg_match('/(?:^|;)\s*display\s*:\s*block\b/i', (string) $style) !== 1) { - continue; - } - $lines[] = $child->get_inner_html(); - } - return $lines; - } - /** - * Checks whether display-code markup contains executable/style content. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when this generic transform should not claim it. - */ - private static function has_unsafe_code_display_markup($element): bool - { - return \preg_match('/<\/?(?:script|style)\b/i', $element->get_inner_html()) === 1; - } - /** - * core/separator transforms - hr elements - * - * @return array Transform definitions - */ - private static function get_separator_transforms() - { - return array(array('blockName' => 'core/separator', 'priority' => 10, 'selector' => 'hr', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'HR'; - }, 'transform' => function ($element) { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - if ($element->has_attribute('class')) { - $class = $element->get_attribute('class'); - if (\strpos($class, 'is-style-wide') !== \false) { - $attributes['className'] = self::merge_class_names($attributes['className'] ?? '', 'is-style-wide'); - } elseif (\strpos($class, 'is-style-dots') !== \false) { - $attributes['className'] = self::merge_class_names($attributes['className'] ?? '', 'is-style-dots'); - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/separator', $attributes); - }), array('blockName' => 'core/separator', 'priority' => 11, 'selector' => 'div,span', 'isMatch' => function ($element) { - return \in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true) && self::is_empty_element($element) && self::class_matches($element, '/(?:^|[-_\s])(divider|separator|rule|ruler)(?:$|[-_\s]|\d)/i'); - }, 'transform' => function ($element) { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - return HTML_To_Blocks_Block_Factory::create_block('core/separator', $attributes); - })); - } - /** - * core/table transforms - table elements - * - * @return array Transform definitions - */ - private static function get_table_transforms() - { - return array(array('blockName' => 'core/table', 'priority' => 10, 'selector' => 'table', 'isMatch' => function ($element) { - return $element->get_tag_name() === 'TABLE'; - }, 'transform' => function ($element) { - return self::create_table_block_from_element($element); - })); - } - /** - * Creates a table block from an HTML element - * - * @param HTML_To_Blocks_HTML_Element $table_element The table element - * @return array Block array - */ - private static function create_table_block_from_element($table_element) - { - $table_html = $table_element->get_outer_html(); - $processor = \WP_HTML_Processor::create_fragment($table_html); - if (!$processor) { - return HTML_To_Blocks_Block_Factory::create_block('core/table', array()); - } - $current_section = 'body'; - $current_row = array(); - $rows_head = array(); - $rows_body = array(); - $rows_foot = array(); - $caption_text = ''; - $html_offset = 0; - while ($processor->next_tag(array('tag_closers' => 'visit'))) { - $tag = $processor->get_tag(); - $is_closer = $processor->is_tag_closer(); - if ('THEAD' === $tag && !$is_closer) { - $current_section = 'head'; - } elseif ('TBODY' === $tag && !$is_closer) { - $current_section = 'body'; - } elseif ('TFOOT' === $tag && !$is_closer) { - $current_section = 'foot'; - } elseif ('TR' === $tag && !$is_closer) { - $current_row = array(); - } elseif ('TR' === $tag) { - if (!empty($current_row)) { - $row_data = array('cells' => $current_row); - if ('head' === $current_section) { - $rows_head[] = $row_data; - } elseif ('foot' === $current_section) { - $rows_foot[] = $row_data; - } else { - $rows_body[] = $row_data; - } - } - $current_row = array(); - } elseif (('TD' === $tag || 'TH' === $tag) && !$is_closer) { - $cell_data = array('content' => '', 'tag' => \strtolower($tag)); - if ($processor->get_attribute('colspan')) { - $cell_data['colspan'] = (int) $processor->get_attribute('colspan'); - } - if ($processor->get_attribute('rowspan')) { - $cell_data['rowspan'] = (int) $processor->get_attribute('rowspan'); - } - $inner_html = self::extract_cell_content_at_offset($table_html, $html_offset, $tag); - $cell_data['content'] = $inner_html; - $current_row[] = $cell_data; - } elseif ('CAPTION' === $tag && !$is_closer) { - $caption_text = self::extract_cell_content_at_offset($table_html, $html_offset, 'CAPTION'); - } - } - $attributes = self::get_block_support_attributes($table_element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'spacing' => \true, 'border' => \true)); - $attributes = \array_merge($attributes, array('head' => $rows_head, 'body' => $rows_body, 'foot' => $rows_foot)); - if (!empty($caption_text)) { - $attributes['caption'] = $caption_text; - } - return HTML_To_Blocks_Block_Factory::create_block('core/table', $attributes); - } - /** - * Extracts cell content from table HTML using regex - * - * @param string $html Full table HTML - * @param int $offset Current offset position (passed by reference) - * @param string $tag Tag name (TD, TH, CAPTION) - * @return string Cell inner HTML - */ - private static function extract_cell_content_at_offset(string $html, int &$offset, string $tag): string - { - $search_html = \substr($html, $offset); - $tag_lower = \strtolower($tag); - $pattern = '/<' . \preg_quote($tag_lower, '/') . '(?:\s[^>]*)?>(.*)$/is'; - if (!\preg_match($pattern, $search_html, $matches, \PREG_OFFSET_CAPTURE)) { - return ''; - } - $content_start = $matches[1][1]; - $content = $matches[1][0]; - $close_tag = ''; - $close_pos = \stripos($content, $close_tag); - if (\false !== $close_pos) { - $inner_html = \substr($content, 0, $close_pos); - $offset = (int) ($offset + $matches[0][1] + \strlen($matches[0][0]) - \strlen($content) + $close_pos + \strlen($close_tag)); - return \trim($inner_html); - } - return ''; - } - /** - * Layout transforms - conservative wrappers only. - * - * Static placeholder form transforms. - * - * Core does not provide a generic form block. When a source form has no real - * submission target, preserve the visible intake copy as editable native blocks - * rather than falling back to core/html for inert markup. - * - * @return array Transform definitions - */ - private static function get_form_transforms() - { - return array(array('blockName' => 'core/group', 'priority' => 10, 'selector' => 'form', 'isMatch' => function ($element) { - return self::is_static_placeholder_form($element); - }, 'transform' => function ($element) { - return self::create_static_placeholder_form_group($element); - })); - } - /** - * Determines whether a form is an inert static placeholder. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the form can safely become editable copy blocks. - */ - private static function is_static_placeholder_form($element): bool - { - if ('FORM' !== $element->get_tag_name()) { - return \false; - } - if (!self::has_direct_form_controls($element)) { - return \false; - } - $action = $element->has_attribute('action') ? \trim((string) $element->get_attribute('action')) : ''; - $method = $element->has_attribute('method') ? \trim((string) $element->get_attribute('method')) : ''; - if ('#' === $action) { - return \true; - } - return '' === $action && '' === $method && self::has_static_placeholder_form_marker($element); - } - /** - * Checks whether a form has direct controls worth preserving. - * - * @param HTML_To_Blocks_HTML_Element $element Source form element. - * @return bool True when direct form controls are present. - */ - private static function has_direct_form_controls($element): bool - { - foreach ($element->get_child_elements() as $child) { - if (\in_array($child->get_tag_name(), array('LABEL', 'INPUT', 'TEXTAREA', 'SELECT', 'BUTTON'), \true)) { - return \true; - } - } - return \false; - } - /** - * Checks whether a no-target form is explicitly presented as static preview copy. - * - * @param HTML_To_Blocks_HTML_Element $element Source form element. - * @return bool True when the source marks the form as inert preview/checklist UI. - */ - private static function has_static_placeholder_form_marker($element): bool - { - $class = $element->has_attribute('class') ? (string) $element->get_attribute('class') : ''; - $aria_label = $element->has_attribute('aria-label') ? (string) $element->get_attribute('aria-label') : ''; - $text = wp_strip_all_tags($element->get_inner_html()); - $haystack = \strtolower($class . ' ' . $aria_label . ' ' . $text); - return \false !== \strpos($haystack, 'static-form') || \false !== \strpos($haystack, 'form-card') || \false !== \strpos($haystack, 'static preview') || \false !== \strpos($haystack, 'preview form') || \false !== \strpos($haystack, 'preview only') || \false !== \strpos($haystack, 'checklist'); - } - /** - * Creates a native group from a static placeholder form. - * - * @param HTML_To_Blocks_HTML_Element $element Source form element. - * @return array Block array. - */ - private static function create_static_placeholder_form_group($element): array - { - $inner_blocks = self::create_static_placeholder_form_child_blocks($element->get_child_elements()); - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - } - /** - * Creates editable blocks for static placeholder form children. - * - * @param array $children Child elements. - * @return array> Block arrays. - */ - private static function create_static_placeholder_form_child_blocks(array $children): array - { - $inner_blocks = array(); - foreach ($children as $child) { - $tag = $child->get_tag_name(); - if (\preg_match('/^H([1-6])$/', $tag, $matches)) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/heading', array('level' => (int) $matches[1], 'content' => \trim($child->get_inner_html()))); - continue; - } - if ('LABEL' === $tag) { - $content = self::get_static_form_label_text($child); - if ('' !== $content) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', array('content' => esc_html($content))); - } - $inner_blocks = \array_merge($inner_blocks, self::create_static_placeholder_form_child_blocks($child->get_child_elements())); - continue; - } - if ('BUTTON' === $tag) { - $content = \trim(wp_strip_all_tags($child->get_inner_html())); - if ('' !== $content) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/buttons', array(), array(HTML_To_Blocks_Block_Factory::create_block('core/button', array('text' => esc_html($content), 'url' => '#')))); - } - continue; - } - if ('P' === $tag) { - $content = \trim(wp_strip_all_tags($child->get_inner_html())); - if ('' !== $content) { - $attributes = self::get_block_support_attributes($child, array('class_name' => \true)); - $attributes['content'] = esc_html(\preg_replace('/\s+/', ' ', $content)); - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - continue; - } - if ('SELECT' === $tag) { - $option_blocks = self::create_static_form_option_blocks($child); - if (!empty($option_blocks)) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/list', array(), $option_blocks); - continue; - } - } - if (\in_array($tag, array('INPUT', 'TEXTAREA', 'SELECT'), \true)) { - $content = self::get_static_form_control_label($child); - if ('' !== $content) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', array('content' => esc_html($content))); - } - continue; - } - if (\in_array($tag, array('DIV', 'SECTION'), \true)) { - $child_blocks = self::create_static_placeholder_form_child_blocks($child->get_child_elements()); - if (!empty($child_blocks)) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($child), $child_blocks); - } - } - } - return $inner_blocks; - } - /** - * Creates editable list item blocks from visible static select options. - * - * @param HTML_To_Blocks_HTML_Element $select Select element. - * @return array> Option list-item blocks. - */ - private static function create_static_form_option_blocks($select): array - { - $option_blocks = array(); - foreach ($select->get_child_elements() as $child) { - if ('OPTION' !== $child->get_tag_name()) { - continue; - } - $content = \trim(wp_strip_all_tags($child->get_inner_html())); - if ('' === $content) { - continue; - } - $option_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/list-item', array('content' => esc_html(\preg_replace('/\s+/', ' ', $content)))); - } - return $option_blocks; - } - /** - * Gets visible text for a static form label. - * - * @param HTML_To_Blocks_HTML_Element $label Label element. - * @return string Label text. - */ - private static function get_static_form_label_text($label): string - { - $text = \trim(wp_strip_all_tags($label->get_inner_html())); - if ('' !== $text) { - return \preg_replace('/\s+/', ' ', $text); - } - foreach ($label->get_child_elements() as $child) { - $text = self::get_static_form_control_label($child); - if ('' !== $text) { - return $text; - } - } - return ''; - } - /** - * Gets the best editable text for an unlabeled static form control. - * - * @param HTML_To_Blocks_HTML_Element $control Form control element. - * @return string Control label text. - */ - private static function get_static_form_control_label($control): string - { - foreach (array('placeholder', 'aria-label', 'name') as $attribute) { - if ($control->has_attribute($attribute)) { - $value = \trim((string) $control->get_attribute($attribute)); - if ('' !== $value) { - return \preg_replace('/\s+/', ' ', $value); - } - } - } - return ''; - } - /** - * @return array Transform definitions - */ - private static function get_layout_transforms() - { - return array(array('blockName' => 'core/spacer', 'priority' => 11, 'isMatch' => function ($element) { - return self::is_spacer_element($element); - }, 'transform' => function ($element) { - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'spacing' => \true)); - $height = self::extract_height_value($element); - if ('' !== $height) { - $attributes['height'] = $height; - } - return HTML_To_Blocks_Block_Factory::create_block('core/spacer', $attributes); - }), array('blockName' => 'core/cover', 'priority' => 12, 'isMatch' => function ($element) { - return self::is_cover_element($element); - }, 'transform' => function ($element, $handler) { - $attributes = self::get_common_layout_attributes($element); - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - $inner_blocks = $handler(array('HTML' => $element->get_inner_html())); - if (\preg_match('/background-image:\s*url\((["\']?)([^)"\']+)\1\)/i', $style, $matches)) { - $attributes['url'] = \trim($matches[2]); - } - $background_color = self::extract_background_color($style); - if ('' !== $background_color) { - $attributes['customOverlayColor'] = $background_color; - } - return HTML_To_Blocks_Block_Factory::create_block('core/cover', $attributes, $inner_blocks); - }), array('blockName' => 'core/columns', 'priority' => 13, 'isMatch' => function ($element) { - return self::is_columns_element($element); - }, 'transform' => function ($element, $handler) { - $inner_blocks = array(); - foreach ($element->get_child_elements() as $child) { - if (!self::is_column_element($child)) { - continue; - } - $column_attributes = self::get_common_layout_attributes($child); - $column_blocks = $handler(array('HTML' => $child->get_inner_html())); - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/column', $column_attributes, $column_blocks); - } - return HTML_To_Blocks_Block_Factory::create_block('core/columns', self::get_common_layout_attributes($element), $inner_blocks); - }), array('blockName' => 'core/group', 'priority' => 3, 'isMatch' => function ($element) { - return self::is_repeated_card_grid_element($element); - }, 'transform' => function ($element, $handler, $args = array()) { - return self::create_repeated_card_grid_group($element, $handler, $args); - }), array('blockName' => 'core/column', 'priority' => 14, 'isMatch' => function ($element) { - return self::is_column_element($element); - }, 'transform' => function ($element, $handler) { - return HTML_To_Blocks_Block_Factory::create_block('core/column', self::get_common_layout_attributes($element), $handler(array('HTML' => $element->get_inner_html()))); - }), array('blockName' => 'core/group', 'priority' => 14, 'isMatch' => function ($element) { - return self::is_image_wrapper_element($element); - }, 'transform' => function ($element) { - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), array(self::create_image_block_from_img($element->query_selector('img')))); - }), array('blockName' => 'core/group', 'priority' => 14, 'isMatch' => function ($element) { - return self::is_nav_logo_chrome_element($element); - }, 'transform' => function ($element, $handler) { - $inner_html = $element->get_inner_html(); - foreach ($element->get_child_elements() as $child) { - if (self::is_empty_decorative_element($child)) { - $inner_html = \str_replace($child->get_outer_html(), '', $inner_html); - } - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $handler(array('HTML' => $inner_html))); - }), array('blockName' => 'core/group', 'priority' => 14, 'isMatch' => function ($element) { - return self::is_inline_scroller_element($element); - }, 'transform' => function ($element, $handler) { - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), self::create_inline_scroller_child_blocks($element, $handler)); - }), array('blockName' => 'core/group', 'priority' => 14, 'isMatch' => function ($element) { - return self::is_decorative_figure_with_caption($element); - }, 'transform' => function ($element) { - $children = $element->get_child_elements(); - $caption = \end($children); - $caption_attrs = self::get_block_support_attributes($caption, array('class_name' => \true)); - $inner_blocks = array(); - if (\count($children) === 2) { - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_empty_decorative_group_attributes($children[0])); - } - $caption_attrs['content'] = \trim($caption->get_inner_html()); - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $caption_attrs); - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - }), array('blockName' => 'core/group', 'priority' => 15, 'isMatch' => function ($element) { - return self::is_group_element($element); - }, 'transform' => function ($element, $handler) { - $attributes = self::is_empty_decorative_element($element) ? self::get_empty_decorative_group_attributes($element) : self::get_common_layout_attributes($element); - return HTML_To_Blocks_Block_Factory::create_block('core/group', $attributes, $handler(array('HTML' => $element->get_inner_html()))); - })); - } - /** - * Gets safe attributes for empty decorative group chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return array Block attributes. - */ - private static function get_empty_decorative_group_attributes($element): array - { - return self::get_block_support_attributes($element, array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'dimensions' => \true, 'spacing' => \true, 'border' => \true, 'aria_label' => \true)); - } - /** - * Gets attributes shared by layout blocks. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return array Block attributes. - */ - private static function get_common_layout_attributes($element): array - { - $options = array('anchor' => \true, 'class_name' => \true, 'align' => \true, 'colors' => \true, 'dimensions' => \true, 'typography' => \true, 'spacing' => \true, 'border' => \true, 'layout' => \true, 'tag_name' => $element->get_tag_name() !== 'DIV', 'aria_label' => \true); - return self::get_block_support_attributes($element, $options); - } - /** - * Extracts direct, mechanical block-support attributes from HTML. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @param array $options Enabled support keys. - * @return array Block attributes. - */ - private static function get_block_support_attributes($element, array $options = array()): array - { - $attributes = array(); - $classes = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - if (!empty($options['anchor']) && $element->has_attribute('id') && $element->get_attribute('id') !== '') { - $attributes['anchor'] = $element->get_attribute('id'); - } - if (!empty($options['align']) && \preg_match('/(?:^|\s)align(wide|full|left|center|right)(?:$|\s)/i', $classes, $matches)) { - $attributes['align'] = \strtolower($matches[1]); - } - if (!empty($options['class_name'])) { - $class_name = self::safe_block_class_name($classes); - if ('' !== $class_name) { - $attributes['className'] = $class_name; - } - } - if (!empty($options['tag_name'])) { - $tag_name = \strtolower($element->get_tag_name()); - if (\in_array($tag_name, array('section', 'main', 'article', 'aside', 'header', 'footer', 'nav'), \true)) { - $attributes['tagName'] = $tag_name; - } - } - if (!empty($options['aria_label']) && $element->has_attribute('aria-label') && $element->get_attribute('aria-label') !== '') { - $attributes['ariaLabel'] = $element->get_attribute('aria-label'); - } - if (!empty($options['colors'])) { - self::apply_color_support_attributes($attributes, $style, $classes); - } - if (!empty($options['typography'])) { - self::apply_typography_support_attributes($attributes, $style, $classes); - } - if ('' !== $style) { - if (!empty($options['text_align'])) { - $text_align = self::extract_css_property($style, 'text-align'); - if (\in_array(\strtolower($text_align), array('left', 'center', 'right'), \true)) { - $attributes['textAlign'] = \strtolower($text_align); - } - } - if (!empty($options['spacing'])) { - self::apply_spacing_support_attributes($attributes, $style); - } - if (!empty($options['border'])) { - self::apply_border_support_attributes($attributes, $style); - } - if (!empty($options['dimensions'])) { - self::apply_dimension_support_attributes($attributes, $style); - } - } - if (!empty($options['layout'])) { - self::apply_layout_support_attributes($attributes, $classes, $style); - } - return $attributes; - } - /** - * Preserves source classes that are safe as block custom classes. - * - * @param string $class_name Source class attribute. - * @return string Safe custom classes. - */ - private static function safe_block_class_name(string $class_name): string - { - $classes = \preg_split('/\s+/', \trim($class_name)); - if (\false === $classes) { - return ''; - } - $classes = \array_filter($classes, function ($class_name) { - return '' !== $class_name && \preg_match('/^[A-Za-z0-9_-]+$/', $class_name) === 1 && \preg_match('/^align(?:wide|full|left|center|right)$/i', $class_name) !== 1 && \preg_match('/^has-(?:[A-Za-z0-9_-]+-(?:color|background-color|font-size)|text-color|background|custom-font-size)$/i', $class_name) !== 1 && \preg_match('/^is-(?:layout-(?:flow|constrained|flex)|vertical|horizontal|nowrap|content-justification-[A-Za-z0-9_-]+)$/i', $class_name) !== 1 && \stripos($class_name, 'wp-block-') !== 0; - }); - return \implode(' ', \array_values(\array_unique($classes))); - } - /** - * Merges two class-name strings without duplicates. - * - * @param string $base Base classes. - * @param string $extra Extra classes. - * @return string Merged classes. - */ - private static function merge_class_names(string $base, string $extra): string - { - return self::safe_block_class_name(\trim($base . ' ' . $extra)); - } - /** - * Applies direct color declarations and explicit WordPress color preset classes. - * - * @param array $attributes Block attributes. - * @param string $style Source style attribute. - * @param string $classes Source class attribute. - */ - private static function apply_color_support_attributes(array &$attributes, string $style, string $classes = ''): void - { - $text_color = self::extract_preset_class_slug($classes, 'color'); - if ('' !== $text_color) { - $attributes['textColor'] = $text_color; - } - $background_color = self::extract_preset_class_slug($classes, 'background-color'); - if ('' !== $background_color) { - $attributes['backgroundColor'] = $background_color; - } - $color = self::extract_css_property($style, 'color'); - if ('' !== $color) { - $attributes['style']['color']['text'] = $color; - } - $background = self::extract_background_color($style); - if ('' !== $background) { - if (\stripos($background, 'gradient(') !== \false) { - $attributes['style']['color']['gradient'] = $background; - } else { - $attributes['style']['color']['background'] = $background; - } - } - } - /** - * Applies explicit WordPress typography preset classes/vars. - * - * @param array $attributes Block attributes. - * @param string $style Source style attribute. - * @param string $classes Source class attribute. - */ - private static function apply_typography_support_attributes(array &$attributes, string $style, string $classes = ''): void - { - $font_size = self::extract_preset_class_slug($classes, 'font-size'); - if ('' !== $font_size) { - $attributes['fontSize'] = $font_size; - return; - } - $font_size_value = self::extract_css_property($style, 'font-size'); - $font_size_token = self::normalise_wp_preset_var($font_size_value, 'font-size'); - if ('' !== $font_size_token) { - $attributes['fontSize'] = $font_size_token; - } - } - /** - * Applies direct margin/padding declarations to block support attributes. - * - * @param array $attributes Block attributes. - * @param string $style Source style attribute. - */ - private static function apply_spacing_support_attributes(array &$attributes, string $style): void - { - foreach (array('margin', 'padding') as $kind) { - $value = self::extract_css_property($style, $kind); - $side_values = array(); - foreach (array('top', 'right', 'bottom', 'left') as $side) { - $side_value = self::extract_css_property($style, $kind . '-' . $side); - if ('' !== $side_value) { - $side_values[$side] = $side_value; - } - } - if (!empty($side_values)) { - foreach ($side_values as $side => $side_value) { - $side_values[$side] = self::normalise_wp_preset_var($side_value, 'spacing') ? self::normalise_wp_preset_var($side_value, 'spacing') : $side_value; - } - $attributes['style']['spacing'][$kind] = $side_values; - continue; - } - if ('' !== $value) { - $attributes['style']['spacing'][$kind] = self::normalise_wp_preset_var($value, 'spacing') ? self::normalise_wp_preset_var($value, 'spacing') : $value; - } - } - } - /** - * Applies explicit WordPress layout classes emitted by block supports. - * - * @param array $attributes Block attributes. - * @param string $classes Source class attribute. - */ - private static function apply_layout_support_attributes(array &$attributes, string $classes, string $style = ''): void - { - if (\preg_match('/(?:^|\s)is-layout-(flow|constrained|flex)(?:\s|$)/i', $classes, $matches)) { - $type = \strtolower($matches[1]); - $attributes['layout']['type'] = 'flow' === $type ? 'default' : $type; - } - if (\strtolower(self::extract_css_property($style, 'display')) === 'flex') { - $attributes['layout']['type'] = 'flex'; - } - if (\preg_match('/(?:^|\s)is-(vertical|horizontal)(?:\s|$)/i', $classes, $matches)) { - $attributes['layout']['orientation'] = \strtolower($matches[1]); - } - $flex_direction = \strtolower(self::extract_css_property($style, 'flex-direction')); - if (\in_array($flex_direction, array('column', 'column-reverse'), \true)) { - $attributes['layout']['orientation'] = 'vertical'; - } elseif (\in_array($flex_direction, array('row', 'row-reverse'), \true)) { - $attributes['layout']['orientation'] = 'horizontal'; - } - if (\preg_match('/(?:^|\s)is-content-justification-(left|right|center|space-between)(?:\s|$)/i', $classes, $matches)) { - $attributes['layout']['justifyContent'] = \strtolower($matches[1]); - } - $justify_content = \strtolower(self::extract_css_property($style, 'justify-content')); - if (\in_array($justify_content, array('left', 'right', 'center', 'space-between'), \true)) { - $attributes['layout']['justifyContent'] = $justify_content; - } - $align_items = \strtolower(self::extract_css_property($style, 'align-items')); - if (\in_array($align_items, array('left', 'right', 'center'), \true)) { - $attributes['layout']['verticalAlignment'] = $align_items; - } - if (\preg_match('/(?:^|\s)is-nowrap(?:\s|$)/i', $classes) || \strtolower(self::extract_css_property($style, 'flex-wrap')) === 'nowrap') { - $attributes['layout']['flexWrap'] = 'nowrap'; - } - } - /** - * Applies direct dimension declarations to block support attributes. - * - * @param array $attributes Block attributes. - * @param string $style Source style attribute. - */ - private static function apply_dimension_support_attributes(array &$attributes, string $style): void - { - $min_height = self::extract_css_property($style, 'min-height'); - if ('' !== $min_height) { - $attributes['style']['dimensions']['minHeight'] = $min_height; - } - $width = self::extract_css_property($style, 'width'); - if ('' !== $width && self::is_safe_dimension_value($width)) { - $attributes['style']['dimensions']['width'] = $width; - } - } - /** - * Checks whether a CSS dimension value is safe to preserve mechanically. - * - * @param string $value CSS dimension value. - * @return bool True when the value contains no executable or external payload. - */ - private static function is_safe_dimension_value(string $value): bool - { - $value = \trim($value); - if ('' === $value || \strlen($value) > 80) { - return \false; - } - if (\preg_match('/(?:url\s*\(|expression\s*\(|javascript\s*:|behavior\s*:)/i', $value)) { - return \false; - } - return \preg_match('/^[0-9.]+(?:px|em|rem|%|vw|vh|vmin|vmax|ch|ex)?$/i', $value) === 1 || \preg_match('/^calc\(\s*[0-9.]+(?:px|em|rem|%|vw|vh|vmin|vmax|ch|ex)?\s*[-+]\s*[0-9.]+(?:px|em|rem|%|vw|vh|vmin|vmax|ch|ex)?\s*\)$/i', $value) === 1; - } - /** - * Applies direct border declarations to block support attributes. - * - * @param array $attributes Block attributes. - * @param string $style Source style attribute. - */ - private static function apply_border_support_attributes(array &$attributes, string $style): void - { - $border_parts = array(); - $border = self::extract_css_property($style, 'border'); - if ('' !== $border) { - $border_parts = self::parse_border_shorthand($border); - } - $color = self::extract_css_property($style, 'border-color'); - if ('' !== $color && self::is_safe_border_color($color)) { - $border_parts['color'] = $color; - } - $border_style = self::extract_css_property($style, 'border-style'); - if ('' !== $border_style && self::is_safe_border_style($border_style)) { - $border_parts['style'] = \strtolower($border_style); - } - $width = self::extract_css_property($style, 'border-width'); - if ('' !== $width && self::is_safe_border_width($width)) { - $border_parts['width'] = $width; - } - $radius = self::extract_css_property($style, 'border-radius'); - if ('' !== $radius && self::is_safe_border_radius($radius)) { - $border_parts['radius'] = $radius; - } - if (!empty($border_parts)) { - $attributes['style']['border'] = $border_parts; - } - } - /** - * Parses an editor-safe `border` shorthand into block support parts. - * - * @param string $value CSS border shorthand value. - * @return array Border support parts. - */ - private static function parse_border_shorthand(string $value): array - { - $parts = array(); - foreach (self::split_css_value_tokens($value) as $token) { - $lower_token = \strtolower($token); - if (empty($parts['width']) && self::is_safe_border_width($token)) { - $parts['width'] = $token; - continue; - } - if (empty($parts['style']) && self::is_safe_border_style($lower_token)) { - $parts['style'] = $lower_token; - continue; - } - if (empty($parts['color']) && self::is_safe_border_color($token)) { - $parts['color'] = $token; - } - } - return $parts; - } - /** - * Splits CSS value tokens without breaking function arguments. - * - * @param string $value CSS value. - * @return array Tokens. - */ - private static function split_css_value_tokens(string $value): array - { - $tokens = array(); - $token = ''; - $depth = 0; - $length = \strlen($value); - for ($index = 0; $index < $length; $index++) { - $char = $value[$index]; - if ('(' === $char) { - ++$depth; - } elseif (')' === $char && $depth > 0) { - --$depth; - } - if (\ctype_space($char) && 0 === $depth) { - if ('' !== $token) { - $tokens[] = $token; - $token = ''; - } - continue; - } - $token .= $char; - } - if ('' !== $token) { - $tokens[] = $token; - } - return $tokens; - } - /** - * Checks whether a border width is safe and editor-valid. - * - * @param string $value CSS border width. - * @return bool True when safe. - */ - private static function is_safe_border_width(string $value): bool - { - $value = \trim($value); - if ('' === $value || \preg_match('/\s/', $value)) { - return \false; - } - return \in_array(\strtolower($value), array('thin', 'medium', 'thick'), \true) || \preg_match('/^(?:0|[0-9.]+(?:px|em|rem|%|vw|vh|vmin|vmax))$/i', $value) === 1; - } - /** - * Checks whether a border style is safe and editor-valid. - * - * @param string $value CSS border style. - * @return bool True when safe. - */ - private static function is_safe_border_style(string $value): bool - { - return \in_array(\strtolower(\trim($value)), array('none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset'), \true); - } - /** - * Checks whether a border color is safe to preserve mechanically. - * - * @param string $value CSS border color. - * @return bool True when safe. - */ - private static function is_safe_border_color(string $value): bool - { - $value = \trim($value); - if ('' === $value || \strlen($value) > 100 || \preg_match('/(?:url\s*\(|expression\s*\(|javascript\s*:|behavior\s*:)/i', $value)) { - return \false; - } - return \preg_match('/^(?:#[0-9a-f]{3,8}|[a-z]+|rgba?\([^()]+\)|hsla?\([^()]+\)|var\(\s*--[A-Za-z0-9_-]+\s*\))$/i', $value) === 1; - } - /** - * Checks whether a border radius is safe to preserve mechanically. - * - * @param string $value CSS border radius. - * @return bool True when safe. - */ - private static function is_safe_border_radius(string $value): bool - { - $tokens = self::split_css_value_tokens(\trim($value)); - if (empty($tokens) || \count($tokens) > 4) { - return \false; - } - foreach ($tokens as $token) { - if (!self::is_safe_border_width($token)) { - return \false; - } - } - return \true; - } - /** - * Extracts one CSS declaration value from a style attribute. - * - * @param string $style CSS style attribute. - * @param string $name CSS property name. - * @return string CSS value or empty string. - */ - private static function extract_css_property(string $style, string $name): string - { - $pattern = '/(?:^|;)\s*' . \preg_quote($name, '/') . '\s*:\s*([^;]+)/i'; - return \preg_match($pattern, $style, $matches) ? \trim($matches[1]) : ''; - } - /** - * Extracts a WordPress preset slug from generated block-support classes. - * - * @param string $classes Source class attribute. - * @param string $kind Preset class kind: color, background-color, or font-size. - * @return string Preset slug or empty string. - */ - private static function extract_preset_class_slug(string $classes, string $kind): string - { - $pattern = 'background-color' === $kind ? '/(?:^|\s)has-([A-Za-z0-9_-]+)-background-color(?:\s|$)/i' : '/(?:^|\s)has-([A-Za-z0-9_-]+)-' . \preg_quote($kind, '/') . '(?:\s|$)/i'; - if (!\preg_match($pattern, $classes, $matches)) { - return ''; - } - $slug = \strtolower($matches[1]); - return \in_array($slug, array('text', 'background', 'custom'), \true) ? '' : $slug; - } - /** - * Converts explicit WordPress preset CSS vars to block attribute token syntax. - * - * @param string $value CSS value. - * @param string $kind Preset kind, such as spacing or font-size. - * @return string Block preset token or empty string. - */ - private static function normalise_wp_preset_var(string $value, string $kind): string - { - $pattern = '/^var\(\s*--wp--preset--' . \preg_quote($kind, '/') . '--([A-Za-z0-9_-]+)\s*\)$/i'; - return \preg_match($pattern, \trim($value), $matches) ? 'var:preset|' . $kind . '|' . \strtolower($matches[1]) : ''; - } - /** - * Checks whether an element is a safe group wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element should become core/group. - */ - private static function is_group_element($element): bool - { - $tag = $element->get_tag_name(); - if ('SECTION' === $tag) { - return \true; - } - if (\in_array($tag, array('MAIN', 'ARTICLE', 'ASIDE', 'HEADER', 'FOOTER', 'NAV'), \true)) { - return \true; - } - if ('SPAN' === $tag) { - return self::is_empty_decorative_element($element); - } - if ('DIV' !== $tag) { - return \false; - } - if (self::is_project_card_status_element($element) && !self::is_empty_element($element)) { - return \false; - } - if (\trim($element->get_text_content()) !== '' && \trim($element->get_inner_html()) === \trim(wp_strip_all_tags($element->get_inner_html()))) { - return \false; - } - if (self::class_matches($element, '/(?:^|[-_\s])quote[-_\s]+(?:attr|attribution)(?:$|[-_\s])/i')) { - return \true; - } - if (self::class_matches($element, '/(?:^|[-_\s])(actions?|buttons?|cta|group|section|container|wrapper|wrap|content|main|page|article|aside|header|footer|inner|row|grid|card|product|compare|feature|visual|text|pin|location|address|detail|chrome|scroll|thumb|stars?|rating|info|demo|panel|arrow)(?:$|[-_\s])/i')) { - return \true; - } - if (self::class_matches($element, '/(?:^|[-_\s])(code|code[-_\s]?window)(?:$|[-_\s])/i')) { - return \false; - } - if ($element->has_attribute('data-widget')) { - return \false; - } - if (array() !== $element->get_child_elements()) { - return \true; - } - return self::is_empty_decorative_element($element); - } - /** - * Checks whether a wrapper is a repeated static card grid. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when direct children should become editable card groups. - */ - private static function is_repeated_card_grid_element($element): bool - { - $cache_key = \spl_object_id($element); - unset(self::$repeated_card_grid_children[$cache_key]); - if (!\in_array($element->get_tag_name(), array('DIV', 'SECTION'), \true)) { - return \false; - } - if (!self::class_matches($element, '/(?:^|[-_\s])(?:grid|cards?|network|list)(?:$|[-_\s]|\d)/i')) { - return \false; - } - $cards = array(); - foreach ($element->get_child_elements() as $child) { - if (self::is_empty_decorative_element($child)) { - continue; - } - if (!self::is_card_grid_item_element($child)) { - return \false; - } - $cards[] = $child; - } - if (\count($cards) < 2) { - return \false; - } - self::$repeated_card_grid_children[$cache_key] = $cards; - return \true; - } - /** - * Checks whether an element is one repeated card/grid item. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element is safe to convert as a card group. - */ - private static function is_card_grid_item_element($element): bool - { - if (\in_array($element->get_tag_name(), array('ARTICLE', 'ASIDE'), \true)) { - return \true; - } - if ('A' === $element->get_tag_name() && array() !== $element->get_child_elements()) { - return \true; - } - if (!\in_array($element->get_tag_name(), array('DIV', 'SECTION'), \true)) { - return \false; - } - return self::class_matches($element, '/(?:^|[-_\s])(?:cards?|card|col|column|item|tile|entry|post|network|site|feature)(?:$|[-_\s]|\d)/i'); - } - /** - * Creates an editable group hierarchy for repeated static card grids. - * - * @param HTML_To_Blocks_HTML_Element $element The grid wrapper. - * @param callable $handler Recursive raw handler. - * @return array Block array. - */ - private static function create_repeated_card_grid_group($element, callable $handler, array $args = array()): array - { - $inner_blocks = array(); - $cache_key = \spl_object_id($element); - $children = self::$repeated_card_grid_children[$cache_key] ?? $element->get_child_elements(); - unset(self::$repeated_card_grid_children[$cache_key]); - $diagnostic = self::get_commerce_product_grid_diagnostic($element, $args); - if (null !== $diagnostic && \function_exists('do_action')) { - \do_action('html_to_blocks_commerce_product_grid_detected', $diagnostic, $element, $args); - } - foreach ($children as $child) { - $inner_blocks[] = self::create_card_grid_item_group($child, $handler); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - } - /** - * Builds a diagnostic payload for explicit commerce-context product grids. - * - * @param HTML_To_Blocks_HTML_Element $element The grid wrapper. - * @param array $args Raw handler arguments. - * @return array|null Diagnostic payload, or null when the commerce gate is closed. - */ - private static function get_commerce_product_grid_diagnostic($element, array $args): ?array - { - if (!self::has_explicit_commerce_context($args) || !self::is_product_grid_element($element)) { - return null; - } - $products = array(); - foreach ($element->get_child_elements() as $child) { - if (self::is_empty_decorative_element($child)) { - continue; - } - $products[] = \array_filter(array('slug' => $child->get_attribute('data-product-slug') ?? '', 'name' => self::get_first_text_for_selectors($child, array('.product-card__name', '.ss-product-name', 'h1', 'h2', 'h3', 'h4')), 'price' => self::get_first_text_for_selectors($child, array('.product-card__price', '.ss-product-price')), 'category' => self::get_first_text_for_selectors($child, array('.product-card__category', '.ss-product-category')), 'badge' => self::get_first_text_for_selectors($child, array('.product-card__badge', '.ss-product-badge')), 'cta' => self::get_first_text_for_selectors($child, array('.product-card__cta', '.ss-product-cta', 'a')), 'has_image' => null !== $child->query_selector('img'), 'placeholder' => null !== $child->query_selector('.product-card__image-placeholder')), function ($value) { - return !\is_string($value) || '' !== $value; - }); - } - return array('type' => 'commerce_product_grid', 'status' => 'static_editable_placeholder', 'product_count' => \count($products), 'products' => $products, 'message' => 'Explicit commerce context detected; h2bc preserved editor-valid static product cards for downstream materialization.'); - } - /** - * Checks whether raw-handler args explicitly opt into commerce-aware handling. - * - * @param array $args Raw handler arguments. - * @return bool True when explicit commerce context is present. - */ - private static function has_explicit_commerce_context(array $args): bool - { - $context = $args['context'] ?? array(); - if (!\is_array($context)) { - return \false; - } - return !empty($context['commerce']) || !empty($context['products']) || !empty($context['product_context']); - } - /** - * Checks whether a repeated card grid carries high-confidence product-grid markers. - * - * @param HTML_To_Blocks_HTML_Element $element The grid wrapper. - * @return bool True when the grid is product-like. - */ - private static function is_product_grid_element($element): bool - { - return self::class_matches($element, '/(?:^|[-_\s])products?(?:$|[-_\s])/i') || self::class_matches($element, '/(?:^|[-_\s])shop(?:$|[-_\s])/i') || 'product-grid' === ($element->get_attribute('data-commerce-region') ?? ''); - } - /** - * Gets the first non-empty text from a simple selector list. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @param array $selectors Simple selectors to inspect. - * @return string Text content or empty string. - */ - private static function get_first_text_for_selectors($element, array $selectors): string - { - foreach ($selectors as $selector) { - $match = $element->query_selector($selector); - if ($match && \trim($match->get_text_content()) !== '') { - return \trim($match->get_text_content()); - } - } - return ''; - } - /** - * Creates one editable card group, preserving whole-card links as CTA buttons. - * - * @param HTML_To_Blocks_HTML_Element $element The card item. - * @param callable $handler Recursive raw handler. - * @return array Block array. - */ - private static function create_card_grid_item_group($element, callable $handler): array - { - $children = $element->get_child_elements(); - $link_element = 'A' === $element->get_tag_name() ? $element : self::get_single_complex_card_anchor_child($element, $children); - $card_element = $link_element ? $link_element : $element; - $card_children = $link_element ? null : $children; - $inner_blocks = self::create_card_grid_item_inner_blocks($card_element, $card_children); - if (null === $inner_blocks) { - $content_html = $link_element ? $link_element->get_inner_html() : $element->get_inner_html(); - $inner_blocks = $handler(array('HTML' => $content_html)); - } - if ($link_element && $link_element->has_attribute('href')) { - $inner_blocks[] = self::create_button_block_from_anchor(self::create_card_grid_cta_anchor($link_element)); - } - return HTML_To_Blocks_Block_Factory::create_block('core/group', self::get_common_layout_attributes($element), $inner_blocks); - } - /** - * Convert common repeated-card children without re-entering the full raw handler. - * - * This keeps repeated card grids on the WordPress HTML API path while avoiding a - * fresh full transform pass for every card in large static exports. - * - * @param HTML_To_Blocks_HTML_Element $element Card content element. - * @return array>|null Blocks, or null when the shape needs the generic handler. - */ - private static function create_card_grid_item_inner_blocks($element, ?array $children = null): ?array - { - $blocks = array(); - foreach ($children ?? $element->get_child_elements() as $child) { - $block = self::create_card_grid_child_block($child); - if (null === $block) { - return null; - } - if (!empty($block)) { - $blocks[] = $block; - } - } - foreach (self::get_direct_image_children_from_html($element->get_inner_html()) as $image) { - $image_src = $image->get_attribute('src') ?? ''; - $has_image = \false; - foreach ($blocks as $block) { - if ('core/image' === ($block['blockName'] ?? '') && ($block['attrs']['url'] ?? '') === $image_src) { - $has_image = \true; - break; - } - } - if (!$has_image) { - \array_unshift($blocks, self::create_image_block_from_img($image)); - } - } - return $blocks; - } - /** - * Gets direct image children from raw inner HTML. - * - * @param string $html Inner HTML to inspect. - * @return array Image elements. - */ - private static function get_direct_image_children_from_html(string $html): array - { - $remaining = $html; - $images = array(); - if (!\preg_match_all('/]*\/?>/i', $html, $matches)) { - return array(); - } - foreach ($matches[0] as $image_html) { - $image = HTML_To_Blocks_HTML_Element::from_html($image_html); - if ($image) { - $images[] = $image; - } - $remaining = \str_replace($image_html, '', $remaining); - } - return '' === \trim(\preg_replace('/<[^>]+>.*?<\/[^>]+>/s', '', $remaining) ?? $remaining) ? $images : array(); - } - /** - * Convert one common card child to a native block. - * - * @param HTML_To_Blocks_HTML_Element $child Card child element. - * @return array|array{}|null Block, empty array for ignored decorative children, or null for fallback. - */ - private static function create_card_grid_child_block($child): ?array - { - if (self::is_empty_decorative_element($child)) { - return array(); - } - $tag = $child->get_tag_name(); - if (\preg_match('/^H[1-6]$/', $tag)) { - return HTML_To_Blocks_Block_Factory::create_block('core/heading', \array_merge(self::get_block_support_attributes($child, array('anchor' => \true, 'class_name' => \true)), array('level' => (int) \substr($tag, 1), 'content' => $child->get_inner_html()))); - } - if ('IMG' === $tag) { - return self::create_image_block_from_img($child); - } - if (\in_array($tag, array('OL', 'UL'), \true)) { - return self::create_list_block_from_element($child); - } - if (self::is_card_label_span($child)) { - return self::create_inline_span_label_paragraph($child); - } - if ('P' === $tag || 'SPAN' === $tag) { - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', \array_merge(self::get_block_support_attributes($child, array('anchor' => \true, 'class_name' => \true)), array('content' => $child->get_inner_html()))); - } - if ('A' === $tag && self::is_button_like_anchor($child)) { - return HTML_To_Blocks_Block_Factory::create_block('core/buttons', array(), array(self::create_button_block_from_anchor($child))); - } - return null; - } - /** - * Checks whether a card child span is a visual label that should keep its tag. - * - * @param HTML_To_Blocks_HTML_Element $child Card child element. - * @return bool True when the span should remain source HTML. - */ - private static function is_card_label_span($child): bool - { - if ('SPAN' !== $child->get_tag_name() || !$child->has_attribute('class')) { - return \false; - } - if (self::class_matches($child, '/(?:^|[-_\s])(?:card[-_\s]?number|item[-_\s]?number|service[-_\s]?number)(?:$|[-_\s])/i')) { - return \preg_match('/^\s*\d{1,3}\s*$/', $child->get_text_content()) === 1; - } - return self::class_matches($child, '/(?:^|\s)tag(?:$|\s)/i'); - } - /** - * Gets a single whole-card anchor child when it is the card's only content. - * - * @param HTML_To_Blocks_HTML_Element $element The card item. - * @return HTML_To_Blocks_HTML_Element|null Anchor child or null. - */ - private static function get_single_complex_card_anchor_child($element, ?array $children = null): ?HTML_To_Blocks_HTML_Element - { - $children = \array_values(\array_filter($children ?? $element->get_child_elements(), function ($child) { - return !self::is_empty_decorative_element($child); - })); - if (\count($children) !== 1 || 'A' !== $children[0]->get_tag_name() || array() === $children[0]->get_child_elements()) { - return null; - } - $remaining = \str_replace($children[0]->get_outer_html(), '', $element->get_inner_html()); - return \trim(wp_strip_all_tags($remaining)) === '' ? $children[0] : null; - } - /** - * Creates a compact CTA anchor from a linked card wrapper. - * - * @param HTML_To_Blocks_HTML_Element $anchor The source card anchor. - * @return HTML_To_Blocks_HTML_Element Synthetic CTA anchor. - */ - private static function create_card_grid_cta_anchor($anchor): HTML_To_Blocks_HTML_Element - { - $text = self::get_card_grid_link_label($anchor); - $attrs = array('href' => $anchor->get_attribute('href') ?? '', 'class' => self::merge_class_names($anchor->get_attribute('class') ?? '', 'card-grid-link')); - foreach (array('target', 'rel') as $attribute) { - if ($anchor->has_attribute($attribute)) { - $attrs[$attribute] = $anchor->get_attribute($attribute); - } - } - return new HTML_To_Blocks_HTML_Element('a', $attrs, '', esc_html($text)); - } - /** - * Gets a stable link label for a whole-card CTA. - * - * @param HTML_To_Blocks_HTML_Element $anchor The source card anchor. - * @return string Link label. - */ - private static function get_card_grid_link_label($anchor): string - { - foreach (array('h1', 'h2', 'h3', 'h4', 'h5', 'h6') as $selector) { - $heading = $anchor->query_selector($selector); - if ($heading && \trim($heading->get_text_content()) !== '') { - return \trim($heading->get_text_content()); - } - } - $text = \trim($anchor->get_text_content()); - return '' !== $text ? $text : 'Read more'; - } - /** - * Checks whether an element is an image-only wrapper that should stay grouped. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the wrapper should become core/group with core/image. - */ - private static function is_image_wrapper_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - $images = $element->query_selector_all('img'); - if (\count($images) !== 1 || self::get_media_src($images[0]) === '') { - return \false; - } - $remaining = \str_replace($images[0]->get_outer_html(), '', $element->get_inner_html()); - return \trim(wp_strip_all_tags($remaining)) === ''; - } - /** - * Checks whether an element is nav/logo chrome with removable visual-only children. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when decorative child placeholders should be ignored. - */ - private static function is_nav_logo_chrome_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - if (!self::class_matches($element, '/(?:^|[-_\s])(?:nav[-_\s]?logo|logo|brand)(?:$|[-_\s])/i')) { - return \false; - } - $has_decorative_child = \false; - foreach ($element->get_child_elements() as $child) { - if (self::is_empty_decorative_element($child)) { - $has_decorative_child = \true; - continue; - } - if (!\in_array($child->get_tag_name(), array('A', 'P', 'SPAN', 'STRONG', 'B', 'EM', 'I'), \true)) { - return \false; - } - } - return $has_decorative_child && '' !== \trim(wp_strip_all_tags($element->get_inner_html())); - } - /** - * Checks whether a wrapper is a horizontal inline scroller/marquee track. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when direct inline children should stay separate blocks. - */ - private static function is_inline_scroller_element($element): bool - { - if ('DIV' !== $element->get_tag_name() || !self::class_matches($element, '/(?:^|[-_\s])(?:marquee|scroller|scroll|ticker|track)(?:$|[-_\s])/i')) { - return \false; - } - $children = $element->get_child_elements(); - if (\count($children) < 2) { - return \false; - } - foreach ($children as $child) { - if ('SPAN' !== $child->get_tag_name() || !$child->has_attribute('class') || \trim($child->get_text_content()) === '') { - return \false; - } - foreach ($child->get_child_elements() as $grandchild) { - if (!self::is_empty_decorative_element($grandchild)) { - return \false; - } - } - } - return \true; - } - /** - * Creates child blocks for direct inline marquee/scroller items. - * - * @param HTML_To_Blocks_HTML_Element $element Source scroller wrapper. - * @param callable $handler Recursive conversion handler. - * @return array Converted child blocks. - */ - private static function create_inline_scroller_child_blocks($element, callable $handler): array - { - $inner_blocks = array(); - foreach ($element->get_child_elements() as $child) { - if (array() !== $child->get_child_elements()) { - $inner_blocks = \array_merge($inner_blocks, $handler(array('HTML' => $child->get_outer_html()))); - continue; - } - $attributes = self::get_block_support_attributes($child, array('anchor' => \true, 'class_name' => \true)); - $attributes['content'] = $child->get_inner_html(); - $inner_blocks[] = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - } - return $inner_blocks; - } - /** - * Checks whether an empty element is safe to preserve as native visual chrome. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element is empty decorative layout chrome. - */ - private static function is_empty_decorative_element($element): bool - { - return self::is_empty_element($element) && (self::is_project_card_status_element($element) || self::class_matches($element, '/(?:^|[-_\s])(background|bg|pattern|texture|divider|separator|connector|rule|ruler|line|blank|overlay|grain|noise|glow|gradient|scan|dot|mark|bullet|icon|orb|blob|fill|img|image|media|photo|picture|thumb|patch|progress|meter|gauge|today|traffic[-_]?light|tl[-_]?(?:red|yellow|green)|task[-_\s]?check)(?:$|[-_\s]|\d)/i') || self::has_visual_placeholder_background($element)); - } - /** - * Checks whether a figure wraps a decorative visual placeholder and caption. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the figure can become an editable group with caption. - */ - private static function is_decorative_figure_with_caption($element): bool - { - if ('FIGURE' !== $element->get_tag_name()) { - return \false; - } - $children = $element->get_child_elements(); - if (\count($children) < 1 || \count($children) > 2) { - return \false; - } - $caption = \end($children); - if ('FIGCAPTION' !== $caption->get_tag_name() || \trim(wp_strip_all_tags($caption->get_inner_html())) === '') { - return \false; - } - return \count($children) === 1 || self::is_empty_decorative_element($children[0]) || self::is_nested_empty_decorative_element($children[0]); - } - /** - * Checks whether an element and its descendants are inert decorative chrome. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the subtree has no meaningful content or controls. - */ - private static function is_nested_empty_decorative_element($element): bool - { - if (!$element->has_attribute('aria-hidden') || 'true' !== \strtolower($element->get_attribute('aria-hidden'))) { - return \false; - } - if (\trim(wp_strip_all_tags($element->get_inner_html())) !== '') { - return \false; - } - foreach ($element->get_child_elements() as $child) { - if (self::is_functional_element_or_descendant($child)) { - return \false; - } - } - return !empty($element->get_child_elements()); - } - /** - * Checks whether an element subtree contains controls, media, or embeds. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when the subtree contains functional markup. - */ - private static function is_functional_element_or_descendant($element): bool - { - if (\in_array($element->get_tag_name(), array('A', 'BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'IMG', 'VIDEO', 'AUDIO', 'IFRAME', 'OBJECT', 'EMBED', 'SVG'), \true)) { - return \true; - } - foreach ($element->get_child_elements() as $child) { - if (self::is_functional_element_or_descendant($child)) { - return \true; - } - } - return \false; - } - /** - * Checks whether an empty element carries visual background styling. - * - * @param HTML_To_Blocks_HTML_Element $element Source element. - * @return bool True when source style provides a native block background. - */ - private static function has_visual_placeholder_background($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - if ('' === $style || \preg_match('/url\s*\(/i', $style) === 1) { - return \false; - } - return self::extract_background_color($style) !== ''; - } - /** - * Checks whether an element is a project-card status chip/dot. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the classes match the project-card status pattern. - */ - private static function is_project_card_status_element($element): bool - { - return 'DIV' === $element->get_tag_name() && self::class_matches($element, '/(?:^|\s)pcard-status(?:\s|$)/i') && self::class_matches($element, '/(?:^|\s)status-(?:done|active|warn|idle)(?:\s|$)/i'); - } - /** - * Checks whether an element has no meaningful text or child elements. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element is empty layout chrome. - */ - private static function is_empty_element($element): bool - { - return \trim(wp_strip_all_tags($element->get_inner_html())) === '' && array() === $element->get_child_elements(); - } - /** - * Checks whether an element is a cover/hero wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element should become core/cover. - */ - private static function is_cover_element($element): bool - { - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - if ('' === $style) { - return \false; - } - $has_background_image = \preg_match('/background-image:\s*url\(/i', $style) === 1; - $has_background_color = self::extract_background_color($style) !== ''; - $is_hero_like = self::class_matches($element, '/(?:^|[-_\s])(hero|cover|banner|masthead)(?:$|[-_\s])/i'); - if (!$has_background_image && !$has_background_color) { - return \false; - } - return $is_hero_like || $has_background_image && $element->get_tag_name() === 'SECTION'; - } - /** - * Checks whether an element is a columns wrapper. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element should become core/columns. - */ - private static function is_columns_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SECTION'), \true)) { - return \false; - } - if (!self::class_matches($element, '/(?:^|[-_\s])(columns|row|grid|flex)(?:$|[-_\s])/i')) { - return \false; - } - $column_count = 0; - foreach ($element->get_child_elements() as $child) { - if (!self::is_column_element($child)) { - return \false; - } - ++$column_count; - } - return $column_count >= 2; - } - /** - * Checks whether an element is an individual column. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element should become core/column. - */ - private static function is_column_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SECTION', 'ARTICLE', 'ASIDE'), \true)) { - return \false; - } - return self::class_matches($element, '/(?:^|[-_\s])(column|col|cell)(?:$|[-_\s]|\d)/i'); - } - /** - * Checks whether an element is an empty explicit spacer. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the element should become core/spacer. - */ - private static function is_spacer_element($element): bool - { - if (!\in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true)) { - return \false; - } - if (\trim(wp_strip_all_tags($element->get_inner_html())) !== '') { - return \false; - } - return self::class_matches($element, '/(?:^|[-_\s])(spacer|gap|separator-space)(?:$|[-_\s])/i') && self::extract_height_value($element) !== ''; - } - /** - * Checks a source element class attribute against a pattern. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @param string $pattern Regex pattern. - * @return bool True when the class matches. - */ - private static function class_matches($element, string $pattern): bool - { - $class_name = $element->has_attribute('class') ? $element->get_attribute('class') : ''; - return '' !== $class_name && \preg_match($pattern, $class_name) === 1; - } - /** - * Extracts an explicit height CSS value. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return string CSS height value or empty string. - */ - private static function extract_height_value($element): string - { - $style = $element->has_attribute('style') ? $element->get_attribute('style') : ''; - if (\preg_match('/(?:^|;)\s*height:\s*([^;]+)/i', $style, $matches)) { - return \trim($matches[1]); - } - return ''; - } - /** - * Extracts a background-color CSS value. - * - * @param string $style CSS style attribute. - * @return string CSS color value or empty string. - */ - private static function extract_background_color(string $style): string - { - if (\preg_match('/(?:^|;)\s*background(?:-color)?:\s*(?![^;]*url\()([^;]+)/i', $style, $matches)) { - return \trim($matches[1]); - } - return ''; - } - /** - * core/paragraph transforms - p/address/a elements, visual labels, and text-only wrappers (lowest priority, fallback) - * - * @return array Transform definitions - */ - private static function get_paragraph_transforms() - { - return array(array('blockName' => 'core/paragraph', 'priority' => 20, 'selector' => 'p,address,a,label,div,span,strong,em', 'isMatch' => function ($element) { - if (\in_array($element->get_tag_name(), array('P', 'ADDRESS', 'A'), \true)) { - return \true; - } - if (self::is_inline_span_label($element)) { - return \true; - } - if (self::is_static_visual_label($element)) { - return \true; - } - if ('SPAN' === $element->get_tag_name() && $element->has_attribute('class')) { - return \false; - } - if (\in_array($element->get_tag_name(), array('STRONG', 'EM'), \true)) { - return array() === $element->get_child_elements() && \trim($element->get_text_content()) !== ''; - } - return \in_array($element->get_tag_name(), array('DIV', 'SPAN'), \true) && array() === $element->get_child_elements() && \trim($element->get_text_content()) !== ''; - }, 'transform' => function ($element) { - if (self::is_inline_span_label($element)) { - return self::create_inline_span_label_paragraph($element); - } - $content = $element->get_tag_name() === 'A' ? self::get_paragraph_anchor_content($element) : $element->get_inner_html(); - if (\in_array($element->get_tag_name(), array('STRONG', 'EM'), \true)) { - $content = \trim($element->get_outer_html()); - } - if (self::is_static_checkbox_label($element)) { - $content = \trim(\preg_replace('/<\s*input\b[^>]*>/i', '', $content)); - } - $attributes = self::get_block_support_attributes($element, array('anchor' => \true, 'align' => \true, 'text_align' => \true, 'colors' => \true, 'typography' => \true, 'spacing' => \true, 'border' => \true, 'class_name' => \true)); - if ($element->get_tag_name() === 'A') { - unset($attributes['className']); - } - $attributes['content'] = $content; - return HTML_To_Blocks_Block_Factory::create_block('core/paragraph', $attributes); - })); - } - /** - * Gets paragraph content for a standalone anchor element. - * - * @param HTML_To_Blocks_HTML_Element $anchor Anchor element. - * @return string Paragraph content with link-owned attributes preserved. - */ - private static function get_paragraph_anchor_content($anchor): string - { - $html = $anchor->get_outer_html(); - $class = $anchor->has_attribute('class') ? (string) $anchor->get_attribute('class') : ''; - if (\preg_match('/(^|[-_\s])(brand|logo)([-_\s]|$)/i', $class)) { - $html = \preg_replace('/<\s*div\b([^>]*)>/i', '', $html) ?? $html; - $html = \preg_replace('/<\s*\/\s*div\s*>/i', '', $html) ?? $html; - } - return $html; - } - /** - * Checks whether a label is static visual UI text rather than a form label. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when the label can safely become editable paragraph text. - */ - private static function is_static_visual_label($element): bool - { - if ('LABEL' !== $element->get_tag_name()) { - return \false; - } - if ($element->has_attribute('for') || $element->has_attribute('form')) { - return \false; - } - $inner_html = $element->get_inner_html(); - if (\trim(wp_strip_all_tags($inner_html)) === '') { - return \false; - } - if (\preg_match('/<\s*(?:select|textarea|button|output|meter|progress)\b/i', $inner_html) === 1) { - return \false; - } - if (\preg_match('/<\s*input\b/i', $inner_html) !== 1) { - return \true; - } - return self::is_static_checkbox_label($element); - } - /** - * Checks whether a label contains only decorative checkbox inputs plus visible text. - * - * @param HTML_To_Blocks_HTML_Element $element The source element. - * @return bool True when checkbox inputs can be dropped and text preserved. - */ - private static function is_static_checkbox_label($element): bool - { - if ('LABEL' !== $element->get_tag_name() || $element->has_attribute('for') || $element->has_attribute('form')) { - return \false; - } - $inner_html = $element->get_inner_html(); - if (\trim(wp_strip_all_tags($inner_html)) === '') { - return \false; - } - if (\preg_match_all('/<\s*input\b[^>]*>/i', $inner_html, $matches) < 1) { - return \false; - } - foreach ($matches[0] as $input_html) { - $type = ''; - if (\preg_match('/\stype\s*=\s*(["\']?)([^"\'\s>]+)\1/i', $input_html, $type_match) === 1) { - $type = \strtolower($type_match[2]); - } - if ('checkbox' !== $type) { - return \false; - } - if (\preg_match('/\s(?:id|name|value|form|required|on[a-z0-9_-]*)\b/i', $input_html) === 1) { - return \false; - } - } - return \true; - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/hooks.php b/vendor_prefixed/chubes4/html-to-blocks-converter/includes/hooks.php deleted file mode 100644 index 55b34e1..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/includes/hooks.php +++ /dev/null @@ -1,193 +0,0 @@ - \true, 'public' => \true))); - $supported_types = apply_filters('html_to_blocks_supported_post_types', $default_types); - foreach ($supported_types as $post_type) { - if (!\function_exists('has_filter') || \false === \has_filter("rest_prepare_{$post_type}", $convert_rest_callback)) { - // @phpstan-ignore-next-line WordPress accepts callable strings resolved at runtime. - \add_filter("rest_prepare_{$post_type}", $convert_rest_callback, 10, 3); - } - } - } -} -// Priority 20: run after plugins have registered custom post types at the -// default init priority (10), so get_post_types() sees the full REST surface. -if (\function_exists('add_action') && (!\function_exists('has_action') || \false === \has_action('init', $html_to_blocks_register_rest_callback))) { - // @phpstan-ignore-next-line WordPress accepts callable strings resolved at runtime. - \add_action('init', $html_to_blocks_register_rest_callback, 20); -} -/** - * Convert HTML to blocks in REST API responses. - * - * Only converts content.raw when the request has edit context (i.e. the - * block editor is loading the post). Frontend REST requests are untouched. - * - * @param WP_REST_Response $response The response object. - * @param WP_Post $post The post object. - * @param WP_REST_Request $request The request object. - * @return WP_REST_Response Modified response. - */ -if (!\function_exists($html_to_blocks_convert_rest_callback)) { - function html_to_blocks_convert_rest_response($response, $post, $request) - { - // Only convert for edit context (block editor). - if ($request->get_param('context') !== 'edit') { - return $response; - } - $data = $response->get_data(); - if (empty($data['content']['raw'])) { - return $response; - } - $raw = $data['content']['raw']; - // Already block markup — nothing to do. - if (\strpos($raw, '', 'expected_names' => array('core/group', 'core/image'), 'snippets' => array('nav-logo-mark', 'theme-part-header-1-b57d7c946b676f82.svg')), 'site-editor-landmark' => array('html' => '

    Site Editor Template Smoke

    Template raw HTML should become blocks.

    ', 'expected_names' => array('core/group', 'core/group', 'core/heading', 'core/paragraph'), 'snippets' => array('site-shell', 'hero', 'Site Editor Template Smoke', 'Template raw HTML should become blocks.')), 'plain-hero-section' => array('html' => '

    Smoke Test Storefront

    Synthetic fixture for iterator smoke runs.

    ', 'expected_names' => array('core/group', 'core/heading', 'core/paragraph'), 'snippets' => array('hero', 'Smoke Test Storefront', 'Synthetic fixture for iterator smoke runs.')), 'static-placeholder-form' => array('html' => '
    ', 'expected_names' => array('core/group', 'core/paragraph', 'core/paragraph', 'core/paragraph', 'core/paragraph', 'core/buttons', 'core/button'), 'snippets' => array('intake-form', 'Parent or guardian name', 'Email', 'Upcoming meeting date', 'What do you need help with?', 'Request consultation details')), 'columns' => array('html' => '

    Left

    Right

    ', 'expected_names' => array('core/columns', 'core/column', 'core/paragraph', 'core/column', 'core/paragraph'), 'snippets' => array('Left', 'Right')), 'cover' => array('html' => '

    Cover text

    ', 'expected_names' => array('core/cover', 'core/paragraph'), 'snippets' => array('Cover text')), 'spacer' => array('html' => '', 'expected_names' => array('core/spacer')), 'buttons' => array('html' => 'Click', 'expected_names' => array('core/buttons', 'core/button'), 'snippets' => array('https://example.com', 'Click')), 'hero-actions' => array('html' => '', 'expected_names' => array('core/buttons', 'core/button', 'core/button'), 'snippets' => array('hero-actions', '/early-access/', 'btn btn-primary', 'Get Early Access', '/proof/', 'btn btn-ghost', 'See the Proof')), 'cta-actions' => array('html' => '', 'expected_names' => array('core/buttons', 'core/button', 'core/button'), 'snippets' => array('cta-actions', '/signup/', 'cta-primary', 'Start Now', '/assets/arrow.svg', 'materialized-icon', '/contact/', 'cta-secondary', 'Contact Us')), 'details' => array('html' => '
    Question

    Answer

    ', 'expected_names' => array('core/details', 'core/paragraph'), 'snippets' => array('Question', 'Answer')), 'pullquote' => array('html' => '

    Pull this

    Citation
    ', 'expected_names' => array('core/pullquote'), 'snippets' => array('Pull this', 'Citation')), 'verse' => array('html' => '
    Line one\nLine two
    ', 'expected_names' => array('core/verse'), 'snippets' => array('Line one', 'Line two')), 'video' => array('html' => '', 'expected_names' => array('core/video'), 'snippets' => array('movie.mp4', 'poster.jpg')), 'audio' => array('html' => '', 'expected_names' => array('core/audio'), 'snippets' => array('clip.mp3')), 'gallery' => array('html' => '', 'expected_names' => array('core/gallery', 'core/image', 'core/image'), 'snippets' => array('Caption A', 'b.jpg')), 'media-text' => array('html' => '
    Hero

    Media copy

    ', 'expected_names' => array('core/media-text', 'core/paragraph'), 'snippets' => array('hero.jpg', 'Media copy')), 'file' => array('html' => 'Download report', 'expected_names' => array('core/file'), 'snippets' => array('report.pdf', 'Download report')), 'recognized-embed' => array('html' => '', 'expected_names' => array('core/embed'), 'snippets' => array('youtube.com/watch?v=abc123'))); - } - /** - * Unsupported fixture matrix. - * - * @return array - */ - private function unsupported_fixtures(): array - { - return array('unknown-iframe-provider' => array('html' => '', 'expected_names' => array('core/html'), 'fallback_tag' => 'IFRAME', 'snippet' => 'example.com/widget'), 'custom-element' => array('html' => 'Custom payload', 'expected_names' => array('core/html'), 'fallback_tag' => 'X-CARD', 'snippet' => 'Custom payload'), 'current-url-form' => array('html' => '
    ', 'expected_names' => array('core/html'), 'fallback_tag' => 'FORM', 'snippet' => 'intake-form'), 'app-widget' => array('html' => '
    AAPL
    ', 'expected_names' => array('core/html'), 'fallback_tag' => 'DIV', 'snippet' => 'stock-ticker')); - } - /** - * Flatten block names recursively. - * - * @param array> $blocks Blocks. - * @return string[] Block names. - */ - private function flatten_block_names(array $blocks): array - { - $names = array(); - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? null; - if (!empty($block['innerBlocks'])) { - $names = \array_merge($names, $this->flatten_block_names($block['innerBlocks'])); - } - } - return $names; - } - /** - * Collect block HTML/content recursively for fixture assertions. - * - * @param array> $blocks Blocks. - * @return string Combined HTML/content. - */ - private function combined_block_html(array $blocks): string - { - $combined = ''; - foreach ($blocks as $block) { - foreach (array('innerHTML', 'content') as $key) { - if (isset($block[$key]) && \is_string($block[$key])) { - $combined .= "\n" . $block[$key]; - } - } - foreach ($block['attrs'] ?? array() as $value) { - if (\is_string($value)) { - $combined .= "\n" . $value; - } - } - if (!empty($block['innerBlocks'])) { - $combined .= $this->combined_block_html($block['innerBlocks']); - } - } - return $combined; - } -} diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/core-block-coverage-docs-smoke.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/core-block-coverage-docs-smoke.php deleted file mode 100644 index 6a2f0bf..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/core-block-coverage-docs-smoke.php +++ /dev/null @@ -1,172 +0,0 @@ -get_contents($path); - $assert(\is_string($contents) && '' !== $contents, \basename($path) . '-readable', 'Unable to read ' . $path); - return \is_string($contents) ? $contents : ''; -}; -$read_json = static function (string $path) use ($read_file, $assert): array { - $raw = $read_file($path); - $data = \json_decode($raw, \true); - $assert(\is_array($data), \basename($path) . '-valid-json', 'Invalid JSON in ' . $path); - return \is_array($data) ? $data : []; -}; -$blocks_dir = \getenv('H2BC_CORE_BLOCKS_DIR'); -$has_blocks_dir = \is_string($blocks_dir) && \is_dir($blocks_dir); -$inventory = []; -if ($has_blocks_dir) { - $command = \escapeshellarg(\PHP_BINARY) . ' ' . \escapeshellarg($repo_root . '/tools/generate-core-block-inventory.php') . ' ' . \escapeshellarg($blocks_dir); - $output = []; - $exit = 0; - \exec($command, $output, $exit); - $assert(0 === $exit, 'core-block-inventory-generator-runs', \implode("\n", $output)); - $generated_inventory = \json_decode(\implode("\n", $output), \true); - $assert(\is_array($generated_inventory), 'core-block-inventory-generator-json'); - $inventory = \is_array($generated_inventory) ? $generated_inventory : []; -} -$classification = $read_json($repo_root . '/docs/core-block-classification.json'); -$coverage_doc = $read_file($repo_root . '/docs/core-block-coverage.md'); -$registry_source = $read_file($repo_root . '/includes/class-transform-registry.php'); -$raw_source = $read_file($repo_root . '/raw-handler.php'); -$inventory_blocks = []; -foreach ((array) ($inventory['blocks'] ?? []) as $block) { - $name = $block['name'] ?? ''; - if (\is_string($name) && '' !== $name) { - $inventory_blocks[$name] = \true; - } -} -if ($has_blocks_dir) { - $assert(\count($inventory_blocks) > 50, 'inventory-has-core-block-catalog', 'Expected more than 50 core blocks'); -} -$classifications = (array) ($classification['classifications'] ?? []); -$doc_rows = []; -$doc_patterns = []; -foreach (\preg_split("/\r?\n/", $coverage_doc) ? \preg_split("/\r?\n/", $coverage_doc) : [] as $line) { - $trimmed = \trim($line); - if (\strpos($trimmed, '|') !== 0 || \strpos($trimmed, '---') !== \false) { - continue; - } - $cells = \array_map('trim', \explode('|', \trim($trimmed, '|'))); - if (\count($cells) < 3) { - continue; - } - $cells = \array_pad($cells, 5, ''); - $row = ['block' => $cells[0], 'status' => \trim((string) $cells[1], '` '), 'signal' => $cells[2], 'test' => $cells[3], 'notes' => $cells[4], 'line' => $trimmed]; - if (\preg_match_all('/`(core\/[a-z0-9-]+\*?)`/', $row['line'], $matches)) { - foreach ($matches[1] as $pattern) { - $doc_patterns[] = ['pattern' => $pattern, 'row' => $row]; - } - } - $doc_rows[] = $row; -} -$matches_pattern = static function (string $block_name, string $pattern): bool { - if (\substr($pattern, -1) === '*') { - return \strpos($block_name, \substr($pattern, 0, -1)) === 0; - } - return $block_name === $pattern; -}; -$rows_for_block = static function (string $block_name) use ($doc_patterns, $matches_pattern): array { - $rows = []; - foreach ($doc_patterns as $entry) { - if ($matches_pattern($block_name, $entry['pattern'])) { - $rows[] = $entry['row']; - } - } - return $rows; -}; -$raw_transform_blocks = []; -\preg_match_all("/'blockName'\\s*=>\\s*'([^']+)'/", $registry_source, $matches); -foreach ($matches[1] as $block_name) { - $raw_transform_blocks[$block_name] = \true; -} -$generated_blocks = []; -\preg_match_all("/create_block\\(\\s*'([^']+)'/", $registry_source . "\n" . $raw_source, $matches); -foreach ($matches[1] as $block_name) { - $generated_blocks[$block_name] = \true; -} -$bucket_counts = []; -foreach ($classifications as $block_name => $entry) { - $bucket = (string) ($entry['bucket'] ?? ''); - $bucket_counts[$bucket] = ($bucket_counts[$bucket] ?? 0) + 1; - $rows = $rows_for_block($block_name); - $assert(!empty($rows), 'coverage-doc-covers-' . $block_name, $bucket); - if (\in_array($bucket, ['raw-transformable', 'explicit-marker'], \true)) { - $has_source = isset($raw_transform_blocks[$block_name]) || isset($generated_blocks[$block_name]); - $assert($has_source, 'transform-source-for-' . $block_name, $bucket); - } - if (\in_array($bucket, ['compiler-only', 'dynamic-unsupported'], \true)) { - $has_rationale = \false; - foreach ($rows as $row) { - $text = \strtolower(wp_strip_all_tags($row['signal'] . ' ' . $row['notes'])); - if (\preg_match('/\b(require|requires|depend|depends|intent|context|state|runtime|metadata|query|taxonomy|route|identity|permission)\b/', $text)) { - $has_rationale = \true; - break; - } - } - $assert($has_rationale, 'coverage-rationale-for-' . $block_name, $bucket); - } - if ('future-candidate' === $bucket) { - $has_source_signal_note = \false; - foreach ($rows as $row) { - $text = \strtolower(wp_strip_all_tags($row['signal'] . ' ' . $row['notes'])); - if (\strpos($text, 'source-signal') !== \false || \strpos($text, 'source signal') !== \false || \strpos($text, 'explicit') !== \false || \strpos($text, 'stable') !== \false) { - $has_source_signal_note = \true; - break; - } - } - $assert($has_source_signal_note, 'future-candidate-source-signal-' . $block_name); - } -} -if ($has_blocks_dir) { - foreach ($inventory_blocks as $block_name => $_) { - $assert(!empty($rows_for_block($block_name)), 'inventory-block-documented-' . $block_name); - } -} -foreach ($doc_patterns as $entry) { - $status = $entry['row']['status']; - if (!\in_array($status, ['supported', 'explicit-marker supported'], \true)) { - continue; - } - $matched_blocks = \array_filter(\array_keys($classifications), static function (string $block_name) use ($entry, $matches_pattern): bool { - return $matches_pattern($block_name, $entry['pattern']); - }); - $assert(!empty($matched_blocks), 'supported-doc-pattern-matches-classification-' . $entry['pattern']); - foreach ($matched_blocks as $block_name) { - $has_source = isset($raw_transform_blocks[$block_name]) || isset($generated_blocks[$block_name]); - $assert($has_source, 'supported-doc-has-transform-source-' . $block_name, $entry['pattern']); - } -} -echo 'Assertions: ' . $assertions . \PHP_EOL; -echo 'Inventory blocks: ' . \count($inventory_blocks) . \PHP_EOL; -echo 'Coverage rows: ' . \count($doc_rows) . \PHP_EOL; -echo 'Classification counts:' . \PHP_EOL; -foreach ($bucket_counts as $bucket => $count) { - echo ' - ' . $bucket . ': ' . $count . \PHP_EOL; -} -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-action-text-transforms.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-action-text-transforms.php deleted file mode 100644 index 5328db7..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-action-text-transforms.php +++ /dev/null @@ -1,311 +0,0 @@ - 'string']); - return (object) ['attributes' => $attributes]; - } -} -\class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -require_once \dirname(__DIR__) . '/includes/class-html-element.php'; -require_once \dirname(__DIR__) . '/includes/class-block-factory.php'; -require_once \dirname(__DIR__) . '/includes/class-transform-registry.php'; -$failures = []; -$assertions = 0; -$smoke_assert = function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$find_transform = function ($element) { - foreach (HTML_To_Blocks_Transform_Registry::get_raw_transforms() as $transform) { - try { - $is_match = \call_user_func($transform['isMatch'], $element); - } catch (\Throwable $e) { - $is_match = \false; - } - if ($is_match) { - return $transform; - } - } - return null; -}; -$handler = function ($args) { - return [HTML_To_Blocks_Block_Factory::create_block('core/paragraph', ['content' => \trim($args['HTML'] ?? '')])]; -}; -// ------------------------------------------------------------------------- -// Buttons: native WordPress button anchors become core/buttons > core/button. -// ------------------------------------------------------------------------- -$button_paragraph = new HTML_To_Blocks_HTML_Element('p', [], '

    Buy Now

    ', 'Buy Now'); -$button_transform = $find_transform($button_paragraph); -$button_block = \call_user_func($button_transform['transform'], $button_paragraph, $handler); -$smoke_assert('core/buttons' === $button_transform['blockName'], 'button-transform-selected'); -$smoke_assert('core/buttons' === $button_block['blockName'], 'button-wrapper-block-name'); -$smoke_assert(\count($button_block['innerBlocks']) === 1, 'button-wrapper-has-one-child'); -$smoke_assert('core/button' === $button_block['innerBlocks'][0]['blockName'], 'button-child-block-name'); -$smoke_assert('/buy' === $button_block['innerBlocks'][0]['attrs']['url'], 'button-url-preserved'); -$smoke_assert('_blank' === $button_block['innerBlocks'][0]['attrs']['linkTarget'], 'button-target-preserved'); -$smoke_assert('nofollow' === $button_block['innerBlocks'][0]['attrs']['rel'], 'button-rel-preserved'); -$smoke_assert(\strpos($button_block['innerBlocks'][0]['attrs']['className'], 'btn-primary') !== \false, 'button-class-preserved'); -$smoke_assert(\strpos($button_block['innerBlocks'][0]['innerHTML'], 'Buy Now') !== \false, 'button-rich-text-preserved'); -$button_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'hero-actions'], '', 'Read the Manifesto →See The Proof'); -$button_row_transform = $find_transform($button_row); -$button_row_block = \call_user_func($button_row_transform['transform'], $button_row, $handler); -$smoke_assert('core/buttons' === $button_row_transform['blockName'], 'button-row-transform-selected'); -$smoke_assert('core/buttons' === $button_row_block['blockName'], 'button-row-wrapper-block-name'); -$smoke_assert(\count($button_row_block['innerBlocks']) === 2, 'button-row-has-two-children'); -$smoke_assert('core/button' === $button_row_block['innerBlocks'][0]['blockName'], 'button-row-first-child-block-name'); -$smoke_assert('core/button' === $button_row_block['innerBlocks'][1]['blockName'], 'button-row-second-child-block-name'); -$smoke_assert('/manifesto/' === $button_row_block['innerBlocks'][0]['attrs']['url'], 'button-row-first-url-preserved'); -$smoke_assert('/proof/' === $button_row_block['innerBlocks'][1]['attrs']['url'], 'button-row-second-url-preserved'); -$smoke_assert(\strpos($button_row_block['innerBlocks'][0]['innerHTML'], 'href="/proof/"') === \false, 'button-row-first-child-does-not-contain-second-anchor'); -$custom_button_paragraph = new HTML_To_Blocks_HTML_Element('p', [], '

    Order Online

    ', 'Order Online'); -$custom_button_transform = $find_transform($custom_button_paragraph); -$custom_button_block = \call_user_func($custom_button_transform['transform'], $custom_button_paragraph, $handler); -$smoke_assert('core/buttons' === $custom_button_transform['blockName'], 'custom-button-anchor-becomes-buttons'); -$smoke_assert(\count($custom_button_block['innerBlocks']) === 1, 'custom-button-anchor-has-one-button'); -$smoke_assert('core/button' === $custom_button_block['innerBlocks'][0]['blockName'], 'custom-button-anchor-child-block-name'); -$smoke_assert('#order' === $custom_button_block['innerBlocks'][0]['attrs']['url'], 'custom-button-anchor-url-preserved'); -$smoke_assert('btn btn-primary' === $custom_button_block['innerBlocks'][0]['attrs']['className'], 'custom-button-anchor-class-preserved'); -$nav_cta_paragraph = new HTML_To_Blocks_HTML_Element('p', [], '

    Request Access

    ', 'Request Access'); -$nav_cta_transform = $find_transform($nav_cta_paragraph); -$nav_cta_block = \call_user_func($nav_cta_transform['transform'], $nav_cta_paragraph, $handler); -$smoke_assert('core/buttons' === $nav_cta_transform['blockName'], 'nav-cta-button-anchor-becomes-buttons'); -$smoke_assert(\count($nav_cta_block['innerBlocks']) === 1, 'nav-cta-button-anchor-has-one-button'); -$smoke_assert('#try' === $nav_cta_block['innerBlocks'][0]['attrs']['url'], 'nav-cta-button-anchor-url-preserved'); -$smoke_assert('btn nav-cta' === $nav_cta_block['innerBlocks'][0]['attrs']['className'], 'nav-cta-button-anchor-class-preserved'); -$custom_button_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'hero-actions'], '', 'Order OnlineSee Our Bakes'); -$custom_button_row_transform = $find_transform($custom_button_row); -$custom_button_row_block = \call_user_func($custom_button_row_transform['transform'], $custom_button_row, $handler); -$smoke_assert('core/buttons' === $custom_button_row_transform['blockName'], 'custom-button-row-becomes-buttons'); -$smoke_assert('core/buttons' === $custom_button_row_block['blockName'], 'custom-button-row-block-name'); -$smoke_assert(\count($custom_button_row_block['innerBlocks']) === 2, 'custom-button-row-has-two-buttons'); -$smoke_assert('hero-actions' === $custom_button_row_block['attrs']['className'], 'custom-button-row-wrapper-class-preserved'); -$smoke_assert('#order' === $custom_button_row_block['innerBlocks'][0]['attrs']['url'], 'custom-button-row-first-url-preserved'); -$smoke_assert('#our-bakes' === $custom_button_row_block['innerBlocks'][1]['attrs']['url'], 'custom-button-row-second-url-preserved'); -$smoke_assert('btn btn-primary' === $custom_button_row_block['innerBlocks'][0]['attrs']['className'], 'custom-button-row-first-class-preserved'); -$smoke_assert('btn btn-ghost' === $custom_button_row_block['innerBlocks'][1]['attrs']['className'], 'custom-button-row-second-class-preserved'); -$custom_cta_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'cta-actions'], '', 'Get Early Access View Demo'); -$custom_cta_row_transform = $find_transform($custom_cta_row); -$custom_cta_row_block = \call_user_func($custom_cta_row_transform['transform'], $custom_cta_row, $handler); -$smoke_assert('core/buttons' === $custom_cta_row_transform['blockName'], 'custom-cta-row-becomes-buttons'); -$smoke_assert(\count($custom_cta_row_block['innerBlocks']) === 2, 'custom-cta-row-has-two-buttons'); -$smoke_assert('cta-actions' === $custom_cta_row_block['attrs']['className'], 'custom-cta-row-wrapper-class-preserved'); -$smoke_assert('/early-access/' === $custom_cta_row_block['innerBlocks'][0]['attrs']['url'], 'custom-cta-row-first-url-preserved'); -$smoke_assert('cta-primary' === $custom_cta_row_block['innerBlocks'][0]['attrs']['className'], 'custom-cta-row-first-class-preserved'); -$smoke_assert(\strpos($custom_cta_row_block['innerBlocks'][0]['attrs']['text'], 'Get Early Access') !== \false, 'custom-cta-row-text-preserved'); -$smoke_assert(\strpos($custom_cta_row_block['innerBlocks'][0]['attrs']['text'], '') !== \false, 'custom-cta-row-icon-image-preserved'); -$custom_cta_anchor = new HTML_To_Blocks_HTML_Element('a', ['href' => 'mailto:hello@saltandstar.com', 'class' => 'btn-cta'], 'Place an Order', 'Place an Order'); -$custom_cta_transform = $find_transform($custom_cta_anchor); -$custom_cta_block = \call_user_func($custom_cta_transform['transform'], $custom_cta_anchor, $handler); -$smoke_assert('core/paragraph' === $custom_cta_transform['blockName'], 'custom-cta-anchor-stays-paragraph'); -$smoke_assert(\strpos($custom_cta_block['attrs']['content'], 'Place an Order') !== \false, 'custom-cta-anchor-preserved'); -$smoke_assert(\strpos($custom_cta_block['attrs']['content'], 'wp-element-button') === \false, 'custom-cta-anchor-avoids-wp-button-class'); -$class_sensitive_cta_anchor = new HTML_To_Blocks_HTML_Element('a', ['class' => 'cta-btn', 'href' => '#install'], 'Install Now', 'Install Now'); -$class_sensitive_cta_transform = $find_transform($class_sensitive_cta_anchor); -$class_sensitive_cta_block = \call_user_func($class_sensitive_cta_transform['transform'], $class_sensitive_cta_anchor, $handler); -$smoke_assert('core/paragraph' === $class_sensitive_cta_transform['blockName'], 'class-sensitive-cta-anchor-stays-paragraph'); -$smoke_assert(\strpos($class_sensitive_cta_block['attrs']['content'], 'Install Now') !== \false, 'class-sensitive-cta-anchor-class-remains-on-link'); -$smoke_assert(\strpos($class_sensitive_cta_block['attrs']['content'], 'wp-block-button') === \false, 'class-sensitive-cta-anchor-avoids-button-wrapper'); -$class_sensitive_cta_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'cta-actions'], '', 'Browse the docs'); -$class_sensitive_cta_row_transform = $find_transform($class_sensitive_cta_row); -$smoke_assert('core/html' === $class_sensitive_cta_row_transform['blockName'], 'class-sensitive-cta-row-uses-html'); -$class_sensitive_cta_row_block = \call_user_func($class_sensitive_cta_row_transform['transform'], $class_sensitive_cta_row, $handler); -$smoke_assert(\strpos($class_sensitive_cta_row_block['attrs']['content'], '') !== \false, 'class-sensitive-cta-row-preserves-wrapper-html'); -$button_variant_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'hero-actions'], '', 'Find a classPlan your first visit'); -$button_variant_row_transform = $find_transform($button_variant_row); -$button_variant_row_block = \call_user_func($button_variant_row_transform['transform'], $button_variant_row, $handler); -$smoke_assert('core/buttons' === $button_variant_row_transform['blockName'], 'generic-button-variant-row-becomes-buttons'); -$smoke_assert('hero-actions' === $button_variant_row_block['attrs']['className'], 'generic-button-variant-row-wrapper-class-preserved'); -$smoke_assert('#classes' === $button_variant_row_block['innerBlocks'][0]['attrs']['url'], 'generic-button-variant-row-first-url-preserved'); -$smoke_assert('#first-visit' === $button_variant_row_block['innerBlocks'][1]['attrs']['url'], 'generic-button-variant-row-second-url-preserved'); -$cta_button_variant_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'hero-actions cta'], '', 'Find a classPlan your first visit'); -$cta_button_variant_row_transform = $find_transform($cta_button_variant_row); -$smoke_assert('core/buttons' !== $cta_button_variant_row_transform['blockName'], 'class-sensitive-button-variant-row-avoids-buttons'); -$ordinary_link = new HTML_To_Blocks_HTML_Element('p', [], '

    Read more.

    ', 'Read more.'); -$ordinary_link_transform = $find_transform($ordinary_link); -$smoke_assert('core/paragraph' === $ordinary_link_transform['blockName'], 'ordinary-link-stays-paragraph'); -$standalone_brand_anchor = new HTML_To_Blocks_HTML_Element('a', ['class' => 'brand', 'href' => '#top', 'aria-label' => 'Studio home'], 'Studio Tattoo', 'Studio Tattoo'); -$standalone_brand_anchor_transform = $find_transform($standalone_brand_anchor); -$standalone_brand_anchor_block = \call_user_func($standalone_brand_anchor_transform['transform'], $standalone_brand_anchor, $handler); -$smoke_assert('core/paragraph' === $standalone_brand_anchor_transform['blockName'], 'standalone-brand-anchor-becomes-paragraph'); -$smoke_assert(\strpos($standalone_brand_anchor_block['attrs']['content'], '') !== \false, 'standalone-brand-anchor-preserves-link-attributes'); -$smoke_assert(!isset($standalone_brand_anchor_block['attrs']['className']), 'standalone-brand-anchor-keeps-class-on-link-only'); -$smoke_assert(\strpos($standalone_brand_anchor_block['innerHTML'], '') === \false, 'standalone-brand-anchor-avoids-freeform'); -$static_button_tabs = new HTML_To_Blocks_HTML_Element('div', ['class' => 'use-case-tabs'], '
    ', ''); -$static_button_tabs_transform = $find_transform($static_button_tabs); -$static_button_tabs_block = \call_user_func($static_button_tabs_transform['transform'], $static_button_tabs, $handler); -$smoke_assert('core/group' === $static_button_tabs_transform['blockName'], 'static-button-tabs-become-group'); -$smoke_assert('use-case-tabs' === $static_button_tabs_block['attrs']['className'], 'static-button-tab-wrapper-class-preserved'); -$smoke_assert(\count($static_button_tabs_block['innerBlocks']) === 4, 'static-button-tabs-children-preserved'); -$smoke_assert('core/paragraph' === $static_button_tabs_block['innerBlocks'][0]['blockName'], 'static-button-tab-child-becomes-paragraph'); -$smoke_assert('Product Managers' === $static_button_tabs_block['innerBlocks'][0]['attrs']['content'], 'static-button-tab-label-preserved'); -$smoke_assert('use-case-tab active' === $static_button_tabs_block['innerBlocks'][0]['attrs']['className'], 'static-button-tab-class-preserved'); -$smoke_assert(\strpos($static_button_tabs_block['innerHTML'], '') === \false, 'static-button-tabs-avoid-wp-html'); -$static_chip_button = new HTML_To_Blocks_HTML_Element('button', ['class' => 'filter-chip active', 'type' => 'button'], '', 'Analytics'); -$static_chip_button_transform = $find_transform($static_chip_button); -$static_chip_button_block = \call_user_func($static_chip_button_transform['transform'], $static_chip_button); -$smoke_assert('core/paragraph' === $static_chip_button_transform['blockName'], 'static-chip-button-becomes-paragraph'); -$smoke_assert('Analytics' === $static_chip_button_block['attrs']['content'], 'static-chip-button-label-preserved'); -$smoke_assert('filter-chip active' === $static_chip_button_block['attrs']['className'], 'static-chip-button-class-preserved'); -$submit_button = new HTML_To_Blocks_HTML_Element('button', ['class' => 'use-case-tab', 'type' => 'submit'], '', 'Submit'); -$smoke_assert($find_transform($submit_button) === null, 'submit-button-falls-through'); -$form_owned_button = new HTML_To_Blocks_HTML_Element('button', ['class' => 'filter-chip', 'form' => 'filters'], '', 'Apply'); -$smoke_assert($find_transform($form_owned_button) === null, 'form-owned-button-falls-through'); -// ------------------------------------------------------------------------- -// Static visual buttons with inline JS handlers: handlers are dropped, label -// and class semantics survive as a native paragraph (no core/html fallback). -// Issue: https://github.com/chubes4/html-to-blocks-converter/issues/234 -// ------------------------------------------------------------------------- -$onclick_tab_button = new HTML_To_Blocks_HTML_Element('button', ['class' => 'tab-btn active', 'onclick' => "showDay('day1', this)"], '', 'Day 1 — Friday, Sept 18'); -$onclick_tab_button_transform = $find_transform($onclick_tab_button); -$onclick_tab_button_block = \call_user_func($onclick_tab_button_transform['transform'], $onclick_tab_button); -$smoke_assert('core/paragraph' === $onclick_tab_button_transform['blockName'], 'onclick-tab-button-becomes-paragraph'); -$smoke_assert('Day 1 — Friday, Sept 18' === $onclick_tab_button_block['attrs']['content'], 'onclick-tab-button-label-preserved'); -$smoke_assert('tab-btn active' === $onclick_tab_button_block['attrs']['className'], 'onclick-tab-button-class-preserved'); -$smoke_assert(\strpos($onclick_tab_button_block['innerHTML'], 'onclick') === \false, 'onclick-tab-button-strips-inline-handler'); -$smoke_assert(\strpos($onclick_tab_button_block['innerHTML'], 'showDay') === \false, 'onclick-tab-button-strips-handler-payload'); -$onclick_tab_row = new HTML_To_Blocks_HTML_Element('div', ['class' => 'tab-bar'], '
    ', ''); -$onclick_tab_row_transform = $find_transform($onclick_tab_row); -$onclick_tab_row_block = \call_user_func($onclick_tab_row_transform['transform'], $onclick_tab_row, $handler); -$smoke_assert('core/group' === $onclick_tab_row_transform['blockName'], 'onclick-tab-row-becomes-group'); -$smoke_assert('tab-bar' === $onclick_tab_row_block['attrs']['className'], 'onclick-tab-row-wrapper-class-preserved'); -$smoke_assert(\count($onclick_tab_row_block['innerBlocks']) === 2, 'onclick-tab-row-children-preserved'); -$smoke_assert('core/paragraph' === $onclick_tab_row_block['innerBlocks'][0]['blockName'], 'onclick-tab-row-first-child-becomes-paragraph'); -$smoke_assert('core/paragraph' === $onclick_tab_row_block['innerBlocks'][1]['blockName'], 'onclick-tab-row-second-child-becomes-paragraph'); -$smoke_assert('Day 1 — Friday, Sept 18' === $onclick_tab_row_block['innerBlocks'][0]['attrs']['content'], 'onclick-tab-row-first-label-preserved'); -$smoke_assert('Day 2 — Saturday, Sept 19' === $onclick_tab_row_block['innerBlocks'][1]['attrs']['content'], 'onclick-tab-row-second-label-preserved'); -$smoke_assert('tab-btn active' === $onclick_tab_row_block['innerBlocks'][0]['attrs']['className'], 'onclick-tab-row-first-class-preserved'); -$smoke_assert('tab-btn' === $onclick_tab_row_block['innerBlocks'][1]['attrs']['className'], 'onclick-tab-row-second-class-preserved'); -$smoke_assert(\strpos($onclick_tab_row_block['innerHTML'], 'onclick') === \false, 'onclick-tab-row-wrapper-strips-handler'); -foreach ($onclick_tab_row_block['innerBlocks'] as $index => $child_block) { - $smoke_assert(\strpos($child_block['innerHTML'], 'onclick') === \false, 'onclick-tab-row-child-' . $index . '-strips-handler'); - $smoke_assert(\strpos($child_block['innerHTML'], 'showDay') === \false, 'onclick-tab-row-child-' . $index . '-strips-handler-payload'); -} -$smoke_assert(\strpos($onclick_tab_row_block['innerHTML'], '') === \false, 'onclick-tab-row-avoids-wp-html'); -// Other on* handlers (onmouseover, onfocus, etc.) should also be dropped without falling back. -$onmouseover_chip = new HTML_To_Blocks_HTML_Element('button', ['class' => 'filter-chip', 'onmouseover' => 'highlight(this)'], '', 'Hover me'); -$onmouseover_chip_transform = $find_transform($onmouseover_chip); -$onmouseover_chip_block = \call_user_func($onmouseover_chip_transform['transform'], $onmouseover_chip); -$smoke_assert('core/paragraph' === $onmouseover_chip_transform['blockName'], 'onmouseover-chip-becomes-paragraph'); -$smoke_assert('Hover me' === $onmouseover_chip_block['attrs']['content'], 'onmouseover-chip-label-preserved'); -$smoke_assert(\strpos($onmouseover_chip_block['innerHTML'], 'onmouseover') === \false, 'onmouseover-chip-strips-handler'); -// Form-control buttons with on* handlers must still fall through to a fallback, -// because dropping the handler would silently change behavior for real controls. -$onclick_submit_button = new HTML_To_Blocks_HTML_Element('button', ['class' => 'tab-btn', 'type' => 'submit', 'onclick' => 'submitForm()'], '', 'Submit'); -$smoke_assert($find_transform($onclick_submit_button) === null, 'onclick-submit-button-falls-through'); -// ------------------------------------------------------------------------- -// Spans: numeric classed leaf spans become editable paragraph markup. -// ------------------------------------------------------------------------- -$classed_leaf_span = new HTML_To_Blocks_HTML_Element('span', ['class' => 'service-number'], '01', '01'); -$classed_leaf_span_transform = $find_transform($classed_leaf_span); -$smoke_assert($classed_leaf_span_transform !== null, 'numeric-classed-leaf-span-has-transform'); -$classed_leaf_span_block = \call_user_func($classed_leaf_span_transform['transform'], $classed_leaf_span); -$smoke_assert(($classed_leaf_span_block['blockName'] ?? '') === 'core/paragraph', 'numeric-classed-leaf-span-becomes-paragraph'); -$smoke_assert(($classed_leaf_span_block['attrs']['content'] ?? '') === '01', 'numeric-classed-leaf-span-preserves-markup'); -$classed_text_span = new HTML_To_Blocks_HTML_Element('span', ['class' => 'service-label'], 'Repair', 'Repair'); -$smoke_assert($find_transform($classed_text_span) === null, 'classed-text-leaf-span-falls-through'); -// ------------------------------------------------------------------------- -// Labels: static visual UI labels become text, real form labels fall through. -// ------------------------------------------------------------------------- -$visual_label = new HTML_To_Blocks_HTML_Element('label', ['class' => 'inspector-label'], '', 'Type'); -$visual_label_transform = $find_transform($visual_label); -$visual_label_block = \call_user_func($visual_label_transform['transform'], $visual_label, $handler); -$smoke_assert('core/paragraph' === $visual_label_transform['blockName'], 'visual-label-becomes-paragraph'); -$smoke_assert('Type' === $visual_label_block['attrs']['content'], 'visual-label-content-preserved'); -$smoke_assert('inspector-label' === $visual_label_block['attrs']['className'], 'visual-label-class-preserved'); -$smoke_assert(\strpos($visual_label_block['innerHTML'], '

    Type

    ') !== \false, 'visual-label-renders-native-paragraph'); -$rich_visual_label = new HTML_To_Blocks_HTML_Element('label', [], '', 'Overlay Color'); -$rich_visual_label_transform = $find_transform($rich_visual_label); -$rich_visual_label_block = \call_user_func($rich_visual_label_transform['transform'], $rich_visual_label, $handler); -$smoke_assert('core/paragraph' === $rich_visual_label_transform['blockName'], 'rich-visual-label-becomes-paragraph'); -$smoke_assert('Overlay Color' === $rich_visual_label_block['attrs']['content'], 'rich-visual-label-inline-markup-preserved'); -$form_label_for = new HTML_To_Blocks_HTML_Element('label', ['for' => 'field-type'], '', 'Type'); -$smoke_assert($find_transform($form_label_for) === null, 'form-label-for-falls-through'); -$form_label_wrapping_input = new HTML_To_Blocks_HTML_Element('label', [], '', 'Type '); -$smoke_assert($find_transform($form_label_wrapping_input) === null, 'form-label-wrapping-input-falls-through'); -// ------------------------------------------------------------------------- -// Details: summary becomes an attribute and nested content becomes blocks. -// ------------------------------------------------------------------------- -$details = new HTML_To_Blocks_HTML_Element('details', [], '
    More info

    Nested copy

    • One
    ', 'More info

    Nested copy

    • One
    '); -$details_transform = $find_transform($details); -$details_block = \call_user_func($details_transform['transform'], $details, $handler); -$smoke_assert('core/details' === $details_transform['blockName'], 'details-transform-selected'); -$smoke_assert('More info' === $details_block['attrs']['summary'], 'details-summary-preserved'); -$smoke_assert(\count($details_block['innerBlocks']) === 1, 'details-content-routed-through-handler'); -$smoke_assert(\strpos($details_block['innerBlocks'][0]['attrs']['content'], '

    Nested copy

    ') !== \false, 'details-nested-content-preserved'); -// ------------------------------------------------------------------------- -// Pullquote: explicit pullquote class wins, ordinary blockquote stays quote. -// ------------------------------------------------------------------------- -$pullquote = new HTML_To_Blocks_HTML_Element('blockquote', ['class' => 'wp-block-pullquote'], '

    Big line

    Author
    ', '

    Big line

    Author'); -$pullquote_transform = $find_transform($pullquote); -$pullquote_block = \call_user_func($pullquote_transform['transform'], $pullquote, $handler); -$smoke_assert('core/pullquote' === $pullquote_transform['blockName'], 'pullquote-transform-selected'); -$smoke_assert('

    Big line

    ' === $pullquote_block['attrs']['value'], 'pullquote-value-preserved'); -$smoke_assert('Author' === $pullquote_block['attrs']['citation'], 'pullquote-citation-preserved'); -$quote = new HTML_To_Blocks_HTML_Element('blockquote', [], '

    Regular quote

    ', '

    Regular quote

    '); -$quote_transform = $find_transform($quote); -$smoke_assert('core/quote' === $quote_transform['blockName'], 'ordinary-blockquote-stays-quote'); -// ------------------------------------------------------------------------- -// Verse: explicit verse pre blocks preserve line breaks. -// ------------------------------------------------------------------------- -$verse = new HTML_To_Blocks_HTML_Element('pre', ['class' => 'wp-block-verse'], "
    Line 1\nLine 2
    Line 3
    ", "Line 1\nLine 2
    Line 3"); -$verse_transform = $find_transform($verse); -$verse_block = \call_user_func($verse_transform['transform'], $verse, $handler); -$smoke_assert('core/verse' === $verse_transform['blockName'], 'verse-transform-selected'); -$smoke_assert("Line 1\nLine 2
    Line 3" === $verse_block['attrs']['content'], 'verse-content-preserves-line-breaks'); -$smoke_assert(\strpos($verse_block['innerHTML'], "Line 1\nLine 2
    Line 3") !== \false, 'verse-inner-html-preserves-line-breaks'); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-address-inline-strong-br.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-address-inline-strong-br.php deleted file mode 100644 index 7bcc1ce..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-address-inline-strong-br.php +++ /dev/null @@ -1,142 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_block_names = static function (array $blocks) use (&$flatten_block_names): array { - $names = []; - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? ''; - $names = \array_merge($names, $flatten_block_names($block['innerBlocks'] ?? [])); - } - return $names; -}; -$html = <<<'HTML' -

    Salt & Star Bakery42 Church Street
    Charleston, SC 29401

    -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$names = $flatten_block_names($blocks); -$assert(\count($blocks) === 1, 'single-top-level-wrapper'); -$assert(!\in_array('core/html', $names, \true), 'does-not-fallback-to-core-html', $serialized); -$assert(\in_array('core/group', $names, \true), 'address-wrapper-becomes-group', \implode(',', $names)); -$assert(\in_array('core/paragraph', $names, \true), 'address-line-becomes-paragraph', \implode(',', $names)); -foreach (['"className":"address-block"', '"className":"address-line"', 'Salt & Star Bakery42 Church Street
    Charleston, SC 29401'] as $expected) { - $assert(\strpos($serialized, $expected) !== \false, 'preserves-' . \substr(\md5($expected), 0, 8), 'Missing: ' . $expected . "\n" . $serialized); -} -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-serialization-fidelity.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-serialization-fidelity.php deleted file mode 100644 index 0ca4637..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-serialization-fidelity.php +++ /dev/null @@ -1,115 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_block_attributes')) { - function serialize_block_attributes($attributes) - { - if (empty($attributes)) { - return ''; - } - return ' ' . \json_encode($attributes, \JSON_UNESCAPED_SLASHES); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_block')) { - function serialize_block($block) - { - $name = $block['blockName'] ?? null; - $attrs = $block['attrs'] ?? []; - $inner_blocks = $block['innerBlocks'] ?? []; - $inner_content = $block['innerContent'] ?? []; - if (!$name) { - return $block['innerHTML'] ?? ''; - } - $content = ''; - $index = 0; - foreach ($inner_content as $chunk) { - if (null === $chunk) { - $content .= serialize_block($inner_blocks[$index] ?? []); - $index++; - continue; - } - $content .= $chunk; - } - return '' . $content . ''; - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks($blocks) - { - return \implode('', \array_map('serialize_block', $blocks)); - } -} -require_once \dirname(__DIR__) . '/includes/class-block-factory.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$paragraph = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', ['content' => 'A generated static website.', 'className' => 'lede']); -$styled_paragraph = HTML_To_Blocks_Block_Factory::create_block('core/paragraph', ['content' => 'Styled paragraph.', 'className' => 'center', 'style' => ['color' => ['text' => 'var(--muted)'], 'spacing' => ['margin' => ['top' => '36px']]]]); -$heading = HTML_To_Blocks_Block_Factory::create_block('core/heading', ['level' => 1, 'content' => 'WordPress is officially dead.', 'className' => 'hero-title']); -$group = HTML_To_Blocks_Block_Factory::create_block('core/group', ['className' => 'hero', 'tagName' => 'section'], [$heading, $paragraph, $styled_paragraph]); -$css_var_background_group = HTML_To_Blocks_Block_Factory::create_block('core/group', ['tagName' => 'section', 'style' => ['color' => ['background' => 'var(--surface)']]], [$paragraph]); -$empty_decorative_group = HTML_To_Blocks_Block_Factory::create_block('core/group', ['className' => 'hero-glow-1']); -$list = HTML_To_Blocks_Block_Factory::create_block('core/list', ['ordered' => \true, 'className' => 'manifesto-list'], [HTML_To_Blocks_Block_Factory::create_block('core/list-item', ['content' => 'One'])]); -$preformatted = HTML_To_Blocks_Block_Factory::create_block('core/preformatted', ['content' => 'The PromptGenerate a site.', 'className' => 'prompt']); -$serialized = serialize_blocks([$group, $css_var_background_group, $empty_decorative_group, $list, $preformatted]); -$assert(\strpos($serialized, '
    ') !== \false, 'group-static-html-uses-tag-and-class', $serialized); -$assert(\strpos($serialized, '
    ') !== \false, 'group-css-var-background-serializes-inline-style', $serialized); -$assert('
    ' === $empty_decorative_group['innerHTML'], 'empty-group-inner-html-matches-gutenberg-save', $empty_decorative_group['innerHTML']); -$assert($empty_decorative_group['innerContent'] === ['
    '], 'empty-group-inner-content-is-complete-wrapper', \var_export($empty_decorative_group['innerContent'], \true)); -$assert(\strpos($serialized, '
    ') !== \false, 'empty-group-serializes-valid-original-content', $serialized); -$assert(\strpos($serialized, '

    WordPress is officially dead.

    ') !== \false, 'heading-static-html-preserves-class', $serialized); -$assert(\strpos($serialized, '

    A generated static website.

    ') !== \false, 'paragraph-static-html-preserves-class', $serialized); -$assert(\strpos($serialized, '

    Styled paragraph.

    ') !== \false, 'paragraph-static-html-preserves-style-supports', $serialized); -$assert(\substr_count($serialized, 'manifesto-list') === 2, 'list-class-in-attrs-and-static-html', $serialized); -$assert(\strpos($serialized, '
      ') !== \false, 'list-static-html-preserves-class', $serialized); -$assert(\substr_count($serialized, 'prompt') === 2, 'preformatted-class-in-attrs-and-static-html', $serialized); -$assert(\strpos($serialized, '
      The PromptGenerate a site.
      ') !== \false, 'preformatted-static-html-preserves-class', $serialized); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-supports.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-supports.php deleted file mode 100644 index 44e0db1..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-block-supports.php +++ /dev/null @@ -1,194 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -class Block_Supports_Smoke_Element -{ - private string $tag_name; - private array $attributes; - private string $inner_html; - public function __construct(string $tag_name, array $attributes = [], string $inner_html = '') - { - $this->tag_name = \strtoupper($tag_name); - $this->attributes = \array_change_key_case($attributes, \CASE_LOWER); - $this->inner_html = $inner_html; - } - public function get_tag_name(): string - { - return $this->tag_name; - } - public function has_attribute(string $name): bool - { - return \array_key_exists(\strtolower($name), $this->attributes); - } - public function get_attribute(string $name): ?string - { - return $this->attributes[\strtolower($name)] ?? null; - } - public function get_inner_html(): string - { - return $this->inner_html; - } - public function get_child_elements(): array - { - return []; - } - public function query_selector(string $selector) - { - return null; - } - public function query_selector_all(string $selector): array - { - return []; - } - public function get_text_content(): string - { - return \trim(wp_strip_all_tags($this->inner_html)); - } -} -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$find_transform = static function ($element, string $block_name) { - foreach (HTML_To_Blocks_Transform_Registry::get_raw_transforms() as $transform) { - if (($transform['blockName'] ?? '') !== $block_name) { - continue; - } - if (\is_callable($transform['isMatch'] ?? null) && \call_user_func($transform['isMatch'], $element)) { - return $transform; - } - } - return null; -}; -$handler = static function ($args) { - return [HTML_To_Blocks_Block_Factory::create_block('core/paragraph', ['content' => 'Inner'])]; -}; -$heading = new Block_Supports_Smoke_Element('h2', ['id' => 'intro', 'class' => 'wp-block-heading alignwide custom-heading unsafe@class has-primary-color has-secondary-background-color has-large-font-size has-text-color has-background', 'style' => 'text-align: center; color: #123456; background-color: rgba(1, 2, 3, .4); margin-top: var(--wp--preset--spacing--40); padding: 2rem; border-color: red; border-style: solid; border-width: 2px; border-radius: 4px; transform: rotate(1deg); display: grid; position: absolute;'], 'Heading'); -$heading_transform = $find_transform($heading, 'core/heading'); -$heading_block = $heading_transform ? \call_user_func($heading_transform['transform'], $heading) : null; -$assert($heading_block && 'core/heading' === $heading_block['blockName'], 'heading-transform-found'); -$assert(($heading_block['attrs']['anchor'] ?? '') === 'intro', 'heading-anchor'); -$assert(($heading_block['attrs']['align'] ?? '') === 'wide', 'heading-align-wide'); -$assert(($heading_block['attrs']['textAlign'] ?? '') === 'center', 'heading-text-align'); -$assert(($heading_block['attrs']['className'] ?? '') === 'custom-heading', 'heading-safe-class-filter'); -$assert(($heading_block['attrs']['textColor'] ?? '') === 'primary', 'heading-preset-text-color'); -$assert(($heading_block['attrs']['backgroundColor'] ?? '') === 'secondary', 'heading-preset-background-color'); -$assert(($heading_block['attrs']['fontSize'] ?? '') === 'large', 'heading-preset-font-size'); -$assert(($heading_block['attrs']['style']['color']['text'] ?? '') === '#123456', 'heading-text-color'); -$assert(($heading_block['attrs']['style']['color']['background'] ?? '') === 'rgba(1, 2, 3, .4)', 'heading-background-color'); -$assert(($heading_block['attrs']['style']['spacing']['margin']['top'] ?? '') === 'var:preset|spacing|40', 'heading-margin-top-preset-var'); -$assert(($heading_block['attrs']['style']['spacing']['padding'] ?? '') === '2rem', 'heading-padding'); -$assert(($heading_block['attrs']['style']['border']['color'] ?? '') === 'red', 'heading-border-color'); -$assert(($heading_block['attrs']['style']['border']['style'] ?? '') === 'solid', 'heading-border-style'); -$assert(($heading_block['attrs']['style']['border']['width'] ?? '') === '2px', 'heading-border-width'); -$assert(($heading_block['attrs']['style']['border']['radius'] ?? '') === '4px', 'heading-border-radius'); -$assert(!isset($heading_block['attrs']['style']['transform']), 'heading-ignores-noisy-style'); -$assert(!isset($heading_block['attrs']['style']['display']), 'heading-ignores-display-style'); -$assert(!isset($heading_block['attrs']['style']['position']), 'heading-ignores-position-style'); -$paragraph = new Block_Supports_Smoke_Element('p', ['class' => 'has-heading-2-font-size readable-copy', 'style' => 'font-size: var(--wp--preset--font-size--heading-2);'], 'Paragraph'); -$paragraph_transform = $find_transform($paragraph, 'core/paragraph'); -$paragraph_block = $paragraph_transform ? \call_user_func($paragraph_transform['transform'], $paragraph) : null; -$assert($paragraph_block && 'core/paragraph' === $paragraph_block['blockName'], 'paragraph-transform-found'); -$assert(($paragraph_block['attrs']['fontSize'] ?? '') === 'heading-2', 'paragraph-preset-font-size-class-wins'); -$assert(($paragraph_block['attrs']['className'] ?? '') === 'readable-copy', 'paragraph-filters-preset-font-class'); -$group = new Block_Supports_Smoke_Element('section', ['id' => 'shell', 'class' => 'wp-block-group alignfull site-shell', 'aria-label' => 'Primary content', 'style' => 'background: #fff; padding-left: 3rem;'], '

      Inner

      '); -$group_transform = $find_transform($group, 'core/group'); -$group_block = $group_transform ? \call_user_func($group_transform['transform'], $group, $handler) : null; -$assert($group_block && 'core/group' === $group_block['blockName'], 'group-transform-found'); -$assert(($group_block['attrs']['anchor'] ?? '') === 'shell', 'group-anchor'); -$assert(($group_block['attrs']['align'] ?? '') === 'full', 'group-align-full'); -$assert(($group_block['attrs']['className'] ?? '') === 'site-shell', 'group-safe-class-filter'); -$assert(($group_block['attrs']['tagName'] ?? '') === 'section', 'group-tag-name'); -$assert(($group_block['attrs']['ariaLabel'] ?? '') === 'Primary content', 'group-aria-label'); -$assert(($group_block['attrs']['style']['color']['background'] ?? '') === '#fff', 'group-background'); -$assert(($group_block['attrs']['style']['spacing']['padding']['left'] ?? '') === '3rem', 'group-padding-left'); -$border_group = new Block_Supports_Smoke_Element('div', ['class' => 'wp-block-group benchmark-card', 'style' => 'border: 1px solid var(--border); background-color: var(--surface-2); margin-top: 2px; padding: 24px;'], '

      Inner

      '); -$border_group_transform = $find_transform($border_group, 'core/group'); -$border_group_block = $border_group_transform ? \call_user_func($border_group_transform['transform'], $border_group, $handler) : null; -$assert($border_group_block && 'core/group' === $border_group_block['blockName'], 'border-group-transform-found'); -$assert(($border_group_block['attrs']['style']['border']['width'] ?? '') === '1px', 'group-border-shorthand-width'); -$assert(($border_group_block['attrs']['style']['border']['style'] ?? '') === 'solid', 'group-border-shorthand-style'); -$assert(($border_group_block['attrs']['style']['border']['color'] ?? '') === 'var(--border)', 'group-border-shorthand-color'); -$assert(\str_contains($border_group_block['innerHTML'] ?? '', 'border-width:1px'), 'group-border-serialized-width', $border_group_block['innerHTML'] ?? ''); -$assert(\str_contains($border_group_block['innerHTML'] ?? '', 'border-style:solid'), 'group-border-serialized-style', $border_group_block['innerHTML'] ?? ''); -$assert(\str_contains($border_group_block['innerHTML'] ?? '', 'border-color:var(--border)'), 'group-border-serialized-color', $border_group_block['innerHTML'] ?? ''); -$misparsed_border_group = new Block_Supports_Smoke_Element('div', ['class' => 'wp-block-group has-background benchmark-card', 'style' => 'border-width: 1px solid var(--border); background-color: var(--surface-2); margin-top: 2px; padding: 24px;'], '

      Inner

      '); -$misparsed_border_group_transform = $find_transform($misparsed_border_group, 'core/group'); -$misparsed_border_group_block = $misparsed_border_group_transform ? \call_user_func($misparsed_border_group_transform['transform'], $misparsed_border_group, $handler) : null; -$assert($misparsed_border_group_block && 'core/group' === $misparsed_border_group_block['blockName'], 'misparsed-border-group-transform-found'); -$assert(!isset($misparsed_border_group_block['attrs']['style']['border']['width']), 'group-drops-invalid-border-width'); -$assert(!\str_contains($misparsed_border_group_block['innerHTML'] ?? '', 'border-width:1px solid var(--border)'), 'group-does-not-serialize-invalid-border-width', $misparsed_border_group_block['innerHTML'] ?? ''); -$separator = new Block_Supports_Smoke_Element('hr', ['class' => 'wp-block-separator alignwide is-style-dots custom-separator']); -$separator_transform = $find_transform($separator, 'core/separator'); -$separator_block = $separator_transform ? \call_user_func($separator_transform['transform'], $separator) : null; -$assert($separator_block && 'core/separator' === $separator_block['blockName'], 'separator-transform-found'); -$assert(($separator_block['attrs']['align'] ?? '') === 'wide', 'separator-align-wide'); -$assert(($separator_block['attrs']['className'] ?? '') === 'is-style-dots custom-separator', 'separator-preserves-safe-classes'); -$custom_separator = new Block_Supports_Smoke_Element('hr', ['class' => 'ep-divider']); -$custom_separator_transform = $find_transform($custom_separator, 'core/separator'); -$custom_separator_block = $custom_separator_transform ? \call_user_func($custom_separator_transform['transform'], $custom_separator) : null; -$assert($custom_separator_block && 'core/separator' === $custom_separator_block['blockName'], 'custom-separator-transform-found'); -$assert(($custom_separator_block['attrs']['className'] ?? '') === 'ep-divider', 'custom-separator-preserves-class'); -$assert(($custom_separator_block['innerHTML'] ?? '') === '
      ', 'custom-separator-serializes-gutenberg-default-opacity-class', $custom_separator_block['innerHTML'] ?? ''); -$ruler_separator = new Block_Supports_Smoke_Element('div', ['class' => 'ruler', 'aria-hidden' => 'true']); -$ruler_separator_transform = $find_transform($ruler_separator, 'core/separator'); -$ruler_separator_block = $ruler_separator_transform ? \call_user_func($ruler_separator_transform['transform'], $ruler_separator) : null; -$assert($ruler_separator_block && 'core/separator' === $ruler_separator_block['blockName'], 'ruler-separator-transform-found'); -$assert(($ruler_separator_block['attrs']['className'] ?? '') === 'ruler', 'ruler-separator-preserves-class'); -$assert(($ruler_separator_block['innerHTML'] ?? '') === '
      ', 'ruler-separator-serializes-as-native-separator', $ruler_separator_block['innerHTML'] ?? ''); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-branded-link-spans.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-branded-link-spans.php deleted file mode 100644 index 916c11c..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-branded-link-spans.php +++ /dev/null @@ -1,164 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\parse_blocks')) { - function parse_blocks($content) - { - if (\preg_match('/^(.*)$/s', \trim((string) $content), $matches)) { - return [['blockName' => 'core/freeform', 'attrs' => [], 'innerBlocks' => [], 'innerHTML' => $matches[1], 'innerContent' => [$matches[1]]]]; - } - return []; - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - if ('core/freeform' === $name) { - $output .= '' . ($block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$brand_cases = ['simple-span-brand' => ['html' => '
      Studio Code', 'snippets' => ['href="#top"', 'aria-label="Studio Code home"', 'class="brand"', '', 'Studio Code']], 'formatted-span-brand' => ['html' => 'WicksteadRefill Works', 'snippets' => ['href="#top"', 'aria-label="Wickstead Refill Works home"', 'class="brand"', '', 'Wickstead', 'Refill Works']], 'formatted-span-brand-with-source-spacing' => ['html' => ' Wickstead Refill Works ', 'snippets' => ['href="#top"', 'aria-label="Wickstead Refill Works home"', 'class="brand"', '', 'Wickstead', 'Refill Works']], 'brand-with-small-tagline' => ['html' => 'Studio Tattoo Studio', 'snippets' => ['href="#top"', 'aria-label="Studio home"', 'class="brand"', 'Studio', 'Tattoo Studio']], 'footer-brand-without-aria-label' => ['html' => ' WicksteadRefill Works ', 'snippets' => ['href="#top"', 'class="brand footer-brand"', '', 'Wickstead', 'Refill Works']], 'footer-brand-reversed-class-order' => ['html' => 'WicksteadRefill Works', 'snippets' => ['href="#top"', 'class="footer-brand brand"', '', 'Wickstead', 'Refill Works']], 'div-wrapped-logo-brand' => ['html' => '', 'snippets' => ['href="#"', 'class="footer-logo"', '', 'Relay Atlas']]]; -foreach ($brand_cases as $case_name => $case) { - foreach ([$case['html'], '' . $case['html'] . ''] as $index => $html) { - $serialized = serialize_blocks(html_to_blocks_raw_handler(['HTML' => $html])); - $label = $case_name . '-' . (0 === $index ? 'raw' : 'freeform'); - $assert(!\str_contains($serialized, ''), $label . '-avoids-core-html', $serialized); - $assert(!\str_contains($serialized, ''), $label . '-avoids-core-freeform', $serialized); - $assert(\str_contains($serialized, ''), 'aria-hidden-span-ruler-avoids-core-html', $aria_hidden_span_ruler); -$assert(\str_contains($aria_hidden_span_ruler, ''), 'visible-diagram-span-container-avoids-core-html', $visible_diagram_span_group); -$assert(\str_contains($visible_diagram_span_group, '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$collect_blocks = static function (array $blocks, string $name) use (&$collect_blocks): array { - $matches = []; - foreach ($blocks as $block) { - if (($block['blockName'] ?? '') === $name) { - $matches[] = $block; - } - if (!empty($block['innerBlocks']) && \is_array($block['innerBlocks'])) { - $matches = \array_merge($matches, $collect_blocks($block['innerBlocks'], $name)); - } - } - return $matches; -}; -$html = <<<'HTML' -
      -
      -
      Your HTML
      -
      <section class="hero">
      -  <h1>Build Something</h1>
      -</section>
      -
      -
      - - Studio Code -
      -
      -
      WordPress Blocks
      -
      <!-- wp:cover -->
      -<div class="wp-block-cover">...
      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$groups = $collect_blocks($blocks, 'core/group'); -$fallbacks = $collect_blocks($blocks, 'core/html'); -$paragraphs = $collect_blocks($blocks, 'core/paragraph'); -$preformatted = $collect_blocks($blocks, 'core/preformatted'); -$assert(\count($blocks) === 1 && ($blocks[0]['blockName'] ?? '') === 'core/group', 'demo-wrapper-becomes-group', $serialized); -$assert(\count($groups) >= 4, 'demo-panels-and-arrow-become-groups', $serialized); -$assert(\count($paragraphs) >= 3, 'labels-become-native-paragraphs', $serialized); -$assert(\count($preformatted) === 2, 'pre-code-panels-become-preformatted', $serialized); -$assert(\str_contains($serialized, 'sc-hero__code-demo'), 'demo-wrapper-class-survives', $serialized); -$assert(\str_contains($serialized, 'sc-code-panel--before') && \str_contains($serialized, 'sc-code-panel--after'), 'panel-classes-survive', $serialized); -$assert(\str_contains($serialized, 'Your HTML') && \str_contains($serialized, 'WordPress Blocks') && \str_contains($serialized, 'Studio Code'), 'labels-survive', $serialized); -$assert(\str_contains($serialized, '<section') && \str_contains($serialized, '<!-- wp:cover -->'), 'escaped-code-survives', $serialized); -$assert(\str_contains($serialized, 'tok-tag') && \str_contains($serialized, 'tok-comment'), 'syntax-span-classes-survive', $serialized); -$assert(\str_contains($serialized, 'language-html') && \str_contains($serialized, 'sc-code-block'), 'code-and-pre-classes-survive', $serialized); -$assert(\count($fallbacks) === 1, 'only-svg-falls-back-to-html', $serialized); -$fallback_content = $fallbacks[0]['attrs']['content'] ?? ''; -$assert(\str_starts_with(\trim($fallback_content), ' []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$collect_blocks = static function (array $blocks, string $name) use (&$collect_blocks): array { - $matches = []; - foreach ($blocks as $block) { - if (($block['blockName'] ?? '') === $name) { - $matches[] = $block; - } - if (!empty($block['innerBlocks']) && \is_array($block['innerBlocks'])) { - $matches = \array_merge($matches, $collect_blocks($block['innerBlocks'], $name)); - } - } - return $matches; -}; -$html = <<<'HTML' -
      -
      - Before: Agent prompt engineering - Fragile -
      -
      -
      /* prompt fragment trying to produce block markup */
      -
      Generate a hero section with a heading and button.
      -
      -
      -
      -
      - After: Plain HTML in, blocks out - Stable -
      -
      -
      <!-- just describe the design -->
      -
      <section class="hero">
      -
      -
      -
      studio-code "Build a consulting firm site"
      -
      tmp/static-site/index.html created
      -
      - <!-- Agent writes normal HTML -->
      - <section class="hero">
      - </section> -
      -
      - <!-- wp:group -->
      - <!-- wp:heading -->
      - <h1>Launch Faster</h1> -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$fallbacks = $collect_blocks($blocks, 'core/html'); -$groups = $collect_blocks($blocks, 'core/group'); -$paragraphs = $collect_blocks($blocks, 'core/paragraph'); -$preformatted = $collect_blocks($blocks, 'core/preformatted'); -$assert(\count($fallbacks) === 0, 'code-display-divs-do-not-fallback', $serialized); -$assert(\count($groups) >= 4, 'code-pane-wrappers-become-groups', $serialized); -$assert(\count($paragraphs) === 2, 'code-pane-headers-become-paragraphs', $serialized); -$assert(\count($preformatted) === 6, 'code-bodies-outputs-and-ws-code-become-preformatted', $serialized); -$assert(\str_contains($serialized, 'code-pane') && \str_contains($serialized, 'code-pane-header'), 'code-pane-classes-survive', $serialized); -$assert(\str_contains($serialized, 'Before: Agent prompt engineering') && \str_contains($serialized, 'Fragile'), 'before-header-text-survives', $serialized); -$assert(\str_contains($serialized, 'After: Plain HTML in, blocks out') && \str_contains($serialized, 'Stable'), 'after-header-text-survives', $serialized); -$assert(\str_contains($serialized, 'prompt fragment trying to produce block markup'), 'comment-like-code-text-survives', $serialized); -$assert(\str_contains($serialized, '<section') && \str_contains($serialized, 'class="hero"'), 'escaped-html-code-survives', $serialized); -$assert(\str_contains($serialized, 'studio-code') && \str_contains($serialized, 'tmp/static-site/index.html'), 'ws-code-text-survives', $serialized); -$assert(\str_contains($serialized, 'wp-block-preformatted ws-code'), 'ws-code-class-survives', $serialized); -$assert(\str_contains($serialized, 'Agent writes normal HTML') && \str_contains($serialized, 'class="hero"'), 'syntax-highlight-code-body-survives', $serialized); -$assert(\str_contains($serialized, '<!-- wp:group -->') && \str_contains($serialized, '<h1>Launch Faster</h1>'), 'code-output-text-survives', $serialized); -$assert(\str_contains($serialized, 'wp-block-preformatted syntax-highlight code-body') && \str_contains($serialized, 'wp-block-preformatted code-output'), 'display-body-output-classes-survive', $serialized); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-window-fallback-scope.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-window-fallback-scope.php deleted file mode 100644 index 7a0144e..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-code-window-fallback-scope.php +++ /dev/null @@ -1,373 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$collect_blocks = static function (array $blocks, string $name) use (&$collect_blocks): array { - $matches = []; - foreach ($blocks as $block) { - if (($block['blockName'] ?? '') === $name) { - $matches[] = $block; - } - if (!empty($block['innerBlocks']) && \is_array($block['innerBlocks'])) { - $matches = \array_merge($matches, $collect_blocks($block['innerBlocks'], $name)); - } - } - return $matches; -}; -$html = <<<'HTML' -
      -
      -
      -
      -
      -
      WordPress Studio — Now in Beta
      -

      Vibe code your site.
      Get real blocks.

      -

      Write pure, familiar HTML. Studio Code converts it into clean WordPress blocks.

      - -
      -
      -
      -
      -
      -
      -
      - index.html → WordPress blocks -
      -
      -
      <!-- You write -->
      -
       
      -
      <section class="hero">
      -
        <h1>Build Something Beautiful</h1>
      -
        <a class="btn">Get Started</a>
      -
      -
      - Studio Code - ↓ converts automatically - WordPress Blocks -
      -
      -
      <!-- WordPress stores -->
      -
      <!-- wp:group {"layout":{"type":"constrained"}} -->
      -
      -
      -
      -
      -
      -
      -
      -
      - -

      HTML in. WordPress blocks out.

      -

      Normal page sections should remain editable native blocks.

      -
      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$groups = $collect_blocks($blocks, 'core/group'); -$fallbacks = $collect_blocks($blocks, 'core/html'); -$buttons = $collect_blocks($blocks, 'core/button'); -$preformatted = $collect_blocks($blocks, 'core/preformatted'); -$assert(\count($blocks) === 1 && ($blocks[0]['blockName'] ?? '') === 'core/group', 'root-page-becomes-group', $serialized); -$assert(\count($groups) >= 6, 'normal-wrappers-become-groups', $serialized); -$assert(\str_contains($serialized, 'sc-page'), 'page-class-survives', $serialized); -$assert(\str_contains($serialized, 'HTML in. WordPress blocks out.'), 'normal-heading-survives', $serialized); -$assert(\str_contains($serialized, 'Normal page sections should remain editable native blocks.'), 'normal-copy-survives', $serialized); -$assert(\str_contains($serialized, '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_blocks = static function (array $blocks) use (&$flatten_blocks): array { - $flat = []; - foreach ($blocks as $block) { - $flat[] = $block; - $flat = \array_merge($flat, $flatten_blocks($block['innerBlocks'] ?? [])); - } - return $flat; -}; -$html = <<<'HTML' -
      - html → blocks
      - wp:group + wp:paragraph
      - one conversion pipeline
      - Site Editor compatible
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$flat = $flatten_blocks($blocks); -$names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flat); -$serialized = serialize_blocks($blocks); -$assert(!\in_array('core/html', $names, \true), 'strip-marquee-does-not-use-core-html', \implode(', ', $names)); -$assert(\count($fallback_events) === 0, 'strip-marquee-emits-no-fallback-events', (string) \count($fallback_events)); -$assert(\substr_count(\implode(',', $names), 'core/group') === 5, 'track-and-items-become-groups', \implode(', ', $names)); -$assert(\substr_count(\implode(',', $names), 'core/paragraph') === 4, 'strip-item-labels-become-paragraphs', \implode(', ', $names)); -$assert(\str_contains($serialized, 'strip-track'), 'track-class-survives', $serialized); -$assert(\str_contains($serialized, 'strip-item accent'), 'item-class-survives', $serialized); -$assert(\str_contains($serialized, 'html → blocks'), 'unicode-label-survives', $serialized); -$assert(\str_contains($serialized, 'wp:group + wp:paragraph'), 'block-syntax-label-survives', $serialized); -$assert(!\str_contains($serialized, 'strip-sep'), 'decorative-separators-are-dropped', $serialized); -$assert(!\str_contains($serialized, '

      -

      - HTML-first generation - Block theme output - Static Site Importer - One-shot site builds -
      - -HTML; -$studio_code_blocks = html_to_blocks_raw_handler(['HTML' => $studio_code_html]); -$studio_code_flat = $flatten_blocks($studio_code_blocks); -$studio_code_names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $studio_code_flat); -$studio_code_serialized = serialize_blocks($studio_code_blocks); -$assert(!\in_array('core/html', $studio_code_names, \true), 'studio-code-scroller-does-not-use-core-html', \implode(', ', $studio_code_names)); -$assert(\count($fallback_events) === 0, 'studio-code-scroller-emits-no-fallback-events', (string) \count($fallback_events)); -$assert(\substr_count(\implode(',', $studio_code_names), 'core/group') === 2, 'studio-code-band-and-track-become-groups', \implode(', ', $studio_code_names)); -$assert(\substr_count(\implode(',', $studio_code_names), 'core/paragraph') === 4, 'studio-code-scroll-items-stay-separate-paragraphs', \implode(', ', $studio_code_names)); -$assert(\str_contains($studio_code_serialized, 'hero-scroll-band'), 'studio-code-band-class-survives', $studio_code_serialized); -$assert(\str_contains($studio_code_serialized, 'scroll-track'), 'studio-code-track-class-survives', $studio_code_serialized); -$assert(\substr_count($studio_code_serialized, '

      ') === 4, 'studio-code-item-classes-survive-individually', $studio_code_serialized); -$assert(!\str_contains($studio_code_serialized, '

      '), 'studio-code-items-not-collapsed-into-one-paragraph', $studio_code_serialized); -$fallback_events = []; -$separator_only = html_to_blocks_raw_handler(['HTML' => '

      ']); -$assert($separator_only === [], 'standalone-strip-separator-is-ignored'); -$assert(\count($fallback_events) === 0, 'standalone-strip-separator-emits-no-fallback-events', (string) \count($fallback_events)); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-visual-clusters.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-visual-clusters.php deleted file mode 100644 index 85332f7..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-decorative-visual-clusters.php +++ /dev/null @@ -1,233 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$collect_blocks = static function (array $blocks, string $name) use (&$collect_blocks): array { - $matches = []; - foreach ($blocks as $block) { - if (($block['blockName'] ?? '') === $name) { - $matches[] = $block; - } - if (!empty($block['innerBlocks']) && \is_array($block['innerBlocks'])) { - $matches = \array_merge($matches, $collect_blocks($block['innerBlocks'], $name)); - } - } - return $matches; -}; -$flatten_block_names = static function (array $blocks) use (&$flatten_block_names): array { - $names = []; - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? ''; - $names = \array_merge($names, $flatten_block_names($block['innerBlocks'] ?? [])); - } - return $names; -}; -$scroll_html = <<<'HTML' - -HTML; -$scroll_blocks = html_to_blocks_raw_handler(['HTML' => $scroll_html]); -$scroll_serialized = serialize_blocks($scroll_blocks); -$scroll_names = $flatten_block_names($scroll_blocks); -$assert(\str_contains($scroll_serialized, 'Scroll'), 'scroll-text-survives', $scroll_serialized); -$assert(\str_contains($scroll_serialized, 'ss-hero-scroll'), 'scroll-wrapper-class-survives', $scroll_serialized); -$assert(\in_array('core/group', $scroll_names, \true), 'scroll-wrapper-becomes-group', $scroll_serialized); -$assert(\in_array('core/paragraph', $scroll_names, \true), 'scroll-text-becomes-paragraph', $scroll_serialized); -$assert(!\in_array('core/html', $scroll_names, \true), 'scroll-has-no-html-fallback', $scroll_serialized); -$product_html = <<<'HTML' - -HTML; -$product_blocks = html_to_blocks_raw_handler(['HTML' => $product_html]); -$product_serialized = serialize_blocks($product_blocks); -$product_names = $flatten_block_names($product_blocks); -$product_fallbacks = $collect_blocks($product_blocks, 'core/html'); -$assert(\in_array('core/group', $product_names, \true), 'product-wrapper-becomes-group', $product_serialized); -$assert(\str_contains($product_serialized, 'ss-product-thumb'), 'product-wrapper-class-survives', $product_serialized); -$assert(\count($product_fallbacks) === 1, 'product-only-svg-falls-back', $product_serialized); -$product_fallback_content = $product_fallbacks[0]['attrs']['content'] ?? ''; -$assert(\str_starts_with(\trim($product_fallback_content), ' - - - - - - -HTML; -$stars_blocks = html_to_blocks_raw_handler(['HTML' => $stars_html]); -$stars_serialized = serialize_blocks($stars_blocks); -$stars_names = $flatten_block_names($stars_blocks); -$star_paragraphs = $collect_blocks($stars_blocks, 'core/paragraph'); -$star_content = $star_paragraphs[0]['attrs']['content'] ?? ''; -$assert(\in_array('core/group', $stars_names, \true), 'stars-wrapper-becomes-group', $stars_serialized); -$assert(\str_contains($stars_serialized, 'ss-quote-stars'), 'stars-wrapper-class-survives', $stars_serialized); -$assert(\str_contains($stars_serialized, '5 out of 5 stars'), 'stars-aria-label-survives', $stars_serialized); -$assert(\substr_count(\html_entity_decode($star_content, \ENT_QUOTES, 'UTF-8'), '★') === 5, 'five-stars-survive', $star_content); -$assert(!\in_array('core/html', $stars_names, \true), 'stars-have-no-html-fallback', $stars_serialized); -$caption_only_figure_html = <<<'HTML' - -HTML; -$caption_only_figure_blocks = html_to_blocks_raw_handler(['HTML' => $caption_only_figure_html]); -$caption_only_figure_serialized = serialize_blocks($caption_only_figure_blocks); -$caption_only_figure_names = $flatten_block_names($caption_only_figure_blocks); -$assert(\in_array('core/group', $caption_only_figure_names, \true), 'caption-only-figure-becomes-group', $caption_only_figure_serialized); -$assert(\in_array('core/paragraph', $caption_only_figure_names, \true), 'caption-only-figure-caption-becomes-paragraph', $caption_only_figure_serialized); -$assert(\str_contains($caption_only_figure_serialized, 'gallery-tile tile-script'), 'caption-only-figure-class-survives', $caption_only_figure_serialized); -$assert(\str_contains($caption_only_figure_serialized, 'Marked scripts at the table'), 'caption-only-figure-caption-survives', $caption_only_figure_serialized); -$assert(!\in_array('core/html', $caption_only_figure_names, \true), 'caption-only-figure-has-no-html-fallback', $caption_only_figure_serialized); -$hearthline_gallery_html = <<<'HTML' - -HTML; -$hearthline_gallery_blocks = html_to_blocks_raw_handler(['HTML' => $hearthline_gallery_html]); -$hearthline_gallery_serialized = serialize_blocks($hearthline_gallery_blocks); -$hearthline_gallery_names = $flatten_block_names($hearthline_gallery_blocks); -$assert(!\in_array('core/html', $hearthline_gallery_names, \true), 'hearthline-gallery-has-no-html-fallback', $hearthline_gallery_serialized); -$assert(\substr_count($hearthline_gallery_serialized, 'photo-card') >= 3, 'hearthline-photo-card-classes-survive', $hearthline_gallery_serialized); -$assert(\str_contains($hearthline_gallery_serialized, 'Corner windows and amber evening light'), 'hearthline-first-caption-survives', $hearthline_gallery_serialized); -$assert(\str_contains($hearthline_gallery_serialized, 'Hands learning a tile-laying game'), 'hearthline-second-caption-survives', $hearthline_gallery_serialized); -$assert(\str_contains($hearthline_gallery_serialized, 'Staff shelf tags by mood and group size'), 'hearthline-third-caption-survives', $hearthline_gallery_serialized); -$nested_decorative_figure_html = <<<'HTML' - -HTML; -$nested_decorative_figure_blocks = html_to_blocks_raw_handler(['HTML' => $nested_decorative_figure_html]); -$nested_decorative_figure_serialized = serialize_blocks($nested_decorative_figure_blocks); -$nested_decorative_figure_names = $flatten_block_names($nested_decorative_figure_blocks); -$assert(\in_array('core/group', $nested_decorative_figure_names, \true), 'nested-decorative-figure-becomes-group', $nested_decorative_figure_serialized); -$assert(\in_array('core/paragraph', $nested_decorative_figure_names, \true), 'nested-decorative-figure-caption-becomes-paragraph', $nested_decorative_figure_serialized); -$assert(\str_contains($nested_decorative_figure_serialized, 'gallery-card'), 'nested-decorative-figure-class-survives', $nested_decorative_figure_serialized); -$assert(\str_contains($nested_decorative_figure_serialized, 'paper-illustration'), 'nested-decorative-placeholder-class-survives', $nested_decorative_figure_serialized); -$assert(\str_contains($nested_decorative_figure_serialized, 'Deep fiction shelves with handwritten shelf talkers.'), 'nested-decorative-caption-survives', $nested_decorative_figure_serialized); -$assert(!\in_array('core/html', $nested_decorative_figure_names, \true), 'nested-decorative-figure-has-no-html-fallback', $nested_decorative_figure_serialized); -$linked_figure_html = <<<'HTML' -
      View menu
      Menu tile
      -HTML; -$linked_figure_blocks = html_to_blocks_raw_handler(['HTML' => $linked_figure_html]); -$linked_figure_names = $flatten_block_names($linked_figure_blocks); -$linked_figure_serialized = serialize_blocks($linked_figure_blocks); -$assert(!\in_array('core/group', $linked_figure_names, \true), 'functional-figure-child-does-not-use-decorative-group-transform', $linked_figure_serialized); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-definition-list-transforms.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-definition-list-transforms.php deleted file mode 100644 index b9a3d26..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-definition-list-transforms.php +++ /dev/null @@ -1,149 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \trim(\strip_tags((string) $text)); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_block_names = static function (array $blocks) use (&$flatten_block_names): array { - $names = []; - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? ''; - $names = \array_merge($names, $flatten_block_names($block['innerBlocks'] ?? [])); - } - return $names; -}; -$html = <<<'HTML' -

      Brownie Depth Set

      Best seller
      Brownie Depth Set
      Use case
      Glossy tops · dense crumb · deep cocoa
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$names = $flatten_block_names($blocks); -$definition_group = null; -foreach ($blocks as $block) { - if (($block['blockName'] ?? '') !== 'core/group') { - continue; - } - foreach ($block['innerBlocks'] ?? [] as $inner_block) { - if (($inner_block['attrs']['className'] ?? '') === 'card-meta') { - $definition_group = $inner_block; - break 2; - } - } -} -$assert(null !== $definition_group, 'visual-definition-list-produces-group'); -$assert(!\in_array('core/html', $names, \true), 'visual-definition-list-has-no-core-html', \implode(', ', $names)); -$assert(!\in_array('core/list', $names, \true), 'visual-definition-list-is-not-bulleted-list', \implode(', ', $names)); -$assert(\count($definition_group['innerBlocks'] ?? []) === 2, 'visual-definition-list-keeps-pair-count'); -$assert(($definition_group['innerBlocks'][0]['attrs']['className'] ?? '') === 'meta-row', 'visual-definition-list-preserves-row-class'); -$assert(($definition_group['innerBlocks'][0]['innerBlocks'][0]['blockName'] ?? '') === 'core/paragraph', 'visual-definition-list-term-is-paragraph'); -$assert(($definition_group['innerBlocks'][0]['innerBlocks'][0]['attrs']['className'] ?? '') === 'meta-label', 'visual-definition-list-preserves-term-class'); -$assert(($definition_group['innerBlocks'][0]['innerBlocks'][0]['attrs']['content'] ?? '') === 'Best seller', 'visual-definition-list-preserves-term-content'); -$assert(($definition_group['innerBlocks'][0]['innerBlocks'][1]['attrs']['className'] ?? '') === 'meta-value', 'visual-definition-list-preserves-description-class'); -$assert(($definition_group['innerBlocks'][0]['innerBlocks'][1]['attrs']['content'] ?? '') === 'Brownie Depth Set', 'visual-definition-list-preserves-description-content'); -$direct_blocks = html_to_blocks_raw_handler(['HTML' => '
      Origin
      Charleston
      ']); -$assert(($direct_blocks[0]['blockName'] ?? '') === 'core/list', 'direct-definition-list-becomes-list'); -$assert(($direct_blocks[0]['innerBlocks'][0]['attrs']['content'] ?? '') === 'Origin: Charleston', 'direct-definition-list-content'); -$wrapper_stat_blocks = html_to_blocks_raw_handler(['HTML' => '
      5
      workflow categories
      18+
      bench-ready tools
      0
      guesswork mornings
      ']); -$assert(\count($wrapper_stat_blocks) === 1, 'wrapped-stat-definition-list-produces-single-block'); -$assert(($wrapper_stat_blocks[0]['blockName'] ?? '') === 'core/group', 'wrapped-stat-definition-list-becomes-group'); -$assert(($wrapper_stat_blocks[0]['attrs']['className'] ?? '') === 'hero-stats', 'wrapped-stat-definition-list-preserves-class'); -$assert(\count($wrapper_stat_blocks[0]['innerBlocks'] ?? []) === 3, 'wrapped-stat-definition-list-keeps-pair-count'); -$assert(($wrapper_stat_blocks[0]['attrs']['ariaLabel'] ?? '') === 'Store highlights', 'wrapped-stat-definition-list-preserves-aria-label'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][0]['innerBlocks'][0]['attrs']['content'] ?? '') === '5', 'wrapped-stat-definition-list-first-term'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][0]['innerBlocks'][1]['attrs']['content'] ?? '') === 'workflow categories', 'wrapped-stat-definition-list-first-description'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][1]['innerBlocks'][0]['attrs']['content'] ?? '') === '18+', 'wrapped-stat-definition-list-second-term'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][1]['innerBlocks'][1]['attrs']['content'] ?? '') === 'bench-ready tools', 'wrapped-stat-definition-list-second-description'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][2]['innerBlocks'][0]['attrs']['content'] ?? '') === '0', 'wrapped-stat-definition-list-third-term'); -$assert(($wrapper_stat_blocks[0]['innerBlocks'][2]['innerBlocks'][1]['attrs']['content'] ?? '') === 'guesswork mornings', 'wrapped-stat-definition-list-third-description'); -$complex_blocks = html_to_blocks_raw_handler(['HTML' => '
      Term
      Description

      Extra

      ']); -$complex_names = $flatten_block_names($complex_blocks); -$assert(\in_array('core/html', $complex_names, \true), 'complex-definition-list-still-falls-back', \implode(', ', $complex_names)); -if ($failures) { - \fwrite(\STDERR, \implode("\n", $failures) . "\n"); - exit(1); -} -\fwrite(\STDOUT, "PASS: {$assertions} definition list assertions\n"); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-detail-br-wrapper-fallback.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-detail-br-wrapper-fallback.php deleted file mode 100644 index 6da9b31..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-detail-br-wrapper-fallback.php +++ /dev/null @@ -1,158 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_block_names = static function (array $blocks) use (&$flatten_block_names): array { - $names = []; - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? ''; - $names = \array_merge($names, $flatten_block_names($block['innerBlocks'] ?? [])); - } - return $names; -}; -$html = <<<'HTML' -
      - -

      Come find us on King Street

      -
      - Hours -

      Tuesday – Friday   7am – 3pm
      Saturday – Sunday   7am – 2pm
      Closed Monday

      -
      -
      - Address -

      482 King Street
      Charleston, SC 29403

      -
      -
      - Order Ahead -

      hello@saltandstar.com
      (843) 555-0182

      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$names = $flatten_block_names($blocks); -$assert(\count($blocks) === 1, 'single-top-level-wrapper'); -$assert(!\in_array('core/html', $names, \true), 'does-not-fallback-to-core-html', $serialized); -$assert(\substr_count(\implode(',', $names), 'core/group') >= 4, 'outer-and-detail-wrappers-become-groups', \implode(',', $names)); -$assert(\in_array('core/heading', $names, \true), 'heading-block-created'); -$assert(\substr_count(\implode(',', $names), 'core/paragraph') >= 7, 'labels-and-details-become-paragraphs', \implode(',', $names)); -foreach (['Find Us', 'Come find us on King Street', 'Hours', 'Tuesday – Friday', '7am – 3pm
      Saturday', 'Closed Monday', 'Address', '482 King Street
      Charleston, SC 29403', 'Order Ahead', 'hello@saltandstar.com
      (843) 555-0182'] as $expected) { - $assert(\strpos($serialized, $expected) !== \false, 'preserves-' . \substr(\md5($expected), 0, 8), 'Missing: ' . $expected . "\n" . $serialized); -} -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-div-code-snippet-linebreaks.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-div-code-snippet-linebreaks.php deleted file mode 100644 index 782c572..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-div-code-snippet-linebreaks.php +++ /dev/null @@ -1,218 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$collect_blocks = static function (array $blocks, string $name) use (&$collect_blocks): array { - $matches = []; - foreach ($blocks as $block) { - if (($block['blockName'] ?? '') === $name) { - $matches[] = $block; - } - if (!empty($block['innerBlocks']) && \is_array($block['innerBlocks'])) { - $matches = \array_merge($matches, $collect_blocks($block['innerBlocks'], $name)); - } - } - return $matches; -}; -$html = <<<'HTML' -
      -
      -

      Map HTML to blocks

      -

      Styled code snippets should remain editable native blocks.

      -
      - <section class="hero">
      -   <h1>Welcome</h1>
      - </section> -
      -
      -
      -

      Compare output

      -

      Comments, arrows, and spacing should survive.

      -
      - wp_html_to_blocks($html)
      - // Maps:
      -   section.herocover
      -
      -
      -
      -

      Describe the site

      -

      Plain multiline prompt snippets should remain native blocks.

      -
      - "Build a SaaS landing page
      - with a hero, pricing,
      - and testimonials." -
      -
      -
      -

      Run the import

      -

      CLI snippets should preserve indentation entities.

      -
      - wp static-site-importer
      -   import-theme index.html
      -   --activate --overwrite -
      -
      -
      -

      Review the result

      -

      Output snippets can contain checkmarks and plain words.

      -
      - ✓ Block theme activated
      - ✓ Site Editor ready
      - ✓ All blocks editable -
      -
      -
      -

      Inspect workflow markup

      -

      Workflow code panels can use display-block spans as visual lines.

      -
      - <!-- Clean semantic HTML --> - <section class="hero"> -   <h1>Native blocks</h1> - <!-- wp:group {"layout":{"type":"constrained"}} --> -
      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$serialized = serialize_blocks($blocks); -$fallbacks = $collect_blocks($blocks, 'core/html'); -$groups = $collect_blocks($blocks, 'core/group'); -$headings = $collect_blocks($blocks, 'core/heading'); -$paragraphs = $collect_blocks($blocks, 'core/paragraph'); -$preformatted = $collect_blocks($blocks, 'core/preformatted'); -$assert(\count($blocks) === 1 && ($blocks[0]['blockName'] ?? '') === 'core/group', 'workflow-section-becomes-group', $serialized); -$assert(\count($groups) >= 3, 'step-cards-become-groups', $serialized); -$assert(\count($headings) === 6, 'step-titles-become-headings', $serialized); -$assert(\count($paragraphs) === 6, 'step-body-copy-becomes-paragraphs', $serialized); -$assert(\count($preformatted) === 6, 'step-code-becomes-preformatted', $serialized); -$assert(\count($fallbacks) === 0, 'step-code-does-not-fallback-to-html', $serialized); -$assert(\str_contains($serialized, 'workflow-steps') && \str_contains($serialized, 'step-card'), 'wrapper-classes-survive', $serialized); -$assert(\str_contains($serialized, 'wp-block-preformatted step-code'), 'step-code-class-survives', $serialized); -$assert(\str_contains($serialized, 'wp-block-preformatted workflow-code'), 'workflow-code-class-survives', $serialized); -$assert(\str_contains($serialized, '<section') && \str_contains($serialized, '</section>'), 'escaped-html-code-survives', $serialized); -$assert(\str_contains($serialized, 'Welcome') && \str_contains($serialized, 'wp_html_to_blocks'), 'span-text-survives', $serialized); -$assert(\str_contains($serialized, '// Maps:') && \str_contains($serialized, '→'), 'comments-and-arrow-survive', $serialized); -$assert(\str_contains($serialized, 'Build a SaaS landing page') && \str_contains($serialized, 'and testimonials'), 'plain-prompt-step-code-survives', $serialized); -$assert(\str_contains($serialized, 'wp static-site-importer') && \str_contains($serialized, '  --activate --overwrite'), 'cli-step-code-survives', $serialized); -$assert(\str_contains($serialized, '✓ Block theme activated') && \str_contains($serialized, '✓ All blocks editable'), 'output-step-code-survives', $serialized); -$assert(\str_contains($serialized, 'Clean semantic HTML') && \str_contains($serialized, 'wp:group'), 'workflow-code-display-block-lines-survive', $serialized); -$assert(\str_contains($serialized, "-->
      \n\n") && \str_contains($serialized, "// Maps:\n"), 'br-tags-become-linebreaks', $serialized); -$assert(!\str_contains($serialized, '
      ') && !\str_contains($serialized, '
      '), 'step-code-br-tags-removed', $serialized); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-ember-rye-media-collage.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-ember-rye-media-collage.php deleted file mode 100644 index b6b0a2c..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-ember-rye-media-collage.php +++ /dev/null @@ -1,160 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_blocks = static function (array $blocks) use (&$flatten_blocks): array { - $flat = []; - foreach ($blocks as $block) { - $flat[] = $block; - $flat = \array_merge($flat, $flatten_blocks($block['innerBlocks'] ?? [])); - } - return $flat; -}; -$html = << -
      - Wood-fired pizza with basil and melted mozzarella - Friends sharing food at a warm restaurant table - Fresh pizza topped with herbs -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$flat = $flatten_blocks($blocks); -$serialized = serialize_blocks($blocks); -$names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flat); -$groups = \array_values(\array_filter($flat, static function ($block) { - return 'core/group' === ($block['blockName'] ?? ''); -})); -$images = \array_values(\array_filter($flat, static function ($block) { - return 'core/image' === ($block['blockName'] ?? ''); -})); -$class_names = \array_map(static function ($block) { - return $block['attrs']['className'] ?? ''; -}, $flat); -$assert(!\str_contains($serialized, ''), 'ember-rye-fragment-avoids-core-html', $serialized); -$assert(\count($blocks) === 2, 'ember-rye-top-level-block-count', (string) \count($blocks)); -$assert(\in_array('hero-media', $class_names, \true), 'hero-media-class-survives', \implode(', ', $class_names)); -$assert(\in_array('photo-collage reveal', $class_names, \true), 'photo-collage-classes-survive', \implode(', ', $class_names)); -$assert(\count($groups) === 2, 'hero-and-collage-use-group-blocks', \implode(', ', $names)); -$assert(($blocks[0]['attrs']['ariaLabel'] ?? '') === 'A wood-fired pizza coming out of a glowing oven', 'hero-media-aria-label-survives', $serialized); -$assert(($blocks[1]['attrs']['ariaLabel'] ?? '') === 'Restaurant food and dining photography', 'photo-collage-aria-label-survives', $serialized); -$assert(\count($images) === 3, 'photo-collage-has-three-image-blocks', \implode(', ', $names)); -$expected_alts = ['Wood-fired pizza with basil and melted mozzarella', 'Friends sharing food at a warm restaurant table', 'Fresh pizza topped with herbs']; -foreach ($expected_alts as $index => $alt) { - $assert(($images[$index]['attrs']['alt'] ?? '') === $alt, 'photo-collage-alt-' . ($index + 1) . '-survives', $serialized); - $assert(\str_contains($images[$index]['attrs']['url'] ?? '', 'images.unsplash.com/photo-'), 'photo-collage-url-' . ($index + 1) . '-survives', $serialized); -} -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-bem-decorative-divs.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-bem-decorative-divs.php deleted file mode 100644 index 91e6a46..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-bem-decorative-divs.php +++ /dev/null @@ -1,156 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -$fallback_events = []; -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - global $fallback_events; - if ('html_to_blocks_unsupported_html_fallback' === $hook_name) { - $fallback_events[] = $args; - } - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_blocks = static function (array $blocks) use (&$flatten_blocks): array { - $flat = []; - foreach ($blocks as $block) { - $flat[] = $block; - $flat = \array_merge($flat, $flatten_blocks($block['innerBlocks'] ?? [])); - } - return $flat; -}; -$html = <<<'HTML' -
      -
      -
      -

      Studio Code

      -

      Build sites faster.

      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$flat = $flatten_blocks($blocks); -$names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flat); -$class_names = \array_filter(\array_map(static function ($block) { - return $block['attrs']['className'] ?? ''; -}, $flat)); -$serialized = serialize_blocks($blocks); -$assert(!\in_array('core/html', $names, \true), 'empty-bem-decorative-div-does-not-use-core-html', \implode(', ', $names)); -$assert(\count($fallback_events) === 0, 'empty-bem-decorative-div-emits-no-fallback-events', (string) \count($fallback_events)); -$assert(\in_array('core/group', $names, \true), 'empty-bem-decorative-div-uses-group-block', \implode(', ', $names)); -$assert(\in_array('core/heading', $names, \true), 'neighbor-heading-converts-natively', \implode(', ', $names)); -$assert(\in_array('core/paragraph', $names, \true), 'neighbor-paragraph-converts-natively', \implode(', ', $names)); -$assert(\in_array('sc-hero__noise', $class_names, \true), 'empty-bem-class-survives', \implode(', ', $class_names)); -$assert(\str_contains($serialized, 'Studio Code'), 'neighbor-heading-text-survives', $serialized); -$assert(\str_contains($serialized, 'Build sites faster.'), 'neighbor-paragraph-text-survives', $serialized); -$assert(!\str_contains($serialized, ''), 'serialized-output-has-no-wp-html', $serialized); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-decorative-icon-placeholders.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-decorative-icon-placeholders.php deleted file mode 100644 index 079c107..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-decorative-icon-placeholders.php +++ /dev/null @@ -1,134 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -$fallback_events = []; -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - global $fallback_events; - if ('html_to_blocks_unsupported_html_fallback' === $hook_name) { - $fallback_events[] = $args; - } - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_blocks = static function (array $blocks) use (&$flatten_blocks): array { - $flat = []; - foreach ($blocks as $block) { - $flat[] = $block; - $flat = \array_merge($flat, $flatten_blocks($block['innerBlocks'] ?? [])); - } - return $flat; -}; -$convert = static function (string $html) use ($flatten_blocks): array { - global $fallback_events; - $fallback_events = []; - $blocks = html_to_blocks_raw_handler(['HTML' => $html]); - $flat = $flatten_blocks($blocks); - return [$blocks, $flat, $fallback_events]; -}; -[$blocks, $flat, $events] = $convert('
      '); -$assert($blocks === [], 'absolute-empty-usecase-icon-is-dropped', \json_encode($blocks)); -$assert(\count($events) === 0, 'absolute-empty-usecase-icon-emits-no-fallback', (string) \count($events)); -[$blocks, $flat, $events] = $convert('

      Feature copy.

      '); -$names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flat); -$assert(!\in_array('core/html', $names, \true), 'hidden-empty-feature-icon-avoids-core-html', \implode(', ', $names)); -$assert(\in_array('core/paragraph', $names, \true), 'neighbor-content-survives-after-icon-drop', \implode(', ', $names)); -$assert(!\str_contains(\json_encode($blocks), 'feature-icon'), 'hidden-empty-feature-icon-is-not-wrapped-in-paragraph', \json_encode($blocks)); -$assert(\count($events) === 0, 'hidden-empty-feature-icon-emits-no-fallback', (string) \count($events)); -[$blocks, $flat, $events] = $convert('
      '); -$assert(\count($events) === 1, 'accessible-empty-icon-still-falls-back', (string) \count($events)); -$assert(($blocks[0]['blockName'] ?? '') === 'core/html', 'accessible-empty-icon-preserved-as-core-html', \json_encode($blocks)); -[$blocks, $flat, $events] = $convert('
      '); -$assert(\count($events) === 1, 'data-bearing-empty-icon-still-falls-back', (string) \count($events)); -$assert(($blocks[0]['blockName'] ?? '') === 'core/html', 'data-bearing-empty-icon-preserved-as-core-html', \json_encode($blocks)); -[$blocks, $flat, $events] = $convert('
      '); -$assert(\count($events) === 1, 'interactive-empty-icon-still-falls-back', (string) \count($events)); -$assert(($blocks[0]['blockName'] ?? '') === 'core/html', 'interactive-empty-icon-preserved-as-core-html', \json_encode($blocks)); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-glow-decorative-divs.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-glow-decorative-divs.php deleted file mode 100644 index 9cf9d90..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-empty-glow-decorative-divs.php +++ /dev/null @@ -1,182 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -$fallback_events = []; -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - global $fallback_events; - if ('html_to_blocks_unsupported_html_fallback' === $hook_name) { - $fallback_events[] = $args; - } - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_blocks = static function (array $blocks) use (&$flatten_blocks): array { - $flat = []; - foreach ($blocks as $block) { - $flat[] = $block; - $flat = \array_merge($flat, $flatten_blocks($block['innerBlocks'] ?? [])); - } - return $flat; -}; -$html = <<<'HTML' -
      -
      -
      -
      -
      -

      Glow Native

      -

      Decorative layers should stay editable.

      -
      -
      -
      -
      -

      Native quote chrome.

      -
      -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $html]); -$flat = $flatten_blocks($blocks); -$names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flat); -$class_names = \array_filter(\array_map(static function ($block) { - return $block['attrs']['className'] ?? ''; -}, $flat)); -$serialized = serialize_blocks($blocks); -$assert(!\in_array('core/html', $names, \true), 'empty-glow-decorative-divs-do-not-use-core-html', \implode(', ', $names)); -$assert(\count($fallback_events) === 0, 'empty-glow-decorative-divs-emit-no-fallback-events', (string) \count($fallback_events)); -$assert(\in_array('core/group', $names, \true), 'empty-glow-decorative-divs-use-group-blocks', \implode(', ', $names)); -$assert(\in_array('core/heading', $names, \true), 'neighbor-heading-converts-natively', \implode(', ', $names)); -$assert(\in_array('core/paragraph', $names, \true), 'neighbor-paragraph-converts-natively', \implode(', ', $names)); -$assert(\in_array('hero-glow-1', $class_names, \true), 'hero-glow-1-class-survives', \implode(', ', $class_names)); -$assert(\in_array('hero-glow-2', $class_names, \true), 'hero-glow-2-class-survives', \implode(', ', $class_names)); -$assert(\in_array('hero-gradient', $class_names, \true), 'hero-gradient-class-survives', \implode(', ', $class_names)); -$assert(\in_array('quote-glow', $class_names, \true), 'quote-glow-class-survives', \implode(', ', $class_names)); -$assert(\in_array('timeline-today', $class_names, \true), 'timeline-today-class-survives', \implode(', ', $class_names)); -$assert(\in_array('check-icon', $class_names, \true), 'check-icon-class-survives', \implode(', ', $class_names)); -$assert(\str_contains($serialized, '
      '), 'empty-hero-glow-1-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, '
      '), 'empty-hero-glow-2-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, '
      '), 'empty-hero-gradient-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, '
      '), 'empty-quote-glow-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, '
      '), 'empty-timeline-today-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, '
      '), 'empty-check-icon-serializes-valid-group-wrapper', $serialized); -$assert(\str_contains($serialized, 'Glow Native'), 'neighbor-heading-text-survives', $serialized); -$assert(\str_contains($serialized, 'Decorative layers should stay editable.'), 'neighbor-paragraph-text-survives', $serialized); -$assert(\str_contains($serialized, 'Native quote chrome.'), 'quote-content-survives', $serialized); -$assert(!\str_contains($serialized, ''), 'serialized-output-has-no-wp-html', $serialized); -$arbitrary_blocks = html_to_blocks_raw_handler(['HTML' => '
      Meaningful widget copy
      ']); -$arbitrary_names = \array_map(static function ($block) { - return $block['blockName'] ?? ''; -}, $flatten_blocks($arbitrary_blocks)); -$assert(!\in_array('core/group', $arbitrary_names, \true), 'non-empty-arbitrary-div-does-not-become-group', \implode(', ', $arbitrary_names)); -$assert(\in_array('core/paragraph', $arbitrary_names, \true), 'non-empty-arbitrary-div-remains-textual', \implode(', ', $arbitrary_names)); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-extrachill-edge-shell-hero.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-extrachill-edge-shell-hero.php deleted file mode 100644 index 0253b3c..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-extrachill-edge-shell-hero.php +++ /dev/null @@ -1,162 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -$unsupported_fallback_events = []; -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - global $unsupported_fallback_events; - if ('html_to_blocks_unsupported_html_fallback' === $hook_name) { - $unsupported_fallback_events[] = $args; - } - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - $attrs = \array_diff_key($block['attrs'] ?? [], ['content' => \true, 'text' => \true]); - $attrs_json = empty($attrs) ? '' : ' ' . \json_encode($attrs, \JSON_UNESCAPED_SLASHES); - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$flatten_block_names = static function (array $blocks) use (&$flatten_block_names): array { - $names = []; - foreach ($blocks as $block) { - $names[] = $block['blockName'] ?? ''; - $names = \array_merge($names, $flatten_block_names($block['innerBlocks'] ?? [])); - } - return $names; -}; -$edge_shell_hero = <<<'HTML' -
      -
      -

      Join the Online Music Scene

      -

      A melting pot for independent music

      - -
      -
      -HTML; -$blocks = html_to_blocks_raw_handler(['HTML' => $edge_shell_hero]); -$serialized = serialize_blocks($blocks); -$names = $flatten_block_names($blocks); -$assert(\count($blocks) === 1, 'edge-shell-single-wrapper'); -$assert(($blocks[0]['blockName'] ?? '') === 'core/group', 'edge-shell-wrapper-is-group'); -$assert(!\in_array('core/html', $names, \true), 'edge-shell-does-not-fallback-to-core-html', 'Blocks: ' . \implode(', ', $names)); -$assert(\count($unsupported_fallback_events) === 0, 'edge-shell-emits-no-unsupported-fallback-events', (string) \count($unsupported_fallback_events)); -$assert(\in_array('core/heading', $names, \true), 'edge-shell-creates-heading-blocks'); -$assert(\in_array('core/buttons', $names, \true), 'edge-shell-creates-buttons-block'); -$assert(\strpos($serialized, 'full-width-breakout ec-edge-shell') !== \false, 'edge-shell-preserves-wrapper-classes', $serialized); -$assert(\strpos($serialized, 'hero-section') !== \false, 'edge-shell-preserves-section-anchor', $serialized); -$assert(\strpos($serialized, 'hero-buttons-container') !== \false, 'edge-shell-preserves-button-wrapper-class', $serialized); -$assert(\strpos($serialized, 'Join the Online Music Scene') !== \false, 'edge-shell-preserves-heading'); -$assert(\strpos($serialized, 'A melting pot for independent music') !== \false, 'edge-shell-preserves-subheading'); -$assert(\strpos($serialized, 'https://extrachill.com/register/') !== \false, 'edge-shell-preserves-primary-button-url'); -$assert(\strpos($serialized, 'https://extrachill.com/network/') !== \false, 'edge-shell-preserves-secondary-button-url'); -echo 'Assertions: ' . $assertions . \PHP_EOL; -if (empty($failures)) { - echo 'ALL PASS' . \PHP_EOL; - exit(0); -} -echo 'FAILURES (' . \count($failures) . '):' . \PHP_EOL; -foreach ($failures as $failure) { - echo ' - ' . $failure . \PHP_EOL; -} -exit(1); diff --git a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-form-fallback-scope.php b/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-form-fallback-scope.php deleted file mode 100644 index 694529a..0000000 --- a/vendor_prefixed/chubes4/html-to-blocks-converter/tests/smoke-form-fallback-scope.php +++ /dev/null @@ -1,244 +0,0 @@ - []]; - } - } - \class_alias('BlockFormatBridge\Vendor\WP_Block_Type_Registry', 'WP_Block_Type_Registry', \false); -} -foreach (['esc_attr', 'esc_html', 'esc_url'] as $function_name) { - if (!\function_exists($function_name)) { - eval('function ' . $function_name . '( $value ) { return htmlspecialchars( (string) $value, ENT_QUOTES, "UTF-8" ); }'); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\wp_strip_all_tags')) { - function wp_strip_all_tags($text) - { - return \strip_tags((string) $text); - } -} -if (!\function_exists('BlockFormatBridge\Vendor\get_shortcode_regex')) { - function get_shortcode_regex() - { - return '(?!)'; - } -} -$fallback_events = []; -if (!\function_exists('do_action')) { - function do_action($hook_name, ...$args) - { - global $fallback_events; - if ('html_to_blocks_unsupported_html_fallback' === $hook_name) { - $fallback_events[] = $args; - } - } -} -if (!\function_exists('BlockFormatBridge\Vendor\serialize_blocks')) { - function serialize_blocks(array $blocks): string - { - $output = ''; - foreach ($blocks as $block) { - $name = $block['blockName'] ?? ''; - if ('core/html' === $name) { - $output .= '' . ($block['attrs']['content'] ?? $block['innerHTML'] ?? '') . ''; - continue; - } - $output .= ''; - $output .= $block['innerContent'][0] ?? $block['innerHTML'] ?? ''; - $output .= serialize_blocks($block['innerBlocks'] ?? []); - $inner_content = $block['innerContent'] ?? []; - $output .= \end($inner_content) ? \end($inner_content) : ''; - $output .= ''; - } - return $output; - } -} -$repo_root = \dirname(__DIR__); -require_once $repo_root . '/includes/class-block-factory.php'; -require_once $repo_root . '/includes/class-attribute-parser.php'; -require_once $repo_root . '/includes/class-html-element.php'; -require_once $repo_root . '/includes/class-transform-registry.php'; -require_once $repo_root . '/raw-handler.php'; -$failures = []; -$assertions = 0; -$assert = static function ($condition, $label, $detail = '') use (&$failures, &$assertions) { - $assertions++; - if (!$condition) { - $failures[] = 'FAIL [' . $label . ']' . ('' !== $detail ? ': ' . $detail : ''); - } -}; -$search_section = << -
      -

      Find your scene

      -

      Search venues, shows, and neighborhood networks.

      -
      -
      - - - -
      -

      Browse the full network

      -
    -HTML; -$fallback_events = []; -$search_serialized = serialize_blocks(html_to_blocks_raw_handler(['HTML' => $search_section])); -$assert(!\str_contains($search_serialized, '