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