diff --git a/.github/workflows/test.yml b/.github/workflows/tests.yml similarity index 93% rename from .github/workflows/test.yml rename to .github/workflows/tests.yml index bf21ccc4e..17b425c78 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/tests.yml @@ -12,6 +12,7 @@ on: env: DRUPAL_TESTING_COMPOSER_PROJECT: thunder/thunder-project DRUPAL_TESTING_COMPOSER_PROJECT_VERSION: "^4.0@stable" + DRUPAL_TESTING_CLEANUP: false DRUPAL_TESTING_DATABASE_USER: root DRUPAL_TESTING_DATABASE_PASSWORD: root DRUPAL_TESTING_TEST_DUMP_FILE: site-dump.tar.gz @@ -28,6 +29,7 @@ env: DRUPAL_TESTING_PARALLEL_TESTING: false MINK_DRIVER_ARGS_WEBDRIVER: '["chrome", {"browserName":"chrome","chromeOptions":{"args":["--disable-gpu","--headless", "--no-sandbox", "--disable-dev-shm-usage"]}}, "http://127.0.0.1:9515"]' SIMPLETEST_BASE_URL: http://thunder-testing:8888 + BROWSERTEST_OUTPUT_DIRECTORY: /tmp SKIP_TEST_CLEANUP: true # The following variable set the version that the upgrade test starts with. DRUPAL_TESTING_UPGRADE_COMPOSER_PROJECT_VERSION: 3.0.12 @@ -38,7 +40,7 @@ jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 services: mysql: @@ -50,7 +52,7 @@ jobs: strategy: matrix: - PHP_VERSION: [ '8.1' ] + PHP_VERSION: [ '8.1', '8.3' ] env: DRUPAL_TESTING_TEST_DEPRECATION: true @@ -106,7 +108,7 @@ jobs: test-max: needs: build - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 services: mysql: @@ -124,7 +126,7 @@ jobs: strategy: fail-fast: false matrix: - PHP_VERSION: [ '8.1' ] + PHP_VERSION: [ '8.3' ] CHUNK: [ 1, 2, 3 ] steps: @@ -168,9 +170,16 @@ jobs: THUNDER_TEST_CHUNK: ${{ matrix.CHUNK }} DRUPAL_TESTING_TEST_PATH: /tmp/test/thunder/install/docroot/profiles/contrib/thunder/tests/src/TestSuites/ThunderTestSuite.php + - name: Upload test output + uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: text-max-output-${{ matrix.CHUNK }}-${{ matrix.PHP_VERSION }} + path: /tmp/test/thunder/install/docroot/sites/simpletest/browser_output/ + test-upgrade: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 services: mysql: @@ -281,7 +290,7 @@ jobs: test-min: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 services: mysql: @@ -336,7 +345,9 @@ jobs: run: test-drupal-project prepare_build - name: Install suggested dependencies - run: composer require "league/container:^4.2" "consolidation/config:^2.1.2" --no-update --no-progress --working-dir=/tmp/test/thunder/install + run: | + composer require "league/container:^4.2" "drupal/core-dev:^10.3" --no-update --no-progress --working-dir=/tmp/test/thunder/install + composer config allow-plugins.php-http/discovery true --no-plugins --working-dir=/tmp/test/thunder/install - name: Build the docroot run: test-drupal-project build diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f6899bab..1456541b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,34 @@ # Changelog +## [7.3.3](https://github.com/thunder/thunder-distribution/tree/7.3.3) 2024-08-22 + +[Full Changelog](https://github.com/thunder/thunder-distribution/compare/7.3.2...7.3.3) + +Add search api GraphQl schema and data producer. + +## [7.3.2](https://github.com/thunder/thunder-distribution/tree/7.3.2) 2024-08-14 + +[Full Changelog](https://github.com/thunder/thunder-distribution/compare/7.3.1...7.3.2) + +* [Issue #3462165: Add focal_point patch](https://www.drupal.org/node/3462165) + +## [7.3.1](https://github.com/thunder/thunder-distribution/tree/7.3.1) 2024-06-024 + +[Full Changelog](https://github.com/thunder/thunder-distribution/compare/7.3.0...7.3.1) + +Add patches for upstream issues. + +* [Issue #3465364: Fatal error when changing password when password_policy_history is enabled](https://www.drupal.org/project/password_policy/issues/3465364) +* [Issue #3455558: There is no visible change to a toggle when pressed (but it does trigger conditional fields, value is saved, etc)](https://www.drupal.org/project/gin/issues/3455558) + +## [7.3.0](https://github.com/thunder/thunder-distribution/tree/7.1.0) 2024-06-024 + +[Full Changelog](https://github.com/thunder/thunder-distribution/compare/7.2.2...7.3.0) + +* Drupal 10.3 compatibility. +* Updated Gin theme. +* PHP8.3 compatibility. + ## [7.2.2](https://github.com/thunder/thunder-distribution/tree/7.2.2) 2024-04-30 [Full Changelog](https://github.com/thunder/thunder-distribution/compare/7.2.1...7.2.2) diff --git a/README.md b/README.md index 0569bc307..35e66e0a9 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ For general help using Thunder, please refer to [the official Thunder documentat ### Community support -For additional help, you can use one of this channel to ask question: +For additional help, you can use one of these channels to ask question: * [Slack](https://thunder.org/contact-us) (highly recommended for faster support) * [Twitter](https://twitter.com/ThunderCoreTeam) diff --git a/composer.json b/composer.json index 8cbdad155..4a0390142 100644 --- a/composer.json +++ b/composer.json @@ -36,14 +36,16 @@ "patches": { "drupal/core": { "Fix Claro styles for exposed views filters wrapped in fieldsets": "https://www.drupal.org/files/issues/2021-05-31/3133639.19.patch", - "Let users configure the text of the \"Add media\" button": "https://www.drupal.org/files/issues/2021-10-01/3169956-34.patch", - "Issue #3388913: Checkbox for Media library modal missing after search": "https://www.drupal.org/files/issues/2024-01-30/3388913-48-d10.1.patch" + "Let users configure the text of the \"Add media\" button": "https://www.drupal.org/files/issues/2021-10-01/3169956-34.patch" }, "drupal/diff": { "Back button for comparison page": "https://www.drupal.org/files/issues/back_button_for-2853193-4.patch" }, "drupal/focal_point": { - "Preview link accidentally closes the media library": "https://www.drupal.org/files/issues/2020-10-11/preview_link_accidentally_closes_the_media_library-3162210-19.patch" + "Issue #3462165: Preview results in Error: Call to a member function getDefinitions() on null": "https://www.drupal.org/files/issues/2024-07-18/Preview-results-in-Error-3462165.patch" + }, + "drupal/gin": { + "Issue #3455558: There is no visible change to a toggle when pressed (but it does trigger conditional fields, value is saved, etc)": "https://www.drupal.org/files/issues/2024-08-06/3455558-Refactor-toggle-styles-mr438.patch" } } }, @@ -53,25 +55,25 @@ "drupal/access_unpublished": "^1.5", "drupal/admin_toolbar": "^3.4", "drupal/autofill": "^1.1", - "drupal/autosave_form": "^1.2", + "drupal/autosave_form": "^1.6", "drupal/blazy": "^2.14", "drupal/checklistapi": "^2.1", - "drupal/core-recommended": "~10.2.4@stable", + "drupal/core-recommended": "~10.3.0@stable", "drupal/config_selector": "^2.1", "drupal/config_update": "^1.7 || ^2.0", "drupal/content_lock": "^2.1", "drupal/crop": "^2.2", "drupal/default_content": "^1.0-alpha7||^2.0@ALPHA", - "drupal/diff": "1.3", + "drupal/diff": "1.7", "drupal/dropzonejs": "^2.8", "drupal/empty_fields": "^1.0-alpha1", - "drupal/entity_reference_actions": "^1.0", + "drupal/entity_reference_actions": "^1.1.1", "drupal/entity_reference_revisions": "^1.3", "drupal/field_group": "^3.4", - "drupal/focal_point": "2.0.2", + "drupal/focal_point": "2.1.1", "drupal/facets": "^2.0.6", - "drupal/gin": "3.0-rc9", - "drupal/gin_toolbar": "^1.0-rc5", + "drupal/gin": "3.0-rc11", + "drupal/gin_toolbar": "^1.0-rc6", "drupal/graphql": "^4.7", "drupal/inline_entity_form": "^1.0-rc14", "drupal/ivw_integration": "^2.0", @@ -82,18 +84,18 @@ "drupal/media_entity_slideshow": "^2.0-alpha1", "drupal/media_entity_twitter": "^2.5", "drupal/media_expire": "^2.6", - "drupal/media_library_media_modify": "^1.0.0-beta14", + "drupal/media_library_media_modify": "^1.0.0", "drupal/media_file_delete": "^1.2", "drupal/metatag": "^1.26", "drupal/metatag_async_widget": "^1.0-alpha2", "drupal/paragraphs": "^1.12", "drupal/paragraphs_features": "^2.0.0-beta3", "drupal/paragraphs_paste": "^2.0-beta3", - "drupal/password_policy": "^4.0", + "drupal/password_policy": "^4.0.3", "drupal/pathauto": "^1.11", "drupal/responsive_preview": "^2.1", "drupal/redirect": "^1.7", - "drupal/scheduler": "^2.0", + "drupal/scheduler": "^2.0.2", "drupal/scheduler_content_moderation_integration": "^2.0", "drupal/schema_metatag": "^2.4", "drupal/select2": "^1.7", @@ -114,7 +116,8 @@ "npm-asset/select2": "^4.0.7", "caxy/php-htmldiff": "^0.1.14", "webonyx/graphql-php": "^14.11.8", - "drupal/jquery_ui": "^1.6" + "drupal/jquery_ui": "^1.6", + "drupal/jquery_ui_draggable": "^2.1" }, "suggest": { "drupal/search_api_solr": "Provides a Apache Solr backend for the Search API module" diff --git a/config/install/editor.editor.basic_html.yml b/config/install/editor.editor.basic_html.yml index 84cc9573c..0b7bb8be6 100644 --- a/config/install/editor.editor.basic_html.yml +++ b/config/install/editor.editor.basic_html.yml @@ -60,7 +60,7 @@ image_upload: status: true scheme: public directory: inline-images - max_size: '' + max_size: null max_dimensions: - width: 0 - height: 0 + width: null + height: null diff --git a/config/install/editor.editor.full_html.yml b/config/install/editor.editor.full_html.yml index 51f25c736..7a67aa5b4 100644 --- a/config/install/editor.editor.full_html.yml +++ b/config/install/editor.editor.full_html.yml @@ -98,7 +98,7 @@ image_upload: status: true scheme: public directory: inline-images - max_size: '' + max_size: null max_dimensions: - width: 0 - height: 0 + width: null + height: null diff --git a/config/install/gin.settings.yml b/config/install/gin.settings.yml index f57d0a8fa..19738d29f 100644 --- a/config/install/gin.settings.yml +++ b/config/install/gin.settings.yml @@ -24,3 +24,4 @@ focus_color: '' layout_density: small show_description_toggle: true show_user_theme_settings: true +sticky_action_buttons: true diff --git a/config/install/image.style.linkit_result_thumbnail.yml b/config/install/image.style.linkit_result_thumbnail.yml index 1d6d88ea4..703d123e7 100644 --- a/config/install/image.style.linkit_result_thumbnail.yml +++ b/config/install/image.style.linkit_result_thumbnail.yml @@ -12,3 +12,9 @@ effects: width: 50 height: 50 anchor: center-center + 451bc36f-b256-43cc-adb5-4f3151fd892d: + uuid: 451bc36f-b256-43cc-adb5-4f3151fd892d + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/install/image.style.thumbnail.yml b/config/install/image.style.thumbnail.yml index 2780cc9c6..3059d4bde 100644 --- a/config/install/image.style.thumbnail.yml +++ b/config/install/image.style.thumbnail.yml @@ -14,3 +14,9 @@ effects: width: 100 height: 100 crop_type: focal_point + c4eb9942-2c9e-4a81-949f-6161a44b6559: + uuid: c4eb9942-2c9e-4a81-949f-6161a44b6559 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/install/views.view.media_library.yml b/config/install/views.view.media_library.yml index 8e1a64aec..a329d29c4 100644 --- a/config/install/views.view.media_library.yml +++ b/config/install/views.view.media_library.yml @@ -150,6 +150,7 @@ display: items_per_page_options_all_label: '- All -' offset: false offset_label: Offset + pagination_heading_level: h4 exposed_form: type: basic options: diff --git a/config/install/views.view.scheduler_scheduled_content.yml b/config/install/views.view.scheduler_scheduled_content.yml index 73e241292..1be50ec8f 100644 --- a/config/install/views.view.scheduler_scheduled_content.yml +++ b/config/install/views.view.scheduler_scheduled_content.yml @@ -537,6 +537,7 @@ display: previous: '‹ previous' first: '« first' last: 'last »' + pagination_heading_level: h4 exposed_form: type: basic options: @@ -1080,7 +1081,7 @@ display: admin_label: '' entity_type: node entity_field: uid - plugin_id: numeric + plugin_id: entity_target_id default_action: empty exception: value: all @@ -1112,6 +1113,7 @@ display: roles: { } break_phrase: false not: false + target_entity_type_id: user defaults: empty: false access: false diff --git a/config/install/views.view.scheduler_scheduled_media.yml b/config/install/views.view.scheduler_scheduled_media.yml index d2235ad80..2ef91dc19 100644 --- a/config/install/views.view.scheduler_scheduled_media.yml +++ b/config/install/views.view.scheduler_scheduled_media.yml @@ -563,6 +563,7 @@ display: items_per_page_options_all_label: '- All -' offset: false offset_label: Offset + pagination_heading_level: h4 exposed_form: type: basic options: @@ -1077,7 +1078,8 @@ display: field: uid entity_type: media entity_field: uid - plugin_id: numeric + plugin_id: entity_target_id + target_entity_type_id: user defaults: empty: false access: false diff --git a/config/install/views.view.scheduler_scheduled_taxonomy_term.yml b/config/install/views.view.scheduler_scheduled_taxonomy_term.yml index 382044bb8..8fdac6400 100644 --- a/config/install/views.view.scheduler_scheduled_taxonomy_term.yml +++ b/config/install/views.view.scheduler_scheduled_taxonomy_term.yml @@ -499,6 +499,7 @@ display: items_per_page_options_all_label: '- All -' offset: false offset_label: Offset + pagination_heading_level: h4 exposed_form: type: basic options: diff --git a/config/optional/image.style.facebook.yml b/config/optional/image.style.facebook.yml index ef5a3bd73..d61808606 100644 --- a/config/optional/image.style.facebook.yml +++ b/config/optional/image.style.facebook.yml @@ -14,3 +14,9 @@ effects: width: 1200 height: 630 crop_type: focal_point + ddffc1b0-0ba5-4137-bc44-90354696de13: + uuid: ddffc1b0-0ba5-4137-bc44-90354696de13 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/optional/image.style.gallery.yml b/config/optional/image.style.gallery.yml index 32ad3e162..b2281adf2 100644 --- a/config/optional/image.style.gallery.yml +++ b/config/optional/image.style.gallery.yml @@ -14,3 +14,9 @@ effects: width: 853 height: 480 crop_type: focal_point + 9d5cfaec-9903-444e-964b-4ea2ccdb6608: + uuid: 9d5cfaec-9903-444e-964b-4ea2ccdb6608 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/optional/image.style.media_image.yml b/config/optional/image.style.media_image.yml index c00cd73f4..3bd0d82e6 100644 --- a/config/optional/image.style.media_image.yml +++ b/config/optional/image.style.media_image.yml @@ -14,3 +14,9 @@ effects: width: 938 height: 527 crop_type: focal_point + 8963e07c-e414-417d-a0dd-59b2c29399d6: + uuid: 8963e07c-e414-417d-a0dd-59b2c29399d6 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/optional/image.style.media_image_mobile.yml b/config/optional/image.style.media_image_mobile.yml index c32a291b0..c2e73a7dc 100644 --- a/config/optional/image.style.media_image_mobile.yml +++ b/config/optional/image.style.media_image_mobile.yml @@ -14,3 +14,9 @@ effects: width: 560 height: 315 crop_type: focal_point + ee591d84-f108-4b47-a0a4-bd3802b38284: + uuid: ee591d84-f108-4b47-a0a4-bd3802b38284 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/optional/image.style.media_image_tablet.yml b/config/optional/image.style.media_image_tablet.yml index baa63534b..c2d4b7d65 100644 --- a/config/optional/image.style.media_image_tablet.yml +++ b/config/optional/image.style.media_image_tablet.yml @@ -14,3 +14,9 @@ effects: width: 850 height: 478 crop_type: focal_point + 3898254c-5ffd-47d9-ac3c-6f2d0be0d9ec: + uuid: 3898254c-5ffd-47d9-ac3c-6f2d0be0d9ec + id: image_convert + weight: 2 + data: + extension: webp \ No newline at end of file diff --git a/config/optional/image.style.twitter.yml b/config/optional/image.style.twitter.yml index 47ab518ea..c24a32c82 100644 --- a/config/optional/image.style.twitter.yml +++ b/config/optional/image.style.twitter.yml @@ -14,3 +14,9 @@ effects: width: 1024 height: 512 crop_type: focal_point + 2c85b491-12ad-48cd-9a33-2d32905c2cf5: + uuid: 2c85b491-12ad-48cd-9a33-2d32905c2cf5 + id: image_convert + weight: 2 + data: + extension: webp diff --git a/config/optional/node.type.article.yml b/config/optional/node.type.article.yml index dababc5f2..81324fb24 100644 --- a/config/optional/node.type.article.yml +++ b/config/optional/node.type.article.yml @@ -22,7 +22,7 @@ third_party_settings: name: Article type: article description: 'Use articles for evergreen content.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/config/optional/node.type.news_article.yml b/config/optional/node.type.news_article.yml index 8ef812c03..c8b47a217 100644 --- a/config/optional/node.type.news_article.yml +++ b/config/optional/node.type.news_article.yml @@ -25,7 +25,7 @@ third_party_settings: name: 'News Article' type: news_article description: 'Use news articles for time-sensitive content like news, press releases or blog posts.' -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: true diff --git a/config/optional/node.type.page.yml b/config/optional/node.type.page.yml index 4c1a522ad..755e8ed8c 100644 --- a/config/optional/node.type.page.yml +++ b/config/optional/node.type.page.yml @@ -4,7 +4,7 @@ dependencies: { } name: 'Basic page' type: page description: "Use basic pages for your static content, such as an 'About us' page." -help: '' +help: null new_revision: true preview_mode: 1 display_submitted: false diff --git a/config/optional/taxonomy.vocabulary.channel.yml b/config/optional/taxonomy.vocabulary.channel.yml index 8293c1bba..b9173d86c 100644 --- a/config/optional/taxonomy.vocabulary.channel.yml +++ b/config/optional/taxonomy.vocabulary.channel.yml @@ -3,5 +3,6 @@ status: true dependencies: { } name: Channel vid: channel -description: '' +description: null weight: 0 +new_revision: false diff --git a/config/optional/taxonomy.vocabulary.tags.yml b/config/optional/taxonomy.vocabulary.tags.yml index 4c754e86c..0d0313cf6 100644 --- a/config/optional/taxonomy.vocabulary.tags.yml +++ b/config/optional/taxonomy.vocabulary.tags.yml @@ -5,3 +5,4 @@ name: Tags vid: tags description: 'Use tags to group articles on similar topics into categories.' weight: 0 +new_revision: false diff --git a/config/optional/views.view.content.yml b/config/optional/views.view.content.yml index 3b4677b27..d90a10b33 100644 --- a/config/optional/views.view.content.yml +++ b/config/optional/views.view.content.yml @@ -412,6 +412,7 @@ display: previous: '‹ Previous' first: '« First' last: 'Last »' + pagination_heading_level: h4 exposed_form: type: basic options: diff --git a/config/optional/views.view.media.yml b/config/optional/views.view.media.yml index 6f98eaff8..6f44b8942 100644 --- a/config/optional/views.view.media.yml +++ b/config/optional/views.view.media.yml @@ -602,6 +602,7 @@ display: offset: false offset_label: Offset quantity: 9 + pagination_heading_level: h4 exposed_form: type: basic options: diff --git a/docs/developer-guide/migration/migrate-7-8.md b/docs/developer-guide/migration/migrate-7-8.md new file mode 100644 index 000000000..53026603b --- /dev/null +++ b/docs/developer-guide/migration/migrate-7-8.md @@ -0,0 +1,39 @@ +# Update Thunder 7 -> Thunder 8 + +## Prerequisites + +These are the instructions to manually update your existing Thunder 7 installation to Thunder 8. If +you want to do a fresh installation of thunder please visit [install Thunder](../setup.md#install-thunder). + +You have to make sure that your Thunder 7 project and all its dependencies are fully updated to the most current +versions. Run the following command in your docroot: + +```bash +drush ev "print drupal_get_installed_schema_version('thunder') . PHP_EOL;" +``` + +This should print the number XXXX or greater. If that is not the case, update your project. + +```bash +cd .. +composer update +``` + +This should update to Thunder 7.3 or greater. + +Now run database updates: + +```bash +cd docroot +drush updb +``` + +You should at least see the Thunder XXXX schema update. If not, double check that the correct version of Thunder +is installed, and that `drush updb` did not throw any errors. + +Before you start with the code and database update please disable the jQuery UI Draggable module or require it on your own. + +```bash +composer require drupal/jquery_ui_draggable +``` + diff --git a/modules/thunder_article/src/Form/ThunderNodeFormHelper.php b/modules/thunder_article/src/Form/ThunderNodeFormHelper.php deleted file mode 100644 index ded7f1fba..000000000 --- a/modules/thunder_article/src/Form/ThunderNodeFormHelper.php +++ /dev/null @@ -1,199 +0,0 @@ -currentUser = $current_user; - $this->messenger = $messenger; - $this->request = $requestStack->getCurrentRequest(); - $this->entityTypeManager = $entity_type_manager; - $this->themeManager = $theme_manager; - $this->moderationInfo = $moderationInfo; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container): self { - return new static( - $container->get('current_user'), - $container->get('messenger'), - $container->get('request_stack'), - $container->get('entity_type.manager'), - $container->get('theme.manager'), - $container->get('content_moderation.moderation_information', ContainerInterface::NULL_ON_INVALID_REFERENCE) - ); - } - - /** - * {@inheritdoc} - */ - public function formAlter(array &$form, FormStateInterface $form_state): array { - /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */ - $form_object = $form_state->getFormObject(); - /** @var \Drupal\node\NodeInterface $entity */ - $entity = $form_object->getEntity(); - - /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ - $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId()); - $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId()); - if ($latest_revision_id !== NULL && $this->moderationInfo && $this->moderationInfo->hasPendingRevision($entity)) { - $this->messenger->addWarning($this->t('This %entity_type has unpublished changes from user %user.', [ - '%entity_type' => $entity->get('type')->entity->label(), - '%user' => $entity->getRevisionUser()->label(), - ])); - } - - $form['actions'] = array_merge($form['actions'], $this->actions($entity)); - - return $form; - } - - /** - * {@inheritdoc} - */ - protected function actions(NodeInterface $entity): array { - /** @var \Drupal\node\Entity\Node $entity */ - /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */ - $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId()); - $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId()); - - if ($latest_revision_id == NULL || !$this->moderationInfo || !$this->moderationInfo->isModeratedEntity($entity)) { - return []; - } - - $element = []; - // @todo Remove after seven / thunder_admin support is dropped. - $activeTheme = $this->themeManager->getActiveTheme(); - $activeThemes = array_keys($activeTheme->getBaseThemeExtensions()); - $activeThemes[] = $activeTheme->getName(); - - if (!empty(array_intersect($activeThemes, ['seven', 'thunder_admin']))) { - /** @var \Drupal\content_moderation\ContentModerationState $state */ - $state = $this->moderationInfo->getWorkflowForEntity($entity)->getTypePlugin()->getState($entity->moderation_state->value); - $element['status'] = [ - '#type' => 'item', - '#markup' => $entity->isNew() || !$this->moderationInfo->isDefaultRevisionPublished($entity) ? $this->t('of unpublished @entity_type', ['@entity_type' => strtolower($entity->type->entity->label())]) : $this->t('of published @entity_type', ['@entity_type' => strtolower($entity->type->entity->label())]), - '#weight' => 200, - '#wrapper_attributes' => [ - 'class' => ['status'], - ], - '#access' => !$state->isDefaultRevisionState(), - ]; - - $element['moderation_state_current'] = [ - '#type' => 'item', - '#markup' => $state->label(), - '#weight' => 210, - '#wrapper_attributes' => [ - 'class' => ['status', $state->id()], - ], - ]; - } - - if ($this->moderationInfo->hasPendingRevision($entity)) { - $route_info = Url::fromRoute('node.revision_revert_default_confirm', [ - 'node' => $entity->id(), - 'node_revision' => $entity->getRevisionId(), - ]); - if ($this->request->query->has('destination')) { - $query = $route_info->getOption('query'); - $query['destination'] = $this->request->query->get('destination'); - $route_info->setOption('query', $query); - } - - $element['revert_to_default'] = [ - '#type' => 'link', - '#title' => $this->t('Revert to default revision'), - '#access' => $entity->access('revert revision', $this->currentUser), - '#weight' => 101, - '#attributes' => [ - 'class' => ['button', 'button--danger'], - ], - ]; - $element['revert_to_default']['#url'] = $route_info; - } - - return $element; - } - -} diff --git a/modules/thunder_gqls/graphql/thunder_menu.base.graphqls b/modules/thunder_gqls/graphql/thunder_menu.base.graphqls index 68b0bb4df..7e233832b 100644 --- a/modules/thunder_gqls/graphql/thunder_menu.base.graphqls +++ b/modules/thunder_gqls/graphql/thunder_menu.base.graphqls @@ -1,7 +1,6 @@ type Menu { id: String! name: String! - items: [MenuItem] } diff --git a/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls b/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls new file mode 100644 index 000000000..29db90559 --- /dev/null +++ b/modules/thunder_gqls/graphql/thunder_search_api.base.graphqls @@ -0,0 +1,4 @@ +type SearchApiResult { + items: [Page!] + total: Int! +} diff --git a/modules/thunder_gqls/graphql/thunder_search_api.extension.graphqls b/modules/thunder_gqls/graphql/thunder_search_api.extension.graphqls new file mode 100644 index 000000000..e69de29bb diff --git a/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php b/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php new file mode 100644 index 000000000..354dfa53f --- /dev/null +++ b/modules/thunder_gqls/src/GraphQL/Buffers/SearchApiResultBuffer.php @@ -0,0 +1,101 @@ +entityTypeManager = $entityTypeManager; + } + + /** + * Add an item to the buffer. + * + * @param string|int|null $index + * The entity type of the given entity ids. + * @param array|int $id + * The entity id(s) to load. + * + * @return \Closure + * The callback to invoke to load the result for this buffer item. + */ + public function add($index, $id) { + $item = new \ArrayObject([ + 'index' => $index, + 'id' => $id, + ]); + + return $this->createBufferResolver($item); + } + + /** + * {@inheritdoc} + */ + protected function getBufferId($item) { + return $item['index']; + } + + /** + * {@inheritdoc} + */ + public function resolveBufferArray(array $buffer) { + $index = reset($buffer)['index']; + $ids = array_map(function (\ArrayObject $item) { + return (array) $item['id']; + }, $buffer); + + $ids = call_user_func_array('array_merge', $ids); + $ids = array_values(array_unique($ids)); + + // Load the buffered entities. + /** @var \Drupal\search_api\IndexInterface $index */ + $index = $this->entityTypeManager + ->getStorage('search_api_index') + ->load($index); + + $resultSet = $index->loadItemsMultiple($ids); + $entities = []; + + foreach ($resultSet as $key => $resultItem) { + if ($resultItem instanceof EntityAdapter) { + $entities[$key] = $resultItem->getEntity(); + } + } + + return array_map(function ($item) use ($entities) { + if (is_array($item['id'])) { + return array_reduce($item['id'], static function ($carry, $current) use ($entities) { + if (!empty($entities[$current])) { + $carry[] = $entities[$current]; + return $carry; + } + + return $carry; + }, []); + } + + return $entities[$item['id']] ?? NULL; + }, $buffer); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php index 8788b2539..9ac9953ae 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntitiesWithTerm.php @@ -4,7 +4,6 @@ use Drupal\graphql\GraphQL\Execution\FieldContext; use Drupal\taxonomy\TermInterface; -use Drupal\thunder_gqls\Wrappers\EntityListResponse; use Drupal\thunder_gqls\Wrappers\EntityListResponseInterface; /** @@ -119,7 +118,7 @@ public function resolve(TermInterface $term, string $type, array $bundles, strin $cacheContext ); - return new EntityListResponse($query); + return $this->entityListResponse($query); } /** diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php index 3afcc2965..43ea0aa03 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityList.php @@ -3,7 +3,6 @@ namespace Drupal\thunder_gqls\Plugin\GraphQL\DataProducer; use Drupal\graphql\GraphQL\Execution\FieldContext; -use Drupal\thunder_gqls\Wrappers\EntityListResponse; use Drupal\thunder_gqls\Wrappers\EntityListResponseInterface; /** @@ -97,7 +96,7 @@ protected function resolve(string $type, array $bundles, int $offset, int $limit $cacheContext ); - return new EntityListResponse($query); + return $this->entityListResponse($query); } } diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php index 610700ab2..c2ad6bbe8 100644 --- a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php @@ -2,12 +2,13 @@ namespace Drupal\thunder_gqls\Plugin\GraphQL\DataProducer; -use Drupal\Core\Entity\EntityTypeManager; +use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Entity\Query\QueryInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Drupal\Core\Session\AccountInterface; use Drupal\graphql\GraphQL\Execution\FieldContext; use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase; +use Drupal\thunder_gqls\Wrappers\EntityListResponse; use GraphQL\Error\UserError; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -21,32 +22,43 @@ abstract class ThunderEntityListProducerBase extends DataProducerPluginBase impl /** * The entity type manager service. * - * @var \Drupal\Core\Entity\EntityTypeManager + * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ - protected $entityTypeManager; + protected EntityTypeManagerInterface $entityTypeManager; /** * The current user. * * @var \Drupal\Core\Session\AccountInterface */ - protected $currentUser; + protected AccountInterface $currentUser; + + /** + * The response wrapper service. + * + * @var \Drupal\thunder_gqls\Wrappers\EntityListResponse + */ + protected EntityListResponse $responseWrapper; /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): self { - return new static( + $instance = new static( $configuration, $plugin_id, $plugin_definition, $container->get('entity_type.manager'), $container->get('current_user') ); + + $instance->setResponseWrapper($container->get('thunder_gqls.entity_list_response_wrapper')); + + return $instance; } /** - * EntityLoad constructor. + * ThunderEntityListProducerBase constructor. * * @param array $configuration * The plugin configuration array. @@ -54,21 +66,51 @@ public static function create(ContainerInterface $container, array $configuratio * The plugin id. * @param array $pluginDefinition * The plugin definition array. - * @param \Drupal\Core\Entity\EntityTypeManager $entityTypeManager + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager * The entity type manager service. - * @param \Drupal\Core\Session\AccountInterface $current_user + * @param \Drupal\Core\Session\AccountInterface $currentUser * The current user. */ public function __construct( array $configuration, string $pluginId, array $pluginDefinition, - EntityTypeManager $entityTypeManager, - AccountInterface $current_user, + EntityTypeManagerInterface $entityTypeManager, + AccountInterface $currentUser, ) { parent::__construct($configuration, $pluginId, $pluginDefinition); + $this->setEntityTypeManager($entityTypeManager); + $this->setCurrentUser($currentUser); + } + + /** + * Set the entity type manager service. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager service. + */ + public function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager): void { $this->entityTypeManager = $entityTypeManager; - $this->currentUser = $current_user; + } + + /** + * Set the current user. + * + * @param \Drupal\Core\Session\AccountInterface $currentUser + * The current user. + */ + public function setCurrentUser(AccountInterface $currentUser): void { + $this->currentUser = $currentUser; + } + + /** + * Set the response wrapper service. + * + * @param \Drupal\thunder_gqls\Wrappers\EntityListResponse $responseWrapper + * The response wrapper service. + */ + public function setResponseWrapper(EntityListResponse $responseWrapper): void { + $this->responseWrapper = $responseWrapper; } /** @@ -163,10 +205,7 @@ protected function query( $query->range($offset, $limit); $storage = $this->entityTypeManager->getStorage($type); - $entityType = $storage->getEntityType(); - - $cacheContext->addCacheTags($entityType->getListCacheTags()); - $cacheContext->addCacheContexts($entityType->getListCacheContexts()); + $cacheContext->addCacheableDependency($storage->getEntityType()); return $query; } @@ -202,4 +241,17 @@ protected function createPublishedCondition(string $type, array $conditions) { ]; } + /** + * The entity list response. + * + * @param \Drupal\Core\Entity\Query\QueryInterface $query + * The entity query. + * + * @return \Drupal\thunder_gqls\Wrappers\EntityListResponse + * The entity list response. + */ + protected function entityListResponse(QueryInterface $query): EntityListResponse { + return $this->responseWrapper->setQuery($query); + } + } diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php new file mode 100644 index 000000000..d26ecc9c6 --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApi.php @@ -0,0 +1,122 @@ + 'status', + 'value' => TRUE, + 'operator' => '=', + ], + [ + 'field' => 'search_api_language', + 'value' => $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->getId(), + 'operator' => '=', + ], + ]; + + // Add default sorts. + $sortBy = $sortBy ?: [ + [ + 'field' => 'search_api_relevance', + 'direction' => QueryInterface::SORT_DESC, + ], + ]; + + $query = $this->buildBaseQuery( + $limit, + $offset, + $index, + $sortBy, + $conditions, + $search, + $cacheContext + ); + + return $this->searchApiResponse($query); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php new file mode 100644 index 000000000..e568b6971 --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderSearchApiProducerBase.php @@ -0,0 +1,181 @@ +setEntityTypeManager($container->get('entity_type.manager')); + $instance->setLanguageManager($container->get('language_manager')); + $instance->setResponseWrapper($container->get('thunder_gqls.search_api_response_wrapper')); + + return $instance; + } + + /** + * Set the entity type manager service. + * + * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager + * The entity type manager service. + */ + public function setEntityTypeManager(EntityTypeManagerInterface $entityTypeManager): void { + $this->entityTypeManager = $entityTypeManager; + } + + /** + * Set the language manager service. + * + * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager + * The language manager service. + */ + public function setLanguageManager(LanguageManagerInterface $languageManager): void { + $this->languageManager = $languageManager; + } + + /** + * Set the response wrapper service. + * + * @param \Drupal\thunder_gqls\Wrappers\SearchApiResponse $responseWrapper + * The response wrapper service. + */ + public function setResponseWrapper(SearchApiResponse $responseWrapper): void { + $this->responseWrapper = $responseWrapper; + } + + /** + * Build base search api query. + * + * @param int $limit + * Limit of the query. + * @param int $offset + * Offset of the query. + * @param string $index + * Id of the search api index. + * @param array|null $sortBy + * List of sorts. + * @param array|null $conditions + * List of conditions to filter the result. + * @param string|null $search + * Query Search. + * @param \Drupal\graphql\GraphQL\Execution\FieldContext $cacheContext + * The caching context related to the current field. + * + * @return \Drupal\search_api\Query\QueryInterface|null + * The query interface. + * + * @throws \Drupal\search_api\SearchApiException + */ + protected function buildBaseQuery( + int $limit, + int $offset, + string $index, + ?array $sortBy, + ?array $conditions, + ?string $search, + FieldContext $cacheContext, + ): ?QueryInterface { + + // Make sure offset is zero or positive. + $offset = max($offset, 0); + + // Make sure limit is positive and cap the max items. + if ($limit <= 0) { + $limit = 10; + } + if ($limit > static::MAX_ITEMS) { + throw new UserError( + sprintf('Exceeded maximum query limit: %s.', static::MAX_ITEMS) + ); + } + + $searchIndex = Index::load($index); + if (!$searchIndex) { + return NULL; + } + + $query = $searchIndex->query(); + + foreach ($conditions as $condition) { + $query->addCondition($condition['field'], $condition['value'], $condition['operator']); + } + + foreach ($sortBy as $sort) { + $direction = $sort['direction'] ?? QueryInterface::SORT_ASC; + $query->sort($sort['field'], $direction); + } + + if (!empty($search)) { + $query->keys($search); + } + + $query->range($offset, $limit); + $cacheContext->addCacheableDependency($searchIndex); + + return $query; + } + + /** + * The search api response. + * + * @param \Drupal\search_api\Query\QueryInterface $query + * The search api query. + * + * @return \Drupal\thunder_gqls\Wrappers\SearchApiResponse + * The search api response. + */ + protected function searchApiResponse(QueryInterface $query): SearchApiResponse { + return $this->responseWrapper->setQuery($query); + } + +} diff --git a/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php b/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php new file mode 100644 index 000000000..dc9c2fbeb --- /dev/null +++ b/modules/thunder_gqls/src/Plugin/GraphQL/SchemaExtension/ThunderSearchApiSchemaExtension.php @@ -0,0 +1,39 @@ +addFieldResolverIfNotExists('SearchApiResult', 'total', + $this->builder->callback(function (SearchApiResponse $result) { + return $result->total(); + }) + ); + + $this->addFieldResolverIfNotExists('SearchApiResult', 'items', + $this->builder->callback(function (SearchApiResponse $result) { + return $result->items(); + }) + ); + } + +} diff --git a/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php b/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php index d09fbca9e..86d66c1e9 100644 --- a/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php +++ b/modules/thunder_gqls/src/Traits/ResolverHelperTrait.php @@ -4,6 +4,7 @@ use Drupal\graphql\GraphQL\Resolver\ResolverInterface; use Drupal\graphql\GraphQL\ResolverBuilder; +use Drupal\graphql\GraphQL\ResolverRegistryInterface; /** * Helper functions for field resolvers. @@ -15,14 +16,14 @@ trait ResolverHelperTrait { * * @var \Drupal\graphql\GraphQL\ResolverBuilder */ - protected $builder; + protected ResolverBuilder $builder; /** * ResolverRegistryInterface. * * @var \Drupal\graphql\GraphQL\ResolverRegistryInterface */ - protected $registry; + protected ResolverRegistryInterface $registry; /** * Add field resolver to registry, if it does not already exist. diff --git a/modules/thunder_gqls/src/Wrappers/EntityListResponse.php b/modules/thunder_gqls/src/Wrappers/EntityListResponse.php index d2502ac23..4b25e5044 100644 --- a/modules/thunder_gqls/src/Wrappers/EntityListResponse.php +++ b/modules/thunder_gqls/src/Wrappers/EntityListResponse.php @@ -2,29 +2,66 @@ namespace Drupal\thunder_gqls\Wrappers; +use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\Query\QueryInterface; +use Drupal\graphql\GraphQL\Buffers\EntityBuffer; use GraphQL\Deferred; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * The thunder entity list response class. */ -class EntityListResponse implements EntityListResponseInterface { +class EntityListResponse implements EntityListResponseInterface, ContainerInjectionInterface { /** * The query interface. * * @var \Drupal\Core\Entity\Query\QueryInterface */ - protected $query; + protected QueryInterface $query; + + /** + * The entity buffer. + * + * @var \Drupal\graphql\GraphQL\Buffers\EntityBuffer + */ + protected EntityBuffer $buffer; /** * EntityListResponse constructor. * + * @param \Drupal\Core\Entity\Query\QueryInterface|\Drupal\graphql\GraphQL\Buffers\EntityBuffer $buffer + * The query or buffer parameter. + */ + public function __construct(QueryInterface|EntityBuffer $buffer) { + if ($buffer instanceof QueryInterface) { + // phpcs:ignore + @trigger_error('Calling the constructor with a query parameter is deprecated in Thunder 7.3.3 and will be removed in Thunder 8.0. Use service injection and ::setQuery() instead.', E_USER_DEPRECATED); + $this->setQuery($buffer); + // phpcs:ignore + $buffer = \Drupal::service('graphql.buffer.entity'); + } + $this->buffer = $buffer; + } + + /** + * {@inheritDoc} + */ + public static function create(ContainerInterface $container): self { + return new static( + $container->get('graphql.buffer.entity'), + ); + } + + /** + * Set query. + * * @param \Drupal\Core\Entity\Query\QueryInterface $query - * The query interface. + * The query. */ - public function __construct(QueryInterface $query) { + public function setQuery(QueryInterface $query): EntityListResponse { $this->query = $query; + return $this; } /** @@ -36,7 +73,7 @@ public function __construct(QueryInterface $query) { public function total(): int { $query = clone $this->query; $query->range(NULL, NULL)->count(); - return intval($query->execute()); + return (int) $query->execute(); } /** @@ -51,8 +88,7 @@ public function items() { return []; } - $buffer = \Drupal::service('graphql.buffer.entity'); - $callback = $buffer->add($this->query->getEntityTypeId(), array_values($result)); + $callback = $this->buffer->add($this->query->getEntityTypeId(), array_values($result)); return new Deferred(fn() => $callback()); } diff --git a/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php b/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php new file mode 100644 index 000000000..612c56d03 --- /dev/null +++ b/modules/thunder_gqls/src/Wrappers/SearchApiResponse.php @@ -0,0 +1,273 @@ +get('thunder_gqls.buffer.search_api_result'), + $container->get('entity_field.manager'), + ); + } + + /** + * Set query. + * + * @param \Drupal\search_api\Query\QueryInterface $query + * The query. + */ + public function setQuery(QueryInterface $query): SearchApiResponse { + $this->query = $query; + return $this; + } + + /** + * Set Facet mapping. + * + * @param array $facetMapping + * The facet mapping. + */ + public function setFacetMapping(array $facetMapping): SearchApiResponse { + $this->facetMapping = $facetMapping; + return $this; + } + + /** + * Set bundle. + * + * @param string $bundle + * The bundle. + */ + public function setBundle(string $bundle): SearchApiResponse { + $this->bundle = $bundle; + return $this; + } + + /** + * Set facets. + * + * @param array $facets + * The facets. + */ + public function setFacets(array $facets): SearchApiResponse { + $this->facets = $facets; + return $this; + } + + /** + * {@inheritdoc} + * + * @throws \Drupal\search_api\SearchApiException + */ + public function facets(): array { + if (!$this->facets || !$this->facetMapping) { + return []; + } + + if (!$this->result) { + $this->result = $this->query->execute(); + } + + $facets = []; + + $facetData = $this->result->getExtraData('search_api_facets'); + foreach ($facetData as $facetFieldId => $facetResults) { + $facets[] = [ + 'key' => $this->facetMapping[$facetFieldId], + 'values' => $this->processFacetResults($this->facets[$facetFieldId], $facetResults), + ]; + } + + return $facets; + } + + /** + * Get search result items. + * + * @return array|\GraphQL\Deferred + * The search result items. + * + * @throws \Drupal\search_api\SearchApiException + */ + public function items(): array|Deferred { + if (!$this->result) { + $this->result = $this->query->execute(); + } + + $ids = array_map(static function ($item) { + return $item->getId(); + }, $this->result->getResultItems()); + + $ids = array_unique($ids); + + if (empty($ids)) { + return []; + } + + $callback = $this->buffer->add( + $this->query->getIndex()->id(), + array_values($ids) + ); + + return new Deferred(function () use ($callback) { + return $callback(); + }); + } + + /** + * Returns the total results. + * + * @return int + * The total results. + * + * @throws \Drupal\search_api\SearchApiException + */ + public function total(): int { + $query = clone $this->query; + $query->range(0, NULL); + $result = $query->execute(); + + return (int) $result->getResultCount(); + } + + /** + * Handles processing of facet values. + * + * @param \Drupal\facets\Entity\Facet $facet + * The facet to process. + * @param array $facetResults + * The facet results. + * + * @return array + * The processed facet results. + */ + private function processFacetResults( + Facet $facet, + array $facetResults, + ): array { + // First process facet results which contain filter like filter=""9"". + // @see Drupal\facets\Plugin\facets\query_type\SearchApiString#build(). + foreach ($facetResults as $i => $facetResult) { + $facetResult['filter'] = $facetResult['filter'] ?? ''; + + if ($facetResult['filter'][0] === '"') { + $facetResult['filter'] = substr($facetResult['filter'], 1); + } + if ($facetResult['filter'][strlen($facetResult['filter']) - 1] === '"') { + $facetResult['filter'] = substr($facetResult['filter'], 0, -1); + } + + $facetResults[$i] = $facetResult; + } + + return $this->processFacetResultsFromFieldConfig($facet, $facetResults); + } + + /** + * Populates label for facet values from allowed options field config. + * + * @param \Drupal\facets\Entity\Facet $facet + * The facet. + * @param array $facetResults + * The facet results. + * + * @return array + * The processed facet results. + */ + private function processFacetResultsFromFieldConfig( + Facet $facet, + array $facetResults, + ): array { + if (!$this->bundle) { + return $facetResults; + } + + $fieldName = $facet->getFieldIdentifier(); + $fieldConfig = $this->entityFieldManager->getFieldDefinitions('node', $this->bundle); + + if (isset($fieldConfig[$fieldName])) { + $allowedValues = options_allowed_values($fieldConfig[$fieldName]->getFieldStorageDefinition()); + + // Use order of allowedValues. + foreach ($facetResults as $key => $facetResult) { + $facetResults[$key]['label'] = $allowedValues[$facetResult['filter']] ?? $facetResult['filter']; + $facetResults[$key]['value'] = $facetResult['filter']; + } + + $allowedValueKeys = array_keys($allowedValues); + usort($facetResults, function ($a, $b) use ($allowedValueKeys) { + $indexA = array_search($a['filter'], $allowedValueKeys, TRUE); + $indexB = array_search($b['filter'], $allowedValueKeys, TRUE); + + return $indexA < $indexB ? -1 : 1; + }); + } + + return $facetResults; + } + +} diff --git a/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php b/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php new file mode 100644 index 000000000..82d2158aa --- /dev/null +++ b/modules/thunder_gqls/src/Wrappers/SearchApiResponseInterface.php @@ -0,0 +1,18 @@ +logWithRole('administrator'); + + $this->drupalGet('admin/config/search/search-api/index/content'); + $this->submitForm([], 'Index now'); + $this->assertSession()->statusCodeEquals(200); + $this->checkForMetaRefresh(); + + $options = [ + 'index' => 'content', + 'search' => 'the', + 'limit' => 10, + 'offset' => 0, + ]; + + $result = $this->executeDataProducer('thunder_search_api', $options); + $this->assertEquals(3, $result->total()); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Burda Launches Open-Source CMS Thunder', $items->result[0]->getTitle()); + + // Change sort order. + $options['sortBy'] = [ + [ + 'field' => 'search_api_relevance', + 'direction' => QueryInterface::SORT_ASC, + ], + ]; + + $this->container->get('kernel')->rebuildContainer(); + $result = $this->executeDataProducer('thunder_search_api', $options); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Legal notice', $items->result[0]->getTitle()); + + // Get articles only. + $options['conditions'] = [ + [ + 'field' => 'type', + 'value' => 'article', + 'operator' => '=', + ], + ]; + + $this->container->get('kernel')->rebuildContainer(); + $result = $this->executeDataProducer('thunder_search_api', $options); + + $items = $result->items(); + $items->runQueue(); + $this->assertEquals('Come to DrupalCon New Orleans', $items->result[0]->getTitle()); + + } + +} diff --git a/modules/thunder_gqls/tests/src/Kernel/DataProducer/ThunderRedirectTest.php b/modules/thunder_gqls/tests/src/Kernel/DataProducer/ThunderRedirectTest.php index cd72e851e..422909e1e 100644 --- a/modules/thunder_gqls/tests/src/Kernel/DataProducer/ThunderRedirectTest.php +++ b/modules/thunder_gqls/tests/src/Kernel/DataProducer/ThunderRedirectTest.php @@ -43,6 +43,7 @@ class ThunderRedirectTest extends GraphQLTestBase { */ public function setUp(): void { parent::setUp(); + $this->installEntitySchema('path_alias'); $this->installConfig(['redirect']); $this->installEntitySchema('redirect'); diff --git a/modules/thunder_gqls/thunder_gqls.services.yml b/modules/thunder_gqls/thunder_gqls.services.yml new file mode 100644 index 000000000..863299712 --- /dev/null +++ b/modules/thunder_gqls/thunder_gqls.services.yml @@ -0,0 +1,12 @@ +services: + _defaults: + autowire: true + thunder_gqls.buffer.search_api_result: + class: Drupal\thunder_gqls\GraphQL\Buffers\SearchApiResultBuffer + Drupal\thunder_gqls\GraphQL\Buffers\SearchApiResultBuffer: '@thunder_gqls.buffer.search_api_result' + thunder_gqls.search_api_response_wrapper: + class: Drupal\thunder_gqls\Wrappers\SearchApiResponse + thunder_gqls.entity_list_response_wrapper: + autowire: false + class: Drupal\thunder_gqls\Wrappers\EntityListResponse + arguments: [ '@graphql.buffer.entity' ] diff --git a/modules/thunder_media/tests/src/Functional/FilenameTransliterationTest.php b/modules/thunder_media/tests/src/Functional/FilenameTransliterationTest.php index 4e7e0b5e6..d5e6845c5 100644 --- a/modules/thunder_media/tests/src/Functional/FilenameTransliterationTest.php +++ b/modules/thunder_media/tests/src/Functional/FilenameTransliterationTest.php @@ -2,7 +2,7 @@ namespace Drupal\Tests\thunder_media\Functional; -use Drupal\Core\File\FileSystemInterface; +use Drupal\Core\File\FileExists; use Drupal\Core\StreamWrapper\PublicStream; use Drupal\file\Entity\File; use Drupal\Tests\thunder\Functional\ThunderTestBase; @@ -55,7 +55,7 @@ public function testFileTransliteration(): void { // Upload with replace to guarantee there's something there. $edit = [ - 'file_test_replace' => FileSystemInterface::EXISTS_RENAME, + 'file_test_replace' => FileExists::Rename->name, 'files[file_test_upload]' => \Drupal::service('file_system')->realpath('public://foo°.png'), ]; $this->drupalGet('file-test/upload'); diff --git a/modules/thunder_workflow/src/ThunderWorkflowFormHelper.php b/modules/thunder_workflow/src/ThunderWorkflowFormHelper.php index 3be126b1e..091662a10 100644 --- a/modules/thunder_workflow/src/ThunderWorkflowFormHelper.php +++ b/modules/thunder_workflow/src/ThunderWorkflowFormHelper.php @@ -204,6 +204,11 @@ public function moveStateToActions(NodeInterface $entity, array $form): array { $form['actions']['moderation_state'] = $form['moderation_state']; unset($form['moderation_state']); + // Promote moderation_state in gin theme to not end up in + // dropdown button. + $form['actions']['moderation_state']['#gin_action_item'] = TRUE; + $form['actions']['moderation_state']['widget'][0]['#attributes']['form'] = $form['#id']; + return $form; } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ea4730437..576ad8cca 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,60 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_article/src/Form/NodeRevisionRevertDefaultForm.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_article/src/Form/ThunderNodeFormHelper.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_article/src/Plugin/Derivative/DynamicLocalTasks.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/EntityLinks.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/FocalPoint.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/MenuLinksActiveTrail.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/MetaTags.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntityListProducerBase.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderEntitySubRequestBase.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderImage.php - - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_gqls/src/Plugin/GraphQL/DataProducer/ThunderRedirect.php - - message: "#^Access to an undefined property Drupal\\\\Core\\\\Entity\\\\ContentEntityInterface\\:\\:\\$field_teaser_media\\.$#" count: 1 @@ -75,11 +20,6 @@ parameters: count: 4 path: modules/thunder_gqls/tests/src/Kernel/DataProducer/EntityLinksTest.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: modules/thunder_taxonomy/src/ThunderTaxonomyPermissions.php - - message: "#^Access to an undefined property Drupal\\\\Core\\\\Entity\\\\EntityInterface\\:\\:\\$status\\.$#" count: 1 @@ -230,11 +170,6 @@ parameters: count: 1 path: tests/src/TestSuites/ThunderTestSuite.php - - - message: "#^Unsafe usage of new static\\(\\)\\.$#" - count: 1 - path: tests/src/TestSuites/ThunderTestSuite.php - - message: "#^Call to method id\\(\\) on an unknown class Drupal\\\\entity_browser\\\\Entity\\\\EntityBrowser\\.$#" count: 1 diff --git a/phpstan.neon b/phpstan.neon index 654ea7304..a604bc45c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,8 +1,11 @@ parameters: customRulesetUsed: true - checkGenericClassInNonGenericObjectType: false - checkMissingIterableValueType: false reportUnmatchedIgnoredErrors: true level: 6 + ignoreErrors: + # new static() is a best practice in Drupal, so we cannot fix that. + - "#^Unsafe usage of new static#" + - identifier: missingType.generics + - identifier: missingType.iterableValue includes: - ./phpstan-baseline.neon diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1bf1a0520..f63099d0d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,17 +1,27 @@ - - + + + + ./includes + ./lib + ./modules + ../modules + ../sites + + + ./modules/*/src/Tests + ./modules/*/tests + ../modules/*/src/Tests + ../modules/*/tests + ../modules/*/*/src/Tests + ../modules/*/*/tests + + @@ -31,11 +41,11 @@ - + - + - + @@ -59,24 +69,4 @@ - - - ./includes - ./lib - - ./modules - - ./modules/*/src/Tests - ./modules/*/tests - - ../modules - - ../modules/*/src/Tests - ../modules/*/tests - ../modules/*/*/src/Tests - ../modules/*/*/tests - - ../sites - - diff --git a/tests/src/Functional/InstalledConfigurationTest.php b/tests/src/Functional/InstalledConfigurationTest.php index 4b0d60c91..1fbeb35e8 100644 --- a/tests/src/Functional/InstalledConfigurationTest.php +++ b/tests/src/Functional/InstalledConfigurationTest.php @@ -278,7 +278,17 @@ class InstalledConfigurationTest extends ThunderTestBase { ], 'views.view.locked_content' => [ 'display' => [ - 'default' => ['display_options' => ['sorts' => ['created' => ['expose' => ['field_identifier' => TRUE]]]]], + 'default' => [ + 'display_options' => [ + 'sorts' => ['created' => ['expose' => ['field_identifier' => TRUE]]], + 'pager' => ['options' => ['pagination_heading_level' => TRUE]], + ], + ], + ], + ], + 'views.view.redirect' => [ + 'display' => [ + 'default' => ['display_options' => ['pager' => ['options' => ['pagination_heading_level' => TRUE]]]], ], ], ]; diff --git a/tests/src/Functional/ThunderTestBase.php b/tests/src/Functional/ThunderTestBase.php index 5b7f7546a..44e79c2fd 100644 --- a/tests/src/Functional/ThunderTestBase.php +++ b/tests/src/Functional/ThunderTestBase.php @@ -4,6 +4,7 @@ use Drupal\Core\StringTranslation\StringTranslationTrait; use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\thunder\Traits\ThunderGinTestTrait; use Drupal\Tests\thunder\Traits\ThunderTestTrait; use Prophecy\PhpUnit\ProphecyTrait; @@ -15,6 +16,7 @@ abstract class ThunderTestBase extends BrowserTestBase { use ThunderTestTrait; use StringTranslationTrait; use ProphecyTrait; + use ThunderGinTestTrait; /** * {@inheritdoc} diff --git a/tests/src/FunctionalJavascript/Integration/FocalPointTest.php b/tests/src/FunctionalJavascript/Integration/FocalPointTest.php new file mode 100644 index 000000000..cfa49561d --- /dev/null +++ b/tests/src/FunctionalJavascript/Integration/FocalPointTest.php @@ -0,0 +1,25 @@ +loadNodeByUuid('0bd5c257-2231-450f-b4c2-ab156af7b78d'); + $this->drupalGet($node->toUrl('edit-form')); + $this->clickDrupalSelector('edit-field-teaser-media-selection-0-edit'); + $this->clickDrupalSelector('edit-field-image-0-preview-preview-link'); + + $this->assertSession()->elementExists('css', '#focal-point-derivatives .focal-point-derivative-preview-image'); + $this->assertSession()->elementExists('css', '.focal-point-original-image > #focal-point-preview-image'); + } + +} diff --git a/tests/src/FunctionalJavascript/MediaImageModifyTest.php b/tests/src/FunctionalJavascript/MediaImageModifyTest.php index 5891b7aa8..701fc3e8e 100644 --- a/tests/src/FunctionalJavascript/MediaImageModifyTest.php +++ b/tests/src/FunctionalJavascript/MediaImageModifyTest.php @@ -88,7 +88,9 @@ public function testRemoveAdd(): void { $this->clickAjaxButtonCssSelector('[name="field_paragraphs_0_collapse"]'); /** @var \Drupal\file\FileInterface $file */ $file = $image2->field_image->entity; - $this->assertEquals([$file->getFilename()], $this->getSession()->evaluateScript('jQuery(\'[data-drupal-selector="edit-field-paragraphs-0-preview"] article.media--view-mode-paragraph-preview img\').attr(\'src\').split(\'?\')[0].split(\'/\').splice(-1)'), 'Image file should be identical to previously selected.'); + // On installs that use Drupal 10.3 onwards, the image will be converted to + // a webp image. + $this->assertMatchesRegularExpression('/^' . preg_quote($file->getFilename()) . '(.webp)?$/', $this->getSession()->evaluateScript('jQuery(\'[data-drupal-selector="edit-field-paragraphs-0-preview"] article.media--view-mode-paragraph-preview img\').attr(\'src\').split(\'?\')[0].split(\'/\').splice(-1)')[0], 'Image file should be identical to previously selected.'); // Go to the media view and try deleting the image media. $this->drupalGet('admin/content/media'); @@ -98,14 +100,16 @@ public function testRemoveAdd(): void { /** @var \Drupal\file\FileInterface $file */ $file = $media->get($media->getSource()->getConfiguration()['source_field'])->entity; $this->assertFileExists($file->getFileUri()); - $this->getSession()->getPage()->find('css', 'div.gin-sidebar')->clickLink('Delete'); + $this->getSession()->getPage()->find('css', '[data-drupal-selector="edit-actions"] .gin-more-actions__trigger')->click(); + $this->getSession()->getPage()->find('css', '[data-drupal-selector="edit-actions"]')->clickLink('Delete'); $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#drupal-modal')); $this->assertSession()->fieldNotExists('also_delete_file'); $this->assertSession()->pageTextContains('This action cannot be undone.The file attached to this media is owned by admin so will be retained.'); Role::load(static::$defaultUserRole)->grantPermission('delete any file')->save(); $this->getSession()->reload(); - $this->getSession()->getPage()->find('css', 'div.gin-sidebar')->clickLink('Delete'); + $this->getSession()->getPage()->find('css', '[data-drupal-selector="edit-actions"] .gin-more-actions__trigger')->click(); + $this->getSession()->getPage()->find('css', '[data-drupal-selector="edit-actions"]')->clickLink('Delete'); $this->assertSession()->assertWaitOnAjaxRequest(); $this->assertNotEmpty($this->assertSession()->waitForElementVisible('css', '#drupal-modal')); $this->assertSession()->fieldExists('also_delete_file')->check(); diff --git a/tests/src/FunctionalJavascript/MetaInformationTest.php b/tests/src/FunctionalJavascript/MetaInformationTest.php index 25a561a1b..43bb678ee 100644 --- a/tests/src/FunctionalJavascript/MetaInformationTest.php +++ b/tests/src/FunctionalJavascript/MetaInformationTest.php @@ -104,8 +104,8 @@ class MetaInformationTest extends ThunderJavascriptTestBase { '[node:title]' => 'Test Note Title', // For testing Media:1 is used for teaser. - '[node:field_teaser_media:entity:field_image:facebook]' => 'LIKE:/files/styles/facebook/public/2016-05/thunder.jpg?', - '[node:field_teaser_media:entity:field_image:facebook:mimetype]' => 'image/jpeg', + '[node:field_teaser_media:entity:field_image:facebook]' => 'LIKE:/files/styles/facebook/public/2016-05/thunder.jpg.webp?', + '[node:field_teaser_media:entity:field_image:facebook:mimetype]' => 'image/webp', '[node:field_teaser_media:entity:field_image:facebook:height]' => '630', '[node:field_teaser_media:entity:field_image:facebook:width]' => '1200', ]; @@ -204,6 +204,8 @@ protected function checkSavedConfiguration(string $configurationUrl, array $conf * Test Meta Tag default configuration and custom configuration for article. * * @dataProvider providerContentTypes + * + * @group NoUpdate */ public function testArticleMetaTags(string $contentType): void { $globalConfigs = $this->generateMetaTagConfiguration([static::$globalMetaTags]); diff --git a/tests/src/Kernel/MetatagTest.php b/tests/src/Kernel/MetatagTest.php index 565cfb81e..23b76d3bb 100644 --- a/tests/src/Kernel/MetatagTest.php +++ b/tests/src/Kernel/MetatagTest.php @@ -37,6 +37,7 @@ class MetatagTest extends KernelTestBase { 'views', 'image', 'file', + 'filter', 'focal_point', 'crop', 'media_expire', @@ -113,14 +114,14 @@ public function testTagDefaultValues(): void { $this->assertStringContainsString('/files/styles/facebook/public/image-test.png', $elements['og_image_0']['#attributes']['content']); $this->assertEquals('630', $elements['og_image_height']['#attributes']['content']); $this->assertEquals('1200', $elements['og_image_width']['#attributes']['content']); - $this->assertEquals('image/png', $elements['og_image_type']['#attributes']['content']); + $this->assertEquals('image/webp', $elements['og_image_type']['#attributes']['content']); $this->assertEquals('Test Site', $elements['og_site_name']['#attributes']['content']); $this->assertEquals($title, $elements['og_title']['#attributes']['content']); $this->assertNotEmpty($elements['og_updated_time']['#attributes']['content']); $this->assertStringEndsWith('/node/1', $elements['og_url']['#attributes']['content']); $this->assertEquals($description, $elements['twitter_cards_description']['#attributes']['content']); - $this->assertStringContainsString('/files/styles/twitter/public/image-test.png', $elements['twitter_cards_image']['#attributes']['content']); + $this->assertStringContainsString('/files/styles/twitter/public/image-test.png.webp', $elements['twitter_cards_image']['#attributes']['content']); $this->assertEquals('512', $elements['twitter_cards_image_height']['#attributes']['content']); $this->assertEquals('1024', $elements['twitter_cards_image_width']['#attributes']['content']); $this->assertEquals('summary_large_image', $elements['twitter_cards_type']['#attributes']['content']); @@ -129,7 +130,7 @@ public function testTagDefaultValues(): void { $this->assertEquals($title, $elements['schema_article_headline']['#attributes']['content']); $this->assertEquals('Title', $elements['schema_article_name']['#attributes']['content']); $this->assertEquals($description, $elements['schema_article_description']['#attributes']['content']); - $this->assertStringContainsString('/files/styles/facebook/public/image-test.png', $elements['schema_article_image']['#attributes']['content']['url']); + $this->assertStringContainsString('/files/styles/facebook/public/image-test.png.webp', $elements['schema_article_image']['#attributes']['content']['url']); $this->assertEquals('Test Site', $elements['schema_article_publisher']['#attributes']['content']['name']); $this->assertEquals('Organization', $elements['schema_article_publisher']['#attributes']['content']['@type']); } diff --git a/tests/src/Traits/ThunderGinTestTrait.php b/tests/src/Traits/ThunderGinTestTrait.php new file mode 100644 index 000000000..6672f993c --- /dev/null +++ b/tests/src/Traits/ThunderGinTestTrait.php @@ -0,0 +1,104 @@ +assertSession(); + $submit_button = $assert_session->buttonExists($submit); + + // Check if button has a form attribute set. + if ($form_id = $submit_button->getAttribute('form')) { + $form = $assert_session->elementExists('xpath', "//form[@id='$form_id']"); + $action = $form->getAttribute('action'); + } + // Get the form. + elseif (isset($form_html_id)) { + $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']"); + $submit_button = $assert_session->buttonExists($submit, $form); + $action = $form->getAttribute('action'); + } + else { + // Gin Form Test: Change check to include //form + // so we keep the search in scope of a form. + $submit_button = $assert_session->elementExists('xpath', "//input[@value='$submit']"); + $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button); + $action = $form->getAttribute('action'); + } + + // Edit the form values. + foreach ($edit as $name => $value) { + // Fields / buttons in static area are not in form context. + $field = $assert_session->fieldExists($name); + + // Provide support for the values '1' and '0' for checkboxes instead of + // TRUE and FALSE. + // @todo Get rid of supporting 1/0 by converting all tests cases using + // this to boolean values. + $field_type = $field->getAttribute('type'); + if ($field_type === 'checkbox') { + $value = (bool) $value; + } + $field->setValue($value); + } + + // Submit form. + $this->prepareRequest(); + $submit_button->press(); + + // Ensure that any changes to variables in the other thread are picked up. + $this->refreshVariables(); + + // Check if there are any meta refresh redirects (like Batch API pages). + if ($this->checkForMetaRefresh()) { + // We are finished with all meta refresh redirects, so reset the counter. + $this->metaRefreshCount = 0; + } + + // Log only for WebDriverTestBase tests because for tests using + // DrupalTestBrowser we log with ::getResponseLogHandler. + if ($this->htmlOutputEnabled && !$this->isTestUsingGuzzleClient()) { + $out = $this->getSession()->getPage()->getContent(); + $html_output = 'POST request to: ' . $action . + '
Ending URL: ' . $this->getSession()->getCurrentUrl(); + $html_output .= '
' . $out; + $html_output .= $this->getHtmlOutputHeaders(); + $this->htmlOutput($html_output); + } + + } + +} diff --git a/thunder.info.yml b/thunder.info.yml index 0ba6b3747..0a166329a 100644 --- a/thunder.info.yml +++ b/thunder.info.yml @@ -2,8 +2,8 @@ name: Thunder type: profile description: 'The Drupal based CMS for professional publishing.' project: thunder -core_version_requirement: ~10.2.4 -version: '7.2.2' +core_version_requirement: ~10.3.0 +version: '7.3.3' distribution: name: Thunder diff --git a/thunder.install b/thunder.install index 0fb23417b..c31c7c6a1 100644 --- a/thunder.install +++ b/thunder.install @@ -44,3 +44,16 @@ function _thunder_mark_update_checklist(string $update_id, bool $successful, Upd function thunder_update_last_removed(): int { return 8327; } + +/** + * Add publish state and unpublish state fields to newly supported entity types. + * + * In Drupal 10.3 taxonomy terms can be moderated. + */ +function thunder_update_8328(): \Stringable|string { + if (\Drupal::moduleHandler()->moduleExists('scheduler_content_moderation_integration')) { + $output = _scheduler_content_moderation_integration_add_fields(); + return $output ? implode('
  • ', $output) : t('No update required.'); + } + return t('No update required.'); +} diff --git a/thunder.post_update.php b/thunder.post_update.php index d8e8af5f7..e34b956ee 100644 --- a/thunder.post_update.php +++ b/thunder.post_update.php @@ -21,7 +21,6 @@ function thunder_post_update_0001_upgrade_to_thunder7(array &$sandbox): string { 'media_library_media_modify', 'gin_toolbar', 'jquery_ui', - 'jquery_ui_draggable', 'ckeditor5', ]); @@ -115,3 +114,14 @@ function thunder_post_update_0002_enable_paragraphs_split(array &$sandbox): stri // Output logged messages to related channel of update execution. return $updater->logger()->output(); } + +/** + * Enable sticky action buttons for the Gin theme. + */ +function thunder_post_update_0003_enable_sticky_action_buttons(array &$sandbox): string { + \Drupal::configFactory()->getEditable('gin.settings') + ->set('sticky_action_buttons', TRUE) + ->save(); + + return t('Sticky action buttons enabled.'); +}