From 4930053056114ea9ca0bd3a183040fad3e06fb03 Mon Sep 17 00:00:00 2001 From: Andreas Schempp Date: Mon, 24 Jul 2017 11:29:26 +0200 Subject: [PATCH] Rewrite the DCA picker (see contao/core-bundle#950). --- CHANGELOG.md | 4 + composer.json | 2 +- src/Menu/NewsPickerProvider.php | 115 -------- src/Picker/NewsPickerProvider.php | 133 +++++++++ src/Resources/config/services.yml | 12 +- .../ContaoNewsExtensionTest.php | 20 +- tests/Menu/NewsPickerProviderTest.php | 226 ---------------- tests/Picker/NewsPickerProviderTest.php | 256 ++++++++++++++++++ 8 files changed, 409 insertions(+), 359 deletions(-) delete mode 100644 src/Menu/NewsPickerProvider.php create mode 100644 src/Picker/NewsPickerProvider.php delete mode 100644 tests/Menu/NewsPickerProviderTest.php create mode 100644 tests/Picker/NewsPickerProviderTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 145ad9221..af7dcd0e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Contao news bundle change log +### DEV + + * Rewrite the DCA picker (see contao/core-bundle#950). + ### 4.4.1 (2017-07-12) * Always show the "show from" and "show until" fields (see contao/core-bundle#908). diff --git a/composer.json b/composer.json index c8e6bf6b3..d6e7e62cb 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "require": { "php": "^5.6|^7.0", "symfony/framework-bundle": "^3.3", - "contao/core-bundle": "^4.4" + "contao/core-bundle": "dev-hotfix/4.4.2" }, "require-dev": { "contao/manager-plugin": "^2.0", diff --git a/src/Menu/NewsPickerProvider.php b/src/Menu/NewsPickerProvider.php deleted file mode 100644 index 7562c046c..000000000 --- a/src/Menu/NewsPickerProvider.php +++ /dev/null @@ -1,115 +0,0 @@ - - */ -class NewsPickerProvider extends AbstractMenuProvider implements PickerMenuProviderInterface, FrameworkAwareInterface -{ - use FrameworkAwareTrait; - - /** - * {@inheritdoc} - */ - public function supports($context) - { - return 'link' === $context; - } - - /** - * {@inheritdoc} - */ - public function createMenu(ItemInterface $menu, FactoryInterface $factory) - { - $user = $this->getUser(); - - if ($user->hasAccess('news', 'modules')) { - $this->addMenuItem($menu, $factory, 'news', 'newsPicker', 'news'); - } - } - - /** - * {@inheritdoc} - */ - public function supportsTable($table) - { - return 'tl_news' === $table; - } - - /** - * {@inheritdoc} - */ - public function processSelection($value) - { - return sprintf('{{news_url::%s}}', $value); - } - - /** - * {@inheritdoc} - */ - public function canHandle(Request $request) - { - return $request->query->has('value') && false !== strpos($request->query->get('value'), '{{news_url::'); - } - - /** - * {@inheritdoc} - */ - public function getPickerUrl(Request $request) - { - $params = $request->query->all(); - $params['do'] = 'news'; - $params['value'] = str_replace(['{{news_url::', '}}'], '', $params['value']); - - if (null !== ($newsArchiveId = $this->getNewsArchiveId($params['value']))) { - $params['table'] = 'tl_news'; - $params['id'] = $newsArchiveId; - } - - return $this->route('contao_backend', $params); - } - - /** - * Returns the news archive ID. - * - * @param int $id - * - * @return int|null - */ - private function getNewsArchiveId($id) - { - /** @var NewsModel $newsAdapter */ - $newsAdapter = $this->framework->getAdapter(NewsModel::class); - - if (!(($newsModel = $newsAdapter->findById($id)) instanceof NewsModel)) { - return null; - } - - if (!(($newsArchive = $newsModel->getRelated('pid')) instanceof NewsArchiveModel)) { - return null; - } - - return $newsArchive->id; - } -} diff --git a/src/Picker/NewsPickerProvider.php b/src/Picker/NewsPickerProvider.php new file mode 100644 index 000000000..352522cfe --- /dev/null +++ b/src/Picker/NewsPickerProvider.php @@ -0,0 +1,133 @@ + + */ +class NewsPickerProvider extends AbstractPickerProvider implements DcaPickerProviderInterface, FrameworkAwareInterface +{ + use FrameworkAwareTrait; + + /** + * {@inheritdoc} + */ + public function getName() + { + return 'newsPicker'; + } + + /** + * {@inheritdoc} + */ + public function supportsContext($context) + { + return 'link' === $context && $this->getUser()->hasAccess('news', 'modules'); + } + + /** + * {@inheritdoc} + */ + public function supportsValue(PickerConfig $config) + { + return false !== strpos($config->getValue(), '{{news_url::'); + } + + /** + * {@inheritdoc} + */ + public function getDcaTable() + { + return 'tl_news'; + } + + /** + * {@inheritdoc} + */ + public function getDcaAttributes(PickerConfig $config) + { + $attributes = ['fieldType' => 'radio']; + + if ($this->supportsValue($config)) { + $attributes['value'] = str_replace(['{{news_url::', '}}'], '', $config->getValue()); + } + + return $attributes; + } + + /** + * {@inheritdoc} + */ + public function convertDcaValue(PickerConfig $config, $value) + { + return '{{news_url::'.$value.'}}'; + } + + /** + * {@inheritdoc} + */ + protected function getLinkClass() + { + return 'news'; + } + + /** + * {@inheritdoc} + */ + protected function getRouteParameters(PickerConfig $config) + { + $params = ['do' => 'news']; + + if ($config->getValue() && false !== strpos($config->getValue(), '{{news_url::')) { + $value = str_replace(['{{news_url::', '}}'], '', $config->getValue()); + + if (null !== ($newsArchiveId = $this->getNewsArchiveId($value))) { + $params['table'] = 'tl_news'; + $params['id'] = $newsArchiveId; + } + } + + return $params; + } + + /** + * Returns the news archive ID. + * + * @param int $id + * + * @return int|null + */ + private function getNewsArchiveId($id) + { + /** @var NewsModel $newsAdapter */ + $newsAdapter = $this->framework->getAdapter(NewsModel::class); + + if (!(($newsModel = $newsAdapter->findById($id)) instanceof NewsModel)) { + return null; + } + + if (!(($newsArchive = $newsModel->getRelated('pid')) instanceof NewsArchiveModel)) { + return null; + } + + return $newsArchive->id; + } +} diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 0ed339bf1..0040eedb5 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -4,12 +4,12 @@ services: calls: - ["setFramework", ["@contao.framework"]] - contao_news.listener.news_picker_provider: - class: Contao\NewsBundle\Menu\NewsPickerProvider + contao_news.picker.news_provider: + class: Contao\NewsBundle\Picker\NewsPickerProvider public: false arguments: - - "@router" - - "@request_stack" - - "@security.token_storage" + - "@knp_menu.factory" + calls: + - [setTokenStorage, ["@security.token_storage"]] tags: - - { name: contao.picker_menu_provider, priority: 128 } + - { name: contao.picker_provider, priority: 128 } diff --git a/tests/DependencyInjection/ContaoNewsExtensionTest.php b/tests/DependencyInjection/ContaoNewsExtensionTest.php index 6b9fbf045..09c641c3d 100644 --- a/tests/DependencyInjection/ContaoNewsExtensionTest.php +++ b/tests/DependencyInjection/ContaoNewsExtensionTest.php @@ -16,7 +16,7 @@ use Contao\NewsBundle\EventListener\InsertTagsListener; use Contao\NewsBundle\EventListener\PreviewUrlConvertListener; use Contao\NewsBundle\EventListener\PreviewUrlCreateListener; -use Contao\NewsBundle\Menu\NewsPickerProvider; +use Contao\NewsBundle\Picker\NewsPickerProvider; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -128,15 +128,13 @@ public function testPreviewUrlConvertListener() */ public function testNewsPickerProvider() { - $this->assertTrue($this->container->has('contao_news.listener.news_picker_provider')); + $this->assertTrue($this->container->has('contao_news.picker.news_provider')); - $definition = $this->container->getDefinition('contao_news.listener.news_picker_provider'); + $definition = $this->container->getDefinition('contao_news.picker.news_provider'); $this->assertSame(NewsPickerProvider::class, $definition->getClass()); $this->assertFalse($definition->isPublic()); - $this->assertSame('router', (string) $definition->getArgument(0)); - $this->assertSame('request_stack', (string) $definition->getArgument(1)); - $this->assertSame('security.token_storage', (string) $definition->getArgument(2)); + $this->assertSame('knp_menu.factory', (string) $definition->getArgument(0)); $conditionals = $definition->getInstanceofConditionals(); @@ -145,13 +143,13 @@ public function testNewsPickerProvider() /** @var ChildDefinition $childDefinition */ $childDefinition = $conditionals[FrameworkAwareInterface::class]; - $methodCalls = $childDefinition->getMethodCalls(); - - $this->assertSame('setFramework', $methodCalls[0][0]); + $this->assertSame('setFramework', $childDefinition->getMethodCalls()[0][0]); $tags = $definition->getTags(); - $this->assertArrayHasKey('contao.picker_menu_provider', $tags); - $this->assertSame(128, $tags['contao.picker_menu_provider'][0]['priority']); + $this->assertSame('setTokenStorage', $definition->getMethodCalls()[0][0]); + + $this->assertArrayHasKey('contao.picker_provider', $tags); + $this->assertSame(128, $tags['contao.picker_provider'][0]['priority']); } } diff --git a/tests/Menu/NewsPickerProviderTest.php b/tests/Menu/NewsPickerProviderTest.php deleted file mode 100644 index 7b01b95b7..000000000 --- a/tests/Menu/NewsPickerProviderTest.php +++ /dev/null @@ -1,226 +0,0 @@ - - */ -class NewsPickerProviderTest extends TestCase -{ - /** - * @var PickerMenuProviderInterface - */ - private $provider; - - /** - * {@inheritdoc} - */ - protected function setUp() - { - parent::setUp(); - - $this->provider = $this->mockPickerProvider(); - } - - /** - * Tests the object instantiation. - */ - public function testInstantiation() - { - $this->assertInstanceOf('Contao\NewsBundle\Menu\NewsPickerProvider', $this->provider); - } - - /** - * Tests the createMenu() method. - */ - public function testCreateMenu() - { - $factory = new MenuFactory(); - $menu = $factory->createItem('foo'); - - $this->provider->createMenu($menu, $factory); - - $this->assertTrue($menu->hasChildren()); - - $eventPicker = $menu->getChild('newsPicker'); - - $this->assertNotNull($eventPicker); - $this->assertSame('news', $eventPicker->getLinkAttribute('class')); - $this->assertSame('contao_backend:do=news', $eventPicker->getUri()); - } - - /** - * Tests the supports() method. - */ - public function testSupports() - { - $this->assertTrue($this->provider->supports('link')); - $this->assertFalse($this->provider->supports('page')); - } - - /** - * Tests the supportsTable() method. - */ - public function testSupportsTable() - { - $this->assertTrue($this->provider->supportsTable('tl_news')); - $this->assertFalse($this->provider->supportsTable('tl_page')); - } - - /** - * Tests the processSelection() method. - */ - public function testProcessSelection() - { - $this->assertSame('{{news_url::2}}', $this->provider->processSelection(2)); - } - - /** - * Tests the canHandle() method. - */ - public function testCanHandle() - { - $request = new Request(); - $request->query->set('value', '{{news_url::2}}'); - - $this->assertTrue($this->provider->canHandle($request)); - - $request->query->set('value', 'files/test/foo.jpg'); - - $this->assertFalse($this->provider->canHandle($request)); - - $request->query->remove('value'); - - $this->assertFalse($this->provider->canHandle($request)); - } - - /** - * Tests the getPickerUrl() method. - */ - public function testGetPickerUrl() - { - $request = new Request(); - $request->query->set('value', '{{news_url::42}}'); - - $this->assertSame( - 'contao_backend:value=42:do=news:table=tl_news:id=2', - $this->provider->getPickerUrl($request) - ); - - $this->assertSame('contao_backend:value=42:do=news', $this->provider->getPickerUrl($request)); - $this->assertSame('contao_backend:value=42:do=news', $this->provider->getPickerUrl($request)); - $this->assertSame('contao_backend:value=42:do=news', $this->provider->getPickerUrl($request)); - } - - /** - * Mocks a picker provider. - * - * @return PickerMenuProviderInterface - */ - protected function mockPickerProvider() - { - $router = $this->createMock(RouterInterface::class); - - $router - ->method('generate') - ->willReturnCallback(function ($name, $params) { - $url = $name; - - foreach ($params as $key => $value) { - $url .= ':'.$key.'='.$value; - } - - return $url; - }) - ; - - $user = $this->createMock(BackendUser::class); - - $user - ->method('hasAccess') - ->willReturn(true) - ; - - $token = $this->createMock(TokenInterface::class); - - $token - ->method('getUser') - ->willReturn($user) - ; - - $tokenStorage = $this->createMock(TokenStorageInterface::class); - - $tokenStorage - ->method('getToken') - ->willReturn($token) - ; - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $archiveModel = $this->createMock(NewsArchiveModel::class); - - $archiveModel - ->method('__get') - ->willReturn(2) - ; - - $newsModel = $this->createMock(NewsModel::class); - - $newsModel - ->method('getRelated') - ->willReturnOnConsecutiveCalls($archiveModel, null) - ; - - $adapter = $this - ->getMockBuilder(Adapter::class) - ->disableOriginalConstructor() - ->setMethods(['findById']) - ->getMock() - ; - - $adapter - ->method('findById') - ->willReturnOnConsecutiveCalls($newsModel, $newsModel, null) - ; - - $framework = $this->createMock(ContaoFrameworkInterface::class); - - $framework - ->method('getAdapter') - ->willReturn($adapter) - ; - - $provider = new NewsPickerProvider($router, $requestStack, $tokenStorage); - $provider->setFramework($framework); - - return $provider; - } -} diff --git a/tests/Picker/NewsPickerProviderTest.php b/tests/Picker/NewsPickerProviderTest.php new file mode 100644 index 000000000..029f5385b --- /dev/null +++ b/tests/Picker/NewsPickerProviderTest.php @@ -0,0 +1,256 @@ + + */ +class NewsPickerProviderTest extends TestCase +{ + /** + * @var NewsPickerProvider + */ + protected $provider; + + /** + * {@inheritdoc} + */ + protected function setUp() + { + parent::setUp(); + + $menuFactory = $this->createMock(FactoryInterface::class); + + $menuFactory + ->method('createItem') + ->willReturnArgument(1) + ; + + $this->provider = new NewsPickerProvider($menuFactory); + + $GLOBALS['TL_LANG']['MSC']['newsPicker'] = 'News picker'; + } + + /** + * {@inheritdoc} + */ + protected function tearDown() + { + parent::tearDown(); + + unset($GLOBALS['TL_LANG']); + } + + /** + * Tests the object instantiation. + */ + public function testInstantiation() + { + $this->assertInstanceOf('Contao\NewsBundle\Picker\NewsPickerProvider', $this->provider); + } + + /** + * Tests the createMenuItem() method. + */ + public function testCreateMenuItem() + { + $picker = json_encode([ + 'context' => 'link', + 'extras' => [], + 'current' => 'newsPicker', + 'value' => '', + ]); + + if (function_exists('gzencode') && false !== ($encoded = @gzencode($picker))) { + $picker = $encoded; + } + + $this->assertSame( + [ + 'label' => 'News picker', + 'linkAttributes' => ['class' => 'news'], + 'current' => true, + 'route' => 'contao_backend', + 'routeParameters' => [ + 'popup' => '1', + 'do' => 'news', + 'picker' => strtr(base64_encode($picker), '+/=', '-_,'), + ], + ], $this->provider->createMenuItem(new PickerConfig('link', [], '', 'newsPicker')) + ); + } + + /** + * Tests the isCurrent() method. + */ + public function testIsCurrent() + { + $this->assertTrue($this->provider->isCurrent(new PickerConfig('link', [], '', 'newsPicker'))); + $this->assertFalse($this->provider->isCurrent(new PickerConfig('link', [], '', 'filePicker'))); + } + + /** + * Tests the getName() method. + */ + public function testGetName() + { + $this->assertSame('newsPicker', $this->provider->getName()); + } + + /** + * Tests the supportsContext() method. + */ + public function testSupportsContext() + { + $user = $this + ->getMockBuilder(BackendUser::class) + ->disableOriginalConstructor() + ->setMethods(['hasAccess']) + ->getMock() + ; + + $user + ->method('hasAccess') + ->willReturn(true) + ; + + $token = $this->createMock(TokenInterface::class); + + $token + ->method('getUser') + ->willReturn($user) + ; + + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage + ->method('getToken') + ->willReturn($token) + ; + + $this->provider->setTokenStorage($tokenStorage); + + $this->assertTrue($this->provider->supportsContext('link')); + $this->assertFalse($this->provider->supportsContext('file')); + } + + /** + * Tests the supportsContext() method without token storage. + */ + public function testSupportsContextWithoutTokenStorage() + { + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('No token storage provided'); + + $this->provider->supportsContext('link'); + } + + /** + * Tests the supportsContext() method without token. + */ + public function testSupportsContextWithoutToken() + { + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage + ->method('getToken') + ->willReturn(null) + ; + + $this->provider->setTokenStorage($tokenStorage); + + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('No token provided'); + + $this->provider->supportsContext('link'); + } + + /** + * Tests the supportsContext() method without a user object. + */ + public function testSupportsContextWithoutUser() + { + $token = $this->createMock(TokenInterface::class); + + $token + ->method('getUser') + ->willReturn(null) + ; + + $tokenStorage = $this->createMock(TokenStorageInterface::class); + + $tokenStorage + ->method('getToken') + ->willReturn($token) + ; + + $this->provider->setTokenStorage($tokenStorage); + + $this->expectException('RuntimeException'); + $this->expectExceptionMessage('The token does not contain a back end user object'); + + $this->provider->supportsContext('link'); + } + + /** + * Tests the supportsValue() method. + */ + public function testSupportsValue() + { + $this->assertTrue($this->provider->supportsValue(new PickerConfig('link', [], '{{news_url::5}}'))); + $this->assertFalse($this->provider->supportsValue(new PickerConfig('link', [], '{{link_url::5}}'))); + } + + /** + * Tests the getDcaTable() method. + */ + public function testGetDcaTable() + { + $this->assertSame('tl_news', $this->provider->getDcaTable()); + } + + /** + * Tests the getDcaAttributes() method. + */ + public function testGetDcaAttributes() + { + $this->assertSame( + [ + 'fieldType' => 'radio', + 'value' => '5', + ], + $this->provider->getDcaAttributes(new PickerConfig('link', [], '{{news_url::5}}')) + ); + + $this->assertSame( + ['fieldType' => 'radio'], + $this->provider->getDcaAttributes(new PickerConfig('link', [], '{{link_url::5}}')) + ); + } + + /** + * Tests the convertDcaValue() method. + */ + public function testConvertDcaValue() + { + $this->assertSame('{{news_url::5}}', $this->provider->convertDcaValue(new PickerConfig('link'), 5)); + } +}