303 lines
7.5 KiB
PHP
303 lines
7.5 KiB
PHP
<?php
|
|
|
|
namespace App\Classes;
|
|
|
|
use App\Classes\Traits\ManagesAttachments;
|
|
use App\Classes\Traits\ManagesMarkdown;
|
|
use App\Classes\Traits\ManagesMetadata;
|
|
use App\Contracts\Bundles;
|
|
use App\Services\BundleRenderers\Facades\BundleRenderer;
|
|
use Exception;
|
|
use Illuminate\Filesystem\FilesystemAdapter;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use League\Flysystem\StorageAttributes;
|
|
|
|
class Bundle implements Bundles
|
|
{
|
|
use ManagesAttachments,
|
|
ManagesMarkdown,
|
|
ManagesMetadata;
|
|
|
|
protected string $dataDir;
|
|
|
|
protected int $currentPage = 1;
|
|
|
|
protected ?Bundle $section;
|
|
|
|
protected ?Bundle $parent;
|
|
|
|
public function __construct(protected string $path, protected FilesystemAdapter $disk)
|
|
{
|
|
$this->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
|
|
{
|
|
if (!isset($this->section)) {
|
|
$parts = preg_split('#/#', $this->path, -1, PREG_SPLIT_NO_EMPTY);
|
|
|
|
if (count($parts) > 0) {
|
|
$this->section = new Bundle($parts[0], $this->disk);
|
|
} else {
|
|
$this->section = null;
|
|
}
|
|
}
|
|
|
|
return $this->section;
|
|
}
|
|
|
|
/**
|
|
* Return the parent bundle where this bundle is located
|
|
*/
|
|
public function getParent(): ?Bundle
|
|
{
|
|
if (!isset($this->parent)) {
|
|
$parts = preg_split('#/#', $this->path, -1, PREG_SPLIT_NO_EMPTY);
|
|
|
|
if (count($parts) > 0) {
|
|
$this->parent = new Bundle($parts[count($parts) - 1], $this->disk);
|
|
} else {
|
|
$this->parent = null;
|
|
}
|
|
}
|
|
|
|
return $this->parent;
|
|
}
|
|
|
|
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) && $section->getPath() !== $this->getPath()) {
|
|
$title .= ' - ' . $section->getArticleTitle();
|
|
}
|
|
|
|
return $title;
|
|
}
|
|
|
|
/**
|
|
* 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 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
|
|
{
|
|
$start = microtime(true);
|
|
$bundles = [];
|
|
|
|
if ($recursive) {
|
|
$bundles = $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 {
|
|
$directories = $disk->directories($path);
|
|
|
|
foreach ($directories as $directory) {
|
|
$bundle = new Bundle($directory, $disk);
|
|
|
|
if ($bundle->exists()) {
|
|
$bundles[] = $bundle;
|
|
}
|
|
}
|
|
}
|
|
|
|
$finish = microtime(true);
|
|
$diff = $finish - $start;
|
|
|
|
Log::debug('findBundles', [
|
|
'path' => $path,
|
|
'duration' => $diff,
|
|
]);
|
|
|
|
return $bundles;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|