Skip to content

Commit

Permalink
Merge pull request #585 from getformwork/feature/file-meta
Browse files Browse the repository at this point in the history
Add support for files metadata
  • Loading branch information
giuscris authored Oct 13, 2024
2 parents 8d2d352 + b2295e6 commit cca89c3
Show file tree
Hide file tree
Showing 25 changed files with 386 additions and 89 deletions.
1 change: 1 addition & 0 deletions formwork/config/system.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fields:

files:
allowedExtensions: []
metadataExtension: .meta.yaml

images:
jpegQuality: 85
Expand Down
32 changes: 31 additions & 1 deletion formwork/src/Files/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,64 @@

namespace Formwork\Files;

use Formwork\App;
use Formwork\Data\Contracts\Arrayable;
use Formwork\Files\Exceptions\FileUriGenerationException;
use Formwork\Model\Attributes\ReadonlyModelProperty;
use Formwork\Model\Model;
use Formwork\Parsers\Yaml;
use Formwork\Utils\FileSystem;
use Formwork\Utils\MimeType;
use Formwork\Utils\Str;
use RuntimeException;
use Stringable;

class File implements Arrayable, Stringable
class File extends Model implements Arrayable, Stringable
{
protected const MODEL_IDENTIFIER = 'file';

protected const SCHEME_IDENTIFIER = 'files.file';

/**
* File name
*/
#[ReadonlyModelProperty]
protected string $name;

/**
* File extension
*/
#[ReadonlyModelProperty]
protected string $extension;

/**
* File MIME type
*/
#[ReadonlyModelProperty]
protected string $mimeType;

/**
* File type in a human-readable format
*/
#[ReadonlyModelProperty]
protected ?string $type = null;

/**
* File size in a human-readable format
*/
#[ReadonlyModelProperty]
protected string $size;

/**
* File last modified time
*/
#[ReadonlyModelProperty]
protected int $lastModifiedTime;

/**
* File hash
*/
#[ReadonlyModelProperty]
protected string $hash;

protected FileUriGenerator $uriGenerator;
Expand All @@ -58,6 +73,7 @@ public function __construct(protected string $path)
{
$this->name = basename($path);
$this->extension = FileSystem::extension($path);
$this->loadData();
}

public function __toString(): string
Expand Down Expand Up @@ -190,6 +206,20 @@ public function toArray(): array
];
}

private function loadData(): void
{
$app = App::instance();

$this->scheme = $app->schemes()->get(static::SCHEME_IDENTIFIER);
$this->fields = $this->scheme->fields();

$metadataFile = $this->path . $app->config()->get('system.files.metadataExtension');

$this->data = FileSystem::exists($metadataFile) ? Yaml::parseFile($metadataFile) : [];

$this->fields->setValues($this->data);
}

