Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F18756933
D11329.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
35 KB
Referenced Files
None
Subscribers
None
D11329.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1282,6 +1282,7 @@
'PhabricatorApplicationTransactionInterface' => 'applications/transactions/interface/PhabricatorApplicationTransactionInterface.php',
'PhabricatorApplicationTransactionNoEffectException' => 'applications/transactions/exception/PhabricatorApplicationTransactionNoEffectException.php',
'PhabricatorApplicationTransactionNoEffectResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionNoEffectResponse.php',
+ 'PhabricatorApplicationTransactionNotificationWorker' => 'applications/transactions/worker/PhabricatorApplicationTransactionNotificationWorker.php',
'PhabricatorApplicationTransactionQuery' => 'applications/transactions/query/PhabricatorApplicationTransactionQuery.php',
'PhabricatorApplicationTransactionResponse' => 'applications/transactions/response/PhabricatorApplicationTransactionResponse.php',
'PhabricatorApplicationTransactionShowOlderController' => 'applications/transactions/controller/PhabricatorApplicationTransactionShowOlderController.php',
@@ -1937,6 +1938,7 @@
'PhabricatorManiphestApplication' => 'applications/maniphest/application/PhabricatorManiphestApplication.php',
'PhabricatorManiphestConfigOptions' => 'applications/maniphest/config/PhabricatorManiphestConfigOptions.php',
'PhabricatorManiphestTaskTestDataGenerator' => 'applications/maniphest/lipsum/PhabricatorManiphestTaskTestDataGenerator.php',
+ 'PhabricatorManiphestTransactionNotificationWorker' => 'applications/maniphest/worker/PhabricatorManiphestTransactionNotificationWorker.php',
'PhabricatorMarkupCache' => 'applications/cache/storage/PhabricatorMarkupCache.php',
'PhabricatorMarkupEngine' => 'infrastructure/markup/PhabricatorMarkupEngine.php',
'PhabricatorMarkupInterface' => 'infrastructure/markup/PhabricatorMarkupInterface.php',
@@ -4469,6 +4471,7 @@
'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory',
'PhabricatorApplicationTransactionNoEffectException' => 'Exception',
'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse',
+ 'PhabricatorApplicationTransactionNotificationWorker' => 'PhabricatorWorker',
'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController',
@@ -5159,6 +5162,7 @@
'PhabricatorManiphestApplication' => 'PhabricatorApplication',
'PhabricatorManiphestConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorManiphestTaskTestDataGenerator' => 'PhabricatorTestDataGenerator',
+ 'PhabricatorManiphestTransactionNotificationWorker' => 'PhabricatorApplicationTransactionNotificationWorker',
'PhabricatorMarkupCache' => 'PhabricatorCacheDAO',
'PhabricatorMarkupOneOff' => 'PhabricatorMarkupInterface',
'PhabricatorMarkupPreviewController' => 'PhabricatorController',
diff --git a/src/applications/maniphest/editor/ManiphestTransactionEditor.php b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
--- a/src/applications/maniphest/editor/ManiphestTransactionEditor.php
+++ b/src/applications/maniphest/editor/ManiphestTransactionEditor.php
@@ -13,6 +13,10 @@
return pht('Maniphest Tasks');
}
+ protected function getTransactionNotificationWorkerClass() {
+ return 'PhabricatorManiphestTransactionNotificationWorker';
+ }
+
public function getTransactionTypes() {
$types = parent::getTransactionTypes();
@@ -406,14 +410,6 @@
return $xactions;
}
- protected function getMailSubjectPrefix() {
- return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
- }
-
- protected function getMailThreadID(PhabricatorLiskDAO $object) {
- return 'maniphest-task-'.$object->getPHID();
- }
-
protected function getMailTo(PhabricatorLiskDAO $object) {
return array(
$object->getOwnerPHID(),
@@ -435,6 +431,9 @@
return $phids;
}
+ // FIXME: Move it to a ManiphestTransaction?
+ // This is currently duplicated in ManiphestTransactionNotificationWorker!
+ // And also needed in email preferences. TODO: Find a place to put this stuff
public function getMailTagsMap() {
return array(
ManiphestTransaction::MAILTAG_STATUS =>
@@ -458,67 +457,6 @@
);
}
- protected function buildReplyHandler(PhabricatorLiskDAO $object) {
- return id(new ManiphestReplyHandler())
- ->setMailReceiver($object);
- }
-
- protected function buildMailTemplate(PhabricatorLiskDAO $object) {
- $id = $object->getID();
- $title = $object->getTitle();
-
- return id(new PhabricatorMetaMTAMail())
- ->setSubject("T{$id}: {$title}")
- ->addHeader('Thread-Topic', "T{$id}: ".$object->getOriginalTitle());
- }
-
- protected function buildMailBody(
- PhabricatorLiskDAO $object,
- array $xactions) {
-
- $body = parent::buildMailBody($object, $xactions);
-
- if ($this->getIsNewObject()) {
- $body->addTextSection(
- pht('TASK DESCRIPTION'),
- $object->getDescription());
- }
-
- $body->addLinkSection(
- pht('TASK DETAIL'),
- PhabricatorEnv::getProductionURI('/T'.$object->getID()));
-
-
- $board_phids = array();
- $type_column = ManiphestTransaction::TYPE_PROJECT_COLUMN;
- foreach ($xactions as $xaction) {
- if ($xaction->getTransactionType() == $type_column) {
- $new = $xaction->getNewValue();
- $project_phid = idx($new, 'projectPHID');
- if ($project_phid) {
- $board_phids[] = $project_phid;
- }
- }
- }
-
- if ($board_phids) {
- $projects = id(new PhabricatorProjectQuery())
- ->setViewer($this->requireActor())
- ->withPHIDs($board_phids)
- ->execute();
-
- foreach ($projects as $project) {
- $body->addLinkSection(
- pht('WORKBOARD'),
- PhabricatorEnv::getProductionURI(
- '/project/board/'.$project->getID().'/'));
- }
- }
-
-
- return $body;
- }
-
protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object,
array $xactions) {
diff --git a/src/applications/maniphest/worker/PhabricatorManiphestTransactionNotificationWorker.php b/src/applications/maniphest/worker/PhabricatorManiphestTransactionNotificationWorker.php
new file mode 100644
--- /dev/null
+++ b/src/applications/maniphest/worker/PhabricatorManiphestTransactionNotificationWorker.php
@@ -0,0 +1,117 @@
+<?php
+
+final class PhabricatorManiphestTransactionNotificationWorker
+ extends PhabricatorApplicationTransactionNotificationWorker {
+
+ protected function loadObject($phid) {
+ $task = id(new ManiphestTaskQuery())
+ ->withPHIDs(array($phid))
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->executeOne();
+ return $task;
+ }
+
+ protected function loadXActions($xactions) {
+ $xactions = id(new ManiphestTransactionQuery())
+ ->withPHIDs($xactions)
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->execute();
+ return $xactions;
+ }
+
+
+ /* -( Sending Mail )---------------------------------------------------- */
+ protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ return id(new ManiphestReplyHandler())
+ ->setMailReceiver($object);
+ }
+
+ protected function getMailSubjectPrefix() {
+ return PhabricatorEnv::getEnvConfig('metamta.maniphest.subject-prefix');
+ }
+
+ protected function getMailThreadID(PhabricatorLiskDAO $object) {
+ return 'maniphest-task-'.$object->getPHID();
+ }
+
+ // FIXME: This is duplicated from the TransactionEditor.
+ // Move it somewhere else
+ public function getMailTagsMap() {
+ return array(
+ ManiphestTransaction::MAILTAG_STATUS =>
+ pht("A task's status changes."),
+ ManiphestTransaction::MAILTAG_OWNER =>
+ pht("A task's owner changes."),
+ ManiphestTransaction::MAILTAG_PRIORITY =>
+ pht("A task's priority changes."),
+ ManiphestTransaction::MAILTAG_CC =>
+ pht("A task's subscribers change."),
+ ManiphestTransaction::MAILTAG_PROJECTS =>
+ pht("A task's associated projects change."),
+ ManiphestTransaction::MAILTAG_UNBLOCK =>
+ pht('One of the tasks a task is blocked by changes status.'),
+ ManiphestTransaction::MAILTAG_COLUMN =>
+ pht('A task is moved between columns on a workboard.'),
+ ManiphestTransaction::MAILTAG_COMMENT =>
+ pht('Someone comments on a task.'),
+ ManiphestTransaction::MAILTAG_OTHER =>
+ pht('Other task activity not listed above occurs.'),
+ );
+ }
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $id = $object->getID();
+ $title = $object->getTitle();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject("T{$id}: {$title}")
+ ->addHeader('Thread-Topic', "T{$id}: ".$object->getOriginalTitle());
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ PhabricatorUser $viewer) {
+
+ $body = parent::buildMailBody($object, $xactions, $viewer);
+
+ if ($this->getIsNewObject()) {
+ $body->addTextSection(
+ pht('TASK DESCRIPTION'),
+ $object->getDescription());
+ }
+
+ $body->addLinkSection(
+ pht('TASK DETAIL'),
+ PhabricatorEnv::getProductionURI('/T'.$object->getID()));
+
+
+ $board_phids = array();
+ $type_column = ManiphestTransaction::TYPE_PROJECT_COLUMN;
+ foreach ($xactions as $xaction) {
+ if ($xaction->getTransactionType() == $type_column) {
+ $new = $xaction->getNewValue();
+ $project_phid = idx($new, 'projectPHID');
+ if ($project_phid) {
+ $board_phids[] = $project_phid;
+ }
+ }
+ }
+
+ if ($board_phids) {
+ $projects = id(new PhabricatorProjectQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($board_phids)
+ ->execute();
+
+ foreach ($projects as $project) {
+ $body->addLinkSection(
+ pht('WORKBOARD'),
+ PhabricatorEnv::getProductionURI(
+ '/project/board/'.$project->getID().'/'));
+ }
+ }
+
+ return $body;
+ }
+}
diff --git a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
--- a/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
+++ b/src/applications/metamta/replyhandler/PhabricatorMailReplyHandler.php
@@ -197,6 +197,35 @@
// for now.
$recipients = $tos + $ccs;
+ // Check if all recipients have proper permissions to the object
+ // Remove them from the list otherwise
+ $recipient_users = id(new PhabricatorPeopleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs(array_keys($recipients))
+ ->execute();
+ $recipient_users = mpull($recipient_users, null, 'getPHID');
+
+ // Check if user has permissions to view this object
+ foreach ($recipients as $phid => $recipient) {
+ if ($this->mailReceiver
+ && $this->mailReceiver instanceof PhabricatorPolicyInterface
+ && idx($recipient_users, $phid)
+ && $recipient_users[$phid] instanceof PhabricatorUser) {
+ if (!PhabricatorPolicyFilter::hasCapability(
+ $recipient_users[$phid],
+ $this->mailReceiver,
+ PhabricatorPolicyCapability::CAN_VIEW)) {
+ // User has no permission to this object
+ // so remove them from the recipient list
+ unset($recipients[$phid]);
+ }
+ }
+ }
+
+ if (!$recipients) {
+ return $result;
+ }
+
// When multiplexing mail, explicitly include To/Cc information in the
// message body and headers.
diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
--- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -29,7 +29,6 @@
private $actingAsPHID;
private $disableEmail;
-
/**
* Get the class name for the application this editor is a part of.
*
@@ -791,29 +790,36 @@
$this->loadHandles($xactions);
- $mail = null;
- if (!$this->getDisableEmail()) {
- if ($this->shouldSendMail($object, $xactions)) {
- $mail = $this->sendMail($object, $xactions);
+ $notifications_sent = $this->sendNotifications($object, $xactions);
+
+ // Fall back to the old code if no notification worker is in use
+ if (!$notifications_sent) {
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
+ $mail = null;
+ if (!$this->getDisableEmail()) {
+ if ($this->shouldSendMail($object, $xactions)) {
+ $mail = $this->sendMail($object, $xactions);
+ }
}
- }
- if ($this->supportsSearch()) {
- id(new PhabricatorSearchIndexer())
- ->queueDocumentForIndexing(
- $object->getPHID(),
- $this->getSearchContextParameter($object, $xactions));
- }
+ if ($this->supportsSearch()) {
+ id(new PhabricatorSearchIndexer())
+ ->queueDocumentForIndexing(
+ $object->getPHID(),
+ $this->getSearchContextParameter($object, $xactions));
+ }
- if ($this->shouldPublishFeedStory($object, $xactions)) {
- $mailed = array();
- if ($mail) {
- $mailed = $mail->buildRecipientList();
+ if ($this->shouldPublishFeedStory($object, $xactions)) {
+ $mailed = array();
+ if ($mail) {
+ $mailed = $mail->buildRecipientList();
+ }
+ $this->publishFeedStory(
+ $object,
+ $xactions,
+ $mailed);
}
- $this->publishFeedStory(
- $object,
- $xactions,
- $mailed);
}
$this->didApplyTransactions($xactions);
@@ -1893,6 +1899,57 @@
return $xaction->isCommentTransaction();
}
+/* -( Notification Worker (Mail & Feeds) )----------------------------------- */
+
+ protected function getTransactionNotificationWorkerClass() {
+ throw new Exception(
+ 'This function needs to be implemented to be able to render Email');
+ }
+
+ protected function sendNotifications(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ try {
+ $worker_class = $this->getTransactionNotificationWorkerClass();
+ } catch (Exception $e) {
+ return false;
+ }
+
+ $herald_xscript = $this->getHeraldTranscript();
+ if ($herald_xscript) {
+ $herald_header = $herald_xscript->getXHeraldRulesHeader();
+ $herald_header = HeraldTranscript::saveXHeraldRulesHeader(
+ $object->getPHID(),
+ $herald_header);
+ } else {
+ $herald_header = HeraldTranscript::loadXHeraldRulesHeader(
+ $object->getPHID());
+ }
+
+ PhabricatorWorker::scheduleTask(
+ $worker_class,
+ array(
+ 'object' => $object->getPHID(),
+ 'xactions' => mpull($xactions, 'getPHID'),
+ 'shouldSendMail' => $this->shouldSendMail($object, $xactions),
+ 'shouldPublishFeedStory' =>
+ $this->shouldPublishFeedStory($object, $xactions),
+ 'mailTo' => $this->getMailTo($object),
+ 'mailCC' => $this->getMailCC($object),
+ 'actingAsPHID' => $this->getActingAsPHID(),
+ 'isNewObject' => $this->getIsNewObject(),
+ 'heraldHeader' => $herald_header,
+ 'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(),
+ 'parentMessageID' => $this->getParentMessageID(),
+ ),
+ array(
+ 'priority' => PhabricatorWorker::PRIORITY_ALERTS, // FIXME: right prio?
+ ));
+
+ return true;
+ }
+
/* -( Sending Mail )------------------------------------------------------- */
@@ -1910,13 +1967,14 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function sendMail(
PhabricatorLiskDAO $object,
array $xactions) {
// Check if any of the transactions are visible. If we don't have any
// visible transactions, don't send the mail.
-
$any_visible = false;
foreach ($xactions as $xaction) {
if (!$xaction->shouldHideForMail($xactions)) {
@@ -1931,8 +1989,8 @@
$email_to = array_filter(array_unique($this->getMailTo($object)));
$email_cc = array_filter(array_unique($this->getMailCC($object)));
-
$phids = array_merge($email_to, $email_cc);
+
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireActor())
->withPHIDs($phids)
@@ -2006,6 +2064,8 @@
return $template;
}
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
private function addMailProjectMetadata(
PhabricatorLiskDAO $object,
PhabricatorMetaMTAMail $template) {
@@ -2044,6 +2104,8 @@
}
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailThreadID(PhabricatorLiskDAO $object) {
return $object->getPHID();
}
@@ -2052,6 +2114,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getStrongestAction(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2062,6 +2126,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function buildReplyHandler(PhabricatorLiskDAO $object) {
throw new Exception('Capability not supported.');
}
@@ -2069,6 +2135,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailSubjectPrefix() {
throw new Exception('Capability not supported.');
}
@@ -2077,6 +2145,9 @@
/**
* @task mail
*/
+ // FIXME: Move it to a ManiphestTransaction?
+ // This is currently duplicated in ManiphestTransactionNotificationWorker!
+ // And also needed in email preferences. TODO: Find a place to put this stuff
protected function getMailTags(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2092,6 +2163,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
public function getMailTagsMap() {
// TODO: We should move shared mail tags, like "comment", here.
return array();
@@ -2101,6 +2174,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailAction(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2111,6 +2186,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function buildMailTemplate(PhabricatorLiskDAO $object) {
throw new Exception('Capability not supported.');
}
@@ -2187,6 +2264,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2273,6 +2352,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedStoryType() {
return 'PhabricatorApplicationTransactionFeedStory';
}
@@ -2281,6 +2362,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedRelatedPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2306,6 +2389,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedNotifyPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2319,6 +2404,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedStoryData(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2336,6 +2423,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function publishFeedStory(
PhabricatorLiskDAO $object,
array $xactions,
diff --git a/src/applications/transactions/worker/PhabricatorApplicationTransactionNotificationWorker.php b/src/applications/transactions/worker/PhabricatorApplicationTransactionNotificationWorker.php
new file mode 100644
--- /dev/null
+++ b/src/applications/transactions/worker/PhabricatorApplicationTransactionNotificationWorker.php
@@ -0,0 +1,511 @@
+<?php
+
+abstract class PhabricatorApplicationTransactionNotificationWorker
+ extends PhabricatorWorker {
+
+ private $shouldPublishFeedStory;
+ private $shouldSendMail;
+ private $actingAsPHID;
+ private $mailTo;
+ private $mailCC;
+ private $isNewObject;
+ private $heraldHeader;
+ private $excludeMailRecipientPHIDs;
+ private $parentMessageID;
+ private $users;
+
+ abstract protected function loadObject($phid);
+ abstract protected function loadXActions($xactions);
+ abstract protected function buildReplyHandler(PhabricatorLiskDAO $object);
+
+ public function getMaximumRetryCount() {
+ return 250;
+ }
+
+ public function getWaitBeforeRetry(PhabricatorWorkerTask $task) {
+ return ($task->getFailureCount() * 15);
+ }
+
+
+ protected function getActingAsPHID() {
+ return $this->actingAsPHID;
+ }
+
+ protected function getIsNewObject() {
+ return $this->isNewObject;
+ }
+
+ protected function expandPHIDs($phids) {
+ // Expand recipients (get projects members)
+ $map = id(new PhabricatorMetaMTAMemberQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs($phids)
+ ->execute();
+
+ $results = array();
+ foreach ($phids as $phid) {
+ if (isset($map[$phid])) {
+ foreach ($map[$phid] as $expanded_phid) {
+ $results[$expanded_phid] = $expanded_phid;
+ }
+ } else {
+ $results[$phid] = $phid;
+ }
+ }
+ return $results;
+ }
+
+ protected function loadUsers(array $phids) {
+ $users = id(new PhabricatorPeopleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs($phids)
+ ->execute();
+ $this->users = mpull($users, null, 'getPHID');
+ }
+
+ protected function filterUsersWithoutPermission($object) {
+ foreach ($this->users as $key => $user) {
+ // Check if the receiving user has permissions to view this object
+ if ($object
+ && $object instanceof PhabricatorPolicyInterface
+ && $user
+ && $user instanceof PhabricatorUser) {
+ if (!PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $object,
+ PhabricatorPolicyCapability::CAN_VIEW)) {
+ // User has no permission to this object
+ // so remove them from all lists
+ unset($this->users[$key]);
+ $phid = array($user->getPHID());
+ $this->mailTo = array_diff($this->mailTo, $phid);
+ $this->mailTo = array_diff($this->mailTo, $phid);
+ }
+ }
+ }
+ }
+
+ protected function getUser($phid) {
+ return idx($this->users, $phid, null);
+ }
+
+ public function doWork() {
+ $task_data = $this->getTaskData();
+
+ $this->shouldSendMail =
+ idx($task_data, 'shouldSendMail', false);
+
+ $this->shouldPublishFeedStory =
+ idx($task_data, 'shouldPublishFeedStory', false);
+
+ if (!idx($task_data, 'actingAsPHID')) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht('Missing actingAsPHID in Tasks Data'));
+ }
+ $this->actingAsPHID = $task_data['actingAsPHID'];
+
+ if (!idx($task_data, 'mailTo')) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht('Missing mailTo in Tasks Data'));
+ }
+ $this->mailTo = $task_data['mailTo'];
+
+ if (!idx($task_data, 'mailCC')) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht('Missing mailCC in Tasks Data'));
+ }
+ $this->mailCC = $task_data['mailCC'];
+
+ $this->heraldHeader =
+ idx($task_data, 'heraldHeader', null);
+
+ $this->isNewObject =
+ idx($task_data, 'isNewObject', false);
+
+ $this->excludeMailRecipientPHIDs =
+ idx($task_data, 'excludeMailRecipientPHIDs');
+
+ $this->parentMessageID =
+ idx($task_data, 'parentMessageID');
+
+ // Load object and xactions
+ $object = $this->loadObject($task_data['object']);
+ if (!$object) {
+ throw new PhabricatorWorkerPermanentFailureException(
+ pht('Unable to load object!'));
+ }
+
+ $xactions = $this->loadXActions($task_data['xactions']);
+ assert_instances_of($xactions, 'PhabricatorApplicationTransaction');
+
+ // Filter non-visible xactions
+ $feed_xactions = mfilter($xactions, 'shouldHideForFeed', true);
+ $mail_xactions = array();
+ foreach ($xactions as $xaction) {
+ if (!$xaction->shouldHideForMail($xactions)) {
+ $mail_xactions[] = $xaction;
+ }
+ }
+
+ // Expand TO & CC (Turn Project PHIDs into its Member PHIDs)
+ $this->mailTo = $this->expandPHIDs(
+ array_filter(array_unique($this->mailTo)));
+ $this->mailCC = $this->expandPHIDs(
+ array_filter(array_unique($this->mailCC)));
+
+ // Load corresponding User Objects
+ $this->loadUsers(array_merge($this->mailTo, $this->mailCC));
+
+ // Filter Users without permission to the object notified about
+ $this->filterUsersWithoutPermission($object);
+
+ // Send Mail
+ $mailed_phids = array();
+ if ($mail_xactions) {
+ $mailed_phids = $this->sendMail($object, $mail_xactions);
+ }
+
+ // Publish Feed Stories
+ if ($feed_xactions) {
+ $this->publishFeedStory($object, $feed_xactions, $mailed_phids);
+ }
+
+ return true;
+ }
+
+ protected function sendMail($object, $xactions) {
+ $email_to = $this->getMailTo($object);
+ $email_cc = $this->getMailCC($object);
+ $recipients = array_merge($email_to, $email_cc);
+
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs($recipients)
+ ->execute();
+
+ $to_handles = array_select_keys($handles, $email_to);
+ $cc_handles = array_select_keys($handles, $email_cc);
+
+ $emails = array();
+ $send_as_one_email = false;
+ // Render the email for each user individually
+ // so correct permissions are preserved
+ foreach ($recipients as $recipient) {
+ $viewer = $this->getUser($recipient);
+
+ $template = $this->buildMailTemplate($object);
+ $template->addPHIDHeaders('X-Phabricator-To', array_keys($email_to));
+ $template->addPHIDHeaders('X-Phabricator-Cc', array_keys($email_cc));
+
+ if (!PhabricatorMetaMTAMail::shouldMultiplexAllMail()) {
+ // If config is set to not multiplex we render as the triggering user
+ $viewer = $this->getUser($this->getActingAsPHID());
+ $template->addTos($email_to);
+ $template->addCCs($email_cc);
+ $send_as_one_email = true;
+ } else {
+ $template->addTos(array($recipient));
+ }
+
+ // This stuff doesen't change per user. We still handle
+ // it here to support sending in the users locale in the future.
+ $mail_tags = $this->getMailTags($object, $xactions);
+ $action = $this->getMailAction($object, $xactions);
+ $reply_handler = $this->buildReplyHandler($object);
+ $reply_section = $reply_handler->getReplyHandlerInstructions();
+
+ // Render the actual mail body
+ $body = $this->buildMailBody($object, $xactions, $viewer);
+
+ if ($reply_section !== null) {
+ $body->addReplySection($reply_section);
+ }
+
+ $body->addEmailPreferenceSection();
+
+ // FIXME?: This changes the emails a bit
+ // (adds a RECIPIENTS header at the bottom)
+ // Before this those were added by raw concatenation to the body
+ $body->addPlainTextSection('RECIPIENTS',
+ $reply_handler->getRecipientsSummary($to_handles, $cc_handles));
+ $body->addHTMLSection('RECIPIENTS',
+ $reply_handler->getRecipientsSummaryHTML($to_handles, $cc_handles));
+
+ // TODO: Reply-To handling is still missing
+
+ $template
+ ->setFrom($this->getActingAsPHID())
+ ->setSubjectPrefix($this->getMailSubjectPrefix())
+ ->setVarySubjectPrefix('['.$action.']')
+ ->setThreadID($this->getMailThreadID($object), $this->getIsNewObject())
+ ->setRelatedPHID($object->getPHID())
+ ->setExcludeMailRecipientPHIDs($this->excludeMailRecipientPHIDs)
+ ->setMailTags($mail_tags)
+ ->setIsBulk(true)
+ ->setBody($body->render())
+ ->setHTMLBody($body->renderHTML());
+
+ foreach ($body->getAttachments() as $attachment) {
+ $template->addAttachment($attachment);
+ }
+
+ if ($this->heraldHeader) {
+ $template->addHeader('X-Herald-Rules', $this->heraldHeader);
+ }
+
+ if ($object instanceof PhabricatorProjectInterface) {
+ $this->addMailProjectMetadata($object, $template, $viewer);
+ }
+
+ if ($this->parentMessageID) {
+ $template->setParentMessageID($this->parentMessageID);
+ }
+
+ $emails[] = $template;
+
+ if ($send_as_one_email) {
+ // We send all just a single email for all users
+ break;
+ }
+ }
+
+ $mailed_phids = array();
+ foreach ($emails as $mail) {
+ $mail->saveAndSend();
+ // The mailed_phids are collected here so we know we've
+ // sent them email and don't have to notify them in the Web UI
+ $mailed_phids += $mail->buildRecipientList();
+ }
+
+ return $mailed_phids;
+ }
+
+
+ /* -( Sending Mail )---------------------------------------------------- */
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ throw new Exception('Capability not supported.');
+ }
+
+ protected function getMailSubjectPrefix() {
+ throw new Exception('Capability not supported.');
+ }
+
+ private function getMailTo(PhabricatorLiskDAO $object) {
+ return $this->mailTo;
+ }
+
+ private function getMailCC(PhabricatorLiskDAO $object) {
+ return $this->mailCC;
+ }
+
+ private function addMailProjectMetadata(
+ PhabricatorLiskDAO $object,
+ PhabricatorMetaMTAMail $template,
+ PhabricatorUser $viewer) {
+
+ $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $object->getPHID(),
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+
+ if (!$project_phids) {
+ return;
+ }
+
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($project_phids)
+ ->execute();
+
+ $project_tags = array();
+ foreach ($handles as $handle) {
+ if (!$handle->isComplete()) {
+ continue;
+ }
+ $project_tags[] = '<'.$handle->getObjectName().'>';
+ }
+
+ if (!$project_tags) {
+ return;
+ }
+
+ $project_tags = implode(', ', $project_tags);
+ $template->addHeader('X-Phabricator-Projects', $project_tags);
+ }
+
+
+ protected function getMailThreadID(PhabricatorLiskDAO $object) {
+ return $object->getPHID();
+ }
+
+ protected function getMailTags(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ $tags = array();
+
+ foreach ($xactions as $xaction) {
+ $tags[] = $xaction->getMailTags();
+ }
+
+ return array_mergev($tags);
+ }
+
+ public function getMailTagsMap() {
+ // TODO: We should move shared mail tags, like "comment", here.
+ return array();
+ }
+
+ protected function getStrongestAction(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return last(msort($xactions, 'getActionStrength'));
+ }
+
+ protected function getMailAction(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return $this->getStrongestAction($object, $xactions)->getActionName();
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ PhabricatorUser $viewer) {
+
+ $headers = array();
+ $comments = array();
+
+ foreach ($xactions as $xaction) {
+ if ($xaction->shouldHideForMail($xactions)) {
+ continue;
+ }
+
+ $header = $xaction->getTitleForMail();
+ if ($header !== null) {
+ $headers[] = $header;
+ }
+
+ $comment = $xaction->getBodyForMail();
+ if ($comment !== null) {
+ $comments[] = $comment;
+ }
+ }
+
+ $body = new PhabricatorMetaMTAMailBody();
+ $body->setViewer($viewer);
+ $body->addRawSection(implode("\n", $headers));
+
+ foreach ($comments as $comment) {
+ $body->addRemarkupSection($comment);
+ }
+
+ if ($object instanceof PhabricatorCustomFieldInterface) {
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $object,
+ PhabricatorCustomField::ROLE_TRANSACTIONMAIL);
+ $field_list->setViewer($viewer);
+ $field_list->readFieldsFromStorage($object);
+
+ foreach ($field_list->getFields() as $field) {
+ $field->updateTransactionMailBody(
+ $body,
+ $this,
+ $xactions);
+ }
+ }
+
+ return $body;
+ }
+
+ /* -( Publishing Feed Stories )------------------------------------------ */
+
+ /**
+ * @task feed
+ */
+ protected function getFeedStoryType() {
+ return 'PhabricatorApplicationTransactionFeedStory';
+ }
+
+
+ /**
+ * @task feed
+ */
+ protected function getFeedRelatedPHIDs(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $phids = array(
+ $object->getPHID(),
+ $this->getActingAsPHID(),
+ );
+
+ if ($object instanceof PhabricatorProjectInterface) {
+ $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
+ $object->getPHID(),
+ PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
+ foreach ($project_phids as $project_phid) {
+ $phids[] = $project_phid;
+ }
+ }
+
+ return $phids;
+ }
+
+
+ /**
+ * @task feed
+ */
+ protected function getFeedNotifyPHIDs(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return array_unique(array_merge(
+ $this->getMailTo($object),
+ $this->getMailCC($object)));
+ }
+
+
+ /**
+ * @task feed
+ */
+ protected function getFeedStoryData(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $xactions = msort($xactions, 'getActionStrength');
+ $xactions = array_reverse($xactions);
+
+ return array(
+ 'objectPHID' => $object->getPHID(),
+ 'transactionPHIDs' => mpull($xactions, 'getPHID'),
+ );
+ }
+
+
+ /**
+ * @task feed
+ */
+ protected function publishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions,
+ array $mailed_phids) {
+
+ $related_phids = $this->getFeedRelatedPHIDs($object, $xactions);
+ $subscribed_phids = $this->getFeedNotifyPHIDs($object, $xactions);
+
+ $story_type = $this->getFeedStoryType();
+ $story_data = $this->getFeedStoryData($object, $xactions);
+
+ id(new PhabricatorFeedStoryPublisher())
+ ->setStoryType($story_type)
+ ->setStoryData($story_data)
+ ->setStoryTime(time())
+ ->setStoryAuthorPHID($this->getActingAsPHID())
+ ->setRelatedPHIDs($related_phids)
+ ->setPrimaryObjectPHID($object->getPHID())
+ ->setSubscribedPHIDs($subscribed_phids)
+ ->setMailRecipientPHIDs($mailed_phids)
+ ->setMailTags($this->getMailTags($object, $xactions))
+ ->publish();
+ }
+
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Oct 6, 2:26 PM (2 w, 2 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/vv/im/s4i3bq6amtmwtqzs
Default Alt Text
D11329.diff (35 KB)
Attached To
Mode
D11329: Refactor email rendering into worker tasks
Attached
Detach File
Event Timeline
Log In to Comment