-
Notifications
You must be signed in to change notification settings - Fork 68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Align rendering of image genercated by ImageShortCodeProvider with generic Image generation #596
Align rendering of image genercated by ImageShortCodeProvider with generic Image generation #596
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Template tree
templates/DBFile_image.ss
is what was used to render the mark-up for images in templates. There's a related templates/DBFile_download.ss
that renders plain file like.
I concluded that those files were misplaced. They should be following the namespace structure.
What I did
- Created matching
DBFile
andDBFile_Image
templates in the correct folder. - ImageShortcodeProvider now generates its markup by calling its own template that includes the namespaced image template.
- I moved all the mark up from the original templates and moved it to the template in the correct namespace.
- If you've customised image rendering by overriding
templates/DBFile_image.ss
, your custom template will still be used. - Because the ImageShortcodeProvider doesn't call
templates/DBFile_image.ss
at all, your custom template won't start bleeding into TinyMCE images.
- If you've customised image rendering by overriding
Things to change in CMS 6
Once this PR is merged, I'll do a follow up PR for CMS 6:
- This method in DBFile should reference the name spaced template.
templates/DBFile_image.ss
andtemplates/DBFile_download.ss
will be deleted.ImageShortcodeProvider::createImageTag()
will be deleted since it's now deprecated.
Other
I haven't added any test because the output of this thing is meant to be identical to the original.
I guess the only test I could run is try to override some templates and validate that I still get the expected output. Not sure how much value that would provide.
@@ -75,10 +75,10 @@ public static function handle_shortcode($args, $content, $parser, $shortcode, $e | |||
} | |||
|
|||
// Check if a resize is required | |||
$manipulatedRecord = $record; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old approach was basically building the <img>
tag by hand.
I'm using standard Image manipulation to assign the correct size and attribute to a manipulated image instead.
@@ -87,7 +87,7 @@ public static function handle_shortcode($args, $content, $parser, $shortcode, $e | |||
$resized = $record->ResizedImage($width, $height); | |||
// Make sure that the resized image actually returns an image | |||
if ($resized) { | |||
$src = $resized->getURL($grant); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The grant isn't needed here. Session grants apply to the original file and all its variant.
@@ -102,7 +102,7 @@ public static function handle_shortcode($args, $content, $parser, $shortcode, $e | |||
// Use all other shortcode arguments | |||
$args, | |||
// But enforce some values | |||
['id' => '', 'src' => $src] | |||
['id' => '', 'src' => ''] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The src
attirbutes need to be unset here because we want the resize picture to pick up its own URL.
|
||
$markup = self::createImageTag($attrs); | ||
// We're calling renderWith() with an explicit template in case someone wants to use a custom template | ||
$markup = $manipulatedRecord->renderWith(self::class . '_Image'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're using a ImageShortcodeProvider
specific template here. This would allow you to customise each template individually.
3c18bb9
to
186d529
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still a WIP, but here's some pointers while I have them in my mind.
The results between the old way and new way should be functionally equivalent. However, because shortcode and plain images were being rendered differently they are a couple situation where you will get slightly different results.
No size in short code
[img id=1]
would be converted to <img src="..." alt="">
Because ImageManipulation
autodetects the size of the image the output will now be <img src="..." alt="" width="123" height="123" loading="lazy">
,
In practice, it should not be possible to not provide sizes for images unless you bypass the UI. So this should be a very marginal use case.
In any case, I'm calling this an improvement. An image in this scenario would now be lazy loaded.
Height without width or Width without height
[img id=1 width="300"]
would be converted to <img src="..." alt="" width="300">
It will now render to <img src="..." alt="" width="300" height="auto">
ImageManipulation
wants to auto-detect the size of the image if it's not provided. This could lead to a wrong aspect ratio.
So I updated the shortcode to explicitly set the missing value to auto
. This should cause the browser to render the missing dimension to preserve the aspect ratio of the physical file.
It's worth pointing out that you can not lazy load the image in this situation because you can not predict the final size of the image.
Rendering missing image
ImageManipulation seem to assume that the image would always be there. It was making no effort to handle the scenario where the dimension of the image could not be determined.
I updated the logic so that if the size of an image can no be determined, ImageManipulation will now disable lazy loading on it.
In theory this could affect the scenario where you are trying to render a broken image.
@@ -1150,8 +1150,8 @@ protected function getDefaultAttributes(): array | |||
$attributes = []; | |||
if ($this->getIsImage()) { | |||
$attributes = [ | |||
'width' => $this->getWidth(), | |||
'height' => $this->getHeight(), | |||
'width' => $this->getWidth() ?: null, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When the short code was rendering an non-existent image, getWidth
and getHeight
would return 0. This would lead to a width="0"
attribute in the HTML.
Defaulting to null here fixes this.
unset($attributes['loading']); | ||
if (isset($attributes['loading'])) { | ||
// Check if the dimensions for the image have been set | ||
$dimensionsUnset = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The shortcode had some logic to make sure that if no size was provided, the image would be eager loaded. This logic made more sense here in Image manipulation than in the original one.
--
The shortcode also had some logic to handle the scenario where only the height or only the width was provided. In that scenario, the missing attribute would be omitted.
Unfortunately, ImageManipulation really wants to read the size from the actual Image. This would lead to the aspect ratio of the image being changed. So I updated ImageShortcodeProvider to explicitely set the missing value to auto
in those situation.
$this->assertEquals( | ||
sprintf( | ||
'<img src="%s" alt="">', | ||
$image->Link() | ||
), | ||
$parser->parse(sprintf('[image id=%d]', $image->ID)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Most of these test were throwing errors because ImageManipulation
was returning attributes in a different order.
ImageManipulation
does a few things a bit differently which also led to slightly different result in some context.
Because none of these test actually included messages, it was really hard to figure out what each test was meant to be doing.
So I basically ended rewriting all the test with more descriptive message and to uso a utility assert method.
|
||
$this->assertEquals( | ||
sprintf( | ||
'<img src="%s" alt="" width="50%%" height="auto">', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This confused me for a bit. The way to escape a %
in sprintf
is to double it.
* @param array $attrs Key pair values of attributes and values. | ||
* If the value is FALSE we assume that it is not present | ||
*/ | ||
private function assertImageTag(array $attrs, string $shortcode, $message = ""): void |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of testing whether the HTML generated by a specific shortcode exactly matches the expected HTML, this method will parse the HTML and check the individual attributes.
This is better because we shouldn't care about the order of the attributes. What we care about is whether the attribute is there or not and whether it has the correct value.
$nodeAttr = $nodeAttrs->getNamedItem($key); | ||
if ($value === false) { | ||
if ($nodeAttr !== null) { | ||
$this->fail("$message\nImage should not contain attribute '$key'\n$tag"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of doing straight assertion here, I decided to directly fail the test. This has a few advantages:
- When I was asserting something about the node it would give a very unpleasant about what was failing which made it difficult to figure out what was actually wrong.
- I'm building much more specific messages here:
- I'm specifying what key is expected and what its expected value is.
- I'm printing out the generated image tag.
- I'm also including the overarching message coming from the original test case to better understand what's the actual scenario we are testing.
$this->fail("$message\nImage attribute '$key' should have value '$value'\n$tag"); | ||
} | ||
} | ||
$this->assertTrue(true, 'Suppress warning about not having any assertion'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I need an actual assertion, otherwise PHPUnit thinks the test is broken.
9985383
to
ac8a75d
Compare
* @param int|null $height | ||
* @return array | ||
*/ | ||
private static function updateLoadingValue(array $args, ?int $width, ?int $height): array |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is private so no need to deprecate the method. We can just nuke it.
The builds are red because they depend on the framework PR. I've created a branch of the sink to test this https://github.com/creative-commoners/recipe-kitchen-sink/actions/runs/9265818257 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still have failing unit tests are merging the framework PR and rerunning
ac8a75d
to
4edc6d7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cannot use width/height="auto", it's invalid html
You can test that here https://www.freeformatter.com/html-validator.html
<!DOCTYPE html>
<head>
<meta charset="utf-8"/>
<title>test</title>
</head>
<body>
<img src="http://example.com/abc.jpg" alt="" width="600" height="auto" />
</body>
Bad value “auto” for attribute “height” on element “img”: Expected a digit but saw “a” instead. From line 7, column 1 to line 7, column 73 Code Extract: d>↩<body><img src="http://example.com/abc.jpg" alt="" width="600" height="auto" />↩</bo
1e88f22
to
ebbb8bf
Compare
Not sure what I was thinking there. I added a bit of logic to suppress the width/height attribute instead of outputting |
ebbb8bf
to
6089cea
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has the following integration failures in CI:
phpunit framework
- SilverStripe\Forms\Tests\HTMLEditor\HTMLEditorFieldTest::testReadonlyField
endtoend asset-admin
- vendor/silverstripe/asset-admin/tests/behat/features/insert-an-image.feature:108
endtoend cms
- vendor/silverstripe/cms/tests/behat/features/edit-a-page.feature:80
silverstripe/silverstripe-framework#11286 fixes the framework issue. |
9d529c7
to
4380406
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merge conflict
…h generic Image generation
4380406
to
cd54355
Compare
…h generic Image generation (silverstripe#596)
This PR aligns how ImageShortcodeProvider generates its HTML markup to be aligned with how regular images are generated.
This reduces complexity and makes it easy to customise the HTML markups.
Parent issue
Depends on