Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions formwork/src/Assets/Asset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<?php

namespace Formwork\Assets;

use Formwork\Assets\Exceptions\AssetNotFoundException;
use Formwork\Utils\FileSystem;
use Formwork\Utils\Uri;

class Asset
{
/**
* Asset file path
*/
private string $path;

/**
* Asset URI
*/
private string $uri;

/**
* Asset version (based on last modification time)
*/
private string $version;

/**
* Asset integrity hash
*/
private string $integrityHash;

/**
* Asset MIME type
*/
private string $mimeType;

public function __construct(string $path, string $uri)
{
$this->path = FileSystem::normalizePath($path);
$this->uri = Uri::normalize($uri);

if (!FileSystem::isFile($this->path)) {
throw new AssetNotFoundException(sprintf('Asset file "%s" not found', $this->path));
}
}

/**
* Get asset path
*/
public function path(): string
{
return $this->path;
}

/**
* Get asset URI
*/
public function uri(bool $includeVersion = false): string
{
$uri = $this->uri;
if ($includeVersion) {
$uri .= '?v=' . $this->version();
}
return $uri;
}

/**
* Get asset version
*/
public function version(): string
{
return $this->version ??= dechex(FileSystem::lastModifiedTime($this->path));
}

/**
* Get asset integrity hash
*/
public function integrityHash(): string
{
return $this->integrityHash ??= 'sha256-' . base64_encode(hash('sha256', FileSystem::read($this->path), true));
}

/**
* Get asset MIME type
*/
public function mimeType(): string
{
return $this->mimeType ??= FileSystem::mimeType($this->path);
}

/**
* Get asset content
*/
public function content(): string
{
return FileSystem::read($this->path);
}

/**
* Get asset content as base64 encoded string
*/
public function toBase64(): string
{
return 'data:' . $this->mimeType() . ';base64,' . base64_encode($this->content());
}
}
45 changes: 45 additions & 0 deletions formwork/src/Assets/AssetCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php

namespace Formwork\Assets;

use Formwork\Data\AbstractCollection;
use Formwork\Utils\Str;

class AssetCollection extends AbstractCollection
{
protected bool $associative = true;

protected ?string $dataType = Asset::class;

protected bool $mutable = true;

/**
* Get stylesheets from the collection
*/
public function stylesheets(): static
{
return $this->filter(function (Asset $asset) {
return $asset->mimeType() === 'text/css';
});
}

/**
* Get scripts from the collection
*/
public function scripts(): static
{
return $this->filter(function (Asset $asset) {
return $asset->mimeType() === 'text/javascript';
});
}

/**
* Get images from the collection
*/
public function images(): static
{
return $this->filter(function (Asset $asset) {
return Str::startsWith($asset->mimeType(), 'image/');
});
}
}
63 changes: 39 additions & 24 deletions formwork/src/Assets/Assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Formwork\Assets;

use Formwork\Utils\FileSystem;
use Formwork\Utils\Path;
use Formwork\Utils\Str;
use Formwork\Utils\Uri;

Expand All @@ -18,50 +19,64 @@ class Assets
*/
protected string $baseUri;

/**
* Asset collection
*/
private AssetCollection $collection;

public function __construct(string $basePath, string $baseUri)
{
$this->basePath = FileSystem::normalizePath($basePath);
$this->baseUri = Uri::normalize(Str::append($baseUri, '/'));
$this->collection = new AssetCollection();
}

