diff --git a/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php b/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php index 1708211554d..5fee9aa7010 100644 --- a/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php +++ b/packages/framework/src/Console/Commands/MakePublicationTypeCommand.php @@ -8,7 +8,7 @@ use Hyde\Console\Commands\Interfaces\CommandHandleInterface; use Hyde\Console\Concerns\ValidatingCommand; use Hyde\Framework\Actions\CreatesNewPublicationType; -use Hyde\Framework\Features\Publications\PublicationService; +use Illuminate\Support\Str; use InvalidArgumentException; use LaravelZero\Framework\Commands\Command; use Rgasch\Collection\Collection; @@ -35,7 +35,7 @@ public function handle(): int $title = $this->argument('title'); if (! $title) { $title = trim($this->askWithValidation('name', 'Publication type name', ['required', 'string'])); - $dirname = PublicationService::formatNameForStorage($title); + $dirname = Str::slug($title); if (file_exists($dirname) && is_dir($dirname) && count(scandir($dirname)) > 2) { throw new InvalidArgumentException("Storage path [$dirname] already exists"); } diff --git a/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php b/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php index a98ef387531..283667de02b 100644 --- a/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php +++ b/packages/framework/src/Framework/Features/Publications/Models/PublicationType.php @@ -42,7 +42,7 @@ class PublicationType implements JsonSerializable, Jsonable, Arrayable public static function get(string $name): static { - return static::fromFile(Hyde::path("$name/schema.json")); + return static::fromFile("$name/schema.json"); } public static function fromFile(string $schemaFile): static @@ -126,7 +126,7 @@ public function save(?string $path = null): void protected static function parseSchemaFile(string $schemaFile): array { - return json_decode(file_get_contents($schemaFile), true, 512, JSON_THROW_ON_ERROR); + return json_decode(file_get_contents(Hyde::path($schemaFile)), true, 512, JSON_THROW_ON_ERROR); } protected static function getRelativeDirectoryName(string $schemaFile): array diff --git a/packages/framework/src/Framework/Features/Publications/PublicationService.php b/packages/framework/src/Framework/Features/Publications/PublicationService.php index 3bb14e451c7..1453de77ef1 100644 --- a/packages/framework/src/Framework/Features/Publications/PublicationService.php +++ b/packages/framework/src/Framework/Features/Publications/PublicationService.php @@ -4,7 +4,10 @@ namespace Hyde\Framework\Features\Publications; -use Carbon\Carbon; +use function basename; +use function dirname; +use Exception; +use function glob; use Hyde\Framework\Features\Publications\Models\PublicationType; use Hyde\Hyde; use Hyde\Pages\PublicationPage; @@ -18,117 +21,96 @@ */ class PublicationService { - /** - * Format the publication type name to a suitable representation for file storage. - */ - public static function formatNameForStorage(string $pubTypeNameRaw): string - { - return Str::slug($pubTypeNameRaw); - } - /** * Return a collection of all defined publication types, indexed by the directory name. * * @todo We might want to refactor to cache this in the Kernel, maybe under $publications? * * @return Collection - * - * @throws \Exception */ public static function getPublicationTypes(): Collection { - $root = Hyde::path(); - $schemaFiles = glob("$root/*/schema.json", GLOB_BRACE); + return Collection::create(static::getSchemaFiles())->mapWithKeys(function (string $schemaFile): array { + $publicationType = PublicationType::fromFile(Hyde::pathToRelative($schemaFile)); - $pubTypes = Collection::create(); - foreach ($schemaFiles as $schemaFile) { - $publicationType = PublicationType::fromFile($schemaFile); - $pubTypes->{$publicationType->getDirectory()} = $publicationType; - } - - return $pubTypes; + return [$publicationType->getDirectory() => $publicationType]; + }); } /** - * Return all publications for a given pub type, optionally sorted by the publication's sortField. - * - * @throws \Safe\Exceptions\FilesystemException + * Return all publications for a given publication type. */ - public static function getPublicationsForPubType(PublicationType $pubType, $sort = true): Collection + public static function getPublicationsForPubType(PublicationType $pubType): Collection { - $root = base_path(); - $files = glob("$root/{$pubType->getDirectory()}/*.md"); - - $publications = Collection::create(); - foreach ($files as $file) { - $publications->add(self::getPublicationData($file)); - } - - if ($sort) { - return $publications->sortBy(function ($publication) use ($pubType) { - return $publication->matter->{$pubType->sortField}; - }); - } - - return $publications; + return Collection::create(static::getPublicationFiles($pubType->getDirectory()))->map(function (string $file): PublicationPage { + return static::parsePublicationFile(Hyde::pathToRelative($file)); + }); } /** * Return all media items for a given publication type. */ - public static function getMediaForPubType(PublicationType $pubType, $sort = true): Collection + public static function getMediaForPubType(PublicationType $pubType): Collection { - $root = Hyde::path(); - $files = glob("$root/_media/{$pubType->getDirectory()}/*.{jpg,jpeg,png,gif,pdf}", GLOB_BRACE); - - $media = Collection::create(); - foreach ($files as $file) { - $media->add($file); - } - - if ($sort) { - return $media->sort()->values(); - } - - return $media; + return Collection::create(static::getMediaFiles($pubType->getDirectory()))->map(function (string $file): string { + return Hyde::pathToRelative($file); + }); } /** - * Read an MD file and return the parsed data. + * Parse a publication Markdown source file and return a PublicationPage object. * - * @throws \Safe\Exceptions\FilesystemException + * @param string $identifier Example: my-publication/hello.md or my-publication/hello */ - public static function getPublicationData(string $mdFileName): PublicationPage + public static function parsePublicationFile(string $identifier): PublicationPage { - $fileData = file_get_contents($mdFileName); - if (! $fileData) { - throw new \Exception("No data read from [$mdFileName]"); - } + $identifier = Str::replaceLast('.md', '', $identifier); + $fileData = static::getFileData("$identifier.md"); $parsedFileData = YamlFrontMatter::markdownCompatibleParse($fileData); - $matter = $parsedFileData->matter(); - $markdown = $parsedFileData->body(); - $matter['__slug'] = basename($mdFileName, '.md'); - $matter['__createdDatetime'] = Carbon::createFromTimestamp($matter['__createdAt']); - - $type = PublicationType::get(basename(dirname($mdFileName))); - $identifier = basename($mdFileName, '.md'); - - return new PublicationPage($type, $identifier, $matter, $markdown); + return new PublicationPage( + type: PublicationType::get(dirname($identifier)), + identifier: basename($identifier), + matter: $parsedFileData->matter(), + markdown: $parsedFileData->body() + ); } /** * Check whether a given publication type exists. - * - * @throws \Exception */ - public static function publicationTypeExists(string $pubTypeName, bool $isRaw = true): bool + public static function publicationTypeExists(string $pubTypeName): bool + { + return static::getPublicationTypes()->has(Str::slug($pubTypeName)); + } + + /** + * @throws \Safe\Exceptions\FilesystemException + * @throws \Exception If the file could not be read. + */ + protected static function getFileData(string $filepath): string { - if ($isRaw) { - $pubTypeName = self::formatNameForStorage($pubTypeName); + $fileData = file_get_contents(Hyde::path($filepath)); + if (! $fileData) { + throw new Exception("No data read from [$filepath]"); } - return self::getPublicationTypes()->has($pubTypeName); + return $fileData; + } + + protected static function getSchemaFiles(): array + { + return glob(Hyde::path(Hyde::getSourceRoot()).'/*/schema.json'); + } + + protected static function getPublicationFiles(string $directory): array + { + return glob(Hyde::path("$directory/*.md")); + } + + protected static function getMediaFiles(string $directory, string $extensions = '{jpg,jpeg,png,gif,pdf}'): array + { + return glob(Hyde::path("_media/$directory/*.$extensions"), GLOB_BRACE); } } diff --git a/packages/framework/tests/Feature/PublicationTypeTest.php b/packages/framework/tests/Feature/PublicationTypeTest.php index 31d856aa473..cbfa75e5f04 100644 --- a/packages/framework/tests/Feature/PublicationTypeTest.php +++ b/packages/framework/tests/Feature/PublicationTypeTest.php @@ -91,7 +91,7 @@ public function test_can_load_from_json_file() 'directory' => 'tests/fixtures', ])); - $this->assertEquals($publicationType, PublicationType::fromFile(Hyde::path('tests/fixtures/test-publication-schema.json'))); + $this->assertEquals($publicationType, PublicationType::fromFile(('tests/fixtures/test-publication-schema.json'))); } public function test_get_fields_method_returns_collection_of_field_objects() @@ -118,7 +118,7 @@ public function test_get_method_can_find_existing_file_on_disk() public function test_get_method_fails_if_publication_type_does_not_exist() { $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('Could not parse schema file '.Hyde::path('missing/schema.json')); + $this->expectExceptionMessage('Could not parse schema file '.('missing/schema.json')); PublicationType::get('missing'); } diff --git a/packages/framework/tests/Feature/Services/PublicationServiceTest.php b/packages/framework/tests/Feature/Services/PublicationServiceTest.php index 3277b8c7096..39ec9750932 100644 --- a/packages/framework/tests/Feature/Services/PublicationServiceTest.php +++ b/packages/framework/tests/Feature/Services/PublicationServiceTest.php @@ -4,12 +4,175 @@ namespace Hyde\Framework\Testing\Feature\Services; +use function copy; +use ErrorException; +use Exception; +use function file_put_contents; +use Hyde\Framework\Features\Publications\Models\PublicationType; +use Hyde\Framework\Features\Publications\PublicationService; +use Hyde\Hyde; +use Hyde\Pages\PublicationPage; use Hyde\Testing\TestCase; +use Illuminate\Support\Facades\File; +use function mkdir; +use Rgasch\Collection\Collection; /** * @covers \Hyde\Framework\Features\Publications\PublicationService */ class PublicationServiceTest extends TestCase { - // + protected function setUp(): void + { + parent::setUp(); + + mkdir(Hyde::path('test-publication')); + } + + protected function tearDown(): void + { + File::deleteDirectory(Hyde::path('test-publication')); + + parent::tearDown(); + } + + public function testGetPublicationTypes() + { + $this->assertEquals(new Collection(), PublicationService::getPublicationTypes()); + } + + public function testGetPublicationTypesWithTypes() + { + $this->createPublicationType(); + + $this->assertEquals(new Collection([ + 'test-publication' => PublicationType::get('test-publication'), + ]), PublicationService::getPublicationTypes()); + } + + public function testGetPublicationsForPubType() + { + $this->createPublicationType(); + + $this->assertEquals( + new Collection(), + PublicationService::getPublicationsForPubType(PublicationType::get('test-publication')) + ); + } + + public function testGetPublicationsForPubTypeWithPublications() + { + $this->createPublicationType(); + $this->createPublication(); + + $this->assertEquals( + new Collection([ + PublicationService::parsePublicationFile('test-publication/foo.md'), + ]), + PublicationService::getPublicationsForPubType(PublicationType::get('test-publication')) + ); + } + + public function testGetPublicationsForPubTypeOnlyContainsInstancesOfPublicationPage() + { + $this->createPublicationType(); + $this->createPublication(); + + $this->assertContainsOnlyInstancesOf( + PublicationPage::class, + PublicationService::getPublicationsForPubType(PublicationType::get('test-publication')) + ); + } + + public function testGetMediaForPubType() + { + $this->createPublicationType(); + + $this->assertEquals( + new Collection(), + PublicationService::getMediaForPubType(PublicationType::get('test-publication')) + ); + } + + public function testGetMediaForPubTypeWithMedia() + { + $this->createPublicationType(); + mkdir(Hyde::path('_media/test-publication')); + file_put_contents(Hyde::path('_media/test-publication/image.png'), ''); + + $this->assertEquals( + new Collection([ + '_media/test-publication/image.png', + ]), + PublicationService::getMediaForPubType(PublicationType::get('test-publication')) + ); + + File::deleteDirectory(Hyde::path('_media/test-publication')); + } + + public function testParsePublicationFile() + { + $this->createPublicationType(); + $this->createPublication(); + + $file = PublicationService::parsePublicationFile('test-publication/foo'); + $this->assertInstanceOf(PublicationPage::class, $file); + $this->assertEquals('test-publication/foo', $file->getIdentifier()); + } + + public function testParsePublicationFileWithFileExtension() + { + $this->createPublicationType(); + $this->createPublication(); + + $this->assertEquals( + PublicationService::parsePublicationFile('test-publication/foo'), + PublicationService::parsePublicationFile('test-publication/foo.md') + ); + } + + public function testParsePublicationFileWithNonExistentFile() + { + $this->createPublicationType(); + + $this->expectException(ErrorException::class); + $this->expectExceptionMessage('Failed to open stream: No such file or directory'); + + PublicationService::parsePublicationFile('test-publication/foo'); + } + + public function testParsePublicationFileWithInvalidFile() + { + $this->createPublicationType(); + file_put_contents(Hyde::path('test-publication/foo.md'), ''); + + $this->expectException(Exception::class); + $this->expectExceptionMessage('No data read from [test-publication/foo.md]'); + + PublicationService::parsePublicationFile('test-publication/foo'); + } + + public function testPublicationTypeExists() + { + $this->createPublicationType(); + + $this->assertTrue(PublicationService::publicationTypeExists('test-publication')); + $this->assertFalse(PublicationService::publicationTypeExists('foo')); + } + + protected function createPublicationType(): void + { + copy( + Hyde::path('tests/fixtures/test-publication-schema.json'), + Hyde::path('test-publication/schema.json') + ); + } + + protected function createPublication(): void + { + file_put_contents( + Hyde::path('test-publication/foo.md'), + "---\n__canonical: canonical\n__createdAt: 2022-11-16 11:32:52\nfoo: bar\n---\n\nHello World!\n" + ); + } }