/**
* Match MIME type with an array of extensions
*
Expand Down
12 changes: 9 additions & 3 deletions formwork/src/Images/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,28 @@
use Formwork\Images\Transform\Sharpen;
use Formwork\Images\Transform\Smoothen;
use Formwork\Images\Transform\TransformCollection;
use Formwork\Model\Attributes\ReadonlyModelProperty;
use Formwork\Utils\FileSystem;
use Formwork\Utils\MimeType;
use Formwork\Utils\Uri;
use RuntimeException;

class Image extends File
{
protected string $path;
protected const MODEL_IDENTIFIER = 'image';

protected const SCHEME_IDENTIFIER = 'files.image';

#[ReadonlyModelProperty]
protected AbstractHandler $handler;

#[ReadonlyModelProperty]
protected ImageInfo $info;

#[ReadonlyModelProperty]
protected TransformCollection $transforms;

protected string $mimeType;

#[ReadonlyModelProperty]
protected ?string $type = 'image';

/**
Expand Down Expand Up @@ -312,6 +317,7 @@ public function process(?string $mimeType = null, bool $forceCache = false): Ima
}

$image = new Image($path, $this->options);
$image->data = $this->data;
$image->uriGenerator = $this->uriGenerator;
$image->transforms = $this->transforms;
$image->handler = $this->handler;
Expand Down
10 changes: 10 additions & 0 deletions formwork/src/Model/Attributes/ReadonlyModelProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Formwork\Model\Attributes;

use Attribute;

#[Attribute(Attribute::TARGET_PROPERTY)]
class ReadonlyModelProperty
{
}
12 changes: 12 additions & 0 deletions formwork/src/Model/Model.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use Formwork\Data\Traits\DataMultipleGetter;
use Formwork\Data\Traits\DataMultipleSetter;
use Formwork\Fields\FieldCollection;
use Formwork\Model\Attributes\ReadonlyModelProperty;
use Formwork\Schemes\Scheme;
use Formwork\Utils\Arr;
use ReflectionAttribute;
use ReflectionProperty;

class Model implements Arrayable
Expand Down Expand Up @@ -104,6 +106,10 @@ public function get(string $key, mixed $default = null): mixed
public function set(string $key, mixed $value): void
{
if (property_exists($this, $key) && !(new ReflectionProperty($this, $key))->isPromoted()) {
if ($this->isReadonly($key)) {
throw new BadMethodCallException(sprintf('Cannot set readonly model property %s::$%s', static::class, $key));
}

// If defined use a setter
if (method_exists($this, $setter = 'set' . ucfirst($key))) {
$this->{$setter}($value);
Expand Down Expand Up @@ -140,4 +146,10 @@ public function data(): array
{
return $this->data;
}

private function isReadonly(string $property): bool
{
$attributes = (new ReflectionProperty($this, $property))->getAttributes(ReadonlyModelProperty::class, ReflectionAttribute::IS_INSTANCEOF);
return $attributes !== [];
}
}
9 changes: 7 additions & 2 deletions formwork/src/Pages/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -568,8 +568,13 @@ protected function loadFiles(): void
$languages[] = $language;
}
}
} elseif (in_array($extension, $config->get('system.files.allowedExtensions'), true)) {
$files[] = App::instance()->getService(FileFactory::class)->make(FileSystem::joinPaths($this->path, $file));
} else {
if (Str::endsWith($file, $config->get('system.files.metadataExtension'))) {
continue;
}
if (in_array($extension, $config->get('system.files.allowedExtensions'), true)) {
$files[] = App::instance()->getService(FileFactory::class)->make(FileSystem::joinPaths($this->path, $file));
}
}
}
}
Expand Down
50 changes: 50 additions & 0 deletions formwork/src/Panel/Controllers/PagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Formwork\Exceptions\TranslatedException;
use Formwork\Fields\Exceptions\ValidationException;
use Formwork\Fields\FieldCollection;
use Formwork\Files\File;
use Formwork\Files\FileUploader;
use Formwork\Http\Files\UploadedFile;
use Formwork\Http\JsonResponse;
Expand Down Expand Up @@ -509,10 +510,32 @@ public function file(RouteParams $routeParams): Response

$files = $page->files();
$file = $files->get($filename);

switch ($this->request->method()) {
case RequestMethod::GET:
$data = $file->data();

$file->fields()->setValues($data);

break;

case RequestMethod::POST:
$data = $this->request->input();

$file->fields()->setValues($data)->validate();

$this->updateFileMetadata($file, $file->fields());

$this->panel()->notify($this->translate('panel.files.metadata.updated'), 'success');

return $this->redirect($this->generateRoute('panel.pages.file', ['page' => $page->route(), 'filename' => $filename]));
}

$fileIndex = $files->indexOf($file);

$this->modal('renameFile');
$this->modal('deleteFile');
$this->modal('changes');

return new Response($this->view('pages.file', [
'title' => $file->name(),
Expand Down Expand Up @@ -586,6 +609,33 @@ protected function createPage(FieldCollection $fieldCollection): Page
return $this->site()->retrievePage($path);
}

protected function updateFileMetadata(File $file, FieldCollection $fieldCollection): void
{
$data = $file->data();

$scheme = $file->scheme();

$defaults = $scheme->fields()->pluck('default');

foreach ($fieldCollection as $field) {
if ($field->isEmpty() || (Arr::has($defaults, $field->name()) && Arr::get($defaults, $field->name()) === $field->value())) {
unset($data[$field->name()]);
continue;
}

$data[$field->name()] = $field->value();
}

$metaFile = $file->path() . $this->config->get('system.files.metadataExtension');

if ($data === [] && FileSystem::exists($metaFile)) {
FileSystem::delete($metaFile);
return;
}

FileSystem::write($metaFile, Yaml::encode($data));
}

/**
* Update a page
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;

class LinkBaseExtension implements ConfigurableExtensionInterface
class FormworkExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $configurationBuilder): void
{
$configurationBuilder->addSchema('formwork', Expect::structure([
'baseRoute' => Expect::string('/'),
'imageAltProperty' => Expect::string('alt'),
'baseRoute' => Expect::string('/'),
]));
}

public function register(EnvironmentBuilderInterface $environmentBuilder): void
{
$environmentBuilder->addEventListener(DocumentParsedEvent::class, new ImageAltProcessor($environmentBuilder->getConfiguration()));
$environmentBuilder->addEventListener(DocumentParsedEvent::class, new LinkBaseProcessor($environmentBuilder->getConfiguration()));
}
}
38 changes: 38 additions & 0 deletions formwork/src/Parsers/Extensions/CommonMark/ImageAltProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Formwork\Parsers\Extensions\CommonMark;

use Formwork\App;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\CommonMark\Node\Inline\Image;
use League\Config\ConfigurationInterface;

class ImageAltProcessor
{
public function __construct(protected ConfigurationInterface $configuration)
{
}

public function __invoke(DocumentParsedEvent $documentParsedEvent): void
{
foreach ($documentParsedEvent->getDocument()->iterator() as $node) {
if (!$node instanceof Image) {
continue;
}

$baseRoute = $this->configuration->get('formwork/baseRoute');

$site = App::instance()->site();

$uri = $node->getUrl();

$key = $this->configuration->get('formwork/imageAltProperty');

$alt = $site->findPage($baseRoute)?->files()->get($uri)?->get($key);

if ($alt !== null) {
$node->data->set('attributes/alt', $alt);
}
}
}
}
Loading

0 comments on commit cca89c3

Please sign in to comment.