diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 47f8817c..55a247ee 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,16 +12,9 @@ jobs: strategy: matrix: - php: [8.1, 8.2, 8.3, 8.4] - laravel: [10.*, 11.*, 12.*] + php: [8.2, 8.3, 8.4] + laravel: [11.*, 12.*] stability: [prefer-lowest, prefer-stable] - exclude: - - php: 8.1 - laravel: 11.* - - php: 8.1 - laravel: 12.* - - php: 8.4 - laravel: 10.* name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} diff --git a/README.md b/README.md index ef92326d..12ead4c2 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ The command will also give you the opportunity to indicate whether you'd like ex If you originally opt-out of importing existing content, then later change your mind, you can import existing content by running the relevant commands: +- Addon Settings: `php please eloquent:import-addon-settings` - Assets: `php please eloquent:import-assets` - Blueprints and Fieldsets: `php please eloquent:import-blueprints` - Collections: `php please eloquent:import-collections` @@ -43,6 +44,7 @@ If your assets are being driven by the Eloquent Driver and you're managing your If you wish to move back to flat-files, you may use the following commands to export your content out of the database: +- Addon Settings: `php please eloquent:export-addon-settings` - Assets: `php please eloquent:export-assets` - Blueprints and Fieldsets: `php please eloquent:export-blueprints` - Collections: `php please eloquent:export-collections` @@ -114,15 +116,18 @@ By default, the Eloquent Driver stores all data in a single `data` column. Howev class Entry extends \Statamic\Eloquent\Entries\EntryModel { - protected $casts = [ - // The casts from Statamic's base model... - 'date' => 'datetime', - 'data' => 'json', - 'published' => 'boolean', - - // Your custom casts... - 'featured_images' => 'json', - ]; + protected function casts(): array + { + return [ + // The casts from Statamic's base model... + 'date' => 'datetime', + 'data' => 'json', + 'published' => 'boolean', + + // Your custom casts... + 'featured_images' => 'json', + ]; + } } ``` diff --git a/composer.json b/composer.json index 9d83f270..2cac5a97 100644 --- a/composer.json +++ b/composer.json @@ -24,14 +24,14 @@ } }, "require": { - "php": "^8.1", - "statamic/cms": "^5.48" + "php": "^8.2", + "statamic/cms": "dev-master" }, "require-dev": { "doctrine/dbal": "^3.8", "laravel/pint": "^1.0", - "orchestra/testbench": "^8.28 || ^9.6.1 || ^10.0", - "phpunit/phpunit": "^10.5.35 || ^11.0" + "orchestra/testbench": "^9.6.1 || ^10.0", + "phpunit/phpunit": "^11.0" }, "scripts": { "test": "phpunit" diff --git a/config/eloquent-driver.php b/config/eloquent-driver.php index 0ba4e51e..a59ea0c4 100644 --- a/config/eloquent-driver.php +++ b/config/eloquent-driver.php @@ -5,6 +5,11 @@ 'connection' => env('STATAMIC_ELOQUENT_CONNECTION', ''), 'table_prefix' => env('STATAMIC_ELOQUENT_PREFIX', ''), + 'addon_settings' => [ + 'driver' => 'file', + 'model' => \Statamic\Eloquent\AddonSettings\AddonSettingsModel::class, + ], + 'asset_containers' => [ 'driver' => 'file', 'model' => \Statamic\Eloquent\Assets\AssetContainerModel::class, diff --git a/database/migrations/2024_03_07_100000_create_global_variables_table.php b/database/migrations/2024_03_07_100000_create_global_variables_table.php index ae04f9f1..0025da09 100644 --- a/database/migrations/2024_03_07_100000_create_global_variables_table.php +++ b/database/migrations/2024_03_07_100000_create_global_variables_table.php @@ -12,7 +12,6 @@ public function up() $table->id(); $table->string('handle')->index(); $table->string('locale')->nullable(); - $table->string('origin')->nullable(); $table->jsonb('data'); $table->timestamps(); }); diff --git a/database/migrations/2025_07_07_100000_create_addon_settings_table.php b/database/migrations/2025_07_07_100000_create_addon_settings_table.php new file mode 100644 index 00000000..7a2555bb --- /dev/null +++ b/database/migrations/2025_07_07_100000_create_addon_settings_table.php @@ -0,0 +1,21 @@ +prefix('addon_settings'), function (Blueprint $table) { + $table->string('addon')->index()->primary(); + $table->json('settings')->nullable(); + }); + } + + public function down() + { + Schema::dropIfExists($this->prefix('addon_settings')); + } +}; diff --git a/database/migrations/updates/drop_origin_on_global_set_variables.php.stub b/database/migrations/updates/drop_origin_on_global_set_variables.php.stub new file mode 100644 index 00000000..3fe54ffc --- /dev/null +++ b/database/migrations/updates/drop_origin_on_global_set_variables.php.stub @@ -0,0 +1,21 @@ +prefix('global_set_variables'), function (Blueprint $table) { + $table->dropColumn('origin'); + }); + } + + public function down() + { + Schema::table($this->prefix('global_set_variables'), function (Blueprint $table) { + $table->string('origin')->nullable(); + }); + } +}; diff --git a/database/migrations/updates/relate_form_submissions_by_handle.php.stub b/database/migrations/updates/relate_form_submissions_by_handle.php.stub index 68b3552f..286beca9 100644 --- a/database/migrations/updates/relate_form_submissions_by_handle.php.stub +++ b/database/migrations/updates/relate_form_submissions_by_handle.php.stub @@ -14,7 +14,7 @@ return new class extends Migration { } Schema::table($this->prefix('form_submissions'), function (Blueprint $table) { - $table->string('form', 30)->nullable()->index()->after('id'); + $table->string('form')->nullable()->index()->after('id'); }); $forms = FormModel::all()->pluck('handle', 'id'); diff --git a/src/AddonSettings/AddonSettings.php b/src/AddonSettings/AddonSettings.php new file mode 100644 index 00000000..c3fcf31b --- /dev/null +++ b/src/AddonSettings/AddonSettings.php @@ -0,0 +1,44 @@ +addon); + + return (new static($addon, $model->settings))->model($model); + } + + public function toModel() + { + return self::makeModelFromContract($this); + } + + public static function makeModelFromContract(AbstractSettings $settings) + { + $class = app('statamic.eloquent.addon_settings.model'); + + return $class::firstOrNew(['addon' => $settings->addon()->id()])->fill([ + 'settings' => array_filter($settings->raw()), + ]); + } + + public function model($model = null) + { + if (func_num_args() === 0) { + return $this->model; + } + + $this->model = $model; + + return $this; + } +} diff --git a/src/AddonSettings/AddonSettingsModel.php b/src/AddonSettings/AddonSettingsModel.php new file mode 100644 index 00000000..f0081dba --- /dev/null +++ b/src/AddonSettings/AddonSettingsModel.php @@ -0,0 +1,25 @@ + 'array', + ]; + } +} diff --git a/src/AddonSettings/AddonSettingsRepository.php b/src/AddonSettings/AddonSettingsRepository.php new file mode 100644 index 00000000..a645c76c --- /dev/null +++ b/src/AddonSettings/AddonSettingsRepository.php @@ -0,0 +1,37 @@ +toModel()->save(); + } + + public function delete(AddonSettingsContract $settings): bool + { + return $settings->toModel()->delete(); + } + + public static function bindings(): array + { + return [ + AddonSettingsContract::class => AddonSettings::class, + ]; + } +} diff --git a/src/Assets/AssetContainerModel.php b/src/Assets/AssetContainerModel.php index f93c3a7b..58a0dad2 100644 --- a/src/Assets/AssetContainerModel.php +++ b/src/Assets/AssetContainerModel.php @@ -11,9 +11,12 @@ class AssetContainerModel extends BaseModel protected $table = 'asset_containers'; - protected $casts = [ - 'settings' => 'json', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Assets/AssetModel.php b/src/Assets/AssetModel.php index 4316e38f..64d08aab 100644 --- a/src/Assets/AssetModel.php +++ b/src/Assets/AssetModel.php @@ -10,8 +10,11 @@ class AssetModel extends BaseModel protected $table = 'assets_meta'; - protected $casts = [ - 'data' => 'json', - 'meta' => 'json', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + 'meta' => 'json', + ]; + } } diff --git a/src/Collections/CollectionModel.php b/src/Collections/CollectionModel.php index ca341d73..f5a1e890 100644 --- a/src/Collections/CollectionModel.php +++ b/src/Collections/CollectionModel.php @@ -10,15 +10,18 @@ class CollectionModel extends BaseModel protected $table = 'collections'; - protected $casts = [ - 'settings' => 'json', - 'settings.routes' => 'array', - 'settings.inject' => 'array', - 'settings.taxonomies' => 'array', - 'settings.structure' => 'array', - 'settings.sites' => 'array', - 'settings.revisions' => 'boolean', - 'settings.dated' => 'boolean', - 'settings.default_publish_state' => 'boolean', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + 'settings.routes' => 'array', + 'settings.inject' => 'array', + 'settings.taxonomies' => 'array', + 'settings.structure' => 'array', + 'settings.sites' => 'array', + 'settings.revisions' => 'boolean', + 'settings.dated' => 'boolean', + 'settings.default_publish_state' => 'boolean', + ]; + } } diff --git a/src/Commands/ExportAddonSettings.php b/src/Commands/ExportAddonSettings.php new file mode 100644 index 00000000..3bd88be3 --- /dev/null +++ b/src/Commands/ExportAddonSettings.php @@ -0,0 +1,49 @@ +each(function ($model) { + Addon::get($model->addon)?->settings()->set($model->settings)->save(); + }); + + $this->newLine(); + $this->info('Addon settings exported'); + + return 0; + } +} diff --git a/src/Commands/ExportGlobals.php b/src/Commands/ExportGlobals.php index 4dd9be28..25683dc9 100644 --- a/src/Commands/ExportGlobals.php +++ b/src/Commands/ExportGlobals.php @@ -2,7 +2,6 @@ namespace Statamic\Eloquent\Commands; -use Closure; use Illuminate\Console\Command; use Illuminate\Support\Facades\Facade; use Statamic\Console\RunsInPlease; @@ -28,7 +27,10 @@ class ExportGlobals extends Command * * @var string */ - protected $signature = 'statamic:eloquent:export-globals'; + protected $signature = 'statamic:eloquent:export-globals + {--force : Force the export to run, with all prompts answered "yes"} + {--only-globals : Only export global sets} + {--only-variables : Only export global variables}'; /** * The console command description. @@ -44,47 +46,75 @@ class ExportGlobals extends Command */ public function handle() { - $this->usingDefaultRepositories(function () { - $this->exportGlobals(); - }); - - return 0; - } - - private function usingDefaultRepositories(Closure $callback) - { + // ensure we are using stache globals, no matter what our config is Facade::clearResolvedInstance(GlobalRepositoryContract::class); - Facade::clearResolvedInstance(GlobalVariablesRepositoryContract::class); - Statamic::repository(GlobalRepositoryContract::class, GlobalRepository::class); - Statamic::repository(GlobalVariablesRepositoryContract::class, GlobalVariablesRepository::class); - app()->bind(GlobalSetContract::class, GlobalSet::class); + + // ensure we are using stache variables, no matter what our config is + Facade::clearResolvedInstance(GlobalVariablesRepositoryContract::class); + Statamic::repository(GlobalVariablesRepositoryContract::class, GlobalVariablesRepository::class); app()->bind(VariablesContract::class, Variables::class); - $callback(); + $this->exportGlobals(); + $this->exportGlobalVariables(); + + return 0; } private function exportGlobals() { + if (! $this->shouldExportGlobals()) { + return; + } + $sets = GlobalSetModel::all(); - $variables = VariablesModel::all(); - $this->withProgressBar($sets, function ($model) use ($variables) { - $global = GlobalSetFacade::make() + $this->withProgressBar($sets, function ($model) { + GlobalSetFacade::make() ->handle($model->handle) - ->title($model->title); + ->title($model->title) + ->sites($model->sites) + ->save(); + }); + + $this->newLine(); + $this->info('Globals exported'); + } + + private function exportGlobalVariables() + { + if (! $this->shouldExportVariables()) { + return; + } + + $variables = VariablesModel::all(); - foreach ($variables->where('handle', $model->handle) as $localization) { - $global->makeLocalization($localization->locale) - ->data($localization->data) - ->origin($localization->origin ?? null); + $this->withProgressBar($variables, function ($model) { + if (! $global = GlobalSetFacade::find($model->handle)) { + return; } - $global->save(); + $globalVariable = $global->makeLocalization($model->locale); + $globalVariable->data($model->data); + $globalVariable->save(); }); $this->newLine(); - $this->info('Globals exported'); + $this->info('Global variables exported'); + } + + private function shouldExportGlobals(): bool + { + return $this->option('only-globals') + || ! $this->option('only-variables') + && ($this->option('force') || $this->confirm('Do you want to export global sets?')); + } + + private function shouldExportVariables(): bool + { + return $this->option('only-variables') + || ! $this->option('only-globals') + && ($this->option('force') || $this->confirm('Do you want to export global variables?')); } } diff --git a/src/Commands/ImportAddonSettings.php b/src/Commands/ImportAddonSettings.php new file mode 100644 index 00000000..72af0508 --- /dev/null +++ b/src/Commands/ImportAddonSettings.php @@ -0,0 +1,50 @@ +filter(fn ($addon) => collect($addon->settings()->raw())->filter()->isNotEmpty()) + ->each(function ($addon) { + app('statamic.eloquent.addon_settings.model')::updateOrCreate( + ['addon' => $addon->id()], + ['settings' => $addon->settings()->raw()] + ); + }); + + $this->components->info('Addon settings imported successfully.'); + + return 0; + } +} diff --git a/src/Entries/EntryModel.php b/src/Entries/EntryModel.php index 77e89122..6d8c0e7c 100644 --- a/src/Entries/EntryModel.php +++ b/src/Entries/EntryModel.php @@ -11,11 +11,14 @@ class EntryModel extends BaseModel protected $table = 'entries'; - protected $casts = [ - 'date' => 'datetime', - 'data' => 'json', - 'published' => 'boolean', - ]; + protected function casts(): array + { + return [ + 'date' => 'datetime', + 'data' => 'json', + 'published' => 'boolean', + ]; + } public function author() { diff --git a/src/Fields/BlueprintModel.php b/src/Fields/BlueprintModel.php index ce75588d..de7ca6b4 100644 --- a/src/Fields/BlueprintModel.php +++ b/src/Fields/BlueprintModel.php @@ -11,9 +11,12 @@ class BlueprintModel extends BaseModel protected $table = 'blueprints'; - protected $casts = [ - 'data' => 'json', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Fields/FieldsetModel.php b/src/Fields/FieldsetModel.php index ad14288a..9de027ef 100644 --- a/src/Fields/FieldsetModel.php +++ b/src/Fields/FieldsetModel.php @@ -11,9 +11,12 @@ class FieldsetModel extends BaseModel protected $table = 'fieldsets'; - protected $casts = [ - 'data' => 'json', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Forms/FormModel.php b/src/Forms/FormModel.php index baae002e..685b1a0d 100644 --- a/src/Forms/FormModel.php +++ b/src/Forms/FormModel.php @@ -10,7 +10,10 @@ class FormModel extends BaseModel protected $table = 'forms'; - protected $casts = [ - 'settings' => 'json', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + ]; + } } diff --git a/src/Forms/SubmissionModel.php b/src/Forms/SubmissionModel.php index 89151e12..197e6274 100644 --- a/src/Forms/SubmissionModel.php +++ b/src/Forms/SubmissionModel.php @@ -12,9 +12,12 @@ class SubmissionModel extends BaseModel protected $table = 'form_submissions'; - protected $casts = [ - 'data' => 'json', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + ]; + } protected $dateFormat = 'Y-m-d H:i:s.u'; } diff --git a/src/Globals/GlobalSet.php b/src/Globals/GlobalSet.php index b4d8025e..9573b3a7 100644 --- a/src/Globals/GlobalSet.php +++ b/src/Globals/GlobalSet.php @@ -5,6 +5,7 @@ use Statamic\Contracts\Globals\GlobalSet as Contract; use Statamic\Eloquent\Globals\GlobalSetModel as Model; use Statamic\Globals\GlobalSet as FileEntry; +use Statamic\Support\Arr; class GlobalSet extends FileEntry { @@ -15,6 +16,7 @@ public static function fromModel(Model $model) $global = (new static) ->handle($model->handle) ->title($model->title) + ->sites(Arr::get($model->settings, 'sites')) ->model($model); return $global; @@ -31,7 +33,11 @@ public static function makeModelFromContract(Contract $source) return $class::firstOrNew(['handle' => $source->handle()])->fill([ 'title' => $source->title(), - 'settings' => [], // future proofing + 'settings' => [ + 'sites' => $source->sites() + ->mapWithKeys(fn ($site) => [$site => $source->origins()->get($site)]) + ->all(), + ], ]); } diff --git a/src/Globals/GlobalSetModel.php b/src/Globals/GlobalSetModel.php index ffba9629..f538c8d6 100644 --- a/src/Globals/GlobalSetModel.php +++ b/src/Globals/GlobalSetModel.php @@ -11,9 +11,12 @@ class GlobalSetModel extends BaseModel protected $table = 'global_sets'; - protected $casts = [ - 'settings' => 'json', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Globals/Variables.php b/src/Globals/Variables.php index 04adeb8e..5926e4bd 100644 --- a/src/Globals/Variables.php +++ b/src/Globals/Variables.php @@ -16,7 +16,6 @@ public static function fromModel(Model $model) ->globalSet($model->handle) ->locale($model->locale) ->data($model->data) - ->origin($model->origin ?? null) ->model($model); } @@ -36,15 +35,9 @@ public static function makeModelFromContract(Contract $source) 'locale' => $source->locale, ])->fill([ 'data' => $data->filter(fn ($v) => $v !== null), - 'origin' => $source->hasOrigin() ? $source->origin()->locale() : null, ]); } - protected function getOriginByString($origin) - { - return $this->globalSet()->in($origin); - } - public function model($model = null) { if (func_num_args() === 0) { diff --git a/src/Globals/VariablesModel.php b/src/Globals/VariablesModel.php index 4f4ec198..93ae0c67 100644 --- a/src/Globals/VariablesModel.php +++ b/src/Globals/VariablesModel.php @@ -11,9 +11,12 @@ class VariablesModel extends BaseModel protected $table = 'global_set_variables'; - protected $casts = [ - 'data' => 'array', - ]; + protected function casts(): array + { + return [ + 'data' => 'array', + ]; + } public function getAttribute($key) { diff --git a/src/Revisions/RevisionModel.php b/src/Revisions/RevisionModel.php index a7c22f14..bb62b1c8 100644 --- a/src/Revisions/RevisionModel.php +++ b/src/Revisions/RevisionModel.php @@ -10,7 +10,10 @@ class RevisionModel extends BaseModel protected $table = 'revisions'; - protected $casts = [ - 'attributes' => 'json', - ]; + protected function casts(): array + { + return [ + 'attributes' => 'json', + ]; + } } diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php index 3a903e09..9c5eb28e 100644 --- a/src/ServiceProvider.php +++ b/src/ServiceProvider.php @@ -4,6 +4,7 @@ use Illuminate\Foundation\Console\AboutCommand; use Statamic\Assets\AssetContainerContents; +use Statamic\Contracts\Addons\SettingsRepository as AddonSettingsRepositoryContract; use Statamic\Contracts\Assets\AssetContainerRepository as AssetContainerRepositoryContract; use Statamic\Contracts\Assets\AssetRepository as AssetRepositoryContract; use Statamic\Contracts\Entries\CollectionRepository as CollectionRepositoryContract; @@ -19,6 +20,7 @@ use Statamic\Contracts\Taxonomies\TaxonomyRepository as TaxonomyRepositoryContract; use Statamic\Contracts\Taxonomies\TermRepository as TermRepositoryContract; use Statamic\Contracts\Tokens\TokenRepository as TokenRepositoryContract; +use Statamic\Eloquent\AddonSettings\AddonSettingsRepository; use Statamic\Eloquent\Assets\AssetContainerContents as EloquentAssetContainerContents; use Statamic\Eloquent\Assets\AssetContainerRepository; use Statamic\Eloquent\Assets\AssetQueryBuilder; @@ -59,6 +61,8 @@ class ServiceProvider extends AddonServiceProvider \Statamic\Eloquent\Updates\ChangeFormSubmissionsIdType::class, \Statamic\Eloquent\Updates\AddIndexToDateOnEntriesTable::class, \Statamic\Eloquent\Updates\AddOrderToSitesTable::class, + \Statamic\Eloquent\Updates\DropOriginOnGlobalSetVariables::class, + \Statamic\Eloquent\Updates\UpdateGlobalVariables::class, ]; public function boot() @@ -169,6 +173,10 @@ private function publishMigrations(): void __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($addonSettingMigrations = [ + __DIR__.'/../database/migrations/2025_07_07_100000_create_addon_settings_table.php' => database_path('migrations/2025_07_07_100000_create_addon_settings_table.php'), + ], 'statamic-eloquent-addon-setting-migrations'); + $this->publishes( array_merge( $taxonomyMigrations, @@ -187,6 +195,7 @@ private function publishMigrations(): void $revisionMigrations, $tokenMigrations, $siteMigrations, + $addonSettingMigrations ), 'migrations' ); @@ -202,6 +211,7 @@ private function publishMigrations(): void public function register() { + $this->registerAddonSettings(); $this->registerAssetContainers(); $this->registerAssets(); $this->registerBlueprints(); @@ -222,6 +232,19 @@ public function register() $this->registerSites(); } + private function registerAddonSettings() + { + if (config('statamic.eloquent-driver.addon_settings.driver', 'file') != 'eloquent') { + return; + } + + $this->app->bind('statamic.eloquent.addon_settings.model', function () { + return config('statamic.eloquent-driver.addon_settings.model'); + }); + + Statamic::repository(AddonSettingsRepositoryContract::class, AddonSettingsRepository::class); + } + private function registerAssetContainers() { // if we have this config key then we started on 2.1.0 or earlier when @@ -547,6 +570,7 @@ protected function addAboutCommandInfo() } AboutCommand::add('Statamic Eloquent Driver', collect([ + 'Addon Settings' => config('statamic.eloquent-driver.addon_settings.driver', 'file'), 'Asset Containers' => config('statamic.eloquent-driver.asset_containers.driver', 'file'), 'Assets' => config('statamic.eloquent-driver.assets.driver', 'file'), 'Blueprints' => config('statamic.eloquent-driver.blueprints.driver', 'file'), diff --git a/src/Sites/SiteModel.php b/src/Sites/SiteModel.php index d5a16065..2baf2dd2 100644 --- a/src/Sites/SiteModel.php +++ b/src/Sites/SiteModel.php @@ -11,9 +11,12 @@ class SiteModel extends BaseModel protected $table = 'sites'; - protected $casts = [ - 'attributes' => 'json', - ]; + protected function casts(): array + { + return [ + 'attributes' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Structures/NavModel.php b/src/Structures/NavModel.php index c31513a3..42824236 100644 --- a/src/Structures/NavModel.php +++ b/src/Structures/NavModel.php @@ -10,7 +10,10 @@ class NavModel extends BaseModel protected $table = 'navigations'; - protected $casts = [ - 'settings' => 'json', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + ]; + } } diff --git a/src/Structures/TreeModel.php b/src/Structures/TreeModel.php index b078bbcc..6e3cca2a 100644 --- a/src/Structures/TreeModel.php +++ b/src/Structures/TreeModel.php @@ -10,8 +10,11 @@ class TreeModel extends BaseModel protected $table = 'trees'; - protected $casts = [ - 'tree' => 'json', - 'settings' => 'json', - ]; + protected function casts(): array + { + return [ + 'tree' => 'json', + 'settings' => 'json', + ]; + } } diff --git a/src/Taxonomies/TaxonomyModel.php b/src/Taxonomies/TaxonomyModel.php index 13d78188..17ccc19e 100644 --- a/src/Taxonomies/TaxonomyModel.php +++ b/src/Taxonomies/TaxonomyModel.php @@ -11,10 +11,13 @@ class TaxonomyModel extends BaseModel protected $table = 'taxonomies'; - protected $casts = [ - 'settings' => 'json', - 'sites' => 'json', - ]; + protected function casts(): array + { + return [ + 'settings' => 'json', + 'sites' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Taxonomies/TermModel.php b/src/Taxonomies/TermModel.php index aa5d326e..9291102e 100644 --- a/src/Taxonomies/TermModel.php +++ b/src/Taxonomies/TermModel.php @@ -11,9 +11,12 @@ class TermModel extends BaseModel protected $table = 'taxonomy_terms'; - protected $casts = [ - 'data' => 'json', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + ]; + } public function getAttribute($key) { diff --git a/src/Tokens/TokenModel.php b/src/Tokens/TokenModel.php index 82b70564..03fa7ec3 100644 --- a/src/Tokens/TokenModel.php +++ b/src/Tokens/TokenModel.php @@ -10,8 +10,11 @@ class TokenModel extends BaseModel protected $table = 'tokens'; - protected $casts = [ - 'data' => 'json', - 'expire_at' => 'datetime', - ]; + protected function casts(): array + { + return [ + 'data' => 'json', + 'expire_at' => 'datetime', + ]; + } } diff --git a/src/Updates/DropOriginOnGlobalSetVariables.php b/src/Updates/DropOriginOnGlobalSetVariables.php new file mode 100644 index 00000000..3b716b53 --- /dev/null +++ b/src/Updates/DropOriginOnGlobalSetVariables.php @@ -0,0 +1,24 @@ +isUpdatingTo('5.0.0'); + } + + public function update() + { + $source = __DIR__.'/../../database/migrations/updates/drop_origin_on_global_set_variables.php.stub'; + $dest = database_path('migrations/'.date('Y_m_d_His').'_drop_origin_on_global_set_variables.php'); + + $this->files->copy($source, $dest); + + $this->console()->info('Migrations created'); + $this->console()->comment('Remember to run `php artisan migrate` to apply it to your database.'); + } +} diff --git a/src/Updates/UpdateGlobalVariables.php b/src/Updates/UpdateGlobalVariables.php new file mode 100644 index 00000000..2b54001b --- /dev/null +++ b/src/Updates/UpdateGlobalVariables.php @@ -0,0 +1,40 @@ +isUpdatingTo('5.0.0'); + } + + public function update() + { + // This update script deals with reading and & writing from the database. There's an + // equivalent update script for Stache-driven sites that deals with reading & writing YAML files. + if (config('statamic.eloquent-driver.global_set_variables.driver') !== 'eloquent') { + return; + } + + // We don't need to do anything for single-site installs. + if (! Site::multiEnabled()) { + return; + } + + GlobalSet::all()->each(function ($globalSet) { + $variables = GlobalVariables::whereSet($globalSet->handle()); + + $sites = $variables->mapWithKeys(function ($variable) { + return [$variable->locale() => $variable->model()->origin]; + }); + + $globalSet->sites($sites)->save(); + }); + } +} diff --git a/tests/Commands/ExportAddonSettingsTest.php b/tests/Commands/ExportAddonSettingsTest.php new file mode 100644 index 00000000..3ea99fb7 --- /dev/null +++ b/tests/Commands/ExportAddonSettingsTest.php @@ -0,0 +1,67 @@ +makeFromPackage(['id' => 'statamic/seo-pro', 'slug' => 'seo-pro']); + $importer = $this->makeFromPackage(['id' => 'statamic/importer', 'slug' => 'importer']); + + Facades\Addon::shouldReceive('all')->andReturn(collect([$seoPro, $importer])); + Facades\Addon::shouldReceive('get')->with('statamic/seo-pro')->andReturn($seoPro); + Facades\Addon::shouldReceive('get')->with('statamic/importer')->andReturn($importer); + + AddonSettingsModel::create(['addon' => 'statamic/seo-pro', 'settings' => ['title' => 'SEO Title', 'description' => 'SEO Description']]); + AddonSettingsModel::create(['addon' => 'statamic/importer', 'settings' => ['chunk_size' => 100]]); + + $this->artisan('statamic:eloquent:export-addon-settings') + ->expectsOutputToContain('Addon settings exported') + ->assertExitCode(0); + + $this->assertFileExists(resource_path('addons/seo-pro.yaml')); + $this->assertEquals(<<<'YAML' +title: 'SEO Title' +description: 'SEO Description' + +YAML + , File::get(resource_path('addons/seo-pro.yaml'))); + + $this->assertFileExists(resource_path('addons/importer.yaml')); + $this->assertEquals(<<<'YAML' +chunk_size: 100 + +YAML + , File::get(resource_path('addons/importer.yaml'))); + } + + private function makeFromPackage($attributes = []) + { + return Addon::makeFromPackage(array_merge([ + 'id' => 'vendor/test-addon', + 'name' => 'Test Addon', + 'description' => 'Test description', + 'namespace' => 'Vendor\\TestAddon', + 'provider' => TestAddonServiceProvider::class, + 'autoload' => '', + 'url' => 'http://test-url.com', + 'developer' => 'Test Developer LLC', + 'developerUrl' => 'http://test-developer.com', + 'version' => '1.0', + 'editions' => ['foo', 'bar'], + ], $attributes)); + } +} diff --git a/tests/Commands/ExportGlobalsTest.php b/tests/Commands/ExportGlobalsTest.php new file mode 100644 index 00000000..8a3494b9 --- /dev/null +++ b/tests/Commands/ExportGlobalsTest.php @@ -0,0 +1,256 @@ +createGlobalSet(); + $this->createGlobalVariables($globalSet->handle); + + // Run command with force flag + $this->artisan('statamic:eloquent:export-globals', ['--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Globals exported') + ->expectsOutput('Global variables exported'); + + // Verify files were created + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/{$globalSet->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$globalSet->handle}.yaml")); + } + + #[Test] + public function it_can_export_only_globals_when_specified() + { + // Create test global set + $globalSet = $this->createGlobalSet(); + $this->createGlobalVariables($globalSet->handle); + + // Run command with only-globals flag + $this->artisan('statamic:eloquent:export-globals', ['--only-globals' => true, '--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Globals exported') + ->doesntExpectOutput('Global variables exported'); + + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/{$globalSet->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$globalSet->handle}.yaml")); + } + + #[Test] + public function it_can_export_only_variables_when_specified() + { + // Create test global set and variables + $globalSet = $this->createGlobalSet(); + $this->createGlobalVariables($globalSet->handle); + + // Create the global set file first (needed for variables export) + GlobalSet::make()->handle($globalSet->handle)->title($globalSet->title)->save(); + + // Run command with only-variables flag + $this->artisan('statamic:eloquent:export-globals', ['--only-variables' => true, '--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Global variables exported') + ->doesntExpectOutput('Globals exported'); + + // Verify variables file was created + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$globalSet->handle}.yaml")); + } + + #[Test] + public function it_prompts_for_confirmation_when_not_forced() + { + $this->createGlobalSet(); + + $this->artisan('statamic:eloquent:export-globals') + ->expectsConfirmation('Do you want to export global sets?', 'yes') + ->expectsConfirmation('Do you want to export global variables?', 'yes') + ->assertExitCode(0); + } + + #[Test] + public function it_skips_globals_when_user_declines() + { + $globalSet = $this->createGlobalSet(); + $this->createGlobalVariables($globalSet->handle); + + // Create the global set file first (needed for variables export) + GlobalSet::make()->handle($globalSet->handle)->title($globalSet->title)->save(); + + $this->artisan('statamic:eloquent:export-globals') + ->expectsConfirmation('Do you want to export global sets?', 'no') + ->expectsConfirmation('Do you want to export global variables?', 'yes') + ->assertExitCode(0) + ->doesntExpectOutput('Globals exported') + ->expectsOutput('Global variables exported'); + } + + #[Test] + public function it_skips_variables_when_user_declines() + { + $this->createGlobalSet(); + + $this->artisan('statamic:eloquent:export-globals') + ->expectsConfirmation('Do you want to export global sets?', 'yes') + ->expectsConfirmation('Do you want to export global variables?', 'no') + ->assertExitCode(0) + ->expectsOutput('Globals exported') + ->doesntExpectOutput('Global variables exported'); + } + + #[Test] + public function it_handles_empty_global_sets() + { + // Ensure there are no global sets + GlobalSetModel::query()->delete(); + + $this->artisan('statamic:eloquent:export-globals', ['--only-globals' => true, '--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Globals exported'); + } + + #[Test] + public function it_handles_empty_variables() + { + // Ensure there are no variables + VariablesModel::query()->delete(); + + $this->artisan('statamic:eloquent:export-globals', ['--only-variables' => true, '--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Global variables exported'); + } + + #[Test] + public function it_skips_variables_when_global_set_not_found() + { + // Create variables for a non-existent global set + $this->createGlobalVariables('non-existent-global'); + + $this->artisan('statamic:eloquent:export-globals', ['--only-variables' => true, '--force' => true]) + ->assertExitCode(0) + ->expectsOutput('Global variables exported'); + + // Verify no variable files were created + $this->assertFileDoesNotExist(Path::resolve('tests/__fixtures__/content/globals/en/non-existent-global.yaml')); + } + + #[Test] + public function it_exports_multiple_globals_and_their_variables() + { + // Create multiple global sets and variables + $global1 = $this->createGlobalSet('site-settings', 'Site Settings'); + $global2 = $this->createGlobalSet('social-media', 'Social Media'); + + $this->createGlobalVariables($global1->handle, ['site_name' => 'My Site', 'description' => 'A test site']); + $this->createGlobalVariables($global2->handle, ['twitter' => '@handle', 'facebook' => 'facebook.com/mypage']); + + $this->artisan('statamic:eloquent:export-globals', ['--force' => true]) + ->assertExitCode(0); + + // Verify all files were created + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/{$global1->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$global1->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/{$global2->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$global2->handle}.yaml")); + + // Verify content of variable files + $content1 = File::get(Path::resolve("tests/__fixtures__/content/globals/en/{$global1->handle}.yaml")); + $content2 = File::get(Path::resolve("tests/__fixtures__/content/globals/en/{$global2->handle}.yaml")); + + $this->assertStringContainsString('site_name: \'My Site\'', $content1); + $this->assertStringContainsString('description: \'A test site\'', $content1); + $this->assertStringContainsString('twitter: \'@handle\'', $content2); + $this->assertStringContainsString('facebook: facebook.com/mypage', $content2); + } + + #[Test] + public function it_exports_globals_with_multiple_sites() + { + // Configure app to support multiple sites + config(['statamic.sites.sites' => [ + 'default' => ['name' => 'English', 'locale' => 'en', 'url' => '/'], + 'fr' => ['name' => 'French', 'locale' => 'fr', 'url' => '/fr/'], + ]]); + + // Create global set with multiple sites + $globalSet = $this->createGlobalSet('site-info', 'Site Info', ['default', 'fr']); + + // Create variables for each site + $this->createGlobalVariables($globalSet->handle, ['title' => 'My Site'], 'default'); + $this->createGlobalVariables($globalSet->handle, ['title' => 'Mon Site'], 'fr'); + + $this->artisan('statamic:eloquent:export-globals', ['--force' => true]) + ->assertExitCode(0); + + // Verify files for both sites were created + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/{$globalSet->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/en/{$globalSet->handle}.yaml")); + $this->assertFileExists(Path::resolve("tests/__fixtures__/content/globals/fr/{$globalSet->handle}.yaml")); + + // Verify content of variable files + File::get(Path::resolve("tests/__fixtures__/content/globals/en/{$globalSet->handle}.yaml")); + File::get(Path::resolve("tests/__fixtures__/content/globals/fr/{$globalSet->handle}.yaml")); + } + + /** + * Create a test GlobalSetModel. + */ + private function createGlobalSet($handle = 'test-global', $title = 'Test Global', $sites = ['default']) + { + return GlobalSetModel::create([ + 'handle' => $handle, + 'title' => $title, + 'settings' => [ + 'sites' => collect($sites)->mapWithKeys(fn ($site) => [$site => null])->all(), + ], + ]); + } + + /** + * Create a test VariablesModel. + */ + private function createGlobalVariables($handle = 'test-global', $data = ['key' => 'value'], $locale = 'en') + { + return VariablesModel::create([ + 'handle' => $handle, + 'locale' => $locale, + 'data' => $data, + ]); + } +} diff --git a/tests/Commands/ImportAddonSettingsTest.php b/tests/Commands/ImportAddonSettingsTest.php new file mode 100644 index 00000000..fa423e82 --- /dev/null +++ b/tests/Commands/ImportAddonSettingsTest.php @@ -0,0 +1,113 @@ +app->bind(SettingsContract::class, FileSettings::class); + $this->app->bind(SettingsRepositoryContract::class, FileSettingsRepository::class); + + $this->app->bind('statamic.eloquent.addon_settings.model', function () { + return AddonSettingsModel::class; + }); + + $this->app['files']->deleteDirectory(resource_path('addons')); + } + + #[Test] + public function it_imports_addon_settings() + { + $this->assertCount(0, AddonSettingsModel::all()); + + $seoPro = $this->makeFromPackage(['id' => 'statamic/seo-pro']); + Facades\Addon::shouldReceive('get')->with('statamic/seo-pro')->andReturn($seoPro); + app(SettingsRepositoryContract::class)->make($seoPro, ['title' => 'SEO Title', 'description' => 'SEO Description'])->save(); + + $importer = $this->makeFromPackage(['id' => 'statamic/importer']); + Facades\Addon::shouldReceive('get')->with('statamic/importer')->andReturn($importer); + app(SettingsRepositoryContract::class)->make($importer, ['chunk_size' => 100])->save(); + + Facades\Addon::shouldReceive('all')->andReturn(collect([$seoPro, $importer])); + + $this->artisan('statamic:eloquent:import-addon-settings') + ->expectsOutputToContain('Addon settings imported successfully.') + ->assertExitCode(0); + + $this->assertCount(2, AddonSettingsModel::all()); + + $this->assertDatabaseHas('addon_settings', [ + 'addon' => 'statamic/seo-pro', + 'settings' => json_encode(['title' => 'SEO Title', 'description' => 'SEO Description']), + ]); + + $this->assertDatabaseHas('addon_settings', [ + 'addon' => 'statamic/importer', + 'settings' => json_encode(['chunk_size' => 100]), + ]); + } + + #[Test] + public function it_doesnt_import_addons_without_settings() + { + $this->assertCount(0, AddonSettingsModel::all()); + + $seoPro = $this->makeFromPackage(['id' => 'statamic/seo-pro']); + Facades\Addon::shouldReceive('get')->with('statamic/seo-pro')->andReturn($seoPro); + app(SettingsRepositoryContract::class)->make($seoPro, ['title' => 'SEO Title', 'description' => 'SEO Description'])->save(); + + $importer = $this->makeFromPackage(['id' => 'statamic/importer']); + Facades\Addon::shouldReceive('get')->with('statamic/importer')->andReturn($importer); + + Facades\Addon::shouldReceive('all')->andReturn(collect([$seoPro, $importer])); + + $this->artisan('statamic:eloquent:import-addon-settings') + ->expectsOutputToContain('Addon settings imported successfully.') + ->assertExitCode(0); + + $this->assertCount(1, AddonSettingsModel::all()); + + $this->assertDatabaseHas('addon_settings', [ + 'addon' => 'statamic/seo-pro', + 'settings' => json_encode(['title' => 'SEO Title', 'description' => 'SEO Description']), + ]); + + $this->assertDatabaseMissing('addon_settings', [ + 'addon' => 'statamic/importer', + ]); + } + + private function makeFromPackage($attributes = []) + { + return Addon::makeFromPackage(array_merge([ + 'id' => 'vendor/test-addon', + 'name' => 'Test Addon', + 'description' => 'Test description', + 'namespace' => 'Vendor\\TestAddon', + 'provider' => TestAddonServiceProvider::class, + 'autoload' => '', + 'url' => 'http://test-url.com', + 'developer' => 'Test Developer LLC', + 'developerUrl' => 'http://test-developer.com', + 'version' => '1.0', + 'editions' => ['foo', 'bar'], + ], $attributes)); + } +} diff --git a/tests/Commands/ImportGlobalsTest.php b/tests/Commands/ImportGlobalsTest.php index 88effc9c..87b57bfb 100644 --- a/tests/Commands/ImportGlobalsTest.php +++ b/tests/Commands/ImportGlobalsTest.php @@ -35,8 +35,7 @@ protected function setUp(): void public function it_imports_global_sets_and_variables() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); @@ -58,8 +57,7 @@ public function it_imports_global_sets_and_variables() public function it_imports_global_sets_and_variables_with_force_argument() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); @@ -79,8 +77,7 @@ public function it_imports_global_sets_and_variables_with_force_argument() public function it_imports_only_global_sets_with_console_question() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); @@ -102,8 +99,7 @@ public function it_imports_only_global_sets_with_console_question() public function it_imports_only_global_sets_with_only_global_sets_argument() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); @@ -123,8 +119,7 @@ public function it_imports_only_global_sets_with_only_global_sets_argument() public function it_imports_only_variables_with_console_question() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); @@ -146,8 +141,7 @@ public function it_imports_only_variables_with_console_question() public function it_imports_only_variables_with_only_global_variables_argument() { $globalSet = tap(GlobalSet::make('footer')->title('Footer'))->save(); - $variables = $globalSet->makeLocalization('en')->data(['foo' => 'bar']); - $globalSet->addLocalization($variables)->save(); + $globalSet->in('en')->data(['foo' => 'bar'])->save(); $this->assertCount(0, GlobalSetModel::all()); $this->assertCount(0, VariablesModel::all()); diff --git a/tests/Data/Globals/GlobalSetTest.php b/tests/Data/Globals/GlobalSetTest.php index b27d983d..a7a87ecd 100644 --- a/tests/Data/Globals/GlobalSetTest.php +++ b/tests/Data/Globals/GlobalSetTest.php @@ -9,46 +9,8 @@ class GlobalSetTest extends TestCase { #[Test] - public function it_gets_file_contents_for_saving_with_a_single_site() + public function it_gets_file_contents_for_saving() { - config()->set('statamic.system.multisite', false); - - $this->setSites([ - 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => 'http://test.com/'], - ]); - - $set = (new GlobalSet)->title('The title'); - - $variables = $set->makeLocalization('en')->data([ - 'array' => ['first one', 'second one'], - 'string' => 'The string', - ]); - - $set->addLocalization($variables); - - $expected = <<<'EOT' -title: 'The title' -data: - array: - - 'first one' - - 'second one' - string: 'The string' - -EOT; - $this->assertEquals($expected, $set->fileContents()); - } - - #[Test] - public function it_gets_file_contents_for_saving_with_multiple_sites() - { - config()->set('statamic.system.multisite', true); - - $this->setSites([ - '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 = (new GlobalSet)->title('The title'); // We set the data but it's basically irrelevant since it won't get saved to this file. @@ -58,12 +20,6 @@ public function it_gets_file_contents_for_saving_with_multiple_sites() 'string' => 'The string', ]); }); - $set->in('fr', function ($loc) { - $loc->data([ - 'array' => ['le first one', 'le second one'], - 'string' => 'Le string', - ]); - }); $expected = <<<'EOT' title: 'The title' diff --git a/tests/Data/Globals/VariablesTest.php b/tests/Data/Globals/VariablesTest.php index c509f14e..d04b4377 100644 --- a/tests/Data/Globals/VariablesTest.php +++ b/tests/Data/Globals/VariablesTest.php @@ -22,7 +22,9 @@ class VariablesTest extends TestCase #[Test] public function it_gets_file_contents_for_saving() { - $entry = (new Variables)->data([ + $global = GlobalSet::make('test')->sites(['en']); + + $entry = (new Variables)->globalSet($global)->data([ 'array' => ['first one', 'second one'], 'string' => 'The string', 'null' => null, // this... @@ -42,33 +44,40 @@ public function it_gets_file_contents_for_saving() #[Test] public function it_gets_file_contents_for_saving_a_localized_set() { - $global = GlobalSet::make('test'); + $this->setSites([ + 'a' => ['url' => '/', 'locale' => 'en'], + 'b' => ['url' => '/b/', 'locale' => 'fr'], + 'c' => ['url' => '/b/', 'locale' => 'fr'], + 'd' => ['url' => '/d/', 'locale' => 'fr'], + ]); - $a = $global->makeLocalization('a')->data([ - 'array' => ['first one', 'second one'], + $global = GlobalSet::make('test')->sites([ + 'a' => null, + 'b' => 'a', + 'c' => null, + ])->save(); + + $a = $global->in('a')->data([ + 'array' => ['first one', 'second one'], 'string' => 'The string', - 'null' => null, // this... - 'empty' => [], // and this should get stripped out because there's no origin to fall back to. + 'null' => null, // this... + 'empty' => [], // and this should get stripped out because there's no origin to fall back to. ]); - $b = $global->makeLocalization('b')->origin($a)->data([ - 'array' => ['first one', 'second one'], + $b = $global->in('b')->data([ + 'array' => ['first one', 'second one'], 'string' => 'The string', - 'null' => null, // this... - 'empty' => [], // and this should not get stripped out, otherwise it would fall back to the origin. + 'null' => null, // this... + 'empty' => [], // and this should not get stripped out, otherwise it would fall back to the origin. ]); - $c = $global->makeLocalization('c')->data([ - 'array' => ['first one', 'second one'], + $c = $global->in('c')->data([ + 'array' => ['first one', 'second one'], 'string' => 'The string', - 'null' => null, // this... - 'empty' => [], // and this should get stripped out because there's no origin to fall back to. + 'null' => null, // this... + 'empty' => [], // and this should get stripped out because there's no origin to fall back to. ]); - $global->addLocalization($a); - $global->addLocalization($b); - $global->addLocalization($c); - $expected = <<<'EOT' array: - 'first one' @@ -85,7 +94,6 @@ public function it_gets_file_contents_for_saving_a_localized_set() string: 'The string' 'null': null empty: { } -origin: a EOT; $this->assertEquals($expected, $b->fileContents()); @@ -103,9 +111,22 @@ public function it_gets_file_contents_for_saving_a_localized_set() #[Test] public function if_the_value_is_explicitly_set_to_null_then_it_should_not_fall_back() { - $global = GlobalSet::make('test'); + $this->setSites([ + 'a' => ['url' => '/', 'locale' => 'en'], + 'b' => ['url' => '/b/', 'locale' => 'fr'], + 'c' => ['url' => '/b/', 'locale' => 'fr'], + 'd' => ['url' => '/d/', 'locale' => 'fr'], + ]); + + $global = GlobalSet::make('test')->sites([ + 'a' => null, + 'b' => 'a', + 'c' => 'b', + 'd' => null, + 'e' => 'd', + ])->save(); - $a = $global->makeLocalization('a')->data([ + $a = $global->in('a')->data([ 'one' => 'alfa', 'two' => 'bravo', 'three' => 'charlie', @@ -113,35 +134,29 @@ public function if_the_value_is_explicitly_set_to_null_then_it_should_not_fall_b ]); // originates from a - $b = $global->makeLocalization('b')->origin($a)->data([ + $b = $global->in('b')->data([ 'one' => 'echo', 'two' => null, ]); // originates from b, which originates from a - $c = $global->makeLocalization('c')->origin($b)->data([ + $c = $global->in('c')->data([ 'three' => 'foxtrot', ]); // does not originate from anything - $d = $global->makeLocalization('d')->data([ + $d = $global->in('d')->data([ 'one' => 'golf', 'two' => 'hotel', 'three' => 'india', ]); // originates from d. just to test that it doesn't unintentionally fall back to the default/first. - $e = $global->makeLocalization('e')->origin($d)->data([ + $e = $global->in('e')->data([ 'one' => 'juliett', 'two' => null, ]); - $global->addLocalization($a); - $global->addLocalization($b); - $global->addLocalization($c); - $global->addLocalization($d); - $global->addLocalization($e); - $this->assertEquals([ 'one' => 'alfa', 'two' => 'bravo', diff --git a/tests/Factories/GlobalFactory.php b/tests/Factories/GlobalFactory.php deleted file mode 100644 index e5498094..00000000 --- a/tests/Factories/GlobalFactory.php +++ /dev/null @@ -1,55 +0,0 @@ -id = $id; - - return $this; - } - - public function handle($handle) - { - $this->handle = $handle; - - return $this; - } - - public function data($data) - { - $this->data = $data; - - return $this; - } - - public function make() - { - $set = GlobalSet::make($this->handle); - - $set->addLocalization( - $set->makeLocalization('en')->data($this->data) - ); - - if ($this->id) { - $set->id($this->id); - } - - return $set; - } - - public function create() - { - return tap($this->make())->save(); - } -} diff --git a/tests/Globals/GlobalsetTest.php b/tests/Globals/GlobalsetTest.php deleted file mode 100644 index a7c9c853..00000000 --- a/tests/Globals/GlobalsetTest.php +++ /dev/null @@ -1,30 +0,0 @@ -addLocalization( - $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']) - ); - - $global->save(); - - Event::assertDispatched(GlobalSetSaved::class); - Event::assertDispatched(GlobalVariablesSaved::class); - } -} diff --git a/tests/Repositories/AddonSettingsRepositoryTest.php b/tests/Repositories/AddonSettingsRepositoryTest.php new file mode 100644 index 00000000..27996102 --- /dev/null +++ b/tests/Repositories/AddonSettingsRepositoryTest.php @@ -0,0 +1,95 @@ +repo = new AddonSettingsRepository; + } + + #[Test] + public function it_gets_addon_settings() + { + $addon = $this->makeFromPackage(); + + Facades\Addon::shouldReceive('all')->andReturn(collect([$addon])); + Facades\Addon::shouldReceive('get')->with('vendor/test-addon')->andReturn($addon); + + AddonSettingsModel::create(['addon' => 'vendor/test-addon', 'settings' => ['foo' => 'bar', 'baz' => 'qux']]); + + $settings = $this->repo->find('vendor/test-addon'); + + $this->assertInstanceOf(AddonSettings::class, $settings); + $this->assertEquals($addon, $settings->addon()); + $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $settings->all()); + } + + #[Test] + public function it_saves_addon_settings() + { + $addon = $this->makeFromPackage(); + + $settings = $this->repo->make($addon, [ + 'foo' => 'bar', + 'baz' => 'qux', + 'quux' => null, // Should be filtered out. + ]); + + $settings->save(); + + $this->assertDatabaseHas('addon_settings', [ + 'addon' => 'vendor/test-addon', + 'settings' => json_encode(['foo' => 'bar', 'baz' => 'qux']), + ]); + } + + #[Test] + public function it_deletes_addon_settings() + { + $addon = $this->makeFromPackage(); + + Facades\Addon::shouldReceive('all')->andReturn(collect([$addon])); + Facades\Addon::shouldReceive('get')->with('vendor/test-addon')->andReturn($addon); + + AddonSettingsModel::create(['addon' => 'vendor/test-addon', 'settings' => ['foo' => 'bar', 'baz' => 'qux']]); + + $this->repo->find('vendor/test-addon')->delete(); + + $this->assertDatabaseMissing('addon_settings', [ + 'addon' => 'vendor/test-addon', + ]); + } + + private function makeFromPackage($attributes = []) + { + return Addon::makeFromPackage(array_merge([ + 'id' => 'vendor/test-addon', + 'name' => 'Test Addon', + 'description' => 'Test description', + 'namespace' => 'Vendor\\TestAddon', + 'provider' => TestAddonServiceProvider::class, + 'autoload' => '', + 'url' => 'http://test-url.com', + 'developer' => 'Test Developer LLC', + 'developerUrl' => 'http://test-developer.com', + 'version' => '1.0', + 'editions' => ['foo', 'bar'], + ], $attributes)); + } +} diff --git a/tests/Repositories/GlobalRepositoryTest.php b/tests/Repositories/GlobalRepositoryTest.php index 197d4868..77c945b0 100644 --- a/tests/Repositories/GlobalRepositoryTest.php +++ b/tests/Repositories/GlobalRepositoryTest.php @@ -24,10 +24,10 @@ protected function setUp(): void $this->app->instance(Stache::class, $stache); $this->repo = new GlobalRepository($stache); - $globalOne = $this->repo->make('contact')->title('Contact Details')->save(); + $globalOne = $this->repo->make('contact')->title('Contact Details')->sites(['en'])->save(); (new Variables)->globalSet($globalOne)->data(['phone' => '555-1234'])->save(); - $globalTwo = $this->repo->make('global')->title('General')->save(); + $globalTwo = $this->repo->make('global')->title('General')->sites(['en'])->save(); (new Variables)->globalSet($globalTwo)->data(['foo' => 'Bar'])->save(); } @@ -89,17 +89,12 @@ public function it_gets_a_global_set_by_handle() #[Test] public function it_saves_a_global_to_the_database() { - $global = GlobalSetAPI::make('new'); - - $global->addLocalization( - $global->makeLocalization('en')->data(['foo' => 'bar', 'baz' => 'qux']) - ); + $global = GlobalSetAPI::make('new')->sites(['en']); $this->assertNull($this->repo->findByHandle('new')); $this->repo->save($global); $this->assertNotNull($item = $this->repo->find('new')); - $this->assertEquals(['foo' => 'bar', 'baz' => 'qux'], $item->in('en')->data()->all()); } } diff --git a/tests/UpdateScripts/Concerns/RunsUpdateScripts.php b/tests/UpdateScripts/Concerns/RunsUpdateScripts.php new file mode 100644 index 00000000..9e036079 --- /dev/null +++ b/tests/UpdateScripts/Concerns/RunsUpdateScripts.php @@ -0,0 +1,29 @@ +update(); + + return $script; + } + + protected function assertUpdateScriptRegistered($class) + { + $this->assertTrue( + app('statamic.update-scripts')->map->class->contains($class), + "Update script $class is not registered." + ); + } +} diff --git a/tests/UpdateScripts/UpdateGlobalVariablesTest.php b/tests/UpdateScripts/UpdateGlobalVariablesTest.php new file mode 100644 index 00000000..adbca2d6 --- /dev/null +++ b/tests/UpdateScripts/UpdateGlobalVariablesTest.php @@ -0,0 +1,62 @@ +string('origin')->nullable(); + }); + } + + #[Test] + public function it_is_registered() + { + $this->assertUpdateScriptRegistered(UpdateGlobalVariables::class); + } + + #[Test] + public function it_builds_the_sites_array_in_a_multisite_install() + { + $this->setSites([ + 'en' => ['url' => '/', 'locale' => 'en_US', 'name' => 'English'], + 'fr' => ['url' => '/', 'locale' => 'fr_FR', 'name' => 'French'], + 'de' => ['url' => '/', 'locale' => 'de_DE', 'name' => 'German'], + ]); + + GlobalSetModel::create(['handle' => 'test', 'title' => 'Test', 'settings' => []]); + VariablesModel::create(['handle' => 'test', 'locale' => 'en', 'origin' => null, 'data' => ['foo' => 'Bar', 'baz' => 'Qux']]); + VariablesModel::create(['handle' => 'test', 'locale' => 'fr', 'origin' => 'en', 'data' => ['foo' => 'Bar']]); + VariablesModel::create(['handle' => 'test', 'locale' => 'de', 'origin' => 'fr', 'data' => []]); + + $this->runUpdateScript(UpdateGlobalVariables::class); + + $this->assertDatabaseHas(GlobalSetModel::class, [ + 'handle' => 'test', + 'settings' => json_encode([ + 'sites' => [ + 'en' => null, + 'fr' => 'en', + 'de' => 'fr', + ], + ]), + ]); + } +}