diff --git a/resources/js/bootstrap/components.js b/resources/js/bootstrap/components.js index 1ab414772f..545539ab3e 100644 --- a/resources/js/bootstrap/components.js +++ b/resources/js/bootstrap/components.js @@ -60,6 +60,7 @@ import ResourceDeleter from '../components/ResourceDeleter.vue'; import Stack from '../components/stacks/Stack.vue'; import StackTest from '../components/stacks/StackTest.vue'; import CodeBlock from '../components/CodeBlock.vue'; +import BlueprintResetter from '../components/blueprints/BlueprintResetter.vue'; // Third Party Vue.component('v-select', vSelect) @@ -142,3 +143,5 @@ Vue.component('resource-deleter', ResourceDeleter); Vue.component('stack', Stack); Vue.component('stack-test', StackTest); + +Vue.component('blueprint-resetter', BlueprintResetter); diff --git a/resources/js/components/blueprints/BlueprintResetter.vue b/resources/js/components/blueprints/BlueprintResetter.vue new file mode 100644 index 0000000000..b4e30be1f7 --- /dev/null +++ b/resources/js/components/blueprints/BlueprintResetter.vue @@ -0,0 +1,102 @@ + + + diff --git a/resources/views/blueprints/index.blade.php b/resources/views/blueprints/index.blade.php index 1a6e9e50bd..549b6c66e3 100644 --- a/resources/views/blueprints/index.blade.php +++ b/resources/views/blueprints/index.blade.php @@ -193,6 +193,20 @@ {{ $blueprint['title'] }} + + @if ($blueprint['is_resettable']) + + + + + + + @endif + @endforeach diff --git a/routes/cp.php b/routes/cp.php index 5029601f26..f10d6cd66d 100644 --- a/routes/cp.php +++ b/routes/cp.php @@ -256,6 +256,7 @@ Route::get('blueprints', [BlueprintController::class, 'index'])->name('blueprints.index'); Route::get('blueprints/{namespace}/{handle}', [BlueprintController::class, 'edit'])->name('blueprints.edit'); Route::patch('blueprints/{namespace}/{handle}', [BlueprintController::class, 'update'])->name('blueprints.update'); + Route::delete('blueprints/{namespace}/{handle}/reset', [BlueprintController::class, 'reset'])->name('blueprints.reset'); Route::get('fieldtypes', [FieldtypesController::class, 'index']); }); diff --git a/src/Events/BlueprintReset.php b/src/Events/BlueprintReset.php new file mode 100644 index 0000000000..2de760bb2e --- /dev/null +++ b/src/Events/BlueprintReset.php @@ -0,0 +1,20 @@ +blueprint = $blueprint; + } + + public function commitMessage() + { + return __('Blueprint reset', [], config('statamic.git.locale')); + } +} diff --git a/src/Events/Concerns/ListensForContentEvents.php b/src/Events/Concerns/ListensForContentEvents.php index 5f3c77a258..dd3707fab8 100644 --- a/src/Events/Concerns/ListensForContentEvents.php +++ b/src/Events/Concerns/ListensForContentEvents.php @@ -19,6 +19,7 @@ trait ListensForContentEvents \Statamic\Events\AssetReuploaded::class, \Statamic\Events\AssetReferencesUpdated::class, \Statamic\Events\BlueprintDeleted::class, + \Statamic\Events\BlueprintReset::class, \Statamic\Events\BlueprintSaved::class, \Statamic\Events\CollectionDeleted::class, \Statamic\Events\CollectionSaved::class, diff --git a/src/Fields/Blueprint.php b/src/Fields/Blueprint.php index 948b744d55..2717299a08 100644 --- a/src/Fields/Blueprint.php +++ b/src/Fields/Blueprint.php @@ -17,6 +17,7 @@ use Statamic\Events\BlueprintCreating; use Statamic\Events\BlueprintDeleted; use Statamic\Events\BlueprintDeleting; +use Statamic\Events\BlueprintReset; use Statamic\Events\BlueprintSaved; use Statamic\Events\BlueprintSaving; use Statamic\Exceptions\DuplicateFieldException; @@ -414,6 +415,12 @@ public function isDeletable() return ! $this->isNamespaced(); } + public function isResettable() + { + return $this->isNamespaced() + && File::exists($this->path()); + } + public function toPublishArray() { return [ @@ -501,6 +508,15 @@ public function delete() return true; } + public function reset() + { + BlueprintRepository::reset($this); + + BlueprintReset::dispatch($this); + + return true; + } + public function ensureField($handle, $fieldConfig, $tab = null, $prepend = false) { return $this->ensureFieldInTab($handle, $fieldConfig, $tab, $prepend); @@ -731,6 +747,11 @@ public function toQueryableValue() return $this->handle(); } + public function resetUrl() + { + return cp_route('blueprints.reset', [$this->namespace(), $this->handle()]); + } + public function writeFile($path = null) { File::put($path ?? $this->buildPath(), $this->fileContents()); diff --git a/src/Fields/BlueprintRepository.php b/src/Fields/BlueprintRepository.php index c1e0da1d2b..f899460d99 100644 --- a/src/Fields/BlueprintRepository.php +++ b/src/Fields/BlueprintRepository.php @@ -130,6 +130,15 @@ public function delete(Blueprint $blueprint) $blueprint->deleteFile(); } + public function reset(Blueprint $blueprint) + { + if (! $blueprint->isNamespaced()) { + throw new \Exception('Non-namespaced blueprints cannot be reset'); + } + + File::delete($blueprint->path()); + } + private function clearBlinkCaches() { Blink::store(self::BLINK_FOUND)->flush(); diff --git a/src/Http/Controllers/CP/Fields/BlueprintController.php b/src/Http/Controllers/CP/Fields/BlueprintController.php index 41a79d0579..bc031724b3 100644 --- a/src/Http/Controllers/CP/Fields/BlueprintController.php +++ b/src/Http/Controllers/CP/Fields/BlueprintController.php @@ -29,6 +29,8 @@ public function index() 'handle' => $blueprint->handle(), 'namespace' => $blueprint->namespace(), 'title' => $blueprint->title(), + 'reset_url' => $blueprint->resetUrl(), + 'is_resettable' => $blueprint->isResettable(), ]; }) ->sortBy('title') @@ -73,4 +75,17 @@ public function update(Request $request, $namespace, $handle) $this->updateBlueprint($request, $blueprint); } + + public function reset($namespace, $handle) + { + $blueprint = Blueprint::find($namespace.'::'.$handle); + + if (! $blueprint) { + throw new NotFoundHttpException; + } + + $blueprint->reset(); + + return response(''); + } } diff --git a/tests/Fields/BlueprintRepositoryTest.php b/tests/Fields/BlueprintRepositoryTest.php index c2e747e658..beb3be3eb8 100644 --- a/tests/Fields/BlueprintRepositoryTest.php +++ b/tests/Fields/BlueprintRepositoryTest.php @@ -356,6 +356,19 @@ public function it_gets_blueprints_in_a_custom_namespace_with_overrides() $this->assertEquals(['First Blueprint', 'Overridden Second Blueprint'], $blueprints->map->title()->values()->all()); } + #[Test] + public function it_resets_a_namespaced_blueprint() + { + File::shouldReceive('exists')->with('/path/to/resources/blueprints/vendor/foo/test.yaml')->andReturnTrue(); + File::shouldReceive('get')->with('/path/to/resources/blueprints/vendor/foo/test.yaml')->once()->andReturn('title: Overwritten Test Blueprint'); + File::shouldReceive('delete')->once(); + + $this->repo->addNamespace('foo', 'foo'); + $blueprint = $this->repo->find('foo::test'); + + $this->repo->reset($blueprint); + } + #[Test] public function it_sets_the_namespace_when_passed_when_making() {