diff --git a/CHANGELOG.md b/CHANGELOG.md index d7d93777..1a534a21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://github.com/Pollora/framework/compare/v13.32.0-beta.9...develop) -### Added -- A theme's `resources/views/patterns` can hold a plain `.html` file alongside `.blade.php` ones. It is registered verbatim, with no compilation step — for a pattern that needs no PHP, such as one exported straight from the block editor. Its header (`Title`, `Slug`, `Categories`…) follows the same fenced-comment convention as a Blade pattern's, `` instead of `{{-- \nTitle: ... \n --}}` - ### Fixed - A Vite script was printed before WordPress's import map, which Firefox and Safari then ignore: any WordPress script module on the page — the navigation block's, the search block's, the image lightbox's — failed on `@wordpress/interactivity was a bare specifier`, so the block did nothing. Chromium tolerates the order, which hid it. In a classic theme the import map is always in the footer, so any theme with a Vite script in the head was affected as soon as an author inserted such a block. The Vite client of the dev server had the same problem - A block theme's own `404.html` answered with HTTP 200. WordPress core resolves it to `wp-includes/template-canvas.php`, which is never a Blade view, so it always rendered through `FrontendController`'s raw-PHP-template branch — the only branch that never looked at `is_404()`. Measured on a fresh block theme: right content, wrong status diff --git a/src/BlockPattern/Application/Services/PatternService.php b/src/BlockPattern/Application/Services/PatternService.php index 5d6c072c..8a70cb50 100755 --- a/src/BlockPattern/Application/Services/PatternService.php +++ b/src/BlockPattern/Application/Services/PatternService.php @@ -193,13 +193,13 @@ private function wordPressTheme(ThemeMetadata $theme): object */ private function registerPatternsFromDirectory(string $directory, object $theme): void { - // Get every Blade (`.blade.php`) and plain (`.html`) pattern file, including subdirectories + // Get all PHP files in the directory (including subdirectories) $iterator = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS) ); foreach ($iterator as $file) { - if ($file->isFile() && in_array($file->getExtension(), PatternConstants::DISCOVERABLE_EXTENSIONS, true)) { + if ($file->isFile() && $file->getExtension() === 'php') { $this->processPatternFile($file->getPathname(), $theme); } } diff --git a/src/BlockPattern/Domain/Contracts/PatternDataExtractorInterface.php b/src/BlockPattern/Domain/Contracts/PatternDataExtractorInterface.php index b233a77e..64132f65 100755 --- a/src/BlockPattern/Domain/Contracts/PatternDataExtractorInterface.php +++ b/src/BlockPattern/Domain/Contracts/PatternDataExtractorInterface.php @@ -32,13 +32,10 @@ public function extractFromFile(string $file): PatternFileData; public function processData(PatternFileData $fileData, object $theme): array; /** - * Get the content for a pattern. - * - * A `.blade.php` file is compiled and rendered; a `.html` file is read - * back verbatim, with no compilation step. + * Get the rendered content for a pattern. * * @param string $file Path to the pattern file - * @return string|null Content or null if not available + * @return string|null Rendered content or null if not available */ public function getContent(string $file): ?string; } diff --git a/src/BlockPattern/Domain/Support/PatternConstants.php b/src/BlockPattern/Domain/Support/PatternConstants.php index 95cbdd17..472be879 100644 --- a/src/BlockPattern/Domain/Support/PatternConstants.php +++ b/src/BlockPattern/Domain/Support/PatternConstants.php @@ -21,9 +21,6 @@ final class PatternConstants /** * File extension for Blade pattern files. - * - * Compiled and executed through the view engine, so the pattern can hold - * dynamic PHP (`{{ get_bloginfo('name') }}`) alongside its block markup. */ public const string PATTERN_FILE_EXTENSION = '.blade.php'; @@ -32,24 +29,6 @@ final class PatternConstants */ public const string PHP_FILE_EXTENSION = '.php'; - /** - * File extension for plain HTML pattern files. - * - * Used verbatim as block markup — never compiled — for a pattern that - * needs no PHP, such as one exported straight from the block editor. - */ - public const string HTML_FILE_EXTENSION = '.html'; - - /** - * Extensions {@see \SplFileInfo::getExtension()} reports for a pattern - * file the discovery walk should hand to the extractor: `foo.blade.php` - * and `foo.html` both qualify, `foo.blade.php` reporting `php` since - * `getExtension()` only ever returns the last dot-segment. - * - * @var array - */ - public const array DISCOVERABLE_EXTENSIONS = ['php', 'html']; - /** * Default viewport width for patterns when none is specified. */ diff --git a/src/BlockPattern/Infrastructure/Adapters/WordPressPatternDataExtractor.php b/src/BlockPattern/Infrastructure/Adapters/WordPressPatternDataExtractor.php index 11f77500..b844e2d5 100755 --- a/src/BlockPattern/Infrastructure/Adapters/WordPressPatternDataExtractor.php +++ b/src/BlockPattern/Infrastructure/Adapters/WordPressPatternDataExtractor.php @@ -84,20 +84,9 @@ public function processData(PatternFileData $fileData, object $theme): array /** * {@inheritdoc} - * - * A `.blade.php` file is compiled and executed through the view engine, so - * it can hold dynamic PHP alongside its block markup. A `.html` file is - * used verbatim — never compiled — for a pattern that needs none, such as - * one exported straight from the block editor. */ public function getContent(string $file): ?string { - if (Str::endsWith($file, PatternConstants::HTML_FILE_EXTENSION)) { - $content = @file_get_contents($file); - - return $content === false ? null : $content; - } - $viewName = Str::replaceLast(PatternConstants::PATTERN_FILE_EXTENSION, '', Str::after($file, 'views/')); return View::exists($viewName) ? View::make($viewName)->render() : null; diff --git a/tests/Feature/BlockPattern/WordPressPatternDataExtractorTest.php b/tests/Feature/BlockPattern/WordPressPatternDataExtractorTest.php deleted file mode 100644 index deba91ac..00000000 --- a/tests/Feature/BlockPattern/WordPressPatternDataExtractorTest.php +++ /dev/null @@ -1,58 +0,0 @@ -themeDir = sys_get_temp_dir().'/pollora-pattern-content-'.uniqid(); - $this->patternsDir = $this->themeDir.'/resources/views/patterns'; - mkdir($this->patternsDir, 0755, true); - - View::addLocation($this->themeDir.'/resources/views'); - - $this->extractor = new WordPressPatternDataExtractor(Mockery::mock(CollectionFactoryInterface::class)); -}); - -afterEach(function (): void { - $files = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($this->themeDir, RecursiveDirectoryIterator::SKIP_DOTS), - RecursiveIteratorIterator::CHILD_FIRST - ); - - foreach ($files as $file) { - $file->isDir() ? rmdir($file->getRealPath()) : unlink($file->getRealPath()); - } - - rmdir($this->themeDir); -}); - -describe('WordPressPatternDataExtractor::getContent()', function (): void { - it('compiles and renders a .blade.php pattern', function (): void { - file_put_contents( - $this->patternsDir.'/hero.blade.php', - '

