Skip to content
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

fix: add ability to send alternet text (html and plain) #10477

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

SebastianKrupinski
Copy link
Contributor

@SebastianKrupinski SebastianKrupinski commented Dec 11, 2024

Resolves #10415

Summary

This modifies the local_message table and mail transmission service to support alternative text for automated messages.

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>

$outboxTable = $schema->getTable('mail_local_messages');
if ($outboxTable->hasColumn('body')) {
$outboxTable->dropColumn('body');
Copy link
Member

Choose a reason for hiding this comment

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

What will be the migration path for production instances?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did that before I realized that we use the same table for drafts, so at the time didn't think a migration was necessary.

Now, going to rename instead of dropping, then execute a sql command postSchemaChange to move the html versions to the body_html column based on the Html flag. Unless there is anything wrong with that?

Copy link
Member

Choose a reason for hiding this comment

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

Creating a new column, moving data over and dropping the old column is the correct approach 👍

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
@ChristophWurst
Copy link
Member

Modify UI to send both plain and html

I'm not a fan of this, as it introduces a lot of complexity in the frontend.

Our backend could handle plain text and HTML messages before. Leave that unchanged. Only add the option to pass text and html alternatives for the mail provider API.

@SebastianKrupinski
Copy link
Contributor Author

Our backend could handle plain text and HTML messages before. Leave that unchanged. Only add the option to pass text and html alternatives for the mail provider API.

Had no choice in this, because we Jsonize the database entity, then send it straight to the UI, the UI needs to be able handle the added database fields.

@ChristophWurst
Copy link
Member

ChristophWurst commented Dec 17, 2024

Simplified algorithm:

The client sends this:
body: bla
isHtml: true

-> you store bla into the text_html column, set text_plain null and html=true

The client sends this:
body: bla
isHtml: false

-> you store bla in the text_plain column, set text_html null and html=false.

Piping through the frontend is also fine 👍

What I'm against is pushing any kind of conversion to the client. The backend mechanism to convert HTML messages to a text alternative has to stay as-is.

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
@SebastianKrupinski SebastianKrupinski marked this pull request as ready for review December 17, 2024 23:32
@kesselb
Copy link
Contributor

kesselb commented Dec 18, 2024

If I understood Christoph's latest message correctly, the part about creating a text version from the HTML version should remain and is currently missing. As we discussed in chat, it might be worth considering whether Hamaz's fork of html2text should be used instead of Horde's text filter.

$textPart->setDescription('Plaintext Version of Message');

/*
* RFC1341: In general, user agents that compose multipart/alternative entities should place the
Copy link
Contributor

Choose a reason for hiding this comment

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

Let’s keep the comment, it provides useful context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

$alternativePart[] = $htmlPart;

/*
* Wrap the multipart/alternative parts in multipart/related when inline images are found.
Copy link
Contributor

Choose a reason for hiding this comment

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

Let’s keep the comment, it provides useful context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

/*
* For attachments wrap the body (multipart/related, multipart/alternative or text/plain) in
Copy link
Contributor

Choose a reason for hiding this comment

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

Let’s keep the comment, it provides useful context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}

/**
* A callback for Horde_Text_Filter.
*
* The purpose of this callback is to overwrite the default behaviour
Copy link
Contributor

Choose a reason for hiding this comment

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

The method is unused?

*
* @return Horde_Mime_Part
*/
public function buildPgpPart(string $content): Horde_Mime_Part {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function buildPgpPart(string $content): Horde_Mime_Part {
private function buildPgpPart(string $content): Horde_Mime_Part {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

*
* @return Horde_Mime_Part
*/
public function buildMessagePart(?string $contentPlain, ?string $contentHtml): Horde_Mime_Part {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public function buildMessagePart(?string $contentPlain, ?string $contentHtml): Horde_Mime_Part {
private function buildMessagePart(?string $contentPlain, ?string $contentHtml): Horde_Mime_Part {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

$plainPart->setContents($contentPlain);
}

if (isset($plainPart) && isset($htmlPart)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (isset($plainPart) && isset($htmlPart)) {
if (isset($plainPart, $htmlPart)) {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. Didn't even realize it can be used this way.

@@ -225,6 +225,8 @@ public function testMultipartMixedRelated() {
}

public function testMultipartAlternativeGreek() {
$this->markTestSkipped();
Copy link
Contributor

Choose a reason for hiding this comment

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

What's wrong with the test? ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was disabled because the forced plain text generation was removed.

Comment on lines 58 to 59
* @param string $contentPlain
* @param string $contentHtml
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* @param string $contentPlain
* @param string $contentHtml

Let’s remove them. They don’t add much value over the type hints and don’t align with them anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

$bodyPart->setType('text/plain');
$bodyPart->setCharset('UTF-8');
$bodyPart->setContents($content);
$messagePart = new Horde_Mime_Part();
Copy link
Contributor

Choose a reason for hiding this comment

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

I’m not a fan of the method accepting an empty contentPlain and contentHtml and then returning an empty MIME part. Conceptually, this feels off, but I don’t have a better alternative in mind yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. But the alternative was to make a separate function for all scenarios, which made it way to complex.

@ChristophWurst
Copy link
Member

If I understood Christoph's latest message correctly, the part about creating a text version from the HTML version should remain and is currently missing.

Correct. We can argue about the quality of the Horde text transformation, and change it one day, but definitely not in the scope of this PR. Let's fix one problem at a time.

@SebastianKrupinski
Copy link
Contributor Author

SebastianKrupinski commented Dec 18, 2024

If I understood Christoph's latest message correctly, the part about creating a text version from the HTML version should remain and is currently missing.

Correct. We can argue about the quality of the Horde text transformation, and change it one day, but definitely not in the scope of this PR. Let's fix one problem at a time.

So I put it back in, but there are some issues doing it this way, even if we omit how bad the the horde text transformer is. By forcing automatic plain text, this produces unexpected behavior for any automated function that sends a message.

Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
Signed-off-by: SebastianKrupinski <krupinskis05@gmail.com>
@ChristophWurst
Copy link
Member

So I put it back in, but there are some issues doing it this way, even if we omit how bad the the horde text transformer is. By forcing automatic plain text, this produces unexpected behavior for any automated function that sends a message.

Dear @SebastianKrupinski, I think there is a discrepancy in goals.

The mail providers feature has broken emails that were previously sent through the system mailer, where the dav app has provided both HTML and the text alternatives. The mail app only works with one of the two types, and if the user writes an HTML message, it automagically generates a text alternative via Horde.

The goal of your fix should be to allow mail provider senders to either send a plain text message, an html message, or an alternative message with plain and html. In all cases the contents will be provided by the caller.
The mail app handling will stay the same. If the user sends a plain text message, it will be sent as-is. If the user sends a HTML message, Horde will also generate a plain text alternative.

I hope this helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: 🏗️ In progress
Development

Successfully merging this pull request may close these issues.

Calendar invites sent through mail provider have bad alternative text
4 participants