Commit bf8c6585 authored by Stefan Neufeind's avatar Stefan Neufeind Committed by Benni Mack
Browse files

[FEATURE] Find image resources in srcset attributes

Extend the HtmlParser to support srcset attributes, which can
be found in img tags or source tags inside of HTML 5 picture
elements, making it fully compatible with responsive images.

Change-Id: If47af154b059c0744c3d660c760fb2cc2d703aeb
Resolves: #71775
Releases: master
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/44901

Tested-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Tested-by: core-ci's avatarcore-ci <typo3@b13.com>
Tested-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
Reviewed-by: Christian Kuhn's avatarChristian Kuhn <lolli@schwarzbu.ch>
Reviewed-by: Benni Mack's avatarBenni Mack <benni@typo3.org>
parent 737af52d
......@@ -690,7 +690,8 @@ class HtmlParser
}
/**
* Prefixes the relative paths of hrefs/src/action in the tags [td,table,body,img,input,form,link,script,a] in the $content with the $main_prefix or and alternative given by $alternatives
* Prefixes the relative paths of hrefs/src/action in the tags [td,table,body,img,input,form,link,script,a]
* in the $content with the $main_prefix or and alternative given by $alternatives
*
* @param string $main_prefix Prefix string
* @param string $content HTML content
......@@ -700,7 +701,7 @@ class HtmlParser
*/
public function prefixResourcePath($main_prefix, $content, $alternatives = [], $suffix = '')
{
$parts = $this->splitTags('embed,td,table,body,img,input,form,link,script,a,param', $content);
$parts = $this->splitTags('embed,td,table,body,img,input,form,link,script,a,param,source', $content);
foreach ($parts as $k => $v) {
if ($k % 2) {
$params = $this->get_tag_attributes($v);
......@@ -708,60 +709,57 @@ class HtmlParser
$tagEnd = substr($v, -2) === '/>' ? ' />' : '>';
// The 'name' of the first tag
$firstTagName = $this->getFirstTagName($v);
$somethingDone = 0;
$prefixedRelPath = false;
$prefix = $alternatives[strtoupper($firstTagName)] ?? $main_prefix;
switch (strtolower($firstTagName)) {
case 'td':
case 'body':
case 'table':
$src = $params[0]['background'];
if ($src) {
if (isset($params[0]['background'])) {
$params[0]['background'] = $this->prefixRelPath($prefix, $params[0]['background'], $suffix);
$somethingDone = 1;
$prefixedRelPath = true;
}
break;
case 'img':
case 'input':
case 'script':
case 'embed':
$src = $params[0]['src'];
if ($src) {
if (isset($params[0]['src'])) {
$params[0]['src'] = $this->prefixRelPath($prefix, $params[0]['src'], $suffix);
$somethingDone = 1;
$prefixedRelPath = true;
}
break;
case 'link':
case 'a':
$src = $params[0]['href'];
if ($src) {
if (isset($params[0]['href'])) {
$params[0]['href'] = $this->prefixRelPath($prefix, $params[0]['href'], $suffix);
$somethingDone = 1;
$prefixedRelPath = true;
}
break;
case 'form':
$src = $params[0]['action'];
if ($src) {
if (isset($params[0]['action'])) {
$params[0]['action'] = $this->prefixRelPath($prefix, $params[0]['action'], $suffix);
$somethingDone = 1;
$prefixedRelPath = true;
}
break;
case 'param':
$test = $params[0]['name'];
if ($test && $test === 'movie') {
if ($params[0]['value']) {
if (isset($params[0]['name']) && $params[0]['name'] === 'movie' && isset($params[0]['value'])) {
$params[0]['value'] = $this->prefixRelPath($prefix, $params[0]['value'], $suffix);
$somethingDone = 1;
$prefixedRelPath = true;
}
break;
case 'source':
if (isset($params[0]['srcset'])) {
$srcsetImagePaths = GeneralUtility::trimExplode(',', $params[0]['srcset']);
for ($i = 0; $i < count($srcsetImagePaths); $i++) {
$srcsetImagePaths[$i] = $this->prefixRelPath($prefix, $srcsetImagePaths[$i], $suffix);
}
$params[0]['srcset'] = implode(', ', $srcsetImagePaths);
$prefixedRelPath = true;
}
break;
}
if ($somethingDone) {
if ($prefixedRelPath) {
$tagParts = preg_split('/\\s+/s', $v, 2);
$tagParts[1] = $this->compileTagAttribs($params[0], $params[1]);
$parts[$k] = '<' . trim(strtolower($firstTagName) . ' ' . $tagParts[1]) . $tagEnd;
......@@ -800,7 +798,7 @@ class HtmlParser
if ($srcVal[0] !== '/' && $srcVal[0] !== '#') {
$urlParts = parse_url($srcVal);
// Only prefix URLs without a scheme
if (!$urlParts['scheme']) {
if (!isset($urlParts['scheme'])) {
$srcVal = $prefix . $srcVal . $suffix;
}
}
......
.. include:: ../../Includes.txt
==========================================
Feature: #71775 - HtmlParser allows srcset
==========================================
See :issue:`71775`
Description
===========
The :php:`HtmlParser` - most commonly used when rendering RTE fields
in the frontend - now handles the :html:`srcset` attribute.
A casual use case for this are responsive images:
.. code-block:: html
<picture>
<source media="(max-width: 799px)" srcset="small-image.jpg">
<source media="(min-width: 800px)" srcset="larger-image.jpg">
</picture>
Impact
======
Using :html:`source` tag with :html:`srcset` attribute is allowed and
:html:`srcset` values are prefixed correctly.
.. index:: Frontend, RTE, ext:core
......@@ -663,4 +663,88 @@ class HtmlParserTest extends UnitTestCase
$this->subject->stripEmptyTags($content, $tagList, $treatNonBreakingSpaceAsEmpty)
);
}
public function prefixResourcePathDataProvider(): array
{
return [
'<td background="test.png">' => [
'<table><tr><td background="test.png">Test</td></tr></table>',
'/prefix/',
'<table><tr><td background="/prefix/test.png">Test</td></tr></table>',
],
'<table background="test.png">' => [
'<table background="test.png"><tr><td>Test</td></tr></table>',
'/prefix/',
'<table background="/prefix/test.png"><tr><td>Test</td></tr></table>',
],
'<body background="test.png">' => [
'<body background="test.png">',
'/prefix/',
'<body background="/prefix/test.png">',
],
'<img src="test.png">' => [
'<img src="test.png">',
'/prefix/',
'<img src="/prefix/test.png">',
],
'<input src="test.png">' => [
'<input type="image" src="test.png"/>',
'/prefix/',
'<input type="image" src="/prefix/test.png" />',
],
'<script src="test.js">' => [
'<script src="test.js"/>',
'/assets/',
'<script src="/assets/test.js" />',
],
'<embed src="test.swf">' => [
'<embed src="test.swf"></embed>',
'/media/',
'<embed src="/media/test.swf"></embed>',
],
'<a href="test.pdf">' => [
'<a href="something/test.pdf">Test PDF</a>',
'/',
'<a href="/something/test.pdf">Test PDF</a>',
],
'<link href="test.css">' => [
'<link rel="stylesheet" type="text/css" href="theme.css">',
'/css/',
'<link rel="stylesheet" type="text/css" href="/css/theme.css">',
],
'<form action="test/">' => [
'<form action="test/"></form>',
'/',
'<form action="/test/"></form>',
],
'<param name="movie" value="test.mp4">' => [
'<param name="movie" value="test.mp4" />',
'/test/',
'<param name="movie" value="/test/test.mp4" />'
],
'<source srcset="large.jpg">' => [
'<source srcset="large.jpg">',
'/assets/',
'<source srcset="/assets/large.jpg">',
],
'<source media="(min-width: 56.25em)" srcset="large.jpg 1x, large@2x.jpg 2x">' => [
'<source media="(min-width: 56.25em)" srcset="large.jpg 1x, large@2x.jpg 2x">',
'/assets/',
'<source media="(min-width: 56.25em)" srcset="/assets/large.jpg 1x, /assets/large@2x.jpg 2x">',
],
];
}
/**
* @test
* @dataProvider prefixResourcePathDataProvider
*/
public function prefixResourcePathTest(string $content, string $prefix, string $expectedResult): void
{
self::assertSame(
$expectedResult,
$this->subject->prefixResourcePath($prefix, $content)
);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment