$author['name'] ?? 'Unknown', 'birth_year' => $author['birth_year'] ?? null, 'death_year' => $author['death_year'] ?? null, ]; }, $authors); } /** * Get the primary author name. * * @return string */ public function getPrimaryAuthorName(): string { if (empty($this->authors)) { return 'Unknown Author'; } return $this->authors[0]['name'] ?? 'Unknown Author'; } /** * Get all author names as a string. * * @param string $separator Separator between author names * @return string */ public function getAuthorNamesString(string $separator = ', '): string { $names = array_map(fn($author) => $author['name'], $this->authors); return implode($separator, $names) ?: 'Unknown Author'; } /** * Get the plain text URL if available. * * @return string|null */ public function getPlainTextUrl(): ?string { // Priority order for text formats $preferredFormats = [ 'text/plain; charset=utf-8', 'text/plain; charset=us-ascii', 'text/plain', ]; foreach ($preferredFormats as $format) { if (isset($this->formats[$format])) { return $this->formats[$format]; } } // Fallback: look for any text/plain format foreach ($this->formats as $format => $url) { if (str_contains($format, 'text/plain')) { return $url; } } return null; } /** * Get the HTML URL if available. * * @return string|null */ public function getHtmlUrl(): ?string { return $this->formats['text/html'] ?? null; } /** * Get the cover image URL. * * @return string|null */ public function getCoverImageUrl(): ?string { return $this->formats['image/jpeg'] ?? null; } /** * Get the primary language. * * @return string */ public function getPrimaryLanguage(): string { return $this->languages[0] ?? 'en'; } /** * Determine the most appropriate category from subjects and bookshelves. * * @return string|null */ public function suggestCategory(): ?string { $categoryMapping = config('gutendex.category_mapping', []); // Check bookshelves first (usually more specific) foreach ($this->bookshelves as $bookshelf) { foreach ($categoryMapping as $key => $slug) { if (stripos($bookshelf, $key) !== false) { return $slug; } } } // Then check subjects foreach ($this->subjects as $subject) { foreach ($categoryMapping as $key => $slug) { if (stripos($subject, $key) !== false) { return $slug; } } } // Default category based on common patterns $allText = implode(' ', array_merge($this->subjects, $this->bookshelves)); $allTextLower = strtolower($allText); if (str_contains($allTextLower, 'fiction')) { return 'fiction'; } if (str_contains($allTextLower, 'history')) { return 'history'; } if (str_contains($allTextLower, 'science')) { return 'science'; } if (str_contains($allTextLower, 'philosophy')) { return 'philosophy'; } return null; } /** * Get a description from subjects and bookshelves. * * @param int $maxLength Maximum length of description * @return string */ public function generateDescription(int $maxLength = 200): string { $parts = []; if (!empty($this->subjects)) { $parts[] = 'Subjects: ' . implode(', ', array_slice($this->subjects, 0, 3)); } if (!empty($this->bookshelves)) { $parts[] = 'Categories: ' . implode(', ', array_slice($this->bookshelves, 0, 2)); } $description = implode('. ', $parts); if (strlen($description) > $maxLength) { $description = substr($description, 0, $maxLength - 3) . '...'; } return $description ?: 'A book from Project Gutenberg.'; } /** * Check if this book is in the public domain. * * @return bool */ public function isPublicDomain(): bool { return $this->copyright === null || $this->copyright === false; } /** * Convert the DTO to an array suitable for database insertion. * * @return array */ public function toBookArray(): array { return [ 'gutenberg_id' => $this->id, 'title' => $this->title, 'author' => $this->getPrimaryAuthorName(), 'cover_image' => $this->getCoverImageUrl(), 'description' => $this->generateDescription(), 'language' => $this->getPrimaryLanguage(), 'full_text_url' => $this->getPlainTextUrl(), 'source' => 'gutendex', ]; } /** * Get all author names for the many-to-many relationship. * * @return array Array of author names */ public function getAuthorNames(): array { return array_map(fn($author) => $author['name'], $this->authors); } }