@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Expand aggregate email recipients prior to multiplexing

Summary:
Ref T4361. Before we figure out which To/CC are addressable, try to expand To/CC. Specifically, the supported expansion right now is project PHIDs expanding to all their members.

Because of the way multiplexing works, we have to do this in two places: explicitly in `multiplexMail()`, and when sending mail that wasn't multiplexed. This is messy; eventually we can get rid of it (after ApplicationTransactions are everywhere).

This has some rough edges, but should basically give us what we need to make stuff like projects mailable. Particularly, it deals with most issues in D7436:

- I got around the resolution/multiplexing issue by resolving aggregate mailables separately from mailable actors.
- We get to keep the Project PHID as a To/CC/Reviewer/Whatever until the last second.
- Users won't get two emails for being a CC and also a member of a CC'd project.
- We can degrade to the list stuff this way if we want, by having the project aggregate yield a single list PHID.

Test Plan: Added a comment to a revision with a project reviewer, got mail to all the project's members.

Reviewers: btrahan

Reviewed By: btrahan

CC: aran

Maniphest Tasks: T4361

Differential Revision: https://secure.phabricator.com/D8117

+160 -2
+2
src/__phutil_library_map__.php
··· 1648 1648 'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/__tests__/PhabricatorMetaMTAMailTestCase.php', 1649 1649 'PhabricatorMetaMTAMailgunReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php', 1650 1650 'PhabricatorMetaMTAMailingList' => 'applications/mailinglists/storage/PhabricatorMetaMTAMailingList.php', 1651 + 'PhabricatorMetaMTAMemberQuery' => 'applications/metamta/query/PhabricatorMetaMTAMemberQuery.php', 1651 1652 'PhabricatorMetaMTAPermanentFailureException' => 'applications/metamta/exception/PhabricatorMetaMTAPermanentFailureException.php', 1652 1653 'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/PhabricatorMetaMTAReceivedMail.php', 1653 1654 'PhabricatorMetaMTAReceivedMailProcessingException' => 'applications/metamta/exception/PhabricatorMetaMTAReceivedMailProcessingException.php', ··· 4325 4326 0 => 'PhabricatorMetaMTADAO', 4326 4327 1 => 'PhabricatorPolicyInterface', 4327 4328 ), 4329 + 'PhabricatorMetaMTAMemberQuery' => 'PhabricatorQuery', 4328 4330 'PhabricatorMetaMTAPermanentFailureException' => 'Exception', 4329 4331 'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO', 4330 4332 'PhabricatorMetaMTAReceivedMailProcessingException' => 'Exception',
+69
src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php
··· 1 + <?php 2 + 3 + /** 4 + * Expands aggregate mail recipients into their component mailables. For 5 + * example, a project currently expands into all of its members. 6 + */ 7 + final class PhabricatorMetaMTAMemberQuery extends PhabricatorQuery { 8 + 9 + private $phids = array(); 10 + private $viewer; 11 + 12 + public function setViewer(PhabricatorUser $viewer) { 13 + $this->viewer = $viewer; 14 + return $this; 15 + } 16 + 17 + public function getViewer() { 18 + return $this->viewer; 19 + } 20 + 21 + public function withPHIDs(array $phids) { 22 + $this->phids = $phids; 23 + return $this; 24 + } 25 + 26 + public function execute() { 27 + $phids = array_fuse($this->phids); 28 + $actors = array(); 29 + $type_map = array(); 30 + foreach ($phids as $phid) { 31 + $type_map[phid_get_type($phid)][] = $phid; 32 + } 33 + 34 + // TODO: Generalize this somewhere else. 35 + 36 + $results = array(); 37 + foreach ($type_map as $type => $phids) { 38 + switch ($type) { 39 + case PhabricatorProjectPHIDTypeProject::TYPECONST: 40 + // TODO: For now, project members are always on the "mailing list" 41 + // implied by the project, but we should differentiate members and 42 + // subscribers (i.e., allow you to unsubscribe from mail about 43 + // a project). 44 + 45 + $projects = id(new PhabricatorProjectQuery()) 46 + ->setViewer($this->getViewer()) 47 + ->needMembers(true) 48 + ->withPHIDs($phids) 49 + ->execute(); 50 + 51 + $projects = mpull($projects, null, 'getPHID'); 52 + foreach ($phids as $phid) { 53 + $project = idx($projects, $phid); 54 + if (!$project) { 55 + $results[$phid] = array(); 56 + } else { 57 + $results[$phid] = $project->getMemberPHIDs(); 58 + } 59 + } 60 + break; 61 + default: 62 + break; 63 + } 64 + } 65 + 66 + return $results; 67 + } 68 + 69 + }
+39
src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
··· 198 198 } 199 199 } 200 200 201 + // TODO: This is pretty messy. We should really be doing all of this 202 + // multiplexing in the task queue, but that requires significant rewriting 203 + // in the general case. ApplicationTransactions can do it fairly easily, 204 + // but other mail sites currently can not, so we need to support this 205 + // junky version until they catch up and we can swap things over. 206 + 207 + $to_handles = $this->expandRecipientHandles($to_handles); 208 + $cc_handles = $this->expandRecipientHandles($cc_handles); 209 + 201 210 $tos = mpull($to_handles, null, 'getPHID'); 202 211 $ccs = mpull($cc_handles, null, 'getPHID'); 203 212 ··· 219 228 $body .= $this->getRecipientsSummary($to_handles, $cc_handles); 220 229 221 230 foreach ($recipients as $phid => $recipient) { 231 + 232 + 222 233 $mail = clone $mail_template; 223 234 if (isset($to_handles[$phid])) { 224 235 $mail->addTos(array($phid)); ··· 330 341 } 331 342 332 343 return rtrim($body); 344 + } 345 + 346 + private function expandRecipientHandles(array $handles) { 347 + if (!$handles) { 348 + return array(); 349 + } 350 + 351 + $phids = mpull($handles, 'getPHID'); 352 + $map = id(new PhabricatorMetaMTAMemberQuery()) 353 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 354 + ->withPHIDs($phids) 355 + ->execute(); 356 + 357 + $results = array(); 358 + foreach ($phids as $phid) { 359 + if (isset($map[$phid])) { 360 + foreach ($map[$phid] as $expanded_phid) { 361 + $results[$expanded_phid] = $expanded_phid; 362 + } 363 + } else { 364 + $results[$phid] = $phid; 365 + } 366 + } 367 + 368 + return id(new PhabricatorHandleQuery()) 369 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 370 + ->withPHIDs($results) 371 + ->execute(); 333 372 } 334 373 335 374 }
+50 -2
src/applications/metamta/storage/PhabricatorMetaMTAMail.php
··· 19 19 20 20 private $excludePHIDs = array(); 21 21 private $overrideNoSelfMail = false; 22 + private $recipientExpansionMap; 22 23 23 24 public function __construct() { 24 25 ··· 379 380 $mailer->addReplyTo($value, $reply_to_name); 380 381 break; 381 382 case 'to': 382 - $to_actors = array_select_keys($deliverable_actors, $value); 383 + $to_phids = $this->expandRecipients($value); 384 + $to_actors = array_select_keys($deliverable_actors, $to_phids); 383 385 $add_to = array_merge( 384 386 $add_to, 385 387 mpull($to_actors, 'getEmailAddress')); ··· 388 390 $add_to = array_merge($add_to, $value); 389 391 break; 390 392 case 'cc': 391 - $cc_actors = array_select_keys($deliverable_actors, $value); 393 + $cc_phids = $this->expandRecipients($value); 394 + $cc_actors = array_select_keys($deliverable_actors, $cc_phids); 392 395 $add_cc = array_merge( 393 396 $add_cc, 394 397 mpull($cc_actors, 'getEmailAddress')); ··· 685 688 $this->getToPHIDs(), 686 689 $this->getCcPHIDs()); 687 690 691 + $this->loadRecipientExpansions($actor_phids); 692 + $actor_phids = $this->expandRecipients($actor_phids); 693 + 688 694 return $this->loadActors($actor_phids); 695 + } 696 + 697 + private function loadRecipientExpansions(array $phids) { 698 + $expansions = id(new PhabricatorMetaMTAMemberQuery()) 699 + ->setViewer(PhabricatorUser::getOmnipotentUser()) 700 + ->withPHIDs($phids) 701 + ->execute(); 702 + 703 + $this->recipientExpansionMap = $expansions; 704 + 705 + return $this; 706 + } 707 + 708 + /** 709 + * Expand a list of recipient PHIDs (possibly including aggregate recipients 710 + * like projects) into a deaggregated list of individual recipient PHIDs. 711 + * For example, this will expand project PHIDs into a list of the project's 712 + * members. 713 + * 714 + * @param list<phid> List of recipient PHIDs, possibly including aggregate 715 + * recipients. 716 + * @return list<phid> Deaggregated list of mailable recipients. 717 + */ 718 + private function expandRecipients(array $phids) { 719 + if ($this->recipientExpansionMap === null) { 720 + throw new Exception( 721 + pht( 722 + 'Call loadRecipientExpansions() before expandRecipients()!')); 723 + } 724 + 725 + $results = array(); 726 + foreach ($phids as $phid) { 727 + if (!isset($this->recipientExpansionMap[$phid])) { 728 + $results[$phid] = $phid; 729 + } else { 730 + foreach ($this->recipientExpansionMap[$phid] as $recipient_phid) { 731 + $results[$recipient_phid] = $recipient_phid; 732 + } 733 + } 734 + } 735 + 736 + return array_keys($results); 689 737 } 690 738 691 739 private function filterDeliverableActors(array $actors) {