1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 14:00:56 +01:00

Add basic support for Herald outbound rules

Summary: Ref T5791. This is still very basic (no global actions, no support for matching headers/bodies/recipients/etc) but gets the core in.

Test Plan:
{F715209}

{F715211}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T5791

Differential Revision: https://secure.phabricator.com/D13897
This commit is contained in:
epriestley 2015-08-15 10:54:33 -07:00
parent bb0d13345d
commit 7a1bbe6634
17 changed files with 570 additions and 17 deletions

View file

@ -2247,6 +2247,9 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionComment' => 'applications/macro/storage/PhabricatorMacroTransactionComment.php',
'PhabricatorMacroTransactionQuery' => 'applications/macro/query/PhabricatorMacroTransactionQuery.php',
'PhabricatorMacroViewController' => 'applications/macro/controller/PhabricatorMacroViewController.php',
'PhabricatorMailEmailHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailHeraldField.php',
'PhabricatorMailEmailHeraldFieldGroup' => 'applications/metamta/herald/PhabricatorMailEmailHeraldFieldGroup.php',
'PhabricatorMailEmailSubjectHeraldField' => 'applications/metamta/herald/PhabricatorMailEmailSubjectHeraldField.php',
'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAdapter.php',
'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationAmazonSESAdapter.php',
'PhabricatorMailImplementationMailgunAdapter' => 'applications/metamta/adapter/PhabricatorMailImplementationMailgunAdapter.php',
@ -2263,10 +2266,15 @@ phutil_register_library_map(array(
'PhabricatorMailManagementShowOutboundWorkflow' => 'applications/metamta/management/PhabricatorMailManagementShowOutboundWorkflow.php',
'PhabricatorMailManagementVolumeWorkflow' => 'applications/metamta/management/PhabricatorMailManagementVolumeWorkflow.php',
'PhabricatorMailManagementWorkflow' => 'applications/metamta/management/PhabricatorMailManagementWorkflow.php',
'PhabricatorMailOutboundMailHeraldAdapter' => 'applications/metamta/herald/PhabricatorMailOutboundMailHeraldAdapter.php',
'PhabricatorMailOutboundRoutingHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingHeraldAction.php',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfEmailHeraldAction.php',
'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'applications/metamta/herald/PhabricatorMailOutboundRoutingSelfNotificationHeraldAction.php',
'PhabricatorMailOutboundStatus' => 'applications/metamta/constants/PhabricatorMailOutboundStatus.php',
'PhabricatorMailReceiver' => 'applications/metamta/receiver/PhabricatorMailReceiver.php',
'PhabricatorMailReceiverTestCase' => 'applications/metamta/receiver/__tests__/PhabricatorMailReceiverTestCase.php',
'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/PhabricatorMailReplyHandler.php',
'PhabricatorMailRoutingRule' => 'applications/metamta/constants/PhabricatorMailRoutingRule.php',
'PhabricatorMailSetupCheck' => 'applications/config/check/PhabricatorMailSetupCheck.php',
'PhabricatorMailTarget' => 'applications/metamta/replyhandler/PhabricatorMailTarget.php',
'PhabricatorMailgunConfigOptions' => 'applications/config/option/PhabricatorMailgunConfigOptions.php',
@ -6178,6 +6186,9 @@ phutil_register_library_map(array(
'PhabricatorMacroTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorMacroTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorMacroViewController' => 'PhabricatorMacroController',
'PhabricatorMailEmailHeraldField' => 'HeraldField',
'PhabricatorMailEmailHeraldFieldGroup' => 'HeraldFieldGroup',
'PhabricatorMailEmailSubjectHeraldField' => 'PhabricatorMailEmailHeraldField',
'PhabricatorMailImplementationAdapter' => 'Phobject',
'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
'PhabricatorMailImplementationMailgunAdapter' => 'PhabricatorMailImplementationAdapter',
@ -6194,10 +6205,15 @@ phutil_register_library_map(array(
'PhabricatorMailManagementShowOutboundWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementVolumeWorkflow' => 'PhabricatorMailManagementWorkflow',
'PhabricatorMailManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorMailOutboundMailHeraldAdapter' => 'HeraldAdapter',
'PhabricatorMailOutboundRoutingHeraldAction' => 'HeraldAction',
'PhabricatorMailOutboundRoutingSelfEmailHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
'PhabricatorMailOutboundRoutingSelfNotificationHeraldAction' => 'PhabricatorMailOutboundRoutingHeraldAction',
'PhabricatorMailOutboundStatus' => 'Phobject',
'PhabricatorMailReceiver' => 'Phobject',
'PhabricatorMailReceiverTestCase' => 'PhabricatorTestCase',
'PhabricatorMailReplyHandler' => 'Phobject',
'PhabricatorMailRoutingRule' => 'Phobject',
'PhabricatorMailSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorMailTarget' => 'Phobject',
'PhabricatorMailgunConfigOptions' => 'PhabricatorApplicationConfigOptions',

View file

@ -48,6 +48,9 @@ final class HeraldTestConsoleController extends HeraldController {
} else if ($object instanceof PonderQuestion) {
$adapter = id(new HeraldPonderQuestionAdapter())
->setQuestion($object);
} else if ($object instanceof PhabricatorMetaMTAMail) {
$adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())
->setObject($object);
} else {
throw new Exception(pht('Can not build adapter for object!'));
}

View file

@ -356,7 +356,17 @@ final class HeraldTranscriptController extends HeraldController {
// Handle older transcripts which used a static string to record
// action results.
if (!is_array($log)) {
if ($xscript->getDryRun()) {
$action_list->addItem(
id(new PHUIStatusItemView())
->setIcon('fa-ban', 'grey')
->setTarget(pht('Dry Run'))
->setNote(
pht(
'This was a dry run, so no actions were taken.')));
continue;
} else if (!is_array($log)) {
$action_list->addItem(
id(new PHUIStatusItemView())
->setIcon('fa-clock-o', 'grey')

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorMailRoutingRule extends Phobject {
const ROUTE_AS_NOTIFICATION = 'route.notification';
const ROUTE_AS_MAIL = 'route.mail';
public static function isStrongerThan($rule_u, $rule_v) {
$strength_u = self::getRuleStrength($rule_u);
$strength_v = self::getRuleStrength($rule_v);
return ($strength_u > $strength_v);
}
public static function getRuleStrength($const) {
$strength = array(
self::ROUTE_AS_NOTIFICATION => 1,
self::ROUTE_AS_MAIL => 2,
);
return idx($strength, $const, 0);
}
public static function getRuleName($const) {
$names = array(
self::ROUTE_AS_NOTIFICATION => pht('Route as Notification'),
self::ROUTE_AS_MAIL => pht('Route as Mail'),
);
return idx($names, $const, $const);
}
public static function getRuleIcon($const) {
$icons = array(
self::ROUTE_AS_NOTIFICATION => 'fa-bell',
self::ROUTE_AS_MAIL => 'fa-envelope',
);
return idx($icons, $const, 'fa-question-circle');
}
public static function getRuleColor($const) {
$colors = array(
self::ROUTE_AS_NOTIFICATION => 'grey',
self::ROUTE_AS_MAIL => 'grey',
);
return idx($colors, $const, 'yellow');
}
}

View file

@ -86,6 +86,10 @@ final class PhabricatorMetaMTAMailViewController
pht('Cc'),
$cc_list);
$properties->addProperty(
pht('Sent'),
phabricator_datetime($mail->getDateCreated(), $viewer));
$properties->addSectionHeader(
pht('Message'),
PHUIPropertyListView::ICON_SUMMARY);
@ -144,23 +148,16 @@ final class PhabricatorMetaMTAMailViewController
$actors = $mail->getDeliveredActors();
$reasons = null;
if (!$actors) {
// TODO: We can get rid of this special-cased message after these changes
// have been live for a while, but provide a more tailored message for
// now so things are a little less confusing for users.
if ($mail->getStatus() == PhabricatorMetaMTAMail::STATUS_SENT) {
$delivery = phutil_tag(
'em',
array(),
pht(
'This is an older message that predates recording delivery '.
'information, so none is available.'));
} else {
$delivery = phutil_tag(
'em',
array(),
if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
$delivery = $this->renderEmptyMessage(
pht(
'This message has not been delivered yet, so delivery information '.
'is not available.'));
} else {
$delivery = $this->renderEmptyMessage(
pht(
'This is an older message that predates recording delivery '.
'information, so none is available.'));
}
} else {
$actor = idx($actors, $viewer->getPHID());
@ -214,6 +211,127 @@ final class PhabricatorMetaMTAMailViewController
$properties->addProperty(pht('Delivery'), $delivery);
if ($reasons) {
$properties->addProperty(pht('Reasons'), $reasons);
$properties->addProperty(
null,
$this->renderEmptyMessage(
pht(
'Delivery reasons are listed from weakest to strongest.')));
}
$properties->addSectionHeader(pht('Routing Rules'));
$map = $mail->getDeliveredRoutingMap();
$routing_detail = null;
if ($map === null) {
if ($mail->getStatus() == PhabricatorMailOutboundStatus::STATUS_QUEUE) {
$routing_result = $this->renderEmptyMessage(
pht(
'This message has not been sent yet, so routing rules have '.
'not been computed.'));
} else {
$routing_result = $this->renderEmptyMessage(
pht(
'This is an older message which predates routing rules.'));
}
} else {
$rule = idx($map, $viewer->getPHID());
if ($rule === null) {
$rule = idx($map, 'default');
}
if ($rule === null) {
$routing_result = $this->renderEmptyMessage(
pht(
'No routing rules applied when delivering this message to you.'));
} else {
$rule_const = $rule['rule'];
$reason_phid = $rule['reason'];
switch ($rule_const) {
case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION:
$routing_result = pht(
'This message was routed as a notification because it '.
'matched %s.',
$viewer->renderHandle($reason_phid)->render());
break;
case PhabricatorMailRoutingRule::ROUTE_AS_MAIL:
$routing_result = pht(
'This message was routed as an email because it matched %s.',
$viewer->renderHandle($reason_phid)->render());
break;
default:
$routing_result = pht('Unknown routing rule "%s".', $rule_const);
break;
}
}
$routing_rules = $mail->getDeliveredRoutingRules();
if ($routing_rules) {
$rules = array();
foreach ($routing_rules as $rule) {
$phids = idx($rule, 'phids');
if ($phids === null) {
$rules[] = $rule;
} else if (in_array($viewer->getPHID(), $phids)) {
$rules[] = $rule;
}
}
// Reorder rules by strength.
foreach ($rules as $key => $rule) {
$const = $rule['routingRule'];
$phids = $rule['phids'];
if ($phids === null) {
$type = 'A';
} else {
$type = 'B';
}
$rules[$key]['strength'] = sprintf(
'~%s%08d',
$type,
PhabricatorMailRoutingRule::getRuleStrength($const));
}
$rules = isort($rules, 'strength');
$routing_detail = id(new PHUIStatusListView());
foreach ($rules as $rule) {
$const = $rule['routingRule'];
$phids = $rule['phids'];
$name = PhabricatorMailRoutingRule::getRuleName($const);
$icon = PhabricatorMailRoutingRule::getRuleIcon($const);
$color = PhabricatorMailRoutingRule::getRuleColor($const);
if ($phids === null) {
$kind = pht('Global');
} else {
$kind = pht('Personal');
}
$target = array($kind, ': ', $name);
$target = phutil_tag('strong', array(), $target);
$item = id(new PHUIStatusItemView())
->setTarget($target)
->setNote($viewer->renderHandle($rule['reasonPHID']))
->setIcon($icon, $color);
$routing_detail->addItem($item);
}
}
}
$properties->addProperty(pht('Effective Rule'), $routing_result);
if ($routing_detail !== null) {
$properties->addProperty(pht('All Matching Rules'), $routing_detail);
$properties->addProperty(
null,
$this->renderEmptyMessage(
pht(
'Matching rules are listed from weakest to strongest.')));
}
return $properties;
@ -252,4 +370,8 @@ final class PhabricatorMetaMTAMailViewController
return $properties;
}
private function renderEmptyMessage($message) {
return phutil_tag('em', array(), $message);
}
}

View file

@ -0,0 +1,14 @@
<?php
abstract class PhabricatorMailEmailHeraldField
extends HeraldField {
public function supportsObject($object) {
return ($object instanceof PhabricatorMetaMTAMail);
}
public function getFieldGroupKey() {
return PhabricatorMailEmailHeraldFieldGroup::FIELDGROUPKEY;
}
}

View file

@ -0,0 +1,15 @@
<?php
final class PhabricatorMailEmailHeraldFieldGroup extends HeraldFieldGroup {
const FIELDGROUPKEY = 'mail.message';
public function getGroupLabel() {
return pht('Message Fields');
}
protected function getGroupOrder() {
return 1000;
}
}

View file

@ -0,0 +1,20 @@
<?php
final class PhabricatorMailEmailSubjectHeraldField
extends PhabricatorMailEmailHeraldField {
const FIELDCONST = 'mail.message.subject';
public function getHeraldFieldName() {
return pht('Subject');
}
public function getHeraldFieldValue($object) {
return $object->getSubject();
}
protected function getHeraldFieldStandardType() {
return self::STANDARD_TEXT;
}
}

View file

@ -0,0 +1,62 @@
<?php
final class PhabricatorMailOutboundMailHeraldAdapter
extends HeraldAdapter {
private $mail;
public function getAdapterApplicationClass() {
return 'PhabricatorMetaMTAApplication';
}
public function getAdapterContentDescription() {
return pht('Route outbound email.');
}
protected function initializeNewAdapter() {
$this->mail = $this->newObject();
}
protected function newObject() {
return new PhabricatorMetaMTAMail();
}
public function getObject() {
return $this->mail;
}
public function setObject(PhabricatorMetaMTAMail $mail) {
$this->mail = $mail;
return $this;
}
public function getAdapterContentName() {
return pht('Outbound Mail');
}
public function isSingleEventAdapter() {
return true;
}
public function getRepetitionOptions() {
return array(
HeraldRepetitionPolicyConfig::FIRST,
);
}
public function supportsRuleType($rule_type) {
switch ($rule_type) {
case HeraldRuleTypeConfig::RULE_TYPE_GLOBAL:
case HeraldRuleTypeConfig::RULE_TYPE_PERSONAL:
return true;
case HeraldRuleTypeConfig::RULE_TYPE_OBJECT:
default:
return false;
}
}
public function getHeraldName() {
return pht('Mail %d', $this->getObject()->getID());
}
}

View file

@ -0,0 +1,46 @@
<?php
abstract class PhabricatorMailOutboundRoutingHeraldAction
extends HeraldAction {
const DO_ROUTE = 'do.route';
public function supportsObject($object) {
return ($object instanceof PhabricatorMetaMTAMail);
}
public function getActionGroupKey() {
return HeraldApplicationActionGroup::ACTIONGROUPKEY;
}
protected function applyRouting(HeraldRule $rule, $route, $phids) {
$adapter = $this->getAdapter();
$mail = $adapter->getObject();
$mail->addRoutingRule($route, $phids, $rule->getPHID());
$this->logEffect(
self::DO_ROUTE,
array(
'route' => $route,
'phids' => $phids,
));
}
protected function getActionEffectMap() {
return array(
self::DO_ROUTE => array(
'icon' => 'fa-arrow-right',
'color' => 'green',
'name' => pht('Routed Message'),
),
);
}
protected function renderActionEffectDescription($type, $data) {
switch ($type) {
case self::DO_ROUTE:
return pht('Routed mail.');
}
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorMailOutboundRoutingSelfEmailHeraldAction
extends PhabricatorMailOutboundRoutingHeraldAction {
const ACTIONCONST = 'routing.self.email';
public function getHeraldActionName() {
return pht('Deliver as email');
}
public function supportsRuleType($rule_type) {
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function applyEffect($object, HeraldEffect $effect) {
$rule = $effect->getRule();
$author_phid = $rule->getAuthorPHID();
$this->applyRouting(
$rule,
PhabricatorMailRoutingRule::ROUTE_AS_MAIL,
array($author_phid));
}
public function getHeraldActionStandardType() {
return self::STANDARD_NONE;
}
public function renderActionDescription($value) {
return pht('Deliver as email.');
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhabricatorMailOutboundRoutingSelfNotificationHeraldAction
extends PhabricatorMailOutboundRoutingHeraldAction {
const ACTIONCONST = 'routing.self.notification';
public function getHeraldActionName() {
return pht('Deliver as notification');
}
public function supportsRuleType($rule_type) {
return ($rule_type == HeraldRuleTypeConfig::RULE_TYPE_PERSONAL);
}
public function applyEffect($object, HeraldEffect $effect) {
$rule = $effect->getRule();
$author_phid = $rule->getAuthorPHID();
$this->applyRouting(
$rule,
PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION,
array($author_phid));
}
public function getHeraldActionStandardType() {
return self::STANDARD_NONE;
}
public function renderActionDescription($value) {
return pht('Deliver as notification.');
}
}

View file

@ -14,6 +14,10 @@ abstract class PhabricatorMetaMTAEmailHeraldAction
return false;
}
if ($object instanceof PhabricatorMetaMTAMail) {
return false;
}
return true;
}

View file

@ -37,7 +37,7 @@ final class PhabricatorMetaMTAMailPHIDType extends PhabricatorPHIDType {
$handle
->setName($name)
->setFullName($name);
->setURI('/mail/detail/'.$id.'/');
}
}
}

View file

@ -18,6 +18,8 @@ final class PhabricatorMetaMTAActor extends Phobject {
const REASON_BOT = 'bot';
const REASON_FORCE = 'force';
const REASON_FORCE_HERALD = 'force-herald';
const REASON_ROUTE_AS_NOTIFICATION = 'route-as-notification';
const REASON_ROUTE_AS_MAIL = 'route-as-mail';
private $phid;
private $emailAddress;
@ -77,6 +79,7 @@ final class PhabricatorMetaMTAActor extends Phobject {
case self::REASON_NONE:
case self::REASON_FORCE:
case self::REASON_FORCE_HERALD:
case self::REASON_ROUTE_AS_MAIL:
return true;
default:
// All other reasons cause the message to not be delivered.
@ -99,6 +102,8 @@ final class PhabricatorMetaMTAActor extends Phobject {
self::REASON_UNLOADABLE => pht('Bad Recipient'),
self::REASON_FORCE => pht('Forced Mail'),
self::REASON_FORCE_HERALD => pht('Forced by Herald'),
self::REASON_ROUTE_AS_NOTIFICATION => pht('Route as Notification'),
self::REASON_ROUTE_AS_MAIL => pht('Route as Mail'),
);
return idx($names, $reason, pht('Unknown ("%s")', $reason));
@ -147,6 +152,12 @@ final class PhabricatorMetaMTAActor extends Phobject {
self::REASON_FORCE_HERALD => pht(
'This recipient was added by a "Send me an Email" rule in Herald, '.
'which overrides some delivery settings.'),
self::REASON_ROUTE_AS_NOTIFICATION => pht(
'This message was downgraded to a notification by outbound mail '.
'rules in Herald.'),
self::REASON_ROUTE_AS_MAIL => pht(
'This message was upgraded to email by outbound mail rules '.
'in Herald.'),
);
return idx($descriptions, $reason, pht('Unknown Reason ("%s")', $reason));

View file

@ -16,6 +16,7 @@ final class PhabricatorMetaMTAMail
protected $relatedPHID;
private $recipientExpansionMap;
private $routingMap;
public function __construct() {
@ -656,6 +657,9 @@ final class PhabricatorMetaMTAMail
}
$this->setParam('actors.sent', $actor_list);
$this->setParam('routing.sent', $this->getParam('routing'));
$this->setParam('routingmap.sent', $this->getRoutingRuleMap());
if (!$add_to && !$add_cc) {
$this->setStatus(PhabricatorMailOutboundStatus::STATUS_VOID);
$this->setMessage(
@ -963,6 +967,22 @@ final class PhabricatorMetaMTAMail
}
}
foreach ($deliverable as $phid) {
switch ($this->getRoutingRule($phid)) {
case PhabricatorMailRoutingRule::ROUTE_AS_NOTIFICATION:
$actors[$phid]->setUndeliverable(
PhabricatorMetaMTAActor::REASON_ROUTE_AS_NOTIFICATION);
break;
case PhabricatorMailRoutingRule::ROUTE_AS_MAIL:
$actors[$phid]->setDeliverable(
PhabricatorMetaMTAActor::REASON_ROUTE_AS_MAIL);
break;
default:
// No change.
break;
}
}
// If recipients were initially deliverable and were added by "Send me an
// email" Herald rules, annotate them as such and make them deliverable
// again, overriding any changes made by the "self mail" and "mail tags"
@ -1065,6 +1085,82 @@ final class PhabricatorMetaMTAMail
return $this->getParam('actors.sent');
}
public function getDeliveredRoutingRules() {
return $this->getParam('routing.sent');
}
public function getDeliveredRoutingMap() {
return $this->getParam('routingmap.sent');
}
/* -( Routing )------------------------------------------------------------ */
public function addRoutingRule($routing_rule, $phids, $reason_phid) {
$routing = $this->getParam('routing', array());
$routing[] = array(
'routingRule' => $routing_rule,
'phids' => $phids,
'reasonPHID' => $reason_phid,
);
$this->setParam('routing', $routing);
// Throw the routing map away so we rebuild it.
$this->routingMap = null;
return $this;
}
private function getRoutingRule($phid) {
$map = $this->getRoutingRuleMap();
$info = idx($map, $phid, idx($map, 'default'));
if ($info) {
return idx($info, 'rule');
}
return null;
}
private function getRoutingRuleMap() {
if ($this->routingMap === null) {
$map = array();
$routing = $this->getParam('routing', array());
foreach ($routing as $route) {
$phids = $route['phids'];
if ($phids === null) {
$phids = array('default');
}
foreach ($phids as $phid) {
$new_rule = $route['routingRule'];
$current_rule = idx($map, $phid);
if ($current_rule === null) {
$is_stronger = true;
} else {
$is_stronger = PhabricatorMailRoutingRule::isStrongerThan(
$new_rule,
$current_rule);
}
if ($is_stronger) {
$map[$phid] = array(
'rule' => $new_rule,
'reason' => $route['reasonPHID'],
);
}
}
}
$this->routingMap = $map;
}
return $this->routingMap;
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -1055,6 +1055,7 @@ abstract class PhabricatorApplicationTransactionEditor
}
if ($this->shouldPublishFeedStory($object, $xactions)) {
$mailed = array();
foreach ($messages as $mail) {
foreach ($mail->buildRecipientList() as $phid) {
@ -2299,6 +2300,8 @@ abstract class PhabricatorApplicationTransactionEditor
}
}
$this->runHeraldMailRules($messages);
return $messages;
}
@ -3140,4 +3143,16 @@ abstract class PhabricatorApplicationTransactionEditor
);
}
private function runHeraldMailRules(array $messages) {
foreach ($messages as $message) {
$engine = new HeraldEngine();
$adapter = id(new PhabricatorMailOutboundMailHeraldAdapter())
->setObject($message);
$rules = $engine->loadRulesForAdapter($adapter);
$effects = $engine->applyRules($rules, $adapter);
$engine->applyEffects($effects, $adapter, $rules);
}
}
}