diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
new file mode 100644
index 0000000..e5b707b
--- /dev/null
+++ b/.github/workflows/ci.yaml
@@ -0,0 +1,150 @@
+name: CI
+on:
+ - pull_request
+jobs:
+ check-composer:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install Composer
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.1
+ coverage: none
+ tools: composer:v2
+ env:
+ COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Validate composer.json
+ run: composer validate
+
+ php-linting:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ php-version:
+ - 7.4
+ - 8.0
+ - 8.1
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+
+ - name: PHP lint
+ run: "find *.php Classes Configuration Tests -name '*.php' -print0 | xargs -0 -n 1 -P 4 php -l"
+
+ xml-linting:
+ runs-on: ubuntu-latest
+ needs: [check-composer]
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.1"
+ coverage: none
+ tools: composer:v2
+
+ - name: Install xmllint
+ run: sudo apt-get install libxml2-utils
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress --no-suggest
+
+ - name: PHPUnit configuration file
+ run: xmllint --schema vendor/phpunit/phpunit/phpunit.xsd --noout phpunit.xml.dist
+
+ - name: Fetch schema for xliff
+ run: wget https://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd --output-document=xliff-core-1.2-strict.xsd
+
+ - name: TYPO3 language files
+ run: xmllint --schema xliff-core-1.2-strict.xsd --noout $(find Resources -name '*.xlf')
+
+ coding-guideline:
+ runs-on: ubuntu-latest
+ needs:
+ - php-linting
+ - xml-linting
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "8.1"
+ coverage: none
+ tools: composer:v2
+
+ - name: Install dependencies
+ run: composer install --prefer-dist --no-progress --no-suggest
+
+ - name: Coding Guideline
+ run: ./vendor/bin/php-cs-fixer fix --dry-run --diff
+
+ code-quality:
+ runs-on: ubuntu-latest
+ needs:
+ - php-linting
+ strategy:
+ matrix:
+ include:
+ - php-version: '7.4'
+ - php-version: '8.0'
+ - php-version: '8.1'
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+ tools: composer:v2
+
+ - name: Install dependencies with expected TYPO3 version
+ run: composer require --no-interaction --prefer-dist --no-progress
+
+ - name: Code Quality (by PHPStan)
+ run: ./vendor/bin/phpstan analyse
+
+ tests-mysql:
+ runs-on: ubuntu-latest
+ needs:
+ - php-linting
+ - xml-linting
+ strategy:
+ matrix:
+ include:
+ - php-version: '7.4'
+ - php-version: '8.0'
+ - php-version: '8.1'
+ steps:
+ - uses: actions/checkout@v2
+
+ - name: Install PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: "${{ matrix.php-version }}"
+ coverage: none
+ tools: composer:v2
+
+ - name: Setup MySQL
+ uses: mirromutth/mysql-action@v1.1
+ with:
+ mysql version: '8'
+ mysql database: 'typo3'
+ mysql root password: 'root'
+
+ - name: Install dependencies
+ run: composer require --no-interaction --prefer-dist --no-progress
+
+ - name: PHPUnit Tests
+ run: |-
+ export typo3DatabaseDriver="pdo_mysql"
+ export typo3DatabaseName="typo3"
+ export typo3DatabaseHost="127.0.0.1"
+ export typo3DatabaseUsername="root"
+ export typo3DatabasePassword="root"
+ ./vendor/bin/phpunit --testdox
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..eb00166
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+/composer.lock
+/.php-cs-fixer.cache
+/vendor/
+/public/
+/var/
diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php
new file mode 100644
index 0000000..80e0228
--- /dev/null
+++ b/.php-cs-fixer.dist.php
@@ -0,0 +1,62 @@
+ignoreVCSIgnored(true)
+ ->in(realpath(__DIR__));
+
+return (new \PhpCsFixer\Config())
+ ->setRiskyAllowed(true)
+ ->setRules([
+ '@DoctrineAnnotation' => true,
+ '@PSR2' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'blank_line_after_opening_tag' => true,
+ 'braces' => ['allow_single_line_closure' => true],
+ 'cast_spaces' => ['space' => 'none'],
+ 'compact_nullable_typehint' => true,
+ 'concat_space' => ['spacing' => 'one'],
+ 'declare_equal_normalize' => ['space' => 'none'],
+ 'dir_constant' => true,
+ 'function_to_constant' => ['functions' => ['get_called_class', 'get_class', 'get_class_this', 'php_sapi_name', 'phpversion', 'pi']],
+ 'function_typehint_space' => true,
+ 'lowercase_cast' => true,
+ 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'],
+ 'modernize_strpos' => true,
+ 'modernize_types_casting' => true,
+ 'native_function_casing' => true,
+ 'new_with_braces' => true,
+ 'no_alias_functions' => true,
+ 'no_blank_lines_after_phpdoc' => true,
+ 'no_empty_phpdoc' => true,
+ 'no_empty_statement' => true,
+ 'no_extra_blank_lines' => true,
+ 'no_leading_import_slash' => true,
+ 'no_leading_namespace_whitespace' => true,
+ 'no_null_property_initialization' => true,
+ 'no_short_bool_cast' => true,
+ 'no_singleline_whitespace_before_semicolons' => true,
+ 'no_superfluous_elseif' => true,
+ 'no_trailing_comma_in_singleline_array' => true,
+ 'no_unneeded_control_parentheses' => true,
+ 'no_unused_imports' => true,
+ 'no_useless_else' => true,
+ 'no_whitespace_in_blank_line' => true,
+ 'ordered_imports' => true,
+ 'php_unit_construct' => ['assertions' => ['assertEquals', 'assertSame', 'assertNotEquals', 'assertNotSame']],
+ 'php_unit_mock_short_will_return' => true,
+ 'php_unit_test_case_static_method_calls' => ['call_type' => 'self'],
+ 'phpdoc_no_access' => true,
+ 'phpdoc_no_empty_return' => true,
+ 'phpdoc_no_package' => true,
+ 'phpdoc_scalar' => true,
+ 'phpdoc_trim' => true,
+ 'phpdoc_types' => true,
+ 'phpdoc_types_order' => ['null_adjustment' => 'always_last', 'sort_algorithm' => 'none'],
+ 'return_type_declaration' => ['space_before' => 'none'],
+ 'single_quote' => true,
+ 'single_line_comment_style' => ['comment_types' => ['hash']],
+ 'single_trait_insert_per_statement' => true,
+ 'trailing_comma_in_multiline' => ['elements' => ['arrays']],
+ 'whitespace_after_comma_in_array' => true,
+ 'yoda_style' => ['equal' => false, 'identical' => false, 'less_and_greater' => false],
+ ])
+ ->setFinder($finder);
diff --git a/Classes/Controller/WatchlistController.php b/Classes/Controller/WatchlistController.php
new file mode 100644
index 0000000..abdf508
--- /dev/null
+++ b/Classes/Controller/WatchlistController.php
@@ -0,0 +1,84 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Controller;
+
+use Psr\Http\Message\ResponseInterface;
+use TYPO3\CMS\Core\Http\PropagateResponseException;
+use TYPO3\CMS\Core\Http\RedirectResponse;
+use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
+use WerkraumMedia\Watchlist\Domain\Repository\WatchlistRepository;
+
+class WatchlistController extends ActionController
+{
+ private WatchlistRepository $repository;
+
+ public function __construct(
+ WatchlistRepository $repository
+ ) {
+ $this->repository = $repository;
+ }
+
+ protected function initializeAction(): void
+ {
+ if ($this->request->hasArgument('watchlist') === false) {
+ $this->request->setArgument('watchlist', 'default');
+ }
+ }
+
+ public function indexAction(
+ Watchlist $watchlist
+ ): ResponseInterface {
+ $this->view->assignMultiple([
+ 'watchlist' => $watchlist,
+ ]);
+
+ return $this->htmlResponse();
+ }
+
+ public function addAction(
+ Watchlist $watchlist,
+ Item $item,
+ string $redirectUri = ''
+ ): void {
+ $watchlist->addItem($item);
+
+ $this->repository->update($watchlist);
+
+ throw new PropagateResponseException(new RedirectResponse($redirectUri, 303), 1664189968);
+ }
+
+ public function removeAction(
+ Watchlist $watchlist,
+ Item $item,
+ string $redirectUri
+ ): void {
+ $watchlist->removeItem($item);
+
+ $this->repository->update($watchlist);
+
+ throw new PropagateResponseException(new RedirectResponse($redirectUri, 303), 1664189969);
+ }
+}
diff --git a/Classes/Domain/ItemHandlerInterface.php b/Classes/Domain/ItemHandlerInterface.php
new file mode 100644
index 0000000..db14b86
--- /dev/null
+++ b/Classes/Domain/ItemHandlerInterface.php
@@ -0,0 +1,45 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain;
+
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+
+/**
+ * Defines interface of concrete handler implementations per item type.
+ *
+ * Each item has a type, and each type has an handler.
+ * The handler needs to take care of creating the item instances.
+ */
+interface ItemHandlerInterface
+{
+ /**
+ * Returns an item instance based on its identifier.
+ */
+ public function return(string $identifier): ?Item;
+
+ /**
+ * Returns the type the handler can handle, e.g. "pages" or "tt_address".
+ */
+ public function handlesType(): string;
+}
diff --git a/Classes/Domain/ItemHandlerRegistry.php b/Classes/Domain/ItemHandlerRegistry.php
new file mode 100644
index 0000000..11c2d33
--- /dev/null
+++ b/Classes/Domain/ItemHandlerRegistry.php
@@ -0,0 +1,55 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain;
+
+class ItemHandlerRegistry
+{
+ /**
+ * @var ItemHandlerInterface[]
+ */
+ private array $itemHandler = [];
+
+ public function exists(string $type): bool
+ {
+ return isset($this->itemHandler[$type]);
+ }
+
+ public function get(string $type): ItemHandlerInterface
+ {
+ return $this->itemHandler[$type];
+ }
+
+ public function add(ItemHandlerInterface $itemHandler): void
+ {
+ $this->itemHandler[$itemHandler->handlesType()] = $itemHandler;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getSupportedTypes(): array
+ {
+ return array_keys($this->itemHandler);
+ }
+}
diff --git a/Classes/Domain/Items/Page/ItemHandler.php b/Classes/Domain/Items/Page/ItemHandler.php
new file mode 100644
index 0000000..83e2f68
--- /dev/null
+++ b/Classes/Domain/Items/Page/ItemHandler.php
@@ -0,0 +1,65 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Items\Page;
+
+use TYPO3\CMS\Core\Database\ConnectionPool;
+use WerkraumMedia\Watchlist\Domain\ItemHandlerInterface;
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+
+class ItemHandler implements ItemHandlerInterface
+{
+ private ConnectionPool $connectionPool;
+
+ public function __construct(
+ ConnectionPool $connectionPool
+ ) {
+ $this->connectionPool = $connectionPool;
+ }
+
+ public function return(string $identifier): ?Item
+ {
+ $pageUid = (int)$identifier;
+ $pageRecord = $this->getPageRecord($pageUid);
+
+ return new Page(
+ $pageUid,
+ (string)$pageRecord['title']
+ );
+ }
+
+ public function handlesType(): string
+ {
+ return 'page';
+ }
+
+ private function getPageRecord(int $uid): array
+ {
+ $qb = $this->connectionPool->getQueryBuilderForTable('pages');
+ $qb->select('title');
+ $qb->from('pages');
+ $qb->where($qb->expr()->eq('uid', $qb->createNamedParameter($uid)));
+ $qb->setMaxResults(1);
+ return $qb->execute()->fetchAssociative() ?: [];
+ }
+}
diff --git a/Classes/Domain/Items/Page/Page.php b/Classes/Domain/Items/Page/Page.php
new file mode 100644
index 0000000..b52b0a6
--- /dev/null
+++ b/Classes/Domain/Items/Page/Page.php
@@ -0,0 +1,51 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Items\Page;
+
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+
+class Page implements Item
+{
+ private int $pageUid;
+
+ private string $title;
+
+ public function __construct(
+ int $pageUid,
+ string $title
+ ) {
+ $this->pageUid = $pageUid;
+ $this->title = $title;
+ }
+
+ public function getUniqueIdentifier(): string
+ {
+ return 'page-' . $this->pageUid;
+ }
+
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+}
diff --git a/Classes/Domain/Model/Item.php b/Classes/Domain/Model/Item.php
new file mode 100644
index 0000000..adf0b0c
--- /dev/null
+++ b/Classes/Domain/Model/Item.php
@@ -0,0 +1,32 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Model;
+
+interface Item
+{
+ /**
+ * Needs to uniquely identify the thing throughout the system.
+ */
+ public function getUniqueIdentifier(): string;
+}
diff --git a/Classes/Domain/Model/Watchlist.php b/Classes/Domain/Model/Watchlist.php
new file mode 100644
index 0000000..93618d5
--- /dev/null
+++ b/Classes/Domain/Model/Watchlist.php
@@ -0,0 +1,60 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Model;
+
+class Watchlist
+{
+ /**
+ * @var Item[]
+ */
+ private array $items = [];
+
+ public function getIdentifier(): string
+ {
+ return 'default';
+ }
+
+ /**
+ * @return Item[]
+ */
+ public function getItems(): array
+ {
+ return array_values($this->items);
+ }
+
+ public function addItem(Item $item): void
+ {
+ $this->items[$item->getUniqueIdentifier()] = $item;
+ }
+
+ public function removeItem(Item $item): void
+ {
+ unset($this->items[$item->getUniqueIdentifier()]);
+ }
+
+ public function isEmpty(): bool
+ {
+ return $this->items === [];
+ }
+}
diff --git a/Classes/Domain/Repository/ItemRepository.php b/Classes/Domain/Repository/ItemRepository.php
new file mode 100644
index 0000000..f637476
--- /dev/null
+++ b/Classes/Domain/Repository/ItemRepository.php
@@ -0,0 +1,56 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Repository;
+
+use WerkraumMedia\Watchlist\Domain\ItemHandlerRegistry;
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+
+class ItemRepository
+{
+ private ItemHandlerRegistry $registry;
+
+ public function __construct(
+ ItemHandlerRegistry $registry
+ ) {
+ $this->registry = $registry;
+ }
+
+ public function getByUniqueIdentifier(string $uniqueIdentifier): ?Item
+ {
+ [$type, $identifier] = explode('-', $uniqueIdentifier, 2);
+
+ if ($this->registry->exists($type)) {
+ return $this->registry->get($type)->return($identifier);
+ }
+
+ throw new \Exception(
+ sprintf(
+ 'No item handler for type "%s", only supported: %s.',
+ $type,
+ implode(', ', $this->registry->getSupportedTypes()),
+ ),
+ 1664186139
+ );
+ }
+}
diff --git a/Classes/Domain/Repository/WatchlistRepository.php b/Classes/Domain/Repository/WatchlistRepository.php
new file mode 100644
index 0000000..828086b
--- /dev/null
+++ b/Classes/Domain/Repository/WatchlistRepository.php
@@ -0,0 +1,57 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Domain\Repository;
+
+use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
+use WerkraumMedia\Watchlist\Session\SessionServiceInterface;
+
+class WatchlistRepository
+{
+ private SessionServiceInterface $sessionService;
+
+ public function __construct(
+ SessionServiceInterface $sessionService
+ ) {
+ $this->sessionService = $sessionService;
+ }
+
+ public function getByIdentifier(string $identifier): ?Watchlist
+ {
+ $watchlist = $this->sessionService->getWatchlist($identifier);
+ if ($watchlist instanceof Watchlist) {
+ return $watchlist;
+ }
+
+ if ($identifier === 'default') {
+ return new Watchlist();
+ }
+
+ return null;
+ }
+
+ public function update(Watchlist $watchlist): void
+ {
+ $this->sessionService->update($watchlist);
+ }
+}
diff --git a/Classes/Extbase/TypeConverter/ItemTypeConverter.php b/Classes/Extbase/TypeConverter/ItemTypeConverter.php
new file mode 100644
index 0000000..4d2acdc
--- /dev/null
+++ b/Classes/Extbase/TypeConverter/ItemTypeConverter.php
@@ -0,0 +1,59 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Extbase\TypeConverter;
+
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
+use TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter;
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+use WerkraumMedia\Watchlist\Domain\Repository\ItemRepository;
+
+class ItemTypeConverter extends AbstractTypeConverter
+{
+ protected $sourceTypes = ['string'];
+
+ protected $targetType = Item::class;
+
+ protected $priority = 10;
+
+ private ItemRepository $itemRepository;
+
+ public function __construct(
+ ItemRepository $itemRepository
+ ) {
+ $this->itemRepository = $itemRepository;
+ }
+
+ public function convertFrom(
+ $source,
+ string $targetType,
+ array $convertedChildProperties = [],
+ PropertyMappingConfigurationInterface $configuration = null
+ ): ?Item {
+ if (is_string($source) === false) {
+ throw new \InvalidArgumentException('Source was not of expected type string.', 1664197305);
+ }
+
+ return $this->itemRepository->getByUniqueIdentifier($source);
+ }
+}
diff --git a/Classes/Extbase/TypeConverter/WatchlistTypeConverter.php b/Classes/Extbase/TypeConverter/WatchlistTypeConverter.php
new file mode 100644
index 0000000..2655648
--- /dev/null
+++ b/Classes/Extbase/TypeConverter/WatchlistTypeConverter.php
@@ -0,0 +1,58 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Extbase\TypeConverter;
+
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
+use TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter;
+use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
+use WerkraumMedia\Watchlist\Domain\Repository\WatchlistRepository;
+
+class WatchlistTypeConverter extends AbstractTypeConverter
+{
+ protected $sourceTypes = ['string'];
+
+ protected $targetType = Watchlist::class;
+
+ protected $priority = 10;
+
+ private WatchlistRepository $repository;
+
+ public function __construct(
+ WatchlistRepository $repository
+ ) {
+ $this->repository = $repository;
+ }
+
+ public function convertFrom(
+ $source,
+ string $targetType,
+ array $convertedChildProperties = [],
+ PropertyMappingConfigurationInterface $configuration = null
+ ): ?Watchlist {
+ if (is_string($source) === false) {
+ throw new \InvalidArgumentException('Source was not of expected type string.', 1664197358);
+ }
+ return $this->repository->getByIdentifier($source);
+ }
+}
diff --git a/Classes/Session/SessionServiceInterface.php b/Classes/Session/SessionServiceInterface.php
new file mode 100644
index 0000000..c8cd1e2
--- /dev/null
+++ b/Classes/Session/SessionServiceInterface.php
@@ -0,0 +1,33 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Session;
+
+use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
+
+interface SessionServiceInterface
+{
+ public function getWatchlist(string $identifier): ?Watchlist;
+
+ public function update(Watchlist $watchlist): void;
+}
diff --git a/Classes/Session/Typo3FrontendSessionService.php b/Classes/Session/Typo3FrontendSessionService.php
new file mode 100644
index 0000000..f67e114
--- /dev/null
+++ b/Classes/Session/Typo3FrontendSessionService.php
@@ -0,0 +1,73 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist\Session;
+
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Frontend\Controller\TypoScriptFrontendController;
+use WerkraumMedia\Watchlist\Domain\Model\Item;
+use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
+
+class Typo3FrontendSessionService implements SessionServiceInterface
+{
+ private PropertyMapper $propertyMapper;
+
+ public function __construct(
+ PropertyMapper $propertyMapper
+ ) {
+ $this->propertyMapper = $propertyMapper;
+ }
+
+ public function getWatchlist(string $identifier): ?Watchlist
+ {
+ $items = $this->getTsfe()->fe_user->getSessionData('watchlist-' . $identifier) ?: [];
+ if ($items === [] || is_array($items) === false) {
+ return null;
+ }
+
+ $watchlist = new Watchlist();
+
+ array_map(function (string $item) use ($watchlist) {
+ $mappedItem = $this->propertyMapper->convert($item, Item::class);
+ if (!$mappedItem instanceof Item) {
+ return;
+ }
+
+ $watchlist->addItem($mappedItem);
+ }, $items);
+
+ return $watchlist;
+ }
+
+ public function update(Watchlist $watchlist): void
+ {
+ $this->getTsfe()->fe_user->setAndSaveSessionData(
+ 'watchlist-' . $watchlist->getIdentifier(),
+ array_map(fn (Item $item) => $item->getUniqueIdentifier(), $watchlist->getItems())
+ );
+ }
+ private function getTsfe(): TypoScriptFrontendController
+ {
+ return $GLOBALS['TSFE'];
+ }
+}
diff --git a/Configuration/Services.php b/Configuration/Services.php
new file mode 100644
index 0000000..6f8f3de
--- /dev/null
+++ b/Configuration/Services.php
@@ -0,0 +1,49 @@
+
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+namespace WerkraumMedia\Watchlist;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
+use WerkraumMedia\Watchlist\Domain\ItemHandlerRegistry;
+
+return function (ContainerConfigurator $container, ContainerBuilder $containerBuilder) {
+ $containerBuilder->addCompilerPass(new class() implements CompilerPassInterface {
+ public function process(ContainerBuilder $container): void
+ {
+ $registry = $container->getDefinition(ItemHandlerRegistry::class)
+ ->setShared(true);
+
+ foreach (array_keys($container->findTaggedServiceIds('watchlist.itemHandler')) as $id) {
+ $definition = $container->getDefinition($id);
+
+ if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
+ continue;
+ }
+
+ $registry->addMethodCall('add', [$definition]);
+ }
+ }
+ });
+};
diff --git a/Configuration/Services.yaml b/Configuration/Services.yaml
new file mode 100644
index 0000000..4ad187a
--- /dev/null
+++ b/Configuration/Services.yaml
@@ -0,0 +1,13 @@
+services:
+ _defaults:
+ autowire: true
+ autoconfigure: true
+ public: false
+
+ WerkraumMedia\Watchlist\:
+ resource: '../Classes/*'
+ exclude:
+ - '../Classes/Domain/Model/*'
+
+ WerkraumMedia\Watchlist\Domain\Items\Page\ItemHandler:
+ tags: ['watchlist.itemHandler']
diff --git a/Configuration/TCA/Overrides/tt_content.php b/Configuration/TCA/Overrides/tt_content.php
new file mode 100644
index 0000000..1172acc
--- /dev/null
+++ b/Configuration/TCA/Overrides/tt_content.php
@@ -0,0 +1,14 @@
+
+
{f:translate(id: 'text.watchlist.isEmpty', extensionName: 'Watchlist')} +