Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
60 / 60 |
|
100.00% |
23 / 23 |
CRAP | |
100.00% |
1 / 1 |
| PublicationType | |
100.00% |
60 / 60 |
|
100.00% |
23 / 23 |
28 | |
100.00% |
1 / 1 |
| get | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| fromFile | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
| __construct | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
1 | |||
| toArray | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
2 | |||
| toJson | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getIdentifier | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getSchemaFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getDirectory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getMetadata | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| setMetadata | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getFields | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getFieldDefinition | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getCanonicalFieldDefinition | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| getPublications | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getPaginator | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
| getListPage | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| usesPagination | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
| save | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| parseSchemaFile | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| getRelativeDirectoryEntry | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| parseFieldData | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
| withoutNullValues | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| validateSchemaFile | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Hyde\Publications\Models; |
| 6 | |
| 7 | use Exception; |
| 8 | use Hyde\Framework\Concerns\InteractsWithDirectories; |
| 9 | use Hyde\Hyde; |
| 10 | use Hyde\Publications\Actions\PublicationSchemaValidator; |
| 11 | use Hyde\Publications\Pages\PublicationListPage; |
| 12 | use Hyde\Publications\Publications; |
| 13 | use Hyde\Support\Concerns\Serializable; |
| 14 | use Hyde\Support\Contracts\SerializableContract; |
| 15 | use Hyde\Support\Paginator; |
| 16 | use Illuminate\Support\Collection; |
| 17 | use Illuminate\Support\Str; |
| 18 | use RuntimeException; |
| 19 | |
| 20 | use function array_filter; |
| 21 | use function array_merge; |
| 22 | use function dirname; |
| 23 | use function file_get_contents; |
| 24 | use function file_put_contents; |
| 25 | use function is_null; |
| 26 | use function json_decode; |
| 27 | use function json_encode; |
| 28 | |
| 29 | /** |
| 30 | * @see \Hyde\Publications\Testing\Feature\PublicationTypeTest |
| 31 | */ |
| 32 | class PublicationType implements SerializableContract |
| 33 | { |
| 34 | use Serializable; |
| 35 | use InteractsWithDirectories; |
| 36 | |
| 37 | /** The "pretty" name of the publication type */ |
| 38 | public string $name; |
| 39 | |
| 40 | /** |
| 41 | * The field name that is used as the canonical (or identifying) field of publications. |
| 42 | * |
| 43 | * It's used primarily for generating filenames, and the publications must thus be unique by this field. |
| 44 | */ |
| 45 | public string $canonicalField = '__createdAt'; |
| 46 | |
| 47 | /** The Blade filename or view identifier used for rendering a single publication */ |
| 48 | public string $detailTemplate = 'detail.blade.php'; |
| 49 | |
| 50 | /** The Blade filename or view identifier used for rendering the index page (or index pages, when using pagination) */ |
| 51 | public string $listTemplate = 'list.blade.php'; |
| 52 | |
| 53 | /** The field that is used for sorting publications. */ |
| 54 | public string $sortField = '__createdAt'; |
| 55 | |
| 56 | /** Whether the sort field should be sorted in ascending order. */ |
| 57 | public bool $sortAscending = true; |
| 58 | |
| 59 | /** The number of publications to show per paginated page. Set to 0 to disable pagination. */ |
| 60 | public int $pageSize = 0; |
| 61 | |
| 62 | /** Generic array field which can be used to store additional data as needed. */ |
| 63 | public array $metadata = []; |
| 64 | |
| 65 | /** |
| 66 | * The front matter fields used for the publications. |
| 67 | * |
| 68 | * @var \Illuminate\Support\Collection<string, \Hyde\Publications\Models\PublicationFieldDefinition> |
| 69 | */ |
| 70 | public Collection $fields; |
| 71 | |
| 72 | /** The directory of the publication files */ |
| 73 | protected string $directory; |
| 74 | |
| 75 | public static function get(string $name): static |
| 76 | { |
| 77 | return static::fromFile("$name/schema.json"); |
| 78 | } |
| 79 | |
| 80 | public static function fromFile(string $schemaFile): static |
| 81 | { |
| 82 | try { |
| 83 | return new static(...array_merge( |
| 84 | static::parseSchemaFile($schemaFile), |
| 85 | static::getRelativeDirectoryEntry($schemaFile)) |
| 86 | ); |
| 87 | } catch (Exception $exception) { |
| 88 | throw new RuntimeException("Could not parse schema file $schemaFile", 0, $exception); |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | /** @param array<array<string, string>> $fields */ |
| 93 | public function __construct( |
| 94 | string $name, // todo get from directory name if not set in schema? |
| 95 | string $canonicalField = '__createdAt', |
| 96 | string $detailTemplate = 'detail.blade.php', |
| 97 | string $listTemplate = 'list.blade.php', |
| 98 | string $sortField = '__createdAt', |
| 99 | bool $sortAscending = true, |
| 100 | int $pageSize = 0, |
| 101 | array $fields = [], |
| 102 | array $metadata = [], |
| 103 | ?string $directory = null |
| 104 | ) { |
| 105 | $this->name = $name; // todo get from directory name if not set in schema? |
| 106 | $this->canonicalField = $canonicalField; |
| 107 | $this->detailTemplate = $detailTemplate; |
| 108 | $this->listTemplate = $listTemplate; |
| 109 | $this->fields = $this->parseFieldData($fields); |
| 110 | $this->directory = $directory ?? Str::slug($name); |
| 111 | $this->sortField = $sortField; |
| 112 | $this->sortAscending = $sortAscending; |
| 113 | $this->pageSize = $pageSize; |
| 114 | $this->metadata = $metadata; |
| 115 | } |
| 116 | |
| 117 | public function toArray(): array |
| 118 | { |
| 119 | $array = $this->withoutNullValues([ |
| 120 | 'name' => $this->name, |
| 121 | 'canonicalField' => $this->canonicalField, |
| 122 | 'detailTemplate' => $this->detailTemplate, |
| 123 | 'listTemplate' => $this->listTemplate, |
| 124 | 'sortField' => $this->sortField, |
| 125 | 'sortAscending' => $this->sortAscending, |
| 126 | 'pageSize' => $this->pageSize, |
| 127 | 'fields' => $this->fields->toArray(), |
| 128 | ]); |
| 129 | |
| 130 | if ($this->metadata) { |
| 131 | $array['metadata'] = $this->metadata; |
| 132 | } |
| 133 | |
| 134 | return $array; |
| 135 | } |
| 136 | |
| 137 | /** @param int $options */ |
| 138 | public function toJson($options = JSON_PRETTY_PRINT): string |
| 139 | { |
| 140 | return json_encode($this->toArray(), $options); |
| 141 | } |
| 142 | |
| 143 | /** Get the publication type's identifier */ |
| 144 | public function getIdentifier(): string |
| 145 | { |
| 146 | return $this->directory ?? Str::slug($this->name); |
| 147 | } |
| 148 | |
| 149 | public function getSchemaFile(): string |
| 150 | { |
| 151 | return "$this->directory/schema.json"; |
| 152 | } |
| 153 | |
| 154 | public function getDirectory(): string |
| 155 | { |
| 156 | return $this->directory; |
| 157 | } |
| 158 | |
| 159 | public function getMetadata(): array |
| 160 | { |
| 161 | return $this->metadata; |
| 162 | } |
| 163 | |
| 164 | public function setMetadata(array $metadata): array |
| 165 | { |
| 166 | return $this->metadata = $metadata; |
| 167 | } |
| 168 | |
| 169 | /** |
| 170 | * Get the publication fields, deserialized to PublicationFieldDefinition objects. |
| 171 | * |
| 172 | * @return \Illuminate\Support\Collection<string, \Hyde\Publications\Models\PublicationFieldDefinition> |
| 173 | */ |
| 174 | public function getFields(): Collection |
| 175 | { |
| 176 | return $this->fields; |
| 177 | } |
| 178 | |
| 179 | public function getFieldDefinition(string $fieldName): PublicationFieldDefinition |
| 180 | { |
| 181 | return $this->getFields()->filter(fn (PublicationFieldDefinition $field): bool => $field->name === $fieldName)->firstOrFail(); |
| 182 | } |
| 183 | |
| 184 | public function getCanonicalFieldDefinition(): PublicationFieldDefinition |
| 185 | { |
| 186 | if ($this->canonicalField === '__createdAt') { |
| 187 | return new PublicationFieldDefinition('string', $this->canonicalField); |
| 188 | } |
| 189 | |
| 190 | return $this->getFields()->filter(fn (PublicationFieldDefinition $field): bool => $field->name === $this->canonicalField)->first(); |
| 191 | } |
| 192 | |
| 193 | /** @return \Illuminate\Support\Collection<\Hyde\Publications\Pages\PublicationPage> */ |
| 194 | public function getPublications(): Collection |
| 195 | { |
| 196 | return Publications::getPublicationsForType($this); |
| 197 | } |
| 198 | |
| 199 | public function getPaginator(?int $currentPageNumber = null): Paginator |
| 200 | { |
| 201 | return new Paginator($this->getPublications(), |
| 202 | $this->pageSize, |
| 203 | $currentPageNumber, |
| 204 | $this->getIdentifier() |
| 205 | ); |
| 206 | } |
| 207 | |
| 208 | public function getListPage(): PublicationListPage |
| 209 | { |
| 210 | return new PublicationListPage($this); |
| 211 | } |
| 212 | |
| 213 | public function usesPagination(): bool |
| 214 | { |
| 215 | return ($this->pageSize > 0) && ($this->pageSize < $this->getPublications()->count()); |
| 216 | } |
| 217 | |
| 218 | public function save(?string $path = null): void |
| 219 | { |
| 220 | $path ??= $this->getSchemaFile(); |
| 221 | $this->needsParentDirectory($path); |
| 222 | file_put_contents(Hyde::path($path), json_encode($this->toArray(), JSON_PRETTY_PRINT)); |
| 223 | } |
| 224 | |
| 225 | protected static function parseSchemaFile(string $schemaFile): array |
| 226 | { |
| 227 | return json_decode(file_get_contents(Hyde::path($schemaFile)), true, 512, JSON_THROW_ON_ERROR); |
| 228 | } |
| 229 | |
| 230 | protected static function getRelativeDirectoryEntry(string $schemaFile): array |
| 231 | { |
| 232 | return ['directory' => Hyde::pathToRelative(dirname($schemaFile))]; |
| 233 | } |
| 234 | |
| 235 | protected function parseFieldData(array $fields): Collection |
| 236 | { |
| 237 | return Collection::make($fields)->map(function (array $data): PublicationFieldDefinition { |
| 238 | return new PublicationFieldDefinition(...$data); |
| 239 | }); |
| 240 | } |
| 241 | |
| 242 | protected function withoutNullValues(array $array): array |
| 243 | { |
| 244 | return array_filter($array, fn (mixed $value): bool => ! is_null($value)); |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Validate the schema.json file is valid. |
| 249 | * |
| 250 | * @internal This method is experimental and may be removed without notice |
| 251 | */ |
| 252 | public function validateSchemaFile(bool $throw = true): ?array |
| 253 | { |
| 254 | $method = $throw ? 'validate' : 'errors'; |
| 255 | |
| 256 | return PublicationSchemaValidator::call($this->getIdentifier(), $throw)->$method(); |
| 257 | } |
| 258 | } |