diff --git a/config/assets.php b/config/assets.php index 2618b48e33b..bd590ade959 100644 --- a/config/assets.php +++ b/config/assets.php @@ -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 diff --git a/config/stache.php b/config/stache.php index 6cca12baba0..ba2250480c5 100644 --- a/config/stache.php +++ b/config/stache.php @@ -93,6 +93,7 @@ 'assets' => [ 'class' => Stores\AssetsStore::class, + 'directory' => base_path('content/assets'), ], 'users' => [ diff --git a/src/Assets/Asset.php b/src/Assets/Asset.php index a7181e62447..8c8e7233972 100644 --- a/src/Assets/Asset.php +++ b/src/Assets/Asset.php @@ -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; @@ -256,8 +258,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()); @@ -303,13 +310,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()); } @@ -319,7 +343,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() @@ -677,7 +706,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); @@ -771,7 +805,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; } diff --git a/tests/Assets/AssetTest.php b/tests/Assets/AssetTest.php index 10b6f055eed..904480b406c 100644 --- a/tests/Assets/AssetTest.php +++ b/tests/Assets/AssetTest.php @@ -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; @@ -709,6 +710,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() { @@ -1238,6 +1262,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_renames() { diff --git a/tests/TestCase.php b/tests/TestCase.php index 7836f9017d5..6ada8770833 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -110,6 +110,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');