{{ 1 + 1 }}

' - ); - - expect($this->extractor->getContent($this->patternsDir.'/hero.blade.php')) - ->toBe('

2

'); - }); - - it('reads a .html pattern back verbatim, with no compilation', function (): void { - file_put_contents( - $this->patternsDir.'/quote.html', - '
{{ 1 + 1 }}
' - ); - - // The literal "{{ 1 + 1 }}" must survive untouched — a .html pattern - // is never handed to the Blade compiler. - expect($this->extractor->getContent($this->patternsDir.'/quote.html')) - ->toBe('
{{ 1 + 1 }}
'); - }); - - it('returns null for a missing .html pattern file', function (): void { - expect($this->extractor->getContent($this->patternsDir.'/missing.html'))->toBeNull(); - }); -}); diff --git a/tests/Pest.php b/tests/Pest.php index a5228840..8c7d283e 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -26,7 +26,7 @@ ->afterEach(function (): void { \Brain\Monkey\tearDown(); }) - ->in('Feature/Route', 'Feature/Theme', 'Feature/Discovery', 'Feature/Hook', 'Feature/VersionCheck', 'Feature/Dashboard', 'Feature/Schedule', 'Feature/Block', 'Feature/BlockPattern', 'Feature/Plugin', 'Feature/Asset', 'Feature/WordPress'); + ->in('Feature/Route', 'Feature/Theme', 'Feature/Discovery', 'Feature/Hook', 'Feature/VersionCheck', 'Feature/Dashboard', 'Feature/Schedule', 'Feature/Block', 'Feature/Plugin', 'Feature/Asset', 'Feature/WordPress'); /* |-------------------------------------------------------------------------- diff --git a/tests/Unit/BlockPattern/Application/Services/PatternServiceTest.php b/tests/Unit/BlockPattern/Application/Services/PatternServiceTest.php index 06394619..fb06f311 100644 --- a/tests/Unit/BlockPattern/Application/Services/PatternServiceTest.php +++ b/tests/Unit/BlockPattern/Application/Services/PatternServiceTest.php @@ -150,26 +150,6 @@ function makePatternService(ThemeService $themeService, array &$registered): Pat expect($registered)->toBeEmpty(); }); - it('registers a plain .html pattern file alongside .blade.php ones', function (): void { - $this->themesRoot = makeThemesRoot(['my-theme' => ['hero']]); - file_put_contents( - $this->themesRoot.'/my-theme/resources/views/patterns/quote.html', - '
quote
' - ); - - $themeService = Mockery::mock(ThemeService::class); - $themeService->shouldReceive('theme')->andReturn(new ThemeMetadata('my-theme', $this->themesRoot)); - $themeService->shouldReceive('getParentThemes')->andReturn([]); - - $registered = []; - makePatternService($themeService, $registered)->registerAll(); - - $slugs = array_map(fn (Pattern $pattern): string => $pattern->getSlug(), $registered); - sort($slugs); - - expect($slugs)->toBe(['theme/hero', 'theme/quote.html']); - }); - it('does nothing when the theme has no patterns directory', function (): void { $this->themesRoot = makeThemesRoot(['my-theme' => []]); removeThemesRoot($this->themesRoot.'/my-theme/resources/views/patterns');