From 3b13905b04d85028325784e0120fb9973bb3d20b Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Wed, 4 Sep 2024 19:54:25 +0300
Subject: [PATCH 1/9] ISAICP-9045: Remove applied updates.

---
 .../joinup_oss_catalogue.deploy.php           | 136 ------------------
 .../joinup_oss_catalogue.install              |  32 -----
 .../custom/joinup_core/joinup_core.install    | 119 ---------------
 .../custom/joinup_log/joinup_log.install      |  39 -----
 4 files changed, 326 deletions(-)
 delete mode 100644 web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.deploy.php
 delete mode 100644 web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
 delete mode 100644 web/modules/custom/joinup_log/joinup_log.install

diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.deploy.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.deploy.php
deleted file mode 100644
index 265203b6ed..0000000000
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.deploy.php
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-
-/**
- * @file
- * Deploy functions for Joinup OSS.
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Url;
-use Drupal\joinup_oss_catalogue\OssCatalogueCollectionInterface;
-
-/**
- * Update path alias for EU OSS catalogue.
- */
-function joinup_oss_catalogue_deploy_111200(): void {
-  /** @var \Drupal\collection\Entity\CollectionInterface $collection */
-  $collection = \Drupal::entityTypeManager()->getStorage('rdf_entity')
-    ->load(OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID);
-  $collection
-    ?->set('path', ['alias' => '/eu-oss-catalogue', 'pathauto' => 0])
-      ->save();
-}
-
-/**
- * Update title of EU OSS catalogue collection.
- */
-function joinup_oss_catalogue_deploy_111201(): void {
-  /** @var \Drupal\collection\Entity\CollectionInterface $collection */
-  $collection = \Drupal::entityTypeManager()->getStorage('rdf_entity')
-    ->load(OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID);
-
-  $collection
-    ?->set('label', 'EU Open Source Solutions Catalogue')
-      ->save();
-}
-
-/**
- * Import content for EU OpenSource Software Solutions Catalogue.
- */
-function joinup_oss_catalogue_deploy_111202(): void {
-  /** @var \Drupal\Core\Extension\ModuleInstallerInterface $moduleInstaller */
-  $moduleInstaller = \Drupal::service('module_installer');
-  /** @var \Drupal\Core\Entity\EntityRepositoryInterface $entityRepository */
-  $entityRepository = \Drupal::service('entity.repository');
-
-  // Delete faulty meta entity.
-  $entityRepository
-    ->loadEntityByUuid('meta_entity', 'ad9be48e-7116-44b3-8df2-db8cb9343268')
-    ?->delete();
-
-  // Import content.
-  $moduleInstaller->install(['default_content']);
-  /** @var \Drupal\default_content\ImporterInterface $importer */
-  \Drupal::service('default_content.importer')
-    ->importContent('joinup_oss_catalogue', TRUE);
-  $moduleInstaller->uninstall(['default_content']);
-
-  // Disable auto-created left-side menu link.
-  $landingPage = $entityRepository->loadEntityByUuid('node', '54acbd6f-47f0-4280-ad09-0a07c397318b');
-  $linkPlugins = \Drupal::service('plugin.manager.menu.link')
-    ->loadLinksByRoute('entity.node.canonical', ['node' => $landingPage->id()], 'ogmenu-3728');
-  /** @var \Drupal\menu_link_content\Plugin\Menu\MenuLinkContent $linkPlugin */
-  $linkPlugin = reset($linkPlugins);
-  $entityRepository
-    ->loadEntityByUuid('menu_link_content', $linkPlugin->getDerivativeId())
-    ->setUnpublished()
-    ->save();
-}
-
-/**
- * Rename menu items for EU OSS catalogue.
- */
-function joinup_oss_catalogue_deploy_111203(): void {
-  $entityTypeManager = \Drupal::entityTypeManager();
-  /** @var  \Drupal\menu_link_content\MenuLinkContentStorageInterface $menuLinkContentStorage */
-  $menuLinkContentStorage = $entityTypeManager->getStorage('menu_link_content');
-
-  $menuName = 'ogmenu-3728';
-  // Collect the IDs of links to the custom page.
-  $ids = $menuLinkContentStorage->getQuery()->accessCheck(FALSE)
-    ->condition('bundle', 'menu_link_content')
-    ->condition('menu_name', $menuName)
-    ->execute();
-
-  $links = $menuLinkContentStorage->loadMultiple($ids);
-  foreach ($links as $link) {
-    $title = $link->getTitle();
-    if ($title === 'About' || $title === 'Members') {
-      $link->setUnpublished()->save();
-    }
-    elseif ($title === 'Overview') {
-      $link->set('title', 'Welcome')->save();
-    }
-  }
-  $link = [
-    'uri' => Url::fromRoute('view.search_oss_catalogue.search')->toUriString(),
-  ];
-  $menuLinkContentStorage->create([
-    'title' => t('Search'),
-    'menu_name' => $menuName,
-    'link' => $link,
-    'weight' => -6,
-  ])->save();
-}
-
-/**
- * Make sure that oss_archived has a default value.
- */
-function joinup_oss_catalogue_deploy_111204(array &$sandbox): string {
-  $entityTypeManager = \Drupal::entityTypeManager();
-  /** @var \Drupal\node\NodeStorage $nodeStorage */
-  $nodeStorage = $entityTypeManager->getStorage('node');
-
-  if (empty($sandbox['ids'])) {
-    $results = $nodeStorage->getQuery()->accessCheck(FALSE)
-      ->condition('type', 'oss_solution')
-      ->notExists('oss_archived')
-      ->execute();
-
-    $sandbox['ids'] = array_values($results);
-    $sandbox['progress'] = 0;
-    $sandbox['count'] = count($sandbox['ids']);
-  }
-
-  $ids = array_splice($sandbox['ids'], 0, 50);
-  foreach ($nodeStorage->loadMultiple($ids) as $solution) {
-    $solution->set('oss_archived', 0);
-    $solution->save();
-  }
-
-  $sandbox['progress'] += count($ids);
-  $sandbox['#finished'] = (int) empty($sandbox['ids']);
-
-  return "Processed {$sandbox['progress']} out of {$sandbox['count']} solutions.";
-}
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
deleted file mode 100644
index f811958465..0000000000
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update, and uninstall functions for the Joinup OSS module.
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Serialization\Yaml;
-
-/**
- * Pre-creates OSS vocabularies.
- */
-function joinup_oss_catalogue_update_111200(): void {
-  // Pre-create the new controlled vocabularies provided by eu_oss_catalogue
-  // module. Normally, these are imported as config but eu_oss_catalogue install
-  // goes first and its install process is also populating them with terms, so
-  // they should be available at that point.
-  $config_factory = \Drupal::configFactory();
-  foreach ([
-    'category',
-    'licence',
-    'intended_audience_scope',
-    'software_type',
-  ] as $vocab) {
-    $name = "taxonomy.vocabulary.oss_$vocab";
-    $config_factory->getEditable($name)
-      ->setData(Yaml::decode(file_get_contents(DRUPAL_ROOT . "/../config/sync/$name.yml")))
-      ->save();
-  }
-}
diff --git a/web/modules/custom/joinup_core/joinup_core.install b/web/modules/custom/joinup_core/joinup_core.install
index fabb9b35dc..823aea261c 100644
--- a/web/modules/custom/joinup_core/joinup_core.install
+++ b/web/modules/custom/joinup_core/joinup_core.install
@@ -7,11 +7,7 @@
 
 declare(strict_types=1);
 
-use Drupal\Core\Datetime\DrupalDateTime;
 use Drupal\Core\Site\Settings;
-use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
-use Drupal\node\Entity\Node;
-use Drupal\paragraphs\Entity\Paragraph;
 use Drupal\user\Entity\User;
 
 /**
@@ -96,118 +92,3 @@ function joinup_core_requirements($phase): array {
 
   return $requirements;
 }
-
-/**
- * Convert videos into document.
- */
-function joinup_core_update_111200(array &$sandbox): string {
-  if (!isset($sandbox['video_nids'])) {
-    // Collect all video nodes to migrate.
-    $video_nids = \Drupal::entityQuery('node')
-      ->accessCheck(FALSE)
-      ->condition('type', 'video')
-      ->execute();
-
-    $sandbox['max'] = count($video_nids);
-    $sandbox['count'] = 0;
-    $sandbox['video_nids'] = $video_nids;
-  }
-
-  // Load video nodes.
-  $video_nids = array_slice($sandbox['video_nids'], $sandbox['count'], 20);
-  $entity_type_manager = \Drupal::entityTypeManager();
-  $video_nodes = $entity_type_manager->getStorage('node')->loadMultiple($video_nids);
-
-  /** @var \Drupal\redirect\RedirectRepository $redirect_repository */
-  $redirect_repository = \Drupal::service('redirect.repository');
-
-  // Migrate each video.
-  foreach ($video_nodes as $video_node) {
-    // Find redirects to the video node to preserve after deletion.
-    /* @see redirect_entity_delete */
-    $redirects = $redirect_repository->findByDestinationUri([
-      "internal:/node/{$video_node->id()}",
-      "entity:node/{$video_node->id()}",
-    ]);
-
-    // Prepare new document node.
-    $formatted_date = (new DrupalDateTime())->setTimestamp($video_node->getCreatedTime())->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
-    $created_time = $video_node->getCreatedTime();
-    $document_node = Node::create([
-      'type' => 'document',
-      'nid' => $video_node->id(),
-      'title' => $video_node->getTitle(),
-      'uid' => $video_node->getOwnerId(),
-      'status' => $video_node->isPublished(),
-      'created' => $created_time,
-      'changed' => $video_node->getChangedTime(),
-      'og_audience' => $video_node->get('og_audience')->target_id,
-      'field_document_spatial_coverage' => $video_node->get('field_video_spatial_coverage')->target_id,
-      'field_keywords' => $video_node->get('field_keywords')->getValue(),
-      'field_document_publication_date' => $formatted_date,
-      'published_at' => $created_time,
-      'field_state' => 'published',
-    ]);
-
-    $layout_paragraph = Paragraph::create(['type' => 'layout']);
-    $layout_paragraph->setBehaviorSettings('layout_paragraphs', [
-      'layout' => 'layout_onecol',
-      'config' => ['label' => ''],
-      'parent_uuid' => NULL,
-      'region' => NULL,
-    ]);
-
-    $body_paragraph = Paragraph::create([
-      'type' => 'text',
-      'field_body' => [
-        'value' => $video_node->get('body')->value,
-        'format' => 'text_html',
-      ],
-    ]);
-    $body_paragraph->setBehaviorSettings('layout_paragraphs', [
-      'parent_uuid' => $layout_paragraph->uuid(),
-      'region' => 'content',
-    ]);
-
-    $video_paragraph = Paragraph::create([
-      'type' => 'video',
-      'paragraph_video_url' => $video_node->get('field_video')->getValue(),
-    ]);
-    $video_paragraph->setBehaviorSettings('layout_paragraphs', [
-      'parent_uuid' => $layout_paragraph->uuid(),
-      'region' => 'content',
-    ]);
-
-    $document_node->set('field_paragraphs_body', [
-      ['entity' => $layout_paragraph],
-      ['entity' => $body_paragraph],
-      ['entity' => $video_paragraph],
-    ]);
-
-    // Delete video node.
-    $video_node->delete();
-
-    // Save new document node.
-    // Create 2 revisions (1 published and 1 archived); un-archiving requires a
-    // previous published revision otherwise a fatal error occurs.
-    /* @see \Drupal\joinup_workflow\WorkflowHelper::unarchiveEntity */
-    $document_node->setRevisionCreationTime($created_time);
-    $document_node->skip_notification = TRUE;
-    $document_node->save();
-
-    $document_node->set('field_state', 'archived');
-    $document_node->setNewRevision();
-    $document_node->skip_notification = TRUE;
-    $document_node->save();
-
-    // Restore redirects.
-    foreach ($redirects as $redirect) {
-      $redirect->createDuplicate()->save();
-    }
-
-    $sandbox['count']++;
-  }
-
-  $sandbox['#finished'] = $sandbox['count'] === $sandbox['max'];
-  return "{$sandbox['count']} out of {$sandbox['max']} video nodes processed.";
-}
diff --git a/web/modules/custom/joinup_log/joinup_log.install b/web/modules/custom/joinup_log/joinup_log.install
deleted file mode 100644
index 5066d9fb68..0000000000
--- a/web/modules/custom/joinup_log/joinup_log.install
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-/**
- * @file
- * Install and update hooks.
- */
-
-declare(strict_types=1);
-
-use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-
-/**
- * Add the `variables` field.
- */
-function joinup_log_update_111200(): TranslatableMarkup {
-  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
-
-  $field = BaseFieldDefinition::create('map')
-    ->setLabel(new TranslatableMarkup('Variables'))
-    ->setDefaultValue(serialize([]))
-    ->setDescription(new TranslatableMarkup('Serialized array of variables that match the message string and that is passed into the t() function.'));
-  $definition_update_manager->installFieldStorageDefinition('variables', 'joinup_log', 'joinup_log', $field);
-
-  return t('The variables fields have been added to Joinup log.');
-}
-
-/**
- * Set the `variables` field.
- */
-function joinup_log_update_111201(): TranslatableMarkup {
-  $query = \Drupal::database()->update('joinup_log')
-    ->fields([
-      'variables' => serialize([]),
-    ]);
-  $query->execute();
-
-  return t('Updated Joinup log entities.');
-}
-- 
GitLab


