diff --git a/composer.json b/composer.json index 0101a9bfb..df49ced96 100644 --- a/composer.json +++ b/composer.json @@ -298,7 +298,7 @@ "https://www.drupal.org/project/auto_entitylabel/issues/3239799": "https://www.drupal.org/files/issues/2021-09-29/issue-3239799.patch" }, "drupal/colorbox": { - "https://www.drupal.org/project/colorbox/issues/3278470": "https://git.drupalcode.org/project/colorbox/-/merge_requests/14.patch" + "https://www.drupal.org/project/colorbox/issues/3278470": "patches/contrib/colorbox-mr-14.patch" }, "drupal/config_readonly": { "https://www.drupal.org/project/config_readonly/issues/2892631": "https://www.drupal.org/files/issues/2023-07-18/config-readonly-2892631-27.patch" @@ -312,17 +312,17 @@ "Unable to resolve path on node in other language than default": "https://www.drupal.org/files/issues/2022-11-30/decouple_router-3111456-resolve-language-issue-58.patch" }, "drupal/default_content": { - "https://www.drupal.org/project/default_content/issues/3160146": "https://git.drupalcode.org/project/default_content/-/merge_requests/15.patch", + "https://www.drupal.org/project/default_content/issues/3160146": "patches/contrib/default_content-mr-15.patch", "https://www.drupal.org/project/default_content/issues/2698425": "https://www.drupal.org/files/issues/2020-09-02/default_content-integrity_constrait_violation-3162987-2.patch" }, "drupal/diff": { - "https://www.drupal.org/project/diff/issues/2882334#comment-13913401": "https://git.drupalcode.org/project/diff/-/merge_requests/42.patch" + "https://www.drupal.org/project/diff/issues/2882334#comment-13913401": "patches/contrib/diff-mr-42.patch" }, "drupal/encrypt": { "https://www.drupal.org/project/encrypt/issues/2975098": "https://www.drupal.org/files/issues/2024-05-23/missing-route-canonical-2975098-5.patch" }, "drupal/field_encrypt": { - "https://www.drupal.org/project/field_encrypt/issues/3299175": "https://git.drupalcode.org/project/field_encrypt/-/merge_requests/31.patch" + "https://www.drupal.org/project/field_encrypt/issues/3299175": "patches/contrib/field_encrypt-mr-31.patch" }, "drupal/field_group": { "https://www.drupal.org/project/field_group/issues/2969051": "https://www.drupal.org/files/issues/2023-12-19/2969051-100_2.patch" @@ -340,12 +340,12 @@ "https://www.drupal.org/project/jsonapi_views/issues/3213575": "https://www.drupal.org/files/issues/2023-09-29/3213575-views_json-pagination-10.patch" }, "drupal/menu_link": { - "https://www.drupal.org/project/menu_link/issues/3358081": "https://git.drupalcode.org/project/menu_link/-/merge_requests/9.patch", + "https://www.drupal.org/project/menu_link/issues/3358081": "patches/contrib/menu_link-mr-9.patch", "https://www.drupal.org/project/menu_link/issues/3145735": "https://www.drupal.org/files/issues/2022-03-22/menu_link-empty-entity-error-3145735-7.patch", - "https://www.drupal.org/project/menu_link/issues/3092282": "https://git.drupalcode.org/project/menu_link/-/merge_requests/8.patch" + "https://www.drupal.org/project/menu_link/issues/3092282": "patches/contrib/menu_link-mr-8.patch" }, "drupal/menu_link_weight": { - "https://www.drupal.org/project/menu_link_weight/issues/2875984": "https://git.drupalcode.org/project/menu_link_weight/-/merge_requests/2.diff", + "https://www.drupal.org/project/menu_link_weight/issues/2875984": "patches/contrib/menu_link_weight-mr-2.patch", "https://www.drupal.org/project/menu_link_weight/issues/3410674": "https://www.drupal.org/files/issues/2023-12-23/menu_link_weight-3410674.patch", "https://www.drupal.org/project/menu_link_weight/issues/3099139": "https://www.drupal.org/files/issues/2019-12-05/menu_link_weight-target_blank-3099139.patch" }, @@ -360,7 +360,7 @@ "https://www.drupal.org/project/redirect/issues/3018897": "https://www.drupal.org/files/issues/2023-09-05/redirect-3018897-22.patch" }, "drupal/response_code_condition": { - "https://www.drupal.org/project/response_code_condition/issues/3366575": "https://git.drupalcode.org/project/response_code_condition/-/merge_requests/2.patch" + "https://www.drupal.org/project/response_code_condition/issues/3366575": "patches/contrib/response_code_condition-mr-2.patch" }, "drupal/shs": { "https://www.drupal.org/project/shs/issues/3047595": "https://www.drupal.org/files/issues/2022-01-06/3047595-5.patch" @@ -379,10 +379,10 @@ "D8CORE-2949: https://www.drupal.org/project/views_infinite_scroll/issues/3173923": "https://www.drupal.org/files/issues/2021-06-30/views_infinite_scroll-3173923-4.patch" }, "drupal/views_taxonomy_term_name_depth": { - "https://www.drupal.org/project/views_taxonomy_term_name_depth/issues/2877249": "https://git.drupalcode.org/project/views_taxonomy_term_name_depth/-/merge_requests/2.diff" + "https://www.drupal.org/project/views_taxonomy_term_name_depth/issues/2877249": "patches/contrib/views_taxonomy_term_name_depth-mr-2.patch" }, "drupal/webp": { - "https://www.drupal.org/project/webp/issues/3281606": "https://git.drupalcode.org/project/webp/-/merge_requests/33.patch" + "https://www.drupal.org/project/webp/issues/3281606": "patches/contrib/webp-mr-33.patch" } } }, diff --git a/patches/contrib/colorbox-mr-14.patch b/patches/contrib/colorbox-mr-14.patch new file mode 100644 index 000000000..1d1fadaa7 --- /dev/null +++ b/patches/contrib/colorbox-mr-14.patch @@ -0,0 +1,32 @@ +From db20d404dbb02a77cf040f6ea3e93f9c7d71b7fe Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Tue, 3 May 2022 13:22:58 -0700 +Subject: [PATCH] 3278470 Decode entities in custom captions with tokens + +--- + colorbox.theme.inc | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/colorbox.theme.inc b/colorbox.theme.inc +index 70ba8dd..566fb00 100644 +--- a/colorbox.theme.inc ++++ b/colorbox.theme.inc +@@ -5,6 +5,7 @@ + * Colorbox theme functions. + */ + ++use Drupal\Component\Render\PlainTextOutput; + use Drupal\Component\Utility\Crypt; + use Drupal\Component\Utility\Xss; + use Drupal\file\Entity\File; +@@ -73,6 +74,7 @@ function template_preprocess_colorbox_formatter(&$variables) { + [$entity_type => $entity, 'file' => $item], + ['clear' => TRUE] + ); ++ $caption = PlainTextOutput::renderFromHtml($caption); + break; + + default: +-- +GitLab + diff --git a/patches/contrib/default_content-mr-15.patch b/patches/contrib/default_content-mr-15.patch new file mode 100644 index 000000000..a216c64bd --- /dev/null +++ b/patches/contrib/default_content-mr-15.patch @@ -0,0 +1,309 @@ +From c65e1a2981afaa11255fda1a6580825158f0c1dd Mon Sep 17 00:00:00 2001 +From: Emil Johnsson +Date: Tue, 1 Feb 2022 13:09:02 +0100 +Subject: [PATCH 1/4] Applied patch from #37 by heddn + +--- + src/Normalizer/ContentEntityNormalizer.php | 38 ++++++++++++++++++++++ + 1 file changed, 38 insertions(+) + +diff --git a/src/Normalizer/ContentEntityNormalizer.php b/src/Normalizer/ContentEntityNormalizer.php +index 4d5a932..aaa036a 100644 +--- a/src/Normalizer/ContentEntityNormalizer.php ++++ b/src/Normalizer/ContentEntityNormalizer.php +@@ -23,6 +23,8 @@ use Drupal\pathauto\PathautoState; + use Drupal\serialization\Normalizer\SerializedColumnNormalizerTrait; + use Drupal\user\UserInterface; + use Symfony\Component\Serializer\Exception\UnexpectedValueException; ++use Drupal\layout_builder\Section; ++use Drupal\layout_builder\Plugin\DataType\SectionData; + + /** + * Normalizes and denormalizes content entities. +@@ -253,6 +255,22 @@ class ContentEntityNormalizer implements ContentEntityNormalizerInterface { + } + $property->setValue($target_entity); + } ++ elseif ($property instanceof SectionData) { ++ // Rebuild references on layout_builder. ++ foreach ($value['components'] as $key => $component) { ++ if (isset($component['configuration']['id'])) { ++ [$component_type] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); ++ if ($component_type == 'inline_block') { ++ $target_uuid = $component['additional']['target_uuid'] ?? NULL; ++ if ($target_uuid && $block_content = $this->entityRepository->loadEntityByUuid('block_content', $target_uuid)) { ++ $value['components'][$key]['configuration']['block_revision_id'] = $block_content->getRevisionId(); ++ } ++ } ++ } ++ } ++ $section_value = Section::fromArray($value); ++ $property->setValue($section_value); ++ } + else { + $property->setValue($value); + } +@@ -419,6 +437,26 @@ class ContentEntityNormalizer implements ContentEntityNormalizerInterface { + $value = $target->uuid(); + } + } ++ elseif ($property instanceof SectionData && $property->getValue() instanceof Section) { ++ $value = $property->getValue()->toArray(); ++ ++ // Layout Sections reference inline blocks by id, we need to reference by ++ // UUID and make sure the section has a dependency on the inline_block. ++ foreach ($value['components'] as $key => $component) { ++ if (isset($component['configuration']['id'])) { ++ // This is only valid for inline_block. ++ [$component_type, $component_bundle] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); ++ if ($component_type == 'inline_block') { ++ $block_revision_id = $component['configuration']['block_revision_id']; ++ $block_content = $this->entityTypeManager->getStorage('block_content')->loadRevision($block_revision_id); ++ if ($block_content) { ++ $value['components'][$key]['additional']['target_uuid'] = $block_content->uuid(); ++ $this->addDependency($block_content); ++ } ++ } ++ } ++ } ++ } + elseif ($property instanceof Uri) { + $value = $property->getValue(); + $scheme = parse_url($value, PHP_URL_SCHEME); +-- +GitLab + + +From e15cf2c278a8b8848ccca0ee4cae732890588d17 Mon Sep 17 00:00:00 2001 +From: Emil Johnsson +Date: Tue, 1 Feb 2022 13:09:10 +0100 +Subject: [PATCH 2/4] Applied patch from #41 by S3b0uN3t (with code cleanup) + +--- + src/Exporter.php | 23 +++++++++++++++++++++++ + 1 file changed, 23 insertions(+) + +diff --git a/src/Exporter.php b/src/Exporter.php +index 6237457..b80dae2 100644 +--- a/src/Exporter.php ++++ b/src/Exporter.php +@@ -11,6 +11,7 @@ use Drupal\Core\Serialization\Yaml; + use Drupal\default_content\Event\DefaultContentEvents; + use Drupal\default_content\Event\ExportEvent; + use Drupal\default_content\Normalizer\ContentEntityNormalizerInterface; ++use Drupal\layout_builder\Plugin\Block\InlineBlock; + use Drupal\user\UserInterface; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + +@@ -199,6 +200,28 @@ class Exporter implements ExporterInterface { + protected function getEntityReferencesRecursive(ContentEntityInterface $entity, $depth = 0, array &$indexed_dependencies = []) { + $entity_dependencies = $entity->referencedEntities(); + ++ // Get dependencies from layout builder. ++ if ($entity->hasField('layout_builder__layout')) { ++ $section_data = $entity->get('layout_builder__layout'); ++ foreach ($section_data->getSections() as $section) { ++ $components = $section->getComponents(); ++ foreach ($components as $component) { ++ $plugin = $component->getPlugin(); ++ if ($plugin instanceof InlineBlock) { ++ $configuration = $component->get('configuration'); ++ if (!empty($block_revision_id = $configuration['block_revision_id'])) { ++ $block_content = $this->entityTypeManager ++ ->getStorage('block_content') ++ ->loadRevision($block_revision_id); ++ if ($block_content) { ++ $entity_dependencies[] = $block_content; ++ } ++ } ++ } ++ } ++ } ++ } ++ + foreach ($entity_dependencies as $dependent_entity) { + // Config entities should not be exported but rather provided by default + // config. +-- +GitLab + + +From e18366bc4b733eaf15973296d110937bf3ba9b31 Mon Sep 17 00:00:00 2001 +From: Florent Torregrosa + <14238-florenttorregrosa@users.noreply.drupalcode.org> +Date: Tue, 6 Dec 2022 15:08:16 +0100 +Subject: [PATCH 3/4] Issue #3160146 by Grimreaper: Remove unused variable, + triggering warnings with non-derivative block plugins. + +--- + src/Normalizer/ContentEntityNormalizer.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Normalizer/ContentEntityNormalizer.php b/src/Normalizer/ContentEntityNormalizer.php +index aaa036a..71152d5 100644 +--- a/src/Normalizer/ContentEntityNormalizer.php ++++ b/src/Normalizer/ContentEntityNormalizer.php +@@ -445,7 +445,7 @@ class ContentEntityNormalizer implements ContentEntityNormalizerInterface { + foreach ($value['components'] as $key => $component) { + if (isset($component['configuration']['id'])) { + // This is only valid for inline_block. +- [$component_type, $component_bundle] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); ++ [$component_type] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); + if ($component_type == 'inline_block') { + $block_revision_id = $component['configuration']['block_revision_id']; + $block_content = $this->entityTypeManager->getStorage('block_content')->loadRevision($block_revision_id); +-- +GitLab + + +From 5c727c9576b2f6b386a141febb55f2e9798fe6a2 Mon Sep 17 00:00:00 2001 +From: CRZDEV +Date: Sun, 10 Mar 2024 19:51:08 +0100 +Subject: [PATCH 4/4] Issue #3160146: Layout builder sections fields normalizer + / denormalizer avoid direct dependency & manage also block serialized + references + +--- + src/Exporter.php | 33 +++++++------ + src/Normalizer/ContentEntityNormalizer.php | 54 +++++++++++----------- + 2 files changed, 44 insertions(+), 43 deletions(-) + +diff --git a/src/Exporter.php b/src/Exporter.php +index b80dae2..0102a27 100644 +--- a/src/Exporter.php ++++ b/src/Exporter.php +@@ -11,9 +11,9 @@ use Drupal\Core\Serialization\Yaml; + use Drupal\default_content\Event\DefaultContentEvents; + use Drupal\default_content\Event\ExportEvent; + use Drupal\default_content\Normalizer\ContentEntityNormalizerInterface; +-use Drupal\layout_builder\Plugin\Block\InlineBlock; + use Drupal\user\UserInterface; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; ++use Drupal\Core\TypedData\TypedDataInternalPropertiesHelper; + + /** + * A service for handling import of default content. +@@ -200,20 +200,23 @@ class Exporter implements ExporterInterface { + protected function getEntityReferencesRecursive(ContentEntityInterface $entity, $depth = 0, array &$indexed_dependencies = []) { + $entity_dependencies = $entity->referencedEntities(); + +- // Get dependencies from layout builder. +- if ($entity->hasField('layout_builder__layout')) { +- $section_data = $entity->get('layout_builder__layout'); +- foreach ($section_data->getSections() as $section) { +- $components = $section->getComponents(); +- foreach ($components as $component) { +- $plugin = $component->getPlugin(); +- if ($plugin instanceof InlineBlock) { +- $configuration = $component->get('configuration'); +- if (!empty($block_revision_id = $configuration['block_revision_id'])) { +- $block_content = $this->entityTypeManager +- ->getStorage('block_content') +- ->loadRevision($block_revision_id); +- if ($block_content) { ++ foreach (TypedDataInternalPropertiesHelper::getNonInternalProperties($entity->getTypedData()) as $name => $field_items) { ++ if (is_a($field_items, 'Drupal\layout_builder\SectionListInterface')) { ++ foreach ($field_items->getSections() as $section) { ++ $components = $section->getComponents(); ++ foreach ($components as $component) { ++ $plugin = $component->getPlugin(); ++ if (is_a($plugin, 'Drupal\layout_builder\Plugin\Block\InlineBlock')) { ++ $reflectionMethod = new \ReflectionMethod($plugin, 'getEntity'); ++ $reflectionMethod->setAccessible(TRUE); ++ $block_content = $reflectionMethod->invoke($plugin); ++ $configuration = $component->get('configuration'); ++ // Look for any inner references. ++ if (!empty($configuration['block_serialized']) && empty($block_content->id())) { ++ $indexed_dependencies += $this->getEntityReferencesRecursive($block_content, $depth, $indexed_dependencies); ++ } ++ // Add direct dependency. ++ elseif(!empty($configuration['block_revision_id'])) { + $entity_dependencies[] = $block_content; + } + } +diff --git a/src/Normalizer/ContentEntityNormalizer.php b/src/Normalizer/ContentEntityNormalizer.php +index 71152d5..93e8a2a 100644 +--- a/src/Normalizer/ContentEntityNormalizer.php ++++ b/src/Normalizer/ContentEntityNormalizer.php +@@ -23,8 +23,6 @@ use Drupal\pathauto\PathautoState; + use Drupal\serialization\Normalizer\SerializedColumnNormalizerTrait; + use Drupal\user\UserInterface; + use Symfony\Component\Serializer\Exception\UnexpectedValueException; +-use Drupal\layout_builder\Section; +-use Drupal\layout_builder\Plugin\DataType\SectionData; + + /** + * Normalizes and denormalizes content entities. +@@ -255,20 +253,19 @@ class ContentEntityNormalizer implements ContentEntityNormalizerInterface { + } + $property->setValue($target_entity); + } +- elseif ($property instanceof SectionData) { +- // Rebuild references on layout_builder. ++ elseif (is_a($property, 'Drupal\layout_builder\Plugin\DataType\SectionData')) { + foreach ($value['components'] as $key => $component) { +- if (isset($component['configuration']['id'])) { +- [$component_type] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); +- if ($component_type == 'inline_block') { +- $target_uuid = $component['additional']['target_uuid'] ?? NULL; +- if ($target_uuid && $block_content = $this->entityRepository->loadEntityByUuid('block_content', $target_uuid)) { +- $value['components'][$key]['configuration']['block_revision_id'] = $block_content->getRevisionId(); +- } +- } ++ // Replace normalized per serialized entity. ++ if (is_array($component['configuration'] ?? NULL) && is_array($component['configuration']['block_serialized'] ?? NULL) && ($component['configuration']['block_serialized']['_meta']['entity_type'] ?? NULL) == 'block_content') { ++ $target_entity = $this->denormalize($component['configuration']['block_serialized']); ++ $value['components'][$key]['configuration']['block_serialized'] = serialize($target_entity); ++ } ++ // Replace uuid per id. ++ elseif (is_array($component['configuration'] ?? NULL) && !empty($component['configuration']['block_revision_id']) && $block_content = $this->entityRepository->loadEntityByUuid('block_content', $component['configuration']['block_revision_id'])) { ++ $value['components'][$key]['configuration']['block_revision_id'] = $block_content->getRevisionId(); + } + } +- $section_value = Section::fromArray($value); ++ $section_value = 'Drupal\layout_builder\Section'::fromArray($value); + $property->setValue($section_value); + } + else { +@@ -437,22 +434,23 @@ class ContentEntityNormalizer implements ContentEntityNormalizerInterface { + $value = $target->uuid(); + } + } +- elseif ($property instanceof SectionData && $property->getValue() instanceof Section) { ++ elseif (is_a($property, 'Drupal\layout_builder\Plugin\DataType\SectionData') && is_a($property->getValue(), 'Drupal\layout_builder\Section')) { + $value = $property->getValue()->toArray(); +- +- // Layout Sections reference inline blocks by id, we need to reference by +- // UUID and make sure the section has a dependency on the inline_block. +- foreach ($value['components'] as $key => $component) { +- if (isset($component['configuration']['id'])) { +- // This is only valid for inline_block. +- [$component_type] = explode(PluginBase::DERIVATIVE_SEPARATOR, $component['configuration']['id']); +- if ($component_type == 'inline_block') { +- $block_revision_id = $component['configuration']['block_revision_id']; +- $block_content = $this->entityTypeManager->getStorage('block_content')->loadRevision($block_revision_id); +- if ($block_content) { +- $value['components'][$key]['additional']['target_uuid'] = $block_content->uuid(); +- $this->addDependency($block_content); +- } ++ $components = $property->getValue()->getComponents(); ++ foreach ($components as $component_uuid => $component) { ++ $plugin = $component->getPlugin(); ++ if (is_a($plugin, 'Drupal\layout_builder\Plugin\Block\InlineBlock')) { ++ $reflectionMethod = new \ReflectionMethod($plugin, 'getEntity'); ++ $reflectionMethod->setAccessible(TRUE); ++ $block_content = $reflectionMethod->invoke($plugin); ++ // Replace serialized per normalized entity. ++ if (!empty($value['components'][$component_uuid]['configuration']['block_serialized']) && empty($block_content->id())) { ++ $value['components'][$component_uuid]['configuration']['block_serialized'] = $this->normalize($block_content); ++ } ++ // Replace id per uuid. ++ elseif (!empty($value['components'][$component_uuid]['configuration']['block_revision_id'])) { ++ $value['components'][$component_uuid]['configuration']['block_revision_id'] = $block_content->uuid(); ++ $this->addDependency($block_content); + } + } + } +-- +GitLab + diff --git a/patches/contrib/diff-mr-42.patch b/patches/contrib/diff-mr-42.patch new file mode 100644 index 000000000..45f36d0a3 --- /dev/null +++ b/patches/contrib/diff-mr-42.patch @@ -0,0 +1,233 @@ +From 5426d831cda940cfc2e624f2ce505e429ce4469e Mon Sep 17 00:00:00 2001 +From: Christopher Lewis +Date: Wed, 7 Feb 2024 13:44:26 +0100 +Subject: [PATCH 1/2] Move revision translation conditions to query, and work + on the translation when building the row. + +--- + src/Form/RevisionOverviewForm.php | 137 +++++++++++++++--------------- + 1 file changed, 69 insertions(+), 68 deletions(-) + +diff --git a/src/Form/RevisionOverviewForm.php b/src/Form/RevisionOverviewForm.php +index be03826..4a52e12 100755 +--- a/src/Form/RevisionOverviewForm.php ++++ b/src/Form/RevisionOverviewForm.php +@@ -156,6 +156,8 @@ class RevisionOverviewForm extends FormBase { + ->condition($node->getEntityType()->getKey('id'), $node->id()) + ->pager($pagerLimit) + ->allRevisions() ++ ->condition($node->getEntityType()->getKey('langcode'), $langcode) ++ ->condition($node->getEntityType()->getKey('revision_translation_affected'), '1') + ->sort($node->getEntityType()->getKey('revision'), 'DESC') + // Access to the content has already been verified. Disable query-level + // access checking so that revisions for unpublished content still +@@ -217,83 +219,82 @@ class RevisionOverviewForm extends FormBase { + } + /** @var \Drupal\Core\Entity\ContentEntityInterface $revision */ + if ($revision = $node_storage->loadRevision($vid)) { +- if ($revision->hasTranslation($langcode) && $revision->getTranslation($langcode)->isRevisionTranslationAffected()) { +- $username = [ +- '#theme' => 'username', +- '#account' => $revision->getRevisionUser(), +- ]; +- $revision_date = $this->date->format($revision->getRevisionCreationTime(), 'short'); +- // Use revision link to link to revisions that are not active. +- if ($vid != $node->getRevisionId()) { +- $link = Link::fromTextAndUrl($revision_date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); +- } +- else { +- $link = $node->toLink($revision_date); +- } ++ $revision = $revision->getTranslation($langcode); ++ $username = [ ++ '#theme' => 'username', ++ '#account' => $revision->getRevisionUser(), ++ ]; ++ $revision_date = $this->date->format($revision->getRevisionCreationTime(), 'short'); ++ // Use revision link to link to revisions that are not active. ++ if ($vid != $node->getRevisionId()) { ++ $link = Link::fromTextAndUrl($revision_date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid])); ++ } ++ else { ++ $link = $node->toLink($revision_date); ++ } + +- if ($vid == $default_revision) { +- $row = [ +- 'revision' => $this->buildRevision($link, $username, $revision, $previous_revision), +- ]; ++ if ($vid == $default_revision) { ++ $row = [ ++ 'revision' => $this->buildRevision($link, $username, $revision, $previous_revision), ++ ]; + +- // Allow comparisons only if there are 2 or more revisions. +- if ($revision_count > 1) { +- $row += [ +- 'select_column_one' => $this->buildSelectColumn('radios_left', $vid, FALSE), +- 'select_column_two' => $this->buildSelectColumn('radios_right', $vid, $vid), +- ]; +- } +- $row['operations'] = [ +- '#prefix' => '', +- '#markup' => $this->t('Current revision'), +- '#suffix' => '', +- '#attributes' => [ +- 'class' => ['revision-current'], +- ], +- ]; +- $row['#attributes'] = [ +- 'class' => ['revision-current'], ++ // Allow comparisons only if there are 2 or more revisions. ++ if ($revision_count > 1) { ++ $row += [ ++ 'select_column_one' => $this->buildSelectColumn('radios_left', $vid, FALSE), ++ 'select_column_two' => $this->buildSelectColumn('radios_right', $vid, $vid), + ]; + } +- else { +- $route_params = [ +- 'node' => $node->id(), +- 'node_revision' => $vid, +- 'langcode' => $langcode, +- ]; +- $links = []; +- if ($revert_permission) { +- $links['revert'] = [ +- 'title' => $vid < $node->getRevisionId() ? $this->t('Revert') : $this->t('Set as current revision'), +- 'url' => $has_translations ? ++ $row['operations'] = [ ++ '#prefix' => '', ++ '#markup' => $this->t('Current revision'), ++ '#suffix' => '', ++ '#attributes' => [ ++ 'class' => ['revision-current'], ++ ], ++ ]; ++ $row['#attributes'] = [ ++ 'class' => ['revision-current'], ++ ]; ++ } ++ else { ++ $route_params = [ ++ 'node' => $node->id(), ++ 'node_revision' => $vid, ++ 'langcode' => $langcode, ++ ]; ++ $links = []; ++ if ($revert_permission) { ++ $links['revert'] = [ ++ 'title' => $vid < $node->getRevisionId() ? $this->t('Revert') : $this->t('Set as current revision'), ++ 'url' => $has_translations ? + Url::fromRoute('node.revision_revert_translation_confirm', ['node' => $node->id(), 'node_revision' => $vid, 'langcode' => $langcode]) : + Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]), +- ]; +- } +- if ($delete_permission) { +- $links['delete'] = [ +- 'title' => $this->t('Delete'), +- 'url' => Url::fromRoute('node.revision_delete_confirm', $route_params), +- ]; +- } +- +- // Here we don't have to deal with 'only one revision' case because +- // if there's only one revision it will also be the default one, +- // entering on the first branch of this if else statement. +- $row = [ +- 'revision' => $this->buildRevision($link, $username, $revision, $previous_revision), +- 'select_column_one' => $this->buildSelectColumn('radios_left', $vid, +- $vids[1] ?? FALSE), +- 'select_column_two' => $this->buildSelectColumn('radios_right', $vid, FALSE), +- 'operations' => [ +- '#type' => 'operations', +- '#links' => $links, +- ], + ]; + } +- // Add the row to the table. +- $build['node_revisions_table'][] = $row; ++ if ($delete_permission) { ++ $links['delete'] = [ ++ 'title' => $this->t('Delete'), ++ 'url' => Url::fromRoute('node.revision_delete_confirm', $route_params), ++ ]; ++ } ++ ++ // Here we don't have to deal with 'only one revision' case because ++ // if there's only one revision it will also be the default one, ++ // entering on the first branch of this if else statement. ++ $row = [ ++ 'revision' => $this->buildRevision($link, $username, $revision, $previous_revision), ++ 'select_column_one' => $this->buildSelectColumn('radios_left', $vid, ++ $vids[1] ?? FALSE), ++ 'select_column_two' => $this->buildSelectColumn('radios_right', $vid, FALSE), ++ 'operations' => [ ++ '#type' => 'operations', ++ '#links' => $links, ++ ], ++ ]; + } ++ // Add the row to the table. ++ $build['node_revisions_table'][] = $row; + } + } + +-- +GitLab + + +From 07ede6614d9a117ccf3235de797d85eeb0e9e598 Mon Sep 17 00:00:00 2001 +From: Christopher Lewis +Date: Wed, 7 Feb 2024 17:49:33 +0100 +Subject: [PATCH 2/2] Integrate logic from #3418442 + +--- + src/Form/RevisionOverviewForm.php | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/src/Form/RevisionOverviewForm.php b/src/Form/RevisionOverviewForm.php +index 4a52e12..c47472d 100755 +--- a/src/Form/RevisionOverviewForm.php ++++ b/src/Form/RevisionOverviewForm.php +@@ -148,23 +148,31 @@ class RevisionOverviewForm extends FormBase { + $languages = $node->getTranslationLanguages(); + $has_translations = (count($languages) > 1); + $node_storage = $this->entityTypeManager->getStorage('node'); ++ $entity_type = $node->getEntityType(); + $type = $node->getType(); + + $pagerLimit = $this->config->get('general_settings.revision_pager_limit'); + + $query = $this->entityTypeManager->getStorage('node')->getQuery() +- ->condition($node->getEntityType()->getKey('id'), $node->id()) +- ->pager($pagerLimit) + ->allRevisions() +- ->condition($node->getEntityType()->getKey('langcode'), $langcode) +- ->condition($node->getEntityType()->getKey('revision_translation_affected'), '1') +- ->sort($node->getEntityType()->getKey('revision'), 'DESC') ++ ->condition($entity_type->getKey('id'), $node->id()) + // Access to the content has already been verified. Disable query-level + // access checking so that revisions for unpublished content still + // appear. + ->accessCheck(FALSE) +- ->execute(); +- $vids = array_keys($query); ++ ->pager($pagerLimit); ++ ++ if ($entity_type->isTranslatable()) { ++ if ($langcode && ($langcode_key = $entity_type->getKey('langcode'))) { ++ $query->condition($langcode_key, $langcode); ++ } ++ if ($revision_translation_affected_key = $entity_type->getKey('revision_translation_affected')) { ++ $query->condition($revision_translation_affected_key, '1'); ++ } ++ } ++ ++ $query->sort($entity_type->getKey('revision'), 'DESC'); ++ $vids = array_keys($query->execute()); + + $revision_count = count($vids); + +-- +GitLab + diff --git a/patches/contrib/field_encrypt-mr-31.patch b/patches/contrib/field_encrypt-mr-31.patch new file mode 100644 index 000000000..caacc5f84 --- /dev/null +++ b/patches/contrib/field_encrypt-mr-31.patch @@ -0,0 +1,44 @@ +From 3cb536cc6dea8bac4373e8f470355ccb5d0af73c Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Fri, 22 Jul 2022 15:21:34 -0700 +Subject: [PATCH] #3299175 Prevent fatal error if eval function is not allowed + +--- + field_encrypt.module | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/field_encrypt.module b/field_encrypt.module +index 215b7d2..9ae38ca 100644 +--- a/field_encrypt.module ++++ b/field_encrypt.module +@@ -318,7 +318,9 @@ function field_encrypt_module_implements_alter(&$implementations, $hook) { + // Move our implementations as early as possible so other + // implementations work with decrypted data. + if (!isset($implementations['field_encrypt'])) { +- _field_encrypt_define_entity_hooks($entity_type_id); ++ if (!_field_encrypt_define_entity_hooks($entity_type_id)) { ++ return; ++ } + } + $group = $implementations['field_encrypt'] ?? FALSE; + $implementations = ['field_encrypt' => $group] + $implementations; +@@ -338,7 +340,7 @@ function field_encrypt_module_implements_alter(&$implementations, $hook) { + */ + function _field_encrypt_define_entity_hooks($entity_type_id = NULL) { + if (!_field_encrypt_can_eval()) { +- return; ++ return FALSE; + } + $functions = _field_encrypt_entity_hooks($entity_type_id); + if ($functions) { +@@ -347,6 +349,7 @@ function _field_encrypt_define_entity_hooks($entity_type_id = NULL) { + eval($functions); + // phpcs:enable + } ++ return $functions; + } + + /** +-- +GitLab + diff --git a/patches/contrib/menu_link-mr-8.patch b/patches/contrib/menu_link-mr-8.patch new file mode 100644 index 000000000..e5e2cc75c --- /dev/null +++ b/patches/contrib/menu_link-mr-8.patch @@ -0,0 +1,32 @@ +From e3790112e4f5b45f7ba520cc2f1b8957ddc4544d Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Wed, 26 Apr 2023 11:53:26 -0700 +Subject: [PATCH] #3092282 Add support for menu_admin_per_menu module + +--- + src/Plugin/Field/MenuLinkItemList.php | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/src/Plugin/Field/MenuLinkItemList.php b/src/Plugin/Field/MenuLinkItemList.php +index c65ebdc..83d11c9 100644 +--- a/src/Plugin/Field/MenuLinkItemList.php ++++ b/src/Plugin/Field/MenuLinkItemList.php +@@ -24,7 +24,14 @@ class MenuLinkItemList extends FieldItemList { + */ + public function defaultAccess($operation = 'view', AccountInterface $account = NULL) { + if ($operation === 'edit') { +- return AccessResult::allowedIfHasPermission($account, 'administer menu'); ++ $available_menus = array_filter($this->getFieldDefinition() ++ ->getSetting('available_menus')); ++ ++ $permissions = ['administer menu']; ++ foreach ($available_menus as $menu_name) { ++ $permissions[] = 'administer ' . $menu_name . ' menu items'; ++ } ++ return AccessResult::allowedIfHasPermissions($account, $permissions, 'OR'); + } + return parent::defaultAccess($operation, $account); + } +-- +GitLab + diff --git a/patches/contrib/menu_link-mr-9.patch b/patches/contrib/menu_link-mr-9.patch new file mode 100644 index 000000000..d31efe0fb --- /dev/null +++ b/patches/contrib/menu_link-mr-9.patch @@ -0,0 +1,273 @@ +From 51020dbd17c3c2e274046f10aa265a3577e00fc5 Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Wed, 3 May 2023 20:45:00 -0700 +Subject: [PATCH] #3358081 Add "Expanded" option to field type and widget + +--- + config/schema/menu_link.schema.yml | 5 +- + menu_link.install | 106 ++++++++++++++++++ + src/Plugin/Field/FieldType/MenuLinkItem.php | 21 +++- + .../Field/FieldWidget/MenuLinkWidget.php | 14 +++ + src/Render/Element/MenuDetails.php | 3 +- + 5 files changed, 146 insertions(+), 3 deletions(-) + +diff --git a/config/schema/menu_link.schema.yml b/config/schema/menu_link.schema.yml +index 283ce44..2cb67d8 100644 +--- a/config/schema/menu_link.schema.yml ++++ b/config/schema/menu_link.schema.yml +@@ -24,7 +24,7 @@ field.storage_settings.menu_link: + menu_link_per_translation: + type: boolean + label: Expose a menu link per translation +- ++ + field.field_settings.menu_link: + type: mapping + label: 'Menu link settings' +@@ -37,6 +37,9 @@ field.field_settings.menu_link: + default_menu_parent: + type: string + label: 'Default menu parent' ++ default_expanded: ++ type: boolean ++ label: 'Default expanded' + + field.value.menu_link: + type: mapping +diff --git a/menu_link.install b/menu_link.install +index c07c776..691ab46 100644 +--- a/menu_link.install ++++ b/menu_link.install +@@ -5,6 +5,7 @@ + */ + + use Drupal\Core\Database\Database; ++use Drupal\Core\Entity\Sql\SqlContentEntityStorageException; + + /** + * Implements hook_theme(). +@@ -30,3 +31,108 @@ function menu_link_update_9201() { + ->condition('provider','', '=') + ->execute(); + } ++ ++/** ++ * Add "Expanded" column to field storage table. ++ */ ++function menu_link_update_9202(){ ++ _field_type_schema_column_add_helper('menu_link', ['expanded']); ++} ++ ++/** ++ * Helper function for HOOK_Update to update the field schema columns. ++ * ++ * Based on address.install (thanks to the maintainer!) ++ * ++ * @param $field_type ++ * The field type id. ++ * @param array $columns_to_add ++ * Array of the column names from schema() function. ++ */ ++function _field_type_schema_column_add_helper($field_type, array $columns_to_add = []) { ++ $processed_fields = []; ++ $field_type_manager = \Drupal::service('plugin.manager.field.field_type'); ++ $field_definition = $field_type_manager->getDefinition($field_type); ++ $field_item_class = $field_definition['class']; ++ ++ $schema = \Drupal::database()->schema(); ++ $entity_type_manager = \Drupal::entityTypeManager(); ++ $entity_field_manager = \Drupal::service('entity_field.manager'); ++ $entity_field_map = $entity_field_manager->getFieldMapByFieldType($field_type); ++ // The key-value collection for tracking installed storage schema. ++ $entity_storage_schema_sql = \Drupal::keyValue('entity.storage_schema.sql'); ++ $entity_definitions_installed = \Drupal::keyValue('entity.definitions.installed'); ++ ++ foreach ($entity_field_map as $entity_type_id => $field_map) { ++ $entity_storage = $entity_type_manager->getStorage($entity_type_id); ++ ++ $entity_type = $entity_type_manager->getDefinition($entity_type_id); ++ $field_storage_definitions = $entity_field_manager->getFieldStorageDefinitions($entity_type_id); ++ /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ ++ $table_mapping = $entity_storage->getTableMapping($field_storage_definitions); ++ // Only need field storage definitions of our field type: ++ /** @var \\Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage_definition */ ++ foreach (array_intersect_key($field_storage_definitions, $field_map) as $field_storage_definition) { ++ $field_name = $field_storage_definition->getName(); ++ try { ++ $table = $table_mapping->getFieldTableName($field_name); ++ } ++ catch (SqlContentEntityStorageException $e) { ++ // Custom storage? Broken site? No matter what, if there is no table ++ // or column, there's little we can do. ++ continue; ++ } ++ // See if the field has a revision table. ++ $revision_table = NULL; ++ if ($entity_type->isRevisionable() && $field_storage_definition->isRevisionable()) { ++ if ($table_mapping->requiresDedicatedTableStorage($field_storage_definition)) { ++ $revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage_definition); ++ } ++ elseif ($table_mapping->allowsSharedTableStorage($field_storage_definition)) { ++ $revision_table = $entity_type->getRevisionDataTable() ?: $entity_type->getRevisionTable(); ++ } ++ } ++ // Load the installed field schema so that it can be updated. ++ $schema_key = "$entity_type_id.field_schema_data.$field_name"; ++ $field_schema_data = $entity_storage_schema_sql->get($schema_key); ++ ++ $processed_fields[] = [$entity_type_id, $field_name]; ++ // Loop over each new column and add it as a schema column change. ++ foreach ($columns_to_add as $column_id) { ++ $column = $table_mapping->getFieldColumnName($field_storage_definition, $column_id); ++ // Add `initial_from_field` to the new spec, as this will copy over ++ // the entire data. ++ $field_schema = $field_item_class::schema($field_storage_definition); ++ $spec = $field_schema['columns'][$column_id]; ++ ++ // Add the new column. ++ $schema->addField($table, $column, $spec); ++ if ($revision_table) { ++ $schema->addField($revision_table, $column, $spec); ++ } ++ ++ // Add the new column to the installed field schema. ++ if (!empty($field_schema_data)) { ++ $field_schema_data[$table]['fields'][$column] = $field_schema['columns'][$column_id]; ++ $field_schema_data[$table]['fields'][$column]['not null'] = FALSE; ++ if ($revision_table) { ++ $field_schema_data[$revision_table]['fields'][$column] = $field_schema['columns'][$column_id]; ++ $field_schema_data[$revision_table]['fields'][$column]['not null'] = FALSE; ++ } ++ } ++ } ++ ++ // Save changes to the installed field schema. ++ if (!empty($field_schema_data)) { ++ $entity_storage_schema_sql->set($schema_key, $field_schema_data); ++ } ++ if ($table_mapping->allowsSharedTableStorage($field_storage_definition)) { ++ $key = "$entity_type_id.field_storage_definitions"; ++ if ($definitions = $entity_definitions_installed->get($key)) { ++ $definitions[$field_name] = $field_storage_definition; ++ $entity_definitions_installed->set($key, $definitions); ++ } ++ } ++ } ++ } ++} +diff --git a/src/Plugin/Field/FieldType/MenuLinkItem.php b/src/Plugin/Field/FieldType/MenuLinkItem.php +index 054492c..6685a19 100644 +--- a/src/Plugin/Field/FieldType/MenuLinkItem.php ++++ b/src/Plugin/Field/FieldType/MenuLinkItem.php +@@ -100,6 +100,7 @@ class MenuLinkItem extends FieldItemBase { + + $settings['available_menus'] = ['main']; + $settings['default_menu_parent'] = 'main:'; ++ $settings['default_expanded'] = FALSE; + return $settings; + } + +@@ -136,6 +137,13 @@ class MenuLinkItem extends FieldItemBase { + '#options' => $parent_options, + ]; + ++ $form['default_expanded'] = [ ++ '#type' => 'checkbox', ++ '#title' => $this->t('Default expanded'), ++ '#description' => $this->t('Whether the menu link should be expanded by default.'), ++ '#default_value' => $this->getSetting('default_expanded'), ++ ]; ++ + return $form; + } + +@@ -156,6 +164,8 @@ class MenuLinkItem extends FieldItemBase { + ->setSetting('default', ''); + $definitions['weight'] = DataDefinition::create('integer') + ->setLabel(t('Menu link weight')); ++ $definitions['expanded'] = DataDefinition::create('integer') ++ ->setLabel(t('Show as Expanded')); + + return $definitions; + } +@@ -199,6 +209,14 @@ class MenuLinkItem extends FieldItemBase { + 'type' => 'int', + ]; + ++ $schema['columns']['expanded'] = [ ++ 'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded) ', ++ 'type' => 'int', ++ 'not null' => TRUE, ++ 'default' => 0, ++ 'size' => 'small', ++ ]; ++ + return $schema; + } + +@@ -288,7 +306,7 @@ class MenuLinkItem extends FieldItemBase { + if (!$this->values['menu_name']) { + return FALSE; + } +- ++ + $entity = $this->getEntity(); + $menu_definition['id'] = $this->getMenuPluginId($langcode); + +@@ -310,6 +328,7 @@ class MenuLinkItem extends FieldItemBase { + $menu_definition['metadata']['langcode'] = $langcode; + $menu_definition['metadata']['translatable'] = $entity->getEntityType()->isTranslatable(); + $menu_definition['provider'] = 'menu_link'; ++ $menu_definition['expanded'] = isset($this->values['expanded']) ? $this->values['expanded'] : $this->getFieldDefinition()->getSetting('default_expanded'); + + $url = $entity->toUrl('canonical'); + $menu_definition['route_name'] = $url->getRouteName(); +diff --git a/src/Plugin/Field/FieldWidget/MenuLinkWidget.php b/src/Plugin/Field/FieldWidget/MenuLinkWidget.php +index a7257fb..ae65cae 100644 +--- a/src/Plugin/Field/FieldWidget/MenuLinkWidget.php ++++ b/src/Plugin/Field/FieldWidget/MenuLinkWidget.php +@@ -155,6 +155,20 @@ class MenuLinkWidget extends WidgetBase { + '#description' => $this->t('Menu links with lower weights are displayed before links with higher weights.'), + ]; + ++ $field_default = (bool) $this->fieldDefinition->getSetting('default_expanded'); ++ ++ $element['expanded'] = [ ++ '#type' => 'checkbox', ++ '#title' => $this->t('Show as expanded'), ++ '#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'), ++ '#default_value' => isset($items[$delta]->expanded) ? $items[$delta]->expanded : $field_default, ++ '#states' => [ ++ 'visible' => [ ++ ':input[name="' . $this->fieldDefinition->getName() . '[0][enabled]"]' => ['checked' => TRUE], ++ ], ++ ] ++ ]; ++ + return $element; + } + +diff --git a/src/Render/Element/MenuDetails.php b/src/Render/Element/MenuDetails.php +index fd968fd..bba2a83 100644 +--- a/src/Render/Element/MenuDetails.php ++++ b/src/Render/Element/MenuDetails.php +@@ -31,6 +31,7 @@ class MenuDetails implements TrustedCallbackInterface { + $element['menu']['description'] = $element['description']; + $element['menu']['menu_parent'] = $element['menu_parent']; + $element['menu']['weight'] = $element['weight']; ++ $element['menu']['expanded'] = $element['expanded']; + + // Hide the elements when enabled is disabled. + foreach (['title', 'description', 'menu_parent', 'weight'] as $form_element) { +@@ -41,7 +42,7 @@ class MenuDetails implements TrustedCallbackInterface { + ]; + } + +- unset($element['enabled'], $element['title'], $element['description'], $element['menu_parent'], $element['weight']); ++ unset($element['enabled'], $element['title'], $element['description'], $element['menu_parent'], $element['weight'], $element['expanded']); + + return $element; + } +-- +GitLab + diff --git a/patches/contrib/menu_link_weight-mr-2.patch b/patches/contrib/menu_link_weight-mr-2.patch new file mode 100644 index 000000000..df862d852 --- /dev/null +++ b/patches/contrib/menu_link_weight-mr-2.patch @@ -0,0 +1,36 @@ +diff --git a/menu_link_weight.menu_ui.inc b/menu_link_weight.menu_ui.inc +index 70884276b47edbc71e2f91d810d0af2fd365bd2d..b80e4666b37e8b87212a9c471bbf57e286a70956 100644 +--- a/menu_link_weight.menu_ui.inc ++++ b/menu_link_weight.menu_ui.inc +@@ -55,10 +55,10 @@ function _menu_link_weight_menu_link_form_alter(&$form, FormStateInterface $form + + $is_admin = $current_user->hasPermission('administer menu') && isset($form); + $is_admin_per_menu = $module_handler->moduleExists('menu_admin_per_menu') +- && function_exists('_menu_admin_per_menu_filter_parent_options') +- && isset($form['parent']) ++ && function_exists('menu_admin_per_menu_filter_parent_options') ++ && isset($form['menu_parent']['#options']) + && !$current_user->hasPermission('administer menu') +- && _menu_admin_per_menu_filter_parent_options($form); ++ && menu_admin_per_menu_filter_parent_options($current_user, $form['menu_parent']['#options']); + + // Only allow users with the "administer menu" permission or that the Menu + // Admin Per Menu has granted access to some menus. +diff --git a/menu_link_weight.node.inc b/menu_link_weight.node.inc +index a7120cf0435aceb9111299b15e603ab98ef0eb01..1cc88d077e4d4db55d620515492b6ddced2a5dd3 100644 +--- a/menu_link_weight.node.inc ++++ b/menu_link_weight.node.inc +@@ -18,10 +18,10 @@ function menu_link_weight_form_node_form_alter(&$form, FormStateInterface $form_ + + $is_admin = $current_user->hasPermission('administer menu') && isset($form['field_menulink']['widget'][0]); + $is_admin_per_menu = $module_handler->moduleExists('menu_admin_per_menu') +- && function_exists('_menu_admin_per_menu_filter_parent_options') +- && isset($form['field_menulink']['widget'][0]['parent']) ++ && function_exists('menu_admin_per_menu_filter_parent_options') ++ && isset($form['field_menulink']['widget'][0]['menu_parent']['#options']) + && !$current_user->hasPermission('administer menu') +- && _menu_admin_per_menu_filter_parent_options($form['field_menulink']['widget'][0]); ++ && menu_admin_per_menu_filter_parent_options($current_user, $form['field_menulink']['widget'][0]['menu_parent']['#options']); + + // Only allow users with the "administer menu" permission or that the Menu + // Admin Per Menu has granted access to some menus. diff --git a/patches/contrib/response_code_condition-mr-2.patch b/patches/contrib/response_code_condition-mr-2.patch new file mode 100644 index 000000000..3719739b7 --- /dev/null +++ b/patches/contrib/response_code_condition-mr-2.patch @@ -0,0 +1,36 @@ +From da85277a7dc6ccae0cdec00ea2940691de9af391 Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Tue, 13 Jun 2023 13:00:35 -0700 +Subject: [PATCH] + +--- + src/Plugin/Condition/ResponseCodeCondition.php | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/Plugin/Condition/ResponseCodeCondition.php b/src/Plugin/Condition/ResponseCodeCondition.php +index 5cd3e50..3ca320f 100644 +--- a/src/Plugin/Condition/ResponseCodeCondition.php ++++ b/src/Plugin/Condition/ResponseCodeCondition.php +@@ -7,6 +7,7 @@ use Drupal\Core\Form\FormStateInterface; + use Drupal\Core\Plugin\ContainerFactoryPluginInterface; + use Symfony\Component\DependencyInjection\ContainerInterface; + use Symfony\Component\HttpFoundation\RequestStack; ++use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; + + /** + * Provides a 'Response code' condition. +@@ -99,7 +100,10 @@ class ResponseCodeCondition extends ConditionPluginBase implements ContainerFact + return TRUE; + } + +- if (!$this->requestStack->getCurrentRequest()->attributes->has('exception')) { ++ if ( ++ !$this->requestStack->getCurrentRequest()->attributes->has('exception') || ++ !($this->requestStack->getCurrentRequest()->attributes->get('exception') instanceof HttpExceptionInterface) ++ ) { + return FALSE; + } + +-- +GitLab + diff --git a/patches/contrib/views_taxonomy_term_name_depth-mr-2.patch b/patches/contrib/views_taxonomy_term_name_depth-mr-2.patch new file mode 100644 index 000000000..af6159bbc --- /dev/null +++ b/patches/contrib/views_taxonomy_term_name_depth-mr-2.patch @@ -0,0 +1,173 @@ +From 38a409c77a3435dac8e39637bb94f4835bd569d3 Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Wed, 7 Jul 2021 09:54:57 -0700 +Subject: [PATCH 1/2] #2877249 Allow multiple terms in the contextual argument + +--- + src/Plugin/views/argument/IndexNameDepth.php | 106 ++++++++++--------- + 1 file changed, 57 insertions(+), 49 deletions(-) + +diff --git a/src/Plugin/views/argument/IndexNameDepth.php b/src/Plugin/views/argument/IndexNameDepth.php +index a51f97b..580b56c 100644 +--- a/src/Plugin/views/argument/IndexNameDepth.php ++++ b/src/Plugin/views/argument/IndexNameDepth.php +@@ -159,65 +159,30 @@ class IndexNameDepth extends ArgumentPluginBase { + public function query($group_by = FALSE) { + $this->ensureMyTable(); + +- if (!empty($this->options['break_phrase'])) { +- $break = static::breakString($this->argument); +- if ($break->value === [-1]) { +- return FALSE; ++ $break = static::breakString($this->argument); ++ if (count($break->value) > 1) { ++ if (!empty($this->options['break_phrase'])) { ++ if ($break->value === [-1]) { ++ return FALSE; ++ } ++ $operator = (count($break->value) > 1) ? 'IN' : '='; ++ $tids = $break->value; + } +- +- $operator = (count($break->value) > 1) ? 'IN' : '='; +- $tids = $break->value; + } + else { +- $operator = "="; ++ $operator = "IN"; + $tids = $this->argument; + } + +- // Now build the subqueries. +- if (is_string($tids)) { +- if ($this->moduleHandler->moduleExists('pathauto')) { +- $query = $this->database->select('taxonomy_term_field_data', 't') +- ->fields('t', ['tid', 'name']); +- +- // Filter by vocabulary ID if one or more are provided. +- if (!empty($this->options['vocabularies'])) { +- $query->condition('t.vid', $this->options['vocabularies'], 'IN'); +- } +- +- $results = $query->execute()->fetchAll(\PDO::FETCH_OBJ); +- +- // Iterate results. +- foreach ($results as $row) { +- if ($this->pathautoAliasCleaner->cleanString($row->name) == $this->pathautoAliasCleaner->cleanString($tids)) { +- $tids = $row->tid; +- } +- } +- } +- else { +- // Replaces "-" with space if exist. +- $argument = str_replace('-', ' ', $tids); +- $query = $this->database->select('taxonomy_term_field_data', 't') +- ->fields('t', ['tid', 'name']); +- +- // Filter by vocabulary ID if one or more are provided. +- if (!empty($this->options['vocabularies'])) { +- $query->condition('t.vid', $this->options['vocabularies'], 'IN'); +- } +- +- $query->condition('t.name', $argument, '='); +- +- $results = $query->execute()->fetchAll(\PDO::FETCH_OBJ); +- +- // Iterate results. +- foreach ($results as $row) { +- $tids = $row->tid; +- } +- } ++ $tids = $this->getTidsFromNames(is_string($tids) ? [$tids] : $tids); ++ if (empty($tids)) { ++ return FALSE; + } + + // Now build the subqueries. + $subquery = $this->database->select('taxonomy_index', 'tn'); + $subquery->addField('tn', 'nid'); ++ $subquery->addField('tn', 'nid'); + $where = (new Condition('OR'))->condition('tn.tid', $tids, $operator); + $last = "tn"; + +@@ -240,7 +205,50 @@ class IndexNameDepth extends ArgumentPluginBase { + } + + $subquery->condition($where); +- $this->query->addWhere(0, "$this->tableAlias.$this->realField", $subquery, 'IN'); ++ $ids = array_keys($subquery->execute()->fetchAllKeyed()); ++ if (empty($ids)) { ++ return $this->query->addWhere(0, "$this->tableAlias.$this->realField", [-1], 'IN'); ++ } ++ return $this->query->addWhere(0, "$this->tableAlias.$this->realField", $ids, 'IN'); ++ } ++ ++ /** ++ * Get the taxonomy term ids from the names. ++ * ++ * @param array $names ++ * Array of taxonomy names. ++ * ++ * @return array ++ * Array of entity ids. ++ */ ++ protected function getTidsFromNames(array $names) { ++ ++ $query = $this->termStorage->getQuery(); ++ // Filter by vocabulary ID if one or more are provided. ++ if (!empty($this->options['vocabularies'])) { ++ $query->condition('vid', $this->options['vocabularies'], 'IN'); ++ } ++ ++ $alias_cleaner = NULL; ++ if (\Drupal::service('module_handler')->moduleExists('pathauto')) { ++ // Service container for alias cleaner. ++ $alias_cleaner = \Drupal::service('pathauto.alias_cleaner'); ++ } ++ ++ $tids = []; ++ foreach ($this->termStorage->loadMultiple($query->execute()) as $term) { ++ foreach ($names as $name) { ++ if ( ++ ($alias_cleaner && $alias_cleaner->cleanString($term->label()) == $alias_cleaner->cleanString($name)) || ++ (!$alias_cleaner && $term->label() == str_replace('-', ' ', $name)) ++ ) { ++ $tids[] = $term->id(); ++ break; ++ } ++ } ++ } ++ ++ return $tids; + } + + /** +-- +GitLab + + +From f4cd1fe42cfd31e4df94de1441ad9c027e724364 Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Fri, 8 Sep 2023 16:42:57 -0700 +Subject: [PATCH 2/2] add required access check for D10 + +--- + src/Plugin/views/argument/IndexNameDepth.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Plugin/views/argument/IndexNameDepth.php b/src/Plugin/views/argument/IndexNameDepth.php +index 580b56c..6b80be4 100644 +--- a/src/Plugin/views/argument/IndexNameDepth.php ++++ b/src/Plugin/views/argument/IndexNameDepth.php +@@ -223,7 +223,7 @@ class IndexNameDepth extends ArgumentPluginBase { + */ + protected function getTidsFromNames(array $names) { + +- $query = $this->termStorage->getQuery(); ++ $query = $this->termStorage->getQuery()->accessCheck(); + // Filter by vocabulary ID if one or more are provided. + if (!empty($this->options['vocabularies'])) { + $query->condition('vid', $this->options['vocabularies'], 'IN'); +-- +GitLab + diff --git a/patches/contrib/webp-mr-33.patch b/patches/contrib/webp-mr-33.patch new file mode 100644 index 000000000..246bf2f81 --- /dev/null +++ b/patches/contrib/webp-mr-33.patch @@ -0,0 +1,239 @@ +From fa093cd2c76fe352f43f1c49e8386244099e1513 Mon Sep 17 00:00:00 2001 +From: Edouard Cunibil +Date: Thu, 23 Feb 2023 12:38:45 +0100 +Subject: [PATCH 1/6] Issue #3281606: Rendering duplicate images when the + original images have the same name but different extension + +--- + src/Webp.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Webp.php b/src/Webp.php +index bbd6de2..410aea4 100644 +--- a/src/Webp.php ++++ b/src/Webp.php +@@ -172,7 +172,7 @@ class Webp { + * Webp version of srcset + */ + public function getWebpSrcset($srcset) { +- return preg_replace('/\.(png|jpg|jpeg)(\\?.*?)?(,| |$)/i', '.webp\\2\\3', $srcset); ++ return preg_replace('/\.(png|jpg|jpeg)(\\?.*?)?(,| |$)/i', '.\\1.webp\\2\\3', $srcset); + } + + /** +-- +GitLab + + +From 6a3c8023a4aaba3390d9a64c6799af738c920e35 Mon Sep 17 00:00:00 2001 +From: Edouard Cunibil +Date: Thu, 23 Feb 2023 12:40:08 +0100 +Subject: [PATCH 2/6] Add old extension when generating webp derivatives. + +--- + .../ImageStyleDownloadController.php | 22 ++----------------- + src/Webp.php | 4 ++-- + 2 files changed, 4 insertions(+), 22 deletions(-) + +diff --git a/src/Controller/ImageStyleDownloadController.php b/src/Controller/ImageStyleDownloadController.php +index 2816fef..c0933c6 100644 +--- a/src/Controller/ImageStyleDownloadController.php ++++ b/src/Controller/ImageStyleDownloadController.php +@@ -107,26 +107,8 @@ class ImageStyleDownloadController extends FileDownloadController { + $image_uri = $scheme . '://' . $target; + + if ($webp_wanted = preg_match('/\.webp$/', $image_uri)) { +- $destination = $this->webp->getWebpDestination($image_uri, '@directory@filename'); +- $possible_image_uris = [$destination]; +- +- // Try out the different possible sources for a webp image. +- $extensions = [ +- '.jpg', +- '.jpeg', +- '.png', +- ]; +- foreach ($extensions as $extension) { +- $possible_image_uris[] = str_replace('.webp', mb_strtoupper($extension), $image_uri); +- $possible_image_uris[] = str_replace('.webp', $extension, $image_uri); +- } +- +- foreach ($possible_image_uris as $possible_image_uri) { +- if (file_exists($possible_image_uri)) { +- $image_uri = $possible_image_uri; +- break; +- } +- } ++ // Drop the webp extension. ++ $image_uri = $this->webp->getWebpDestination($image_uri, '@directory@filename'); + } + + // Don't try to generate file if source is missing. +diff --git a/src/Webp.php b/src/Webp.php +index 410aea4..d814891 100644 +--- a/src/Webp.php ++++ b/src/Webp.php +@@ -114,7 +114,7 @@ class Webp { + // If we can generate a GD resource from the source image, generate the URI + // of the WebP copy and try to create it. + if ($sourceImage !== NULL) { +- $destination = $this->getWebpDestination($uri, '@directory@filename.webp'); ++ $destination = $this->getWebpDestination($uri, '@directory@filename.@extension.webp'); + + imagesavealpha($sourceImage, TRUE); + imagealphablending($sourceImage, TRUE); +@@ -195,7 +195,7 @@ class Webp { + // We'll convert the image into webp. + $ImageMagickImg->apply('convert', ['extension' => 'webp', 'quality' => $quality]); + +- $destination = $this->getWebpDestination($uri, '@directory@filename.webp'); ++ $destination = $this->getWebpDestination($uri, '@directory@filename.@extension.webp'); + if ($ImageMagickImg->save($destination)) { + $webp = $destination; + +-- +GitLab + + +From c7ca326e8ee86b12a3e7a06d2c14f918330e1511 Mon Sep 17 00:00:00 2001 +From: Edouard Cunibil +Date: Thu, 23 Feb 2023 12:42:39 +0100 +Subject: [PATCH 3/6] Fix tests. + +--- + tests/src/Unit/WebpTest.php | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +diff --git a/tests/src/Unit/WebpTest.php b/tests/src/Unit/WebpTest.php +index db4a31f..7367e53 100644 +--- a/tests/src/Unit/WebpTest.php ++++ b/tests/src/Unit/WebpTest.php +@@ -28,27 +28,27 @@ class WebpTest extends UnitTestCase { + * @covers Drupal\webp\Webp::getWebpSrcset + */ + public function testgetWebpSrcset() { +- $this->assertEquals("testimage.webp", $this->webp->getWebpSrcset("testimage.jpg")); +- $this->assertEquals("testimage2.webp", $this->webp->getWebpSrcset("testimage2.png")); +- $this->assertEquals("testimage2.webp", $this->webp->getWebpSrcset("testimage2.jpeg")); +- $this->assertEquals("testimage2.webp", $this->webp->getWebpSrcset("testimage2.jpg")); +- $this->assertEquals("testimage2.ext.webp", $this->webp->getWebpSrcset("testimage2.ext.jpg")); +- $this->assertEquals("testimage2.ext.ext.webp", $this->webp->getWebpSrcset("testimage2.ext.ext.jpg")); ++ $this->assertEquals("testimage.jpg.webp", $this->webp->getWebpSrcset("testimage.jpg")); ++ $this->assertEquals("testimage2.png.webp", $this->webp->getWebpSrcset("testimage2.png")); ++ $this->assertEquals("testimage2.jpeg.webp", $this->webp->getWebpSrcset("testimage2.jpeg")); ++ $this->assertEquals("testimage2.jpg.webp", $this->webp->getWebpSrcset("testimage2.jpg")); ++ $this->assertEquals("testimage2.ext.jpg.webp", $this->webp->getWebpSrcset("testimage2.ext.jpg")); ++ $this->assertEquals("testimage2.ext.ext.jpg.webp", $this->webp->getWebpSrcset("testimage2.ext.ext.jpg")); + + // Test that double extensions are handled properly. +- $this->assertEquals("testimage2.png.webp", $this->webp->getWebpSrcset("testimage2.png.jpg")); +- $this->assertEquals("testimage2.jpeg.png.webp", $this->webp->getWebpSrcset("testimage2.jpeg.png.jpg")); ++ $this->assertEquals("testimage2.png.jpg.webp", $this->webp->getWebpSrcset("testimage2.png.jpg")); ++ $this->assertEquals("testimage2.jpeg.png.jpg.webp", $this->webp->getWebpSrcset("testimage2.jpeg.png.jpg")); + + // Test source sets with width descriptor/pixel density and multiple images. +- $this->assertEquals("some/path/image.webp?itok=vOpRgtYZ 1x", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ 1x")); +- $this->assertEquals("some/path/image.webp?itok=vOpRgtYZ 1x, some/path/image.webp?itok=vOpRgtYZ 2x", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ 1x, some/path/image.JPG?itok=vOpRgtYZ 2x")); ++ $this->assertEquals("some/path/image.JPG.webp?itok=vOpRgtYZ 1x", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ 1x")); ++ $this->assertEquals("some/path/image.JPG.webp?itok=vOpRgtYZ 1x, some/path/image.JPG.webp?itok=vOpRgtYZ 2x", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ 1x, some/path/image.JPG?itok=vOpRgtYZ 2x")); + + // Test source sets with multiple images but without width descriptor/pixel density. +- $this->assertEquals("some/path/image.webp?itok=vOpRgtYZ, some/path/image.webp?itok=vOpRgtYZ", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ, some/path/image.JPG?itok=vOpRgtYZ")); ++ $this->assertEquals("some/path/image.JPG.webp?itok=vOpRgtYZ, some/path/image.JPG.webp?itok=vOpRgtYZ", $this->webp->getWebpSrcset("some/path/image.JPG?itok=vOpRgtYZ, some/path/image.JPG?itok=vOpRgtYZ")); + + // And multiple source sets with multiple images. +- $this->assertEquals("some/path/image.png.webp?itok=vOpRgtYZ 1x, some/path/image.jpg.ext.webp?itok=vOpRgtYZ 2x", $this->webp->getWebpSrcset("some/path/image.png.JPG?itok=vOpRgtYZ 1x, some/path/image.jpg.ext.JPG?itok=vOpRgtYZ 2x")); +- $this->assertEquals("some/path/image.png.webp?itok=vOpRgtYZ, some/path/image.jpg.ext.webp?itok=vOpRgtYZ", $this->webp->getWebpSrcset("some/path/image.png.JPG?itok=vOpRgtYZ, some/path/image.jpg.ext.JPG?itok=vOpRgtYZ")); ++ $this->assertEquals("some/path/image.png.JPG.webp?itok=vOpRgtYZ 1x, some/path/image.jpg.ext.JPG.webp?itok=vOpRgtYZ 2x", $this->webp->getWebpSrcset("some/path/image.png.JPG?itok=vOpRgtYZ 1x, some/path/image.jpg.ext.JPG?itok=vOpRgtYZ 2x")); ++ $this->assertEquals("some/path/image.png.JPG.webp?itok=vOpRgtYZ, some/path/image.jpg.ext.JPG.webp?itok=vOpRgtYZ", $this->webp->getWebpSrcset("some/path/image.png.JPG?itok=vOpRgtYZ, some/path/image.jpg.ext.JPG?itok=vOpRgtYZ")); + } + + } +-- +GitLab + + +From cf58c1903dc4d8dfbea2c45fc17b4c540bd92886 Mon Sep 17 00:00:00 2001 +From: Xavier Masson +Date: Fri, 22 Dec 2023 12:54:12 +0100 +Subject: [PATCH 4/6] Drop the webp extension only if derivate file doesn't + exist + +--- + src/Controller/ImageStyleDownloadController.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Controller/ImageStyleDownloadController.php b/src/Controller/ImageStyleDownloadController.php +index c0933c6..3933661 100644 +--- a/src/Controller/ImageStyleDownloadController.php ++++ b/src/Controller/ImageStyleDownloadController.php +@@ -106,7 +106,7 @@ class ImageStyleDownloadController extends FileDownloadController { + $target = $request->query->get('file'); + $image_uri = $scheme . '://' . $target; + +- if ($webp_wanted = preg_match('/\.webp$/', $image_uri)) { ++ if (!file_exists($image_uri) && ($webp_wanted = preg_match('/\.webp$/', $image_uri))) { + // Drop the webp extension. + $image_uri = $this->webp->getWebpDestination($image_uri, '@directory@filename'); + } +-- +GitLab + + +From d1369f9a279f2c362b5a3a3252b156e54f3414ae Mon Sep 17 00:00:00 2001 +From: Mike Decker +Date: Tue, 23 Apr 2024 12:51:47 -0700 +Subject: [PATCH 5/6] Remove webp extension to match the source file + +--- + src/Controller/ImageStyleDownloadController.php | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/Controller/ImageStyleDownloadController.php b/src/Controller/ImageStyleDownloadController.php +index 3933661..e594d19 100644 +--- a/src/Controller/ImageStyleDownloadController.php ++++ b/src/Controller/ImageStyleDownloadController.php +@@ -105,11 +105,12 @@ class ImageStyleDownloadController extends FileDownloadController { + public function deliver(Request $request, $scheme, ImageStyleInterface $image_style) { + $target = $request->query->get('file'); + $image_uri = $scheme . '://' . $target; +- +- if (!file_exists($image_uri) && ($webp_wanted = preg_match('/\.webp$/', $image_uri))) { ++ $webp_wanted = preg_match('/\.webp$/', $image_uri); ++ if (!file_exists($image_uri) && $webp_wanted) { + // Drop the webp extension. + $image_uri = $this->webp->getWebpDestination($image_uri, '@directory@filename'); + } ++ $image_uri = preg_replace('/\.webp$/', '', $image_uri); + + // Don't try to generate file if source is missing. + if (!file_exists($image_uri)) { +-- +GitLab + + +From 48064e9c28358dfff0228ca532b8c32e91d86649 Mon Sep 17 00:00:00 2001 +From: Travis Neilans <46648-porchlight@users.noreply.drupalcode.org> +Date: Tue, 28 May 2024 19:19:03 +0000 +Subject: [PATCH 6/6] Dont replace .webp extension if thats the original source + extension. + +--- + src/Controller/ImageStyleDownloadController.php | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/Controller/ImageStyleDownloadController.php b/src/Controller/ImageStyleDownloadController.php +index e594d19..6c14309 100644 +--- a/src/Controller/ImageStyleDownloadController.php ++++ b/src/Controller/ImageStyleDownloadController.php +@@ -110,7 +110,7 @@ class ImageStyleDownloadController extends FileDownloadController { + // Drop the webp extension. + $image_uri = $this->webp->getWebpDestination($image_uri, '@directory@filename'); + } +- $image_uri = preg_replace('/\.webp$/', '', $image_uri); ++ $image_uri = preg_replace('/\.[^.]*\.webp$/', '.webp', $image_uri); + + // Don't try to generate file if source is missing. + if (!file_exists($image_uri)) { +-- +GitLab +