path = $this->normalizeBundlePath($path); $this->dataDir = $this->path . 'data/'; $this->registerDefaultManagers(); } /** * Return current page number */ public function getCurrentPage(): int { return $this->currentPage; } /** * Return bundle's path */ public function getPath(): string { return $this->path; } /** * Return bundle's data dir */ public function getDataDir(): string { return $this->dataDir; } /** * Return bundle's filesystem instance */ public function getDisk(): FilesystemAdapter { return $this->disk; } /** * Return the section where this bundle is located */ public function getSection(): ?Bundle { $parts = preg_split('#/#', $this->path, -1, PREG_SPLIT_NO_EMPTY); if (count($parts) > 0) { return new Bundle($parts[0], $this->disk); } return null; } /** * Return a boolean value indicating if there already is a bundle in * specified path */ public function exists(): bool { return $this->markdown()->exists(); } /** * Load everything */ public function load(): void { $this->loadAttachments(); $this->loadMetadata(); $this->loadMarkdown(); } /** * Store all files of the bundle */ public function save(): void { $this->saveAttachments(); $this->saveMetadata(); $this->saveMarkdown(); } /** * Repair bundle */ public function repair(): void { $this->load(); $this->repairCover(); $this->lintMarkdown(); $this->repairAttachments(); $this->save(); } public function getArticleTitle(): string { return $this->metadata()->get('title') ?? Str::title(basename($this->path)); } public function getSiteTitle(): string { $title = $this->getArticleTitle(); $section = $this->getSection(); if (!empty($section)) { $title .= ' - ' . $section->getArticleTitle(); } return $title; } public function render() { $renderer = BundleRenderer::getBundleRendererFor($this); return $renderer->render(); } public function renderCard() { $renderer = BundleRenderer::getBundleRendererFor($this); return $renderer->renderCard(); } /** * Return a list of bundles in the specified path */ public static function findBundles(FilesystemAdapter $disk, ?string $path = '/', bool $recursive = false): array { if ($recursive) { return $disk ->listContents($path, $recursive) ->filter(fn (StorageAttributes $attributes) => ($attributes->isFile() && Str::endsWith($attributes->path(), '.md'))) ->map(fn (StorageAttributes $attributes) => new Bundle(dirname($attributes->path()), $disk)) ->toArray(); } else { $bundles = []; $directories = $disk->directories($path); foreach ($directories as $directory) { $bundle = new Bundle($directory, $disk); if ($bundle->exists()) { $bundles[] = $bundle; } } return $bundles; } } /** * Return the most recent modification date of this bundle and all * sub-bundles */ public function lastModified(): ?Carbon { $dates = $this->disk ->listContents($this->getPath(), true) ->map(fn (StorageAttributes $attributes) => $attributes->lastModified()) ->toArray(); $latestTime = 0; foreach ($dates as $date) { if ($date > $latestTime) { $latestTime = $date; } } return $latestTime == 0 ? null : Carbon::parse($latestTime); } private function repairCover() { $cover = $this->metadata()->get('cover'); if (empty($cover)) { return; } if (is_array($cover)) { $path = $cover['url']; foreach ($this->attachments(AttachmentsManager::Images)->manager()->get('files') as $ref => $data) { if (Str::endsWith($data['filename'], $path)) { $this->metadata()->set('cover', $ref); } } } } /** * Return a normalized representation of bundle's path */ private function normalizeBundlePath(string $path): string { $parts = preg_split('#/#', $path, -1, PREG_SPLIT_NO_EMPTY); $count = count($parts); if ($count >= 2 && $parts[$count - 2] === 'page') { // Requested URL is pagination $page = array_pop($parts); if (!is_numeric($page) || $page < 1) { throw new Exception(sprintf('Invalid page number: %s', $page)); } $this->currentPage = $page; // Pop "page" out of the parts array_pop($parts); } return Str::start(Str::finish(implode('/', $parts), '/'), '/'); } /** * Register default managers */ private function registerDefaultManagers(): void { $this->markdown(); $this->metadata(); $this->metadata('metadata'); $this->attachments(AttachmentsManager::Images); $this->attachments(AttachmentsManager::Sounds); $this->attachments(AttachmentsManager::Videos); } /** * Get a complete filename prefixed with bundle's path */ private function getFilenameInBundle(string $filename, ?string $extension = null): string { return $this->getFullpath($this->path, $filename, $extension); } /** * Get a complete filename prefixed with bundle's data dir */ private function getFilenameInDataBundle(string $filename, ?string $extension = null): string { return $this->getFullpath($this->dataDir, $filename, $extension); } /** * Return full path of specified filename in specified root directory, and * optionally add specified extension */ private function getFullpath(string $root, string $filename, ?string $extension = null): string { $filename = Str::remove($root, $filename); if (!empty($extension) && !Str::endsWith($filename, $extension)) { $filename .= $extension; } return sprintf('%s%s', $root, $filename); } }