Skip to content

[Mime] Fix inline parts when added via attachPart() #46963

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

Merged
merged 1 commit into from
Jul 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 19 additions & 7 deletions src/Symfony/Component/Mime/Email.php
Original file line number Diff line number Diff line change
Expand Up @@ -507,25 +507,37 @@ private function prepareParts(): ?array
$names = array_filter(array_unique(array_merge($names[2], $names[3])));
}

// usage of reflection is a temporary workaround for missing getters that will be added in 6.2
$dispositionRef = new \ReflectionProperty(TextPart::class, 'disposition');
$dispositionRef->setAccessible(true);
$nameRef = new \ReflectionProperty(TextPart::class, 'name');
$nameRef->setAccessible(true);
$attachmentParts = $inlineParts = [];
foreach ($this->attachments as $attachment) {
$part = $this->createDataPart($attachment);
if (isset($attachment['part'])) {
$attachment['name'] = $nameRef->getValue($part);
}

foreach ($names as $name) {
if (isset($attachment['part'])) {
continue;
}
if ($name !== $attachment['name']) {
continue;
}
if (isset($inlineParts[$name])) {
continue 2;
}
$attachment['inline'] = true;
$inlineParts[$name] = $part = $this->createDataPart($attachment);
$part->setDisposition('inline');
$html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html);
$part->setName($part->getContentId());
continue 2;

break;
}

if ('inline' === $dispositionRef->getValue($part)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, we always inline files when set explicitly as inline.

$inlineParts[$attachment['name']] = $part;
} else {
$attachmentParts[] = $part;
}
$attachmentParts[] = $this->createDataPart($attachment);
}
if (null !== $htmlPart) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
Expand Down
102 changes: 89 additions & 13 deletions src/Symfony/Component/Mime/Tests/EmailTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -246,76 +246,116 @@ public function testGetBody()
$this->assertEquals($text, $e->getBody());
}

public function testGenerateBody()
public function testGenerateBodyWithTextOnly()
{
$text = new TextPart('text content');
$html = new TextPart('html content', 'utf-8', 'html');
$att = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r'));
$img = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif');

$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->text('text content');
$this->assertEquals($text, $e->getBody());
$this->assertEquals('text content', $e->getTextBody());
}

public function testGenerateBodyWithHtmlOnly()
{
$html = new TextPart('html content', 'utf-8', 'html');
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html('html content');
$this->assertEquals($html, $e->getBody());
$this->assertEquals('html content', $e->getHtmlBody());
}

public function testGenerateBodyWithTextAndHtml()
{
$text = new TextPart('text content');
$html = new TextPart('html content', 'utf-8', 'html');
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html('html content');
$e->text('text content');
$this->assertEquals(new AlternativePart($text, $html), $e->getBody());
}

public function testGenerateBodyWithTextAndHtmlNotUtf8()
{
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html('html content', 'iso-8859-1');
$e->text('text content', 'iso-8859-1');
$this->assertEquals('iso-8859-1', $e->getTextCharset());
$this->assertEquals('iso-8859-1', $e->getHtmlCharset());
$this->assertEquals(new AlternativePart(new TextPart('text content', 'iso-8859-1'), new TextPart('html content', 'iso-8859-1', 'html')), $e->getBody());
}

public function testGenerateBodyWithTextContentAndAttachedFile()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->attach($file);
$e->text('text content');
$this->assertEquals(new MixedPart($text, $att), $e->getBody());
$this->assertEquals(new MixedPart($text, $filePart), $e->getBody());
}

public function testGenerateBodyWithHtmlContentAndAttachedFile()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->attach($file);
$e->html('html content');
$this->assertEquals(new MixedPart($html, $att), $e->getBody());
$this->assertEquals(new MixedPart($html, $filePart), $e->getBody());
}

public function testGenerateBodyWithAttachedFileOnly()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->attach($file);
$this->assertEquals(new MixedPart($att), $e->getBody());
$this->assertEquals(new MixedPart($filePart), $e->getBody());
}

public function testGenerateBodyWithTextAndHtmlContentAndAttachedFile()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html('html content');
$e->text('text content');
$e->attach($file);
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att), $e->getBody());
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $filePart), $e->getBody());
}

public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImageNotReferenced()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html('html content');
$e->text('text content');
$e->attach($file);
$e->attach($image, 'test.gif');
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $att, $img), $e->getBody());
$this->assertEquals(new MixedPart(new AlternativePart($text, $html), $filePart, $imagePart), $e->getBody());
}

