diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 958da555..9c2aa911 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -9,207 +9,9 @@ on: - '1.x' workflow_dispatch: -env: - LOCALGOV_DRUPAL_PROJECT: localgovdrupal/localgov_alert_banner - LOCALGOV_DRUPAL_PROJECT_PATH: web/modules/contrib/localgov_alert_banner - jobs: - - build: - name: Install LocalGov Drupal - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Save git branch and git repo names to env if this is not a pull request - if: github.event_name != 'pull_request' - run: | - echo "GIT_BASE=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "GIT_BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "HEAD_USER=localgovdrupal" >> $GITHUB_ENV - - - name: Save git branch and git repo names to env if this is a pull request - if: github.event_name == 'pull_request' - run: | - echo "GIT_BASE=${GITHUB_BASE_REF}" >> $GITHUB_ENV - echo "GIT_BRANCH=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - export HEAD="${{ github.event.pull_request.head.label }}" - echo "HEAD_USER=${HEAD%%:*}" >> $GITHUB_ENV - - - name: Set composer branch reference for version branches - if: endsWith(github.ref, '.x') - run: echo "COMPOSER_REF=${GIT_BRANCH}-dev" >> $GITHUB_ENV - - - name: Set composer branch reference for non-version branches - if: endsWith(github.ref, '.x') == false - run: echo "COMPOSER_REF=dev-${GIT_BRANCH}" >> $GITHUB_ENV - - - name: Get the latest tagged release for branch version - run: | - LATEST_RELEASE=$(curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/${GITHUB_REPOSITORY}/git/matching-refs/tags/${GIT_BASE%'.x'} | grep -Po '(?<=refs/tags/)[^"]+' | tail -1) - if [ -z $LATEST_RELEASE ]; then LATEST_RELEASE=1; fi - echo "LATEST_RELEASE=${LATEST_RELEASE}" >> $GITHUB_ENV - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Clone drupal_container - uses: actions/checkout@v2 - with: - repository: localgovdrupal/drupal-container - ref: php${{ matrix.php-version }} - - - name: Create LocalGov Drupal project - run: | - composer create-project --stability dev --no-install localgovdrupal/localgov-project ./html "${{ matrix.localgov-version }}" - composer --working-dir=./html require --no-install localgovdrupal/localgov:${{ matrix.localgov-version }}-dev - composer --working-dir=./html require --no-install drupal/core-recommended:${{ matrix.drupal-version }} drupal/core-composer-scaffold:${{ matrix.drupal-version }} drupal/core-project-message:${{ matrix.drupal-version }} drupal/core-dev:${{ matrix.drupal-version }} - composer --working-dir=./html install - - - name: Obtain the test target using Composer - if: env.HEAD_USER == 'localgovdrupal' - run: | - composer --working-dir=html config repositories.1 vcs git@github.com:${LOCALGOV_DRUPAL_PROJECT}.git - composer global config github-oauth.github.com ${{ github.token }} - composer --working-dir=./html require --with-all-dependencies ${LOCALGOV_DRUPAL_PROJECT}:"${COMPOSER_REF} as ${LATEST_RELEASE}" - - - name: Obtain the test target using Git - if: env.HEAD_USER != 'localgovdrupal' - uses: actions/checkout@v2 - with: - path: ${{ env.LOCALGOV_DRUPAL_PROJECT_PATH }} - - - name: Obtain suggested dependencies - run: composer --working-dir=./html/${LOCALGOV_DRUPAL_PROJECT_PATH} suggests --list | xargs composer --working-dir=./html require - - phpcs: - name: Coding standards checks - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Run coding standards checks - run: | - cd html - ./bin/phpcs -p ${LOCALGOV_DRUPAL_PROJECT_PATH} - - phpstan: - name: Deprecated code checks - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - - - name: Run deprecated code checks - run: | - cd html - ./bin/phpstan analyse -c ./phpstan.neon ${LOCALGOV_DRUPAL_PROJECT_PATH} - phpunit: - name: PHPUnit tests - needs: build - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - localgov-version: - - '3.x' - drupal-version: - - '~10.0' - php-version: - - '8.1' - - '8.2' - - steps: - - - name: Clone Drupal container - uses: actions/checkout@v2 - with: - repository: localgovdrupal/drupal-container - ref: php${{ matrix.php-version }} - - - name: Cached workspace - uses: actions/cache@v2 - with: - path: ./html - key: localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}-${{ github.run_id }}-${{ secrets.CACHE_VERSION }} - restore-keys: | - localgov-build-${{ matrix.localgov-version }}-${{ matrix.drupal-version }}-${{ matrix.php-version }}- - - - name: Start Docker environment - run: docker-compose -f docker-compose.yml up -d - - - name: Run PHPUnit tests - run: | - mkdir -p ./html/web/sites/simpletest && chmod 777 ./html/web/sites/simpletest - sed -i "s#http://localgov.lndo.site#http://drupal#" ./html/phpunit.xml.dist - docker exec -t drupal bash -c 'chown docker:docker -R /var/www/html' - docker exec -u docker -t drupal bash -c "cd /var/www/html && ./bin/paratest --processes=4 /var/www/html/${{ env.LOCALGOV_DRUPAL_PROJECT_PATH }}" + tests: + uses: localgovdrupal/localgov_shared_workflows/.github/workflows/test-module.yml@1.x + with: + project: 'localgovdrupal/localgov_alert_banner' + project_path: 'web/modules/contrib/localgov_alert_banner' diff --git a/src/Entity/AlertBannerEntity.php b/src/Entity/AlertBannerEntity.php index 1fbab7e3..bc1a06fe 100644 --- a/src/Entity/AlertBannerEntity.php +++ b/src/Entity/AlertBannerEntity.php @@ -314,6 +314,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) { ->setLabel(t('Title')) ->setDescription(t('The title of the Alert banner.')) ->setRevisionable(TRUE) + ->setTranslatable(TRUE) ->setSettings([ 'max_length' => 50, 'text_processing' => 0, diff --git a/src/Plugin/Block/AlertBannerBlock.php b/src/Plugin/Block/AlertBannerBlock.php index 248e3e98..5e62de6c 100644 --- a/src/Plugin/Block/AlertBannerBlock.php +++ b/src/Plugin/Block/AlertBannerBlock.php @@ -4,6 +4,7 @@ use Drupal\Core\Block\BlockBase; use Drupal\Core\Cache\Cache; +use Drupal\Core\Entity\EntityRepositoryInterface; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; @@ -37,11 +38,11 @@ class AlertBannerBlock extends BlockBase implements ContainerFactoryPluginInterf protected $currentUser; /** - * Current alert banners. + * The entity repository services. * - * @var \Drupal\localgov_alert_banner\Entity\AlertBannerEntity[] + * @var \Drupal\Core\Entity\EntityRepositoryInterface */ - protected $currentAlertBanners = []; + protected $entityRepository; /** * Constructs a new AlertBannerBlock. @@ -56,12 +57,14 @@ class AlertBannerBlock extends BlockBase implements ContainerFactoryPluginInterf * The entity type manager service. * @param \Drupal\Core\Session\AccountProxyInterface $current_user * Current user service. + * @param Drupal\Core\Entity\EntityRepositoryInterface $entity_repository + * The entity repository service. */ - public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user) { + public function __construct(array $configuration, $plugin_id, $plugin_definition, EntityTypeManagerInterface $entity_type_manager, AccountProxyInterface $current_user, EntityRepositoryInterface $entity_repository) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->entityTypeManager = $entity_type_manager; $this->currentUser = $current_user; - $this->currentAlertBanners = $this->getCurrentAlertBanners(); + $this->entityRepository = $entity_repository; } /** @@ -84,7 +87,8 @@ public static function create(ContainerInterface $container, array $configuratio $plugin_id, $plugin_definition, $container->get('entity_type.manager'), - $container->get('current_user') + $container->get('current_user'), + $container->get('entity.repository'), ); } @@ -131,6 +135,7 @@ public function blockSubmit($form, FormStateInterface $form_state) { * {@inheritdoc} */ public function build() { + // Fetch the current published banner. $published_alert_banners = $this->getCurrentAlertBanners(); @@ -141,7 +146,9 @@ public function build() { // Render the alert banner. $build = []; - foreach ($this->currentAlertBanners as $alert_banner) { + $contexts = []; + foreach ($published_alert_banners as $alert_banner) { + $contexts = Cache::mergeContexts($contexts, $alert_banner->getCacheContexts()); // Only add to the build if it is visible. // @see #154. @@ -150,6 +157,7 @@ public function build() { ->view($alert_banner); } } + $build['#cache']['contexts'] = $contexts; return $build; } @@ -163,7 +171,8 @@ public function build() { * Array of all published and visible alert banners. */ protected function getCurrentAlertBanners() { - $alert_banners = []; + + $current_alert_banners = []; // Get list of published alert banner IDs. $types = $this->mapTypesConfigToQuery(); @@ -194,13 +203,15 @@ protected function getCurrentAlertBanners() { // Visibility check happens in build, so we get cache contexts on all. foreach ($published_alert_banners as $alert_banner_id) { $alert_banner = $this->entityTypeManager->getStorage('localgov_alert_banner')->load($alert_banner_id); + $alert_banner = $this->entityRepository->getTranslationFromContext($alert_banner); + $is_accessible = $alert_banner->access('view', $this->currentUser); if ($is_accessible) { - $alert_banners[] = $alert_banner; + $current_alert_banners[] = $alert_banner; } } - return $alert_banners; + return $current_alert_banners; } /** @@ -216,17 +227,6 @@ protected function mapTypesConfigToQuery() : array { }); } - /** - * {@inheritdoc} - */ - public function getCacheContexts() { - $contexts = []; - foreach ($this->currentAlertBanners as $alert_banner) { - $contexts = Cache::mergeContexts($contexts, $alert_banner->getCacheContexts()); - } - return Cache::mergeContexts(parent::getCacheContexts(), $contexts); - } - /** * {@inheritdoc} */ diff --git a/tests/src/Functional/TranslationsTest.php b/tests/src/Functional/TranslationsTest.php new file mode 100644 index 00000000..9e24d5c2 --- /dev/null +++ b/tests/src/Functional/TranslationsTest.php @@ -0,0 +1,240 @@ +drupalPlaceBlock('localgov_alert_banner_block'); + ConfigurableLanguage::createFromLangcode('zz')->save(); + $this->container->get('content_translation.manager')->setEnabled('localgov_alert_banner', 'localgov_alert_banner', TRUE); + FieldConfig::loadByName('localgov_alert_banner', 'localgov_alert_banner', 'short_description')->setTranslatable(TRUE)->save(); + } + + /** + * Test that valid translation is brought back based on current language. + */ + public function testAlertBannerTranslation(): void { + + $default_langcode = $this->container->get('language.default')->get()->getId(); + + // Create alert banner. + $alert_title = 'home page alert title - ' . $this->randomMachineName(8); + $alert_body = 'home page alert body - ' . $this->randomMachineName(32); + $alert = $this->container->get('entity_type.manager')->getStorage('localgov_alert_banner') + ->create([ + 'type' => 'localgov_alert_banner', + 'title' => $alert_title, + 'short_description' => $alert_body, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + 'langcode' => $default_langcode, + ]); + $alert->save(); + + // Create translation. + $translated_alert_title = 'translated home page alert title - ' . $this->randomMachineName(8); + $translated_alert_body = 'translated home page alert body - ' . $this->randomMachineName(32); + $alert->addTranslation('zz', [ + 'title' => $translated_alert_title, + 'short_description' => $translated_alert_body, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + ])->save(); + + // Test on home page. + $this->drupalGet(''); + $this->assertSession()->pageTextContains($alert_title); + $this->assertSession()->pageTextContains($alert_body); + $this->assertSession()->pageTextNotContains($translated_alert_title); + $this->assertSession()->pageTextNotContains($translated_alert_body); + + // Switch language. + $this->drupalGet('/zz'); + + // Test correct translation on home page. + $this->assertSession()->pageTextNotContains($alert_title); + $this->assertSession()->pageTextNotContains($alert_body); + $this->assertSession()->pageTextContains($translated_alert_title); + $this->assertSession()->pageTextContains($translated_alert_body); + + // Create node. + $this->drupalCreateContentType(['type' => 'page']); + $page = $this->createNode([ + 'type' => 'page', + 'title' => $this->randomMachineName(8), + 'status' => NodeInterface::PUBLISHED, + 'langcode' => $default_langcode, + ]); + + // Add node translation. + $page->addTranslation('zz', [ + 'title' => $this->randomMachineName(8), + 'status' => NodeInterface::PUBLISHED, + ]); + + // Create banner with page restriction. + $alert_title_node = 'node 1 alert title - ' . $this->randomMachineName(8); + $alert_body_node = 'node 1 alert body - ' . $this->randomMachineName(32); + $node_alert = $this->container->get('entity_type.manager')->getStorage('localgov_alert_banner') + ->create([ + 'type' => 'localgov_alert_banner', + 'title' => $alert_title_node, + 'short_description' => $alert_body_node, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + 'langcode' => $default_langcode, + 'visibility' => [ + 'conditions' => [ + 'request_path' => [ + 'pages' => '/node/1', + 'negate' => 0, + ], + ], + ], + ]); + $node_alert->save(); + + // Translate banner with page restriction. + $translated_alert_title_node = 'translated node 1 alert title - ' . $this->randomMachineName(8); + $translated_alert_body_node = 'translated node 1 alert body - ' . $this->randomMachineName(32); + $node_alert->addTranslation('zz', [ + 'title' => $translated_alert_title_node, + 'short_description' => $translated_alert_body_node, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + ])->save(); + + // Go to node in default language. + $this->drupalGet('/node/1'); + + // Test correct translation appears. + $this->assertSession()->pageTextContains($alert_title_node); + $this->assertSession()->pageTextContains($alert_body_node); + $this->assertSession()->pageTextNotContains($translated_alert_title_node); + $this->assertSession()->pageTextNotContains($translated_alert_body_node); + + // Change language. + $this->drupalGet('/zz/node/1'); + + // Test correct translation appears. + $this->assertSession()->pageTextNotContains($alert_title_node); + $this->assertSession()->pageTextNotContains($alert_body_node); + $this->assertSession()->pageTextContains($translated_alert_title_node); + $this->assertSession()->pageTextContains($translated_alert_body_node); + + // Create node with path. + $page = $this->createNode([ + 'type' => 'page', + 'title' => $this->randomMachineName(8), + 'status' => NodeInterface::PUBLISHED, + 'langcode' => $default_langcode, + ]); + $this->container->get('entity_type.manager')->getStorage('path_alias')->create([ + 'path' => '/node/' . $page->id(), + 'alias' => '/untranslated-path', + 'langcode' => $default_langcode, + ])->save(); + + // Add node translation. + $page->addTranslation('zz', [ + 'title' => $this->randomMachineName(8), + 'status' => NodeInterface::PUBLISHED, + ]); + $this->container->get('entity_type.manager')->getStorage('path_alias')->create([ + 'path' => '/node/' . $page->id(), + 'alias' => '/translated-path', + 'langcode' => 'zz', + ])->save(); + + // Enable translation of the visibility field. + FieldConfig::loadByName('localgov_alert_banner', 'localgov_alert_banner', 'visibility')->setTranslatable(TRUE)->save(); + + // Create banner with page restriction. + $alert_title_pa_node = 'path alias page alert title - ' . $this->randomMachineName(8); + $alert_body_pa_node = 'path alias page alert body - ' . $this->randomMachineName(32); + $pa_node_alert = $this->container->get('entity_type.manager')->getStorage('localgov_alert_banner') + ->create([ + 'type' => 'localgov_alert_banner', + 'title' => $alert_title_pa_node, + 'short_description' => $alert_body_pa_node, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + 'langcode' => $default_langcode, + 'visibility' => [ + 'conditions' => [ + 'request_path' => [ + 'pages' => '/untranslated-path', + 'negate' => 0, + ], + ], + ], + ]); + $node_alert->save(); + + // Translate banner with page restriction. + $translated_alert_title_pa_node = 'translated path alias page alert title - ' . $this->randomMachineName(8); + $translated_alert_body_pa_node = 'translated path alias page alert body - ' . $this->randomMachineName(32); + $pa_node_alert->addTranslation('zz', [ + 'title' => $translated_alert_title_pa_node, + 'short_description' => $translated_alert_body_pa_node, + 'type_of_alert' => 'minor', + 'moderation_state' => 'published', + ])->save(); + + // Go to node in default language. + $this->drupalGet('/untranslated-path'); + + // Test correct translation appears. + $this->assertSession()->pageTextContains($alert_title_pa_node); + $this->assertSession()->pageTextContains($alert_body_pa_node); + $this->assertSession()->pageTextNotContains($translated_alert_title_pa_node); + $this->assertSession()->pageTextNotContains($translated_alert_body_pa_node); + + // Change language. + $this->drupalGet('/zz/translated-path'); + + // Test correct translation appears. + $this->assertSession()->pageTextNotContains($alert_title_pa_node); + $this->assertSession()->pageTextNotContains($alert_body_pa_node); + $this->assertSession()->pageTextContains($translated_alert_title_pa_node); + $this->assertSession()->pageTextContains($translated_alert_body_pa_node); + } + +} diff --git a/tests/src/Kernel/AlertBannerBlockOrderTest.php b/tests/src/Kernel/AlertBannerBlockOrderTest.php index 92a981ed..aa82f289 100644 --- a/tests/src/Kernel/AlertBannerBlockOrderTest.php +++ b/tests/src/Kernel/AlertBannerBlockOrderTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\localgov_alert_banner\Kernel; +use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Datetime\DrupalDateTime; use Drupal\KernelTests\KernelTestBase; use Drupal\localgov_alert_banner\Entity\AlertBannerEntity; @@ -90,7 +91,7 @@ public function testAlertBannerBlockOrder() { $plugin_block = $block_manager->createInstance('localgov_alert_banner_block', $config); // Render the block and get the alert banner IDs as an array. - $render = $plugin_block->build(); + $render = $this->getBannersFromBlockRenderArray($plugin_block); $result = []; foreach ($render as $render_value) { $result[] = $render_value['#localgov_alert_banner']->id(); @@ -118,7 +119,7 @@ public function testAlertBannerBlockOrder() { // Create and render the block and get the alert banner IDs as an array. $plugin_block = $block_manager->createInstance('localgov_alert_banner_block', $config); - $render = $plugin_block->build(); + $render = $this->getBannersFromBlockRenderArray($plugin_block); $result_2 = []; foreach ($render as $render_value) { $result_2[] = $render_value['#localgov_alert_banner']->id(); @@ -144,7 +145,7 @@ public function testAlertBannerBlockOrder() { // Create and render the block and get the alert banner IDs as an array. $plugin_block = $block_manager->createInstance('localgov_alert_banner_block', $config); - $render = $plugin_block->build(); + $render = $this->getBannersFromBlockRenderArray($plugin_block); $result_3 = []; foreach ($render as $render_value) { $result_3[] = $render_value['#localgov_alert_banner']->id(); @@ -206,7 +207,7 @@ public function testAlertBannerBlockOrderWithoutTypeOfAlert() { $block_manager = $this->container->get('plugin.manager.block'); $config = []; $plugin_block = $block_manager->createInstance('localgov_alert_banner_block', $config); - $render = $plugin_block->build(); + $render = $this->getBannersFromBlockRenderArray($plugin_block); $result = []; foreach ($render as $render_value) { $result[] = $render_value['#localgov_alert_banner']->id(); @@ -222,4 +223,19 @@ public function testAlertBannerBlockOrderWithoutTypeOfAlert() { $this->assertEquals($expected, $result); } + /** + * Get banners from block render array. + * + * @param \Drupal\Core\Block\BlockPluginInterface $plugin_block + * Block plugin. + * + * @return array + * Block render array missing keys starting with #. + */ + protected function getBannersFromBlockRenderArray(BlockPluginInterface $plugin_block): array { + return array_filter($plugin_block->build(), function ($key) { + return strpos($key, '#') !== 0; + }, ARRAY_FILTER_USE_KEY); + } + }