mirror of
https://github.com/werkraum-media/watchlist.git
synced 2024-11-21 13:36:10 +01:00
[TASK] Kickstart extension with basic features (#1)
This commit is contained in:
parent
15120714de
commit
916b2723d5
33 changed files with 1546 additions and 3 deletions
150
.github/workflows/ci.yaml
vendored
Normal file
150
.github/workflows/ci.yaml
vendored
Normal file
|
@ -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
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/composer.lock
|
||||
/.php-cs-fixer.cache
|
||||
/vendor/
|
||||
/public/
|
||||
/var/
|
62
.php-cs-fixer.dist.php
Normal file
62
.php-cs-fixer.dist.php
Normal file
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
$finder = (new PhpCsFixer\Finder())
|
||||
->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);
|
84
Classes/Controller/WatchlistController.php
Normal file
84
Classes/Controller/WatchlistController.php
Normal file
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
45
Classes/Domain/ItemHandlerInterface.php
Normal file
45
Classes/Domain/ItemHandlerInterface.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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;
|
||||
}
|
55
Classes/Domain/ItemHandlerRegistry.php
Normal file
55
Classes/Domain/ItemHandlerRegistry.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
65
Classes/Domain/Items/Page/ItemHandler.php
Normal file
65
Classes/Domain/Items/Page/ItemHandler.php
Normal file
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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() ?: [];
|
||||
}
|
||||
}
|
51
Classes/Domain/Items/Page/Page.php
Normal file
51
Classes/Domain/Items/Page/Page.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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;
|
||||
}
|
||||
}
|
32
Classes/Domain/Model/Item.php
Normal file
32
Classes/Domain/Model/Item.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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;
|
||||
}
|
60
Classes/Domain/Model/Watchlist.php
Normal file
60
Classes/Domain/Model/Watchlist.php
Normal file
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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 === [];
|
||||
}
|
||||
}
|
56
Classes/Domain/Repository/ItemRepository.php
Normal file
56
Classes/Domain/Repository/ItemRepository.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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
|
||||
);
|
||||
}
|
||||
}
|
57
Classes/Domain/Repository/WatchlistRepository.php
Normal file
57
Classes/Domain/Repository/WatchlistRepository.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
59
Classes/Extbase/TypeConverter/ItemTypeConverter.php
Normal file
59
Classes/Extbase/TypeConverter/ItemTypeConverter.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
58
Classes/Extbase/TypeConverter/WatchlistTypeConverter.php
Normal file
58
Classes/Extbase/TypeConverter/WatchlistTypeConverter.php
Normal file
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
}
|
33
Classes/Session/SessionServiceInterface.php
Normal file
33
Classes/Session/SessionServiceInterface.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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;
|
||||
}
|
73
Classes/Session/Typo3FrontendSessionService.php
Normal file
73
Classes/Session/Typo3FrontendSessionService.php
Normal file
|
@ -0,0 +1,73 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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'];
|
||||
}
|
||||
}
|
49
Configuration/Services.php
Normal file
49
Configuration/Services.php
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
13
Configuration/Services.yaml
Normal file
13
Configuration/Services.yaml
Normal file
|
@ -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']
|
14
Configuration/TCA/Overrides/tt_content.php
Normal file
14
Configuration/TCA/Overrides/tt_content.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
|
||||
|
||||
defined('TYPO3') || die();
|
||||
|
||||
ExtensionUtility::registerPlugin(
|
||||
'Watchlist',
|
||||
'Watchlist',
|
||||
'LLL:EXT:watchlist/Resources/Private/Language/locallang.xlf:plugin.watchlist',
|
||||
'EXT:watchlist/Resources/Public/Icons/Extension.svg'
|
||||
);
|
||||
|
||||
$GLOBALS['TCA']['tt_content']['types']['list']['subtypes_excludelist']['watchlist_watchlist'] = 'recursive,select_key,pages';
|
|
@ -1,3 +0,0 @@
|
|||
# watchlist
|
||||
|
||||
TYPO3 watchlist extension
|
53
README.rst
Normal file
53
README.rst
Normal file
|
@ -0,0 +1,53 @@
|
|||
=========================
|
||||
TYPO3 Extension Watchlist
|
||||
=========================
|
||||
|
||||
Adds the feature of a watchlist to the frontend of TYPO3 CMS.
|
||||
|
||||
Users are able to add "items" to the watchlist.
|
||||
They are also able to remove things from watchlist and show the current watchlist.
|
||||
|
||||
Items can be anything. The extension uses interfaces and developers are able to
|
||||
connect whatever they want to be a thing that can be added to the watchlist.
|
||||
|
||||
Feature set
|
||||
===========
|
||||
|
||||
The extension provides an controller with actions to add and remove items from watchlist.
|
||||
|
||||
There is also an action to show the current watchlist.
|
||||
|
||||
The extension ships with support for items of type ``pages``,
|
||||
but this is more a demonstration an used for testing.
|
||||
Projects are way too different and should provide their own items, see "Custom Items".
|
||||
|
||||
Custom Items
|
||||
============
|
||||
|
||||
A developer needs to implement an ``ItemHandler`` and an class representing the ``Item``.
|
||||
|
||||
``ItemHandler``
|
||||
---------------
|
||||
|
||||
The class needs to implement the ``WerkraumMedia\Watchlist\Domain\ItemHandlerInterface``.
|
||||
The purpose is to convert an identifier of that item to an actual instance of that item.
|
||||
The Handler needs to be registered via Symfony Tags, e.g. via ``Services.yaml``:
|
||||
|
||||
.. code:: yam;
|
||||
|
||||
services:
|
||||
_defaults:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
public: false
|
||||
|
||||
WerkraumMedia\Watchlist\:
|
||||
resource: '../Classes/*'
|
||||
|
||||
WerkraumMedia\Watchlist\Domain\Items\Page\ItemHandler:
|
||||
tags: ['watchlist.itemHandler']
|
||||
|
||||
``Item``
|
||||
--------
|
||||
|
||||
The class needs to implement the ``WerkraumMedia\Watchlist\Domain\Model\Item``.
|
14
Resources/Private/Language/locallang.xlf
Normal file
14
Resources/Private/Language/locallang.xlf
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en" datatype="plaintext" original="messages" date="2022-09-26T10:43:30Z" product-name="Watchlist">
|
||||
<header/>
|
||||
<body>
|
||||
<trans-unit id="headline.index" xml:space="preserve">
|
||||
<source>Watchlist</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="text.watchlist.isEmpty" xml:space="preserve">
|
||||
<source>Watchlist is empty</source>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
18
Resources/Private/Templates/Watchlist/Index.html
Normal file
18
Resources/Private/Templates/Watchlist/Index.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
|
||||
data-namespace-typo3-fluid="true">
|
||||
|
||||
<h2>{f:translate(id: 'headline.index', extensionName: 'Watchlist')}</h2>
|
||||
|
||||
<f:if condition="{watchlist.empty}">
|
||||
<f:then>
|
||||
<p>{f:translate(id: 'text.watchlist.isEmpty', extensionName: 'Watchlist')}
|
||||
</f:then>
|
||||
<f:else>
|
||||
<ol>
|
||||
<f:for each="{watchlist.items}" as="item">
|
||||
<li>{item.title}</li>
|
||||
</f:for>
|
||||
</ol>
|
||||
</f:else>
|
||||
</f:if>
|
||||
</html>
|
144
Tests/Functional/Frontend/BasicsTest.php
Normal file
144
Tests/Functional/Frontend/BasicsTest.php
Normal file
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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\Tests\Functional\Frontend;
|
||||
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalRequest;
|
||||
use TYPO3\TestingFramework\Core\Functional\Framework\Frontend\InternalResponse;
|
||||
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
|
||||
|
||||
/**
|
||||
* @covers \WerkraumMedia\Watchlist\Frontend\Basics
|
||||
*/
|
||||
class BasicsTest extends FunctionalTestCase
|
||||
{
|
||||
protected $coreExtensionsToLoad = [
|
||||
'fluid_styled_content',
|
||||
];
|
||||
|
||||
protected $testExtensionsToLoad = [
|
||||
'typo3conf/ext/watchlist',
|
||||
];
|
||||
|
||||
protected $pathsToProvideInTestInstance = [
|
||||
'typo3conf/ext/watchlist/Tests/Functional/Frontend/Fixtures/Sites/' => 'typo3conf/sites/',
|
||||
];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->setUpBackendUserFromFixture(1);
|
||||
|
||||
$this->importCSVDataSet(__DIR__ . '/Fixtures/BasicDatabase.csv');
|
||||
$this->setUpFrontendRootPage(1, [
|
||||
'EXT:watchlist/Tests/Functional/Frontend/Fixtures/FrontendRendering.typoscript',
|
||||
'EXT:fluid_styled_content/Configuration/TypoScript/setup.typoscript',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function watchlistIsRenderedAsEmptyByDefault(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $result->getStatusCode());
|
||||
self::assertStringContainsString('Watchlist is empty', $result->getBody()->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function canStorePagesOnWatchlistAccrossPageCalls(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[redirectUri]', $request->getUri()->__toString());
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[action]', 'add');
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[item]', 'page-1');
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertIsRedirect('http://localhost/?id=1', $result);
|
||||
self::assertHasCookie('fe_typo_user', $result);
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withHeader('Cookie', self::getCookies($result));
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertStringContainsString('Page Title', $result->getBody()->__toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function canRemoveStoredEntryFromWatchlist(): void
|
||||
{
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[redirectUri]', $request->getUri()->__toString());
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[action]', 'add');
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[item]', 'page-1');
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
$cookies = self::getCookies($result);
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withHeader('Cookie', $cookies);
|
||||
$request = $request->withPageId(1);
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[redirectUri]', $request->getUri()->__toString());
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[action]', 'remove');
|
||||
$request = $request->withQueryParameter('tx_watchlist_watchlist[item]', 'page-1');
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertIsRedirect('http://localhost/?id=1', $result);
|
||||
|
||||
$request = new InternalRequest();
|
||||
$request = $request->withHeader('Cookie', $cookies);
|
||||
$request = $request->withPageId(1);
|
||||
$result = $this->executeFrontendRequest($request);
|
||||
|
||||
self::assertSame(200, $result->getStatusCode());
|
||||
self::assertStringContainsString('Watchlist is empty', $result->getBody()->__toString());
|
||||
}
|
||||
|
||||
private static function assertIsRedirect(string $redirectLocation, InternalResponse $result): void
|
||||
{
|
||||
self::assertSame(303, $result->getStatusCode());
|
||||
self::assertSame($redirectLocation, $result->getHeader('location')[0] ?? '');
|
||||
}
|
||||
|
||||
private static function assertHasCookie(string $cookieName, InternalResponse $result): void
|
||||
{
|
||||
self::assertStringContainsString($cookieName . '=', self::getCookies($result));
|
||||
}
|
||||
|
||||
private static function getCookies(InternalResponse $result): string
|
||||
{
|
||||
return explode(' ', $result->getHeader('Set-Cookie')[0] ?? '', 2)[0] ?? '';
|
||||
}
|
||||
}
|
3
Tests/Functional/Frontend/Fixtures/BasicDatabase.csv
Normal file
3
Tests/Functional/Frontend/Fixtures/BasicDatabase.csv
Normal file
|
@ -0,0 +1,3 @@
|
|||
pages
|
||||
,uid,pid,slug,title
|
||||
,1,0,/,Page Title
|
|
|
@ -0,0 +1,2 @@
|
|||
page = PAGE
|
||||
page.10 =< tt_content.watchlist_watchlist.20
|
32
Tests/Functional/Frontend/Fixtures/Sites/default/config.yaml
Normal file
32
Tests/Functional/Frontend/Fixtures/Sites/default/config.yaml
Normal file
|
@ -0,0 +1,32 @@
|
|||
base: /
|
||||
languages:
|
||||
-
|
||||
title: Deutsch
|
||||
enabled: true
|
||||
base: /
|
||||
typo3Language: de
|
||||
locale: de_DE.UTF-8
|
||||
iso-639-1: de
|
||||
navigationTitle: Deutsch
|
||||
hreflang: de-DE
|
||||
direction: ''
|
||||
flag: de
|
||||
websiteTitle: ''
|
||||
languageId: 0
|
||||
-
|
||||
title: English
|
||||
enabled: true
|
||||
base: /en
|
||||
typo3Language: default
|
||||
locale: en_GB.UTF-8
|
||||
iso-639-1: en
|
||||
websiteTitle: ''
|
||||
navigationTitle: English
|
||||
hreflang: en-GB
|
||||
direction: ''
|
||||
flag: gb
|
||||
languageId: 1
|
||||
fallbackType: strict
|
||||
fallbacks: '0'
|
||||
rootPageId: 1
|
||||
websiteTitle: 'Example Website'
|
87
Tests/Unit/Domain/Model/WatchlistTest.php
Normal file
87
Tests/Unit/Domain/Model/WatchlistTest.php
Normal file
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Copyright (C) 2022 Daniel Siepmann <coding@daniel-siepmann.de>
|
||||
*
|
||||
* 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\Tests\Unit\Domain\Model;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use WerkraumMedia\Watchlist\Domain\Items\Page\Page;
|
||||
use WerkraumMedia\Watchlist\Domain\Model\Watchlist;
|
||||
|
||||
/**
|
||||
* @covers \WerkraumMedia\Watchlist\Domain\Model\Watchlist
|
||||
* @testdox The Watchlist
|
||||
*/
|
||||
class WatchlistTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function canBeCreated(): void
|
||||
{
|
||||
$subject = new Watchlist();
|
||||
|
||||
self::assertInstanceOf(
|
||||
Watchlist::class,
|
||||
$subject
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function doesNotHaveItemsByDefault(): void
|
||||
{
|
||||
$subject = new Watchlist();
|
||||
|
||||
self::assertCount(0, $subject->getItems());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function canAddAItem(): void
|
||||
{
|
||||
$subject = new Watchlist();
|
||||
|
||||
$thing = new Page(1, 'test');
|
||||
$subject->addItem($thing);
|
||||
|
||||
self::assertCount(1, $subject->getItems());
|
||||
self::assertSame($thing, $subject->getItems()[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function canRemoveAItem(): void
|
||||
{
|
||||
$subject = new Watchlist();
|
||||
|
||||
$thing = new Page(1, 'test');
|
||||
$subject->addItem($thing);
|
||||
|
||||
$subject->removeItem($thing);
|
||||
|
||||
self::assertCount(0, $subject->getItems());
|
||||
}
|
||||
}
|
80
composer.json
Normal file
80
composer.json
Normal file
|
@ -0,0 +1,80 @@
|
|||
{
|
||||
"name": "werkraummedia/watchlist",
|
||||
"description": "Add a watchlist to frontend of TYPO3",
|
||||
"type": "typo3-cms-extension",
|
||||
"license": "GPL-2.0-or-later",
|
||||
"homepage": "https://github.com/werkraum-media/watchlist",
|
||||
"support": {
|
||||
"docs": "https://docs.typo3.org/p/werkraummedia/watchlist/master/en-us/",
|
||||
"email": "coding@daniel-siepmann.de",
|
||||
"issues": "https://github.com/werkraum-media/watchlist/issues",
|
||||
"source": "https://github.com/werkraum-media/watchlist"
|
||||
},
|
||||
"authors": [
|
||||
{
|
||||
"name": "Daniel Siepmann",
|
||||
"email": "coding@daniel-siepmann.de",
|
||||
"homepage": "https://daniel-siepmann.de/",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"WerkraumMedia\\Watchlist\\": "Classes/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"WerkraumMedia\\Watchlist\\Tests\\": "Tests/"
|
||||
}
|
||||
},
|
||||
"require": {
|
||||
"php": "~7.4.0 || ~8.0.0 || ~8.1.0",
|
||||
"typo3/cms-backend": "^11.5",
|
||||
"typo3/cms-core": "^11.5",
|
||||
"typo3/cms-extbase": "^11.5",
|
||||
"typo3/cms-frontend": "^11.5"
|
||||
},
|
||||
"require-dev": {
|
||||
"cweagans/composer-patches": "^1.7",
|
||||
"friendsofphp/php-cs-fixer": "^3.11",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "1.1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"saschaegerer/phpstan-typo3": "^1.1",
|
||||
"typo3/cms-fluid-styled-content": "^11.5",
|
||||
"typo3/cms-tstemplate": "^11.5",
|
||||
"typo3/testing-framework": "^6.6"
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true,
|
||||
"allow-plugins": {
|
||||
"typo3/cms-composer-installers": true,
|
||||
"typo3/class-alias-loader": true,
|
||||
"ocramius/package-versions": true,
|
||||
"phpstan/extension-installer": true,
|
||||
"cweagans/composer-patches": true
|
||||
}
|
||||
},
|
||||
"extra": {
|
||||
"typo3/cms": {
|
||||
"cms-package-dir": "{$vendor-dir}/typo3/cms",
|
||||
"extension-key": "watchlist",
|
||||
"web-dir": "public"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"post-autoload-dump": [
|
||||
"@php -r 'is_dir($extFolder=__DIR__.\"/public/typo3conf/ext/\") || mkdir($extFolder, 0777, true);'",
|
||||
"@php -r 'file_exists($extFolder=__DIR__.\"/public/typo3conf/ext/watchlist\") || symlink(__DIR__,$extFolder);'"
|
||||
]
|
||||
},
|
||||
"extra": {
|
||||
"patches": {
|
||||
"typo3/testing-framework": {
|
||||
"Support cookies in request": "patches/testing-framework-cookies.patch"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
ext_localconf.php
Normal file
25
ext_localconf.php
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
|
||||
use WerkraumMedia\Watchlist\Controller\WatchlistController;
|
||||
use WerkraumMedia\Watchlist\Extbase\TypeConverter\ItemTypeConverter;
|
||||
use WerkraumMedia\Watchlist\Extbase\TypeConverter\WatchlistTypeConverter;
|
||||
|
||||
defined('TYPO3') || die('Access denied.');
|
||||
|
||||
(static function (): void {
|
||||
ExtensionUtility::configurePlugin(
|
||||
'Watchlist',
|
||||
'Watchlist',
|
||||
[
|
||||
WatchlistController::class => 'index, add, remove',
|
||||
],
|
||||
[
|
||||
WatchlistController::class => 'index',
|
||||
],
|
||||
ExtensionUtility::PLUGIN_TYPE_CONTENT_ELEMENT
|
||||
);
|
||||
|
||||
ExtensionUtility::registerTypeConverter(WatchlistTypeConverter::class);
|
||||
ExtensionUtility::registerTypeConverter(ItemTypeConverter::class);
|
||||
})();
|
17
patches/testing-framework-cookies.patch
Normal file
17
patches/testing-framework-cookies.patch
Normal file
|
@ -0,0 +1,17 @@
|
|||
diff --git a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php
|
||||
index f8b2a40..4f4020d 100644
|
||||
--- a/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php
|
||||
+++ b/Classes/Core/Functional/Framework/Frontend/RequestBootstrap.php
|
||||
@@ -106,6 +106,12 @@ class RequestBootstrap
|
||||
}
|
||||
// Populating $_COOKIE
|
||||
$_COOKIE = [];
|
||||
+ if ($this->request->hasHeader('Cookie')) {
|
||||
+ foreach ($this->request->getHeader('Cookie') as $cookie) {
|
||||
+ [$cookieName, $cookieValue] = explode('=', $cookie, 2);
|
||||
+ $_COOKIE[$cookieName] = rtrim($cookieValue, ';');
|
||||
+ }
|
||||
+ }
|
||||
|
||||
// Setting up the server environment
|
||||
$_SERVER = [];
|
12
phpstan.neon
Normal file
12
phpstan.neon
Normal file
|
@ -0,0 +1,12 @@
|
|||
parameters:
|
||||
level: max
|
||||
paths:
|
||||
- Classes
|
||||
- Configuration
|
||||
- Tests
|
||||
checkMissingIterableValueType: false
|
||||
reportUnmatchedIgnoredErrors: true
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
ignoreErrors:
|
||||
- "#^Cannot call method fetchAssociative\\(\\) on Doctrine\\\\DBAL\\\\Result\\|int\\.$#"
|
||||
- "#^Right side of \\|\\| is always false\\.$#"
|
38
phpunit.xml.dist
Normal file
38
phpunit.xml.dist
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
backupStaticAttributes="false"
|
||||
beStrictAboutCoversAnnotation="true"
|
||||
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
|
||||
colors="true"
|
||||
convertErrorsToExceptions="true"
|
||||
convertWarningsToExceptions="true"
|
||||
forceCoversAnnotation="true"
|
||||
processIsolation="false"
|
||||
stopOnError="false"
|
||||
stopOnFailure="false"
|
||||
stopOnIncomplete="false"
|
||||
stopOnSkipped="false"
|
||||
verbose="false"
|
||||
>
|
||||
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">Classes</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="unit">
|
||||
<directory>Tests/Unit/</directory>
|
||||
</testsuite>
|
||||
<testsuite name="functional">
|
||||
<directory>Tests/Functional/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<php>
|
||||
<env name="typo3DatabaseDriver" value="pdo_sqlite"/>
|
||||
</php>
|
||||
</phpunit>
|
Loading…
Reference in a new issue