diff --git a/resources/js/components/navigation/View.vue b/resources/js/components/navigation/View.vue index c741272f5b..4681af6597 100644 --- a/resources/js/components/navigation/View.vue +++ b/resources/js/components/navigation/View.vue @@ -421,6 +421,10 @@ export default { }, treeSaved(response) { + if (! response.data.saved) { + return this.$toast.error(`Couldn't save tree`) + } + this.replaceGeneratedIds(response.data.generatedIds); this.changed = false; diff --git a/resources/js/components/structures/PageTree.vue b/resources/js/components/structures/PageTree.vue index 2170ee128d..a9c572dacc 100644 --- a/resources/js/components/structures/PageTree.vue +++ b/resources/js/components/structures/PageTree.vue @@ -207,6 +207,10 @@ export default { }; return this.$axios.patch(this.submitUrl, payload).then(response => { + if (! response.data.saved) { + return this.$toast.error(`Couldn't save tree`) + } + this.$emit('saved', response); this.$toast.success(__('Saved')); this.initialPages = this.pages; diff --git a/src/Events/CollectionTreeSaving.php b/src/Events/CollectionTreeSaving.php new file mode 100644 index 0000000000..02537e2f81 --- /dev/null +++ b/src/Events/CollectionTreeSaving.php @@ -0,0 +1,23 @@ +tree = $tree; + } + + /** + * Dispatch the event with the given arguments, and halt on first non-null listener response. + * + * @return mixed + */ + public static function dispatch() + { + return event(new static(...func_get_args()), [], true); + } +} diff --git a/src/Events/NavTreeSaving.php b/src/Events/NavTreeSaving.php new file mode 100644 index 0000000000..8b1f05b7cb --- /dev/null +++ b/src/Events/NavTreeSaving.php @@ -0,0 +1,23 @@ +tree = $tree; + } + + /** + * Dispatch the event with the given arguments, and halt on first non-null listener response. + * + * @return mixed + */ + public static function dispatch() + { + return event(new static(...func_get_args()), [], true); + } +} diff --git a/src/Http/Controllers/CP/Collections/CollectionTreeController.php b/src/Http/Controllers/CP/Collections/CollectionTreeController.php index 5c848b4e19..fb8acc556c 100644 --- a/src/Http/Controllers/CP/Collections/CollectionTreeController.php +++ b/src/Http/Controllers/CP/Collections/CollectionTreeController.php @@ -46,7 +46,9 @@ public function update(Request $request, $collection) // if somehow the root would end up having child pages, which isn't allowed. $contents = $structure->validateTree($contents, $request->site); - $tree->tree($contents)->save(); + return [ + 'saved' => $tree->tree($contents)->save(), + ]; } private function toTree($items) diff --git a/src/Http/Controllers/CP/Navigation/NavigationTreeController.php b/src/Http/Controllers/CP/Navigation/NavigationTreeController.php index 7cecc54201..67c9a2f477 100644 --- a/src/Http/Controllers/CP/Navigation/NavigationTreeController.php +++ b/src/Http/Controllers/CP/Navigation/NavigationTreeController.php @@ -51,10 +51,11 @@ public function update(Request $request, $nav) $tree = $this->reorderTree($request->pages); - $nav->in($request->site)->tree($tree)->save(); + $saved = $nav->in($request->site)->tree($tree)->save(); return [ 'generatedIds' => $this->generatedIds, + 'saved' => $saved, ]; } diff --git a/src/Structures/CollectionTree.php b/src/Structures/CollectionTree.php index 3af66f3865..6213dd3c5b 100644 --- a/src/Structures/CollectionTree.php +++ b/src/Structures/CollectionTree.php @@ -7,6 +7,7 @@ use Statamic\Contracts\Structures\CollectionTreeRepository; use Statamic\Events\CollectionTreeDeleted; use Statamic\Events\CollectionTreeSaved; +use Statamic\Events\CollectionTreeSaving; use Statamic\Facades\Blink; use Statamic\Facades\Collection; use Statamic\Facades\Site; @@ -45,6 +46,11 @@ protected function dispatchSavedEvent() CollectionTreeSaved::dispatch($this); } + protected function dispatchSavingEvent() + { + return CollectionTreeSaving::dispatch($this); + } + protected function dispatchDeletedEvent() { CollectionTreeDeleted::dispatch($this); diff --git a/src/Structures/NavTree.php b/src/Structures/NavTree.php index 3dd7caba93..0faa887696 100644 --- a/src/Structures/NavTree.php +++ b/src/Structures/NavTree.php @@ -7,6 +7,7 @@ use Statamic\Contracts\Structures\NavTreeRepository; use Statamic\Events\NavTreeDeleted; use Statamic\Events\NavTreeSaved; +use Statamic\Events\NavTreeSaving; use Statamic\Facades\Blink; use Statamic\Facades\Nav; use Statamic\Facades\Site; @@ -43,6 +44,11 @@ protected function dispatchSavedEvent() NavTreeSaved::dispatch($this); } + protected function dispatchSavingEvent() + { + return NavTreeSaving::dispatch($this); + } + protected function dispatchDeletedEvent() { NavTreeDeleted::dispatch($this); diff --git a/src/Structures/Tree.php b/src/Structures/Tree.php index af7214fd90..1caff9d5d4 100644 --- a/src/Structures/Tree.php +++ b/src/Structures/Tree.php @@ -156,6 +156,10 @@ public function findByEntry($id) public function save() { + if ($this->dispatchSavingEvent() === false) { + return false; + } + $this->cachedFlattenedPages = null; Blink::forget('collection-structure-flattened-pages-collection*'); @@ -166,6 +170,8 @@ public function save() $this->dispatchSavedEvent(); $this->syncOriginal(); + + return true; } public function delete() @@ -186,6 +192,11 @@ protected function dispatchSavedEvent() // } + protected function dispatchSavingEvent() + { + // + } + protected function dispatchDeletedEvent() { // diff --git a/tests/Data/Structures/CollectionTreeTest.php b/tests/Data/Structures/CollectionTreeTest.php index d591862e99..e5c848e0a1 100644 --- a/tests/Data/Structures/CollectionTreeTest.php +++ b/tests/Data/Structures/CollectionTreeTest.php @@ -2,7 +2,9 @@ namespace Tests\Data\Structures; +use Illuminate\Support\Facades\Event; use PHPUnit\Framework\Attributes\Test; +use Statamic\Events\CollectionTreeSaving; use Statamic\Facades\Blink; use Statamic\Facades\Collection; use Statamic\Structures\CollectionTree; @@ -16,16 +18,6 @@ class CollectionTreeTest extends TestCase use PreventSavingStacheItemsToDisk; use UnlinksPaths; - private $directory; - - public function setUp(): void - { - parent::setUp(); - - $stache = $this->app->make('stache'); - $stache->store('collection-trees')->directory($this->directory = '/path/to/structures/collections'); - } - #[Test] public function it_can_get_and_set_the_handle() { @@ -61,7 +53,7 @@ public function it_gets_the_path() $collection = Collection::make('pages')->structureContents(['root' => true]); Collection::shouldReceive('findByHandle')->with('pages')->andReturn($collection); $tree = $collection->structure()->makeTree('en'); - $this->assertEquals('/path/to/structures/collections/pages.yaml', $tree->path()); + $this->assertEquals($this->fakeStacheDirectory.'/content/structures/collections/pages.yaml', $tree->path()); } #[Test] @@ -75,7 +67,7 @@ public function it_gets_the_path_when_using_multisite() $collection = Collection::make('pages')->structureContents(['root' => true]); Collection::shouldReceive('findByHandle')->with('pages')->andReturn($collection); $tree = $collection->structure()->makeTree('en'); - $this->assertEquals('/path/to/structures/collections/en/pages.yaml', $tree->path()); + $this->assertEquals($this->fakeStacheDirectory.'/content/structures/collections/en/pages.yaml', $tree->path()); } #[Test] @@ -118,4 +110,36 @@ public function it_does_a_diff() $this->assertEquals(['1.1', '2.2', '2.3'], $diff->moved()); $this->assertEquals(['1.1'], $diff->ancestryChanged()); } + + #[Test] + public function it_fires_a_saving_event() + { + Event::fake(); + + $collection = Collection::make('test')->structureContents(['root' => true]); + Collection::shouldReceive('findByHandle')->with('test')->andReturn($collection); + + $tree = $collection->structure()->makeTree('en'); + $tree->save(); + + Event::assertDispatched(CollectionTreeSaving::class); + + $this->assertFileExists($tree->path()); + } + + #[Test] + public function returning_false_in_collection_tree_saving_stops_saving() + { + Event::listen(CollectionTreeSaving::class, function (CollectionTreeSaving $event) { + return false; + }); + + $collection = Collection::make('test')->structureContents(['root' => true]); + Collection::shouldReceive('findByHandle')->with('test')->andReturn($collection); + + $tree = $collection->structure()->makeTree('en'); + $tree->save(); + + $this->assertFileDoesNotExist($tree->path()); + } } diff --git a/tests/Data/Structures/NavTreeTest.php b/tests/Data/Structures/NavTreeTest.php index 88d9e93972..7122e3ab50 100644 --- a/tests/Data/Structures/NavTreeTest.php +++ b/tests/Data/Structures/NavTreeTest.php @@ -3,7 +3,9 @@ namespace Tests\Data\Structures; use Facades\Statamic\Structures\BranchIds; +use Illuminate\Support\Facades\Event; use PHPUnit\Framework\Attributes\Test; +use Statamic\Events\NavTreeSaving; use Statamic\Facades\Blink; use Statamic\Facades\File; use Statamic\Facades\Nav; @@ -113,4 +115,28 @@ public function it_doesnt_save_tree_when_ensuring_ids_if_nothing_changed() $this->assertEquals($existingTree, $tree->tree()); $this->assertEquals($existingFileContents, File::get($tree->path())); } + + #[Test] + public function it_fires_a_saving_event() + { + Event::fake(); + + $nav = tap(Nav::make('links'))->save(); + tap($nav->makeTree('en', [['id' => 'the-id', 'title' => 'Branch']]))->save(); + + Event::assertDispatched(NavTreeSaving::class); + } + + #[Test] + public function returning_false_in_nav_tree_saving_stops_saving() + { + Event::listen(NavTreeSaving::class, function (NavTreeSaving $event) { + return false; + }); + + $nav = tap(Nav::make('links'))->save(); + $tree = tap($nav->makeTree('en', [['id' => 'the-id', 'title' => 'Branch']]))->save(); + + $this->assertFileDoesNotExist($tree->path()); + } }