From 529c793230d18bd8667e195ccab66231924a727c Mon Sep 17 00:00:00 2001
From: Lauri Eskola <lauri.eskola@acquia.com>
Date: Fri, 27 Oct 2023 14:20:51 +0300
Subject: [PATCH] Issue #3349394 by plopesc, fjgarlin, hestenet, penyaskito,
 friera: Provide announcement feed as a block

---
 .../announcements_feed.services.yml           |  4 +
 .../src/AnnounceRenderer.php                  | 84 +++++++++++++++++++
 .../src/Controller/AnnounceController.php     | 56 ++-----------
 .../src/Plugin/Block/AnnounceBlock.php        | 71 ++++++++++++++++
 .../AnnounceBlockTest.php                     | 70 ++++++++++++++++
 .../tests/src/Kernel/AnnounceRendererTest.php | 66 +++++++++++++++
 6 files changed, 301 insertions(+), 50 deletions(-)
 create mode 100644 core/modules/announcements_feed/src/AnnounceRenderer.php
 create mode 100644 core/modules/announcements_feed/src/Plugin/Block/AnnounceBlock.php
 create mode 100644 core/modules/announcements_feed/tests/src/FunctionalJavascript/AnnounceBlockTest.php
 create mode 100644 core/modules/announcements_feed/tests/src/Kernel/AnnounceRendererTest.php

diff --git a/core/modules/announcements_feed/announcements_feed.services.yml b/core/modules/announcements_feed/announcements_feed.services.yml
index d6cc2d80198d..98e2ce7e3e8b 100644
--- a/core/modules/announcements_feed/announcements_feed.services.yml
+++ b/core/modules/announcements_feed/announcements_feed.services.yml
@@ -15,3 +15,7 @@ services:
     class: Drupal\announcements_feed\LazyBuilders
     arguments: [ '@plugin.manager.element_info']
   Drupal\announcements_feed\LazyBuilders: '@announcements_feed.lazy_builders'
+  announcements_feed.renderer:
+    class: Drupal\announcements_feed\AnnounceRenderer
+    arguments: ['@announcements_feed.fetcher', '%announcements_feed.feed_link%']
+  Drupal\announcements_feed\AnnounceRenderer: '@announcements_feed.renderer'
diff --git a/core/modules/announcements_feed/src/AnnounceRenderer.php b/core/modules/announcements_feed/src/AnnounceRenderer.php
new file mode 100644
index 000000000000..ec19d5e9844c
--- /dev/null
+++ b/core/modules/announcements_feed/src/AnnounceRenderer.php
@@ -0,0 +1,84 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\announcements_feed;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Service to render announcements from the external feed.
+ *
+ * @internal
+ */
+final class AnnounceRenderer {
+
+  use StringTranslationTrait;
+
+  /**
+   * Constructs an AnnouncementRenderer object.
+   *
+   * @param \Drupal\announcements_feed\AnnounceFetcher $announceFetcher
+   *   The AnnounceFetcher service.
+   * @param string $feedLink
+   *   The feed url path.
+   */
+  public function __construct(
+    protected AnnounceFetcher $announceFetcher,
+    protected string $feedLink
+  ) {
+  }
+
+  /**
+   * Generates the announcements feed render array.
+   *
+   * @return array
+   *   Render array containing the announcements feed.
+   */
+  public function render(): array {
+    try {
+      $announcements = $this->announceFetcher->fetch();
+    }
+    catch (\Exception $e) {
+      return [
+        '#theme' => 'status_messages',
+        '#message_list' => [
+          'error' => [
+            $this->t('An error occurred while parsing the announcements feed, check the logs for more information.'),
+          ],
+        ],
+        '#status_headings' => [
+          'error' => $this->t('Error Message'),
+        ],
+      ];
+    }
+
+    $build = [];
+    foreach ($announcements as $announcement) {
+      $key = $announcement->featured ? '#featured' : '#standard';
+      $build[$key][] = $announcement;
+    }
+
+    $build += [
+      '#theme' => 'announcements_feed',
+      '#count' => count($announcements),
+      '#feed_link' => $this->feedLink,
+      '#cache' => [
+        'contexts' => [
+          'url.query_args:_wrapper_format',
+        ],
+        'tags' => [
+          'announcements_feed:feed',
+        ],
+      ],
+      '#attached' => [
+        'library' => [
+          'announcements_feed/drupal.announcements_feed.dialog',
+        ],
+      ],
+    ];
+
+    return $build;
+  }
+
+}
diff --git a/core/modules/announcements_feed/src/Controller/AnnounceController.php b/core/modules/announcements_feed/src/Controller/AnnounceController.php
index 816cae36efc1..0776ae3fc615 100644
--- a/core/modules/announcements_feed/src/Controller/AnnounceController.php
+++ b/core/modules/announcements_feed/src/Controller/AnnounceController.php
@@ -4,9 +4,9 @@
 
 namespace Drupal\announcements_feed\Controller;
 