From 9887642f77fa344fa39f96b927012e46ec6ab65f Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Thu, 5 Sep 2024 09:31:37 +0300
Subject: [PATCH 2/9] ISAICP-9045: Drop update tests.

---
 tests/features/update/ISAICP-8911.feature | 39 -----------------------
 1 file changed, 39 deletions(-)
 delete mode 100644 tests/features/update/ISAICP-8911.feature

diff --git a/tests/features/update/ISAICP-8911.feature b/tests/features/update/ISAICP-8911.feature
deleted file mode 100644
index b654cfff6d..0000000000
--- a/tests/features/update/ISAICP-8911.feature
+++ /dev/null
@@ -1,39 +0,0 @@
-@api @group-clone
-Feature: Test the converted videos into document.
-
-  @update:joinup_core_update_111200
-  Scenario: Videos have migrated correctly.
-    When I visit "node/125946"
-    Then I should see the heading "eGovernment Reduction of Administrative Burden-ePractice TV interview: Mr Siim Sikkut"
-    And I should see the text "Mr Siim Sikkut, Government office of Estonia"
-    And I should see the text "Published on: 10/06/2014"
-    And I should see the text "Last update: 09/06/2014"
-
-    When I visit "node/125943"
-    Then I should see the heading "Concluding Remarks - Terje Peetso, DG CONNECT"
-    And I should see the text "Terje Peetso, DG CONNECT - eHealth and the Brain - ICT for Neuropsychiatric Health, 5 November, Brussels"
-    And I should see the text "Published on: 20/11/2013"
-
-    When I visit "node/125865"
-    Then I should see the heading "EIF workshop-ePractice TV:Soeren Bittins, Massimiliano Massi"
-    And I should see the text "EIF workshop, 16 April 2012"
-    And I should see the text "Interviewees: Soeren Bittins, Massimiliano Massi, Fraunhofer Fokus"
-    And I should see the text "Published on: 11/05/2012"
-
-    When I visit "/community/epractice/video/egov-reduction-admininstrative-burden-epractice-tv-interview-kris-blancke"
-    Then I should see the heading "eGov Reduction of Admininstrative Burden-ePractice TV interview: Kris Blancke"
-    And I should see the text "Mr Kris Blancke, Chancellery of the Prime Minister, Administrative Simplification Agency (DAV/ASA)"
-    And I should see the text "Published on: 10/06/2014"
-
-  @update:joinup_core_update_111200
-  Scenario Outline: Ensure old /elibrary/video/* redirects are maintained and are pointing to the new document nodes.
-    Given I visit "<source>"
-    Then I should be on "<destination>"
-
-    Examples:
-      | source                                                                                                | destination                                                                                                                                 |
-      | /elibrary/video/us-national-information-exchange-model-niem-explained                                 | /collection/semic-support-centre/document/us-national-information-exchange-model-niem-explained                                             |
-      | /elibrary/video/e-codex-e-justice-communication-online-data-exchange                                  | /collection/justice-law-and-security/document/e-codex-e-justice-communication-online-data-exchange                                          |
-      | /elibrary/video/eu-declan-deasy-about-digital-single-market                                           | /collection/egovernment/document/eu-declan-deasy-about-digital-single-market                                                                |
-      | /elibrary/video/eu-story-explaining-digital-single-market-and-large-scale-pilots                      | /collection/egovernment/document/eu-story-explaining-digital-single-market-and-large-scale-pilots                                           |
-      | /elibrary/video/presenting-osepa-project-share-knowledge-and-experience-about-foss-public-administrat | /collection/open-source-observatory-osor/document/presenting-osepa-project-share-knowledge-and-experience-about-foss-public-administrations |
-- 
GitLab


From 02015cf75ba5da5b75ed7323f63a6b8ef9a4966a Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Thu, 5 Sep 2024 15:02:14 +0300
Subject: [PATCH 3/9] ISAICP-9052: Revert some changes to config readonly
 switch.

---
 .ddev/commands/host/install                   |  4 +-
 .ddev/commands/host/rebuild                   |  4 +-
 .env.example                                  |  4 -
 .gitignore                                    |  2 +
 .opts.yml                                     | 22 ++---
 README.md                                     | 19 +++--
 resources/runner/config_readonly.yml          | 16 ++++
 resources/runner/dev.yml                      | 16 ++--
 resources/runner/drupal.yml                   |  3 +-
 resources/runner/joinup.yml                   |  8 --
 resources/runner/toolkit.yml                  | 16 ++--
 scripts/switch                                | 82 -------------------
 tests/src/Context/FeatureContext.php          |  6 +-
 .../src/Context/JoinupOSSSolutionContext.php  |  8 +-
 tests/src/Traits/ConfigReadOnlyTrait.php      | 42 ++++++++--
 .../dashboard/src/Form/DashboardForm.php      | 15 ++--
 .../joinup_core/joinup_core.services.yml      |  6 +-
 .../src/ConfigReadOnlyInterface.php           | 30 +++++++
 .../joinup_core/src/FileConfigReadOnly.php    | 56 +++++++++++++
 .../custom/joinup_core/src/JoinupSwitcher.php | 64 ---------------
 .../src/JoinupSwitcherInterface.php           | 39 ---------
 .../custom/joinup_user/joinup_user.module     |  8 +-
 .../src/Plugin/Block/WarningMessageBlock.php  |  8 +-
 23 files changed, 199 insertions(+), 279 deletions(-)
 create mode 100644 resources/runner/config_readonly.yml
 delete mode 100755 scripts/switch
 create mode 100644 web/modules/custom/joinup_core/src/ConfigReadOnlyInterface.php
 create mode 100644 web/modules/custom/joinup_core/src/FileConfigReadOnly.php
 delete mode 100644 web/modules/custom/joinup_core/src/JoinupSwitcher.php
 delete mode 100644 web/modules/custom/joinup_core/src/JoinupSwitcherInterface.php

diff --git a/.ddev/commands/host/install b/.ddev/commands/host/install
index a7bc84c896..745623f7ba 100755
--- a/.ddev/commands/host/install
+++ b/.ddev/commands/host/install
@@ -6,6 +6,6 @@
 
 ddev solr:empty
 ddev exec vendor/bin/run toolkit:install-clean
-ddev exec scripts/switch config-readonly off
+ddev exec vendor/bin/run config-readonly:disable
 ddev exec vendor/bin/run dev:install-modules
-ddev exec scripts/switch config-readonly on
+ddev exec vendor/bin/run config-readonly:enable
diff --git a/.ddev/commands/host/rebuild b/.ddev/commands/host/rebuild
index c8a8a9abe1..95cd7825de 100755
--- a/.ddev/commands/host/rebuild
+++ b/.ddev/commands/host/rebuild
@@ -7,7 +7,7 @@
 ddev solr:restore
 ddev virtuoso:restore
 ddev run toolkit:install-clone
-ddev exec scripts/switch config-readonly off
+ddev run config-readonly:disable
 ddev run dev:install-modules
 ddev drush user:unblock --uid=1
-ddev exec scripts/switch config-readonly on
+ddev run config-readonly:enable
diff --git a/.env.example b/.env.example
index b93332a7ad..ad74c13ac9 100644
--- a/.env.example
+++ b/.env.example
@@ -18,7 +18,3 @@ NEXTCLOUD_PASS=
 # The GitHub token. Generate a personal access token if you want to be able to
 # use the GitHub feed paragraph.
 JOINUP_GITHUB_TOKEN=
-
-# Set this variable "false" in your .env file to permanently disable the config
-# read-only feature on your development environment.
-CONFIG_READONLY=
diff --git a/.gitignore b/.gitignore
index f5e9d58a87..a38f1aa36e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,6 +43,8 @@
 /.vscode/
 /.devcontainer/
 
+# Ignore the config_readonly killswitch.
+/disable-config-readonly
 # Ignore the cookie consent killswitch.
 /disable-cookie-consent
 
diff --git a/.opts.yml b/.opts.yml
index fd5159c78c..45ec3ab20b 100644
--- a/.opts.yml
+++ b/.opts.yml
@@ -5,36 +5,26 @@
 # See \EcEuropa\Toolkit\TaskRunner\Commands\CloneCommands::runDeploy().
 upgrade_commands:
   default:
-    - scripts/switch config-readonly off
+    - touch disable-config-readonly
     - ./vendor/bin/drush deploy --yes
     - ./vendor/bin/drush search-api:reset-tracker --yes
     - ./vendor/bin/drush joinup:node-access-rebuild
     - ./vendor/bin/drush joinup:search-api-tasks
     - ./vendor/bin/drush twigc --yes
-    - scripts/switch config-readonly on
+    - rm disable-config-readonly
     - ./vendor/bin/drush joinup:unpublish-alert --category
     - ./scripts/check_status_report.php
-    - test ! -f vendor/bin/run || vendor/bin/run eu-oss-catalogue:enable
   append:
-    production:
-      - scripts/switch eu-oss-catalogue off
     acceptance:
-      - scripts/switch config-readonly off
+      - touch disable-config-readonly
       - ./vendor/bin/drush joinup:acc
       - ./vendor/bin/drush cache:rebuild
-      - scripts/switch config-readonly on
-      # Remove this line when deploying to POC.
-      - scripts/switch eu-oss-catalogue off
-      - ./vendor/bin/drush eu-oss:fetch developers_italia
-      - ./vendor/bin/drush eu-oss:fetch hosting_platform:open_code
-      - ./vendor/bin/drush queue:run eu_oss_catalogue
-      - ./vendor/bin/drush queue:run joinup_oss_catalogue_imagecache_warmer
+      - rm disable-config-readonly
     ephemeral:
-      - scripts/switch config-readonly off
+      - touch disable-config-readonly
       - ./vendor/bin/drush joinup:acc
       - ./vendor/bin/drush cache:rebuild
-      - scripts/switch config-readonly on
-
+      - rm disable-config-readonly
 php_version: 8.3
 extra_pkgs:
   - php8.3-apcu
diff --git a/README.md b/README.md
index b18a479c20..812a443623 100644
--- a/README.md
+++ b/README.md
@@ -82,7 +82,7 @@ ddev composer install
 
 This will install the PHP and Node dependencies. The codebase is deployed.
 
-### Configure secrets and local settings
+### Configure sensitive data (secrets)
 
 * Copy the `.env.example` file as `.env`. The `.env` file is not under VCS
   control and is used to override values stored into `.env.dist` file but, more
@@ -90,12 +90,6 @@ This will install the PHP and Node dependencies. The codebase is deployed.
   passwords, etc.
 * You should fill the `.env` file environment variables with values. Each
   variable is documented in `.env.example`.
-* On production, the site uses the `config_readonly` module to prevent config
-  changes. This is a safety measure. You can turn this protection on/off by
-  running `ddev exec scripts/switch config-readonly [operation]`, where
-  `[operation]` is `enable` or `disable`. However, when developing locally, this
-  could become annoying. To completely turn off the protection locally, just add
-  `CONFIG_READONLY=false` in your `.env` file.
 
 ### Install Joinup
 
@@ -241,7 +235,16 @@ Note that Toolkit uses its own configuration. Few aspects:
 * Apart from configuration provided by Toolkit, Joinup exposes its own
   configuration under `resources/runner`.
 * You can override any configuration by creating a `runner.yml` file in the
-  project's root directory. Note that this file is not under VCS control.
+  project's root directory. Note that this file is not under VCS control. For
+  instance, you may want to disable the "config read-only" feature while you
+  work on the project. All you need to do is to add this line in `runner.yml`:
+  ```yaml
+  config_readonly: false
+  ```
+* You can inspect the configuration. Find out how by running this command:
+  ```yaml
+  ddev run config --help
+  ```
 
 #### Developer local commands
 
diff --git a/resources/runner/config_readonly.yml b/resources/runner/config_readonly.yml
new file mode 100644
index 0000000000..da4e29a358
--- /dev/null
+++ b/resources/runner/config_readonly.yml
@@ -0,0 +1,16 @@
+# Config readonly settings and commands.
+
+# Set this config to `false` in your runner.yml to permanently disable the
+# config read-only feature on your development environment.
+config_readonly: true
+
+commands:
+
+  # Config readonly kill-switch.
+  config-readonly:enable:
+    - task: exec
+      command: (test "${config_readonly}" = "1" && rm -f ${joinup.dir}/disable-config-readonly) || true
+
+  config-readonly:disable:
+    - task: touch
+      file: ${joinup.dir}/disable-config-readonly
diff --git a/resources/runner/dev.yml b/resources/runner/dev.yml
index dad18a4fd6..1639e5ba02 100644
--- a/resources/runner/dev.yml
+++ b/resources/runner/dev.yml
@@ -32,17 +32,17 @@ commands:
       command: solr:download-snapshot
 
   dev:install-modules:
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly off
+    - task: run
+      command: config-readonly:disable
     - task: run
       command: drush:module-install
       arguments: ${dev.modules}
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly on
+    - task: run
+      command: config-readonly:enable
 
   dev:demo-users:
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly off
+    - task: run
+      command: config-readonly:disable
     # Make sure CAS mock server module is installed.
     - task: run
       command: drush:module-install
@@ -194,8 +194,8 @@ commands:
       options:
         yes: null
         root: ${joinup.site_dir}
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly on
+    - task: run
+      command: config-readonly:enable
 
   dev:import-sparql-fixtures:
     - task: run
diff --git a/resources/runner/drupal.yml b/resources/runner/drupal.yml
index 7a4a0c6473..883e0cc29c 100644
--- a/resources/runner/drupal.yml
+++ b/resources/runner/drupal.yml
@@ -245,7 +245,7 @@ drupal:
         ];
         // Location of the site configuration files.
         $settings['config_sync_directory'] = '../config/sync';
-        $settings['config_readonly'] = getenv('CONFIG_READONLY') !== 'false' && !file_exists(getenv('DRUPAL_PRIVATE_FILE_SYSTEM') . '/killswitch/disable-config-readonly');
+        $settings['config_readonly'] = !file_exists(getcwd() . '/../disable-config-readonly');
       Testing configuration alters: |
         // The video from the home page interacts with Selenium tests.
         $config['page_manager.page_variant.homepage-layout_builder-0']['variant_settings']['sections'][4]['components']['6ab0ceea-4541-4153-8b64-227e02369d30']['configuration']['text'] = 'Some joinup video';
@@ -380,7 +380,6 @@ drupal:
         $settings['reverse_proxy'] = (bool) getenv('DRUPAL_REVERSE_PROXY_ENABLE');
         $settings['reverse_proxy_addresses'] = array_filter(explode(',', (string) getenv('DRUPAL_REVERSE_PROXY_ADDRESSES')));
       EU OSS Catalogue: |
-        $settings['eu_oss_catalogue']['enabled'] = !file_exists(getenv('DRUPAL_PRIVATE_FILE_SYSTEM') . '/killswitch/disable-eu-oss-catalogue');
         $settings['eu_oss_catalogue']['platform_github']['access_token'] = getenv('JOINUP_GITHUB_TOKEN');
       Stage file proxy: |
         $config['stage_file_proxy.settings']['hotlink'] = TRUE;
diff --git a/resources/runner/joinup.yml b/resources/runner/joinup.yml
index 7e8ff875d9..5d5c08ab6a 100644
--- a/resources/runner/joinup.yml
+++ b/resources/runner/joinup.yml
@@ -6,11 +6,3 @@ joinup:
   files_private_dir: ${joinup.site_dir}/${env.DRUPAL_PRIVATE_FILE_SYSTEM}
   files_temp_dir: ${joinup.site_dir}/${env.DRUPAL_FILE_TEMP_PATH}
   ddev_dir: ${joinup.dir}/.ddev
-
-
-commands:
-  # Drop when EU OSS Catalogue is enabled in production, in ISAICP-9053.
-  eu-oss-catalogue:enable:
-    - task: exec
-      command: ${drush.bin} eval 'if (joinup_oss_catalogue_is_enabled()) \Drupal::entityTypeManager()->getStorage("rdf_entity")->load("http://data.europa.eu/w21/6d621a47-71ce-4f1a-90e2-8859403d3ecd")->set("field_ar_state", "published")->save();'
-
diff --git a/resources/runner/toolkit.yml b/resources/runner/toolkit.yml
index 8c7864ede2..fb5b5164db 100644
--- a/resources/runner/toolkit.yml
+++ b/resources/runner/toolkit.yml
@@ -52,8 +52,8 @@ toolkit:
               - install
         after:
           # The `toolkit:run-deploy` command has enabled config read-only.
-          - task: exec
-            command: ${joinup.dir}/scripts/switch config-readonly off
+          - task: run
+            command: config-readonly:disable
           - task: run
             command: testing:install-modules
           - task: exec
@@ -72,8 +72,8 @@ toolkit:
             command: drupal:settings
             arguments:
               - site-clone
-          - task: exec
-            command: ${joinup.dir}/scripts/switch config-readonly on
+          - task: run
+            command: config-readonly:enable
           - task: exec
             command: test ! -z "$CI" || vendor/bin/run solr:build-core-config
 
@@ -113,8 +113,8 @@ commands:
       command: drupal:settings
       arguments:
         - install
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly off
+    - task: run
+      command: config-readonly:disable
     - task: run
       command: drupal:site-install
       options:
@@ -146,8 +146,8 @@ commands:
       arguments:
         - php:eval
         - if (node_access_needs_rebuild()) { node_access_rebuild(); }
-    - task: exec
-      command: ${joinup.dir}/scripts/switch config-readonly on
+    - task: run
+      command: config-readonly:enable
     - task: exec
       command: test ! -z "$CI" || vendor/bin/run solr:build-core-config
 
diff --git a/scripts/switch b/scripts/switch
deleted file mode 100755
index 7aeefc2edf..0000000000
--- a/scripts/switch
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/usr/bin/env php
-<?php
-
-use Symfony\Component\Filesystem\Filesystem;
-
-include_once __DIR__ . '/../vendor/autoload.php';
-
-// Logs messages
-$log = function (string $type, string $message): void {
-  $message = trim($message);
-  print match($type) {
-    'info' => "\033[36m$message\033[0m",
-    'warning' => "\033[33m$message\033[0m",
-    'error' => "\033[31m$message\033[0m",
-  };
-  print "\n";
-  if ($type === 'error') {
-    exit(1);
-  }
-};
-
-[, $switch, $operation] = $argv;
-
-// Check command arguments.
-$errors = [];
-if (!in_array($switch, ['config-readonly', 'eu-oss-catalogue'])) {
-  $errors[] = "First argument should be 'config-readonly' or 'eu-oss-catalogue' but '$switch' given";
-}
-if (!in_array($operation, ['on', 'off'])) {
-  $errors[] = "Second argument should be 'on' or 'off' but '$operation' given";
-}
-if (($count = count($argv) -1 ) > 2) {
-  $errors[] = "This command only accepts 2 arguments but $count given";
-}
-if ($errors) {
-  $log('error', implode("\n", $errors));
-}
-
-$fileSystem = new Filesystem();
-$path = getenv('DRUPAL_PRIVATE_FILE_SYSTEM');
-
-if (empty($path)) {
-  $log('error', 'The DRUPAL_PRIVATE_FILE_SYSTEM environment variable is not set');
-}
-
-// Drupal expects the private filesystem path as relative to web root but in
-// CI/CD it might be set as absolute. Convert to absolute path.
-if (!$fileSystem->isAbsolutePath($path)) {
-  $dir = DRUPAL_ROOT . '/' . $path;
-  $path = realpath($dir);
-  if ($path === FALSE) {
-    $log('error', "The directory $dir doesn't exist");
-  }
-}
-
-$path .= '/killswitch';
-
-if (!$fileSystem->exists($path)) {
-  // Create the directory if it's missing.
-  $fileSystem->mkdir($path);
-  $fileSystem->chmod($path, 0770);
-}
-
-// File used as kill-switch.
-$killSwitch = "$path/disable-$switch";
-
-if ($operation === 'on') {
-  if ($fileSystem->exists($killSwitch)) {
-    $fileSystem->remove($killSwitch);
-  }
-
-  if ($switch === 'config-readonly' && getenv('CONFIG_READONLY') === 'false') {
-    $log('warning', "Config read-only is permanently disabled because the CONFIG_READONLY environment\nvariable is set to 'false'");
-  }
-  else {
-    $log('info', "The $switch functionality has been enabled");
-  }
-}
-elseif ($operation === 'off') {
-  $fileSystem->touch($killSwitch);
-  $log('info', "The $switch functionality has been disabled");
-}
diff --git a/tests/src/Context/FeatureContext.php b/tests/src/Context/FeatureContext.php
index d4b879f7f1..091b7ef476 100644
--- a/tests/src/Context/FeatureContext.php
+++ b/tests/src/Context/FeatureContext.php
@@ -2439,10 +2439,10 @@ public function iRunTheQueueWorker(string $queue_name): void {
   public function theConfigurationState(string $state): void {
     \assert(in_array($state, ['locked', 'unlocked']));
 
-    /** @var \Drupal\joinup_core\JoinupSwitcherInterface $fileReadOnly */
-    $fileReadOnly = \Drupal::service('joinup_core.switcher');
+    /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileReadOnly */
+    $fileReadOnly = \Drupal::service('joinup_core.file_config_read_only');
 
