Opened 6 years ago
Closed 6 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: | boonebgorges | Owned by: | DJPaul |
---|---|---|---|
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_Recipient
inset_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_address
and$from_name
are trusted values that go through WP filterswp_mail_from
andwp_mail_from_name
, so it's not clear that the extra lookup is necessary. Skipping the use ofBP_Email_Recipient
in this case would mean a non-trivial patch, sinceBP_Email
expects theBP_Email_Recipient
interface. We could, for example, introduce aBP_Email_User
interface that is implemented by bothBP_Email_Recipient
andBP_Email_Sender
.
- Provide a short-circuit filter in
BP_Email_Recipient
that 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
@
6 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
@
6 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_Person
that implementsBP_Email_Address
and has some default logic (and filters) for these methods - A new
BP_Email_Sender
that extends (without modifying)BP_Email_Person
. This class is then used for setting up the Reply-To and From fields inBP_Email
BP_Email_Recipient
now 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
@
6 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
@
6 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
@
6 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_Recipient
the way it is, but disable user lookups for theReply-To
andFrom
email addresses.See
8003.disable-user-lookup.patch
.