+use Drupal\announcements_feed\AnnounceRenderer;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
-use Drupal\announcements_feed\AnnounceFetcher;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -20,14 +20,11 @@ class AnnounceController extends ControllerBase implements ContainerInjectionInt
   /**
    * Constructs an AnnounceController object.
    *
-   * @param \Drupal\announcements_feed\AnnounceFetcher $announceFetcher
-   *   The AnnounceFetcher service.
-   * @param string $feedLink
-   *   The feed url path.
+   * @param \Drupal\announcements_feed\AnnounceRenderer $announceRenderer
+   *   The AnnounceRenderer service.
    */
   public function __construct(
-    protected AnnounceFetcher $announceFetcher,
-    protected string $feedLink
+    protected AnnounceRenderer $announceRenderer,
   ) {
   }
 
@@ -36,8 +33,7 @@ public function __construct(
    */
   public static function create(ContainerInterface $container): AnnounceController {
     return new static(
-      $container->get('announcements_feed.fetcher'),
-      $container->getParameter('announcements_feed.feed_link')
+      $container->get('announcements_feed.renderer'),
     );
   }
 
@@ -51,47 +47,7 @@ public static function create(ContainerInterface $container): AnnounceController
    *   A build array with announcements.
    */
   public function getAnnouncements(Request $request): array {
-    try {
-      $announcements = $this->announceFetcher->fetch();
-    }
-    catch (\Exception $e) {
-      return [
-        '#theme' => 'status_messages',
-        '#message_list' => [
-          'error' => [
-            $this->t('An error occurred while parsing the announcements feed, check the logs for more information.'),
-          ],
-        ],
-        '#status_headings' => [
-          'error' => $this->t('Error Message'),
-        ],
-      ];
-    }
-
-    $build = [];
-    foreach ($announcements as $announcement) {
-      $key = $announcement->featured ? '#featured' : '#standard';
-      $build[$key][] = $announcement;
-    }
-
-    $build += [
-      '#theme' => 'announcements_feed',
-      '#count' => count($announcements),
-      '#feed_link' => $this->feedLink,
-      '#cache' => [
-        'contexts' => [
-          'url.query_args:_wrapper_format',
-        ],
-        'tags' => [
-          'announcements_feed:feed',
-        ],
-      ],
-      '#attached' => [
-        'library' => [
-          'announcements_feed/drupal.announcements_feed.dialog',
-        ],
-      ],
-    ];
+    $build = $this->announceRenderer->render();
     if ($request->query->get('_wrapper_format') != 'drupal_dialog.off_canvas') {
       $build['#theme'] = 'announcements_feed_admin';
       $build['#attached'] = [];
diff --git a/core/modules/announcements_feed/src/Plugin/Block/AnnounceBlock.php b/core/modules/announcements_feed/src/Plugin/Block/AnnounceBlock.php
new file mode 100644
index 000000000000..5540eac9ac75
--- /dev/null
+++ b/core/modules/announcements_feed/src/Plugin/Block/AnnounceBlock.php
@@ -0,0 +1,71 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\announcements_feed\Plugin\Block;
+
+use Drupal\announcements_feed\AnnounceRenderer;
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Block\BlockBase;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Session\AccountInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides an 'Announcements Feed' block.
+ *
+ * @Block(
+ *   id = "announce_block",
+ *   admin_label = @Translation("Announcements Feed"),
+ * )
+ *
+ * @internal
+ */
+class AnnounceBlock extends BlockBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * Constructs a new AnnouncementsFeedBlock instance.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\announcements_feed\AnnounceRenderer $announceRenderer
+   *   The AnnounceRenderer service.
+   * @param \Drupal\Core\Session\AccountInterface $currentUser
+   *   The current user.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, protected AnnounceRenderer $announceRenderer, protected AccountInterface $currentUser) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('announcements_feed.renderer'),
+      $container->get('current_user')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function access(AccountInterface $account, $return_as_object = FALSE) {
+    return AccessResult::allowedIfHasPermission($this->currentUser, 'access announcements');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    return $this->announceRenderer->render();
+  }
+
+}
diff --git a/core/modules/announcements_feed/tests/src/FunctionalJavascript/AnnounceBlockTest.php b/core/modules/announcements_feed/tests/src/FunctionalJavascript/AnnounceBlockTest.php
new file mode 100644
index 000000000000..d04974112820
--- /dev/null
+++ b/core/modules/announcements_feed/tests/src/FunctionalJavascript/AnnounceBlockTest.php
@@ -0,0 +1,70 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\announcements_feed\FunctionalJavascript;
+
+use Drupal\announce_feed_test\AnnounceTestHttpClientMiddleware;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+
+/**
+ * Test the announcement block test visibility.
+ *
+ * @group announcements_feed
+ */
+class AnnounceBlockTest extends WebDriverTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'announcements_feed',
+    'block',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * The announce block instance.
+   *
+   * @var \Drupal\block\Entity\Block
+   */
+  protected $announceBlock;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+    AnnounceTestHttpClientMiddleware::setAnnounceTestEndpoint('/announce-feed-json/community-feeds');
+    $this->announceBlock = $this->placeBlock('announce_block', [
+      'label' => 'Announcements Feed',
+    ]);
+  }
+
+  /**
+   * Testing announce feed block visibility.
+   */
+  public function testAnnounceWithoutPermission() {
+    // User with "access announcements" permission.
+    $account = $this->drupalCreateUser([
+      'access announcements',
+    ]);
+    $this->drupalLogin($account);
+    $this->drupalGet('<front>');
+
+    $assert_session = $this->assertSession();
+
+    // Block should be visible for the user.
+    $assert_session->pageTextContains('Announcements Feed');
+
+    // Block is not accessible without permission.
+    $this->drupalLogout();
+    $assert_session->pageTextNotContains('Announcements Feed');
+
+  }
+
+}
diff --git a/core/modules/announcements_feed/tests/src/Kernel/AnnounceRendererTest.php b/core/modules/announcements_feed/tests/src/Kernel/AnnounceRendererTest.php
new file mode 100644
index 000000000000..d91eebca43da
--- /dev/null
+++ b/core/modules/announcements_feed/tests/src/Kernel/AnnounceRendererTest.php
@@ -0,0 +1,66 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\announcements_feed\Kernel;
+
+use GuzzleHttp\Psr7\Response;
+
+/**
+ * @coversDefaultClass \Drupal\announcements_feed\AnnounceRenderer
+ *
+ * @group announcements_feed
+ */
+class AnnounceRendererTest extends AnnounceTestBase {
+
+  /**
+   * Tests rendered valid when something goes wrong.
+   */
+  public function testRendererException() {
+    $this->setTestFeedResponses([
+      new Response(403),
+    ]);
+    $render = $this->container->get('announcements_feed.renderer')->render();
+    $this->assertEquals('status_messages', $render['#theme']);
+    $this->assertEquals('An error occurred while parsing the announcements feed, check the logs for more information.', $render['#message_list']['error'][0]);
+  }
+
+  /**
+   * Tests rendered valid content.
+   */
+  public function testRendererContent() {
+    $feed_item_1 = [
+      'id' => '1001',
+      'content_html' => 'Test teaser 1',
+      'url' => 'https://www.drupal.org/project/announce',
+      '_drupalorg' => [
+        'featured' => TRUE,
+        'version' => '^10||^11',
+      ],
+      'date_modified' => "2021-09-02T15:09:42+00:00",
+      'date_published' => "2021-09-01T15:09:42+00:00",
+    ];
+    $feed_item_2 = [
+      'id' => '1002',
+      'content_html' => 'Test teaser 1',
+      'url' => 'https://www.drupal.org/project/announce',
+      '_drupalorg' => [
+        'featured' => FALSE,
+        'version' => '^10||^11',
+      ],
+      'date_modified' => "2021-09-02T15:09:42+00:00",
+      'date_published' => "2021-09-01T15:09:42+00:00",
+    ];
+    $this->setFeedItems([$feed_item_1, $feed_item_2]);
+    $render = $this->container->get('announcements_feed.renderer')->render();
+    $this->assertEquals('announcements_feed', $render['#theme']);
+    $this->assertEquals(1, $render['#count']);
+    $this->assertEquals(1001, $render['#featured'][0]->id);
+
+    $render = $this->container->get('announcements_feed.renderer')->render();
+    $this->assertEquals('announcements_feed', $render['#theme']);
+    $this->assertEquals(1, $render['#count']);
+    $this->assertEquals(1002, $render['#standard'][0]->id);
+  }
+
+}
-- 
GitLab