Page MenuHomePhabricator

D11329.id27431.diff
No OneTemporary

D11329.id27431.diff

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
@@ -1281,6 +1281,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',
@@ -1933,6 +1934,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',
@@ -4451,6 +4453,7 @@
'PhabricatorApplicationTransactionFeedStory' => 'PhabricatorFeedStory',
'PhabricatorApplicationTransactionNoEffectException' => 'Exception',
'PhabricatorApplicationTransactionNoEffectResponse' => 'AphrontProxyResponse',
+ 'PhabricatorApplicationTransactionNotificationWorker' => 'PhabricatorWorker',
'PhabricatorApplicationTransactionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorApplicationTransactionResponse' => 'AphrontProxyResponse',
'PhabricatorApplicationTransactionShowOlderController' => 'PhabricatorApplicationTransactionController',
@@ -5138,6 +5141,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,116 @@
+<?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,55 @@
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 +1965,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 +1987,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 +2062,8 @@
return $template;
}
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
private function addMailProjectMetadata(
PhabricatorLiskDAO $object,
PhabricatorMetaMTAMail $template) {
@@ -2044,6 +2102,8 @@
}
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailThreadID(PhabricatorLiskDAO $object) {
return $object->getPHID();
}
@@ -2052,6 +2112,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getStrongestAction(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2062,6 +2124,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 +2133,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailSubjectPrefix() {
throw new Exception('Capability not supported.');
}
@@ -2077,6 +2143,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 +2161,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 +2172,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getMailAction(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2111,6 +2184,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 +2262,8 @@
/**
* @task mail
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function buildMailBody(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2273,6 +2350,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedStoryType() {
return 'PhabricatorApplicationTransactionFeedStory';
}
@@ -2281,6 +2360,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedRelatedPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2306,6 +2387,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedNotifyPHIDs(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2319,6 +2402,8 @@
/**
* @task feed
*/
+ // NotificationWorkerGrepToken
+ // Remove this code when all apps have transitioned
protected function getFeedStoryData(
PhabricatorLiskDAO $object,
array $xactions) {
@@ -2336,6 +2421,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,495 @@
+<?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);
+ }
+
+
+ public function getActingAsPHID() {
+ return $this->actingAsPHID;
+ }
+
+ public 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;
+ }
+
+ public function loadUsers(array $phids) {
+ $users = id(new PhabricatorPeopleQuery())
+ ->setViewer(PhabricatorUser::getOmnipotentUser())
+ ->withPHIDs($phids)
+ ->execute();
+ $this->users = mpull($users, null, 'getPHID');
+ }
+
+ public 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);
+ }
+ }
+ }
+ }
+
+ public 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

Mime Type
text/plain
Expires
May 10 2024, 2:55 PM (5 w, 1 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/lr/mi/5wjaqkp2nh6ql7vh
Default Alt Text
D11329.id27431.diff (35 KB)

Event Timeline