diff --git a/config/sync/core.entity_view_display.media.image.redia_feed_large.yml b/config/sync/core.entity_view_display.media.image.redia_feed_large.yml new file mode 100644 index 000000000..6b9f0a460 --- /dev/null +++ b/config/sync/core.entity_view_display.media.image.redia_feed_large.yml @@ -0,0 +1,36 @@ +uuid: 014a5ed5-1c85-432f-a1b8-3814869178fd +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.redia_feed_large + - field.field.media.image.field_byline + - field.field.media.image.field_media_image + - image.style.redia_feed_large + - media.type.image + module: + - image +id: media.image.redia_feed_large +targetEntityType: media +bundle: image +mode: redia_feed_large +content: + field_media_image: + type: image + label: hidden + settings: + image_link: '' + image_style: redia_feed_large + image_loading: + attribute: lazy + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + field_byline: true + langcode: true + name: true + search_api_excerpt: true + thumbnail: true + uid: true diff --git a/config/sync/core.entity_view_display.media.image.redia_feed_small.yml b/config/sync/core.entity_view_display.media.image.redia_feed_small.yml new file mode 100644 index 000000000..a3545c59f --- /dev/null +++ b/config/sync/core.entity_view_display.media.image.redia_feed_small.yml @@ -0,0 +1,36 @@ +uuid: 7cd49745-6747-4b76-b64a-f3e2a22a338e +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.media.redia_feed_small + - field.field.media.image.field_byline + - field.field.media.image.field_media_image + - image.style.redia_feed_small + - media.type.image + module: + - image +id: media.image.redia_feed_small +targetEntityType: media +bundle: image +mode: redia_feed_small +content: + field_media_image: + type: image + label: hidden + settings: + image_link: '' + image_style: redia_feed_small + image_loading: + attribute: lazy + third_party_settings: { } + weight: 0 + region: content +hidden: + created: true + field_byline: true + langcode: true + name: true + search_api_excerpt: true + thumbnail: true + uid: true diff --git a/config/sync/core.entity_view_mode.media.redia_feed_large.yml b/config/sync/core.entity_view_mode.media.redia_feed_large.yml new file mode 100644 index 000000000..a89901597 --- /dev/null +++ b/config/sync/core.entity_view_mode.media.redia_feed_large.yml @@ -0,0 +1,11 @@ +uuid: 74eba980-7ec0-4b81-a6da-5c33830b08d9 +langcode: en +status: true +dependencies: + module: + - media +id: media.redia_feed_large +label: 'Redia feed - large' +description: '' +targetEntityType: media +cache: true diff --git a/config/sync/core.entity_view_mode.media.redia_feed_small.yml b/config/sync/core.entity_view_mode.media.redia_feed_small.yml new file mode 100644 index 000000000..ab7001994 --- /dev/null +++ b/config/sync/core.entity_view_mode.media.redia_feed_small.yml @@ -0,0 +1,11 @@ +uuid: fd0b39a0-f244-4d82-af84-54c63cf451bc +langcode: en +status: true +dependencies: + module: + - media +id: media.redia_feed_small +label: 'Redia feed - small' +description: '' +targetEntityType: media +cache: true diff --git a/config/sync/core.extension.yml b/config/sync/core.extension.yml index 06913fdc6..90296dd02 100644 --- a/config/sync/core.extension.yml +++ b/config/sync/core.extension.yml @@ -66,6 +66,7 @@ module: dpl_react: 0 dpl_react_apps: 0 dpl_recommender: 0 + dpl_redia_legacy: 0 dpl_related_content: 0 dpl_reservations: 0 dpl_rest_base: 0 diff --git a/config/sync/image.style.redia_feed_large.yml b/config/sync/image.style.redia_feed_large.yml new file mode 100644 index 000000000..617326ab9 --- /dev/null +++ b/config/sync/image.style.redia_feed_large.yml @@ -0,0 +1,15 @@ +uuid: 923e70e6-ea34-446d-ae06-2f6ac0ac4f00 +langcode: en +status: true +dependencies: { } +name: redia_feed_large +label: 'Redia feed large' +effects: + 71f3eb2a-c6f8-4a85-a16f-651a6e43239e: + uuid: 71f3eb2a-c6f8-4a85-a16f-651a6e43239e + id: image_scale + weight: 1 + data: + width: 2000 + height: null + upscale: false diff --git a/config/sync/image.style.redia_feed_small.yml b/config/sync/image.style.redia_feed_small.yml new file mode 100644 index 000000000..bfd8dbefc --- /dev/null +++ b/config/sync/image.style.redia_feed_small.yml @@ -0,0 +1,15 @@ +uuid: 33f9623f-9eea-4b05-8e9c-f1d3ac224119 +langcode: en +status: true +dependencies: { } +name: redia_feed_small +label: 'Redia feed small' +effects: + 2cd37185-a9d4-43e1-ba81-66b73d18cae2: + uuid: 2cd37185-a9d4-43e1-ba81-66b73d18cae2 + id: image_scale + weight: 1 + data: + width: 220 + height: null + upscale: true diff --git a/web/modules/custom/dpl_event/src/EventWrapper.php b/web/modules/custom/dpl_event/src/EventWrapper.php index 7ecdba38d..2e5d0af0c 100644 --- a/web/modules/custom/dpl_event/src/EventWrapper.php +++ b/web/modules/custom/dpl_event/src/EventWrapper.php @@ -86,6 +86,22 @@ private function getDate(string $value): \DateTimeInterface { return new DateTimeImmutable($event_date_values[$value], new \DateTimeZone('UTC')); } + /** + * Getting an events branches. + * + * @return array|null + * The matching branches. + */ + public function getBranches(): ?array { + $field = $this->getField('branch'); + + if (!$field instanceof FieldItemListInterface) { + return NULL; + } + + return $field->referencedEntities() ?? NULL; + } + /** * Load an eventinstance address - either from the series/instance or branch. */ @@ -96,15 +112,10 @@ public function getAddressField(): ?FieldItemListInterface { return $instance_field; } - // Could not find data - look up address from branch instead. - $branch_field = $this->getField('branch'); - - if (!$branch_field instanceof FieldItemListInterface) { - return NULL; - } + // Could not find data - look up address from first branch instead. + $branch = $this->getBranches()[0] ?? NULL; $branch_address_field = 'field_address'; - $branch = $branch_field->referencedEntities()[0] ?? NULL; if (!($branch instanceof NodeInterface) || !$branch->hasField($branch_address_field)) { return NULL; @@ -113,6 +124,22 @@ public function getAddressField(): ?FieldItemListInterface { return $branch->get($branch_address_field); } + /** + * Getting the description, from the first available text paragraph. + */ + public function getDescription(): ?string { + /** @var \Drupal\paragraphs\ParagraphInterface[] $paragraphs */ + $paragraphs = $this->event->get('event_paragraphs')->referencedEntities(); + + foreach ($paragraphs as $paragraph) { + if ($paragraph->bundle() === 'text_body') { + return $paragraph->get('field_body')->getValue()[0]['value'] ?? NULL; + } + } + + return NULL; + } + /** * Get the EventState object of an eventinstance. */ diff --git a/web/modules/custom/dpl_event/src/Services/EventRestMapper.php b/web/modules/custom/dpl_event/src/Services/EventRestMapper.php index 8249af5f5..a29d626a0 100644 --- a/web/modules/custom/dpl_event/src/Services/EventRestMapper.php +++ b/web/modules/custom/dpl_event/src/Services/EventRestMapper.php @@ -57,7 +57,7 @@ public function getResponse(EventInstance $event_instance): EventsGET200Response 'url' => $this->event->toUrl()->setAbsolute(TRUE)->toString(TRUE)->getGeneratedUrl(), 'ticketManagerRelevance' => !empty($this->getSeriesValue('field_relevant_ticket_manager')), 'description' => $this->getValue('event_description'), - 'body' => $this->getDescription(), + 'body' => $this->eventWrapper->getDescription(), 'state' => $this->eventWrapper->getState()?->value, 'image' => $this->getImage(), 'branches' => $this->getBranches(), @@ -84,8 +84,7 @@ public function getResponse(EventInstance $event_instance): EventsGET200Response private function getBranches(): array { $names = []; - /** @var \Drupal\node\NodeInterface[] $branches */ - $branches = $this->event->get('branch')->referencedEntities(); + $branches = $this->eventWrapper->getBranches() ?? []; foreach ($branches as $branch) { $label = $branch->getTitle(); @@ -117,22 +116,6 @@ private function getTags(): array { return $names; } - /** - * Getting the description, from the first available text paragraph. - */ - private function getDescription(): ?string { - /** @var \Drupal\paragraphs\ParagraphInterface[] $paragraphs */ - $paragraphs = $this->event->get('event_paragraphs')->referencedEntities(); - - foreach ($paragraphs as $paragraph) { - if ($paragraph->bundle() === 'text_body') { - return $paragraph->get('field_body')->getValue()[0]['value'] ?? NULL; - } - } - - return NULL; - } - /** * Getting the external data, supplied by third party PATCH. */ diff --git a/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.info.yml b/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.info.yml new file mode 100644 index 000000000..18cc38836 --- /dev/null +++ b/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.info.yml @@ -0,0 +1,9 @@ +name: "DPL Redia Legacy feeds" +type: module +description: "Various legacy integrations, used for the Redia App." +package: DPL +core_version_requirement: ^10 + +dependencies: + - dpl_opening_hours:dpl_opening_hours + - drupal:drupal_typed diff --git a/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.routing.yml b/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.routing.yml new file mode 100644 index 000000000..efb867c3d --- /dev/null +++ b/web/modules/custom/dpl_redia_legacy/dpl_redia_legacy.routing.yml @@ -0,0 +1,9 @@ +dpl_redia_legacy.events: + path: "/ding-redia-rss/event" + defaults: + _controller: '\Drupal\dpl_redia_legacy\Controller\RssFeeds\EventsController::getFeed' + _title: "Redia APP Event" + requirements: + _permission: "access content" + options: + _format: "xml" diff --git a/web/modules/custom/dpl_redia_legacy/src/Controller/RssFeeds/EventsController.php b/web/modules/custom/dpl_redia_legacy/src/Controller/RssFeeds/EventsController.php new file mode 100644 index 000000000..0a0516549 --- /dev/null +++ b/web/modules/custom/dpl_redia_legacy/src/Controller/RssFeeds/EventsController.php @@ -0,0 +1,151 @@ +get('file_url_generator'), + $container->get('date.formatter'), + ); + } + + /** + * Getting the RSS/XML feed of the items. + */ + public function getFeed(Request $request): CacheableResponse { + $items = $this->getItems(); + + $rss_content = $this->buildRss($items, $request); + + $response = new CacheableResponse(); + $response->setContent($rss_content); + + // Create cache metadata. + $cache_metadata = new CacheableMetadata(); + $cache_metadata->setCacheTags(['eventinstance_list', 'eventseries_list']); + + // Add cache metadata to the response. + $response->addCacheableDependency($cache_metadata); + + $response->headers->set('Content-Type', 'application/rss+xml'); + return $response; + } + + /** + * Loading events, and turning it into a simple array of relevant values. + * + * @return \Drupal\dpl_redia_legacy\RediaEvent[] + * An array of necessary item fields, used in buildRss(). + */ + private function getItems(): array { + + $storage = $this->entityTypeManager()->getStorage('eventinstance'); + $query = $storage->getQuery() + ->condition('status', TRUE) + ->condition('date.value', strtotime('today'), '>=') + ->accessCheck(TRUE) + ->sort('date.value'); + $ids = $query->execute(); + + /** @var \Drupal\recurring_events\Entity\EventInstance[] $events */ + $events = $storage->loadMultiple($ids); + + $items = []; + + foreach ($events as $event) { + $items[] = new RediaEvent($event); + } + + return $items; + } + + /** + * Building the actual RSS feed, from the items and site information. + * + * @param \Drupal\dpl_redia_legacy\RediaEvent[] $items + * See $this->getItems();. + * @param \Symfony\Component\HttpFoundation\Request $request + * The request, for looking up the current site info. + */ + private function buildRss(array $items, Request $request): string { + $config = $this->config('system.site'); + $site_title = $config->get('name'); + $site_url = $request->getSchemeAndHttpHost(); + $feed_url = Url::fromRoute('dpl_redia_legacy.events'); + $feed_url->setAbsolute(); + $feed_url = $feed_url->toString(); + + $date = $this->dateFormatter->format(time(), 'custom', 'r'); + + $rss_feed = << + + + $site_title + $site_url + + da + $date + $date +RSS; + + foreach ($items as $item) { + $rss_feed .= << + {$item->title} + {$item->description} + {$item->author} + {$item->id} + {$item->date} + $site_title + + {$item->media?->md5} + + + {$item->subtitle} + {$item->startTime} + {$item->endTime} + {$item->branch?->label()} + {$item->branch?->id()} + {$item->promoted} + +ITEM; + } + + $rss_feed .= << + +RSS; + + return $rss_feed; + + } + +} diff --git a/web/modules/custom/dpl_opening_hours/src/Plugin/rest/resource/v1/OpeningHoursLegacyResource.php b/web/modules/custom/dpl_redia_legacy/src/Plugin/rest/resource/OpeningHoursResource.php similarity index 97% rename from web/modules/custom/dpl_opening_hours/src/Plugin/rest/resource/v1/OpeningHoursLegacyResource.php rename to web/modules/custom/dpl_redia_legacy/src/Plugin/rest/resource/OpeningHoursResource.php index a402e6bfe..0174c8e48 100644 --- a/web/modules/custom/dpl_opening_hours/src/Plugin/rest/resource/v1/OpeningHoursLegacyResource.php +++ b/web/modules/custom/dpl_redia_legacy/src/Plugin/rest/resource/OpeningHoursResource.php @@ -2,12 +2,13 @@ declare(strict_types=1); -namespace Drupal\dpl_opening_hours\Plugin\rest\resource\v1; +namespace Drupal\dpl_redia_legacy\Plugin\rest\resource; use DanskernesDigitaleBibliotek\CMS\Api\Model\DplOpeningHoursLegacyListGET200ResponseInner as OpeningHoursLegacyResponse; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\CacheableResponse; use Drupal\dpl_opening_hours\Model\OpeningHoursInstance; +use Drupal\dpl_opening_hours\Plugin\rest\resource\v1\OpeningHoursResourceBase; use Drupal\drupal_typed\RequestTyped; use JMS\Serializer\ContextFactory\DefaultSerializationContextFactory; use Safe\DateTime; @@ -35,7 +36,7 @@ * }, * ) */ -final class OpeningHoursLegacyResource extends OpeningHoursResourceBase { +final class OpeningHoursResource extends OpeningHoursResourceBase { /** * {@inheritdoc} diff --git a/web/modules/custom/dpl_redia_legacy/src/RediaEvent.php b/web/modules/custom/dpl_redia_legacy/src/RediaEvent.php new file mode 100644 index 000000000..f3cf51550 --- /dev/null +++ b/web/modules/custom/dpl_redia_legacy/src/RediaEvent.php @@ -0,0 +1,80 @@ +getBranches()[0] ?? NULL; + $start_date = $event_wrapper->getStartDate(); + $end_date = $event_wrapper->getEndDate(); + + $changed_date = DrupalDateTime::createFromFormat('U', strval($event_instance->getChangedTime())); + + $media = NULL; + $media_field = $event_instance->get('event_image'); + + if (($media_field instanceof FieldItemListInterface)) { + $media = $media_field->referencedEntities()[0] ?? NULL; + } + + $this->title = $event_instance->label(); + $this->description = $event_wrapper->getDescription(); + $this->author = $event_instance->getOwner()->get('field_author_name')->getString(); + $this->id = $event_instance->id(); + $this->date = $changed_date->format('r'); + $this->subtitle = $event_wrapper->getField('event_description')?->getString(); + $this->startTime = $start_date->format('U'); + $this->endTime = $end_date->format('U'); + $this->media = NULL; + $this->mediaThumbnail = NULL; + + if ($media) { + $this->media = new RediaEventMedia($media, 'redia_feed_large'); + $this->mediaThumbnail = new RediaEventMedia($media, 'redia_feed_small'); + } + + $this->branch = $branch; + + // In the old system, there was a way for editors to mark content a + // promoted. However, this does not exist in the new CMS, so we wil + // just hardcode it. + $this->promoted = 'Falsk'; + } + +} diff --git a/web/modules/custom/dpl_redia_legacy/src/RediaEventMedia.php b/web/modules/custom/dpl_redia_legacy/src/RediaEventMedia.php new file mode 100644 index 000000000..e7a738800 --- /dev/null +++ b/web/modules/custom/dpl_redia_legacy/src/RediaEventMedia.php @@ -0,0 +1,66 @@ +hasField($file_field_name)) { + return; + } + + // @phpstan-ignore-next-line PHPStan does not know that entity is available. + $file = $media->get($file_field_name)->first()?->entity; + + if (!($file instanceof FileInterface)) { + return; + } + + $file_uri = $file->getFileUri(); + $style = $this->entityTypeManager()->getStorage('image_style')->load($image_style); + + if (empty($file_uri) || !($style instanceof ImageStyleInterface)) { + return; + } + + $image_url = $style->buildUrl($file_uri); + $image_sizes = getimagesize($file_uri); + $file_size = filesize($file_uri); + + $image_type = $image_sizes[2] ?? NULL; + + if ($image_type) { + $this->type = image_type_to_mime_type($image_type); + } + + $this->url = $image_url; + $this->size = filesize($file_uri); + $this->width = $image_sizes[0] ?? NULL; + $this->height = $image_sizes[1] ?? NULL; + $this->md5 = md5($image_url . $file_size); + } + +} diff --git a/web/modules/custom/dpl_update/dpl_update.install b/web/modules/custom/dpl_update/dpl_update.install index 63f116fe1..ce58aceb4 100644 --- a/web/modules/custom/dpl_update/dpl_update.install +++ b/web/modules/custom/dpl_update/dpl_update.install @@ -82,6 +82,8 @@ function dpl_update_install(): string { $messages[] = dpl_update_update_10015(); $messages[] = dpl_update_update_10016(); $messages[] = dpl_update_update_10017(); + $messages[] = dpl_update_update_10018(); + $messages[] = dpl_update_update_10019(); return implode('\r\n', $messages); } @@ -209,6 +211,13 @@ function dpl_update_update_10017(): string { return _dpl_update_install_modules(['dpl_search']); } +/** + * Install dpl_redia_legacy module. + */ +function dpl_update_update_10018(): string { + return _dpl_update_install_modules(['dpl_redia_legacy']); +} + /** * Add initial translation for paragraph error messages. */