Skip to content
12 changes: 12 additions & 0 deletions config/assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@

'cache_meta' => true,

/*
|--------------------------------------------------------------------------
| Metadata as Content
|--------------------------------------------------------------------------
|
| Asset metadata will be saved as content alongside the rest of the content.
| This is useful when wanting to track metadata changes in git while using
| another storage location for assets (ie. S3).
|
*/
'meta_as_content' => false,

/*
|--------------------------------------------------------------------------
| Focal Point Editor
Expand Down
1 change: 1 addition & 0 deletions config/stache.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@

'assets' => [
'class' => Stores\AssetsStore::class,
'directory' => base_path('content/assets'),
],

'users' => [
Expand Down
54 changes: 46 additions & 8 deletions src/Assets/Asset.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,10 @@
use Statamic\Facades;
use Statamic\Facades\AssetContainer as AssetContainerAPI;
use Statamic\Facades\Blink;
use Statamic\Facades\File;
use Statamic\Facades\Image;
use Statamic\Facades\Path;
use Statamic\Facades\Stache;
use Statamic\Facades\URL;
use Statamic\Facades\YAML;
use Statamic\GraphQL\ResolvesValues;
Expand Down Expand Up @@ -261,8 +263,13 @@ public function meta($key = null)
}

return $this->meta = $this->cacheStore()->rememberForever($this->metaCacheKey(), function () {
if ($contents = $this->disk()->get($path = $this->metaPath())) {
return YAML::file($path)->parse($contents);
$contents = match (config('statamic.assets.meta_as_content')) {
true => File::get($this->metaPath()),
false => $this->disk()->get($this->metaPath()),
};

if ($contents) {
return YAML::parse($contents);
}

$this->writeMeta($meta = $this->generateMeta());
Expand Down Expand Up @@ -308,13 +315,30 @@ public function generateMeta()

public function metaPath()
{
$path = dirname($this->path()).'/.meta/'.$this->basename().'.yaml';

return (string) Str::of($path)->replaceFirst('./', '')->ltrim('/');
return Str::of($this->path())
->dirname()
->finish('/') // Sometimes the dirname is just '.', so we ensure it ends with a slash
->replaceFirst('./', '')
->explode('/')
->when(
config('statamic.assets.meta_as_content'),
fn ($path) => collect([
Stache::store('assets')->directory(),
$this->container()->handle(),
])->concat($path),
fn ($path) => $path->push('.meta'),
)
->push($this->basename().'.yaml')
->filter() // Remove any empty segments
->implode('/');
}

protected function metaExists()
{
if (config('statamic.assets.meta_as_content')) {
return File::exists($this->metaPath());
}

return $this->container()->metaFiles()->contains($this->metaPath());
}

Expand All @@ -324,7 +348,12 @@ public function writeMeta($meta)

$contents = YAML::dump($meta);

$this->disk()->put($this->metaPath(), $contents);
if (config('statamic.assets.meta_as_content')) {
File::makeDirectory(dirname($this->metaPath()), 0755, true);
File::put($this->metaPath(), $contents);
} else {
$this->disk()->put($this->metaPath(), $contents);
}
}

public function metaCacheKey()
Expand Down Expand Up @@ -683,7 +712,12 @@ public function delete()
}

$this->disk()->delete($this->path());
$this->disk()->delete($this->metaPath());

if (config('statamic.assets.meta_as_content')) {
File::delete($this->metaPath());
} else {
$this->disk()->delete($this->metaPath());
}

Facades\Asset::delete($this);

Expand Down Expand Up @@ -779,7 +813,11 @@ public function move($folder, $filename = null)
$this->path($newPath);
$this->save();

$this->disk()->rename($oldMetaPath, $this->metaPath());
if (config('statamic.assets.meta_as_content')) {
File::move($oldMetaPath, $this->metaPath());
} else {
$this->disk()->rename($oldMetaPath, $this->metaPath());
}

return $this;
}
Expand Down
158 changes: 130 additions & 28 deletions tests/Assets/AssetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
use Statamic\Facades;
use Statamic\Facades\Antlers;
use Statamic\Facades\File;
use Statamic\Facades\Stache;
use Statamic\Facades\YAML;
use Statamic\Fields\Blueprint;
use Statamic\Fields\Fieldtype;
Expand All @@ -59,10 +60,12 @@ public function setUp(): void
config(['cache.default' => 'file']);
Cache::clear();

config(['filesystems.disks.test' => [
'driver' => 'local',
'root' => __DIR__.'/tmp',
]]);
config([
'filesystems.disks.test' => [
'driver' => 'local',
'root' => __DIR__.'/tmp',
],
]);

$this->container = (new AssetContainer)
->handle('test_container')
Expand Down Expand Up @@ -352,11 +355,13 @@ public static function reAddRemovedDataProvider()
'by calling set method' => [fn ($asset) => $asset->set('one', 'new-foo')],
'by calling data method' => [fn ($asset) => $asset->data(['one' => 'new-foo', 'two' => 'bar', 'three' => 'qux'])],
'by calling merge method' => [fn ($asset) => $asset->merge(['one' => 'new-foo', 'three' => 'qux'])],
'by calling __set() magically via property' => [function ($asset) {
$asset->one = 'new-foo';
'by calling __set() magically via property' => [
function ($asset) {
$asset->one = 'new-foo';

return $asset;
}],
return $asset;
},
],
];
}

Expand Down Expand Up @@ -635,8 +640,33 @@ public function it_checks_if_its_an_image_file()
public function it_checks_if_it_can_be_previewed_in_google_docs_previewer()
{
$extensions = [
'doc', 'docx', 'pages', 'txt', 'ai', 'psd', 'eps', 'ps', 'css', 'html', 'php', 'c', 'cpp', 'h', 'hpp', 'js',
'ppt', 'pptx', 'flv', 'tiff', 'ttf', 'dxf', 'xps', 'zip', 'rar', 'xls', 'xlsx',
'doc',
'docx',
'pages',
'txt',
'ai',
'psd',
'eps',
'ps',
'css',
'html',
'php',
'c',
'cpp',
'h',
'hpp',
'js',
'ppt',
'pptx',
'flv',
'tiff',
'ttf',
'dxf',
'xps',
'zip',
'rar',
'xls',
'xlsx',
];

foreach ($extensions as $ext) {
Expand Down Expand Up @@ -725,6 +755,29 @@ public function it_gets_existing_meta_data()
$this->assertEquals($expected, Cache::get($asset->metaCacheKey()));
}

#[Test]
public function it_gets_existing_meta_data_as_content()
{
config()->set('statamic.assets.meta_as_content', true);
$relativePath = 'foo/test.txt';
$metaFilePath = Stache::store('assets')->directory()."/test/{$relativePath}.yaml";

Storage::fake('test');
Storage::disk('test')->put($relativePath, '');

File::makeDirectory(dirname($metaFilePath), 0755, true);
File::put($metaFilePath, YAML::dump($data = [
'data' => ['foo' => 'bar'],
'size' => 123,
]));

$container = tap(Facades\AssetContainer::make('test')->disk('test'))->save();
$asset = (new Asset)->container($container)->path($relativePath);

$this->assertEquals($metaFilePath, $asset->metaPath());
$this->assertEquals($data, $asset->meta());
}

#[Test]
public function it_properly_merges_new_unsaved_data_to_meta()
{
Expand Down Expand Up @@ -1275,6 +1328,36 @@ public function it_doesnt_lowercase_moved_files_when_configured()
], $container->assets('/', true)->map->path()->all());
}

#[Test]
public function it_can_be_moved_to_another_folder_and_renamed_when_meta_as_content()
{
config()->set('statamic.assets.meta_as_content', true);

Storage::fake('test');
$disk = Storage::disk('test');
$disk->put('old/asset.txt', 'The asset contents');

$container = tap(Facades\AssetContainer::make('test')->disk('test'))->save();
$asset = tap($container->makeAsset('old/asset.txt')->data(['foo' => 'bar']))->save();
$meta = $asset->meta();

$metaPath = Stache::store('assets')->directory().'/'.$container->handle();

$this->assertFileExists("{$metaPath}/old/asset.txt.yaml");
$this->assertEquals(YAML::dump($asset->meta()), File::get("{$metaPath}/old/asset.txt.yaml"));
$this->assertEquals(['old/asset.txt'], $container->files()->all());

$return = $asset->move('new', 'asset2');

$this->assertEquals($return, $asset);
$disk->assertMissing('old/asset.txt');
$this->assertFileDoesNotExist("{$metaPath}/old/asset.txt.yaml");
$disk->assertExists('new/asset2.txt');
$this->assertFileExists($newMetaPath = "{$metaPath}/new/asset2.txt.yaml");
$this->assertEquals(YAML::dump($meta), File::get($newMetaPath));
$this->assertEquals(['new/asset2.txt'], $container->files()->all());
}

#[Test]
public function it_can_be_moved_uniquely_to_another_folder_when_conflict_exists()
{
Expand Down Expand Up @@ -1936,10 +2019,12 @@ public function it_can_upload_an_image_into_a_container_with_glide_config()
{
Event::fake();

config(['statamic.assets.image_manipulation.presets.small' => [
'w' => '15',
'h' => '15',
]]);
config([
'statamic.assets.image_manipulation.presets.small' => [
'w' => '15',
'h' => '15',
],
]);

$this->container->sourcePreset('small');

Expand Down Expand Up @@ -1982,9 +2067,11 @@ public function it_can_upload_an_image_into_a_container_with_new_extension_forma
{
Event::fake();

config(['statamic.assets.image_manipulation.presets.enforce_png' => [
$formatParam => 'png',
]]);
config([
'statamic.assets.image_manipulation.presets.enforce_png' => [
$formatParam => 'png',
],
]);

$this->container->sourcePreset('enforce_png');

Expand Down Expand Up @@ -2018,9 +2105,11 @@ public function it_normalizes_pjpg_format_to_jpg_extension_on_upload()
{
Event::fake();

config(['statamic.assets.image_manipulation.presets.progressive' => [
'fm' => 'pjpg',
]]);
config([
'statamic.assets.image_manipulation.presets.progressive' => [
'fm' => 'pjpg',
],
]);

$this->container->sourcePreset('progressive');

Expand Down Expand Up @@ -2105,10 +2194,12 @@ public function it_doesnt_process_or_error_when_uploading_non_glideable_file_wit
{
Event::fake();

config(['statamic.assets.image_manipulation.presets.small' => [
'w' => '15',
'h' => '15',
]]);
config([
'statamic.assets.image_manipulation.presets.small' => [
'w' => '15',
'h' => '15',
],
]);

$this->container->sourcePreset('small');

Expand Down Expand Up @@ -2418,10 +2509,21 @@ public function it_sends_a_download_response_with_a_different_name_and_custom_he
private function toArrayKeysWhenFileExists()
{
return [
'size', 'size_bytes', 'size_kilobytes', 'size_megabytes', 'size_gigabytes',
'size_b', 'size_kb', 'size_mb', 'size_gb',
'last_modified', 'last_modified_timestamp', 'last_modified_instance',
'focus', 'focus_css', 'mime_type',
'size',
'size_bytes',
'size_kilobytes',
'size_megabytes',
'size_gigabytes',
'size_b',
'size_kb',
'size_mb',
'size_gb',
'last_modified',
'last_modified_timestamp',
'last_modified_instance',
'focus',
'focus_css',
'mime_type',
];
}

Expand Down
1 change: 1 addition & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ protected function getEnvironmentSetUp($app)
$app['config']->set('statamic.stache.stores.globals.directory', __DIR__.'/__fixtures__/content/globals');
$app['config']->set('statamic.stache.stores.global-variables.directory', __DIR__.'/__fixtures__/content/globals');
$app['config']->set('statamic.stache.stores.asset-containers.directory', __DIR__.'/__fixtures__/content/assets');
$app['config']->set('statamic.stache.stores.assets.directory', __DIR__.'/__fixtures__/content/assets');
$app['config']->set('statamic.stache.stores.nav-trees.directory', __DIR__.'/__fixtures__/content/structures/navigation');
$app['config']->set('statamic.stache.stores.collection-trees.directory', __DIR__.'/__fixtures__/content/structures/collections');
$app['config']->set('statamic.stache.stores.form-submissions.directory', __DIR__.'/__fixtures__/content/submissions');
Expand Down
Loading