diff --git a/composer.json b/composer.json index 72f4d58d8..ef14003f8 100644 --- a/composer.json +++ b/composer.json @@ -51,7 +51,7 @@ "openeuropa/oe_media": "^1.14", "openeuropa/oe_multilingual": "^1.9", "openeuropa/oe_paragraphs": "^1.13", - "openeuropa/oe_starter_content": "^1.0.0-beta1", + "openeuropa/oe_starter_content": "1.x-dev", "openeuropa/task-runner-drupal-project-symlink": "^1.0-beta5", "phpspec/prophecy-phpunit": "^2", "symfony/dom-crawler": "^4.4.12" @@ -95,9 +95,6 @@ "patches": { "drupal/entity_reference_revisions": { "https://www.drupal.org/project/entity_reference_revisions/issues/2937835": "https://www.drupal.org/files/issues/2021-03-26/entity_reference_revisions-field_formatter_label-2937835-36.patch" - }, - "openeuropa/oe_starter_content": { - "latest": "https://github.com/openeuropa/oe_starter_content/compare/1.0.0-beta1..OEL-1662.diff" } }, "drupal-scaffold": { diff --git a/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.full.yml b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.full.yml new file mode 100644 index 000000000..ad4c52d88 --- /dev/null +++ b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.full.yml @@ -0,0 +1,56 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.full + - field.field.node.oe_sc_person.oe_sc_person_additional_info + - field.field.node.oe_sc_person.oe_sc_person_country + - field.field.node.oe_sc_person.oe_sc_person_documents + - field.field.node.oe_sc_person.oe_sc_person_first_name + - field.field.node.oe_sc_person.oe_sc_person_image + - field.field.node.oe_sc_person.oe_sc_person_last_name + - field.field.node.oe_sc_person.oe_sc_person_occupation + - field.field.node.oe_sc_person.oe_sc_person_position + - field.field.node.oe_sc_person.oe_social_media_links + - field.field.node.oe_sc_person.oe_summary + - node.type.oe_sc_person + module: + - text + - user +id: node.oe_sc_person.full +targetEntityType: node +bundle: oe_sc_person +mode: full +content: + oe_sc_person_additional_info: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 0 + region: content + oe_sc_person_documents: + type: entity_reference_entity_view + label: hidden + settings: + view_mode: default + link: true + third_party_settings: { } + weight: 1 + region: content +hidden: + langcode: true + links: true + oe_content_content_owner: true + oe_content_legacy_link: true + oe_content_navigation_title: true + oe_content_short_title: true + oe_sc_person_country: true + oe_sc_person_first_name: true + oe_sc_person_image: true + oe_sc_person_last_name: true + oe_sc_person_occupation: true + oe_sc_person_position: true + oe_social_media_links: true + oe_summary: true + search_api_excerpt: true diff --git a/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.oe_w_content_banner.yml b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.oe_w_content_banner.yml new file mode 100644 index 000000000..a2985a3f7 --- /dev/null +++ b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.oe_w_content_banner.yml @@ -0,0 +1,69 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.oe_w_content_banner + - field.field.node.oe_sc_person.oe_sc_person_additional_info + - field.field.node.oe_sc_person.oe_sc_person_country + - field.field.node.oe_sc_person.oe_sc_person_documents + - field.field.node.oe_sc_person.oe_sc_person_first_name + - field.field.node.oe_sc_person.oe_sc_person_image + - field.field.node.oe_sc_person.oe_sc_person_last_name + - field.field.node.oe_sc_person.oe_sc_person_occupation + - field.field.node.oe_sc_person.oe_sc_person_position + - field.field.node.oe_sc_person.oe_social_media_links + - field.field.node.oe_sc_person.oe_summary + - node.type.oe_sc_person + module: + - address + - text + - user +id: node.oe_sc_person.oe_w_content_banner +targetEntityType: node +bundle: oe_sc_person +mode: oe_w_content_banner +content: + oe_sc_person_country: + type: address_country_default + label: hidden + settings: { } + third_party_settings: { } + weight: 1 + region: content + oe_sc_person_occupation: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 2 + region: content + oe_sc_person_position: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 3 + region: content + oe_summary: + type: text_default + label: hidden + settings: { } + third_party_settings: { } + weight: 0 + region: content +hidden: + langcode: true + links: true + oe_content_content_owner: true + oe_content_legacy_link: true + oe_content_navigation_title: true + oe_content_short_title: true + oe_sc_person_additional_info: true + oe_sc_person_documents: true + oe_sc_person_first_name: true + oe_sc_person_image: true + oe_sc_person_last_name: true + oe_social_media_links: true + search_api_excerpt: true diff --git a/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.teaser.yml b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.teaser.yml new file mode 100644 index 000000000..fb96f44b8 --- /dev/null +++ b/modules/oe_whitelabel_starter_person/config/install/core.entity_view_display.node.oe_sc_person.teaser.yml @@ -0,0 +1,55 @@ +langcode: en +status: true +dependencies: + config: + - core.entity_view_mode.node.teaser + - field.field.node.oe_sc_person.oe_sc_person_additional_info + - field.field.node.oe_sc_person.oe_sc_person_country + - field.field.node.oe_sc_person.oe_sc_person_documents + - field.field.node.oe_sc_person.oe_sc_person_first_name + - field.field.node.oe_sc_person.oe_sc_person_image + - field.field.node.oe_sc_person.oe_sc_person_last_name + - field.field.node.oe_sc_person.oe_sc_person_occupation + - field.field.node.oe_sc_person.oe_sc_person_position + - field.field.node.oe_sc_person.oe_social_media_links + - field.field.node.oe_sc_person.oe_summary + - node.type.oe_sc_person + module: + - user +id: node.oe_sc_person.teaser +targetEntityType: node +bundle: oe_sc_person +mode: teaser +content: + oe_sc_person_occupation: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 0 + region: content + oe_sc_person_position: + type: string + label: hidden + settings: + link_to_entity: false + third_party_settings: { } + weight: 1 + region: content +hidden: + langcode: true + links: true + oe_content_content_owner: true + oe_content_legacy_link: true + oe_content_navigation_title: true + oe_content_short_title: true + oe_sc_person_additional_info: true + oe_sc_person_country: true + oe_sc_person_documents: true + oe_sc_person_first_name: true + oe_sc_person_image: true + oe_sc_person_last_name: true + oe_social_media_links: true + oe_summary: true + search_api_excerpt: true diff --git a/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.info.yml b/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.info.yml new file mode 100644 index 000000000..2a2bdff50 --- /dev/null +++ b/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.info.yml @@ -0,0 +1,14 @@ +name: OpenEuropa Whitelabel Starter Person +type: module +description: Companion module to OE Person providing styling to nodes. +package: OpenEuropa Whitelabel Theme +core_version_requirement: ^9.2 +dependencies: + - oe_whitelabel:oe_whitelabel_helper + - oe_starter_content:oe_starter_content_person + +config_devel: + install: + - core.entity_view_display.node.oe_sc_person.full + - core.entity_view_display.node.oe_sc_person.oe_w_content_banner + - core.entity_view_display.node.oe_sc_person.teaser diff --git a/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.module b/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.module new file mode 100644 index 000000000..2277ac9af --- /dev/null +++ b/modules/oe_whitelabel_starter_person/oe_whitelabel_starter_person.module @@ -0,0 +1,109 @@ +get('oe_sc_person_image')->isEmpty()) { + return; + } + + /** @var \Drupal\media\Entity\Media $media */ + $media = $node->get('oe_sc_person_image')->entity; + if (!$media instanceof MediaInterface) { + // The media entity is not available anymore, bail out. + return; + } + + // Retrieve the correct media translation. + /** @var \Drupal\media\Entity\Media $media */ + $media = \Drupal::service('entity.repository')->getTranslationFromContext($media, $node->language()->getId()); + $cacheability = CacheableMetadata::createFromRenderArray($variables); + $cacheability->addCacheableDependency($media); + + // Run access checks on the media entity. + $access = $media->access('view', $variables['user'], TRUE); + $cacheability->addCacheableDependency($access); + if (!$access->isAllowed()) { + $cacheability->applyTo($variables); + return; + } + + // Get the media source. + $source = $media->getSource(); + + $is_image = $source instanceof MediaAvPortalPhotoSource || $source instanceof Image; + + // If it's not an image bail out. + if (!$is_image) { + $cacheability->applyTo($variables); + return; + } + + $thumbnail = $media->get('thumbnail')->first(); + $variables['image'] = ImageValueObject::fromImageItem($thumbnail); + + $cacheability->applyTo($variables); +} + +/** + * Helper function to preprocess the social media links. + * + * @param array $variables + * Variables from the preprocess function. + */ +function _oe_whitelabel_starter_person_social_media_links(array &$variables): void { + /** @var \Drupal\node\NodeInterface $node */ + $node = $variables['node']; + + $social_links = $node->get('oe_social_media_links')->getValue(); + + foreach ($social_links as $link) { + $variables['social_links'][] = [ + 'icon_position' => 'before', + 'icon' => [ + 'path' => $variables['bcl_icon_path'], + 'name' => $link['link_type'], + ], + 'label' => $link['title'], + 'path' => Url::fromUri($link['uri'])->toString(), + ]; + } + +} diff --git a/runner.yml.dist b/runner.yml.dist index 1f2f14298..2a2e81658 100644 --- a/runner.yml.dist +++ b/runner.yml.dist @@ -24,8 +24,9 @@ drupal: - "./vendor/bin/drush en oe_whitelabel_helper -y" - "./vendor/bin/drush en oe_whitelabel_search -y" - "./vendor/bin/drush en oe_whitelabel_list_pages -y" - - "./vendor/bin/drush en oe_whitelabel_starter_news -y" - "./vendor/bin/drush en oe_whitelabel_starter_event -y" + - "./vendor/bin/drush en oe_whitelabel_starter_news -y" + - "./vendor/bin/drush en oe_whitelabel_starter_person -y" - "./vendor/bin/drush en oe_whitelabel_paragraphs -y" - "./vendor/bin/drush en toolbar -y" - "./vendor/bin/drush theme:enable oe_whitelabel -y" diff --git a/templates/content/field--node--oe-sc-person-additional-info.html.twig b/templates/content/field--node--oe-sc-person-additional-info.html.twig new file mode 100644 index 000000000..826064f72 --- /dev/null +++ b/templates/content/field--node--oe-sc-person-additional-info.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * Field template for person additional information. + * + * @see ./core/themes/stable/templates/field/field.html.twig + */ +#} +{% if items is not empty %} +

{{ 'Additional information'|t }}

+
+ {% for item in items %} + {{ item.content }} + {% endfor %} +
+{% endif %} + + diff --git a/templates/content/field--node--oe-sc-person-documents.html.twig b/templates/content/field--node--oe-sc-person-documents.html.twig new file mode 100644 index 000000000..d434ea8cc --- /dev/null +++ b/templates/content/field--node--oe-sc-person-documents.html.twig @@ -0,0 +1,18 @@ +{# +/** + * @file + * Field template for person documents. + * + * @see ./core/themes/stable/templates/field/field.html.twig + */ +#} +{% if items is not empty %} +

{{ 'Related documents'|t }}

+
+ {% for item in items %} + {{ item.content }} + {% endfor %} +
+{% endif %} + + diff --git a/templates/content/field--oe-document-reference--oe-document--oe-document.html.twig b/templates/content/field--oe-document-reference--oe-document--oe-document.html.twig new file mode 100644 index 000000000..70a8be5eb --- /dev/null +++ b/templates/content/field--oe-document-reference--oe-document--oe-document.html.twig @@ -0,0 +1,13 @@ +{# +/** + * @file + * Field template of the oe_document_reference for the oe_document document. + * + * @see ./core/themes/stable/templates/field/field.html.twig + */ +#} +{% for item in items %} + + {{ item.content }} + +{% endfor %} diff --git a/templates/content/field--oe-document-reference--oe-documents--oe-document-group.html.twig b/templates/content/field--oe-document-reference--oe-documents--oe-document-group.html.twig new file mode 100644 index 000000000..e896e61ac --- /dev/null +++ b/templates/content/field--oe-document-reference--oe-documents--oe-document-group.html.twig @@ -0,0 +1,13 @@ +{# +/** + * @file + * Field template of the oe_document_reference for the oe_document_group document. + * + * @see ./core/themes/stable/templates/field/field.html.twig + */ +#} +{% for item in items %} + + {{ item.content }} + +{% endfor %} diff --git a/templates/content/field--oe-document-reference--oe-title--oe-document-group.html.twig b/templates/content/field--oe-document-reference--oe-title--oe-document-group.html.twig new file mode 100644 index 000000000..ee65ac7d6 --- /dev/null +++ b/templates/content/field--oe-document-reference--oe-title--oe-document-group.html.twig @@ -0,0 +1,11 @@ +{# +/** + * @file + * Field template of the oe_document_reference for the oe_document_group title. + * + * @see ./core/themes/stable/templates/field/field.html.twig + */ +#} +{% for item in items %} +

{{ item.content }}

+{% endfor %} diff --git a/templates/content/node--oe-sc-person--full.html.twig b/templates/content/node--oe-sc-person--full.html.twig new file mode 100644 index 000000000..7bec8f4a9 --- /dev/null +++ b/templates/content/node--oe-sc-person--full.html.twig @@ -0,0 +1,14 @@ +{# +/** + * @file + * Person full display. + */ +#} + +
+
+ {{ content.oe_sc_person_additional_info }} + {{ content.oe_sc_person_documents }} +
+
+ diff --git a/templates/content/node--oe-sc-person--oe-w-content-banner.html.twig b/templates/content/node--oe-sc-person--oe-w-content-banner.html.twig new file mode 100644 index 000000000..a9b8ae8ad --- /dev/null +++ b/templates/content/node--oe-sc-person--oe-w-content-banner.html.twig @@ -0,0 +1,35 @@ +{# +/** + * @file + * Person content banner display. + */ +#} +{% set _content %} + {{ content.oe_summary }} + {% if social_links is not empty %} +
+ {% for _link in social_links %} +
+ {{ pattern('link', _link|merge( + { + 'attributes': create_attribute().addClass(['d-block', 'd-md-inline-block', 'mb-3', 'mb-md-0', 'standalone']) + } + ) ) }} +
+ {% endfor %} +
+ {% endif %} +{% endset %} + + {{ pattern('content_banner', { + background: 'gray', + title: label, + content: _content, + image: image, + meta: [ + content.oe_sc_person_occupation|field_value, + content.oe_sc_person_position|field_value, + content.oe_sc_person_country|field_value, + ]|filter(element => element is not empty), + }) }} + diff --git a/templates/content/node--oe-sc-person--teaser.html.twig b/templates/content/node--oe-sc-person--teaser.html.twig new file mode 100644 index 000000000..4228ef6c7 --- /dev/null +++ b/templates/content/node--oe-sc-person--teaser.html.twig @@ -0,0 +1,21 @@ +{# +/** + * @file + * Theme override to display a node of bundle person in the teaser view mode. + */ +#} +{% set _title %} + {{ label }} +{% endset %} +{% block content %} + {{ pattern('card', { + variant: 'search', + title: _title, + image: image, + meta: [ + content.oe_sc_person_occupation|field_value, + content.oe_sc_person_position|field_value, + ]|filter(element => element is not empty), + attributes: attributes, + }) }} +{% endblock %} diff --git a/tests/src/Functional/PersonContentRenderTest.php b/tests/src/Functional/PersonContentRenderTest.php new file mode 100644 index 000000000..5298d37ab --- /dev/null +++ b/tests/src/Functional/PersonContentRenderTest.php @@ -0,0 +1,294 @@ +createExamplePersonWithRequiredFieldsOnly(); + $this->drupalGet($node->toUrl()); + /** @var \Symfony\Component\BrowserKit\AbstractBrowser $client */ + $client = $this->getSession()->getDriver()->getClient(); + $crawler = $client->getCrawler(); + + // Assert required fields are present. + $content_banner = $crawler->filter('.bcl-content-banner'); + $this->assertCount(1, $content_banner); + + $this->assertEquals( + 'Stefan Mayer', + $content_banner->filter('.card-title')->text() + ); + $this->assertEquals( + 'DG Test', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(1)')->text() + ); + $this->assertEquals( + 'Director', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(2)')->text() + ); + $this->assertEquals( + 'Germany', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(3)')->text() + ); + + // Assert optional fields are not present. + // Assert no image is present. + $this->assertCount(0, $content_banner->filter('img')); + + // Assert short description is not present. + $this->assertCount(0, $content_banner->filter('p')); + + // Assert no social media links are present. + $this->assertCount(0, $content_banner->filter('a.standalone')); + + // Assert additional information is not present. + $this->assertStringNotContainsString( + 'Additional information', + $crawler->text() + ); + + // Assert documents are not present. + $this->assertStringNotContainsString( + 'Related documents', + $crawler->text() + ); + + // Test person with all fields. + $node_with_all_fields = $this->createExamplePersonWithAllFields(); + $this->drupalGet($node_with_all_fields->toUrl()); + $crawler = $client->getCrawler(); + + $content_banner = $crawler->filter('.bcl-content-banner'); + + $this->assertEquals( + 'Stefan Mayer', + $content_banner->filter('.card-title')->text() + ); + $this->assertEquals( + 'DG Test', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(1)')->text() + ); + $this->assertEquals( + 'Director', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(2)')->text() + ); + $this->assertEquals( + 'Germany', + $content_banner->filter('.card-body > div.my-4 > span:nth-child(3)')->text() + ); + + // Assert content banner image. + $image = $content_banner->filter('img.card-img-top'); + $this->assertCount(1, $image); + $this->assertStringContainsString( + 'image-test.png', + $image->attr('src') + ); + $this->assertEquals('Starter Image test alt', + $image->attr('alt') + ); + + $this->assertEquals( + 'This field is used to add a short biography of the person.', + $content_banner->filter('p')->text() + ); + + $this->assertEquals( + 'Twitter profile', + $content_banner->filter('div.mb-2 > div.mb-3 > a.standalone')->text() + ); + + $content = $crawler->filter('div.col-12'); + + $this->assertEquals( + 'Additional information', + $content->filter('h2')->eq(0)->text() + ); + $this->assertEquals( + 'Additional information example field.', + $content->filter('div > p')->text() + ); + + $this->assertEquals( + 'Related documents', + $content->filter('h2')->eq(1)->text() + ); + + $document_group_title = $content->filter('h3.fs-4'); + $this->assertEquals('Curriculum Vitae', $document_group_title->text()); + $files = $content->filter('.bcl-file'); + $this->assertEquals( + 'Person document test', + $files->filter('p')->text() + ); + + $this->assertCount(3, $files); + } + + /** + * Tests the person rendered in 'Teaser' view mode. + */ + public function testPersonRenderingTeaser(): void { + $node = $this->createExamplePersonWithAllFields(); + // Build node teaser view. + $builder = \Drupal::entityTypeManager()->getViewBuilder('node'); + $build = $builder->view($node, 'teaser'); + $markup = $this->container->get('renderer')->renderRoot($build); + $crawler = new Crawler((string) $markup); + + $article = $crawler->filter('article'); + $this->assertCount(1, $article); + + $this->assertEquals( + 'Stefan Mayer', + $article->filter('h1.card-title')->text() + ); + $image = $article->filter('img.card-img-top'); + $this->assertCount(1, $image); + $this->assertStringContainsString( + 'image-test.png', + $image->attr('src') + ); + $this->assertEquals( + 'DG Test', + $article->filter('.card-body > div.my-3 > span:nth-child(1)')->text() + ); + $this->assertEquals( + 'Director', + $article->filter('.card-body > div.my-3 > span:nth-child(2)')->text() + ); + } + + /** + * Creates an example person node only with required fields. + * + * @return \Drupal\node\NodeInterface + * Person node. + */ + protected function createExamplePersonWithRequiredFieldsOnly(): NodeInterface { + // Create a Person node with required fields. + /** @var \Drupal\node\Entity\Node $node */ + $node = \Drupal::entityTypeManager() + ->getStorage('node') + ->create([ + 'type' => 'oe_sc_person', + 'oe_sc_person_first_name' => 'Stefan', + 'oe_sc_person_last_name' => 'Mayer', + 'oe_sc_person_country' => 'DE', + 'oe_sc_person_occupation' => 'DG Test', + 'oe_sc_person_position' => 'Director', + 'uid' => 1, + 'status' => 1, + ]); + $node->save(); + return $node; + } + + /** + * Creates an example person node with required and optional fields. + * + * @return \Drupal\node\NodeInterface + * Person node. + */ + protected function createExamplePersonWithAllFields(): NodeInterface { + /** @var \Drupal\node\Entity\Node $node */ + $node = $this->createExamplePersonWithRequiredFieldsOnly(); + // Create a sample image media entity to be embedded. + $image_file = File::create([ + 'uri' => $this->getTestFiles('image')[0]->uri, + ]); + $image_file->save(); + $media_image = Media::create([ + 'bundle' => 'image', + 'name' => 'Starter Image test', + 'oe_media_image' => [ + [ + 'target_id' => $image_file->id(), + 'alt' => 'Starter Image test alt', + 'title' => 'Starter Image test title', + ], + ], + ]); + $media_image->save(); + + // Create a sample document media entity to be embedded. + $document_file = File::create([ + 'uri' => $this->getTestFiles('text')[0]->uri, + ]); + $document_file->save(); + $media_document = Media::create([ + 'bundle' => 'document', + 'name' => 'Person document test', + 'oe_media_file_type' => 'local', + 'oe_media_file' => [ + [ + 'target_id' => (int) $document_file->id(), + 'alt' => 'Person document alt', + 'title' => 'Person document title', + ], + ], + ]); + $media_document->save(); + + $document_reference = \Drupal::entityTypeManager() + ->getStorage('oe_document_reference') + ->create([ + 'type' => 'oe_document', + 'oe_document' => $media_document, + 'status' => 1, + ]); + $document_reference->save(); + $document_group_reference = \Drupal::entityTypeManager() + ->getStorage('oe_document_reference') + ->create([ + 'type' => 'oe_document_group', + 'oe_title' => 'Curriculum Vitae', + 'oe_documents' => [$media_document, $media_document], + 'status' => 1, + ]); + $document_reference->save(); + + $node->set('oe_summary', 'This field is used to add a short biography of the person.'); + $node->set('oe_sc_person_additional_info', 'Additional information example field.'); + $node->set('oe_social_media_links', [ + 'uri' => 'https://twitter.com', + 'title' => 'Twitter profile', + 'link_type' => 'twitter', + ]); + $node->set( + 'oe_sc_person_documents', + [$document_reference, $document_group_reference] + ); + $node->set('oe_sc_person_image', [$media_image]); + $node->save(); + return $node; + } + +}