diff --git a/composer.json b/composer.json index f8c79040962f39ad372fcc2ffe805bcf9f22e8aa..5b83184f19f5585adc5b278ee80b7fb19edf8e9a 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,7 @@ "cweagans/composer-patches": "^1.7", "drupal/core": "^9.2", "drupal/twig_field_value": "^2.0", - "openeuropa/oe_bootstrap_theme": "0.1.202203290731" + "openeuropa/oe_bootstrap_theme": "0.1.202204061107" }, "require-dev": { "composer/installers": "^1.11", @@ -59,6 +59,17 @@ "url": "https://github.com/openeuropa/oe_starter_content" } }, + "autoload": { + "psr-4": { + "Drupal\\oe_whitelabel\\": "./src/" + } + }, + "autoload-dev": { + "psr-4": { + "Drupal\\Tests\\oe_whitelabel\\": "./tests/src/", + "Drupal\\Tests\\oe_bootstrap_theme\\": "./build/themes/contrib/oe_bootstrap_theme/tests/src/" + } + }, "extra": { "composer-exit-on-patch-failure": true, "enable-patching": true, @@ -71,8 +82,8 @@ } }, "patches": { - "openeuropa/oe_bootstrap_theme" : { - "1.x latest": "https://github.com/openeuropa/oe_bootstrap_theme/compare/0.1.202203290731..1.x.diff" + "openeuropa/oe_paragraphs": { + "latest": "https://github.com/openeuropa/oe_paragraphs/compare/1.12.0..master.diff" } }, "drupal-scaffold": { diff --git a/modules/oe_whitelabel_helper/src/EuropeanUnionLanguages.php b/modules/oe_whitelabel_helper/src/EuropeanUnionLanguages.php deleted file mode 100644 index 9ac60d58512bcf24f145222c85cf20adc9fdbdd4..0000000000000000000000000000000000000000 --- a/modules/oe_whitelabel_helper/src/EuropeanUnionLanguages.php +++ /dev/null @@ -1,120 +0,0 @@ -<?php - -declare(strict_types = 1); - -namespace Drupal\oe_whitelabel_helper; - -/** - * Helper class storing European Union languages information. - * - * @see https://github.com/openeuropa/oe_theme/blob/HEAD/modules/oe_theme_helper/src/EuropeanUnionLanguages.php - */ -class EuropeanUnionLanguages { - - /** - * List of European Union languages. - * - * Each entry includes: - * - * - The language name in English - * - The language name in its native form - * - The internal language ID, used on URLs, asset names, etc. - * - * @var array - */ - protected static $languages = [ - 'bg' => ['Bulgarian', 'българÑки', 'bg'], - 'cs' => ['Czech', 'ÄeÅ¡tina', 'cs'], - 'da' => ['Danish', 'dansk', 'da'], - 'de' => ['German', 'Deutsch', 'de'], - 'et' => ['Estonian', 'eesti', 'et'], - 'el' => ['Greek', 'ελληνικά', 'el'], - 'en' => ['English', 'English', 'en'], - 'es' => ['Spanish', 'español', 'es'], - 'fr' => ['French', 'français', 'fr'], - 'ga' => ['Irish', 'Gaeilge', 'ga'], - 'hr' => ['Croatian', 'hrvatski', 'hr'], - 'it' => ['Italian', 'italiano', 'it'], - 'lt' => ['Lithuanian', 'lietuvių', 'lt'], - 'lv' => ['Latvian', 'latvieÅ¡u', 'lv'], - 'hu' => ['Hungarian', 'magyar', 'hu'], - 'mt' => ['Maltese', 'Malti', 'mt'], - 'nl' => ['Dutch', 'Nederlands', 'nl'], - 'pl' => ['Polish', 'polski', 'pl'], - 'pt-pt' => ['Portuguese', 'português', 'pt'], - 'ro' => ['Romanian', 'română', 'ro'], - 'sk' => ['Slovak', 'slovenÄina', 'sk'], - 'sl' => ['Slovenian', 'slovenÅ¡Äina', 'sl'], - 'fi' => ['Finnish', 'suomi', 'fi'], - 'sv' => ['Swedish', 'svenska', 'sv'], - ]; - - /** - * Returns a list of language data. - * - * This is the data that is expected to be returned by the overridden language - * manager as supplied by the OpenEuropa Multilingual module. - * - * @return array - * An array with language codes as keys, and English and native language - * names as values. - */ - public static function getLanguageList(): array { - return self::$languages; - } - - /** - * Assert whether the given language is a European Union one. - * - * @param string $language_code - * The language code as defined by the W3C language tags document. - * - * @return bool - * Whereas the given language is a European Union one. - */ - public static function hasLanguage(string $language_code): bool { - return isset(self::$languages[$language_code]); - } - - /** - * Get the language name in English given its W3C code. - * - * @param string $language_code - * The language code as defined by the W3C language tags document. - * - * @return string - * The language name in English if any, an empty string otherwise. - */ - public static function getEnglishLanguageName(string $language_code): string { - return self::$languages[$language_code][0] ?? ''; - } - - /** - * Get the native language name given its W3C code. - * - * @param string $language_code - * The language code as defined by the W3C language tags document. - * - * @return string - * The native language name if any, an empty string otherwise. - */ - public static function getNativeLanguageName(string $language_code): string { - return self::$languages[$language_code][1] ?? ''; - } - - /** - * Get the internal language code given its W3C code. - * - * Internal language codes may differ from the standard ones. - * - * @param string $language_code - * The language code as defined by the W3C language tags document. - * - * @return string - * The internal language code if any, an empty string otherwise. - */ - public static function getInternalLanguageCode(string $language_code): string { - return self::$languages[$language_code][2] ?? ''; - } - -} diff --git a/modules/oe_whitelabel_helper/src/TwigExtension/TwigExtension.php b/modules/oe_whitelabel_helper/src/TwigExtension/TwigExtension.php index 944b17e5fa625af94525c1631f9d1a6d2eb700e2..e667825177985d5331d24f9d741dfb4a5b50f1a8 100644 --- a/modules/oe_whitelabel_helper/src/TwigExtension/TwigExtension.php +++ b/modules/oe_whitelabel_helper/src/TwigExtension/TwigExtension.php @@ -6,7 +6,6 @@ namespace Drupal\oe_whitelabel_helper\TwigExtension; use Drupal\Core\Cache\CacheableDependencyInterface; use Drupal\Core\StringTranslation\PluralTranslatableMarkup; -use Drupal\oe_whitelabel_helper\EuropeanUnionLanguages; use Drupal\Core\Url; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; @@ -37,7 +36,6 @@ class TwigExtension extends AbstractExtension { public function getFilters(): array { return [ new TwigFilter('bcl_timeago', [$this, 'bclTimeAgo']), - new TwigFilter('to_internal_language_id', [$this, 'toInternalLanguageId']), ]; } @@ -184,21 +182,4 @@ class TwigExtension extends AbstractExtension { return $block_plugin->build(); } - /** - * Get an internal language ID given its code. - * - * @param string $language_code - * The language code as defined by the W3C language tags document. - * - * @return string - * The internal language ID, or the given language code if none found. - */ - public function toInternalLanguageId(string $language_code): string { - if (EuropeanUnionLanguages::hasLanguage($language_code)) { - return EuropeanUnionLanguages::getInternalLanguageCode($language_code); - } - - return $language_code; - } - } diff --git a/modules/oe_whitelabel_multilingual/oe_whitelabel_multilingual.module b/modules/oe_whitelabel_multilingual/oe_whitelabel_multilingual.module index 1dc9264e18018eb5f766cd521a7382da0ec50495..85fdf74f758967d9e56ded9a7b72b1b0b8848dc5 100755 --- a/modules/oe_whitelabel_multilingual/oe_whitelabel_multilingual.module +++ b/modules/oe_whitelabel_multilingual/oe_whitelabel_multilingual.module @@ -8,7 +8,7 @@ declare(strict_types = 1); use Drupal\Component\Utility\Html; -use Drupal\oe_whitelabel_helper\EuropeanUnionLanguages; +use Drupal\oe_bootstrap_theme_helper\EuropeanUnionLanguages; /** * Implements hook_preprocess_links(). diff --git a/modules/oe_whitelabel_paragraphs/tests/src/Kernel/Paragraphs/DocumentParagraphTest.php b/modules/oe_whitelabel_paragraphs/tests/src/Kernel/Paragraphs/DocumentParagraphTest.php new file mode 100644 index 0000000000000000000000000000000000000000..7ca6a7926f94c630e43f13520356861af76e83f7 --- /dev/null +++ b/modules/oe_whitelabel_paragraphs/tests/src/Kernel/Paragraphs/DocumentParagraphTest.php @@ -0,0 +1,234 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\Tests\oe_whitelabel_paragraphs\Kernel\Paragraphs; + +use Drupal\Core\Site\Settings; +use Drupal\file\Entity\File; +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\media\Entity\Media; +use Drupal\paragraphs\Entity\Paragraph; +use Drupal\Tests\oe_bootstrap_theme\PatternAssertion\FilePatternAssert; +use Drupal\Tests\user\Traits\UserCreationTrait; +use Drupal\user\Entity\User; +use Symfony\Component\DomCrawler\Crawler; + +/** + * Tests the document paragraph. + */ +class DocumentParagraphTest extends ParagraphsTestBase { + + use UserCreationTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'content_translation', + 'file_link_test', + 'language', + 'node', + 'oe_paragraphs_document', + ]; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + // The node dependency is wrongfully forced by oe_media_media_access(). + $this->installEntitySchema('node'); + $this->installEntitySchema('media'); + $this->installConfig([ + 'content_translation', + 'language', + 'media', + 'oe_media', + ]); + + $this->container->get('module_handler')->loadInclude('oe_paragraphs_media_field_storage', 'install'); + oe_paragraphs_media_field_storage_install(FALSE); + $this->installConfig(['oe_paragraphs_document']); + + ConfigurableLanguage::createFromLangcode('it')->save(); + ConfigurableLanguage::createFromLangcode('es')->save(); + + // Enable translations for the document media bundle. + $this->container->get('content_translation.manager')->setEnabled('media', 'document', TRUE); + // Make fields translatable. + $field_ids = [ + 'media.document.oe_media_file_type', + 'media.document.oe_media_remote_file', + 'media.document.oe_media_file', + ]; + foreach ($field_ids as $field_id) { + $field_config = $this->container->get('entity_type.manager')->getStorage('field_config')->load($field_id); + $field_config->set('translatable', TRUE)->save(); + } + $this->container->get('router.builder')->rebuild(); + + // Simulate the presence of test remote files. This avoids real requests to + // external websites. + $settings = Settings::getAll(); + $settings['file_link_test_middleware'] = [ + 'http://oe_whitelabel.drupal/spanish-document.txt' => [ + 'status' => 200, + 'headers' => [ + 'Content-Type' => 'text/plain', + 'Content-Length' => 45187, + ], + ], + 'http://oe_whitelabel.drupal/spreadsheet.xls' => [ + 'status' => 200, + 'headers' => [ + 'Content-Type' => 'application/vnd.ms-excel', + 'Content-Length' => 78459784, + ], + ], + ]; + new Settings($settings); + + // Tests need to run with user 1 as access checks prevent entity reference + // rendering otherwise. + $this->setCurrentUser(User::load(1)); + } + + /** + * Tests the file paragraph rendering. + */ + public function testRendering(): void { + $uri_en = $this->container->get('file_system')->copy( + $this->container->get('extension.list.module')->getPath('oe_media') . '/tests/fixtures/sample.pdf', + 'public://test.pdf' + ); + $pdf_en = File::create(['uri' => $uri_en]); + $pdf_en->save(); + + $local_media = Media::create([ + 'bundle' => 'document', + 'name' => 'Local PDF file', + 'oe_media_file_type' => 'local', + 'oe_media_file' => [ + 'target_id' => $pdf_en->id(), + ], + ]); + $local_media->save(); + + $paragraph = Paragraph::create([ + 'type' => 'oe_document', + 'field_oe_media' => [ + 'target_id' => $local_media->id(), + ], + ]); + $paragraph->save(); + + $html = $this->renderParagraph($paragraph); + $crawler = new Crawler($html); + $paragraph_wrapper = $crawler->filter('.paragraph'); + $this->assertCount(1, $paragraph_wrapper); + + $expected = [ + 'file' => [ + 'title' => 'Local PDF file', + 'language' => 'English', + 'url' => file_create_url($uri_en), + 'meta' => '(2.96 KB - PDF)', + 'icon' => 'file-pdf-fill', + ], + 'translations' => NULL, + 'link_label' => 'Download', + ]; + $assert = new FilePatternAssert(); + $assert->assertPattern($expected, $paragraph_wrapper->html()); + + // Add an Italian translation for the media. + $uri_it = $this->container->get('file_system')->copy( + $this->container->get('extension.list.module')->getPath('oe_media') . '/tests/fixtures/sample.pdf', + 'public://test_it.pdf' + ); + $pdf_it = File::create(['uri' => $uri_it]); + $pdf_it->save(); + $local_media->addTranslation('it', [ + 'name' => 'Italian translation', + 'oe_media_file_type' => 'local', + 'oe_media_file' => [ + 'target_id' => $pdf_it->id(), + ], + ]); + $local_media->save(); + + $html = $this->renderParagraph($paragraph); + $crawler = new Crawler($html); + $paragraph_wrapper = $crawler->filter('.paragraph'); + $this->assertCount(1, $paragraph_wrapper); + $expected['translations'] = [ + [ + 'title' => 'Italian translation', + 'language' => 'Italian', + 'url' => file_create_url($uri_it), + 'meta' => '(2.96 KB - PDF)', + ], + ]; + $assert->assertPattern($expected, $paragraph_wrapper->html()); + + // Add a Spanish translation that points to a remote file. + $local_media->addTranslation('es', [ + 'name' => 'Spanish translation', + 'oe_media_file_type' => 'remote', + 'oe_media_remote_file' => 'http://oe_whitelabel.drupal/spanish-document.txt', + ]); + $local_media->save(); + + $html = $this->renderParagraph($paragraph); + $crawler = new Crawler($html); + $paragraph_wrapper = $crawler->filter('.paragraph'); + $this->assertCount(1, $paragraph_wrapper); + $expected['translations'][] = [ + 'title' => 'Spanish translation', + 'language' => 'Spanish', + 'url' => 'http://oe_whitelabel.drupal/spanish-document.txt', + 'meta' => '(44.13 KB - TXT)', + ]; + $assert->assertPattern($expected, $paragraph_wrapper->html()); + + // Test a remote document as main file, to make sure that the + // DocumentMediaWrapper class is tested in all scenarios. + $remote_media = Media::create([ + 'bundle' => 'document', + 'name' => 'Remote XLS file', + 'oe_media_file_type' => 'remote', + 'oe_media_remote_file' => 'http://oe_whitelabel.drupal/spreadsheet.xls', + ]); + $remote_media->save(); + + $paragraph = Paragraph::create([ + 'type' => 'oe_document', + 'field_oe_media' => [ + 'target_id' => $remote_media->id(), + ], + ]); + $paragraph->save(); + + $html = $this->renderParagraph($paragraph); + $crawler = new Crawler($html); + $paragraph_wrapper = $crawler->filter('.paragraph'); + $this->assertCount(1, $paragraph_wrapper); + + $expected = [ + 'file' => [ + 'title' => 'Remote XLS file', + 'language' => 'English', + 'url' => 'http://oe_whitelabel.drupal/spreadsheet.xls', + 'meta' => '(74.83 MB - XLS)', + 'icon' => 'file-excel-fill', + ], + 'translations' => NULL, + 'link_label' => 'Download', + ]; + $assert = new FilePatternAssert(); + $assert->assertPattern($expected, $paragraph_wrapper->html()); + } + +} diff --git a/oe_whitelabel.theme b/oe_whitelabel.theme index 5da6c190eebd347b07f207bc2265114b0e6df528..6be05efd19aac9b9da2c23fe12627f36130b6e1b 100644 --- a/oe_whitelabel.theme +++ b/oe_whitelabel.theme @@ -9,7 +9,8 @@ declare(strict_types = 1); use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; -use Drupal\oe_whitelabel_helper\EuropeanUnionLanguages; +use Drupal\oe_bootstrap_theme_helper\EuropeanUnionLanguages; +use Drupal\oe_whitelabel\DocumentMediaWrapper; // Include all files from the includes directory. $includes_path = __DIR__ . '/includes/*.inc'; @@ -137,3 +138,33 @@ function oe_whitelabel_preprocess_page(&$variables) { } $variables['site_logo_href'] = $site_logo_href; } + +/** + * Implements hook_preprocess_HOOK() for document media bundle. + */ +function oe_whitelabel_preprocess_media__document__default(&$variables) { + /** @var \Drupal\media\Entity\Media $media */ + $media = $variables['media']; + + $wrapper = new DocumentMediaWrapper($media); + if ($wrapper->isEmpty()) { + return; + } + + $variables['file'] = $wrapper->toFileValueObject(); + + // Generate the file information for all available translations. + foreach ($media->getTranslationLanguages() as $langcode => $language) { + // We don't want to include the information of the current language again. + if ($media->language()->getId() === $langcode) { + continue; + } + + $translation = $media->getTranslation($langcode); + $wrapper = new DocumentMediaWrapper($translation); + if ($wrapper->isEmpty()) { + continue; + } + $variables['translations'][] = $wrapper->toFileValueObject(); + } +} diff --git a/runner.yml.dist b/runner.yml.dist index 17e68fd13c793c6c8bfc4a1d7bff8b029f845a01..78ae4aaad5f680e9b2357fcc5c4e69f180121c7b 100644 --- a/runner.yml.dist +++ b/runner.yml.dist @@ -41,6 +41,7 @@ drupal: - "bower_components" - "vendor" - "${drupal.root}" + file_private_path: 'sites/default/files/private' databases: sparql_default: default: diff --git a/src/DocumentMediaWrapper.php b/src/DocumentMediaWrapper.php new file mode 100644 index 0000000000000000000000000000000000000000..b486b3b8d5b40ec10d6535d4bd37e797623a732f --- /dev/null +++ b/src/DocumentMediaWrapper.php @@ -0,0 +1,106 @@ +<?php + +declare(strict_types = 1); + +namespace Drupal\oe_whitelabel; + +use Drupal\Core\Field\FieldItemListInterface; +use Drupal\media\MediaInterface; +use Drupal\oe_bootstrap_theme\ValueObject\FileValueObject; + +/** + * Wraps a media entity of bundle "document". + * + * @internal + */ +class DocumentMediaWrapper { + + /** + * The media. + * + * @var \Drupal\media\MediaInterface + */ + protected MediaInterface $media; + + /** + * Construct a new wrapper object. + * + * @param \Drupal\media\MediaInterface $media + * The media to wrap. + */ + public function __construct(MediaInterface $media) { + if ($media->bundle() !== 'document') { + throw new \InvalidArgumentException(sprintf('Invalid media of type "%s" passed, "document" expected.', $media->bundle())); + } + + $this->media = $media; + } + + /** + * Returns if the media is empty. + * + * A media is considered empty if the current active field, based on the type, + * is empty. + * + * @return bool + * Whether the media is referencing a field or not. + */ + public function isEmpty(): bool { + $field = $this->getActiveField(); + return !$field || $field->isEmpty(); + } + + /** + * Returns the type of the media. + * + * @return string|null + * The media type, usually "remote" or "local". NULL if no value set. + */ + public function getType(): ?string { + return $this->media->get('oe_media_file_type')->value; + } + + /** + * Creates a file value object from the current media values. + * + * @return \Drupal\oe_bootstrap_theme\ValueObject\FileValueObject|null + * A file value object, or NULL if the media is empty. + */ + public function toFileValueObject(): ?FileValueObject { + if ($this->isEmpty()) { + return NULL; + } + + $field = $this->getActiveField(); + $object = $this->getType() === 'remote' + ? FileValueObject::fromFileLink($field->first()) + : FileValueObject::fromFileEntity($field->first()->entity); + + return $object->setTitle($this->media->getName()) + ->setLanguageCode($this->media->language()->getId()); + } + + /** + * Returns the field that is being used for the document, based on the type. + * + * @return \Drupal\Core\Field\FieldItemListInterface|null + * The field item list, or NULL if an invalid type is specified. + */ + protected function getActiveField(): ?FieldItemListInterface { + if (!$this->getType()) { + return NULL; + } + + switch ($this->getType()) { + case 'remote': + return $this->media->get('oe_media_remote_file'); + + case 'local': + return $this->media->get('oe_media_file'); + + default: + return NULL; + } + } + +} diff --git a/templates/media/media--document--default.html.twig b/templates/media/media--document--default.html.twig new file mode 100644 index 0000000000000000000000000000000000000000..dcaaf033af0fc12effb79e1d307adc2823f436ba --- /dev/null +++ b/templates/media/media--document--default.html.twig @@ -0,0 +1,14 @@ +{# +/** + * @file + * Theme override to display a media item. + * + * @see ./core/themes/stable/templates/content/media.html.twig + */ +#} +{% if file %} + {{ pattern('file', { + file: file, + translations: translations, + }) }} +{% endif %}