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()
{
|