-    Assert::assertTrue($fileReadOnly->isEnabled('config-readonly') === ($state === 'locked'));
+    Assert::assertTrue($fileReadOnly->isReadOnlyEnabled() === ($state === 'locked'));
   }
 
   /**
diff --git a/tests/src/Context/JoinupOSSSolutionContext.php b/tests/src/Context/JoinupOSSSolutionContext.php
index 70209e2549..04b7a22877 100644
--- a/tests/src/Context/JoinupOSSSolutionContext.php
+++ b/tests/src/Context/JoinupOSSSolutionContext.php
@@ -62,15 +62,13 @@ public static function disableOssCron(): void {
    */
   public function toggleEuOssCatalogue(string $toggle): void {
     assert(in_array($toggle, ['enable', 'disable'], TRUE));
-
-    $switcher = \Drupal::getContainer()->get('joinup_core.switcher');
+    $file = DRUPAL_ROOT . '/../disable-eu-oss-catalogue';
     if ($toggle === 'enable') {
-      $switcher->enable('eu-oss-catalogue');
+      unlink($file);
     }
     else {
-      $switcher->enable('eu-oss-catalogue');
+      touch($file);
     }
-
     \Drupal::getContainer()->get('cache.page')->deleteAll();
   }
 
diff --git a/tests/src/Traits/ConfigReadOnlyTrait.php b/tests/src/Traits/ConfigReadOnlyTrait.php
index 36640d0fd0..54e258b40d 100644
--- a/tests/src/Traits/ConfigReadOnlyTrait.php
+++ b/tests/src/Traits/ConfigReadOnlyTrait.php
@@ -16,6 +16,13 @@
  */
 trait ConfigReadOnlyTrait {
 
+  /**
+   * The initial state of config_readonly.
+   *
+   * @var bool
+   */
+  protected static $isConfigReadonlyEnabled;
+
   /**
    * Temporarily disables read only configuration.
    *
@@ -24,7 +31,17 @@ trait ConfigReadOnlyTrait {
    */
   public static function bypassReadOnlyConfig(): void {
     static::checkConfigReadOnlyKillSwitch();
-    \Drupal::getContainer()->get('joinup_core.switcher')->disable('config-readonly');
+
+    /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileReadOnly */
+    $fileReadOnly = \Drupal::service('joinup_core.file_config_read_only');
+
+    if (!isset(static::$isConfigReadonlyEnabled)) {
+      // Save the initial state of config_readonly kill-switch.
+      static::$isConfigReadonlyEnabled = $fileReadOnly->isReadOnlyEnabled();
+    }
+
+    $fileReadOnly->disableReadOnly();
+
     // Ensure the new value also for the current request.
     new Settings(['config_readonly' => FALSE] + Settings::getAll());
   }
@@ -33,11 +50,18 @@ public static function bypassReadOnlyConfig(): void {
    * Restores the read only configuration functionality if available.
    */
   public static function restoreReadOnlyConfig(): void {
-    \Drupal::getContainer()->get('joinup_core.switcher')->enable('config-readonly');
-    // Ensure the new value also for the current request.
-    new Settings([
-      'config_readonly' => getenv('CONFIG_READONLY') !== 'false',
-    ] + Settings::getAll());
+    /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileReadOnly */
+    $fileReadOnly = \Drupal::service('joinup_core.file_config_read_only');
+
+    // Restore as enabled only if initially has been enabled. This allows to
+    // keep config_readonly disabled on a local development environment (i.e.
+    // where the Task Runner config `config_readonly` was set to `false`), after
+    // the tests had finished.
+    if (static::$isConfigReadonlyEnabled) {
+      $fileReadOnly->enableReadOnly();
+      // Ensure the new value also for the current request.
+      new Settings(['config_readonly' => TRUE] + Settings::getAll());
+    }
   }
 
   /**
@@ -50,9 +74,9 @@ protected static function checkConfigReadOnlyKillSwitch(): void {
     /** @var \Drupal\Core\DrupalKernelInterface $kernel */
     $kernel = \Drupal::service('kernel');
     $site_path = $kernel->getSitePath();
-    $needle = "\$settings['config_readonly'] = getenv('CONFIG_READONLY') !== 'false' && !file_exists(getenv('DRUPAL_PRIVATE_FILE_SYSTEM') . '/killswitch/disable-config-readonly');";
-    $settingsPhp = file_get_contents("{$site_path}/settings.php");
-    if (!str_contains($settingsPhp, $needle)) {
+    $needle = "\$settings['config_readonly'] = !file_exists(getcwd() . '/../disable-config-readonly');";
+    $settings_php = file_get_contents("{$site_path}/settings.php");
+    if (strpos($settings_php, $needle) === FALSE) {
       throw new \Exception("The following line is missing from web/sites/default/settings.php\n$needle");
     }
   }
diff --git a/web/modules/custom/dashboard/src/Form/DashboardForm.php b/web/modules/custom/dashboard/src/Form/DashboardForm.php
index a0c82cd99e..cd076becfc 100644
--- a/web/modules/custom/dashboard/src/Form/DashboardForm.php
+++ b/web/modules/custom/dashboard/src/Form/DashboardForm.php
@@ -11,7 +11,7 @@
 use Drupal\Core\Render\Element;
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Url;
-use Drupal\joinup_core\JoinupSwitcherInterface;
+use Drupal\joinup_core\ConfigReadOnlyInterface;
 use Drupal\joinup_core\Logger\Handler\MailHandler;
 use Drupal\joinup_core\Logger\Handler\TelegramHandler;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -23,7 +23,7 @@ class DashboardForm extends FormBase {
 
   public function __construct(
     protected StateInterface $state,
-    protected JoinupSwitcherInterface $switcher,
+    protected ConfigReadOnlyInterface $fileConfigReadOnly,
     protected CacheTagsInvalidatorInterface $invalidator,
   ) {
   }
@@ -34,7 +34,7 @@ public function __construct(
   public static function create(ContainerInterface $container): self {
     return new static(
       $container->get('state'),
-      $container->get('joinup_core.switcher'),
+      $container->get('joinup_core.file_config_read_only'),
       $container->get('cache_tags.invalidator')
     );
   }
@@ -103,18 +103,17 @@ public function buildForm(array $form, FormStateInterface $form_state): array {
     ];
 
     $hasEnableDisablePermission = $this->currentUser()->hasPermission('enable/disable config readonly');
-    $configReadonlyIsEnabled = $this->switcher->isEnabled('config-readonly');
     $form['actions']['enable'] = [
       '#type' => 'submit',
       '#value' => $this->t('Enable config readonly'),
       '#submit' => ['::enableConfigSubmitForm'],
-      '#access' => $hasEnableDisablePermission && !$configReadonlyIsEnabled,
+      '#access' => $hasEnableDisablePermission && !$this->fileConfigReadOnly->isReadOnlyEnabled(),
     ];
     $form['actions']['disable'] = [
       '#type' => 'submit',
       '#value' => $this->t('Disable config readonly'),
       '#submit' => ['::disableConfigSubmitForm'],
-      '#access' => $hasEnableDisablePermission && $configReadonlyIsEnabled,
+      '#access' => $hasEnableDisablePermission && $this->fileConfigReadOnly->isReadOnlyEnabled(),
     ];
 
     return $form;
@@ -124,7 +123,7 @@ public function buildForm(array $form, FormStateInterface $form_state): array {
    * Enable the configuration submit form.
    */
   public function enableConfigSubmitForm(): void {
-    $this->switcher->enable('config-readonly');
+    $this->fileConfigReadOnly->enableReadOnly();
     $this->invalidator->invalidateTags(['joinup_user:read_only_config']);
     $this->messenger()->addStatus($this->t('Enabled config readonly'));
   }
@@ -133,7 +132,7 @@ public function enableConfigSubmitForm(): void {
    * Disables the config submit form.
    */
   public function disableConfigSubmitForm(): void {
-    $this->switcher->disable('config-readonly');
+    $this->fileConfigReadOnly->disableReadOnly();
     $this->invalidator->invalidateTags(['joinup_user:read_only_config']);
     $this->messenger()->addStatus($this->t('Disabled config readonly'));
   }
diff --git a/web/modules/custom/joinup_core/joinup_core.services.yml b/web/modules/custom/joinup_core/joinup_core.services.yml
index 7d15ddfbae..9ed58a6e57 100644
--- a/web/modules/custom/joinup_core/joinup_core.services.yml
+++ b/web/modules/custom/joinup_core/joinup_core.services.yml
@@ -42,9 +42,9 @@ services:
     tags:
       - { name: event_subscriber }
 
-  joinup_core.switcher:
-    class: Drupal\joinup_core\JoinupSwitcher
-    arguments: ['@file_system']
+  joinup_core.file_config_read_only:
+    class: Drupal\joinup_core\FileConfigReadOnly
+    arguments: [ '@file_system' ]
 
   # Prevent errors in twigc by implementing the missing get_dummy_text function,
   # which is referenced in BCL component templates.
diff --git a/web/modules/custom/joinup_core/src/ConfigReadOnlyInterface.php b/web/modules/custom/joinup_core/src/ConfigReadOnlyInterface.php
new file mode 100644
index 0000000000..e74bce804d
--- /dev/null
+++ b/web/modules/custom/joinup_core/src/ConfigReadOnlyInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\joinup_core;
+
+/**
+ * Config read-only interface.
+ */
+interface ConfigReadOnlyInterface {
+
+  /**
+   * Checks if read-only mode is enabled.
+   *
+   * @return bool
+   *   Returns true if read-only mode is enabled, false otherwise.
+   */
+  public function isReadOnlyEnabled(): bool;
+
+  /**
+   * Disables read-only mode.
+   */
+  public function disableReadOnly(): void;
+
+  /**
+   * Enables read-only mode.
+   */
+  public function enableReadOnly(): void;
+
+}
diff --git a/web/modules/custom/joinup_core/src/FileConfigReadOnly.php b/web/modules/custom/joinup_core/src/FileConfigReadOnly.php
new file mode 100644
index 0000000000..7ff9241f8d
--- /dev/null
+++ b/web/modules/custom/joinup_core/src/FileConfigReadOnly.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\joinup_core;
+
+use Drupal\Core\File\FileSystemInterface;
+
+/**
+ * Represents a file-based implementation of the ConfigReadOnlyInterface.
+ */
+class FileConfigReadOnly implements ConfigReadOnlyInterface {
+
+  /**
+   * File name of the disabled config readonly.
+   *
+   * @var string
+   */
+  protected const string FILE_NAME = 'disable-config-readonly';
+
+  public function __construct(protected FileSystemInterface $fileSystem) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isReadOnlyEnabled(): bool {
+    return !file_exists($this->pathToReadOnlyFile());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function enableReadOnly(): void {
+    if (!$this->isReadOnlyEnabled()) {
+      $this->fileSystem->unlink($this->pathToReadOnlyFile());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function disableReadOnly(): void {
+    touch($this->pathToReadOnlyFile());
+  }
+
+  /**
+   * Returns the file path to disable the config readonly flag.
+   *
+   * @return string
+   *   The file path
+   */
+  protected function pathToReadOnlyFile(): string {
+    return DRUPAL_ROOT . '/../' . self::FILE_NAME;
+  }
+
+}
diff --git a/web/modules/custom/joinup_core/src/JoinupSwitcher.php b/web/modules/custom/joinup_core/src/JoinupSwitcher.php
deleted file mode 100644
index 40f3d87c4a..0000000000
--- a/web/modules/custom/joinup_core/src/JoinupSwitcher.php
+++ /dev/null
@@ -1,64 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\joinup_core;
-
-use Drupal\Core\File\FileExists;
-use Drupal\Core\File\FileSystemInterface;
-
-/**
- * Represents a file-based implementation of the ConfigReadOnlyInterface.
- */
-class JoinupSwitcher implements JoinupSwitcherInterface {
-
-  protected const string DIR = 'private://killswitch';
-
-  protected const array FEATURES = ['config-readonly', 'eu-oss-catalogue'];
-
-  public function __construct(protected FileSystemInterface $fileSystem) {}
-
-  /**
-   * {@inheritdoc}
-   */
-  public function isEnabled(string $feature): bool {
-    $file = $this->fileSystem->realpath($this->pathToFile($feature));
-    if ($file && file_exists($file)) {
-      return TRUE;
-    }
-    return FALSE;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function enable(string $feature): void {
-    if (!$this->isEnabled('config-readonly')) {
-      $file = $this->fileSystem->realpath($this->pathToFile($feature));
-      $this->fileSystem->unlink($file);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function disable(string $feature): void {
-    $file = $this->fileSystem->realpath($this->pathToFile($feature));
-    $this->fileSystem->saveData('', $file, FileExists::Replace);
-  }
-
-  /**
-   * Returns the file path to the kill-switch file.
-   *
-   * @param string $feature
-   *   The feature. E.g. 'config-readonly'.
-   *
-   * @return string
-   *   The file path
-   */
-  protected function pathToFile(string $feature): string {
-    assert(in_array($feature, static::FEATURES, TRUE));
-    return static::DIR . "/disable-$feature";
-  }
-
-}
diff --git a/web/modules/custom/joinup_core/src/JoinupSwitcherInterface.php b/web/modules/custom/joinup_core/src/JoinupSwitcherInterface.php
deleted file mode 100644
index 9a49e350f8..0000000000
--- a/web/modules/custom/joinup_core/src/JoinupSwitcherInterface.php
+++ /dev/null
@@ -1,39 +0,0 @@
-<?php
-
-declare(strict_types=1);
-
-namespace Drupal\joinup_core;
-
-/**
- * Switcher interface.
- */
-interface JoinupSwitcherInterface {
-
-  /**
-   * Checks if the passed functionality is enabled.
-   *
-   * @param string $feature
-   *   The feature to be checked. E.g. 'config-readonly'.
-   *
-   * @return bool
-   *   Returns true if read-only mode is enabled, false otherwise.
-   */
-  public function isEnabled(string $feature): bool;
-
-  /**
-   * Disables the feature mode.
-   *
-   * @param string $feature
-   *   The feature to be disabled. E.g. 'config-readonly'.
-   */
-  public function disable(string $feature): void;
-
-  /**
-   * Enables read-only mode.
-   *
-   * @param string $feature
-   *   The feature to be disabled. E.g. 'config-readonly'.
-   */
-  public function enable(string $feature): void;
-
-}
diff --git a/web/modules/custom/joinup_user/joinup_user.module b/web/modules/custom/joinup_user/joinup_user.module
index 4097ae0944..cb54526fae 100644
--- a/web/modules/custom/joinup_user/joinup_user.module
+++ b/web/modules/custom/joinup_user/joinup_user.module
@@ -690,15 +690,15 @@ function joinup_user_module_implements_alter(array &$implementations, string $ho
  * Implements hook_user_logout().
  */
 function joinup_user_user_logout(AccountInterface $account): void {
-  /** @var \Drupal\joinup_core\JoinupSwitcherInterface $switcher */
-  $switcher = \Drupal::service('joinup_core.switcher');
+  /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileConfigReadOnly */
+  $fileConfigReadOnly = \Drupal::service('joinup_core.file_config_read_only');
   /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator */
   $cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator');
   $logger = \Drupal::logger('joinup_user');
 
   if ($account->hasPermission('enable/disable config readonly') &&
-    !$switcher->isEnabled('config-readonly')) {
-    $switcher->enable('config-readonly');
+    !$fileConfigReadOnly->isReadOnlyEnabled()) {
+    $fileConfigReadOnly->enableReadOnly();
     $logger->info('Config read-only mode has been restored/enabled.');
     $cacheTagsInvalidator->invalidateTags(['joinup_user:read_only_config']);
   }
diff --git a/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php b/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
index fbff730abb..b274f61879 100644
--- a/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
+++ b/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
@@ -11,7 +11,7 @@
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\joinup_core\JoinupSwitcherInterface;
+use Drupal\joinup_core\ConfigReadOnlyInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -28,7 +28,7 @@ public function __construct(
     array $configuration,
     string $pluginId,
     mixed $pluginDefinition,
-    protected JoinupSwitcherInterface $fileConfigReadOnly,
+    protected ConfigReadOnlyInterface $fileConfigReadOnly,
   ) {
     parent::__construct($configuration, $pluginId, $pluginDefinition);
   }
@@ -41,7 +41,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $configuration,
       $plugin_id,
       $plugin_definition,
-      $container->get('joinup_core.switcher')
+      $container->get('joinup_core.file_config_read_only')
     );
   }
 
@@ -53,7 +53,7 @@ public function build(): array {
       $this->t('You are working on production. Please, be extra cautious!'),
     ];
 
-    if (!$this->fileConfigReadOnly->isEnabled('config-readonly')) {
+    if (!$this->fileConfigReadOnly->isReadOnlyEnabled()) {
       $warnings[] = $this->t('Config readonly is disabled!');
     }
 
-- 
GitLab


From a4a9f32c95f8ffc3808da86186feee5cbd526903 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Thu, 5 Sep 2024 18:38:08 +0300
Subject: [PATCH 4/9] ISAICP-9052: Move EU OSS Catalogue switch to a state var

Also removed some config_readonly functionality that doesn't work anymore.
---
 .opts.yml                                     | 27 ++++++-----
 ...ature => oss_catalogue_visibility.feature} |  9 +++-
 tests/features/eulogin/eulogin.feature        |  2 +
 tests/features/user/developer.feature         | 28 -----------
 tests/src/Context/FeatureContext.php          | 17 -------
 .../src/Context/JoinupOSSSolutionContext.php  | 20 --------
 .../dashboard/src/Form/DashboardForm.php      | 47 ++++++++++---------
 .../joinup_oss_catalogue.install              | 20 ++++++++
 .../joinup_oss_catalogue.module               | 22 ++++++++-
 .../joinup_oss_catalogue.permissions.yml      |  4 ++
 .../joinup_oss_catalogue.services.yml         |  1 +
 .../src/Access/KillSwitchCheck.php            |  4 +-
 .../Commands/JoinupOssCatalogueCommands.php   |  2 +
 .../src/OssRouteSubscriber.php                |  1 +
 .../custom/joinup_user/joinup_user.module     | 18 -------
 .../joinup_user/joinup_user.permissions.yml   |  2 -
 .../src/Plugin/Block/WarningMessageBlock.php  |  5 --
 17 files changed, 102 insertions(+), 127 deletions(-)
 rename tests/features/communities/oss_catalogue/{killswitch.feature => oss_catalogue_visibility.feature} (90%)
 create mode 100644 web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
 delete mode 100644 web/modules/custom/joinup_user/joinup_user.permissions.yml

diff --git a/.opts.yml b/.opts.yml
index 45ec3ab20b..6ba43c612e 100644
--- a/.opts.yml
+++ b/.opts.yml
@@ -6,24 +6,29 @@
 upgrade_commands:
   default:
     - touch disable-config-readonly
-    - ./vendor/bin/drush deploy --yes
-    - ./vendor/bin/drush search-api:reset-tracker --yes
-    - ./vendor/bin/drush joinup:node-access-rebuild
-    - ./vendor/bin/drush joinup:search-api-tasks
-    - ./vendor/bin/drush twigc --yes
+    - vendor/bin/drush deploy --yes
+    - vendor/bin/drush search-api:reset-tracker --yes
+    - vendor/bin/drush joinup:node-access-rebuild
+    - vendor/bin/drush joinup:search-api-tasks
+    - vendor/bin/drush twigc --yes
     - rm disable-config-readonly
-    - ./vendor/bin/drush joinup:unpublish-alert --category
-    - ./scripts/check_status_report.php
+    - vendor/bin/drush joinup:unpublish-alert --category
+    - scripts/check_status_report.php
   append:
+    production:
+      # TODO: Remove this command in ISAICP-9053.
+      - vendor/bin/drush state:set joinup.oss_catalogue_is_disabled 1 --input-format=boolean
     acceptance:
       - touch disable-config-readonly
-      - ./vendor/bin/drush joinup:acc
-      - ./vendor/bin/drush cache:rebuild
+      - vendor/bin/drush joinup:acc
+      - vendor/bin/drush cache:rebuild
       - rm disable-config-readonly
+      # TODO: Remove this command in ISAICP-9053.
+      - vendor/bin/drush state:set joinup.oss_catalogue_is_disabled 1 --input-format=boolean
     ephemeral:
       - touch disable-config-readonly
-      - ./vendor/bin/drush joinup:acc
-      - ./vendor/bin/drush cache:rebuild
+      - vendor/bin/drush joinup:acc
+      - vendor/bin/drush cache:rebuild
       - rm disable-config-readonly
 php_version: 8.3
 extra_pkgs:
diff --git a/tests/features/communities/oss_catalogue/killswitch.feature b/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
similarity index 90%
rename from tests/features/communities/oss_catalogue/killswitch.feature
rename to tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
index a77ba248f2..47def81f2e 100644
--- a/tests/features/communities/oss_catalogue/killswitch.feature
+++ b/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
@@ -1,3 +1,4 @@
+# TODO: Remove this test in ISAICP-9053
 @api @group-clone
 Feature: Test EU OSS Catalogue killswitch
 
@@ -28,7 +29,9 @@ Feature: Test EU OSS Catalogue killswitch
     And I go to "/admin/content/eu-oss-catalogue/log"
     Then the response status code should be 200
 
-    Given I disable EU OSS Catalogue
+    Given I visit "/dashboard"
+    When I press "Disable EU OSS Catalogue"
+    Then I should see the success message "EU OSS Catalogue has been disabled"
 
     Given I am an anonymous user
     When I go to "/eu-oss-catalogue"
@@ -56,7 +59,9 @@ Feature: Test EU OSS Catalogue killswitch
     And I go to "/admin/content/eu-oss-catalogue/log"
     Then the response status code should be 403
 
-    Given I enable EU OSS Catalogue
+    Given I visit "/dashboard"
+    When I press "Enable EU OSS Catalogue"
+    Then I should see the success message "EU OSS Catalogue has been enabled"
 
     Given I am an anonymous user
     When I go to "/eu-oss-catalogue"
diff --git a/tests/features/eulogin/eulogin.feature b/tests/features/eulogin/eulogin.feature
index 2a0be93c14..db96a511c1 100644
--- a/tests/features/eulogin/eulogin.feature
+++ b/tests/features/eulogin/eulogin.feature
@@ -329,6 +329,8 @@ Feature: Log in through EU Login
     When I go to "/user/password"
     Then the response status code should be 404
 
+  # TODO: Re-enable this test in ISAICP-9059
+  @wip
   Scenario: As a developer I can temporary disable the site registration.
     Given CAS users:
       | Username | E-mail          | Password |
diff --git a/tests/features/user/developer.feature b/tests/features/user/developer.feature
index ce9ad8758b..299cf3d57a 100644
--- a/tests/features/user/developer.feature
+++ b/tests/features/user/developer.feature
@@ -55,31 +55,3 @@ Feature: Variety tests for the developer role.
     Given I am logged in as an authenticated
     And I am on the homepage
     Then I should not see the text "Warning: You are working on production. Please, be extra cautious!"
-
-  Scenario: Configuration is locked and unlocked on developers login and logout.
-    Given users:
-      | Username   | Roles     |
-      | Some dev 1 | Developer |
-
-    When I am logged in as "Some dev 1"
-    Then the configuration should be locked
-
-    When I go to "/dashboard"
-    And I press "Disable config readonly"
-    Then I should see the success message "Disabled config readonly"
-    And the configuration should be unlocked
-    And I should not see the button "Disable config readonly"
-    And I should see the button "Enable config readonly"
-    And I should see the warning message "Config readonly is disabled!"
-
-    When I press "Enable config readonly"
-    Then I should see the success message "Enabled config readonly"
-    And the configuration should be locked
-    And I should see the button "Disable config readonly"
-    And I should not see the button "Enable config readonly"
-    And I should not see the warning message "Config readonly is disabled!"
-
-    When I press "Disable config readonly"
-    And I am on the homepage
-    And I click "Sign out"
-    Then the configuration should be locked
diff --git a/tests/src/Context/FeatureContext.php b/tests/src/Context/FeatureContext.php
index 091b7ef476..a303b7baf0 100644
--- a/tests/src/Context/FeatureContext.php
+++ b/tests/src/Context/FeatureContext.php
@@ -2428,23 +2428,6 @@ public function iRunTheQueueWorker(string $queue_name): void {
     sleep(1);
   }
 
-  /**
-   * Asserts that configuration is editable or locked.
-   *
-   * @param string $state
-   *   The state of the configuration. Either 'locked' or 'unlocked'.
-   *
-   * @Then the configuration should be :state
-   */
-  public function theConfigurationState(string $state): void {
-    \assert(in_array($state, ['locked', 'unlocked']));
-
-    /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileReadOnly */
-    $fileReadOnly = \Drupal::service('joinup_core.file_config_read_only');
-
-    Assert::assertTrue($fileReadOnly->isReadOnlyEnabled() === ($state === 'locked'));
-  }
-
   /**
    * Check the items in a queue.
    *
diff --git a/tests/src/Context/JoinupOSSSolutionContext.php b/tests/src/Context/JoinupOSSSolutionContext.php
index 04b7a22877..8f392676e1 100644
--- a/tests/src/Context/JoinupOSSSolutionContext.php
+++ b/tests/src/Context/JoinupOSSSolutionContext.php
@@ -52,24 +52,4 @@ public static function disableOssCron(): void {
     $state->set('eu_oss_catalogue.last_check', strtotime('- 1 hour'));
   }
 
-  /**
-   * Toggles ON/OFF EU OSS Catalogue functionality.
-   *
-   * @param string $toggle
-   *   Could be 'enable', 'disable'.
-   *
-   * @Given I :toggle EU OSS Catalogue
-   */
-  public function toggleEuOssCatalogue(string $toggle): void {
-    assert(in_array($toggle, ['enable', 'disable'], TRUE));
-    $file = DRUPAL_ROOT . '/../disable-eu-oss-catalogue';
-    if ($toggle === 'enable') {
-      unlink($file);
-    }
-    else {
-      touch($file);
-    }
-    \Drupal::getContainer()->get('cache.page')->deleteAll();
-  }
-
 }
diff --git a/web/modules/custom/dashboard/src/Form/DashboardForm.php b/web/modules/custom/dashboard/src/Form/DashboardForm.php
index cd076becfc..0c0a74d909 100644
--- a/web/modules/custom/dashboard/src/Form/DashboardForm.php
+++ b/web/modules/custom/dashboard/src/Form/DashboardForm.php
@@ -5,13 +5,12 @@
 namespace Drupal\dashboard\Form;
 
 use Drupal\Core\Access\AccessResultInterface;
-use Drupal\Core\Cache\CacheTagsInvalidatorInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Form\FormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\State\StateInterface;
 use Drupal\Core\Url;
-use Drupal\joinup_core\ConfigReadOnlyInterface;
 use Drupal\joinup_core\Logger\Handler\MailHandler;
 use Drupal\joinup_core\Logger\Handler\TelegramHandler;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -23,8 +22,7 @@ class DashboardForm extends FormBase {
 
   public function __construct(
     protected StateInterface $state,
-    protected ConfigReadOnlyInterface $fileConfigReadOnly,
-    protected CacheTagsInvalidatorInterface $invalidator,
+    protected CacheBackendInterface $pageCache,
   ) {
   }
 
@@ -34,8 +32,7 @@ public function __construct(
   public static function create(ContainerInterface $container): self {
     return new static(
       $container->get('state'),
-      $container->get('joinup_core.file_config_read_only'),
-      $container->get('cache_tags.invalidator')
+      $container->get('cache.page'),
     );
   }
 
@@ -102,18 +99,20 @@ public function buildForm(array $form, FormStateInterface $form_state): array {
       '#value' => $this->t('Submit'),
     ];
 
-    $hasEnableDisablePermission = $this->currentUser()->hasPermission('enable/disable config readonly');
+    // @todo Remove this button in ISAICP-9053.
+    $hasEnableDisablePermission = $this->currentUser()->hasPermission('toggle eu oss catalogue state');
     $form['actions']['enable'] = [
       '#type' => 'submit',
-      '#value' => $this->t('Enable config readonly'),
-      '#submit' => ['::enableConfigSubmitForm'],
-      '#access' => $hasEnableDisablePermission && !$this->fileConfigReadOnly->isReadOnlyEnabled(),
+      '#value' => $this->t('Enable EU OSS Catalogue'),
+      '#submit' => ['::enableEuOssCatalogueSubmitForm'],
+      '#access' => $hasEnableDisablePermission && !joinup_oss_catalogue_is_enabled(),
     ];
+    // @todo Remove this button in ISAICP-9053.
     $form['actions']['disable'] = [
       '#type' => 'submit',
-      '#value' => $this->t('Disable config readonly'),
-      '#submit' => ['::disableConfigSubmitForm'],
-      '#access' => $hasEnableDisablePermission && $this->fileConfigReadOnly->isReadOnlyEnabled(),
+      '#value' => $this->t('Disable EU OSS Catalogue'),
+      '#submit' => ['::disableEuOssCatalogueSubmitForm'],
+      '#access' => $hasEnableDisablePermission && joinup_oss_catalogue_is_enabled(),
     ];
 
     return $form;
@@ -121,20 +120,26 @@ public function buildForm(array $form, FormStateInterface $form_state): array {
 
   /**
    * Enable the configuration submit form.
+   *
+   * @todo Remove this method in ISAICP-9053.
    */
-  public function enableConfigSubmitForm(): void {
-    $this->fileConfigReadOnly->enableReadOnly();
-    $this->invalidator->invalidateTags(['joinup_user:read_only_config']);
-    $this->messenger()->addStatus($this->t('Enabled config readonly'));
+  public function enableEuOssCatalogueSubmitForm(): void {
+    $this->state->delete('joinup.oss_catalogue_is_disabled');
+    // Only clear cache for anonymous.
+    $this->pageCache->deleteAll();
+    $this->messenger()->addStatus($this->t('EU OSS Catalogue has been enabled'));
   }
 
   /**
    * Disables the config submit form.
+   *
+   * @todo Remove this method in ISAICP-9053.
    */
-  public function disableConfigSubmitForm(): void {
-    $this->fileConfigReadOnly->disableReadOnly();
-    $this->invalidator->invalidateTags(['joinup_user:read_only_config']);
-    $this->messenger()->addStatus($this->t('Disabled config readonly'));
+  public function disableEuOssCatalogueSubmitForm(): void {
+    $this->state->set('joinup.oss_catalogue_is_disabled', TRUE);
+    // Only clear cache for anonymous.
+    $this->pageCache->deleteAll();
+    $this->messenger()->addStatus($this->t('EU OSS Catalogue has been disabled'));
   }
 
   /**
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
new file mode 100644
index 0000000000..8de55f0c53
--- /dev/null
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.install
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Install, update hooks for Joinup OSS Catalogue module.
+ */
+
+declare(strict_types=1);
+
+use Drupal\joinup_oss_catalogue\OssCatalogueCollectionInterface;
+
+/**
+ * Publish the EU OSS Catalogue collection.
+ */
+function joinup_oss_catalogue_update_200001(): void {
+  $collection = \Drupal::entityTypeManager()->getStorage('rdf_entity')
+    ->load(OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID);
+  $collection->skip_notification = TRUE;
+  $collection->set('field_ar_state', 'published')->save();
+}
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
index b3e7954502..2673fdafc4 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
@@ -10,7 +10,6 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\Site\Settings;
 use Drupal\Core\Url;
 use Drupal\block\BlockInterface;
 use Drupal\eu_oss_catalogue\Entity\OssSolutionInterface;
@@ -18,6 +17,7 @@
 use Drupal\joinup_oss_catalogue\Entity\JoinupOssSolution;
 use Drupal\joinup_oss_catalogue\OssCatalogueCollectionInterface;
 use Drupal\node\NodeInterface;
+use Drupal\rdf_entity\RdfInterface;
 
 /**
  * Implements hook_views_plugins_argument_alter().
@@ -190,13 +190,29 @@ function joinup_oss_catalogue_preprocess_views_view__eu_oss_catalogue_admin_caro
  *
  * @return bool
  *   Whether the EU OSS Catalogue functionality is enabled.
+ *
+ * @todo Remove this function in ISAICP-9053.
  */
 function joinup_oss_catalogue_is_enabled(): bool {
-  return Settings::get('eu_oss_catalogue')['enabled'] ?? FALSE;
+  return !\Drupal::state()->get('joinup.oss_catalogue_is_disabled', FALSE);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_access().
+ *
+ * @todo Remove this function in ISAICP-9053.
+ */
+function joinup_oss_catalogue_rdf_entity_access(RdfInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
+  if ($entity->id() === OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID) {
+    return AccessResult::forbiddenIf(!joinup_oss_catalogue_is_enabled())->addCacheableDependency($entity);
+  }
+  return AccessResult::neutral();
 }
 
 /**
  * Implements hook_module_implements_alter().
+ *
+ * @todo Remove this function in ISAICP-9053.
  */
 function joinup_oss_catalogue_module_implements_alter(array &$implementations, string $hook): void {
   if ($hook === 'cron') {
@@ -209,6 +225,8 @@ function joinup_oss_catalogue_module_implements_alter(array &$implementations, s
 
 /**
  * Implements hook_cron().
+ *
+ * @todo Remove this function in ISAICP-9053.
  */
 function joinup_oss_catalogue_cron(): void {
   if (joinup_oss_catalogue_is_enabled()) {
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.permissions.yml b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.permissions.yml
index a2b76e0424..7b38dbd014 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.permissions.yml
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.permissions.yml
@@ -1,2 +1,6 @@
 eu oss catalogue log:
   title: 'Access eu oss catalogue log'
+# TODO: Drop this permission in ISAICP-9053.
+toggle eu oss catalogue state:
+  title: 'Toggle ON/OFF the EU OSS Catalogue'
+  restrict access: true
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.services.yml b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.services.yml
index bb66d2ef83..8b719cbe32 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.services.yml
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.services.yml
@@ -16,6 +16,7 @@ services:
     arguments:
       - '@queue'
 
+  # TODO: Remove this service in ISAICP-9053.
   joinup_oss_catalogue.killswitch_check:
     class: Drupal\joinup_oss_catalogue\Access\KillSwitchCheck
     tags:
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
index a13856cc66..b9bcd590b1 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
@@ -15,8 +15,10 @@
 
 /**
  * Access checker for _eu_oss_catalogue_check_enabled routes.
+ *
+ * @todo Remove this service in ISAICP-9053.
  */
-class KillSwitchCheck implements AccessInterface {
+final readonly class KillSwitchCheck implements AccessInterface {
 
   public function __construct(
     protected readonly EuOssCatalogueInterface $service,
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Drush/Commands/JoinupOssCatalogueCommands.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Drush/Commands/JoinupOssCatalogueCommands.php
index a792d38b20..78e167c5de 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Drush/Commands/JoinupOssCatalogueCommands.php
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Drush/Commands/JoinupOssCatalogueCommands.php
@@ -18,6 +18,8 @@
 
 /**
  * EU OSS Catalogue commands replacements.
+ *
+ * @todo Remove this class in ISAICP-9053.
  */
 class JoinupOssCatalogueCommands extends DrushCommands {
 
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
index fc04b4f9b5..8ac9a157b3 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
@@ -26,6 +26,7 @@ public function alterRoutes(RouteCollection $collection): void {
       $route->setRequirement('_oss_access_check', 'TRUE');
     }
 
+    // @todo Remove this route alteration in ISAICP-9053.
     $routeNames = [
       'eu_oss_catalogue.provider.collection',
       'eu_oss_catalogue.hosting_platform_add',
diff --git a/web/modules/custom/joinup_user/joinup_user.module b/web/modules/custom/joinup_user/joinup_user.module
index cb54526fae..b721c4bad5 100644
--- a/web/modules/custom/joinup_user/joinup_user.module
+++ b/web/modules/custom/joinup_user/joinup_user.module
@@ -686,24 +686,6 @@ function joinup_user_module_implements_alter(array &$implementations, string $ho
   }
 }
 
-/**
- * Implements hook_user_logout().
- */
-function joinup_user_user_logout(AccountInterface $account): void {
-  /** @var \Drupal\joinup_core\ConfigReadOnlyInterface $fileConfigReadOnly */
-  $fileConfigReadOnly = \Drupal::service('joinup_core.file_config_read_only');
-  /** @var \Drupal\Core\Cache\CacheTagsInvalidatorInterface $cacheTagsInvalidator */
-  $cacheTagsInvalidator = \Drupal::service('cache_tags.invalidator');
-  $logger = \Drupal::logger('joinup_user');
-
-  if ($account->hasPermission('enable/disable config readonly') &&
-    !$fileConfigReadOnly->isReadOnlyEnabled()) {
-    $fileConfigReadOnly->enableReadOnly();
-    $logger->info('Config read-only mode has been restored/enabled.');
-    $cacheTagsInvalidator->invalidateTags(['joinup_user:read_only_config']);
-  }
-}
-
 /**
  * Implements hook_entity_base_field_info().
  */
diff --git a/web/modules/custom/joinup_user/joinup_user.permissions.yml b/web/modules/custom/joinup_user/joinup_user.permissions.yml
deleted file mode 100644
index 491ef3dd1d..0000000000
--- a/web/modules/custom/joinup_user/joinup_user.permissions.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-enable/disable config readonly:
-  title: 'Enable/disable config readonly'
diff --git a/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php b/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
index b274f61879..eb54e480d4 100644
--- a/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
+++ b/web/modules/custom/joinup_user/src/Plugin/Block/WarningMessageBlock.php
@@ -53,17 +53,12 @@ public function build(): array {
       $this->t('You are working on production. Please, be extra cautious!'),
     ];
 
-    if (!$this->fileConfigReadOnly->isReadOnlyEnabled()) {
-      $warnings[] = $this->t('Config readonly is disabled!');
-    }
-
     $build['content'] = [
       '#theme' => 'status_messages',
       '#message_list' => [
         'warning' => $warnings,
       ],
     ];
-    $build['#cache']['tags'][] = 'joinup_user:read_only_config';
 
     return $build;
   }
-- 
GitLab


From 51d222121b82be2f68dcce5a70b5a82104ae249b Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Fri, 6 Sep 2024 10:14:01 +0300
Subject: [PATCH 5/9] ISAICP-9052: Sanitize the state variable.

---
 .../Commands/JoinupDataSanitizationCommands.php | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/web/modules/custom/joinup_core/src/Commands/JoinupDataSanitizationCommands.php b/web/modules/custom/joinup_core/src/Commands/JoinupDataSanitizationCommands.php
index fc64beb5af..8734c8dff3 100644
--- a/web/modules/custom/joinup_core/src/Commands/JoinupDataSanitizationCommands.php
+++ b/web/modules/custom/joinup_core/src/Commands/JoinupDataSanitizationCommands.php
@@ -47,6 +47,8 @@ public function sanitize($result, CommandData $commandData): void {
     $this->sanitizeLogAlertsSettings();
     $this->sanitizeEmailConfirmerData();
     $this->sanitizeDownloadEventData();
+    // @todo Remove this call in ISAICP-9053.
+    $this->sanitizeEuOssCatalogueSwitcher();
   }
 
   /**
@@ -60,6 +62,8 @@ public function messages(&$messages, InputInterface $input): void {
     $messages[] = dt('Sanitize log alerts settings.');
     $messages[] = dt('Sanitize email confirmer data.');
     $messages[] = dt('Sanitize download event data.');
+    // @todo Remove this message in ISAICP-9053.
+    $messages[] = dt('Sanitize EU OSS Catalogue switcher.');
   }
 
   /**
@@ -179,4 +183,17 @@ protected function sanitizeEntityFieldsData(string $entityType, array $fieldName
     }
   }
 
+  /**
+   * Sanitizes the EU OSS Catalogue switcher.
+   *
+   * @todo Remove this method in ISAICP-9053.
+   */
+  protected function sanitizeEuOssCatalogueSwitcher(): void {
+    $this->db->delete('key_value')
+      ->condition('collection', 'state')
+      ->condition('name', 'joinup.oss_catalogue_is_disabled')
+      ->execute();
+    $this->logger()->success(dt('joinup.oss_catalogue_is_disabled state var sanitized.'));
+  }
+
 }
-- 
GitLab


From e5e8e16e79b6cfe08d783fc2b6b773e72042a4e4 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Fri, 6 Sep 2024 16:42:53 +0300
Subject: [PATCH 6/9] ISAICP-9052: Switch should hide also OSS Solutions.

---
 .../oss_catalogue_visibility.feature          | 47 +++++++++++++++----
 .../joinup_oss_catalogue.module               | 22 +++++++--
 .../src/Access/KillSwitchCheck.php            | 11 -----
 .../src/OssRouteSubscriber.php                |  1 -
 4 files changed, 54 insertions(+), 27 deletions(-)

diff --git a/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature b/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
index 47def81f2e..de3d282ba2 100644
--- a/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
+++ b/tests/features/communities/oss_catalogue/oss_catalogue_visibility.feature
@@ -3,11 +3,19 @@
 Feature: Test EU OSS Catalogue killswitch
 
   Scenario: Switch ON/OFF
+    Given oss_solution content:
+      | title                  | oss_development_status | oss_source        | oss_categories     | oss_short_description | status    |
+      | The Opensource Panacea | development            | developers_italia | content-management | The cure              | published |
 
     When I go to "/eu-oss-catalogue"
     Then the response status code should be 200
+    But I should not see "Sign in to continue"
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 200
+    But I should not see "Sign in to continue"
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
+    But I should not see "Sign in to continue"
 
     Given I am logged in as an "authenticated"
     When I go to "/eu-oss-catalogue"
@@ -16,6 +24,8 @@ Feature: Test EU OSS Catalogue killswitch
     Then the response status code should be 200
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 200
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
 
     Given I am logged in as a "developer"
     When I go to "/eu-oss-catalogue"
@@ -28,6 +38,8 @@ Feature: Test EU OSS Catalogue killswitch
     Then the response status code should be 200
     And I go to "/admin/content/eu-oss-catalogue/log"
     Then the response status code should be 200
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
 
     Given I visit "/dashboard"
     When I press "Disable EU OSS Catalogue"
@@ -38,6 +50,8 @@ Feature: Test EU OSS Catalogue killswitch
     Then I should see the heading "Sign in to continue"
     And I go to "/eu-oss-catalogue/solutions"
     Then I should see the heading "Sign in to continue"
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then I should see the heading "Sign in to continue"
 
     Given I am logged in as an "authenticated"
     When I go to "/eu-oss-catalogue"
@@ -46,6 +60,8 @@ Feature: Test EU OSS Catalogue killswitch
     Then the response status code should be 403
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 403
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 403
 
     Given I am logged in as a "developer"
     When I go to "/eu-oss-catalogue"
@@ -58,33 +74,44 @@ Feature: Test EU OSS Catalogue killswitch
     Then the response status code should be 403
     And I go to "/admin/content/eu-oss-catalogue/log"
     Then the response status code should be 403
+    # Developer is and amin role, they can still see the node.
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
 
     Given I visit "/dashboard"
     When I press "Enable EU OSS Catalogue"
     Then I should see the success message "EU OSS Catalogue has been enabled"
 
-    Given I am an anonymous user
     When I go to "/eu-oss-catalogue"
     Then the response status code should be 200
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 200
+    And I go to "/admin/content/eu-oss-catalogue"
+    Then the response status code should be 200
+    And I go to "/admin/content/eu-oss-catalogue/oss-solutions"
+    Then the response status code should be 200
+    And I go to "/admin/content/eu-oss-catalogue/log"
+    Then the response status code should be 200
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
 
-    Given I am logged in as an "authenticated"
+    Given I am an anonymous user
     When I go to "/eu-oss-catalogue"
-    And I check "I agree to the Legal notice document"
-    And I press "Submit"
     Then the response status code should be 200
+    But I should not see "Sign in to continue"
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 200
+    But I should not see "Sign in to continue"
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
+    Then the response status code should be 200
+    But I should not see "Sign in to continue"
 
-    Given I am logged in as a "developer"
+    Given I am logged in as an "authenticated"
     When I go to "/eu-oss-catalogue"
+    And I check "I agree to the Legal notice document"
+    And I press "Submit"
     Then the response status code should be 200
     And I go to "/eu-oss-catalogue/solutions"
     Then the response status code should be 200
-    And I go to "/admin/content/eu-oss-catalogue"
-    Then the response status code should be 200
-    And I go to "/admin/content/eu-oss-catalogue/oss-solutions"
-    Then the response status code should be 200
-    And I go to "/admin/content/eu-oss-catalogue/log"
+    And I go to the content page of the type "oss_solution" with the title "The Opensource Panacea"
     Then the response status code should be 200
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
index 2673fdafc4..8212527be9 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/joinup_oss_catalogue.module
@@ -9,15 +9,17 @@
 
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\block\BlockInterface;
+use Drupal\collection\Entity\CollectionInterface;
+use Drupal\custom_page\Entity\CustomPageInterface;
 use Drupal\eu_oss_catalogue\Entity\OssSolutionInterface;
 use Drupal\joinup_group\Entity\GroupInterface;
 use Drupal\joinup_oss_catalogue\Entity\JoinupOssSolution;
 use Drupal\joinup_oss_catalogue\OssCatalogueCollectionInterface;
 use Drupal\node\NodeInterface;
-use Drupal\rdf_entity\RdfInterface;
 
 /**
  * Implements hook_views_plugins_argument_alter().
@@ -198,13 +200,23 @@ function joinup_oss_catalogue_is_enabled(): bool {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_access().
+ * Implements hook_entity_access().
  *
  * @todo Remove this function in ISAICP-9053.
  */
-function joinup_oss_catalogue_rdf_entity_access(RdfInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
-  if ($entity->id() === OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID) {
-    return AccessResult::forbiddenIf(!joinup_oss_catalogue_is_enabled())->addCacheableDependency($entity);
+function joinup_oss_catalogue_entity_access(EntityInterface $entity): AccessResultInterface {
+  if (
+    // The entity is the EU OSS Catalogue collection...
+    ($entity instanceof CollectionInterface && $entity->id() === OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID) ||
+    // ...or the EU OSS Catalogue collection landing page...
+    ($entity instanceof CustomPageInterface && $entity->uuid() === OssCatalogueCollectionInterface::LANDING_PAGE) ||
+    // ...or an OSS Solution.
+    ($entity instanceof OssSolutionInterface)
+  ) {
+    return AccessResult::forbiddenIf(
+      !joinup_oss_catalogue_is_enabled(),
+      'EU OSS Catalogue is disabled',
+    )->addCacheableDependency($entity);
   }
   return AccessResult::neutral();
 }
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
index b9bcd590b1..5c68df86ad 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/Access/KillSwitchCheck.php
@@ -8,9 +8,7 @@
 use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Routing\Access\AccessInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\collection\Entity\CollectionInterface;
 use Drupal\eu_oss_catalogue\EuOssCatalogueInterface;
-use Drupal\joinup_oss_catalogue\OssCatalogueCollectionInterface;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -35,15 +33,6 @@ public function access(Route $route, RouteMatchInterface $routeMatch): AccessRes
     if ($requirement !== 'TRUE') {
       throw new \Exception("The _eu_oss_catalogue_check_enabled route requirement when present can only have 'TRUE' as value but '$requirement' was given.");
     }
-
-    if ($routeMatch->getRouteName() === 'entity.rdf_entity.canonical') {
-      $rdf_entity = $routeMatch->getParameter('rdf_entity');
-      if (!$rdf_entity instanceof CollectionInterface || $rdf_entity->id() !== OssCatalogueCollectionInterface::COLLECTION_ENTITY_ID) {
-        // Any page except the EU OSS Catalogue landing page is allowed.
-        return AccessResult::allowed();
-      }
-    }
-
     return joinup_oss_catalogue_is_enabled() ? AccessResult::allowed() : AccessResult::forbidden('EU OSS Catalogue functionality is disabled');
   }
 
diff --git a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
index 8ac9a157b3..bffe6453ee 100644
--- a/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
+++ b/web/modules/custom/joinup_communities/joinup_oss_catalogue/src/OssRouteSubscriber.php
@@ -34,7 +34,6 @@ public function alterRoutes(RouteCollection $collection): void {
       'view.eu_oss_catalogue_admin.page',
       'view.eu_oss_catalogue_log.page',
       'view.search_oss_catalogue.search',
-      'entity.rdf_entity.canonical',
     ];
     foreach ($routeNames as $routeName) {
       if ($route = $collection->get($routeName)) {
-- 
GitLab


From 0d0c859565ad90d91c186bd428e9bc29f4145a97 Mon Sep 17 00:00:00 2001
From: Herve Donner <hervedonner@gmail.com>
Date: Fri, 6 Sep 2024 13:16:53 +0200
Subject: [PATCH 7/9] ISAICP-9060: Add group filter and extra fields in
 ExportUserListForm.

---
 .../joinup/src/Form/ExportUserListForm.php    | 162 ++++++++++++------
 1 file changed, 113 insertions(+), 49 deletions(-)

diff --git a/web/profiles/joinup/src/Form/ExportUserListForm.php b/web/profiles/joinup/src/Form/ExportUserListForm.php
index 2e0459f865..efc95efa0a 100644
--- a/web/profiles/joinup/src/Form/ExportUserListForm.php
+++ b/web/profiles/joinup/src/Form/ExportUserListForm.php
@@ -5,6 +5,7 @@
 namespace Drupal\joinup\Form;
 
 use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\Form\FormBase;
@@ -12,6 +13,10 @@
 use Drupal\Core\Url;
 use Drupal\csv_serialization\Encoder\CsvEncoder;
 use Drupal\joinup_user\EntityAuthorshipHelperInterface;
+use Drupal\og\MembershipManagerInterface;
+use Drupal\og\OgMembershipInterface;
+use Drupal\og\OgRoleInterface;
+use Drupal\taxonomy\TermInterface;
 use Drupal\user\UserInterface;
 use Drupal\user\UserStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -72,53 +77,31 @@ class ExportUserListForm extends FormBase {
       'input' => ['getFieldValues', ['uid']],
       'output' => ['getFirstValue', 'formatProfileLink'],
     ],
+    'Country of origin' => [
+      'input' => ['getFieldValues', ['field_user_nationality']],
+      'output' => ['formatTaxonomyTermLabel'],
+    ],
+    'Professional domain' => [
+      'input' => ['getFieldValues', ['field_user_professional_domain']],
+      'output' => ['formatTaxonomyTermLabel'],
+    ],
+    'Organisation' => [
+      'input' => ['getFieldValues', ['field_user_organisation']],
+      'output' => ['getAllValues', 'formatString'],
+    ],
+    'Business title' => [
+      'input' => ['getFieldValues', ['field_user_business_title']],
+      'output' => ['getFirstValue', 'formatString'],
+    ],
   ];
 
-  /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The entity authorship helper service.
-   *
-   * @var \Drupal\joinup_user\EntityAuthorshipHelperInterface
-   */
-  protected $entityAuthorshipHelper;
-
-  /**
-   * The filesystem service.
-   *
-   * @var \Drupal\Core\File\FileSystemInterface
-   */
-  protected $fileSystem;
-
-  /**
-   * The CSRF token generator.
-   *
-   * @var \Drupal\Core\Access\CsrfTokenGenerator
-   */
-  protected $csrfTokenGenerator;
-
-  /**
-   * Constructs an ExportUserListForm.
-   *
-   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
-   *   The entity type manager.
-   * @param \Drupal\joinup_user\EntityAuthorshipHelperInterface $entityAuthorshipHelper
-   *   The entity authorship helper service.
-   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
-   *   The filesystem service.
-   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfTokenGenerator
-   *   The CSRF token generator service.
-   */
-  public function __construct(EntityTypeManagerInterface $entityTypeManager, EntityAuthorshipHelperInterface $entityAuthorshipHelper, FileSystemInterface $fileSystem, CsrfTokenGenerator $csrfTokenGenerator) {
-    $this->entityTypeManager = $entityTypeManager;
-    $this->entityAuthorshipHelper = $entityAuthorshipHelper;
-    $this->fileSystem = $fileSystem;
-    $this->csrfTokenGenerator = $csrfTokenGenerator;
+  public function __construct(
+    protected EntityTypeManagerInterface $entityTypeManager,
+    protected EntityAuthorshipHelperInterface $entityAuthorshipHelper,
+    protected MembershipManagerInterface $membershipManager,
+    protected FileSystemInterface $fileSystem,
+    protected CsrfTokenGenerator $csrfTokenGenerator,
+  ) {
   }
 
   /**
@@ -128,6 +111,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('entity_type.manager'),
       $container->get('joinup_user.entity_authorship_helper'),
+      $container->get('og.membership_manager'),
       $container->get('file_system'),
       $container->get('csrf_token')
     );
@@ -153,6 +137,17 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#required' => TRUE,
     ];
 
+    $form['group'] = [
+      '#type' => 'entity_autocomplete',
+      '#title' => $this->t('Group'),
+      '#description' => $this->t('Filter on a specific collection / solution.'),
+      '#maxlength' => 1024,
+      '#target_type' => 'rdf_entity',
+      '#selection_settings' => [
+        'target_bundles' => ['collection', 'solution'],
+      ],
+    ];
+
     $form['actions'] = [
       '#type' => 'actions',
     ];
@@ -168,9 +163,18 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
-    // Retrieve all user IDs, but exclude the anonymous user.
-    $user_ids = $this->getUserStorage()->getQuery()->accessCheck(FALSE)->execute();
-    unset($user_ids[0]);
+    $group_id = $form_state->getValue('group');
+    if ($group_id) {
+      // Retrieve all user IDs for a specific group.
+      $group_entity = $this->entityTypeManager->getStorage('rdf_entity')->load($group_id);
+      \assert($group_entity instanceof EntityInterface);
+      $user_ids = $this->getGroupMemberUids($group_entity);
+    }
+    else {
+      // Retrieve all user IDs, but exclude the anonymous user.
+      $user_ids = $this->getUserStorage()->getQuery()->accessCheck(FALSE)->execute();
+      unset($user_ids[0]);
+    }
 
     // Split up the work in batches of 250 users.
     $form_class = $this;
@@ -314,6 +318,19 @@ protected function getIsAuthor(UserInterface $user): bool {
     return !empty($this->entityAuthorshipHelper->getEntityIdsAuthoredByUser($user->id(), ['published']));
   }
 
+  /**
+   * Output processor; returns all field values of a multivalue array.
+   *
+   * @param array $value
+   *   The multivalue array.
+   *
+   * @return mixed
+   *   The first value.
+   */
+  protected function getAllValues(array $value): mixed {
+    return array_filter(array_column($value, 'value'));
+  }
+
   /**
    * Output processor; returns the first value of a multivalue array.
    *
@@ -343,6 +360,10 @@ protected function getFirstValue(array $value): mixed {
    *   The value as a string.
    */
   protected function formatString($value): string {
+    if (\is_array($value)) {
+      return implode(PHP_EOL, $value);
+    }
+
     return (string) $value;
   }
 
@@ -373,7 +394,7 @@ protected function formatRoles(array $values): string {
       return $value['target_id'];
     }, $values);
     sort($values);
-    return implode(',', $values);
+    return implode(PHP_EOL, $values);
   }
 
   /**
@@ -419,6 +440,28 @@ protected function formatProfileLink($value): string {
     return Url::fromRoute('entity.user.canonical', ['user' => (int) $value])->setAbsolute()->toString();
   }
 
+  /**
+   * Output processor; returns taxonomy term labels.
+   *
+   * @param array $values
+   *   The field values.
+   *
+   * @return string
+   *   The term labels.
+   */
+  protected function formatTaxonomyTermLabel(array $values): string {
+    $ids = array_filter(array_column($values, 'target_id'));
+    if (empty($ids)) {
+      return '';
+    }
+
+    $storage = $this->entityTypeManager->getStorage('taxonomy_term');
+    $labels = array_map(static function (TermInterface $term): string {
+      return $term->label();
+    }, $storage->loadMultiple($ids));
+    return implode(PHP_EOL, $labels);
+  }
+
   /**
    * Returns the user storage.
    *
@@ -429,4 +472,25 @@ protected function getUserStorage(): UserStorageInterface {
     return $this->entityTypeManager->getStorage('user');
   }
 
+  /**
+   * Returns user IDs of all active members for the given OG group.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $group
+   *   The group entity.
+   *
+   * @return array
+   *   An array of user IDs.
+   */
+  public function getGroupMemberUids(EntityInterface $group): array {
+    $og_membership_ids = $this->membershipManager->getGroupMembershipIdsByRoleNames($group, [OgRoleInterface::AUTHENTICATED]);
+    if (empty($og_membership_ids)) {
+      return [];
+    }
+
+    $memberships = $this->entityTypeManager->getStorage('og_membership')->loadMultiple($og_membership_ids);
+    return array_values(array_map(static function (OgMembershipInterface $membership): int {
+      return (int) $membership->getOwnerId();
+    }, $memberships));
+  }
+
 }
-- 
GitLab


From b6cdfba335de2bc743060c3f0a34259dbec67a88 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Thu, 5 Sep 2024 09:29:54 +0300
Subject: [PATCH 8/9] ISAICP-9045: Temporarily disable a test (until
 ISAICP-9055).

---
 tests/features/communities/oss_catalogue/oss_catalogue.feature | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/features/communities/oss_catalogue/oss_catalogue.feature b/tests/features/communities/oss_catalogue/oss_catalogue.feature
index e80971b6a5..fa76710b68 100644
--- a/tests/features/communities/oss_catalogue/oss_catalogue.feature
+++ b/tests/features/communities/oss_catalogue/oss_catalogue.feature
@@ -131,7 +131,7 @@ Feature:
     When I go to the content page of the type "oss_solution" with the title "Foo"
     Then I should see the text "Foo's GIT description"
 
-  @loggedErrors
+  @wip @loggedErrors
   Scenario: All necessary OSS Solution fields are visible on node page.
     Given oss_contact content:
       | title           |
-- 
GitLab


From 9d56ada4fcc3c050395160a23c41ff0019ea756f Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Fri, 6 Sep 2024 18:48:42 +0300
Subject: [PATCH 9/9] ISAICP-9060: Fix PHPUnit test.

---
 web/profiles/joinup/tests/src/Unit/ExportUserListFormTest.php | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/web/profiles/joinup/tests/src/Unit/ExportUserListFormTest.php b/web/profiles/joinup/tests/src/Unit/ExportUserListFormTest.php
index 9722b3a088..81419e1aea 100644
--- a/web/profiles/joinup/tests/src/Unit/ExportUserListFormTest.php
+++ b/web/profiles/joinup/tests/src/Unit/ExportUserListFormTest.php
@@ -10,6 +10,7 @@
 use Drupal\Tests\UnitTestCase;
 use Drupal\joinup\Form\ExportUserListForm;
 use Drupal\joinup_user\EntityAuthorshipHelperInterface;
+use Drupal\og\MembershipManagerInterface;
 
 /**
  * Tests certain atomic methods of ExportUserListForm.
@@ -34,6 +35,7 @@ public function setUp(): void {
     $this->formInstance = new ExportUserListForm(
       $this->prophesize(EntityTypeManagerInterface::class)->reveal(),
       $this->prophesize(EntityAuthorshipHelperInterface::class)->reveal(),
+      $this->prophesize(MembershipManagerInterface::class)->reveal(),
       $this->prophesize(FileSystemInterface::class)->reveal(),
       $this->prophesize(CsrfTokenGenerator::class)->reveal()
     );
-- 
GitLab