diff --git a/core/modules/comment/src/Plugin/views/field/EntityLink.php b/core/modules/comment/src/Plugin/views/field/EntityLink.php index 9760057103f0362a80217fec2f50cc506de840b3..6584f21318bdd288753fe7291e2ce04f28a94615 100644 --- a/core/modules/comment/src/Plugin/views/field/EntityLink.php +++ b/core/modules/comment/src/Plugin/views/field/EntityLink.php @@ -76,7 +76,10 @@ public function render(ResultRow $values) { $entity = $this->getEntity($values); // Only render the links, if they are defined. - return !empty($this->build[$entity->id()]['links']['comment__comment']) ? \Drupal::service('renderer')->render($this->build[$entity->id()]['links']['comment__comment']) : ''; + if (!$entity || empty($this->build[$entity->id()]['links']['comment__comment'])) { + return ''; + } + return \Drupal::service('renderer')->render($this->build[$entity->id()]['links']['comment__comment']); } } diff --git a/core/modules/comment/src/Plugin/views/field/LinkApprove.php b/core/modules/comment/src/Plugin/views/field/LinkApprove.php index 683c3cbb4cf043e4cdac574ab66240aaa9674c90..86687f52f0fbac7910f41084c530f39268d46ca4 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkApprove.php +++ b/core/modules/comment/src/Plugin/views/field/LinkApprove.php @@ -19,7 +19,11 @@ class LinkApprove extends LinkBase { * {@inheritdoc} */ protected function getUrlInfo(ResultRow $row) { - return Url::fromRoute('comment.approve', ['comment' => $this->getEntity($row)->id()]); + $entity = $this->getEntity($row); + if (!$entity) { + return NULL; + } + return Url::fromRoute('comment.approve', ['comment' => $entity->id()]); } /** diff --git a/core/modules/comment/src/Plugin/views/field/LinkReply.php b/core/modules/comment/src/Plugin/views/field/LinkReply.php index 3ddbd081528d22e4cbae924f9f3dc0e564ccdf07..816f341a6c704f2c5911227c54a6c4e63c2e30a1 100644 --- a/core/modules/comment/src/Plugin/views/field/LinkReply.php +++ b/core/modules/comment/src/Plugin/views/field/LinkReply.php @@ -21,6 +21,9 @@ class LinkReply extends LinkBase { protected function getUrlInfo(ResultRow $row) { /** @var \Drupal\comment\CommentInterface $comment */ $comment = $this->getEntity($row); + if (!$comment) { + return NULL; + } return Url::fromRoute('comment.reply', [ 'entity_type' => $comment->getCommentedEntityTypeId(), 'entity' => $comment->getCommentedEntityId(), diff --git a/core/modules/comment/tests/src/Unit/Plugin/views/field/EntityLinkTest.php b/core/modules/comment/tests/src/Unit/Plugin/views/field/EntityLinkTest.php new file mode 100644 index 0000000000000000000000000000000000000000..96d59bc77de00d80a69e7b914dbc81a716aa4864 --- /dev/null +++ b/core/modules/comment/tests/src/Unit/Plugin/views/field/EntityLinkTest.php @@ -0,0 +1,42 @@ +<?php + +namespace Drupal\Tests\comment\Unit\Plugin\views\field; + +use Drupal\comment\Plugin\views\field\EntityLink; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\comment\Plugin\views\field\EntityLink + * @group comment + */ +class EntityLinkTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new EntityLink(['entity_type' => 'foo', 'entity field' => 'bar'], '', []); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkApproveTest.php b/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkApproveTest.php new file mode 100644 index 0000000000000000000000000000000000000000..b8e21dda92184c816ebc41e06798746674cb5775 --- /dev/null +++ b/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkApproveTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\comment\Unit\Plugin\views\field; + +use Drupal\comment\Plugin\views\field\LinkApprove; +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\comment\Plugin\views\field\LinkApprove + * @group comment + */ +class LinkApproveTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new LinkApprove(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkReplyTest.php b/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkReplyTest.php new file mode 100644 index 0000000000000000000000000000000000000000..ed48b8dabd40dd4c4b5df541746c0351b9c150cb --- /dev/null +++ b/core/modules/comment/tests/src/Unit/Plugin/views/field/LinkReplyTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\comment\Unit\Plugin\views\field; + +use Drupal\comment\Plugin\views\field\LinkReply; +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\comment\Plugin\views\field\LinkReply + * @group comment + */ +class LinkReplyTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new LinkReply(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/contact/src/Plugin/views/field/ContactLink.php b/core/modules/contact/src/Plugin/views/field/ContactLink.php index a98be137ddda4ff74ad3da74d6383744a41e6fe2..af72aa3313deef3e975a40fdb40f4290d3f4a0b6 100644 --- a/core/modules/contact/src/Plugin/views/field/ContactLink.php +++ b/core/modules/contact/src/Plugin/views/field/ContactLink.php @@ -30,7 +30,11 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { * {@inheritdoc} */ protected function getUrlInfo(ResultRow $row) { - return Url::fromRoute('entity.user.contact_form', ['user' => $this->getEntity($row)->id()]); + $entity = $this->getEntity($row); + if (!$entity) { + return NULL; + } + return Url::fromRoute('entity.user.contact_form', ['user' => $entity->id()]); } /** @@ -38,6 +42,9 @@ protected function getUrlInfo(ResultRow $row) { */ protected function renderLink(ResultRow $row) { $entity = $this->getEntity($row); + if (!$entity) { + return ''; + } $this->options['alter']['make_link'] = TRUE; $this->options['alter']['url'] = $this->getUrlInfo($row); diff --git a/core/modules/contact/tests/src/Unit/ContactLinkTest.php b/core/modules/contact/tests/src/Unit/ContactLinkTest.php new file mode 100644 index 0000000000000000000000000000000000000000..88304410326723ef4e9f6be8585ed83e14b62847 --- /dev/null +++ b/core/modules/contact/tests/src/Unit/ContactLinkTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\contact\Unit; + +use Drupal\contact\Plugin\views\field\ContactLink; +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\contact\Plugin\views\field\ContactLink + * @group contact + */ +class ContactLinkTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new ContactLink(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/media_library/media_library.module b/core/modules/media_library/media_library.module index 0c8e197f836932d0be795ebcf3ddf03ffbfedcb7..33cb83b983c0ed41a526aef66285cbaafe266bec 100644 --- a/core/modules/media_library/media_library.module +++ b/core/modules/media_library/media_library.module @@ -277,9 +277,7 @@ function media_library_form_views_form_media_library_page_alter(array &$form, Fo foreach (Element::getVisibleChildren($form['media_bulk_form']) as $key) { if (isset($view->result[$key])) { $media = $view->field['media_bulk_form']->getEntity($view->result[$key]); - $form['media_bulk_form'][$key]['#title'] = t('Select @label', [ - '@label' => $media->label(), - ]); + $form['media_bulk_form'][$key]['#title'] = $media ? t('Select @label', ['@label' => $media->label()]) : ''; } } } diff --git a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php index ce273305c604e49080bcb17ce45033dc778446ec..fe3e6b6cf60e67a4163fcaa63aed28cc6383ada3 100644 --- a/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php +++ b/core/modules/media_library/src/Plugin/views/field/MediaLibrarySelectForm.php @@ -56,6 +56,10 @@ public function viewsForm(array &$form, FormStateInterface $form_state) { $form[$this->options['id']]['#tree'] = TRUE; foreach ($this->view->result as $row_index => $row) { $entity = $this->getEntity($row); + if (!$entity) { + $form[$this->options['id']][$row_index] = []; + continue; + } $form[$this->options['id']][$row_index] = [ '#type' => 'checkbox', '#title' => $this->t('Select @label', [ diff --git a/core/modules/media_library/tests/src/Unit/MediaLibrarySelectFormTest.php b/core/modules/media_library/tests/src/Unit/MediaLibrarySelectFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5a4863b22f0031a5bc55e583b9ba8d878411230f --- /dev/null +++ b/core/modules/media_library/tests/src/Unit/MediaLibrarySelectFormTest.php @@ -0,0 +1,115 @@ +<?php + +namespace Drupal\Tests\media_library\Unit; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm; +use Drupal\Tests\UnitTestCase; +use Drupal\views\Entity\View; +use Drupal\views\Plugin\views\display\DefaultDisplay; +use Drupal\views\Plugin\ViewsPluginManager; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; + +/** + * @coversDefaultClass \Drupal\media_library\Plugin\views\field\MediaLibrarySelectForm + * @group media_library + */ +class MediaLibrarySelectFormTest extends UnitTestCase { + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + parent::tearDown(); + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + } + + /** + * @covers ::viewsForm + */ + public function testViewsForm(): void { + $row = new ResultRow(); + + $field = $this->getMockBuilder(MediaLibrarySelectForm::class) + ->onlyMethods(['getEntity']) + ->disableOriginalConstructor() + ->getMock(); + $field->expects($this->any()) + ->method('getEntity') + ->willReturn(NULL); + + $container = new ContainerBuilder(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + + $query = $this->getMockBuilder(ParameterBag::class) + ->onlyMethods(['all']) + ->disableOriginalConstructor() + ->getMock(); + $query->expects($this->any()) + ->method('all') + ->willReturn([]); + + $request = $this->getMockBuilder(Request::class) + ->disableOriginalConstructor() + ->getMock(); + $request->query = $query; + + $view = $this->getMockBuilder(ViewExecutable::class) + ->onlyMethods(['getRequest', 'initStyle', 'getDisplay']) + ->disableOriginalConstructor() + ->getMock(); + $view->expects($this->any()) + ->method('getRequest') + ->willReturn($request); + $view->expects($this->any()) + ->method('initStyle') + ->willReturn(TRUE); + + $display = $this->getMockBuilder(DefaultDisplay::class) + ->disableOriginalConstructor() + ->getMock(); + $display->display['id'] = 'foo'; + $view->expects($this->any()) + ->method('getDisplay') + ->willReturn($display); + + $view_entity = $this->getMockBuilder(View::class) + ->disableOriginalConstructor() + ->getMock(); + $view_entity->expects($this->any()) + ->method('get') + ->willReturn([]); + $view->storage = $view_entity; + + $display_manager = $this->getMockBuilder(ViewsPluginManager::class) + ->disableOriginalConstructor() + ->getMock(); + $display = $this->getMockBuilder(DefaultDisplay::class) + ->disableOriginalConstructor() + ->getMock(); + $display_manager->expects($this->any()) + ->method('createInstance') + ->willReturn($display); + $container->set('plugin.manager.views.display', $display_manager); + \Drupal::setContainer($container); + + $form_state = $this->createMock(FormStateInterface::class); + $view->result = [$row]; + $field->view = $view; + $field->options = ['id' => 'bar']; + $form = []; + $field->viewsForm($form, $form_state); + $this->assertNotEmpty($form); + $this->assertNotEmpty($field->view->result); + $this->assertIsArray($form[$field->options['id']][0]); + $this->assertEmpty($form[$field->options['id']][0]); + } + +} diff --git a/core/modules/node/src/Plugin/views/field/RevisionLink.php b/core/modules/node/src/Plugin/views/field/RevisionLink.php index 949ad6e07cc49b6a373d987ed37eacf0c4e9ff5f..b1b981c607c72b10e610bed3a8fcb5ada6c5c411 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLink.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLink.php @@ -21,9 +21,15 @@ class RevisionLink extends LinkBase { protected function getUrlInfo(ResultRow $row) { /** @var \Drupal\node\NodeInterface $node */ $node = $this->getEntity($row); + if (!$node) { + return NULL; + } // Current revision uses the node view path. return !$node->isDefaultRevision() ? - Url::fromRoute('entity.node.revision', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]) : + Url::fromRoute('entity.node.revision', [ + 'node' => $node->id(), + 'node_revision' => $node->getRevisionId(), + ]) : $node->toUrl(); } @@ -33,7 +39,7 @@ protected function getUrlInfo(ResultRow $row) { protected function renderLink(ResultRow $row) { /** @var \Drupal\node\NodeInterface $node */ $node = $this->getEntity($row); - if (!$node->getRevisionid()) { + if (!$node || !$node->getRevisionid()) { return ''; } $text = parent::renderLink($row); diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php index 2e1f683b5292b378e7bfee4797ad41de6cce6a57..8f92a2ee1b99da3ecd50fb6c73fb41fb1159a92f 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLinkDelete.php @@ -20,6 +20,9 @@ class RevisionLinkDelete extends RevisionLink { protected function getUrlInfo(ResultRow $row) { /** @var \Drupal\node\NodeInterface $node */ $node = $this->getEntity($row); + if (!$node) { + return NULL; + } return Url::fromRoute('node.revision_delete_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]); } diff --git a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php index 33b20b88855d153a28042851ab3da0310deb0357..7c6c10f8b3c68646ed7c3cc63001e798d977c74a 100644 --- a/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php +++ b/core/modules/node/src/Plugin/views/field/RevisionLinkRevert.php @@ -20,6 +20,9 @@ class RevisionLinkRevert extends RevisionLink { protected function getUrlInfo(ResultRow $row) { /** @var \Drupal\node\NodeInterface $node */ $node = $this->getEntity($row); + if (!$node) { + return NULL; + } return Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $node->getRevisionId()]); } diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkDeleteTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkDeleteTest.php new file mode 100644 index 0000000000000000000000000000000000000000..d8b1b8183faf4080e817a1c24cddce56b87a0cf4 --- /dev/null +++ b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkDeleteTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\node\Unit\Plugin\views\field; + +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\node\Plugin\views\field\RevisionLinkDelete; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\node\Plugin\views\field\RevisionLinkDelete + * @group node + */ +class RevisionLinkDeleteTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new RevisionLinkDelete(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkRevertTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkRevertTest.php new file mode 100644 index 0000000000000000000000000000000000000000..f6c189b1c2cc7b7d2934a563f7596b7f204e72ab --- /dev/null +++ b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkRevertTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\node\Unit\Plugin\views\field; + +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\node\Plugin\views\field\RevisionLinkDelete; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\node\Plugin\views\field\RevisionLinkRevert + * @group node + */ +class RevisionLinkRevertTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new RevisionLinkDelete(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkTest.php b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkTest.php new file mode 100644 index 0000000000000000000000000000000000000000..46966dff606072e6eaeb8708917f3ed10270ddc2 --- /dev/null +++ b/core/modules/node/tests/src/Unit/Plugin/views/field/RevisionLinkTest.php @@ -0,0 +1,50 @@ +<?php + +namespace Drupal\Tests\node\Unit\Plugin\views\field; + +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\node\Plugin\views\field\RevisionLink; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\node\Plugin\views\field\RevisionLink + * @group node + */ +class RevisionLinkTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Test the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new RevisionLink(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(AccessManagerInterface::class), $this->createMock(EntityTypeManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(LanguageManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/modules/user/src/Plugin/views/field/Permissions.php b/core/modules/user/src/Plugin/views/field/Permissions.php index 705ba7a589241ff87f42914fa2e25c61b701fc72..c9b29a6e2d5b418f3fac3ae7ed481869e117a0a2 100644 --- a/core/modules/user/src/Plugin/views/field/Permissions.php +++ b/core/modules/user/src/Plugin/views/field/Permissions.php @@ -87,11 +87,14 @@ public function preRender(&$values) { $rids = []; foreach ($values as $result) { - $user_rids = $this->getEntity($result)->getRoles(); - $uid = $this->getValue($result); + $user = $this->getEntity($result); + if ($user) { + $user_rids = $user->getRoles(); + $uid = $this->getValue($result); - foreach ($user_rids as $rid) { - $rids[$rid][] = $uid; + foreach ($user_rids as $rid) { + $rids[$rid][] = $uid; + } } } diff --git a/core/modules/user/tests/src/Unit/Plugin/views/field/PermissionsTest.php b/core/modules/user/tests/src/Unit/Plugin/views/field/PermissionsTest.php new file mode 100644 index 0000000000000000000000000000000000000000..0beff242bee5f8ae42487bc24b81ee36b06cfea4 --- /dev/null +++ b/core/modules/user/tests/src/Unit/Plugin/views/field/PermissionsTest.php @@ -0,0 +1,51 @@ +<?php + +namespace Drupal\Tests\user\Unit\Plugin\views\field; + +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Extension\ModuleHandlerInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\user\PermissionHandlerInterface; +use Drupal\user\Plugin\views\field\Permissions; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\user\Plugin\views\field\Permissions + * @group user + */ +class PermissionsTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + $container->set('user.permissions', $this->createMock(PermissionHandlerInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Tests the preRender method when getEntity returns NULL. + * + * @covers ::preRender + */ + public function testPreRenderNullEntity(): void { + $values = [new ResultRow()]; + $field = new Permissions(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(ModuleHandlerInterface::class), $this->createMock(EntityTypeManagerInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $field->preRender($values); + $this->assertEmpty($field->items); + } + +} diff --git a/core/modules/views/src/Plugin/views/field/BulkForm.php b/core/modules/views/src/Plugin/views/field/BulkForm.php index 04c58e46acf04c36fbb4734f1d20541ba1145ee3..53d59125d0ed50cb9e0943490c9ec9a3eed08cec 100644 --- a/core/modules/views/src/Plugin/views/field/BulkForm.php +++ b/core/modules/views/src/Plugin/views/field/BulkForm.php @@ -288,17 +288,23 @@ public function viewsForm(&$form, FormStateInterface $form_state) { // Render checkboxes for all rows. $form[$this->options['id']]['#tree'] = TRUE; foreach ($this->view->result as $row_index => $row) { - $entity = $this->getEntityTranslation($this->getEntity($row), $row); - - $form[$this->options['id']][$row_index] = [ - '#type' => 'checkbox', - // We are not able to determine a main "title" for each row, so we can - // only output a generic label. - '#title' => $this->t('Update this item'), - '#title_display' => 'invisible', - '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL, - '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision), - ]; + $entity = $this->getEntity($row); + if ($entity !== NULL) { + $entity = $this->getEntityTranslation($entity, $row); + + $form[$this->options['id']][$row_index] = [ + '#type' => 'checkbox', + // We are not able to determine a main "title" for each row, so we + // can only output a generic label. + '#title' => $this->t('Update this item'), + '#title_display' => 'invisible', + '#default_value' => !empty($form_state->getValue($this->options['id'])[$row_index]) ? 1 : NULL, + '#return_value' => $this->calculateEntityBulkFormKey($entity, $use_revision), + ]; + } + else { + $form[$this->options['id']][$row_index] = []; + } } // Replace the form submit button label. diff --git a/core/modules/views/src/Plugin/views/field/EntityLink.php b/core/modules/views/src/Plugin/views/field/EntityLink.php index f7b2877a6c6dfc00619f4b428b1afb12b784c161..7e1d91a61df4cf31256ce9da104306b76b61b769 100644 --- a/core/modules/views/src/Plugin/views/field/EntityLink.php +++ b/core/modules/views/src/Plugin/views/field/EntityLink.php @@ -26,7 +26,8 @@ public function render(ResultRow $row) { */ protected function renderLink(ResultRow $row) { if ($this->options['output_url_as_text']) { - return $this->getUrlInfo($row)->toString(); + $url_info = $this->getUrlInfo($row); + return $url_info ? $url_info->toString() : ''; } return parent::renderLink($row); } @@ -37,6 +38,9 @@ protected function renderLink(ResultRow $row) { protected function getUrlInfo(ResultRow $row) { $template = $this->getEntityLinkTemplate(); $entity = $this->getEntity($row); + if ($entity === NULL) { + return NULL; + } if ($this->languageManager->isMultilingual()) { $entity = $this->getEntityTranslation($entity, $row); } diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php index 89dec8b702cd6a82d87b85d779031fc1ffc551e2..a4bb58f2d4d28687ad033c097b033fe537b99882 100644 --- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php +++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php @@ -420,12 +420,35 @@ public function elementWrapperClasses($row_index = NULL) { */ public function getEntity(ResultRow $values) { $relationship_id = $this->options['relationship']; + $entity = NULL; if ($relationship_id == 'none') { - return $values->_entity; + $entity = $values->_entity; } elseif (isset($values->_relationship_entities[$relationship_id])) { - return $values->_relationship_entities[$relationship_id]; + $entity = $values->_relationship_entities[$relationship_id]; } + + if ($entity === NULL) { + // Don't log an error if we're getting an entity for an optional + // relationship. + if ($relationship_id !== 'none') { + $relationship = $this->view->relationship[$relationship_id] ?? NULL; + if ($relationship && !$relationship->options['required']) { + return NULL; + } + } + \Drupal::logger('views')->error( + 'The view %id failed to load an entity of type %entity_type at row %index for field %field', + [ + '%id' => $this->view->id(), + '%entity_type' => $this->configuration['entity_type'], + '%index' => $values->index, + '%field' => $this->label() ?: $this->realField, + ] + ); + return NULL; + } + return $entity; } /** diff --git a/core/modules/views/src/Plugin/views/field/LinkBase.php b/core/modules/views/src/Plugin/views/field/LinkBase.php index ed8e0ca10b61cc95abba80b79792df24eab48568..068aa076b3a5d483afe075b60cb94513f52dc781 100644 --- a/core/modules/views/src/Plugin/views/field/LinkBase.php +++ b/core/modules/views/src/Plugin/views/field/LinkBase.php @@ -164,9 +164,12 @@ public function query() { */ public function render(ResultRow $row) { $access = $this->checkUrlAccess($row); - $build = ['#markup' => $access->isAllowed() ? $this->renderLink($row) : '']; - BubbleableMetadata::createFromObject($access)->applyTo($build); - return $build; + if ($access) { + $build = ['#markup' => $access->isAllowed() ? $this->renderLink($row) : '']; + BubbleableMetadata::createFromObject($access)->applyTo($build); + return $build; + } + return ''; } /** @@ -175,12 +178,13 @@ public function render(ResultRow $row) { * @param \Drupal\views\ResultRow $row * A view result row. * - * @return \Drupal\Core\Access\AccessResultInterface - * The access result. + * @return \Drupal\Core\Access\AccessResultInterface|null + * The access result, or NULL if the URI elements of the link doesn't exist. */ protected function checkUrlAccess(ResultRow $row) { - $url = $this->getUrlInfo($row); - return $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $this->currentUser(), TRUE); + if ($url = $this->getUrlInfo($row)) { + return $this->accessManager->checkNamedRoute($url->getRouteName(), $url->getRouteParameters(), $this->currentUser(), TRUE); + } } /** @@ -189,7 +193,7 @@ protected function checkUrlAccess(ResultRow $row) { * @param \Drupal\views\ResultRow $row * A view result row. * - * @return \Drupal\Core\Url + * @return \Drupal\Core\Url|null * The URI elements of the link. */ abstract protected function getUrlInfo(ResultRow $row); @@ -219,7 +223,7 @@ protected function renderLink(ResultRow $row) { */ protected function addLangcode(ResultRow $row) { $entity = $this->getEntity($row); - if ($this->languageManager->isMultilingual()) { + if ($entity && $this->languageManager->isMultilingual()) { $this->options['alter']['language'] = $this->getEntityTranslation($entity, $row)->language(); } } diff --git a/core/modules/views/src/Plugin/views/field/RenderedEntity.php b/core/modules/views/src/Plugin/views/field/RenderedEntity.php index dce5efcd219d3a445f3bae2beb72980fae228d42..17f6c373f930063b0e54a5a7e632871fe6b0cd24 100644 --- a/core/modules/views/src/Plugin/views/field/RenderedEntity.php +++ b/core/modules/views/src/Plugin/views/field/RenderedEntity.php @@ -129,15 +129,17 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { * {@inheritdoc} */ public function render(ResultRow $values) { + $entity = $this->getEntity($values); + if ($entity === NULL) { + return ''; + } $entity = $this->getEntityTranslation($this->getEntity($values), $values); $build = []; - if (isset($entity)) { - $access = $entity->access('view', NULL, TRUE); - $build['#access'] = $access; - if ($access->isAllowed()) { - $view_builder = $this->entityTypeManager->getViewBuilder($this->getEntityTypeId()); - $build += $view_builder->view($entity, $this->options['view_mode'], $entity->language()->getId()); - } + $access = $entity->access('view', NULL, TRUE); + $build['#access'] = $access; + if ($access->isAllowed()) { + $view_builder = $this->entityTypeManager->getViewBuilder($this->getEntityTypeId()); + $build += $view_builder->view($entity, $this->options['view_mode'], $entity->language()->getId()); } return $build; } diff --git a/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity_null.yml b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity_null.yml new file mode 100644 index 0000000000000000000000000000000000000000..47e066debc0221ed2150cf9facc7a448fc4177bf --- /dev/null +++ b/core/modules/views/tests/modules/views_test_config/test_views/views.view.test_field_get_entity_null.yml @@ -0,0 +1,202 @@ +langcode: en +status: true +dependencies: + module: + - node + - user +id: test_field_get_entity_null +label: test_field_get_entity_null +module: views +description: '' +tag: '' +base_table: node_field_data +base_field: nid +display: + default: + id: default + display_title: Default + display_plugin: default + position: 0 + display_options: + fields: + title: + id: title + table: node_field_data + field: title + relationship: none + group_type: group + admin_label: '' + entity_type: node + entity_field: title + plugin_id: field + label: '' + exclude: false + alter: + alter_text: false + make_link: false + absolute: false + word_boundary: false + ellipsis: false + strip_tags: false + trim: false + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: true + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: string + settings: + link_to_entity: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + nid: + id: nid + table: node_field_data + field: nid + relationship: field_test_reference + group_type: group + admin_label: '' + entity_type: node + entity_field: nid + plugin_id: field + label: '' + exclude: false + alter: + alter_text: false + text: '' + make_link: false + path: '' + absolute: false + external: false + replace_spaces: false + path_case: none + trim_whitespace: false + alt: '' + rel: '' + link_class: '' + prefix: '' + suffix: '' + target: '' + nl2br: false + max_length: 0 + word_boundary: true + ellipsis: true + more_link: false + more_link_text: '' + more_link_path: '' + strip_tags: false + trim: false + preserve_tags: '' + html: false + element_type: '' + element_class: '' + element_label_type: '' + element_label_class: '' + element_label_colon: false + element_wrapper_type: '' + element_wrapper_class: '' + element_default_classes: true + empty: '' + hide_empty: false + empty_zero: false + hide_alter_empty: true + click_sort_column: value + type: number_integer + settings: + thousand_separator: '' + prefix_suffix: true + group_column: value + group_columns: { } + group_rows: true + delta_limit: 0 + delta_offset: 0 + delta_reversed: false + delta_first_last: false + multi_type: separator + separator: ', ' + field_api_classes: false + pager: + type: none + options: + offset: 0 + exposed_form: + type: basic + options: + submit_button: Apply + reset_button: false + reset_button_label: Reset + exposed_sorts_label: 'Sort by' + expose_sort_order: true + sort_asc_label: Asc + sort_desc_label: Desc + access: + type: perm + options: + perm: 'access content' + cache: + type: tag + options: { } + empty: { } + sorts: { } + arguments: { } + filters: { } + style: + type: default + options: + grouping: { } + row_class: '' + default_row_class: true + uses_fields: false + row: + type: fields + options: + default_field_elements: true + inline: { } + separator: '' + hide_empty: false + query: + type: views_query + options: + query_comment: '' + disable_sql_rewrite: false + distinct: false + replica: false + query_tags: { } + relationships: + field_test_reference: + id: field_test_reference + table: node__field_test_reference + field: field_test_reference + relationship: none + group_type: group + admin_label: 'field_test_reference: Content' + plugin_id: standard + required: false + header: { } + footer: { } + display_extenders: { } + cache_metadata: + max-age: -1 + contexts: + - 'languages:language_content' + - 'languages:language_interface' + - 'user.node_grants:view' + - user.permissions + tags: { } diff --git a/core/modules/views/tests/src/Functional/Entity/FieldEntityTest.php b/core/modules/views/tests/src/Functional/Entity/FieldEntityTest.php index 5385121ea42fef32baf9a44a7208e1d23e085423..1489884840a32236a6e25d1ab0af2b0654c0f0eb 100644 --- a/core/modules/views/tests/src/Functional/Entity/FieldEntityTest.php +++ b/core/modules/views/tests/src/Functional/Entity/FieldEntityTest.php @@ -3,7 +3,11 @@ namespace Drupal\Tests\views\Functional\Entity; use Drupal\comment\Tests\CommentTestTrait; +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Drupal\field\Entity\FieldConfig; +use Drupal\field\Entity\FieldStorageConfig; use Drupal\node\Entity\Node; +use Drupal\node\NodeInterface; use Drupal\user\Entity\User; use Drupal\Tests\views\Functional\ViewTestBase; use Drupal\views\Tests\ViewTestData; @@ -24,7 +28,10 @@ class FieldEntityTest extends ViewTestBase { * * @var array */ - public static $testViews = ['test_field_get_entity']; + public static $testViews = [ + 'test_field_get_entity', + 'test_field_get_entity_null', + ]; /** * Modules to enable. @@ -47,6 +54,30 @@ protected function setUp($import_test_views = TRUE, $modules = ['views_test_conf $this->drupalCreateContentType(['type' => 'page']); $this->addDefaultCommentField('node', 'page'); + // Add an entity reference field for the test_field_get_entity_null view. + FieldStorageConfig::create([ + 'field_name' => 'field_test_reference', + 'type' => 'entity_reference', + 'entity_type' => 'node', + 'cardinality' => 1, + 'settings' => [ + 'target_type' => 'node', + ], + ])->save(); + FieldConfig::create([ + 'field_name' => 'field_test_reference', + 'entity_type' => 'node', + 'bundle' => 'page', + 'label' => 'field_test_reference', + 'settings' => [ + 'handler' => 'default', + 'handler_settings' => [ + 'target_bundles' => [ + 'page' => 'page', + ], + ], + ], + ])->save(); ViewTestData::createTestViews(static::class, $modules); } @@ -92,4 +123,51 @@ public function testGetEntity() { $this->assertEquals($account->id(), $entity->id(), 'Make sure the right user entity got loaded.'); } + /** + * Tests the getEntity method returning NULL for an optional relationship. + */ + public function testGetEntityNullEntityOptionalRelationship(): void { + $nodeReference = Node::create([ + 'type' => 'page', + 'title' => $this->randomString(), + 'status' => NodeInterface::PUBLISHED, + ]); + $nodeReference->save(); + $node = Node::create([ + 'type' => 'page', + 'title' => $this->randomString(), + 'status' => NodeInterface::PUBLISHED, + 'field_test_reference' => [ + 'target_id' => $nodeReference->id(), + ], + ]); + $node->save(); + + $this->drupalLogin($this->drupalCreateUser(['access content'])); + $view = Views::getView('test_field_get_entity_null'); + $this->executeView($view); + // Second row will be $node. + $row = $view->result[1]; + + $entity = $view->field['nid']->getEntity($row); + $this->assertEquals($nodeReference->id(), $entity->id()); + + // Tests optional relationships with NULL entities don't log an error. + $nodeReference->delete(); + + // Use a mock logger so we can check that no errors were logged. + $loggerFactory = $this->createMock(LoggerChannelFactoryInterface::class); + $loggerFactory->expects($this->never()) + ->method('get'); + $container = \Drupal::getContainer(); + $container->set('logger.factory', $loggerFactory); + \Drupal::setContainer($container); + + $view = Views::getView('test_field_get_entity_null'); + $this->executeView($view); + // First row will be $node since the other is now deleted. + $row = $view->result[0]; + $this->assertNull($view->field['nid']->getEntity($row)); + } + } diff --git a/core/modules/views/tests/src/Traits/ViewsLoggerTestTrait.php b/core/modules/views/tests/src/Traits/ViewsLoggerTestTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..bea149498d45d0f146f65a80af2dcf6095d00470 --- /dev/null +++ b/core/modules/views/tests/src/Traits/ViewsLoggerTestTrait.php @@ -0,0 +1,36 @@ +<?php + +namespace Drupal\Tests\views\Traits; + +use Drupal\Core\Logger\LoggerChannelFactoryInterface; +use Psr\Log\LoggerInterface; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * Provides helper functions for logging in views. + */ +trait ViewsLoggerTestTrait { + + /** + * Sets up a mock logger for when views can't load an entity. + */ + public function setUpMockLoggerWithMissingEntity(): void { + $loggerFactory = $this->createMock(LoggerChannelFactoryInterface::class); + $logger = $this->createMock(LoggerInterface::class); + $logger->expects($this->once()) + ->method('error') + ->with( + 'The view %id failed to load an entity of type %entity_type at row %index for field %field', + $this->anything(), + ); + + $loggerFactory->expects($this->once()) + ->method('get') + ->willReturn($logger); + + $container = new ContainerBuilder(); + $container->set('logger.factory', $loggerFactory); + \Drupal::setContainer($container); + } + +} diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/BulkFormTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/BulkFormTest.php new file mode 100644 index 0000000000000000000000000000000000000000..e7629346c6ac96d18354c00965af4b22b519279a --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/BulkFormTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Drupal\Tests\views\Unit\Plugin\views\field; + +use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\views\Plugin\views\field\BulkForm; +use Drupal\views\Plugin\views\query\QueryPluginBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +/** + * @coversDefaultClass \Drupal\views\Plugin\views\field\BulkForm + * @group Views + */ +class BulkFormTest extends UnitTestCase { + + /** + * {@inheritdoc} + */ + protected function tearDown(): void { + parent::tearDown(); + $container = new ContainerBuilder(); + \Drupal::setContainer($container); + } + + /** + * @covers ::viewsForm + */ + public function testViewsForm(): void { + $row = new ResultRow(); + + $container = new ContainerBuilder(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + \Drupal::setContainer($container); + + $field = $this->getMockBuilder(BulkForm::class) + ->onlyMethods(['getEntityType', 'getEntity']) + ->disableOriginalConstructor() + ->getMock(); + $field->expects($this->any()) + ->method('getEntityType') + ->willReturn('foo'); + $field->expects($this->any()) + ->method('getEntity') + ->willReturn(NULL); + + $query = $this->getMockBuilder(QueryPluginBase::class) + ->onlyMethods(['getEntityTableInfo']) + ->disableOriginalConstructor() + ->getMock(); + $query->expects($this->any()) + ->method('getEntityTableInfo') + ->willReturn([]); + $view = $this->getMockBuilder(ViewExecutable::class) + ->onlyMethods(['getQuery']) + ->disableOriginalConstructor() + ->getMock(); + $view->expects($this->any()) + ->method('getQuery') + ->willReturn($query); + $view->result = [$row]; + $view->query = $query; + $field->view = $view; + $field->options = ['id' => 'bar', 'action_title' => 'zee']; + $form_state = $this->createMock(FormStateInterface::class); + $form = []; + $field->viewsForm($form, $form_state); + $this->assertNotEmpty($form); + $this->assertIsArray($form[$field->options['id']][0]); + $this->assertEmpty($form[$field->options['id']][0]); + } + +} diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php index 160a87eeff59c7eea5e9d3a4a2164c27df4c1d76..f844955f8fd4c9740997e5681ac0dcdc014174a0 100644 --- a/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/EntityOperationsUnitTest.php @@ -5,6 +5,7 @@ use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; use Drupal\views\Plugin\views\field\EntityOperations; use Drupal\views\ResultRow; @@ -14,6 +15,8 @@ */ class EntityOperationsUnitTest extends UnitTestCase { + use ViewsLoggerTestTrait; + /** * The entity type manager. * @@ -52,7 +55,7 @@ protected function setUp(): void { $this->entityRepository = $this->createMock(EntityRepositoryInterface::class); $this->languageManager = $this->createMock('\Drupal\Core\Language\LanguageManagerInterface'); - $configuration = []; + $configuration = ['entity_type' => 'foo', 'entity field' => 'bar']; $plugin_id = $this->randomMachineName(); $plugin_definition = [ 'title' => $this->randomMachineName(), @@ -178,6 +181,8 @@ public function testRenderWithoutDestination() { * @covers ::render */ public function testRenderWithoutEntity() { + $this->setUpMockLoggerWithMissingEntity(); + $entity = NULL; $result = new ResultRow(); diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/LinkBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/LinkBaseTest.php new file mode 100644 index 0000000000000000000000000000000000000000..897a86ae3fc6f6f98f72736cc4e4308804981b65 --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/LinkBaseTest.php @@ -0,0 +1,76 @@ +<?php + +namespace Drupal\Tests\views\Unit\Plugin\views\field; + +use Drupal\Core\Access\AccessManagerInterface; +use Drupal\Core\Access\AccessResultAllowed; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Core\Render\RendererInterface; +use Drupal\Core\StringTranslation\TranslationInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\Plugin\views\field\LinkBase; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\views\Plugin\views\field\EntityLink + * @group Views + */ +class LinkBaseTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + $container = \Drupal::getContainer(); + $container->set('string_translation', $this->createMock(TranslationInterface::class)); + $container->set('renderer', $this->createMock(RendererInterface::class)); + \Drupal::setContainer($container); + } + + /** + * Tests the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + + $access = new AccessResultAllowed(); + $languageManager = $this->createMock(LanguageManagerInterface::class); + $languageManager->expects($this->any()) + ->method('isMultilingual') + ->willReturn(TRUE); + $field = $this->getMockBuilder(LinkBase::class) + ->setConstructorArgs([ + ['entity_type' => 'foo', 'entity field' => 'bar'], + 'foo', + [], + $this->createMock(AccessManagerInterface::class), + $this->createMock(EntityTypeManagerInterface::class), + $this->createMock(EntityRepositoryInterface::class), + $languageManager, + ]) + ->onlyMethods(['checkUrlAccess', 'getUrlInfo']) + ->getMock(); + $field->expects($this->any()) + ->method('checkUrlAccess') + ->willReturn($access); + + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + + $field->init($view, $display); + $field_built = $field->render($row); + $this->assertEquals('', \Drupal::service('renderer')->render($field_built)); + } + +} diff --git a/core/modules/views/tests/src/Unit/Plugin/views/field/RenderedEntityTest.php b/core/modules/views/tests/src/Unit/Plugin/views/field/RenderedEntityTest.php new file mode 100644 index 0000000000000000000000000000000000000000..8639db20f9ab116eafd518ee2cb5627c0e620a51 --- /dev/null +++ b/core/modules/views/tests/src/Unit/Plugin/views/field/RenderedEntityTest.php @@ -0,0 +1,46 @@ +<?php + +namespace Drupal\Tests\views\Unit\Plugin\views\field; + +use Drupal\Core\Entity\EntityDisplayRepositoryInterface; +use Drupal\Core\Entity\EntityRepositoryInterface; +use Drupal\Core\Entity\EntityTypeManagerInterface; +use Drupal\Core\Language\LanguageManagerInterface; +use Drupal\Tests\UnitTestCase; +use Drupal\Tests\views\Traits\ViewsLoggerTestTrait; +use Drupal\views\Plugin\views\display\DisplayPluginBase; +use Drupal\views\Plugin\views\field\RenderedEntity; +use Drupal\views\ResultRow; +use Drupal\views\ViewExecutable; + +/** + * @coversDefaultClass \Drupal\views\Plugin\views\field\RenderedEntity + * @group Views + */ +class RenderedEntityTest extends UnitTestCase { + + use ViewsLoggerTestTrait; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + $this->setUpMockLoggerWithMissingEntity(); + } + + /** + * Tests the render method when getEntity returns NULL. + * + * @covers ::render + */ + public function testRenderNullEntity(): void { + $row = new ResultRow(); + $field = new RenderedEntity(['entity_type' => 'foo', 'entity field' => 'bar'], '', [], $this->createMock(EntityTypeManagerInterface::class), $this->createMock(LanguageManagerInterface::class), $this->createMock(EntityRepositoryInterface::class), $this->createMock(EntityDisplayRepositoryInterface::class)); + $view = $this->createMock(ViewExecutable::class); + $display = $this->createMock(DisplayPluginBase::class); + $field->init($view, $display); + $this->assertEmpty($field->render($row)); + } + +} diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon index 81e3ab3fcf341ae66f3eabddfd25458aa83e28c8..b592d0f05b9855f3249120cf87135c7d99f9fd48 100644 --- a/core/phpstan-baseline.neon +++ b/core/phpstan-baseline.neon @@ -2560,11 +2560,6 @@ parameters: count: 1 path: modules/views/src/Plugin/views/field/PrerenderList.php - - - message: "#^Variable \\$entity in isset\\(\\) always exists and is not nullable\\.$#" - count: 1 - path: modules/views/src/Plugin/views/field/RenderedEntity.php - - message: "#^Variable \\$items might not be defined\\.$#" count: 1