public function testGenerateBodyWithTextAndAttachedFileAndAttachedImageNotReferenced()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->text('text content');
$e->attach($file);
$e->attach($image, 'test.gif');
$this->assertEquals(new MixedPart($text, $att, $img), $e->getBody());
$this->assertEquals(new MixedPart($text, $filePart, $imagePart), $e->getBody());
}

public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImageNotReferencedViaCid()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html($content = 'html content <img src="test.gif">');
$e->text('text content');
$e->attach($file);
$e->attach($image, 'test.gif');
$fullhtml = new TextPart($content, 'utf-8', 'html');
$this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $att, $img), $e->getBody());
$this->assertEquals(new MixedPart(new AlternativePart($text, $fullhtml), $filePart, $imagePart), $e->getBody());
}

public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImageReferencedViaCid()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html($content = 'html content <img src="cid:test.gif">');
$e->text('text content');
Expand All @@ -325,12 +365,35 @@ public function testGenerateBody()
$this->assertInstanceOf(MixedPart::class, $body);
$this->assertCount(2, $related = $body->getParts());
$this->assertInstanceOf(RelatedPart::class, $related[0]);
$this->assertEquals($att, $related[1]);
$this->assertEquals($filePart, $related[1]);
$this->assertCount(2, $parts = $related[0]->getParts());
$this->assertInstanceOf(AlternativePart::class, $parts[0]);
$generatedHtml = $parts[0]->getParts()[1];
$this->assertStringContainsString('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody());
}

public function testGenerateBodyWithTextAndHtmlAndAttachedFileAndAttachedImagePartAsInlineReferencedViaCid()
{
[$text, $html, $filePart, $file, $imagePart, $image] = $this->generateSomeParts();
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html($content = 'html content <img src="cid:test.gif">');
$e->text('text content');
$e->attach($file);
$e->attachPart((new DataPart($image, 'test.gif'))->asInline());
$body = $e->getBody();
$this->assertInstanceOf(MixedPart::class, $body);
$this->assertCount(2, $related = $body->getParts());
$this->assertInstanceOf(RelatedPart::class, $related[0]);
$this->assertEquals($filePart, $related[1]);
$this->assertCount(2, $parts = $related[0]->getParts());
$this->assertInstanceOf(AlternativePart::class, $parts[0]);
$generatedHtml = $parts[0]->getParts()[1];
$this->assertStringContainsString('cid:'.$parts[1]->getContentId(), $generatedHtml->getBody());
}

public function testGenerateBodyWithHtmlAndInlinedImageTwiceReferencedViaCid()
{
// inline image (twice) referenced in the HTML content
$content = 'html content <img src="cid:test.gif">';
$r = fopen('php://memory', 'r+', false);
fwrite($r, $content);
Expand All @@ -339,6 +402,7 @@ public function testGenerateBody()
$e = (new Email())->from('me@example.com')->to('you@example.com');
$e->html($r);
// embedding the same image twice results in one image only in the email
$image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r');
$e->embed($image, 'test.gif');
$e->embed($image, 'test.gif');
$body = $e->getBody();
Expand All @@ -348,8 +412,19 @@ public function testGenerateBody()
$this->assertStringMatchesFormat('html content <img src=3D"cid:%s@symfony">', $parts[0]->bodyToString());
}

private function generateSomeParts(): array
{
$text = new TextPart('text content');
$html = new TextPart('html content', 'utf-8', 'html');
$filePart = new DataPart($file = fopen(__DIR__.'/Fixtures/mimetypes/test', 'r'));
$imagePart = new DataPart($image = fopen(__DIR__.'/Fixtures/mimetypes/test.gif', 'r'), 'test.gif');

return [$text, $html, $filePart, $file, $imagePart, $image];
}

public function testAttachments()
{
// inline part
$contents = file_get_contents($name = __DIR__.'/Fixtures/mimetypes/test', 'r');
$att = new DataPart($file = fopen($name, 'r'), 'test');
$inline = (new DataPart($contents, 'test'))->asInline();
Expand All @@ -358,6 +433,7 @@ public function testAttachments()
$e->embed($contents, 'test');
$this->assertEquals([$att, $inline], $e->getAttachments());

// inline part from path
$att = DataPart::fromPath($name, 'test');
$inline = DataPart::fromPath($name, 'test')->asInline();
$e = new Email();
Expand Down