diff --git a/config/stache.php b/config/stache.php index d594c8f05ae..e0db82e05ca 100644 --- a/config/stache.php +++ b/config/stache.php @@ -70,6 +70,11 @@ 'directory' => base_path('content/globals'), ], + 'global-variables' => [ + 'class' => Stores\GlobalVariablesStore::class, + 'directory' => base_path('content/globals'), + ], + 'asset-containers' => [ 'class' => Stores\AssetContainersStore::class, 'directory' => base_path('content/assets'), diff --git a/src/Contracts/Globals/GlobalVariablesRepository.php b/src/Contracts/Globals/GlobalVariablesRepository.php new file mode 100644 index 00000000000..9115cf37233 --- /dev/null +++ b/src/Contracts/Globals/GlobalVariablesRepository.php @@ -0,0 +1,18 @@ +variables = $variables; + } +} diff --git a/src/Events/GlobalVariablesDeleted.php b/src/Events/GlobalVariablesDeleted.php new file mode 100644 index 00000000000..44afbf320c1 --- /dev/null +++ b/src/Events/GlobalVariablesDeleted.php @@ -0,0 +1,13 @@ +variables = $variables; + } +} diff --git a/src/Events/GlobalVariablesSaved.php b/src/Events/GlobalVariablesSaved.php new file mode 100644 index 00000000000..93a3538bf6f --- /dev/null +++ b/src/Events/GlobalVariablesSaved.php @@ -0,0 +1,13 @@ +variables = $variables; + } +} diff --git a/src/Events/GlobalVariablesSaving.php b/src/Events/GlobalVariablesSaving.php new file mode 100644 index 00000000000..df2996f536f --- /dev/null +++ b/src/Events/GlobalVariablesSaving.php @@ -0,0 +1,23 @@ +variables = $variables; + } + + /** + * Dispatch the event with the given arguments, and halt on first non-null listener response. + * + * @return mixed + */ + public static function dispatch() + { + return event(new static(...func_get_args()), [], true); + } +} diff --git a/src/Facades/GlobalVariables.php b/src/Facades/GlobalVariables.php new file mode 100644 index 00000000000..c99719851d1 --- /dev/null +++ b/src/Facades/GlobalVariables.php @@ -0,0 +1,22 @@ +saveOrDeleteLocalizations(); + foreach ($afterSaveCallbacks as $callback) { $callback($this); } @@ -106,8 +110,21 @@ public function save() return $this; } + protected function saveOrDeleteLocalizations() + { + $localizations = $this->localizations(); + + $localizations->each->save(); + + $this->freshLocalizations() + ->diffKeys($localizations) + ->each->delete(); + } + public function delete() { + $this->localizations()->each->delete(); + Facades\GlobalSet::delete($this); GlobalSetDeleted::dispatch($this); @@ -121,9 +138,9 @@ public function fileData() 'title' => $this->title(), ]; - if (! Site::hasMultiple()) { + if (! Site::hasMultiple() && ($variables = $this->in(Site::default()->handle()))) { $data['data'] = Arr::removeNullValues( - $this->in(Site::default()->handle())->data()->all() + $variables->data()->all() ); } @@ -132,7 +149,7 @@ public function fileData() public function makeLocalization($site) { - return (new Variables) + return app(Variables::class) ->globalSet($this) ->locale($site); } @@ -141,21 +158,21 @@ public function addLocalization($localization) { $localization->globalSet($this); - $this->localizations[$localization->locale()] = $localization; + $this->localizations()[$localization->locale()] = $localization; return $this; } public function removeLocalization($localization) { - unset($this->localizations[$localization->locale()]); + $this->localizations()->forget($localization->locale()); return $this; } public function in($locale) { - return $this->localizations[$locale] ?? null; + return $this->localizations()->get($locale); } public function inSelectedSite() @@ -180,7 +197,14 @@ public function existsIn($locale) public function localizations() { - return collect($this->localizations); + return Blink::once('global-set-localizations-'.$this->id(), function () { + return $this->freshLocalizations(); + }); + } + + private function freshLocalizations() + { + return GlobalVariables::whereSet($this->handle())->keyBy->locale(); } public function editUrl() diff --git a/src/Globals/Variables.php b/src/Globals/Variables.php index 7f9a4447410..10757f4efd5 100644 --- a/src/Globals/Variables.php +++ b/src/Globals/Variables.php @@ -7,6 +7,7 @@ use Statamic\Contracts\Data\Augmentable; use Statamic\Contracts\Data\Augmented; use Statamic\Contracts\Data\Localization; +use Statamic\Contracts\Globals\GlobalSet; use Statamic\Contracts\Globals\Variables as Contract; use Statamic\Contracts\GraphQL\ResolvesValues as ResolvesValuesContract; use Statamic\Data\ContainsData; @@ -15,6 +16,10 @@ use Statamic\Data\HasOrigin; use Statamic\Data\TracksQueriedRelations; use Statamic\Events\GlobalVariablesBlueprintFound; +use Statamic\Events\GlobalVariablesCreated; +use Statamic\Events\GlobalVariablesDeleted; +use Statamic\Events\GlobalVariablesSaved; +use Statamic\Events\GlobalVariablesSaving; use Statamic\Facades; use Statamic\Facades\Blink; use Statamic\Facades\Site; @@ -28,6 +33,8 @@ class Variables implements Contract, Localization, Augmentable, ResolvesValuesCo protected $set; protected $locale; + protected $afterSaveCallbacks = []; + protected $withEvents = true; public function __construct() { @@ -37,7 +44,11 @@ public function __construct() public function globalSet($set = null) { - return $this->fluentlyGetOrSet('set')->args(func_get_args()); + return $this->fluentlyGetOrSet('set') + ->getter(function ($set) { + return $set instanceof GlobalSet ? $set : Facades\GlobalSet::find($set); + }) + ->args(func_get_args()); } public function locale($locale = null) @@ -47,12 +58,12 @@ public function locale($locale = null) public function id() { - return $this->globalSet()->id(); + return $this->handle().($this->locale ? '::'.$this->locale : ''); } public function handle() { - return $this->globalSet()->handle(); + return $this->set instanceof GlobalSet ? $this->set->handle() : $this->set; } public function title() @@ -91,16 +102,62 @@ protected function cpUrl($route) return cp_route($route, $params); } + public function afterSave($callback) + { + $this->afterSaveCallbacks[] = $callback; + + return $this; + } + + public function saveQuietly() + { + $this->withEvents = false; + + return $this->save(); + } + public function save() { - $this - ->globalSet() - ->addLocalization($this) - ->save(); + $isNew = is_null(Facades\GlobalVariables::find($this->id())); + + $withEvents = $this->withEvents; + $this->withEvents = true; + + $afterSaveCallbacks = $this->afterSaveCallbacks; + $this->afterSaveCallbacks = []; + + if ($withEvents) { + if (GlobalVariablesSaving::dispatch($this) === false) { + return false; + } + } + + Facades\GlobalVariables::save($this); + + foreach ($afterSaveCallbacks as $callback) { + $callback($this); + } + + if ($withEvents) { + if ($isNew) { + GlobalVariablesCreated::dispatch($this); + } + + GlobalVariablesSaved::dispatch($this); + } return $this; } + public function delete() + { + Facades\GlobalVariables::delete($this); + + GlobalVariablesDeleted::dispatch($this); + + return true; + } + public function site() { return Site::get($this->locale()); @@ -184,6 +241,6 @@ protected function defaultAugmentedRelations() public function fresh() { - return Facades\GlobalSet::find($this->id())->in($this->locale); + return Facades\GlobalSet::find($this->handle())->in($this->locale); } } diff --git a/src/Globals/VariablesCollection.php b/src/Globals/VariablesCollection.php new file mode 100644 index 00000000000..5abc90d0d59 --- /dev/null +++ b/src/Globals/VariablesCollection.php @@ -0,0 +1,9 @@ + \Statamic\Stache\Repositories\TaxonomyRepository::class, \Statamic\Contracts\Entries\CollectionRepository::class => \Statamic\Stache\Repositories\CollectionRepository::class, \Statamic\Contracts\Globals\GlobalRepository::class => \Statamic\Stache\Repositories\GlobalRepository::class, + \Statamic\Contracts\Globals\GlobalVariablesRepository::class => \Statamic\Stache\Repositories\GlobalVariablesRepository::class, \Statamic\Contracts\Assets\AssetContainerRepository::class => \Statamic\Stache\Repositories\AssetContainerRepository::class, \Statamic\Contracts\Structures\StructureRepository::class => \Statamic\Structures\StructureRepository::class, \Statamic\Contracts\Structures\CollectionTreeRepository::class => \Statamic\Stache\Repositories\CollectionTreeRepository::class, diff --git a/src/Stache/Repositories/GlobalVariablesRepository.php b/src/Stache/Repositories/GlobalVariablesRepository.php new file mode 100644 index 00000000000..7e8608f132b --- /dev/null +++ b/src/Stache/Repositories/GlobalVariablesRepository.php @@ -0,0 +1,58 @@ +stache = $stache; + $this->store = $stache->store('global-variables'); + } + + public function all(): VariablesCollection + { + $keys = $this->store->paths()->keys(); + + return VariablesCollection::make($this->store->getItems($keys)); + } + + public function find($id): ?Variables + { + return $this->store->getItem($id); + } + + public function whereSet($handle): VariablesCollection + { + return $this + ->all() + ->filter(fn ($variable) => Str::before($variable->id(), '::') == $handle) + ->values(); + } + + public function save($variable) + { + $this->store->save($variable); + } + + public function delete($variable) + { + $this->store->delete($variable); + } + + public static function bindings(): array + { + return [ + Variables::class => \Statamic\Globals\Variables::class, + ]; + } +} diff --git a/src/Stache/Stores/GlobalVariablesStore.php b/src/Stache/Stores/GlobalVariablesStore.php new file mode 100644 index 00000000000..77096a34313 --- /dev/null +++ b/src/Stache/Stores/GlobalVariablesStore.php @@ -0,0 +1,87 @@ +getExtension() !== 'yaml') { + return false; + } + + $filename = str_after(Path::tidy($file->getPathName()), $this->directory); + + if (! Site::hasMultiple()) { + return substr_count($filename, '/') === 0; + } + + return substr_count($filename, '/') === 1; + } + + public function makeItemFromFile($path, $contents) + { + $relative = str_after($path, $this->directory); + $handle = str_before($relative, '.yaml'); + + $data = YAML::file($path)->parse($contents); + + if (! Site::hasMultiple()) { + $data = $data['data'] ?? []; + } + + return $this->makeVariablesFromFile($handle, $path, $data); + } + + protected function makeVariablesFromFile($handle, $path, $data) + { + $variables = app(Variables::class) + ->initialPath($path) + ->data(Arr::except($data, 'origin')); + + $handle = explode('/', $handle); + if (count($handle) > 1) { + $variables->globalSet($handle[1]) + ->locale($handle[0]); + } else { + $variables->globalSet($handle[0]) + ->locale(Site::default()->handle()); + } + + if ($origin = Arr::get($data, 'origin')) { + $variables->origin($origin); + } + + return $variables; + } + + protected function writeItemToDisk($item) + { + if (Site::hasMultiple()) { + $item->writeFile(); + } else { + $item->globalSet()->writeFile(); + } + } + + protected function deleteItemFromDisk($item) + { + if (Site::hasMultiple()) { + $item->deleteFile(); + } else { + $item->globalSet()->removeLocalization($item)->writeFile(); + } + } +} diff --git a/src/Stache/Stores/GlobalsStore.php b/src/Stache/Stores/GlobalsStore.php index bb6a85310b8..aafdf2f01b6 100644 --- a/src/Stache/Stores/GlobalsStore.php +++ b/src/Stache/Stores/GlobalsStore.php @@ -2,13 +2,10 @@ namespace Statamic\Stache\Stores; -use Statamic\Facades\File; use Statamic\Facades\GlobalSet; use Statamic\Facades\Path; -use Statamic\Facades\Site; use Statamic\Facades\YAML; use Statamic\Support\Arr; -use Statamic\Support\Str; use Symfony\Component\Finder\SplFileInfo; class GlobalsStore extends BasicStore @@ -32,48 +29,9 @@ public function makeItemFromFile($path, $contents) $relative = str_after($path, $this->directory); $handle = str_before($relative, '.yaml'); - // If it's a variables file that was requested, instead assume that the - // base file was requested. The variables will get made as part of it. - if (Site::hasMultiple() && Str::contains($relative, '/')) { - $handle = pathinfo($relative, PATHINFO_FILENAME); - $path = $this->directory.$handle.'.yaml'; - $data = YAML::file($path)->parse(); - - return $this->makeMultiSiteGlobalFromFile($handle, $path, $data); - } - - $data = YAML::file($path)->parse($contents); - - return Site::hasMultiple() - ? $this->makeMultiSiteGlobalFromFile($handle, $path, $data) - : $this->makeSingleSiteGlobalFromFile($handle, $path, $data); - } - - protected function makeSingleSiteGlobalFromFile($handle, $path, $data) - { - $set = $this->makeBaseGlobalFromFile($handle, $path, $data); - - return $set->addLocalization( - $set - ->makeLocalization(Site::default()->handle()) - ->initialPath($path) - ->data($data['data'] ?? []) - ); - } - - protected function makeMultiSiteGlobalFromFile($handle, $path, $data) - { - $set = $this->makeBaseGlobalFromFile($handle, $path, $data); - - Site::all()->filter(function ($site) use ($handle) { - return File::exists($this->directory.$site->handle().'/'.$handle.'.yaml'); - })->map->handle()->map(function ($site) use ($set) { - return $this->makeVariables($set, $site); - })->filter()->each(function ($variables) use ($set) { - $set->addLocalization($variables); - }); + $data = YAML::file($path)->parse(); - return $set; + return $this->makeBaseGlobalFromFile($handle, $path, Arr::except($data, 'data')); } protected function makeBaseGlobalFromFile($handle, $path, $data) @@ -83,58 +41,4 @@ protected function makeBaseGlobalFromFile($handle, $path, $data) ->title($data['title'] ?? null) ->initialPath($path); } - - protected function makeVariables($set, $site) - { - $variables = $set->makeLocalization($site); - - // todo: cache the reading and parsing of the file - if (! File::exists($path = $variables->path())) { - return; - } - $data = YAML::file($path)->parse(); - - $variables - ->initialPath($path) - ->data(Arr::except($data, 'origin')); - - if ($origin = Arr::get($data, 'origin')) { - $variables->origin($origin); - } - - return $variables; - } - - protected function getKeyFromPath($path) - { - if ($key = parent::getKeyFromPath($path)) { - return $key; - } - - // If we're not using multiple sites and no key has been - // found at this point, then we aren't going to find one. - if (! Site::hasMultiple()) { - return null; - } - - // Given a path to a variables file, get the key based on its base global set path. - if (Str::contains($relative = str_after($path, $this->directory), '/')) { - $handle = pathinfo($relative, PATHINFO_FILENAME); - $path = $this->directory.$handle.'.yaml'; - - return $this->paths()->flip()->get($path); - } - } - - public function save($set) - { - parent::save($set); - - if (Site::hasMultiple()) { - Site::all()->each(function ($site) use ($set) { - $site = $site->handle(); - $set->existsIn($site) ? $set->in($site)->writeFile() : $set->makeLocalization($site)->deleteFile(); - }); - } - } } diff --git a/tests/Data/Globals/GlobalSetTest.php b/tests/Data/Globals/GlobalSetTest.php index 0416ce1c7dd..926051f7d8d 100644 --- a/tests/Data/Globals/GlobalSetTest.php +++ b/tests/Data/Globals/GlobalSetTest.php @@ -7,8 +7,10 @@ use Statamic\Events\GlobalSetSaved; use Statamic\Events\GlobalSetSaving; use Statamic\Facades\GlobalSet as GlobalSetFacade; +use Statamic\Facades\GlobalVariables; use Statamic\Facades\Site; use Statamic\Globals\GlobalSet; +use Statamic\Globals\VariablesCollection; use Tests\PreventSavingStacheItemsToDisk; use Tests\TestCase; @@ -120,6 +122,80 @@ public function it_saves_through_the_api() }); } + /** @test */ + public function saving_a_new_global_set_will_create_its_localizations() + { + Site::setConfig([ + 'default' => 'en', + 'sites' => [ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'], + ], + ]); + + // when it queries for fresh localizations + GlobalVariables::shouldReceive('whereSet')->with('test')->andReturn(VariablesCollection::make()); + // when it checks if it's new + GlobalVariables::shouldReceive('find')->with('test::en'); + GlobalVariables::shouldReceive('find')->with('test::fr'); + // when it saves + GlobalVariables::shouldReceive('save') + ->withArgs(fn ($arg) => $arg->locale() === 'en') + ->once(); + GlobalVariables::shouldReceive('save') + ->withArgs(fn ($arg) => $arg->locale() === 'fr') + ->once(); + + $set = GlobalSet::make('test'); + $set->addLocalization($en = $set->makeLocalization('en')->data(['foo' => 'bar'])); + $set->addLocalization($fr = $set->makeLocalization('fr')->data(['foo' => 'le bar'])); + $set->save(); + } + + /** @test */ + public function saving_an_existing_global_set_will_save_or_delete_its_localizations() + { + Site::setConfig([ + 'default' => 'en', + 'sites' => [ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'], + ], + ]); + + $set = GlobalSet::make('test'); + $set->addLocalization($en = $set->makeLocalization('en')->data(['foo' => 'bar'])); + $set->addLocalization($fr = $set->makeLocalization('fr')->data(['foo' => 'le bar'])); + $set->addLocalization($de = $set->makeLocalization('de')->data(['foo' => 'der bar'])); + $set->save(); + + // when it queries for fresh localizations + GlobalVariables::shouldReceive('whereSet')->with('test')->andReturn(VariablesCollection::make([ + 'en' => $en, + 'fr' => $fr, + 'de' => $de, + ])); + // when it checks if it's new + GlobalVariables::shouldReceive('find')->with('test::en'); + GlobalVariables::shouldReceive('find')->with('test::de'); + // when it saves + GlobalVariables::shouldReceive('save') + ->withArgs(fn ($arg) => $arg->locale() === 'en') + ->once(); + GlobalVariables::shouldReceive('save') + ->withArgs(fn ($arg) => $arg->locale() === 'de') + ->once(); + // when it deletes + GlobalVariables::shouldReceive('delete') + ->withArgs(fn ($arg) => $arg->locale() === 'fr') + ->once(); + + $set->removeLocalization($fr); + $set->save(); + } + /** @test */ public function it_dispatches_global_set_created_only_once() { diff --git a/tests/Stache/FeatureTest.php b/tests/Stache/FeatureTest.php index b8a413c6cb1..ddac20b2758 100644 --- a/tests/Stache/FeatureTest.php +++ b/tests/Stache/FeatureTest.php @@ -34,6 +34,7 @@ public function setUp(): void $stache->store('entries')->directory($dir.'/content/collections'); $stache->store('navigation')->directory($dir.'/content/navigation'); $stache->store('globals')->directory($dir.'/content/globals'); + $stache->store('global-variables')->directory($dir.'/content/globals'); $stache->store('asset-containers')->directory($dir.'/content/assets'); $stache->store('collection-trees')->directory($dir.'/content/structures/collections'); $stache->store('nav-trees')->directory($dir.'/content/structures/navigation'); diff --git a/tests/Stache/Repositories/GlobalRepositoryTest.php b/tests/Stache/Repositories/GlobalRepositoryTest.php index 280e290a3df..1fee0aca1b8 100644 --- a/tests/Stache/Repositories/GlobalRepositoryTest.php +++ b/tests/Stache/Repositories/GlobalRepositoryTest.php @@ -8,6 +8,7 @@ use Statamic\Stache\Repositories\GlobalRepository; use Statamic\Stache\Stache; use Statamic\Stache\Stores\GlobalsStore; +use Statamic\Stache\Stores\GlobalVariablesStore; use Tests\TestCase; class GlobalRepositoryTest extends TestCase @@ -23,6 +24,7 @@ public function setUp(): void $this->app->instance(Stache::class, $stache); $this->directory = __DIR__.'/../__fixtures__/content/globals'; $stache->registerStore((new GlobalsStore($stache, app('files')))->directory($this->directory)); + $stache->registerStore((new GlobalVariablesStore($stache, app('files')))->directory($this->directory)); $this->repo = new GlobalRepository($stache); } diff --git a/tests/Stache/Repositories/GlobalVariablesRepositoryTest.php b/tests/Stache/Repositories/GlobalVariablesRepositoryTest.php new file mode 100644 index 00000000000..722e326b104 --- /dev/null +++ b/tests/Stache/Repositories/GlobalVariablesRepositoryTest.php @@ -0,0 +1,312 @@ +sites(['en']); + $this->app->instance(Stache::class, $stache); + $this->directory = __DIR__.'/../__fixtures__/content/globals'; + $stache->registerStore((new GlobalsStore($stache, app('files')))->directory($this->directory)); + $stache->registerStore((new GlobalVariablesStore($stache, app('files')))->directory($this->directory)); + + $this->repo = new GlobalVariablesRepository($stache); + $this->globalRepo = new GlobalRepository($stache); + } + + private function setUpMultiSite() + { + Site::setConfig(['sites' => [ + 'en' => ['url' => '/'], + 'fr' => ['url' => '/fr/'], + ]]); + $stache = (new Stache)->sites(['en', 'fr']); + $this->app->instance(Stache::class, $stache); + $this->directory = __DIR__.'/../__fixtures__/content/globals-multisite'; + $stache->registerStore((new GlobalsStore($stache, app('files')))->directory($this->directory)); + $stache->registerStore((new GlobalVariablesStore($stache, app('files')))->directory($this->directory)); + + $this->repo = new GlobalVariablesRepository($stache); + $this->globalRepo = new GlobalRepository($stache); + } + + /** @test */ + public function it_gets_all_global_variables_with_single_site() + { + $this->setUpSingleSite(); + + $vars = $this->repo->all(); + + $this->assertInstanceOf(VariablesCollection::class, $vars); + $this->assertCount(2, $vars); + $this->assertEveryItemIsInstanceOf(Variables::class, $vars); + + $ordered = $vars->sortBy->path()->values(); + $this->assertEquals(['contact::en', 'global::en'], $ordered->map->id()->all()); + $this->assertEquals(['contact', 'global'], $ordered->map->handle()->all()); + } + + /** @test */ + public function it_gets_all_global_variables_with_multi_site() + { + $this->setUpMultiSite(); + + $sets = $this->repo->all(); + + $this->assertInstanceOf(VariablesCollection::class, $sets); + $this->assertCount(4, $sets); + $this->assertEveryItemIsInstanceOf(Variables::class, $sets); + + $ordered = $sets->sortBy->path()->values(); + $this->assertEquals(['contact::en', 'global::en', 'contact::fr', 'global::fr'], $ordered->map->id()->all()); + $this->assertEquals(['contact', 'global', 'contact', 'global'], $ordered->map->handle()->all()); + } + + /** @test */ + public function it_gets_a_global_variable_by_id_with_single_site() + { + $this->setUpSingleSite(); + + tap($this->repo->find('global::en'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('global::en', $variable->id()); + $this->assertEquals('global', $variable->handle()); + }); + + tap($this->repo->find('contact::en'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('contact::en', $variable->id()); + $this->assertEquals('contact', $variable->handle()); + }); + + $this->assertNull($this->repo->find('unknown')); + } + + /** @test */ + public function it_gets_a_global_variable_by_id_with_multi_site() + { + $this->setUpMultiSite(); + + tap($this->repo->find('global::en'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('global::en', $variable->id()); + $this->assertEquals('global', $variable->handle()); + }); + + tap($this->repo->find('global::fr'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('global::fr', $variable->id()); + $this->assertEquals('global', $variable->handle()); + }); + + $this->assertNull($this->repo->find('global::de')); + + tap($this->repo->find('contact::en'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('contact::en', $variable->id()); + $this->assertEquals('contact', $variable->handle()); + }); + + tap($this->repo->find('contact::fr'), function ($variable) { + $this->assertInstanceOf(Variables::class, $variable); + $this->assertEquals('contact::fr', $variable->id()); + $this->assertEquals('contact', $variable->handle()); + }); + + $this->assertNull($this->repo->find('contact::de')); + + $this->assertNull($this->repo->find('unknown')); + } + + /** @test */ + public function it_gets_global_variables_by_set_handle_with_single_site() + { + $this->setUpSingleSite(); + + tap($this->repo->whereSet('global'), function ($variables) { + $this->assertInstanceOf(VariablesCollection::class, $variables); + $first = $variables->first(); + $this->assertEquals('global::en', $first->id()); + $this->assertEquals('global', $first->handle()); + }); + + tap($this->repo->whereSet('contact'), function ($variables) { + $this->assertInstanceOf(VariablesCollection::class, $variables); + $first = $variables->first(); + $this->assertEquals('contact::en', $first->id()); + $this->assertEquals('contact', $first->handle()); + }); + + $this->assertCount(0, $this->repo->whereSet('unknown')); + } + + /** @test */ + public function it_gets_global_variables_by_set_handle_with_multi_site() + { + $this->setUpMultiSite(); + + tap($this->repo->whereSet('global'), function ($variables) { + $this->assertInstanceOf(VariablesCollection::class, $variables); + $ordered = $variables->sortBy->path()->values(); + $this->assertEquals(['global::en', 'global::fr'], $ordered->map->id()->all()); + }); + + tap($this->repo->whereSet('contact'), function ($variables) { + $this->assertInstanceOf(VariablesCollection::class, $variables); + $ordered = $variables->sortBy->path()->values(); + $this->assertEquals(['contact::en', 'contact::fr'], $ordered->map->id()->all()); + }); + + $this->assertCount(0, $this->repo->whereSet('unknown')); + } + + /** @test */ + public function it_saves_a_global_to_the_stache_and_to_a_file_with_single_site() + { + // In single site, the actual global set should get written. + // There should be no dedicated global variables file. + // The Variables object should still exist in the store though. + $this->setUpSingleSite(); + + $global = GlobalSet::make('new')->title('Test Global Test'); + + $localization = $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']); + $global->addLocalization($localization); + + $this->assertNull($this->repo->find('new::en')); + + $this->globalRepo->save($global); + + // Delete the global set file so we can test that it's not being written. + // At this point it exists in the Stache, so deleting the file will have no effect. + @unlink($this->directory.'/new.yaml'); + + $this->repo->save($localization); + + $this->assertNotNull($item = $this->repo->find('new::en')); + $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $item->data()->all()); + $this->assertFileExists($this->directory.'/new.yaml'); + $this->assertFileDoesNotExist($this->directory.'/en/new.yaml'); + $yaml = <<<'YAML' +title: 'Test Global Test' +data: + foo: bar + baz: qux + +YAML; + $this->assertEquals($yaml, file_get_contents($this->directory.'/new.yaml')); + @unlink($this->directory.'/new.yaml'); + } + + /** @test */ + public function it_saves_a_global_to_the_stache_and_to_a_file_with_multi_site() + { + // In multi-site, the global set should not get written. + // There should be a dedicated global variables file. + + $this->setUpMultiSite(); + + $global = GlobalSet::make('new'); + + $localization = $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']); + $global->addLocalization($localization); + + $this->assertNull($this->repo->find('new::en')); + + $this->globalRepo->save($global); + + // Delete the global set file so we can test that it's not being written. + // At this point it exists in the Stache, so deleting the file will have no effect. + @unlink($this->directory.'/new.yaml'); + + $this->repo->save($localization); + + $this->assertNotNull($item = $this->repo->find('new::en')); + $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $item->data()->all()); + $this->assertFileDoesNotExist($this->directory.'/new.yaml'); + $this->assertFileExists($this->directory.'/en/new.yaml'); + @unlink($this->directory.'/en/new.yaml'); + } + + /** @test */ + public function it_deletes_a_global_from_the_stache_and_file_with_single_site() + { + // In single site, the actual global set holds the data. + // The file should remain, but the data should get emptied out. + // There would have been no dedicated global variables file. + // The Variables object should also be removed from the store. + // (Realistically, you wouldn't ever delete variables without also deleting the set.) + $this->setUpSingleSite(); + + $global = GlobalSet::make('new')->title('Test Global Test'); + $localization = $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']); + $global->addLocalization($localization); + $this->globalRepo->save($global); + $this->repo->save($localization); + + $this->assertNotNull($item = $this->repo->find('new::en')); + $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $item->data()->all()); + $this->assertFileExists($this->directory.'/new.yaml'); + $yaml = <<<'YAML' +title: 'Test Global Test' +data: + foo: bar + baz: qux + +YAML; + $this->assertEquals($yaml, file_get_contents($this->directory.'/new.yaml')); + + $this->repo->delete($item); + + $this->assertNull($this->repo->find('new::en')); + $this->assertNotNull($this->globalRepo->find('new')); + $this->assertFileExists($this->directory.'/new.yaml'); + $yaml = <<<'YAML' +title: 'Test Global Test' + +YAML; + $this->assertEquals($yaml, file_get_contents($this->directory.'/new.yaml')); + @unlink($this->directory.'/new.yaml'); + } + + /** @test */ + public function it_deletes_a_global_from_the_stache_and_file_with_multi_site() + { + // In multi-site, the global set file should not be touched. + // There should be a dedicated global variables file, which should be deleted. + + $this->setUpMultiSite(); + + $global = GlobalSet::make('new'); + $localization = $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']); + $global->addLocalization($localization); + $this->globalRepo->save($global); + $this->repo->save($localization); + + $this->assertNotNull($item = $this->repo->find('new::en')); + $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $item->data()->all()); + + $this->repo->delete($item); + + $this->assertNull($this->repo->find('new::en')); + $this->assertNotNull($this->globalRepo->find('new')); + $this->assertFileDoesNotExist($this->directory.'/en/new.yaml'); + @unlink($this->directory.'/new.yaml'); + } +} diff --git a/tests/Stache/Stores/GlobalVariablesStoreTest.php b/tests/Stache/Stores/GlobalVariablesStoreTest.php new file mode 100644 index 00000000000..352a823c32b --- /dev/null +++ b/tests/Stache/Stores/GlobalVariablesStoreTest.php @@ -0,0 +1,83 @@ +tempDir = __DIR__.'/tmp'); + + $stache = (new Stache)->sites(['en']); + $this->app->instance(Stache::class, $stache); + $stache->registerStore((new GlobalsStore($stache, app('files')))->directory($this->tempDir)); + $stache->registerStore($this->store = (new GlobalVariablesStore($stache, app('files')))->directory($this->tempDir)); + } + + public function tearDown(): void + { + parent::tearDown(); + (new Filesystem)->deleteDirectory($this->tempDir); + } + + /** @test */ + public function it_gets_yaml_files_from_the_root() + { + touch($this->tempDir.'/one.yaml', 1234567890); + touch($this->tempDir.'/two.yaml', 1234567890); + touch($this->tempDir.'/three.txt', 1234567890); + mkdir($this->tempDir.'/subdirectory'); + touch($this->tempDir.'/subdirectory/nested-one.yaml', 1234567890); + touch($this->tempDir.'/subdirectory/nested-two.yaml', 1234567890); + + $files = Traverser::filter([$this->store, 'getItemFilter'])->traverse($this->store); + + $dir = Path::tidy($this->tempDir); + $this->assertEquals([ + $dir.'/one.yaml' => 1234567890, + $dir.'/two.yaml' => 1234567890, + ], $files->all()); + + // Sanity check. Make sure the file is there but wasn't included. + $this->assertTrue(file_exists($dir.'/subdirectory/nested-one.yaml')); + $this->assertTrue(file_exists($dir.'/subdirectory/nested-two.yaml')); + $this->assertTrue(file_exists($dir.'/three.txt')); + } + + /** @test */ + public function it_makes_global_variable_instances_from_files() + { + $item = $this->store->makeItemFromFile(Path::tidy($this->tempDir.'/example.yaml'), "title: Example\ndata:\n foo: bar"); + + $this->assertInstanceOf(Variables::class, $item); + $this->assertEquals('example::en', $item->id()); + $this->assertEquals('example', $item->handle()); + } + + /** @test */ + public function it_uses_the_id_as_the_item_key() + { + $set = Mockery::mock(); + $set->shouldReceive('id')->andReturn('123'); + + $this->assertEquals( + '123', + $this->store->getItemKey($set) + ); + } +} diff --git a/tests/Stache/Stores/GlobalsStoreTest.php b/tests/Stache/Stores/GlobalsStoreTest.php index 03b426b230f..b312743c09b 100644 --- a/tests/Stache/Stores/GlobalsStoreTest.php +++ b/tests/Stache/Stores/GlobalsStoreTest.php @@ -10,6 +10,7 @@ use Statamic\Facades\Path; use Statamic\Stache\Stache; use Statamic\Stache\Stores\GlobalsStore; +use Statamic\Stache\Stores\GlobalVariablesStore; use Tests\TestCase; class GlobalsStoreTest extends TestCase @@ -26,6 +27,7 @@ public function setUp(): void $stache = (new Stache)->sites(['en']); $this->app->instance(Stache::class, $stache); $stache->registerStore($this->store = (new GlobalsStore($stache, app('files')))->directory($this->tempDir)); + $stache->registerStore((new GlobalVariablesStore($stache, app('files')))->directory($this->tempDir)); } public function tearDown(): void @@ -67,7 +69,6 @@ public function it_makes_global_set_instances_from_files() $this->assertEquals('example', $item->id()); $this->assertEquals('example', $item->handle()); $this->assertEquals('Example', $item->title()); - $this->assertEquals(['foo' => 'bar'], $item->in('en')->data()->all()); } /** @test */ diff --git a/tests/Stache/__fixtures__/content/globals-multisite/contact.yaml b/tests/Stache/__fixtures__/content/globals-multisite/contact.yaml new file mode 100644 index 00000000000..c3d44c7fb7f --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/contact.yaml @@ -0,0 +1,2 @@ +--- +title: Contact Details diff --git a/tests/Stache/__fixtures__/content/globals-multisite/en/contact.yaml b/tests/Stache/__fixtures__/content/globals-multisite/en/contact.yaml new file mode 100644 index 00000000000..40644262447 --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/en/contact.yaml @@ -0,0 +1,2 @@ +--- +phone: '+1 555-1234' diff --git a/tests/Stache/__fixtures__/content/globals-multisite/en/global.yaml b/tests/Stache/__fixtures__/content/globals-multisite/en/global.yaml new file mode 100644 index 00000000000..2d047d83ea3 --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/en/global.yaml @@ -0,0 +1,2 @@ +--- +foo: Bar diff --git a/tests/Stache/__fixtures__/content/globals-multisite/fr/contact.yaml b/tests/Stache/__fixtures__/content/globals-multisite/fr/contact.yaml new file mode 100644 index 00000000000..96df6bb2af3 --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/fr/contact.yaml @@ -0,0 +1,2 @@ +--- +phone: '+33 555-1234' diff --git a/tests/Stache/__fixtures__/content/globals-multisite/fr/global.yaml b/tests/Stache/__fixtures__/content/globals-multisite/fr/global.yaml new file mode 100644 index 00000000000..5a58affbac5 --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/fr/global.yaml @@ -0,0 +1,2 @@ +--- +foo: Le Bar diff --git a/tests/Stache/__fixtures__/content/globals-multisite/global.yaml b/tests/Stache/__fixtures__/content/globals-multisite/global.yaml new file mode 100644 index 00000000000..980694dc70d --- /dev/null +++ b/tests/Stache/__fixtures__/content/globals-multisite/global.yaml @@ -0,0 +1,2 @@ +--- +title: General diff --git a/tests/TestCase.php b/tests/TestCase.php index 88b0417a6af..db295ac12aa 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -101,6 +101,7 @@ protected function getEnvironmentSetUp($app) $app['config']->set('statamic.stache.stores.entries.directory', __DIR__.'/__fixtures__/content/collections'); $app['config']->set('statamic.stache.stores.navigation.directory', __DIR__.'/__fixtures__/content/navigation'); $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.nav-trees.directory', __DIR__.'/__fixtures__/content/structures/navigation'); $app['config']->set('statamic.stache.stores.collection-trees.directory', __DIR__.'/__fixtures__/content/structures/collections');