Opened 7 years ago
Closed 7 years ago
#8003 closed enhancement (fixed)
Setting Reply-To and From causes excessive database overhead in cases where addresses don't match a user
| Reported by: |
|
Owned by: |
|
|---|---|---|---|
| Milestone: | 5.0.0 | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Emails | Keywords: | has-patch dev-feedback |
| Cc: |
Description
When setting the Reply-To and From headers of an outgoing email, BP_Email uses the BP_Email_Recipient class, which does a WP user lookup:
https://buddypress.trac.wordpress.org/browser/tags/3.2.0/src/bp-core/classes/class-bp-email.php?marks=698#L688
https://buddypress.trac.wordpress.org/browser/tags/3.2.0/src/bp-core/classes/class-bp-email.php?marks=770#L759
If the installation uses a From or Reply-To address that does not match a WP user account, the WP user lookup will fail. And because of WP's get_user_by() internals, failed lookups are not cached. https://core.trac.wordpress.org/browser/tags/4.9.8/src/wp-includes/class-wp-user.php#L214
One or two extra database queries is not a serious problem, but in cases where many emails are sent in a single request - as in BuddyPress Group Email Subscription, or as when the group admin checks "notify members of this change" - the result can be dozens or hundreds of additional database hits. See https://redmine.gc.cuny.edu/issues/10720 for a real-world case.
I imagine it's very common, especially for larger installations, to use a From and Reply-To address on outgoing emails that does not correspond to a WP user. There are many use cases: custom Reply-To for reply-by-email or for bounce handling; an inbox that is shared by members of a support team; a no-reply inbox.
A few suggested approaches:
- Don't use
BP_Email_Recipientinset_replyto()andset_from(). Aside from general DRY principles, it looks like the primary purpose for this class in the case of *recipients* is to validate email addresses and to get canonical display names for the "To" field. These motivations don't apply, or apply differently, in the case of email *senders*. Specifically, the$from_addressand$from_nameare trusted values that go through WP filterswp_mail_fromandwp_mail_from_name, so it's not clear that the extra lookup is necessary. Skipping the use ofBP_Email_Recipientin this case would mean a non-trivial patch, sinceBP_Emailexpects theBP_Email_Recipientinterface. We could, for example, introduce aBP_Email_Userinterface that is implemented by bothBP_Email_RecipientandBP_Email_Sender.
- Provide a short-circuit filter in
BP_Email_Recipientthat allows plugins or sites to skip the database lookup. For example, something near the beginning of https://buddypress.trac.wordpress.org/browser/tags/3.2.0/src/bp-core/classes/class-bp-email-recipient.php#L46; a plugin would then be able to filter, and if a certain email address is passed, then it could send back a set of dummy values, or something like that.
I'm happy to work up a patch, but was hoping first to get some architectural feedback from @djpaul.
Attachments (2)
Change History (11)
#2
@
7 years ago
Good spot with this!
it looks like the primary purpose for this class in the case of *recipients* is to validate email addresses and to get canonical display names for the "To" field.
Yep.
I'm happy to work up a patch, but was hoping first to get some architectural feedback from @djpaul.
I think the better solution is to convert BP_Email_Recipient to an interface, and create classes to represent those likely to have a WordPress user account, and those not, like you suggest.
I don't like the idea of adding a filter or optional function parameter to control the behaviour because it goes against the email API design's intentions.
If @boonebgorges you want a fix for 4.0 but don't have time or desire to do the full interface rewrite, I think in set_replyto() etc that if we pass null to the BP_Email_Recipient constructor for $email_or_user, $this->address will be empty, which should avoid the get_user_by('email') lookup in the get_user() method. We can then manually set the class' address and name properties manually.
#3
@
7 years ago
- Milestone changed from Under Consideration to Up Next
Thanks for the quick feedback, @DJPaul.
IMO, no rush to sneak a quick fix into 4.0. If I'm the first person to see this issue in the wild, then it won't hurt to let it slide for another release.
I've attached a quick patch that adds the following:
- An interface
BP_Email_Address, which describes classes withget_name(),get_address(), and their correspondingsetters() - An abstract base class
BP_Email_Personthat implementsBP_Email_Addressand has some default logic (and filters) for these methods - A new
BP_Email_Senderthat extends (without modifying)BP_Email_Person. This class is then used for setting up the Reply-To and From fields inBP_Email BP_Email_Recipientnow extendsBP_Email_Person. This took just a few small changes. I've converted theget_address()etc methods to wrappers for the parent methods, in order to maintain backward compatibility with the_recipient_filters.
All this (especially the interface) might be a little overarchitected given that BP doesn't currently use type-hinting or dependency injection or other OOP techniques that would necessitate an actual interface. But it does give us a framework for introducing that kind of stuff into the BP_Email stack in the future. Alternatively, we could scrap the interface, and just have an abstract base class.
#4
@
7 years ago
Patch is OK.
I don’t prefer the Person part of BP_Email_Person class name, but don’t have a better suggestion. I’m not sure if User or Member are better or worse. Mostly, it’s that we don’t use Person anywhere else, and nothing really ties an address to a person exactly.
#5
@
7 years ago
I'm not a big fan of the 'Person' terminology either, but "member" and "user" already have specific meanings in WP/BP, meanings that we are specifically trying to avoid (since From etc may not point to any "member" or "user" in the BP/WP sense). I suppose that, strictly speaking, what From and Reply-To and To and CC and BCC and others all have in common is that they are email addresses, so maybe the nomenclature should reflect that instead.
#7
@
7 years ago
Circling back to this ticket, I think that Participant may be a more descriptive and less objectionable term than Person. Let's ship it.
I've opted for a slightly, different approach. Keep
BP_Email_Recipientthe way it is, but disable user lookups for theReply-ToandFromemail addresses.See
8003.disable-user-lookup.patch.