1
0
cms11/app/Classes/Bundle.php

410 lines
11 KiB
PHP
Raw Normal View History

2024-04-17 11:41:10 +02:00
<?php
namespace App\Classes;
2024-04-17 15:29:12 +02:00
use App\Classes\Traits\ManagesAttachments;
2024-04-17 11:41:10 +02:00
use App\Classes\Traits\ManagesMarkdown;
use App\Classes\Traits\ManagesMetadata;
use App\Contracts\Bundles;
use App\Services\BundleRenderers\Facades\BundleRenderer;
2024-04-25 00:57:06 +02:00
use Carbon\Carbon;
use Exception;
2024-04-17 11:41:10 +02:00
use Illuminate\Filesystem\FilesystemAdapter;
2024-04-25 17:11:12 +02:00
use Illuminate\Support\Collection;
2024-05-09 22:24:59 +02:00
use Illuminate\Support\Facades\Cache;
2024-04-17 11:41:10 +02:00
use Illuminate\Support\Str;
2024-04-18 00:43:44 +02:00
use League\Flysystem\StorageAttributes;
2024-04-17 11:41:10 +02:00
class Bundle implements Bundles
2024-04-17 11:41:10 +02:00
{
use ManagesAttachments,
ManagesMarkdown,
ManagesMetadata;
2024-04-17 11:41:10 +02:00
protected string $dataDir;
2024-04-17 16:18:39 +02:00
protected int $currentPage = 1;
2024-04-18 00:43:44 +02:00
2024-04-24 00:47:46 +02:00
protected ?Bundle $section;
protected ?Bundle $parent;
2024-04-25 00:57:06 +02:00
protected ?array $directChildren;
2024-04-17 11:41:10 +02:00
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;
2024-04-17 11:41:10 +02:00
}
/**
* Return bundle's path
2024-04-17 11:41:10 +02:00
*/
public function getPath(): string
2024-04-17 11:41:10 +02:00
{
return $this->path;
2024-04-17 11:41:10 +02:00
}
/**
* Return bundle's data dir
*/
public function getDataDir(): string
{
return $this->dataDir;
}
2024-04-17 11:41:10 +02:00
/**
* Return bundle's filesystem instance
2024-04-17 11:41:10 +02:00
*/
public function getDisk(): FilesystemAdapter
2024-04-17 11:41:10 +02:00
{
return $this->disk;
}
/**
* Return the section where this bundle is located
*/
public function getSection(): ?Bundle
{
2024-04-24 00:47:46 +02:00
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)) {
$parentPath = dirname($this->getPath());
if ($parentPath === $this->getPath()) {
2024-04-24 00:47:46 +02:00
$this->parent = null;
} else {
$this->parent = new Bundle($parentPath, $this->getDisk());
2024-04-24 00:47:46 +02:00
}
}
2024-04-24 00:47:46 +02:00
return $this->parent;
}
2024-04-25 00:57:06 +02:00
/**
* Return an array containing direct children of this bundle
*/
public function getDirectChildren(): array
{
if (!isset($this->directChildren)) {
$this->directChildren = static::findBundles($this->getDisk(), $this->getPath());
}
return $this->directChildren;
}
2024-04-24 14:20:20 +02:00
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;
}
2024-04-25 17:11:12 +02:00
/**
* Return "virtual" metadata, which is a Collection of extended metadata,
* with additions and corrections from the "metadata.json" file
*/
public function virtualMetadata(): Collection
{
$mergedData = [];
foreach ($this->metadata()->get('virtualMetadata', []) as $additionalFile) {
$mergedData = array_merge_recursive($mergedData, $this->metadata($additionalFile)->all() ?? []);
}
2024-04-27 07:40:10 +02:00
$replacedData = array_merge_recursive($mergedData, $this->metadata('metadata')->all() ?? []);
2024-04-25 17:11:12 +02:00
return collect($replacedData);
}
/**
* Return a boolean value indicating if there already is a bundle in
* specified path
*/
public function exists(): bool
{
return $this->markdown()->exists();
}
2024-04-17 11:41:10 +02:00
/**
* Load everything
*/
public function load(): self
{
$this->loadAttachments();
$this->loadMetadata();
$this->loadMarkdown();
return $this;
}
2024-04-17 11:41:10 +02:00
/**
* Store all files of the bundle
*/
2024-05-05 17:31:32 +02:00
public function save(): bool
{
2024-05-05 17:16:12 +02:00
$attachmentsSaved = $this->saveAttachments();
$metadataSaved = $this->saveMetadata();
$markdownSaved = $this->saveMarkdown();
2024-04-26 23:05:49 +02:00
if (
2024-05-05 17:16:12 +02:00
$attachmentsSaved
|| $metadataSaved
|| $markdownSaved
2024-04-26 23:05:49 +02:00
) {
2024-04-26 23:26:55 +02:00
$this->touch();
2024-05-05 17:31:32 +02:00
return true;
2024-04-26 23:05:49 +02:00
}
2024-05-05 17:31:32 +02:00
return false;
2024-04-17 11:41:10 +02:00
}
/**
* Repair bundle
2024-04-17 11:41:10 +02:00
*/
2024-05-05 17:31:32 +02:00
public function repair(): bool
2024-04-17 11:41:10 +02:00
{
$this->load();
$this->repairCover();
$this->lintMarkdown();
$this->repairAttachments();
2024-05-05 17:31:32 +02:00
return $this->save();
2024-04-17 11:41:10 +02:00
}
2024-04-18 00:43:44 +02:00
2024-05-09 22:24:59 +02:00
/**
* "Touches" a bundle, forgetting some cached data.
*/
public function touch(bool $ignoreFeed = false)
2024-04-26 23:26:55 +02:00
{
2024-05-09 22:24:59 +02:00
if (!$ignoreFeed) {
2024-05-09 23:11:48 +02:00
Cache::forget('feed');
2024-05-09 22:24:59 +02:00
}
$cacheKey = sprintf('render_%s', Str::slug($this->getPath()));
2024-05-09 23:02:20 +02:00
Cache::forget($cacheKey);
2024-05-09 22:24:59 +02:00
$parent = $this->getParent();
if (!empty($parent)) {
// Always ignore feed when touching parent bundles
$parent->touch(true);
}
2024-05-09 22:24:59 +02:00
//TODO: Touch bundles linking this one
2024-04-26 23:26:55 +02:00
}
2024-05-12 00:07:04 +02:00
public function render(bool $ignoreCache = false)
{
$renderer = BundleRenderer::getBundleRendererFor($this);
2024-05-12 00:07:04 +02:00
$result = $renderer->render($ignoreCache);
2024-04-26 23:05:49 +02:00
return $result;
}
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, bool $sortByDate = true): array
2024-04-18 00:43:44 +02:00
{
2024-04-24 00:47:46 +02:00
$bundles = [];
2024-04-18 00:43:44 +02:00
if ($recursive) {
2024-04-24 00:47:46 +02:00
$bundles = $disk
2024-04-18 00:43:44 +02:00
->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;
}
}
}
2024-04-24 00:47:46 +02:00
if ($sortByDate) {
$bundles = collect($bundles)
->sortBy([
fn (Bundle $a, Bundle $b) => Carbon::parse($a->metadata()->get('date'))
->gt(Carbon::parse($b->metadata()->get('date'))),
fn (Bundle $a, Bundle $b) => Carbon::parse($b->metadata()->get('date'))
->gt(Carbon::parse($a->metadata()->get('date'))),
])
->toArray();
}
2024-04-25 00:57:06 +02:00
2024-04-24 00:47:46 +02:00
return $bundles;
2024-04-18 00:43:44 +02:00
}
public static function getFeedItems(FilesystemAdapter $disk)
{
$subBundles = Bundle::findBundles($disk, '/', true);
$subBundles = collect($subBundles)
->filter(fn (Bundle $bundle) => !empty($bundle->metadata()->get('date')))
->sort(function (Bundle $bundleA, Bundle $bundleB) {
return Carbon::parse($bundleA->metadata()->get('date'))->lt(Carbon::parse($bundleB->metadata()->get('date')));
})
->map(fn (Bundle $bundle) => $bundle->load())
->take(10);
return $subBundles;
}
public static function renderFeed(FilesystemAdapter $disk)
{
2024-05-09 23:11:48 +02:00
return Cache::rememberForever('feed', function () use ($disk) {
2024-05-09 22:24:59 +02:00
$lastBundles = static::getFeedItems($disk);
return [
'/index.xml' => (string) view('feed', [
'bundles' => $lastBundles,
'lastBuildDate' => now()->toRssString(),
]),
];
});
}
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
{
2024-04-26 21:55:05 +02:00
if ($path === './' || $path === '' || $path === '.') {
return '/';
}
$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);
}
2024-04-17 11:41:10 +02:00
}