-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Mail: New Version for Multipart solution #9500
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
Conversation
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Core Committers: Use this line as a base for the props when committing in SVN: To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
ce1c83f to
883983c
Compare
| if ( false !== stripos( $content_type, 'multipart' ) && ! empty( $boundary ) ) { | ||
| $phpmailer->addCustomHeader( sprintf( 'Content-Type: %s; boundary="%s"', $content_type, $boundary ) ); | ||
| } |
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 tried restoring this block of code and the tests still pass. Is that expected?
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.
In other words, should there be a test that checks for only one Content-Type header being present?
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 was the original issue. I'm not sure if this ever worked, but for long time, everyone thought this was doing something. I've been playing a lot with the headers both in other tickets and PHPMailer, and then I got back to this ticket and I noted that this was not been triggered at all on multipart, because if you check the logic above, the default condition of the switch case, is never reached (because the Content-Type is already set in the corresponding case).
If anything happens to drops into the default it is not going to be a Content-Type and for this reason, this is never going to be triggered.
So it can be safely removed, it's useless.
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 added 2111d68 to ensure there is only one header sent. Without this removed, the assertion now fails with:
1) Tests_Pluggable_wpMail::test_wp_mail_custom_boundaries
Expected only one Content-Type header to be sent. Saw:
multipart/mixed; boundary="----=_Part_4892_25692638.1192452070893"; boundary="----=_Part_4892_25692638.1192452070893"
multipart/mixed; boundary="----=_Part_4892_25692638.1192452070893"; charset=
Failed asserting that actual size 2 matches expected size 1.
| $this->assertArrayHasKey( 'Content-Type', $headers, 'Expected Content-Type header to be sent.' ); | ||
| $content_type_headers = (array) $headers['Content-Type']; | ||
| $this->assertCount( 1, $content_type_headers, "Expected only one Content-Type header to be sent. Saw:\n" . implode( "\n", $content_type_headers ) ); | ||
| $this->assertSame( 'multipart/mixed; boundary="----=_Part_4892_25692638.1192452070893"; charset=', $content_type_headers[0], 'Expected Content-Type to match.' ); |
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.
What I don't get is why the Content-Type header ends in charset=. I guess PHPMailer adds this?
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.
Gemini seems to have explained it well:
I've finished reviewing
PHPMailer.php. TheContent-Typeheader is constructed in thegetMailMIME()method, which is called bycreateHeader().In the test,
wp_mailpasses a raw MIME message to PHPMailer's$body. PHPMailer'ssetMessageType()method then determines themessage_typeas'plain'because it sees a single body, not a parsed multipart structure.For a
'plain'message,getMailMIME()constructs theContent-Typeheader as:'Content-Type: ' . $this->ContentType . '; charset=' . $this->CharSet.In
wp_mail, the providedContent-Typeheader (multipart/mixed; boundary="...") is parsed. Since there's nocharset,wp_mailsets$phpmailer->CharSetto''.Therefore, the final header becomes:
Content-Type: multipart/mixed; boundary="..."; charset=, which is what the test asserts. This is an intentional behavior in PHPMailer for pre-formatted MIME messages, and the test correctly verifies it.
And the charset is not really relevant in the top-level header for the multipart/mixed content type, since the boundary is all that matters. The actual charset is set on one of the parts.
westonruter
left a comment
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'm not an expert on mail headers, but from all I can tell this is right.
Gemini also likes it:
Here is my revised review of the changes:
This is an excellent set of changes that improves the handling of multipart
Content-Typeheaders inwp_mail()and adds robust, valuable test coverage.
src/wp-includes/pluggable.php
- Good Refactor: The logic for handling multipart
Content-Typeheaders has been simplified and made more robust. Moving away fromaddCustomHeaderto directly setting the$content_typevariable which is then passed to PHPMailer is a cleaner implementation.- Improved Consistency: The new logic to normalize the
multipart/subtype to lowercase and correctly quote the boundary enhances consistency and reduces the chance of malformed headers.
tests/phpunit/tests/pluggable/wpMail.php
- Precise Assertions: The updated assertions in
test_wp_mail_custom_boundaries()are a significant improvement. UsingassertSame()provides a much stricter and more reliable test than the previousassertStringContainsString().- Excellent Test Coverage: The two new tests,
test_wp_mail_plain_and_html()andtest_wp_mail_plain_and_html_workaround(), are great additions. They cover common, real-world scenarios for sending multipart emails.- Modern PHP: The use of a
static function(a static closure) intest_wp_mail_plain_and_html_workaround()is a nice touch. It's a modern, clean way to handle the action callback, keeping the logic scoped to the test itself. TheWP_PHPMailertype hint is also good practice.This is a high-quality contribution. The refactoring is sound, and the tests are well-written and thorough. This looks good to go.
This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in #9500 Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes #15448. git-svn-id: https://develop.svn.wordpress.org/trunk@61201 602fd350-edb4-49c9-b593-d223f7449a82
This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in WordPress/wordpress-develop#9500 Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes #15448. Built from https://develop.svn.wordpress.org/trunk@61201 git-svn-id: http://core.svn.wordpress.org/trunk@60537 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in WordPress/wordpress-develop#9500 Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes #15448. Built from https://develop.svn.wordpress.org/trunk@61201 git-svn-id: https://core.svn.wordpress.org/trunk@60537 1a063a9b-81f0-0310-95a4-ce76da25c4cd
…st PRs (#2868) ## Motivation for the change, related issues Adds support for previewing branches, not just PRs in the Import PR modal: <img width="2288" height="1500" alt="CleanShot 2025-11-05 at 15 13 58@2x" src="https://github.com/user-attachments/assets/d7efc465-43bc-4786-a66d-493d70ec60b8" /> @SirLouen asked how to preview the latest trunk trunk, not a PR, and I didn't have a good answer. This change makes it easy. ## Implementation details * Website: adds `?core-branch` and `?gutenberg-branch` Query API params to indicate which branch to preview. * Plugin proxy: Adds a `?branch=` query API param that's an alternative to `?pr=`. There's no artifact validation when previewing a branch. If the build for the most recent commit is not ready yet, the next available build is used. ## Testing Instructions (or ideally a Blueprint) Try these locally and confirm they do the right thing. You can see the WordPress version number at the bottom of wp-admin and also use the file browser to confirm the files changed in specific PRs are present in the filesystem: * http://127.0.0.1:5400/website-server/?core-branch=trunk * http://127.0.0.1:5401/website-server/?core-pr=9500 (see WordPress/wordpress-develop#9500) * http://127.0.0.1:5400/website-server/?gutenberg-branch=trunk * http://127.0.0.1:5401/website-server/?gutenberg-pr=73010 (see https://github.com/WordPress/wordpress-develop/pull/73010) --------- Co-authored-by: Copilot <[email protected]>
This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in WordPress/wordpress-develop#9500 WP:Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes https://core.trac.wordpress.org/ticket/15448. --- Merges https://core.trac.wordpress.org/changeset/61201 / WordPress/wordpress-develop@024bbfcc4c to ClassicPress.
* WP-r61201: Mail: Improve multipart message handling in `wp_mail()`. This improves how `wp_mail()` handles `Content-Type` headers for multipart messages, preventing cases where the header could be duplicated. Developed in WordPress/wordpress-develop#9500 WP:Props SirLouen, gitlost, rmccue, westi, MattyRob, bgermann, nacin, SergeyBiryukov, dd32, MikeHansenMe, Kleor, kitchin, JeffMatson, abcd95, westonruter, christinecooper, JohnVieth, dawidadach, imokweb, ayeshrajans, lakshyajeet, tusharbharti, sajjad67. Fixes https://core.trac.wordpress.org/ticket/15448. --- Merges https://core.trac.wordpress.org/changeset/61201 / WordPress/wordpress-develop@024bbfcc4c to ClassicPress. * Update callback type hint to MockPHPMailer in test Changed the type hint from WP_PHPMailer to MockPHPMailer in the $set_alt_body callback within the wpMail test. This ensures the callback matches the actual mock object used in the test environment. --------- Co-authored-by: Weston Ruter <[email protected]> CP:Props mattyrob, xxsimoxx
This is the new version that solves all the problems without having to do any upgrade + not having to modify anything in the PHPMailer part. More info and testing info below.
Trac ticket: https://core.trac.wordpress.org/ticket/15448
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket. Do not merge this Pull Request. See GitHub Pull Requests for Code Review in the Core Handbook for more details.