diff --git a/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php b/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php index 5ad6e763335..5e302bb576d 100644 --- a/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php +++ b/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php @@ -6,7 +6,7 @@ use Exception; use Hyde\Console\Commands\Interfaces\CommandHandleInterface; -use Hyde\Framework\Actions\CreatesNewPublicationTypeSchema; +use Hyde\Framework\Actions\CreatesNewPublicationType; use Hyde\Framework\Features\Publications\PublicationHelper; use InvalidArgumentException; use LaravelZero\Framework\Commands\Command; @@ -60,10 +60,10 @@ public function handle(): int 2 => 'DESC', }; - $pagesize = (int) PublicationHelper::askWithValidation( + $pageSize = (int) PublicationHelper::askWithValidation( $this, - 'pagesize', - 'Enter the pagesize (0 for no limit)', + 'pageSize', + 'Enter the pageSize (0 for no limit)', ['required', 'integer', 'between:0,100'], 25 ); @@ -86,7 +86,7 @@ public function handle(): int $canonicalField = $fields[$selected - 1]['name']; try { - $creator = new CreatesNewPublicationTypeSchema($title, $fields, $canonicalField, $sortField, $sortDirection, $pagesize, $prevNextLinks); + $creator = new CreatesNewPublicationType($title, $fields, $canonicalField, $sortField, $sortDirection, $pageSize, $prevNextLinks, $this->output); $creator->create(); } catch (Exception $e) { $this->error('Error: '.$e->getMessage().' at '.$e->getFile().':'.$e->getLine()); @@ -94,6 +94,8 @@ public function handle(): int return Command::FAILURE; } + $this->info('Publication type created successfully!'); + return Command::SUCCESS; } diff --git a/packages/framework/src/Foundation/PageCollection.php b/packages/framework/src/Foundation/PageCollection.php index cc0b1ec2b87..5e6018a0a70 100644 --- a/packages/framework/src/Foundation/PageCollection.php +++ b/packages/framework/src/Foundation/PageCollection.php @@ -24,6 +24,9 @@ * This class is stored as a singleton in the HydeKernel. * You would commonly access it via one of the facades: * + * @todo We could improve this by catching exceptions and rethrowing them using a + * DiscoveryException to make it clear that the problem is with the discovery process. + * * @see \Hyde\Foundation\Facades\PageCollection * @see \Hyde\Hyde::pages() */ diff --git a/packages/framework/src/Framework/Actions/CreatesNewPublicationType.php b/packages/framework/src/Framework/Actions/CreatesNewPublicationType.php new file mode 100644 index 00000000000..f56372e7fce --- /dev/null +++ b/packages/framework/src/Framework/Actions/CreatesNewPublicationType.php @@ -0,0 +1,63 @@ +name); + $outFile = Hyde::path("$dirName/schema.json"); + + $type = new PublicationType( + $this->name, + $this->canonicalField, + $this->sortField, + $this->sortDirection, + $this->pageSize, + $this->prevNextLinks, + "{$dirName}_detail", + "{$dirName}_list", + $this->fields->toArray() + ); + + $this->output?->writeln(sprintf('Saving publication data to [%s]', Hyde::pathToRelative($outFile))); + + $type->save($outFile); + $this->result = $type->toJson(); + } + + public function getResult(): string + { + return $this->result; + } +} diff --git a/packages/framework/src/Framework/Actions/CreatesNewPublicationTypeSchema.php b/packages/framework/src/Framework/Actions/CreatesNewPublicationTypeSchema.php deleted file mode 100644 index 927bcbd684c..00000000000 --- a/packages/framework/src/Framework/Actions/CreatesNewPublicationTypeSchema.php +++ /dev/null @@ -1,65 +0,0 @@ -name); - $outFile = "$dirName/schema.json"; - mkdir($dirName); - - $data = []; - $data['name'] = $this->name; - $data['canonicalField'] = $this->canonicalField; - $data['sortField'] = $this->sortField; - $data['sortDirection'] = $this->sortDirection; - $data['pagesize'] = $this->pagesize; - $data['prevNextLinks'] = $this->prevNextLinks; - $data['detailTemplate'] = "{$dirName}_detail"; - $data['listTemplate'] = "{$dirName}_list"; - $data['fields'] = $this->fields; - $json = json_encode($data, JSON_PRETTY_PRINT); - $this->result = $json; - - echo "Saving publicationType data to [$outFile]\n"; - - file_put_contents($outFile, $json); - } - - public function getResult(): string - { - return $this->result; - } -} diff --git a/packages/framework/src/Framework/Features/Publications/Models/PublicationListPage.php b/packages/framework/src/Framework/Features/Publications/Models/PublicationListPage.php index 45a2ee03eec..2553f2e6af8 100644 --- a/packages/framework/src/Framework/Features/Publications/Models/PublicationListPage.php +++ b/packages/framework/src/Framework/Features/Publications/Models/PublicationListPage.php @@ -9,6 +9,7 @@ use Hyde\Hyde; use Hyde\Pages\BladePage; use Illuminate\Support\Facades\Blade; +use InvalidArgumentException; use function str_contains; use function view; @@ -37,14 +38,19 @@ public function compile(): string 'publications' => PublicationHelper::getPublicationsForPubType($this->type), ]; - $template = $this->type->getSchema()['listTemplate']; + $template = $this->type->listTemplate; if (str_contains($template, '::')) { return view($template, $data)->render(); } // Using the Blade facade we can render any file without having to register the directory with the view finder. + $viewPath = Hyde::path("{$this->type->getDirectory()}/$template").'.blade.php'; + if (! file_exists($viewPath)) { + throw new InvalidArgumentException("View [$viewPath] not found."); + } + return Blade::render( - file_get_contents(Hyde::path("{$this->type->getDirectory()}/$template").'.blade.php'), $data + file_get_contents($viewPath), $data ); } diff --git a/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php b/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php index 5221a4013b6..2edabc268bc 100644 --- a/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php +++ b/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php @@ -4,34 +4,101 @@ namespace Hyde\Framework\Features\Publications\Models; +use function dirname; +use Exception; +use function file_get_contents; +use Hyde\Framework\Concerns\InteractsWithDirectories; use Hyde\Hyde; +use Hyde\Support\Concerns\JsonSerializesArrayable; +use Illuminate\Contracts\Support\Arrayable; +use Illuminate\Contracts\Support\Jsonable; +use Illuminate\Support\Str; +use function json_decode; +use JsonSerializable; +use RuntimeException; -class PublicationType +/** + * @see \Hyde\Framework\Testing\Feature\PublicationTypeTest + */ +class PublicationType implements JsonSerializable, Jsonable, Arrayable { - protected string $schemaFile; + use JsonSerializesArrayable; + use InteractsWithDirectories; + protected string $directory; - protected array $schema; - public static function get(string $name): self + public string $name; + public string $canonicalField; + public string $sortField; + public string $sortDirection; + public int $pageSize; + public bool $prevNextLinks; + public string $detailTemplate; + public string $listTemplate; + public array $fields; + + public static function get(string $name): static + { + return static::fromFile(Hyde::path("$name/schema.json")); + } + + public static function fromFile(string $schemaFile): static + { + try { + return new static(...array_merge( + static::parseSchemaFile($schemaFile), + static::getRelativeDirectoryName($schemaFile)) + ); + } catch (Exception $exception) { + throw new RuntimeException("Could not parse schema file $schemaFile", 0, $exception); + } + } + + public function __construct(string $name, string $canonicalField, string $sortField, string $sortDirection, int $pageSize, bool $prevNextLinks, string $detailTemplate, string $listTemplate, array $fields, ?string $directory = null) { - return new self(Hyde::path("$name/schema.json")); + $this->name = $name; + $this->canonicalField = $canonicalField; + $this->sortField = $sortField; + $this->sortDirection = $sortDirection; + $this->pageSize = $pageSize; + $this->prevNextLinks = $prevNextLinks; + $this->detailTemplate = $detailTemplate; + $this->listTemplate = $listTemplate; + $this->fields = $fields; + + if ($directory) { + $this->directory = $directory; + } } - public function __construct(string $schemaFile) + public function toArray(): array { - $this->schemaFile = $schemaFile; - $this->directory = Hyde::pathToRelative(dirname($schemaFile)); - $this->schema = static::parseSchema($schemaFile); + return [ + 'name' => $this->name, + 'canonicalField' => $this->canonicalField, + 'sortField' => $this->sortField, + 'sortDirection' => $this->sortDirection, + 'pageSize' => $this->pageSize, + 'prevNextLinks' => $this->prevNextLinks, + 'detailTemplate' => $this->detailTemplate, + 'listTemplate' => $this->listTemplate, + 'fields' => $this->fields, + ]; } - public function __get(string $name): mixed + public function toJson($options = JSON_PRETTY_PRINT): string { - return $this->$name ?? $this->schema[$name] ?? null; + return json_encode($this->toArray(), $options); + } + + public function getIdentifier(): string + { + return $this->directory ?? Str::slug($this->name); } public function getSchemaFile(): string { - return $this->schemaFile; + return "$this->directory/schema.json"; } public function getDirectory(): string @@ -39,15 +106,20 @@ public function getDirectory(): string return $this->directory; } - public function getSchema(): array + public function save(?string $path = null): void { - return $this->schema; + $path ??= $this->getSchemaFile(); + $this->needsParentDirectory($path); + file_put_contents($path, json_encode($this->toArray(), JSON_PRETTY_PRINT)); } - protected static function parseSchema(string $schemaFile): array + protected static function parseSchemaFile(string $schemaFile): array { return json_decode(file_get_contents($schemaFile), true, 512, JSON_THROW_ON_ERROR); } - // TODO build list pages and detail pages for each publication type + protected static function getRelativeDirectoryName(string $schemaFile): array + { + return ['directory' => Hyde::pathToRelative(dirname($schemaFile))]; + } } diff --git a/packages/framework/src/Framework/Features/Publications/PublicationHelper.php b/packages/framework/src/Framework/Features/Publications/PublicationHelper.php index 79d313d445a..066d8fd7df3 100644 --- a/packages/framework/src/Framework/Features/Publications/PublicationHelper.php +++ b/packages/framework/src/Framework/Features/Publications/PublicationHelper.php @@ -72,7 +72,7 @@ public static function getPublicationTypes(): Collection $pubTypes = Collection::create(); foreach ($schemaFiles as $schemaFile) { - $publicationType = new PublicationType($schemaFile); + $publicationType = PublicationType::fromFile($schemaFile); $pubTypes->{$publicationType->getDirectory()} = $publicationType; } @@ -87,7 +87,7 @@ public static function getPublicationTypes(): Collection public static function getPublicationsForPubType(PublicationType $pubType, $sort = true): Collection { $root = base_path(); - $files = glob("$root/{$pubType->directory}/*.md"); + $files = glob("$root/{$pubType->getDirectory()}/*.md"); $publications = Collection::create(); foreach ($files as $file) { @@ -109,7 +109,7 @@ public static function getPublicationsForPubType(PublicationType $pubType, $sort public static function getMediaForPubType(PublicationType $pubType, $sort = true): Collection { $root = Hyde::path(); - $files = glob("$root/_media/{$pubType->directory}/*.{jpg,jpeg,png,gif,pdf}", GLOB_BRACE); + $files = glob("$root/_media/{$pubType->getDirectory()}/*.{jpg,jpeg,png,gif,pdf}", GLOB_BRACE); $media = Collection::create(); foreach ($files as $file) { diff --git a/packages/framework/src/Pages/PublicationPage.php b/packages/framework/src/Pages/PublicationPage.php index 7ce93a1ce84..481f833bdff 100644 --- a/packages/framework/src/Pages/PublicationPage.php +++ b/packages/framework/src/Pages/PublicationPage.php @@ -47,7 +47,7 @@ protected function renderComponent(): string 'publication' => $this, ]; - $template = $this->type->getSchema()['detailTemplate']; + $template = $this->type->detailTemplate; if (str_contains($template, '::')) { return view($template, $data)->render(); } diff --git a/packages/framework/tests/Feature/Actions/CreatesNewPublicationTypeTest.php b/packages/framework/tests/Feature/Actions/CreatesNewPublicationTypeTest.php new file mode 100644 index 00000000000..8d91c9c21e2 --- /dev/null +++ b/packages/framework/tests/Feature/Actions/CreatesNewPublicationTypeTest.php @@ -0,0 +1,37 @@ +create(); + + $this->assertFileExists('name/schema.json'); + $this->assertStringContainsString('"name": "name"', $creator->getResult()); + $this->assertStringContainsString('"canonicalField": "canonical"', $creator->getResult()); + $this->assertStringContainsString('"sortField": "sort"', $creator->getResult()); + $this->assertStringContainsString('"sortDirection": "asc"', $creator->getResult()); + $this->assertStringContainsString('"pageSize": 10', $creator->getResult()); + $this->assertStringContainsString('"prevNextLinks": true', $creator->getResult()); + $this->assertStringContainsString('"detailTemplate": "name_detail"', $creator->getResult()); + $this->assertStringContainsString('"listTemplate": "name_list"', $creator->getResult()); + + deleteDirectory(Hyde::path('name')); + } +} diff --git a/packages/framework/tests/Feature/Commands/MakePublicationTypeCommandTest.php b/packages/framework/tests/Feature/Commands/MakePublicationTypeCommandTest.php index fca80e2026a..79b7ad64464 100644 --- a/packages/framework/tests/Feature/Commands/MakePublicationTypeCommandTest.php +++ b/packages/framework/tests/Feature/Commands/MakePublicationTypeCommandTest.php @@ -24,18 +24,18 @@ public function test_command_creates_publication() ->expectsQuestion('Add another field (y/n)', 'n') ->expectsQuestion('Sort field (0-1)', 0) ->expectsQuestion('Sort field (1-2)', 1) - ->expectsQuestion('Enter the pagesize (0 for no limit)', 10) + ->expectsQuestion('Enter the pageSize (0 for no limit)', 10) ->expectsQuestion('Generate previous/next links in detail view (y/n)', 'n') ->expectsQuestion('Canonical field (1-1)', 1) ->expectsOutputToContain('Creating a new Publication Type!') ->expectsOutput('Choose the default field you wish to sort by:') ->expectsOutput('Choose the default sort direction:') - // ->expectsOutput('Publication type created successfully!') - // ->expectsOutput('Saving publicationType data to [test-publication/schema.json]') + ->expectsOutput('Saving publication data to [test-publication/schema.json]') + ->expectsOutput('Publication type created successfully!') ->assertExitCode(0); $this->assertFileExists(Hyde::path('test-publication/schema.json')); - $this->assertEquals( + $this->assertEqualsIgnoringLineEndingType( file_get_contents(Hyde::path('test-publication/schema.json')), <<<'JSON' { @@ -43,7 +43,7 @@ public function test_command_creates_publication() "canonicalField": "Title", "sortField": "__createdAt", "sortDirection": "ASC", - "pagesize": 10, + "pageSize": 10, "prevNextLinks": true, "detailTemplate": "test-publication_detail", "listTemplate": "test-publication_list", diff --git a/packages/framework/tests/Feature/PageCollectionTest.php b/packages/framework/tests/Feature/PageCollectionTest.php index b96db553a41..e362066295a 100644 --- a/packages/framework/tests/Feature/PageCollectionTest.php +++ b/packages/framework/tests/Feature/PageCollectionTest.php @@ -4,6 +4,7 @@ namespace Hyde\Framework\Testing\Feature; +use function copy; use Hyde\Foundation\PageCollection; use Hyde\Framework\Features\Publications\Models\PublicationListPage; use Hyde\Hyde; @@ -207,7 +208,7 @@ public function test_listing_pages_for_publications_are_discovered() protected function createPublication(): void { - file_put_contents(Hyde::path('publication/schema.json'), json_encode(['foo' => 'bar'])); + copy(Hyde::path('tests/fixtures/test-publication-schema.json'), Hyde::path('publication/schema.json')); file_put_contents(Hyde::path('publication/foo.md'), '--- __canonical: canonical diff --git a/packages/framework/tests/Feature/PublicationListPageTest.php b/packages/framework/tests/Feature/PublicationListPageTest.php index 6edb65f1880..0f392ba0b00 100644 --- a/packages/framework/tests/Feature/PublicationListPageTest.php +++ b/packages/framework/tests/Feature/PublicationListPageTest.php @@ -32,7 +32,7 @@ public function test_listing_page_can_be_compiled() { $this->createPublicationFiles(); - file_put_contents(Hyde::path('test-publication/test_list.blade.php'), 'Listing Page'); + file_put_contents(Hyde::path('test-publication/list.blade.php'), 'Listing Page'); $page = new PublicationListPage($this->getPublicationType()); @@ -44,11 +44,7 @@ public function test_listing_page_can_be_compiled() protected function createPublicationFiles(): void { mkdir(Hyde::path('test-publication')); - file_put_contents(Hyde::path('test-publication/schema.json'), json_encode([ - 'foo' => 'bar', - 'detailTemplate' => 'test_detail', - 'listTemplate' => 'test_list', - ])); + file_put_contents(Hyde::path('test-publication/schema.json'), json_encode($this->getTestData())); file_put_contents( Hyde::path('test-publication/foo.md'), '--- @@ -64,6 +60,23 @@ protected function createPublicationFiles(): void protected function getPublicationType(): PublicationType { - return new PublicationType('test-publication/schema.json'); + return PublicationType::fromFile('test-publication/schema.json'); + } + + protected function getTestData(): array + { + return [ + 'name' => 'test', + 'canonicalField' => 'canonical', + 'sortField' => 'sort', + 'sortDirection' => 'asc', + 'pageSize' => 10, + 'prevNextLinks' => true, + 'detailTemplate' => 'detail', + 'listTemplate' => 'list', + 'fields' => [ + 'foo' => 'bar', + ], + ]; } } diff --git a/packages/framework/tests/Feature/PublicationPageTest.php b/packages/framework/tests/Feature/PublicationPageTest.php index 4c59fed8f34..46dd99e72c8 100644 --- a/packages/framework/tests/Feature/PublicationPageTest.php +++ b/packages/framework/tests/Feature/PublicationPageTest.php @@ -4,6 +4,7 @@ namespace Hyde\Framework\Testing\Feature; +use function copy; use function deleteDirectory; use function file_put_contents; use Hyde\Framework\Features\Publications\Models\PublicationType; @@ -11,7 +12,6 @@ use Hyde\Pages\PublicationPage; use Hyde\Support\Models\Route; use Hyde\Testing\TestCase; -use function json_encode; use function mkdir; /** @@ -37,7 +37,7 @@ public function test_source_path_mappings() { $this->createPublicationFiles(); - $page = new PublicationPage(new PublicationType('test-publication/schema.json'), 'foo'); + $page = new PublicationPage(PublicationType::fromFile('test-publication/schema.json'), 'foo'); $this->assertSame('test-publication/foo', $page->getIdentifier()); $this->assertSame('test-publication/foo', $page->getRouteKey()); @@ -49,7 +49,7 @@ public function test_publication_pages_are_routable() { $this->createPublicationFiles(); - $page = new PublicationPage(new PublicationType('test-publication/schema.json'), 'foo'); + $page = new PublicationPage(PublicationType::fromFile('test-publication/schema.json'), 'foo'); $this->assertInstanceOf(Route::class, $page->getRoute()); $this->assertEquals(new Route($page), $page->getRoute()); @@ -97,7 +97,7 @@ protected function createRealPublicationFiles(): void "canonicalField": "slug", "sortField": "__createdAt", "sortDirection": "DESC", - "pagesize": 0, + "pageSize": 0, "prevNextLinks": true, "detailTemplate": "test_detail", "listTemplate": "test_list", @@ -128,7 +128,7 @@ protected function createRealPublicationFiles(): void protected function createPublicationFiles(): void { - file_put_contents(Hyde::path('test-publication/schema.json'), json_encode(['foo' => 'bar'])); + copy(Hyde::path('tests/fixtures/test-publication-schema.json'), Hyde::path('test-publication/schema.json')); file_put_contents( Hyde::path('test-publication/foo.md'), '--- diff --git a/packages/framework/tests/Feature/PublicationTypeTest.php b/packages/framework/tests/Feature/PublicationTypeTest.php new file mode 100644 index 00000000000..eeb80597b8b --- /dev/null +++ b/packages/framework/tests/Feature/PublicationTypeTest.php @@ -0,0 +1,117 @@ +getTestData()); + + foreach ($this->getTestData() as $key => $property) { + $this->assertEquals($property, $publicationType->$key); + } + } + + public function test_class_is_arrayable() + { + $publicationType = new PublicationType(...$this->getTestData()); + $this->assertSame($this->getTestData(), $publicationType->toArray()); + } + + public function test_class_is_json_serializable() + { + $publicationType = new PublicationType(...$this->getTestData()); + $this->assertSame(json_encode($this->getTestData()), json_encode($publicationType)); + } + + public function test_class_is_jsonable() + { + $publicationType = new PublicationType(...$this->getTestData()); + $this->assertSame(json_encode($this->getTestData(), 128), $publicationType->toJson()); + } + + public function test_get_directory() + { + $publicationType = new PublicationType(...$this->getTestDataWithPathInformation()); + $this->assertSame('test-publication', $publicationType->getDirectory()); + } + + public function test_get_identifier() + { + $publicationType = new PublicationType(...$this->getTestDataWithPathInformation()); + $this->assertSame('test-publication', $publicationType->getIdentifier()); + } + + public function test_get_identifier_with_no_directory() + { + $publicationType = new PublicationType(...$this->getTestData()); + $this->assertSame('test', $publicationType->getIdentifier()); + } + + public function test_can_save_to_json_file() + { + $publicationType = new PublicationType(...$this->getTestDataWithPathInformation()); + $publicationType->save(); + + $this->assertFileExists('test-publication/schema.json'); + $this->assertSame(json_encode($this->getTestData(), 128), file_get_contents(Hyde::path('test-publication/schema.json'))); + + File::deleteDirectory(Hyde::path('test-publication')); + } + + public function test_can_save_to_json_file_using_custom_path() + { + $publicationType = new PublicationType(...$this->getTestData()); + $publicationType->save('test-publication/foo.json'); + + $this->assertFileExists('test-publication/foo.json'); + $this->assertSame(json_encode($this->getTestData(), 128), file_get_contents(Hyde::path('test-publication/foo.json'))); + + File::deleteDirectory(Hyde::path('test-publication')); + } + + public function test_can_load_from_json_file() + { + $publicationType = new PublicationType(...array_merge($this->getTestData(), [ + 'directory' => 'tests/fixtures', + ])); + + $this->assertEquals($publicationType, PublicationType::fromFile(Hyde::path('tests/fixtures/test-publication-schema.json'))); + } + + protected function getTestData(): array + { + return [ + 'name' => 'test', + 'canonicalField' => 'canonical', + 'sortField' => 'sort', + 'sortDirection' => 'asc', + 'pageSize' => 10, + 'prevNextLinks' => true, + 'detailTemplate' => 'detail', + 'listTemplate' => 'list', + 'fields' => [ + 'foo' => 'bar', + ], + ]; + } + + protected function getTestDataWithPathInformation(): array + { + return array_merge($this->getTestData(), [ + 'directory' => 'test-publication', + ]); + } +} diff --git a/tests/fixtures/test-publication-schema.json b/tests/fixtures/test-publication-schema.json new file mode 100644 index 00000000000..4ed5899266e --- /dev/null +++ b/tests/fixtures/test-publication-schema.json @@ -0,0 +1,13 @@ +{ + "name": "test", + "canonicalField": "canonical", + "sortField": "sort", + "sortDirection": "asc", + "pageSize": 10, + "prevNextLinks": true, + "detailTemplate": "detail", + "listTemplate": "list", + "fields": { + "foo": "bar" + } +} \ No newline at end of file