Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Add ability to reset namespaced blueprints #9327

Merged
merged 11 commits into from
Aug 19, 2024
3 changes: 3 additions & 0 deletions resources/js/bootstrap/components.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -142,3 +143,5 @@ Vue.component('resource-deleter', ResourceDeleter);

Vue.component('stack', Stack);
Vue.component('stack-test', StackTest);

Vue.component('blueprint-resetter', BlueprintResetter);
102 changes: 102 additions & 0 deletions resources/js/components/blueprints/BlueprintResetter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<confirmation-modal
v-if="resetting"
:title="modalTitle"
:bodyText="modalBody"
:buttonText="__('Reset')"
:danger="true"
@confirm="confirmed"
@cancel="cancel"
>
</confirmation-modal>
</template>

<script>
export default {

props: {
resource: {
type: Object
},
resourceTitle: {
type: String
},
route: {
type: String,
},
redirect: {
type: String
},
reload: {
type: Boolean
}
},

data() {
return {
resetting: false,
redirectFromServer: null,
}
},

computed: {
title() {
return data_get(this.resource, 'title', this.resourceTitle);
},

modalTitle() {
return __('Reset :resource', {resource: this.title});
},

modalBody() {
return __('Are you sure you want to reset this item?');
},

resetUrl() {
let url = data_get(this.resource, 'reset_url', this.route);
if (! url) console.error('BlueprintResetter cannot find reset url');
return url;
},

redirectUrl() {
return this.redirect || this.redirectFromServer;
},
},

methods: {
confirm() {
this.resetting = true;
},

confirmed() {
this.$axios.delete(this.resetUrl)
.then(response => {
this.redirectFromServer = data_get(response, 'data.redirect');
this.success();
})
.catch(() => {
this.$toast.error(__('Something went wrong'));
});
},

success() {
if (this.redirectUrl) {
location.href = this.redirectUrl;
return;
}

if (this.reload) {
location.reload();
return;
}

this.$toast.success(__('Reset'));
this.$emit('reset');
},

cancel() {
this.resetting = false;
}
}
}
</script>
14 changes: 14 additions & 0 deletions resources/views/blueprints/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@
<a href="{{ cp_route('blueprints.edit', [$blueprint['namespace'], $blueprint['handle']]) }}">{{ $blueprint['title'] }}</a>
</div>
</td>
<th class="actions-column">
@if ($blueprint['is_resettable'])
<dropdown-list class="dropdown-list">
<dropdown-item :text="__('Reset')" class="warning" @click="$refs[`resetter_{{ $blueprint['namespace'] }}_{{ $blueprint['handle'] }}`].confirm()">
<blueprint-resetter
ref="resetter_{{ $blueprint['namespace'] }}_{{ $blueprint['handle'] }}"
:resource='@json($blueprint)'
reload
>
</blueprint-resetter>
</dropdown-item>
</dropdown-list>
@endif
</td>
</tr>
@endforeach
</table>
Expand Down
1 change: 1 addition & 0 deletions routes/cp.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
});

Expand Down
20 changes: 20 additions & 0 deletions src/Events/BlueprintReset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Statamic\Events;

use Statamic\Contracts\Git\ProvidesCommitMessage;

class BlueprintReset extends Event implements ProvidesCommitMessage
{
public $blueprint;

public function __construct($blueprint)
{
$this->blueprint = $blueprint;
}

public function commitMessage()
{
return __('Blueprint reset', [], config('statamic.git.locale'));
}
}
1 change: 1 addition & 0 deletions src/Events/Concerns/ListensForContentEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions src/Fields/Blueprint.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -414,6 +415,12 @@ public function isDeletable()
return ! $this->isNamespaced();
}

public function isResettable()
{
return $this->isNamespaced()
&& File::exists($this->path());
}

public function toPublishArray()
{
return [
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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());
Expand Down
9 changes: 9 additions & 0 deletions src/Fields/BlueprintRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
15 changes: 15 additions & 0 deletions src/Http/Controllers/CP/Fields/BlueprintController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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('');
}
}
13 changes: 13 additions & 0 deletions tests/Fields/BlueprintRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
Loading