diff --git a/config/install/pathologic.settings.yml b/config/install/pathologic.settings.yml index ed7f9a3ea3953ef4b650934fd7aa30a3dabd64e6..3c40489f9868bd23f008c52b29fee57a9a76c8cb 100644 --- a/config/install/pathologic.settings.yml +++ b/config/install/pathologic.settings.yml @@ -1,3 +1,4 @@ scheme_allow_list: ['http', 'https', 'files', 'internal'] protocol_style: 'full' local_paths: '' +keep_language_prefix: true diff --git a/config/schema/pathologic.schema.yml b/config/schema/pathologic.schema.yml index 856990c06b4f9a917dc1f42be7e22606c97d0573..ce6bdf3890bcc72c27c8592acbe39e57153b3d46 100644 --- a/config/schema/pathologic.schema.yml +++ b/config/schema/pathologic.schema.yml @@ -13,6 +13,9 @@ pathologic.settings: local_paths: type: string label: 'Also considered local' + keep_language_prefix: + type: boolean + label: 'Keep language prefix' filter_settings.filter_pathologic: type: filter @@ -31,3 +34,6 @@ filter_settings.filter_pathologic: local_paths: type: string label: 'Also considered local' + keep_language_prefix: + type: boolean + label: 'Keep language prefix' diff --git a/pathologic.module b/pathologic.module index 464410f005a439e9d774e16395ccc87e7220ca68..d0c6b553fef706c0c987ee6f0eb3ee423f052687 100644 --- a/pathologic.module +++ b/pathologic.module @@ -297,7 +297,7 @@ function _pathologic_replace($matches) { // Okay, deal with language stuff. // Let's see if we can split off a language prefix from the path. - if (\Drupal::moduleHandler()->moduleExists('language')) { + if ((($cached_settings['current_settings']['keep_language_prefix'] ?? FALSE) === FALSE) && \Drupal::moduleHandler()->moduleExists('language')) { // This logic is based on // \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl::getLangcode(). $languages = \Drupal::languageManager()->getLanguages(); diff --git a/pathologic.post_update.php b/pathologic.post_update.php index fb7576dd156bd446cd4557fe3aae5da4735ae165..e1cc9de7ba0d949759463e297a478b7286a5b680 100644 --- a/pathologic.post_update.php +++ b/pathologic.post_update.php @@ -5,6 +5,10 @@ * Post update functions for the Pathologic module. */ +use Drupal\filter\Entity\FilterFormat; +use Drupal\filter\FilterFormatInterface; +use Drupal\filter\FilterPluginCollection; + /** * Move setting for allowed schemes to a new name (#3216612). */ @@ -16,3 +20,26 @@ function pathologic_post_update_rename_scheme_allow_list() { $config->clear('scheme_whitelist'); $config->save(); } + +/** + * Define 'keep_language_prefix' using legacy behavior for existing sites. + */ +function pathologic_post_update_set_keep_language_prefix() { + \Drupal::configFactory()->getEditable('pathologic.settings') + ->set('keep_language_prefix', FALSE) + ->save(); + + if (\Drupal::service('plugin.manager.filter')->hasDefinition('filter_pathologic')) { + foreach (FilterFormat::loadMultiple() as $format) { + assert($format instanceof FilterFormatInterface); + $collection = $format->filters(); + $configuration = $collection->getConfiguration(); + assert($collection instanceof FilterPluginCollection); + if (array_key_exists('filter_pathologic', $configuration)) { + $configuration['filter_pathologic']['settings']['local_settings']['keep_language_prefix'] = FALSE; + $collection->setConfiguration($configuration); + $format->save(); + } + } + } +} diff --git a/src/PathologicSettingsCommon.php b/src/PathologicSettingsCommon.php index 48964c93ff05ac412b13c12ced412ba7a155a7b7..5c03cf325d59e3d3834a5ff41966f62b9cfaffbf 100644 --- a/src/PathologicSettingsCommon.php +++ b/src/PathologicSettingsCommon.php @@ -43,6 +43,13 @@ class PathologicSettingsCommon { '#description' => $this->t('If this site is or was available at more than one base path or URL, enter them here, separated by line breaks. For example, if this site is live at <code>http://example.com/</code> but has a staging version at <code>http://dev.example.org/staging/</code>, you would enter both those URLs here. If confused, please read <a href=":docs" target="_blank">Pathologic’s documentation</a> for more information about this option and what it affects.', [':docs' => 'https://www.drupal.org/node/257026']), '#weight' => 20, ], + 'keep_language_prefix' => [ + '#type' => 'checkbox', + '#title' => $this->t('Keep language prefix'), + '#description' => $this->t('If not checked, URL prefixes that match configured languages will be removed from links.'), + '#default_value' => $defaults['keep_language_prefix'] ?? TRUE, + '#weight' => 20, + ], ]; } diff --git a/src/PathologicSettingsForm.php b/src/PathologicSettingsForm.php index 64e75305e6d93cb327d19931891a86d7f1ffa82e..7c21ff14d4fa0ded3c12cb51fa1071245d6ce568 100644 --- a/src/PathologicSettingsForm.php +++ b/src/PathologicSettingsForm.php @@ -30,6 +30,7 @@ class PathologicSettingsForm extends ConfigFormBase { $defaults = [ 'protocol_style' => $config->get('protocol_style'), 'local_paths' => $config->get('local_paths'), + 'keep_language_prefix' => $config->get('keep_language_prefix'), ]; $common = new PathologicSettingsCommon(); @@ -45,6 +46,7 @@ class PathologicSettingsForm extends ConfigFormBase { $this->config('pathologic.settings') ->set('protocol_style', $form_state->getValue('protocol_style')) ->set('local_paths', $form_state->getValue('local_paths')) + ->set('keep_language_prefix', $form_state->getValue('keep_language_prefix')) ->save(); parent::submitForm($form, $form_state); diff --git a/src/Plugin/Filter/FilterPathologic.php b/src/Plugin/Filter/FilterPathologic.php index 055f8a585f091e2bb9a1e5dfd0a862908703393e..e2197655fd13cf12950a5d2fce4955a0fdd0889e 100644 --- a/src/Plugin/Filter/FilterPathologic.php +++ b/src/Plugin/Filter/FilterPathologic.php @@ -87,6 +87,7 @@ class FilterPathologic extends FilterBase { $config = \Drupal::config('pathologic.settings'); $settings['protocol_style'] = $config->get('protocol_style'); $settings['local_paths'] = $config->get('local_paths'); + $settings['keep_language_prefix'] = $config->get('keep_language_prefix'); } else { $settings = $settings['local_settings']; diff --git a/tests/src/Functional/PathologicLanguageTest.php b/tests/src/Functional/PathologicLanguageTest.php new file mode 100644 index 0000000000000000000000000000000000000000..5eba374607b035a51319f52d3c51257a638276a2 --- /dev/null +++ b/tests/src/Functional/PathologicLanguageTest.php @@ -0,0 +1,190 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\pathologic\Functional; + +use Drupal\language\Entity\ConfigurableLanguage; +use Drupal\Tests\BrowserTestBase; +use Drupal\Tests\node\Traits\ContentTypeCreationTrait; +use Drupal\Tests\node\Traits\NodeCreationTrait; +use Drupal\Tests\pathologic\Traits\PathologicFormatTrait; +use Drupal\Tests\user\Traits\UserCreationTrait; + +/** + * Test multilingual integration of Pathologic functionality. + * + * @group pathologic + */ +class PathologicLanguageTest extends BrowserTestBase { + + use ContentTypeCreationTrait; + use NodeCreationTrait; + use PathologicFormatTrait; + use UserCreationTrait; + + /** + * {@inheritdoc} + */ + protected static $modules = [ + 'content_translation', + 'field', + 'language', + 'locale', + 'node', + 'pathologic', + 'text', + 'user', + ]; + + /** + * {@inheritdoc} + */ + protected $defaultTheme = 'stark'; + + /** + * {@inheritdoc} + */ + protected function setUp(): void { + parent::setUp(); + + $this->setUpCurrentUser(); + + $this->createContentType([ + 'type' => 'page', + 'name' => 'Basic page', + ]); + + // Add more languages. + ConfigurableLanguage::createFromLangcode('fr')->save(); + ConfigurableLanguage::createFromLangcode('pt-br')->save(); + + // Enable URL language detection and selection. + \Drupal::configFactory()->getEditable('language.negotiation') + ->set('url.prefixes.fr', 'fr') + ->set('url.prefixes.pt-br', 'pt-br') + ->save(); + + // Configure Pathologic on a text format. + $this->buildFormat([ + 'settings_source' => 'local', + 'local_settings' => [ + 'protocol_style' => 'path', + 'keep_language_prefix' => TRUE, + ], + ]); + + // To reflect the changes for a multilingual site, rebuild the container. + $this->container->get('kernel')->rebuildContainer(); + } + + /** + * Tests how links to nodes and files are handled with translations. + */ + public function testContentTranslation(): void { + + // Create a node that will be referenced in a link inside another node. + $node_to_reference = $this->createNode([ + 'type' => 'page', + 'title' => 'Reference page', + ]); + + // Add translations for the reference node, and try a whole + // series of possible input texts to see how they are handled. + $fr_reference = $node_to_reference->addTranslation('fr', [ + 'title' => 'Page de référence en français', + ])->save(); + $pt_br_reference = $node_to_reference->addTranslation('pt-br', [ + 'title' => 'Página de referência em Português', + ])->save(); + + global $base_path; + $nid = $node_to_reference->id(); + + // The link replacement shouldn't change for any of these based on the language the filter runs with. + foreach (['en', 'fr', 'pt-br'] as $langcode) { + $this->assertSame( + '<a href="' . $base_path . 'sites/default/files/test.png">Test file link</a>', + $this->runFilter('<a href="/sites/default/files/test.png">Test file link</a>', $langcode), + "$langcode: file links do not get a language prefix", + ); + $this->assertSame( + '<a href="' . $base_path . 'node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: node/N link should be unchanged" + ); + $this->assertSame( + '<a href="' . $base_path . 'fr/node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/fr/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: fr/node/N link should be unchanged", + ); + $this->assertSame( + '<a href="' . $base_path . 'pt-br/node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/pt-br/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: pt-br/node/N link should be unchanged", + ); + $this->assertSame( + '<a href="' . $base_path . 'reference-en">Test node link</a>', + $this->runFilter('<a href="/reference-en">Test node link</a>', $langcode), + "$langcode: /reference-en link uses EN alias", + ); + $this->assertSame( + '<a href="' . $base_path . 'fr/reference-fr">Test node link</a>', + $this->runFilter('<a href="/fr/reference-fr">Test node link</a>', $langcode), + "$langcode: fr/reference-fr link uses the FR alias", + ); + $this->assertSame( + '<a href="' . $base_path . 'pt-br/referencia-pt">Test node link</a>', + $this->runFilter('<a href="/pt-br/referencia-pt">Test node link</a>', $langcode), + "$langcode: pt-br/referencia-pt uses the PT-BR alias", + ); + } + + // Try again with language code stripping configured. + $this->buildFormat([ + 'settings_source' => 'local', + 'local_settings' => [ + 'protocol_style' => 'path', + 'keep_language_prefix' => FALSE, + ], + ]); + foreach (['en', 'fr', 'pt-br'] as $langcode) { + $this->assertSame( + '<a href="' . $base_path . 'sites/default/files/test.png">Test file link</a>', + $this->runFilter('<a href="/sites/default/files/test.png">Test file link</a>', $langcode), + "$langcode: file links do not get a language prefix", + ); + $this->assertSame( + '<a href="' . $base_path . 'node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: node/N link is unchanged" + ); + $this->assertSame( + '<a href="' . $base_path . 'node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/fr/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: fr/node/N link should have no langcode prefix" + ); + $this->assertSame( + '<a href="' . $base_path . 'node/' . $nid . '">Test node link</a>', + $this->runFilter('<a href="/pt-br/node/' . $nid . '">Test node link</a>', $langcode), + "$langcode: pt-br/node/N link should have no langcode prefix" + ); + $this->assertSame( + '<a href="' . $base_path . 'reference-en">Test node link</a>', + $this->runFilter('<a href="/reference-en">Test node link</a>', $langcode), + "$langcode: /reference-en link uses EN alias", + ); + $this->assertSame( + '<a href="' . $base_path . 'reference-fr">Test node link</a>', + $this->runFilter('<a href="/fr/reference-fr">Test node link</a>', $langcode), + "$langcode: fr/reference-fr link should have no langcode prefix", + ); + $this->assertSame( + '<a href="' . $base_path . 'referencia-pt">Test node link</a>', + $this->runFilter('<a href="/pt-br/referencia-pt">Test node link</a>', $langcode), + "$langcode: pt-br/referencia-pt link should have no langcode prefix", + ); + } + } + +} diff --git a/tests/src/Kernel/PathologicKernelTestBase.php b/tests/src/Kernel/PathologicKernelTestBase.php index e5eb77c8c60539496b9316f6ddf4dc432e9d551e..0b11e8a8472299ec7fc3bce922ef2f7fff22c993 100644 --- a/tests/src/Kernel/PathologicKernelTestBase.php +++ b/tests/src/Kernel/PathologicKernelTestBase.php @@ -6,14 +6,16 @@ namespace Drupal\Tests\pathologic\Kernel; use Drupal\Component\Utility\Html; use Drupal\Core\Url; -use Drupal\filter\Entity\FilterFormat; use Drupal\KernelTests\KernelTestBase; +use Drupal\Tests\pathologic\Traits\PathologicFormatTrait; /** * Base class for all Pathologic Kernel tests. */ abstract class PathologicKernelTestBase extends KernelTestBase { + use PathologicFormatTrait; + /** * {@inheritdoc} */ @@ -28,13 +30,6 @@ abstract class PathologicKernelTestBase extends KernelTestBase { */ protected $defaultTheme = 'stark'; - /** - * The ID of a text format to be used in a test. - * - * @var formatId - */ - protected $formatId = ''; - /** * {@inheritdoc} */ @@ -44,42 +39,6 @@ abstract class PathologicKernelTestBase extends KernelTestBase { $this->installConfig(['system', 'filter', 'pathologic']); } - /** - * Build a text format with Pathologic configured a certain way. - * - * @param array $settings - * An array of settings for the Pathologic instance on the format. - * - * @return string - * The randomly generated format machine name for the new format. - */ - protected function buildFormat(array $settings) { - $this->formatId = ($settings['local_settings']['protocol_style'] ?? 'unknown') . '_' . $this->randomMachineName(8); - $format = FilterFormat::create([ - 'format' => $this->formatId, - 'name' => $this->formatId, - ]); - $format->setFilterConfig('filter_pathologic', [ - 'status' => 1, - 'settings' => $settings, - ]); - $format->save(); - return $this->formatId; - } - - /** - * Runs the given string through the Pathologic text filter. - * - * @param string $markup - * Raw markup to be processed. - * - * @return string - * A string of text-format-filtered markup. - */ - protected function runFilter(string $markup): string { - return check_markup($markup, $this->formatId)->__toString(); - } - /** * Wrapper around url() which does HTML entity decoding and encoding. * diff --git a/tests/src/Kernel/PathologicLanguageTest.php b/tests/src/Kernel/PathologicLanguageTest.php index a4d659203c8dc0140ee64825933c235d273b8eb5..fb3bb153ec10a4b5b62662eca928654c34902413 100644 --- a/tests/src/Kernel/PathologicLanguageTest.php +++ b/tests/src/Kernel/PathologicLanguageTest.php @@ -60,6 +60,7 @@ class PathologicLanguageTest extends PathologicKernelTestBase { 'settings_source' => 'local', 'local_settings' => [ 'protocol_style' => 'path', + 'keep_language_prefix' => TRUE, ], ]); diff --git a/tests/src/Kernel/PathologicTest.php b/tests/src/Kernel/PathologicTest.php index 5318cf98cd2e76be7194dffda179e535bfe4e7c4..cde31e61b58eb4d8dab1a053abbe72dd1e27bd3a 100644 --- a/tests/src/Kernel/PathologicTest.php +++ b/tests/src/Kernel/PathologicTest.php @@ -82,6 +82,7 @@ class PathologicTest extends PathologicKernelTestBase { 'settings_source' => 'local', 'local_settings' => [ 'protocol_style' => $protocol_style, + 'keep_language_prefix' => TRUE, ], ]); $paths = []; diff --git a/tests/src/Traits/PathologicFormatTrait.php b/tests/src/Traits/PathologicFormatTrait.php new file mode 100644 index 0000000000000000000000000000000000000000..7099865d0150e9614dda1177ed115605344e41ce --- /dev/null +++ b/tests/src/Traits/PathologicFormatTrait.php @@ -0,0 +1,61 @@ +<?php + +declare(strict_types=1); + +namespace Drupal\Tests\pathologic\Traits; + +use Drupal\filter\Entity\FilterFormat; + +/** + * Provides management of a text format configured for Pathologic. + * + * This trait is meant to be used only by test classes. + */ +trait PathologicFormatTrait { + + /** + * The ID of a text format to be used in a test. + * + * @var formatId + */ + protected $formatId = ''; + + /** + * Build a text format with Pathologic configured a certain way. + * + * @param array $settings + * An array of settings for the Pathologic instance on the format. + * + * @return string + * The randomly generated format machine name for the new format. + */ + protected function buildFormat(array $settings) { + $this->formatId = ($settings['local_settings']['protocol_style'] ?? 'unknown') . '_' . $this->randomMachineName(8); + $format = FilterFormat::create([ + 'format' => $this->formatId, + 'name' => $this->formatId, + ]); + $format->setFilterConfig('filter_pathologic', [ + 'status' => 1, + 'settings' => $settings, + ]); + $format->save(); + return $this->formatId; + } + + /** + * Runs the given string through the Pathologic text filter. + * + * @param string $markup + * Raw markup to be processed. + * @param string $langcode + * The optional language to render the text with. + * + * @return string + * A string of text-format-filtered markup. + */ + protected function runFilter(string $markup, string $langcode = ''): string { + return check_markup($markup, $this->formatId, $langcode)->__toString(); + } + +}