diff --git a/src/Entries/Entry.php b/src/Entries/Entry.php index 8e95457db19..685a84d2411 100644 --- a/src/Entries/Entry.php +++ b/src/Entries/Entry.php @@ -3,6 +3,7 @@ namespace Statamic\Entries; use ArrayAccess; +use Facades\Statamic\Entries\InitiatorStack; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Contracts\Support\Responsable; use Illuminate\Support\Carbon; @@ -336,6 +337,8 @@ public function save() $this->ancestors()->each(fn ($entry) => Blink::forget('entry-descendants-'.$entry->id())); + $stack = InitiatorStack::entry($this)->push(); + $this->directDescendants()->each->save(); $this->taxonomize(); @@ -362,6 +365,8 @@ public function save() }); } + $stack->pop(); + return true; } diff --git a/src/Entries/InitiatorStack.php b/src/Entries/InitiatorStack.php new file mode 100644 index 00000000000..09b08e8c227 --- /dev/null +++ b/src/Entries/InitiatorStack.php @@ -0,0 +1,48 @@ +entry = $entry; + $this->key = 'entry-event-initiator-'.$entry->root()->id(); + $this->stack = Blink::get($this->key) ?? collect(); + + return $this; + } + + public function push() + { + $initiator = $this->stack->first() ?? $this->entry; + + $initiatorIsAncestor = $this->entry + ->ancestors() + ->contains(fn ($entry) => $entry->id() === $initiator->id()); + + if ($this->stack->isEmpty() || $initiatorIsAncestor) { + $this->stack->push($this->entry); + Blink::put($this->key, $this->stack); + } + + return $this; + } + + public function initiator(): ?Entry + { + return $this->stack->first(); + } + + public function pop() + { + $this->stack->pop(); + } +} diff --git a/src/Events/EntrySaved.php b/src/Events/EntrySaved.php index 1b3ecb19266..29513b6d8fb 100644 --- a/src/Events/EntrySaved.php +++ b/src/Events/EntrySaved.php @@ -2,19 +2,27 @@ namespace Statamic\Events; +use Facades\Statamic\Entries\InitiatorStack; use Statamic\Contracts\Git\ProvidesCommitMessage; class EntrySaved extends Event implements ProvidesCommitMessage { public $entry; + public $initiator; public function __construct($entry) { $this->entry = $entry; + $this->initiator = InitiatorStack::entry($entry)->initiator(); } public function commitMessage() { return __('Entry saved', [], config('statamic.git.locale')); } + + public function isInitiator() + { + return $this->entry->id() === $this->initiator->id(); + } } diff --git a/tests/Data/Entries/EntryTest.php b/tests/Data/Entries/EntryTest.php index a1c92be9da8..4860561ab82 100644 --- a/tests/Data/Entries/EntryTest.php +++ b/tests/Data/Entries/EntryTest.php @@ -2237,4 +2237,54 @@ public function __call($method, $args) $this->assertCount(2, $fakeBlink->calls['origin-Entry-2']); $this->assertCount(2, $fakeBlink->calls['origin-Entry-3']); } + + /** @test */ + public function initially_saved_entry_gets_put_into_events() + { + Facades\Site::setConfig([ + 'default' => 'en', + 'sites' => [ + 'en' => ['name' => 'English', 'locale' => 'en_US', 'url' => '/'], + 'fr' => ['name' => 'French', 'locale' => 'fr_FR', 'url' => '/fr/'], + 'de' => ['name' => 'German', 'locale' => 'de_DE', 'url' => '/de/'], + 'es' => ['name' => 'Spanish', 'locale' => 'es_ES', 'url' => '/es/'], + ], + ]); + + // Bunch of localizations of the same entry. + $one = EntryFactory::collection('test')->id('1')->locale('en')->data(['foo' => 'root'])->create(); + $two = EntryFactory::collection('test')->id('2')->origin('1')->locale('fr')->create(); + $three = EntryFactory::collection('test')->id('3')->origin('2')->locale('de')->create(); + $four = EntryFactory::collection('test')->id('4')->origin('3')->locale('es')->create(); + + // Separate entry with localization. + $five = EntryFactory::collection('test')->id('5')->locale('en')->create(); + $six = EntryFactory::collection('test')->id('6')->origin('5')->locale('fr')->create(); + + // Yet another separate entry. + $seven = EntryFactory::collection('test')->id('7')->create(); + + // Avoid using a fake so we can use a real listener. + $events = collect(); + Event::listen(function (EntrySaved $event) use ($five, &$events) { + $events[] = $event; + + // Save unrelated entry during the localization recursion. + if ($event->entry->id() === '3') { + $five->save(); + } + }); + + $two->save(); + $seven->save(); + + $this->assertEquals([ + ['4', '2'], + ['3', '2'], + ['6', '5'], + ['5', '5'], + ['2', '2'], + ['7', '7'], + ], $events->map(fn ($event) => [$event->entry->id(), $event->initiator->id()])->all()); + } } diff --git a/tests/Search/UpdateItemIndexesTest.php b/tests/Search/UpdateItemIndexesTest.php index eaade70cf0b..7089708c238 100644 --- a/tests/Search/UpdateItemIndexesTest.php +++ b/tests/Search/UpdateItemIndexesTest.php @@ -4,8 +4,8 @@ use Mockery; use Statamic\Contracts\Search\Searchable; -use Statamic\Events\EntryDeleted; -use Statamic\Events\EntrySaved; +use Statamic\Events\UserDeleted; +use Statamic\Events\UserSaved; use Statamic\Facades\Search; use Statamic\Search\UpdateItemIndexes; use Tests\TestCase; @@ -19,7 +19,7 @@ public function it_updates_indexes_on_save() Search::shouldReceive('updateWithinIndexes')->with($item)->once(); - $event = new EntrySaved($item); + $event = new UserSaved($item); $listener = new UpdateItemIndexes; @@ -33,7 +33,7 @@ public function it_updates_indexes_on_delete() Search::shouldReceive('deleteFromIndexes')->with($item)->once(); - $event = new EntryDeleted($item); + $event = new UserDeleted($item); $listener = new UpdateItemIndexes;