/**
* Get asset version, if possible, based on its last modified time
*
* @param string $path Requested asset path
* Add an asset to the collection
*/
public function version(string $path): ?string
public function add(string $key): void
{
$file = FileSystem::joinPaths($this->basePath, $path);
if (FileSystem::exists($file)) {
return dechex(FileSystem::lastModifiedTime($file));
if (!$this->collection->has($key)) {
$path = FileSystem::joinPaths($this->basePath, Path::resolve($key, '/', DIRECTORY_SEPARATOR));
$uri = Path::join([$this->baseUri, Path::resolve($key, '/')]);
$this->collection->set($key, new Asset($path, $uri));
}
return null;
}

/**
* Get a SHA-256 integrity hash for an asset if possible
* Get an asset from the collection
*/
public function integrityHash(string $path): ?string
public function get(string $key): Asset
{
$file = FileSystem::joinPaths($this->basePath, $path);
if (FileSystem::exists($file)) {
return 'sha256-' . base64_encode(hash('sha256', FileSystem::read($file), true));
if (!$this->collection->has($key)) {
$path = FileSystem::joinPaths($this->basePath, Path::resolve($key, '/', DIRECTORY_SEPARATOR));
$uri = Path::join([$this->baseUri, Path::resolve($key, '/')]);
$this->collection->set($key, new Asset($path, $uri));
}
return null;
return $this->collection->get($key);
}

/**
* Get asset URI optionally followed by a version query parameter
*
* @param string $path Requested asset path
* @param bool $includeVersion Whether to include asset version
* Get stylesheets from the assets collection
*/
public function uri(string $path, bool $includeVersion = false): string
public function stylesheets(): AssetCollection
{
$uri = $this->baseUri . trim($path, '/');
if ($includeVersion && ($version = $this->version($path)) !== null) {
$uri .= '?v=' . $version;
}
return $uri;
return $this->collection->stylesheets();
}

/**
* Get scripts from the assets collection
*/
public function scripts(): AssetCollection
{
return $this->collection->scripts();
}

/**
* Get images from the assets collection
*/
public function images(): AssetCollection
{
return $this->collection->images();
}
}
7 changes: 7 additions & 0 deletions formwork/src/Assets/Exceptions/AssetNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace Formwork\Assets\Exceptions;

use Formwork\Utils\Exceptions\FileNotFoundException;

class AssetNotFoundException extends FileNotFoundException {}
2 changes: 1 addition & 1 deletion formwork/src/Panel/Panel.php
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ public function modals(): Modals
*/
public function assets(): Assets
{
return $this->assets ?? ($this->assets = new Assets($this->config->get('system.panel.paths.assets'), $this->uri('/assets/')));
return $this->assets ??= new Assets($this->config->get('system.panel.paths.assets'), $this->uri('/assets/'));
}

public function colorScheme(): ColorScheme
Expand Down
6 changes: 2 additions & 4 deletions panel/methods.php
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
<?php

use Formwork\Config\Config;
use Formwork\Panel\Panel;
use Formwork\Utils\FileSystem;

return function (Config $config, Panel $panel) {
return function (Panel $panel) {
return [
'assets' => $panel->assets(...),

'modals' => $panel->modals(...),

'icon' => fn(string $icon) => FileSystem::read(FileSystem::joinPaths($config->get('system.panel.paths.assets'), '/icons/svg/', $icon . '.svg')),
'icon' => fn(string $icon) => $panel->assets()->get('/icons/svg/' . $icon . '.svg')->content(),
];
};
8 changes: 4 additions & 4 deletions panel/views/errors/error.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<title><?php if (!empty($title)) : ?><?= $title ?> | <?php endif ?>Formwork</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->uri('images/icon.svg') ?>">
<link rel="alternate icon" href="<?= $this->assets()->uri('images/icon.png') ?>">
<link rel="stylesheet" href="<?= $this->assets()->uri('css/panel.min.css', true) ?>">
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->get('images/icon.svg')->uri() ?>">
<link rel="alternate icon" href="<?= $this->assets()->get('images/icon.png')->uri() ?>">
<link rel="stylesheet" href="<?= $this->assets()->get('css/panel.min.css')->uri(includeVersion: true) ?>">
</head>

<body>
Expand All @@ -18,7 +18,7 @@
<span class="error-code"><?= $code ?></span>
<span class="error-status"><?= $status ?></span>
</h1>
<img class="logo" src="<?= $this->assets()->uri('images/icon.svg') ?>">
<img class="logo" src="<?= $this->assets()->get('images/icon.svg')->uri() ?>">
<h2><?= $heading ?></h2>
<p><?= $description ?></p>
<?php if (isset($action)) : ?><a class="action" href="<?= $action['href'] ?>"><?= $action['label'] ?></a><?php endif ?>
Expand Down
6 changes: 3 additions & 3 deletions panel/views/layouts/login.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="robots" content="noindex, nofollow">
<meta name="color-scheme" content="<?= $panel->colorScheme()->getCompatibleSchemes() ?>">
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->uri('images/icon.svg') ?>">
<link rel="alternate icon" href="<?= $this->assets()->uri('images/icon.png') ?>">
<link rel="stylesheet" href="<?= $this->assets()->uri('css/panel.min.css', true) ?>">
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->get('images/icon.svg')->uri() ?>">
<link rel="alternate icon" href="<?= $this->assets()->get('images/icon.png')->uri() ?>">
<link rel="stylesheet" href="<?= $this->assets()->get('css/panel.min.css')->uri(includeVersion: true) ?>">
</head>

<body>
Expand Down
10 changes: 7 additions & 3 deletions panel/views/layouts/panel.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@
<?php foreach ($panel->notifications() as $notification) : ?>
<meta name="notification" content='<?= $this->escapeAttr(Formwork\Parsers\Json::encode($notification)) ?>'>
<?php endforeach ?>
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->uri('images/icon.svg') ?>">
<link rel="alternate icon" href="<?= $this->assets()->uri('images/icon.png') ?>">
<link rel="stylesheet" href="<?= $this->assets()->uri('css/panel.min.css', true) ?>">
<link rel="icon" type="image/svg+xml" href="<?= $this->assets()->get('images/icon.svg')->uri() ?>">
<link rel="alternate icon" href="<?= $this->assets()->get('images/icon.png')->uri() ?>">
<?php $this->assets()->add('css/panel.min.css') ?>
<?php foreach ($this->assets()->stylesheets() as $stylesheet): ?>
<link rel="stylesheet" href="<?= $stylesheet->uri(includeVersion: true) ?>">
<?php endforeach ?>
</head>

<body>
Expand All @@ -29,6 +32,7 @@
<?php foreach ($this->modals() as $modal) : ?>
<?php $this->insert('modals.modal', ['modal' => $modal]) ?>
<?php endforeach ?>
<?php $this->assets()->add('js/app.min.js') ?>
<?php $this->insert('partials.scripts') ?>
</body>

Expand Down
5 changes: 4 additions & 1 deletion panel/views/partials/scripts.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<script src="<?= $this->assets()->uri('js/app.min.js', true) ?>" integrity="<?= $this->assets()->integrityHash('js/app.min.js') ?>"></script>
<?php foreach ($this->assets()->scripts() as $script): ?>
<script src="<?= $script->uri(includeVersion: true) ?>" integrity="<?= $script->integrityHash() ?>"></script>
<?php endforeach ?>

<script>
Formwork.app.load(<?= $appConfig ?>);
</script>
2 changes: 1 addition & 1 deletion panel/views/partials/sidebar.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<button type="button" class="button button-link sidebar-toggle hide-from-md" aria-label="<?= $this->translate('panel.navigation.toggle') ?>"><?= $this->icon('bars') ?></button>
<div class="sidebar show-from-md">
<div class="logo"><a href="<?= $panel->uri('/dashboard/') ?>"><img src="<?= $this->assets()->uri('images/icon.svg', true) ?>" alt=""> Formwork</a> <span class="show-from-md text-color-gray-medium text-size-xs"><?= $app::VERSION ?></span></div>
<div class="logo"><a href="<?= $panel->uri('/dashboard/') ?>"><img src="<?= $this->assets()->get('images/icon.svg')->uri(includeVersion: true) ?>" alt=""> Formwork</a> <span class="show-from-md text-color-gray-medium text-size-xs"><?= $app::VERSION ?></span></div>
<a href="<?= $panel->uri('/users/' . $panel->user()->username() . '/profile/') ?>">
<div class="panel-user-card">
<div class="panel-user-image">
Expand Down
6 changes: 3 additions & 3 deletions site/templates/layouts/site.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
<?= $this->insert('_meta') ?>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>馃毀</text></svg>">
<link rel="stylesheet" type="text/css" href="<?= $this->assets()->uri('css/style.min.css') ?>">
<script src="<?= $this->assets()->uri('js/script.min.js') ?>"></script>
<link rel="stylesheet" type="text/css" href="<?= $this->assets()->get('css/style.min.css')->uri() ?>">
<script src="<?= $this->assets()->get('js/script.min.js')->uri() ?>"></script>
</head>

<body>
Expand All @@ -21,4 +21,4 @@
</footer>
</body>

</html>
</html>