diff --git a/core/core.services.yml b/core/core.services.yml index 2dff154d821bc9f6ff3ddca52b2081a09b633cbd..6be6109fb26ab6dfdb314fce49b9cdb11cb26493 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -1832,6 +1832,7 @@ services: arguments: ['@current_user', '@path.current', '@path.matcher', '@language_manager'] response_filter.rss.cdata: class: Drupal\Core\EventSubscriber\RssResponseCdata + arguments: ['@config.factory', '@renderer'] response_filter.rss.relative_url: class: Drupal\Core\EventSubscriber\RssResponseRelativeUrlFilter messenger: diff --git a/core/lib/Drupal/Core/EventSubscriber/RssResponseCdata.php b/core/lib/Drupal/Core/EventSubscriber/RssResponseCdata.php index 4c3c88726d0bc54a246e92d27ac2926742d5c73b..87dfa6d65d3d07da9d547521081c1e6439861ea1 100644 --- a/core/lib/Drupal/Core/EventSubscriber/RssResponseCdata.php +++ b/core/lib/Drupal/Core/EventSubscriber/RssResponseCdata.php @@ -2,7 +2,8 @@ namespace Drupal\Core\EventSubscriber; -use Drupal\Component\Utility\Xss; +use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Render\RendererInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\Event\ResponseEvent; use Symfony\Component\HttpKernel\KernelEvents; @@ -12,6 +13,11 @@ */ class RssResponseCdata implements EventSubscriberInterface { + public function __construct( + protected readonly ConfigFactoryInterface $configFactory, + protected readonly RendererInterface $renderer, + ) {} + /** * Wraps RSS descriptions in CDATA. * @@ -49,13 +55,20 @@ protected function wrapDescriptionCdata(string $rss_markup): string|false { if ($errors) { return $rss_markup; } + $config = $this->configFactory->get('system.rss'); + $format = $config->get('items.text_format') ?: 'basic_html'; foreach ($rss_dom->getElementsByTagName('item') as $item) { foreach ($item->getElementsByTagName('description') as $node) { $html_markup = $node->nodeValue; if (!empty($html_markup)) { - $html_markup = Xss::filter($html_markup, ['a', 'abbr', 'acronym', 'address', 'b', 'bdo', 'big', 'blockquote', 'br', 'caption', 'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dl', 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'ins', 'kbd', 'li', 'ol', 'p', 'pre', 'q', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'ul', 'var']); - $new_node = $rss_dom->createCDATASection($html_markup); + $processed = [ + '#type' => 'processed_text', + '#text' => $html_markup, + '#format' => $format, + ]; + $markup = $this->renderer->renderInIsolation($processed); + $new_node = $rss_dom->createCDATASection($markup); $node->replaceChild($new_node, $node->firstChild); } } diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml index 0c7cfac313c3b9e2f21f2127562f7d952f78c643..60c503ef038b0970bd1391f823a956f585f4662d 100644 --- a/core/modules/system/config/schema/system.schema.yml +++ b/core/modules/system/config/schema/system.schema.yml @@ -246,6 +246,9 @@ system.rss: type: mapping label: 'Feed items' mapping: + text_format: + type: string + label: 'Feed text format' view_mode: type: string label: 'Feed content' diff --git a/core/modules/system/src/Form/RssFeedsForm.php b/core/modules/system/src/Form/RssFeedsForm.php index 72617220f2c0edcf1462da1c29986d35a38c4531..e507a2206e18a40307e064e5bc9657dd0ca00526 100644 --- a/core/modules/system/src/Form/RssFeedsForm.php +++ b/core/modules/system/src/Form/RssFeedsForm.php @@ -4,6 +4,8 @@ use Drupal\Core\Form\ConfigFormBase; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Session\AccountProxyInterface; +use Symfony\Component\DependencyInjection\ContainerInterface; /** * Configure RSS settings for this site. @@ -12,6 +14,19 @@ */ class RssFeedsForm extends ConfigFormBase { + public function __construct( + protected readonly AccountProxyInterface $currentUser, + ) {} + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container): static { + return new static( + $container->get('current_user') + ); + } + /** * {@inheritdoc} */ @@ -30,6 +45,8 @@ protected function getEditableConfigNames() { * {@inheritdoc} */ public function buildForm(array $form, FormStateInterface $form_state) { + $text_formats = $this->getTextFormats(); + $form['feed_view_mode'] = [ '#type' => 'select', '#title' => $this->t('Feed content'), @@ -42,7 +59,34 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#description' => $this->t('Global setting for the default display of content items in each feed.'), ]; + $form['text_format'] = [ + '#type' => 'select', + '#title' => $this->t('Text Format'), + '#config_target' => 'system.rss:items.text_format', + '#description' => $this->t('Choose a text format to apply to RSS feeds.'), + '#options' => $text_formats, + '#required' => TRUE, + '#default' => 'basic_html', + ]; + return parent::buildForm($form, $form_state); } + /** + * Helper function to get available text formats. + * + * @return array + * An array of text formats. + */ + protected function getTextFormats() { + $available_formats = []; + foreach (filter_formats() as $format) { + // Check if the current user has access to use this format. + if ($format->status() && $format->access('use', $this->currentUser)) { + $available_formats[$format->id()] = $format->label(); + } + } + return $available_formats; + } + } diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/RssResponseCdataTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/RssResponseCdataTest.php index ccacafc20645ee03b2481a288d973bddbc1bd5fb..2c5884b48208d9145c0cbce21afadc6f4478ed86 100644 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/RssResponseCdataTest.php +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/RssResponseCdataTest.php @@ -4,7 +4,9 @@ namespace Drupal\Tests\Core\EventSubscriber; +use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\EventSubscriber\RssResponseCdata; +use Drupal\Core\Render\RendererInterface; use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -131,8 +133,11 @@ public function testOnResponse(string $content, string $expected_content): void 'Content-Type' => 'application/rss+xml', ]) ); - - $url_filter = new RssResponseCdata(); + $configFactoryMock = $this->createMock(ConfigFactoryInterface::class); + // Mock the RendererInterface. + $rendererMock = $this->createMock(RendererInterface::class); + $rendererMock->method('render')->willReturn('<rendered output>'); + $url_filter = new RssResponseCdata($configFactoryMock, $rendererMock); $url_filter->onResponse($event); $this->assertEquals($expected_content, $event->getResponse()->getContent());