diff --git a/composer.json b/composer.json index 26304565..1859e1c2 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ }, "require": { "php": "^8.1", - "statamic/cms": "^5.14" + "statamic/cms": "^5.16" }, "require-dev": { "doctrine/dbal": "^3.8", diff --git a/config/eloquent-driver.php b/config/eloquent-driver.php index b6901f5a..0ba4e51e 100644 --- a/config/eloquent-driver.php +++ b/config/eloquent-driver.php @@ -97,4 +97,9 @@ 'driver' => 'file', 'model' => \Statamic\Eloquent\Tokens\TokenModel::class, ], + + 'sites' => [ + 'driver' => 'file', + 'model' => \Statamic\Eloquent\Sites\SiteModel::class, + ], ]; diff --git a/database/migrations/2024_07_16_100000_create_sites_table.php b/database/migrations/2024_07_16_100000_create_sites_table.php new file mode 100644 index 00000000..f2cfe9e4 --- /dev/null +++ b/database/migrations/2024_07_16_100000_create_sites_table.php @@ -0,0 +1,27 @@ +prefix('sites'), function (Blueprint $table) { + $table->id(); + $table->string('handle')->unique(); + $table->string('name'); + $table->string('url'); + $table->string('locale'); + $table->string('lang'); + $table->jsonb('attributes'); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists($this->prefix('sites')); + } +}; diff --git a/src/Commands/ExportSites.php b/src/Commands/ExportSites.php new file mode 100644 index 00000000..203377db --- /dev/null +++ b/src/Commands/ExportSites.php @@ -0,0 +1,55 @@ +mapWithKeys(function ($model) { + return [ + $model->handle => [ + 'name' => $model->name, + 'lang' => $model->lang, + 'locale' => $model->locale, + 'url' => $model->url, + 'attributes' => $model->attributes ?? [], + ], + ]; + }); + + (new Sites)->setSites($sites)->save(); + + $this->newLine(); + $this->info('Sites exported'); + + return 0; + } +} diff --git a/src/Commands/ImportSites.php b/src/Commands/ImportSites.php new file mode 100644 index 00000000..5edbcfc3 --- /dev/null +++ b/src/Commands/ImportSites.php @@ -0,0 +1,41 @@ +config(); + + (new EloquentSites)->setSites($sites)->save(); + + $this->components->info('Sites imported successfully.'); + + return 0; + } +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index dbc4a73c..b6514e6c 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -83,6 +83,7 @@ public function boot() Commands\ExportNavs::class, Commands\ExportRevisions::class, Commands\ExportTaxonomies::class, + Commands\ExportSites::class, Commands\ImportAssets::class, Commands\ImportBlueprints::class, Commands\ImportCollections::class, @@ -92,6 +93,7 @@ public function boot() Commands\ImportNavs::class, Commands\ImportRevisions::class, Commands\ImportTaxonomies::class, + Commands\ImportSites::class, Commands\SyncAssets::class, ]); @@ -161,6 +163,10 @@ private function publishMigrations(): void __DIR__.'/../database/migrations/2024_03_07_100000_create_tokens_table.php' => database_path('migrations/2024_03_07_100000_create_tokens_table.php'), ], 'statamic-eloquent-token-migrations'); + $this->publishes($siteMigrations = [ + __DIR__.'/../database/migrations/2024_07_16_100000_create_sites_table.php' => database_path('migrations/2024_07_16_100000_create_sites_table.php'), + ], 'statamic-eloquent-site-migrations'); + $this->publishes( array_merge( $taxonomyMigrations, @@ -177,7 +183,8 @@ private function publishMigrations(): void $assetContainerMigrations, $assetMigrations, $revisionMigrations, - $tokenMigrations + $tokenMigrations, + $siteMigrations, ), 'migrations' ); @@ -210,6 +217,7 @@ public function register() $this->registerTaxonomies(); $this->registerTerms(); $this->registerTokens(); + $this->registerSites(); } private function registerAssetContainers() @@ -517,6 +525,19 @@ public function registerTokens() Statamic::repository(TokenRepositoryContract::class, TokenRepository::class); } + public function registerSites() + { + if (config('statamic.eloquent-driver.sites.driver', 'file') != 'eloquent') { + return; + } + + $this->app->bind('statamic.eloquent.sites.model', function () { + return config('statamic.eloquent-driver.sites.model'); + }); + + $this->app->singleton(\Statamic\Sites\Sites::class, \Statamic\Eloquent\Sites\Sites::class); + } + protected function addAboutCommandInfo() { if (! class_exists(AboutCommand::class)) { @@ -539,6 +560,7 @@ protected function addAboutCommandInfo() 'Taxonomies' => config('statamic.eloquent-driver.taxonomies.driver', 'file'), 'Terms' => config('statamic.eloquent-driver.terms.driver', 'file'), 'Tokens' => config('statamic.eloquent-driver.tokens.driver', 'file'), + 'Sites' => config('statamic.eloquent-driver.sites.driver', 'file'), ])->map(fn ($value) => $this->applyAboutCommandFormatting($value))->all()); } diff --git a/src/Sites/SiteModel.php b/src/Sites/SiteModel.php new file mode 100644 index 00000000..d5a16065 --- /dev/null +++ b/src/Sites/SiteModel.php @@ -0,0 +1,22 @@ + 'json', + ]; + + public function getAttribute($key) + { + return Arr::get($this->getAttributeValue('attributes'), $key, parent::getAttribute($key)); + } +} diff --git a/src/Sites/Sites.php b/src/Sites/Sites.php new file mode 100644 index 00000000..08458fe8 --- /dev/null +++ b/src/Sites/Sites.php @@ -0,0 +1,40 @@ +isEmpty() ? $this->getFallbackConfig() : $sites->mapWithKeys(function ($model) { + return [ + $model->handle => [ + 'name' => $model->name, + 'lang' => $model->lang, + 'locale' => $model->locale, + 'url' => $model->url, + 'attributes' => $model->attributes ?? [], + ], + ]; + }); + } + + protected function saveToStore() + { + foreach ($this->config() as $handle => $config) { + app('statamic.eloquent.sites.model')::firstOrNew(['handle' => $handle]) + ->fill([ + 'name' => $config['name'] ?? '', + 'lang' => $config['lang'] ?? '', + 'locale' => $config['locale'] ?? '', + 'url' => $config['url'] ?? '', + 'attributes' => $config['attributes'] ?? [], + ]) + ->save(); + } + + app('statamic.eloquent.sites.model')::whereNotIn('handle', array_keys($this->config()))->get()->each->delete(); + } +} diff --git a/tests/Commands/ImportSitesTest.php b/tests/Commands/ImportSitesTest.php new file mode 100644 index 00000000..fb9e9d2a --- /dev/null +++ b/tests/Commands/ImportSitesTest.php @@ -0,0 +1,63 @@ +app->bind('statamic.eloquent.sites.model', function () { + return SiteModel::class; + }); + } + + #[Test] + public function it_imports_sites() + { + $this->assertCount(0, SiteModel::all()); + + $this->setSites([ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'], + ]); + + \Statamic\Facades\Site::save(); + + $this->artisan('statamic:eloquent:import-sites') + ->expectsOutputToContain('Sites imported successfully.') + ->assertExitCode(0); + + $this->assertCount(4, SiteModel::all()); + + $this->assertDatabaseHas('sites', [ + 'handle' => 'en', + 'name' => 'English', + ]); + + $this->assertDatabaseHas('sites', [ + 'handle' => 'fr', + 'name' => 'French', + ]); + + $this->assertDatabaseHas('sites', [ + 'handle' => 'de', + 'name' => 'German', + ]); + + $this->assertDatabaseHas('sites', [ + 'handle' => 'es', + 'name' => 'Spanish', + ]); + } +} diff --git a/tests/Sites/SitesTest.php b/tests/Sites/SitesTest.php new file mode 100644 index 00000000..a830c67b --- /dev/null +++ b/tests/Sites/SitesTest.php @@ -0,0 +1,76 @@ +app->bind('statamic.eloquent.sites.model', function () { + return SiteModel::class; + }); + + $this->app->singleton( + 'Statamic\Sites\Sites', + 'Statamic\Eloquent\Sites\Sites' + ); + + Facade::clearResolvedInstance(\Statamic\Sites\Sites::class); + } + + #[Test] + public function it_saves_sites() + { + $this->assertCount(0, SiteModel::all()); + + $this->setSites([ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'], + ]); + + Site::save(); + + $this->assertCount(4, Site::all()); + $this->assertCount(4, SiteModel::all()); + } + + #[Test] + public function it_deletes_sites() + { + $this->assertCount(0, SiteModel::all()); + + $this->setSites([ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => 'http://test.com/de/'], + ]); + + Site::save(); + + $this->setSites([ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => 'http://fr.test.com/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => 'http://test.com/es/'], + ]); + + Site::save(); + + $this->assertCount(3, Site::all()); + $this->assertCount(3, SiteModel::all()); + $this->assertSame(['en', 'fr', 'es'], SiteModel::all()->pluck('handle')->all()); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index d733b8ec..d63d4660 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -23,6 +23,7 @@ protected function resolveApplicationConfiguration($app) collect(config('statamic.eloquent-driver')) ->filter(fn ($config) => isset($config['driver'])) + ->reject(fn ($config, $key) => $key === 'sites') ->each(fn ($config, $key) => $app['config']->set("statamic.eloquent-driver.{$key}.driver", 'eloquent')); }