diff --git a/doc/01-installation.md b/doc/01-installation.md
index 382c5204..9c55afb5 100644
--- a/doc/01-installation.md
+++ b/doc/01-installation.md
@@ -111,3 +111,14 @@ framework:
```
All commands from the plugin implement the `WishlistSyncCommandInterface` interface, so there is no need for other configuration.
+
+## Removing anonymous wishlists after expiration period
+
+You can remove anonymous wishlists that have not been updated for a specified period of time. To do so, you need to add `bitbag:remove-anonymous-wishlists` Symfony console command to your cron jobs.
+
+You can specify the expiration period in your parameters file to override the default value of 30 days:
+
+```yaml
+parameters:
+ bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period: 30 days # Remove all anonymous wishlists that were updated more than 30 days ago.
+```
\ No newline at end of file
diff --git a/src/Console/RemoveAnonymousWishlistsCommand.php b/src/Console/RemoveAnonymousWishlistsCommand.php
new file mode 100644
index 00000000..571ea9df
--- /dev/null
+++ b/src/Console/RemoveAnonymousWishlistsCommand.php
@@ -0,0 +1,56 @@
+setDescription('Removes anonymous wishlists that have been idle for a period set in `bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period` configuration key.')
+ ->setDescription('Removes anonymous wishlists that have been idle for a period set in `bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period` configuration key.')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output)
+ {
+ /** @var string $expirationTime */
+ $expirationTime = $this->getContainer()->getParameter('bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period');
+
+ if (empty($expirationTime)) {
+ $output->writeln('`bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period` configuration key is not set, so no wishlists will be removed.');
+
+ return 0;
+ }
+
+ $output->writeln(sprintf(
+ 'Command will remove anonymous wishlists that have been idle for %s.',
+ (string) $expirationTime,
+ ));
+
+ /** @var AnonymousWishlistsRemoverInterface $anonymousWishlistsRemover */
+ $anonymousWishlistsRemover = $this->getContainer()->get('bitbag_sylius_wishlist_plugin.services.anonymous_wishlists_remover');
+ $anonymousWishlistsRemover->remove();
+
+ return 0;
+ }
+}
diff --git a/src/DependencyInjection/BitBagSyliusWishlistExtension.php b/src/DependencyInjection/BitBagSyliusWishlistExtension.php
index 338b482a..4db58e1f 100644
--- a/src/DependencyInjection/BitBagSyliusWishlistExtension.php
+++ b/src/DependencyInjection/BitBagSyliusWishlistExtension.php
@@ -29,6 +29,7 @@ public function load(array $config, ContainerBuilder $container): void
$this->registerResources('bitbag_sylius_wishlist_plugin', 'doctrine/orm', $config['resources'], $container);
$loader->load('services.yml');
$container->setParameter('bitbag_sylius_wishlist_plugin.parameters.wishlist_cookie_token', $config['wishlist_cookie_token']);
+ $container->setParameter('bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period', $config['anonymous_wishlist_expiration_period']);
$container->setParameter('bitbag_sylius_wishlist_plugin.parameters.allowed_mime_types', $config['allowed_mime_types']);
}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
index af32f9cc..b2a8e5ce 100644
--- a/src/DependencyInjection/Configuration.php
+++ b/src/DependencyInjection/Configuration.php
@@ -45,6 +45,19 @@ public function getConfigTreeBuilder(): TreeBuilder
})
->end()
->end()
+ ->scalarNode('anonymous_wishlist_expiration_period')
+ ->defaultValue('30 days')
+ ->cannotBeEmpty()
+ ->validate()
+ ->always(function ($value) {
+ if (!is_string($value)) {
+ throw new InvalidConfigurationException('anonymous_wishlist_expiration_period must be string');
+ }
+
+ return $value;
+ })
+ ->end()
+ ->end()
->arrayNode('allowed_mime_types')
->defaultValue([
'text/csv',
diff --git a/src/Remover/AnonymousWishlistsRemover.php b/src/Remover/AnonymousWishlistsRemover.php
new file mode 100644
index 00000000..248e4b21
--- /dev/null
+++ b/src/Remover/AnonymousWishlistsRemover.php
@@ -0,0 +1,32 @@
+wishlistRepository->deleteAllAnonymousUntil(
+ new \DateTime('-' . $this->expirationPeriod),
+ );
+ }
+}
diff --git a/src/Remover/AnonymousWishlistsRemoverInterface.php b/src/Remover/AnonymousWishlistsRemoverInterface.php
new file mode 100644
index 00000000..9d609013
--- /dev/null
+++ b/src/Remover/AnonymousWishlistsRemoverInterface.php
@@ -0,0 +1,17 @@
+getOneOrNullResult()
;
}
+
+ public function deleteAllAnonymousUntil(\DateTime $until): int
+ {
+ return $this->createQueryBuilder('o')
+ ->delete(WishlistInterface::class, 'o')
+ ->where('o.shopUser IS NULL')
+ ->andWhere('o.updatedAt < :until')
+ ->setParameter('until', $until)
+ ->getQuery()
+ ->execute()
+ ;
+ }
}
diff --git a/src/Repository/WishlistRepositoryInterface.php b/src/Repository/WishlistRepositoryInterface.php
index b7f685f2..6f72e664 100644
--- a/src/Repository/WishlistRepositoryInterface.php
+++ b/src/Repository/WishlistRepositoryInterface.php
@@ -40,4 +40,9 @@ public function findAllByAnonymousAndChannel(?string $token, ChannelInterface $c
public function findOneByTokenAndName(string $token, string $name): ?WishlistInterface;
public function findOneByShopUserAndName(ShopUserInterface $shopUser, string $name): ?WishlistInterface;
+
+ /**
+ * @return int Number of deleted wishlists.
+ */
+ public function deleteAllAnonymousUntil(\DateTime $until): int;
}
diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml
index 350e05c5..bdcad2ae 100644
--- a/src/Resources/config/services.yml
+++ b/src/Resources/config/services.yml
@@ -2,6 +2,11 @@ imports:
- { resource: "services/**/*.yml" }
services:
+ bitbag_sylius_wishlist_plugin.console.remove_anonymous_wishlists_command:
+ class: BitBag\SyliusWishlistPlugin\Console\RemoveAnonymousWishlistsCommand
+ tags:
+ - { name: console.command }
+
bitbag_sylius_wishlist_plugin.controller.action.base_wishlist_products_action:
abstract: true
class: BitBag\SyliusWishlistPlugin\Controller\Action\BaseWishlistProductsAction
@@ -348,6 +353,14 @@ services:
- '@request_stack'
- "@translator"
+ bitbag_sylius_wishlist_plugin.services.anonymous_wishlists_remover:
+ public: true
+ class: BitBag\SyliusWishlistPlugin\Remover\AnonymousWishlistsRemover
+ arguments:
+ - "@bitbag_sylius_wishlist_plugin.repository.wishlist"
+ - "@bitbag_sylius_wishlist_plugin.manager.wishlist"
+ - "%bitbag_sylius_wishlist_plugin.parameters.anonymous_wishlist_expiration_period%"
+
bitbag_sylius_wishlist_plugin.checker.wishlist_name_checker:
class: BitBag\SyliusWishlistPlugin\Checker\WishlistNameChecker
diff --git a/tests/Integration/DataFixtures/ORM/test_it_delete_all_anonymous_until_wishlists.yaml b/tests/Integration/DataFixtures/ORM/test_it_delete_all_anonymous_until_wishlists.yaml
new file mode 100644
index 00000000..6937b1db
--- /dev/null
+++ b/tests/Integration/DataFixtures/ORM/test_it_delete_all_anonymous_until_wishlists.yaml
@@ -0,0 +1,46 @@
+Sylius\Component\Locale\Model\Locale:
+ locale:
+ createdAt: ''
+ code: 'en_US'
+Sylius\Component\Currency\Model\Currency:
+ dollar:
+ code: 'USD'
+Sylius\Component\Core\Model\Channel:
+ channel_us:
+ code: 'US'
+ name: 'name'
+ defaultLocale: '@locale'
+ locales: [ '@locale' ]
+ taxCalculationStrategy: 'order_items_based'
+ baseCurrency: '@dollar'
+ enabled: true
+Sylius\Component\Core\Model\Customer:
+ customer_oliver:
+ firstName: 'John'
+ lastName: 'Nowak'
+ email: 'oliver@queen.com'
+ emailCanonical: 'test2@example.com'
+Sylius\Component\Core\Model\ShopUser:
+ user_oliver:
+ plainPassword: '123password'
+ roles: [ 'ROLE_USER' ]
+ enabled: 'true'
+ customer: '@customer_oliver'
+ username: 'oliver@queen.com'
+ usernameCanonical: 'oliver@queen.com'
+BitBag\SyliusWishlistPlugin\Entity\Wishlist:
+ wishlist_one:
+ name: 'Wishlist One'
+ channel: '@channel_us'
+ token: 'token'
+ updatedAt: ''
+ wishlist_two:
+ name: 'Wishlist Two'
+ channel: '@channel_us'
+ token: 'token'
+ updatedAt: ''
+ olivier_wishlist:
+ name: 'Olivier Wishlist'
+ shopUser: '@user_oliver'
+ channel: '@channel_us'
+ updatedAt: ''
\ No newline at end of file
diff --git a/tests/Integration/Repository/WishlistRepositoryTest.php b/tests/Integration/Repository/WishlistRepositoryTest.php
index 1bf04da7..8d8e6d86 100644
--- a/tests/Integration/Repository/WishlistRepositoryTest.php
+++ b/tests/Integration/Repository/WishlistRepositoryTest.php
@@ -182,4 +182,20 @@ public function testItFindsOneWishlistByShopUserAndName(): void
$missingResult = $this->repository->findOneByShopUserAndName($shopUser, 'Bruce Wishlist');
$this->assertNull($missingResult);
}
+
+ public function testItDeleteAllAnonymousUntilWishlists(): void
+ {
+ $this->loadFixturesFromFile('test_it_delete_all_anonymous_until_wishlists.yaml');
+
+ /** @var int $result */
+ $result = $this->repository->deleteAllAnonymousUntil(new \DateTime('2024-01-01 00:00:00'));
+
+ $this->assertIsInt($result);
+ $this->assertSame(1, $result);
+
+ $wishlists = $this->repository->findAll();
+ $this->assertCount(2, $wishlists);
+ $this->assertSame('Wishlist Two', $wishlists[0]->getName());
+ $this->assertSame('Olivier Wishlist', $wishlists[1]->getName());
+ }
}