Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements to bassets #19

Merged
merged 1 commit into from
Feb 20, 2023
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
112 changes: 84 additions & 28 deletions src/AssetManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace DigitallyHappy\Assets;

use Exception;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class AssetManager
Expand All @@ -11,16 +12,18 @@ class AssetManager
const TYPE_SCRIPT = 'script';

const STATUS_DISABLED = 'Cache CDN is disabled in the configuration.';
const STATUS_LOCAL = 'Asset is not in a CDN.';
const STATUS_INVALID = 'Asset is not in a CDN or local filesystem.';
const STATUS_IN_CACHE = 'Asset was already in cache.';
const STATUS_DOWNLOADED = 'Asset downloaded.';
const STATUS_NO_ACTION = 'Asset was not downloaded, falling back to CDN.';
const STATUS_NO_ACTION = 'Asset was not internalized, falling back to provided path.';

private $loaded;
private $disk;

public function __construct()
{
$this->loaded = [];
$this->disk = Storage::disk(config('digitallyhappy.assets.disk'));
}

/**
Expand Down Expand Up @@ -123,7 +126,7 @@ public function loaded(): array
}

/**
* Localize a CDN asset.
* Internalize a CDN or local asset.
*
* @param string $asset
* @param mixed $output
Expand All @@ -134,66 +137,119 @@ public function loaded(): array
public function basset(string $asset, mixed $output = true, array $attributes = [], string $type = null): string
{
// Valiate user configuration
if (! config('digitallyhappy.assets.cache_cdns')) {
if (! config('digitallyhappy.assets.cache')) {
$output && $this->echoFile($asset, $attributes, $type);

return self::STATUS_DISABLED;
}

// Make sure asset() is removed
$asset = str_replace(asset(''), '', $asset);

// Validate the asset comes from a CDN
if (substr($asset, 0, 4) !== 'http') {
// Validate the asset is an aboslute path or a CDN
if (! str_starts_with($asset, base_path()) && ! str_starts_with($asset, 'http')) {
$output && $this->echoFile($asset, $attributes, $type);

return self::STATUS_LOCAL;
return self::STATUS_INVALID;
}

// Override asset in case output is a string
if (is_string($output)) {
$asset = $output;
}
$path = is_string($output) ? $output : $asset;

$assetSlug = str_replace(['http://', 'https://', '://', '<', '>', ':', '"', '|', '?', "\0", '*', '`', ';', "'", '+'], '', $asset);
// Remove absolute path
$path = str_replace(base_path(), '', $path);

$localizedFilePath = Str::of(config('digitallyhappy.assets.cache_path'))->trim('\\/')->append("/$assetSlug");
$localizedUrl = Str::of(config('digitallyhappy.assets.cache_public_path'))->trim('\\/')->append("/$assetSlug");
$localizedPath = $localizedFilePath->beforeLast('/');
// Get asset paths
[$path, $url] = $this->getAssetPaths($path);

// Check if asset exists in bassets folder
if (is_file($localizedFilePath)) {
$output && $this->echoFile($localizedUrl, $attributes, $type);
if ($this->disk->exists($path)) {
$output && $this->echoFile($url, $attributes, $type);

return self::STATUS_IN_CACHE;
}

// Create the directory
if (! is_dir($localizedPath)) {
mkdir($localizedPath, recursive:true);
}

try {
// Download file
// Download/copy file content
$content = file_get_contents($asset);

// Clean source map
$content = preg_replace('/sourceMappingURL=/', '', $content);

$result = file_put_contents($localizedFilePath, $content);
$result = $this->disk->put($path, $content);

} catch (Exception $e) {
$result = false;
}

if ($result) {
$output && $this->echoFile($localizedUrl, $attributes, $type);
$output && $this->echoFile($url, $attributes, $type);

return self::STATUS_DOWNLOADED;
}

// Fallback to the CDN
// Fallback to the CDN/path
$output && $this->echoFile($asset, $attributes, $type);

return self::STATUS_NO_ACTION;
}

/**
* Internalize a basset code block
*
* @param string $asset
* @param string $code
* @return void
*/
public function bassetBlock(string $asset, string $code)
{
// Valiate user configuration
if (! config('digitallyhappy.assets.cache')) {
echo $code;
return;
}

// Get asset paths
[$path, $url] = $this->getAssetPaths($asset);

// Check if asset exists in bassets folder
if ($this->disk->exists($path)) {
return $this->echoFile($url);
}

// Store the file
// clean the tags and empty lines
$cleanCode = preg_replace('`\A[ \t]*\r?\n|\r?\n[ \t]*\Z`', '', strip_tags($code));

// clean the left padding
preg_match("/^\s*/", $cleanCode, $matches);
$cleanCode = preg_replace('/^'.($matches[0] ?? '').'/m', '', $cleanCode);

try {
$result = $this->disk->put($path, $cleanCode);
} catch (Exception $e) {
$result = false;
}

if ($result) {
return $this->echoFile($url);
}

// Fallback to the code
echo $code;
}

/**
* Returns the asset proper path and url
*
* @param string $asset
* @return array
*/
private function getAssetPaths(string $asset): array
{
$path = Str::of(config('digitallyhappy.assets.path'))->finish('/')->append(str_replace(['http://', 'https://', '://', '<', '>', ':', '"', '|', '?', "\0", '*', '`', ';', "'", '+'], '', $asset));
$url = $this->disk->url($path);

return [
$path,
$url,
];
}
}
10 changes: 10 additions & 0 deletions src/AssetsServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,16 @@ protected function registerBladeDirectives()
$bladeCompiler->directive('basset', function (string $parameter): string {
return "<?php Assets::basset({$parameter}); ?>";
});

$bladeCompiler->directive('bassetBlock', function (string $parameter): string {
$filePath = Str::of($parameter)->trim("'")->trim('"')->trim('`')->before('?')->before('#');

return "<?php \$bassetBlock = '{$filePath}'; ob_start(); ?>";
});

$bladeCompiler->directive('endBassetBlock', function (): string {
return '<?php Assets::bassetBlock($bassetBlock, ob_get_clean()); ?>';
});
});
}

Expand Down
12 changes: 9 additions & 3 deletions src/config/digitallyhappy/assets.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<?php

return [
'cache_cdns' => true,
'cache_path' => storage_path('app/public/bassets'),
'cache_public_path' => 'storage/bassets',
// cache assets
'cache' => true,

// disk and path where to store bassets
'disk' => 'public',
'path' => 'bassets',

// view paths that may use @basset
// used to internalize assets in advance with artisan basset:internalize
'view_paths' => [
resource_path('views'),
base_path('vendor/backpack/crud/src/resources'),
Expand Down