Differential D13107 Diff 31771 src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
<?php | <?php | ||||
/** | /** | ||||
* @task mail Sending Mail | * @task mail Sending Mail | ||||
* @task feed Publishing Feed Stories | * @task feed Publishing Feed Stories | ||||
* @task search Search Index | * @task search Search Index | ||||
* @task files Integration with Files | * @task files Integration with Files | ||||
* @task workers Managing Workers | |||||
*/ | */ | ||||
abstract class PhabricatorApplicationTransactionEditor | abstract class PhabricatorApplicationTransactionEditor | ||||
extends PhabricatorEditor { | extends PhabricatorEditor { | ||||
private $contentSource; | private $contentSource; | ||||
private $object; | private $object; | ||||
private $xactions; | private $xactions; | ||||
Show All 9 Lines | abstract class PhabricatorApplicationTransactionEditor | ||||
private $applicationEmail; | private $applicationEmail; | ||||
private $isPreview; | private $isPreview; | ||||
private $isHeraldEditor; | private $isHeraldEditor; | ||||
private $isInverseEdgeEditor; | private $isInverseEdgeEditor; | ||||
private $actingAsPHID; | private $actingAsPHID; | ||||
private $disableEmail; | private $disableEmail; | ||||
private $heraldEmailPHIDs = array(); | |||||
private $heraldForcedEmailPHIDs = array(); | |||||
private $heraldHeader; | |||||
/** | /** | ||||
* Get the class name for the application this editor is a part of. | * Get the class name for the application this editor is a part of. | ||||
* | * | ||||
* Uninstalling the application will disable the editor. | * Uninstalling the application will disable the editor. | ||||
* | * | ||||
* @return string Editor's application class name. | * @return string Editor's application class name. | ||||
*/ | */ | ||||
▲ Show 20 Lines • Show All 815 Lines • ▼ Show 20 Lines | if ($this->getIsHeraldEditor()) { | ||||
->setActor($herald_actor) | ->setActor($herald_actor) | ||||
->setActingAsPHID($herald_phid) | ->setActingAsPHID($herald_phid) | ||||
->setContentSource($herald_source); | ->setContentSource($herald_source); | ||||
$herald_xactions = $herald_editor->applyTransactions( | $herald_xactions = $herald_editor->applyTransactions( | ||||
$object, | $object, | ||||
$herald_xactions); | $herald_xactions); | ||||
$adapter = $this->getHeraldAdapter(); | |||||
$this->heraldEmailPHIDs = $adapter->getEmailPHIDs(); | |||||
$this->heraldForcedEmailPHIDs = $adapter->getForcedEmailPHIDs(); | |||||
// Merge the new transactions into the transaction list: we want to | // Merge the new transactions into the transaction list: we want to | ||||
// send email and publish feed stories about them, too. | // send email and publish feed stories about them, too. | ||||
$xactions = array_merge($xactions, $herald_xactions); | $xactions = array_merge($xactions, $herald_xactions); | ||||
} | } | ||||
} | } | ||||
$this->didApplyTransactions($xactions); | |||||
if ($object instanceof PhabricatorCustomFieldInterface) { | |||||
// Maybe this makes more sense to move into the search index itself? For | |||||
// now I'm putting it here since I think we might end up with things that | |||||
// need it to be up to date once the next page loads, but if we don't go | |||||
// there we we could move it into search once search moves to the daemons. | |||||
// It now happens in the search indexer as well, but the search indexer is | |||||
// always daemonized, so the logic above still potentially holds. We could | |||||
// possibly get rid of this. The major motivation for putting it in the | |||||
// indexer was to enable reindexing to work. | |||||
$fields = PhabricatorCustomField::getObjectFields( | |||||
$object, | |||||
PhabricatorCustomField::ROLE_APPLICATIONSEARCH); | |||||
$fields->readFieldsFromStorage($object); | |||||
$fields->rebuildIndexes($object); | |||||
} | |||||
$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()); | |||||
} | |||||
$this->heraldHeader = $herald_header; | |||||
if ($this->supportsWorkers()) { | |||||
PhabricatorWorker::scheduleTask( | |||||
'PhabricatorApplicationTransactionPublishWorker', | |||||
array( | |||||
'objectPHID' => $object->getPHID(), | |||||
'actorPHID' => $this->getActingAsPHID(), | |||||
'xactionPHIDs' => mpull($xactions, 'getPHID'), | |||||
'state' => $this->getWorkerState(), | |||||
), | |||||
array( | |||||
'objectPHID' => $object->getPHID(), | |||||
'priority' => PhabricatorWorker::PRIORITY_ALERTS, | |||||
)); | |||||
} else { | |||||
$this->publishTransactions($object, $xactions); | |||||
} | |||||
return $xactions; | |||||
} | |||||
public function publishTransactions( | |||||
PhabricatorLiskDAO $object, | |||||
array $xactions) { | |||||
// Before sending mail or publishing feed stories, reload the object | // Before sending mail or publishing feed stories, reload the object | ||||
// subscribers to pick up changes caused by Herald (or by other side effects | // subscribers to pick up changes caused by Herald (or by other side effects | ||||
// in various transaction phases). | // in various transaction phases). | ||||
$this->loadSubscribers($object); | $this->loadSubscribers($object); | ||||
// Hook for other edges that may need (re-)loading | // Hook for other edges that may need (re-)loading | ||||
$this->loadEdges($object, $xactions); | $this->loadEdges($object, $xactions); | ||||
$this->loadHandles($xactions); | $this->loadHandles($xactions); | ||||
Show All 18 Lines | if ($this->shouldPublishFeedStory($object, $xactions)) { | ||||
$mailed = $mail->buildRecipientList(); | $mailed = $mail->buildRecipientList(); | ||||
} | } | ||||
$this->publishFeedStory( | $this->publishFeedStory( | ||||
$object, | $object, | ||||
$xactions, | $xactions, | ||||
$mailed); | $mailed); | ||||
} | } | ||||
$this->didApplyTransactions($xactions); | |||||
if ($object instanceof PhabricatorCustomFieldInterface) { | |||||
// Maybe this makes more sense to move into the search index itself? For | |||||
// now I'm putting it here since I think we might end up with things that | |||||
// need it to be up to date once the next page loads, but if we don't go | |||||
// there we we could move it into search once search moves to the daemons. | |||||
// It now happens in the search indexer as well, but the search indexer is | |||||
// always daemonized, so the logic above still potentially holds. We could | |||||
// possibly get rid of this. The major motivation for putting it in the | |||||
// indexer was to enable reindexing to work. | |||||
$fields = PhabricatorCustomField::getObjectFields( | |||||
$object, | |||||
PhabricatorCustomField::ROLE_APPLICATIONSEARCH); | |||||
$fields->readFieldsFromStorage($object); | |||||
$fields->rebuildIndexes($object); | |||||
} | |||||
return $xactions; | return $xactions; | ||||
} | } | ||||
protected function didApplyTransactions(array $xactions) { | protected function didApplyTransactions(array $xactions) { | ||||
// Hook for subclasses. | // Hook for subclasses. | ||||
return; | return; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 1,106 Lines • ▼ Show 20 Lines | protected function sendMail( | ||||
if (!$any_visible) { | if (!$any_visible) { | ||||
return; | return; | ||||
} | } | ||||
$email_force = array(); | $email_force = array(); | ||||
$email_to = $this->getMailTo($object); | $email_to = $this->getMailTo($object); | ||||
$email_cc = $this->getMailCC($object); | $email_cc = $this->getMailCC($object); | ||||
$adapter = $this->getHeraldAdapter(); | $email_cc = array_merge($email_cc, $this->heraldEmailPHIDs); | ||||
if ($adapter) { | $email_force = $this->heraldForcedEmailPHIDs; | ||||
$email_cc = array_merge($email_cc, $adapter->getEmailPHIDs()); | |||||
$email_force = $adapter->getForcedEmailPHIDs(); | |||||
} | |||||
$phids = array_merge($email_to, $email_cc); | $phids = array_merge($email_to, $email_cc); | ||||
$handles = id(new PhabricatorHandleQuery()) | $handles = id(new PhabricatorHandleQuery()) | ||||
->setViewer($this->requireActor()) | ->setViewer($this->requireActor()) | ||||
->withPHIDs($phids) | ->withPHIDs($phids) | ||||
->execute(); | ->execute(); | ||||
$template = $this->buildMailTemplate($object); | $template = $this->buildMailTemplate($object); | ||||
Show All 23 Lines | $template | ||||
->setIsBulk(true) | ->setIsBulk(true) | ||||
->setBody($body->render()) | ->setBody($body->render()) | ||||
->setHTMLBody($body->renderHTML()); | ->setHTMLBody($body->renderHTML()); | ||||
foreach ($body->getAttachments() as $attachment) { | foreach ($body->getAttachments() as $attachment) { | ||||
$template->addAttachment($attachment); | $template->addAttachment($attachment); | ||||
} | } | ||||
$herald_xscript = $this->getHeraldTranscript(); | if ($this->heraldHeader) { | ||||
if ($herald_xscript) { | $template->addHeader('X-Herald-Rules', $this->heraldHeader); | ||||
$herald_header = $herald_xscript->getXHeraldRulesHeader(); | |||||
$herald_header = HeraldTranscript::saveXHeraldRulesHeader( | |||||
$object->getPHID(), | |||||
$herald_header); | |||||
} else { | |||||
$herald_header = HeraldTranscript::loadXHeraldRulesHeader( | |||||
$object->getPHID()); | |||||
} | |||||
if ($herald_header) { | |||||
$template->addHeader('X-Herald-Rules', $herald_header); | |||||
} | } | ||||
if ($object instanceof PhabricatorProjectInterface) { | if ($object instanceof PhabricatorProjectInterface) { | ||||
$this->addMailProjectMetadata($object, $template); | $this->addMailProjectMetadata($object, $template); | ||||
} | } | ||||
if ($this->getParentMessageID()) { | if ($this->getParentMessageID()) { | ||||
$template->setParentMessageID($this->getParentMessageID()); | $template->setParentMessageID($this->getParentMessageID()); | ||||
▲ Show 20 Lines • Show All 649 Lines • ▼ Show 20 Lines | foreach ($nodes as $node) { | ||||
->setActor($this->requireActor()) | ->setActor($this->requireActor()) | ||||
->setActingAsPHID($this->getActingAsPHID()) | ->setActingAsPHID($this->getActingAsPHID()) | ||||
->setContentSource($this->getContentSource()); | ->setContentSource($this->getContentSource()); | ||||
$editor->applyTransactions($target, array($template)); | $editor->applyTransactions($target, array($template)); | ||||
} | } | ||||
} | } | ||||
/* -( Workers )------------------------------------------------------------ */ | |||||
/** | |||||
* @task workers | |||||
*/ | |||||
protected function supportsWorkers() { | |||||
// TODO: Remove this method once everything supports workers. | |||||
return false; | |||||
} | |||||
/** | |||||
* Convert the editor state to a serializable dictionary which can be passed | |||||
* to a worker. | |||||
* | |||||
* This data will be loaded with @{method:loadWorkerState} in the worker. | |||||
* | |||||
* @return dict<string, wild> Serializable editor state. | |||||
* @task workers | |||||
*/ | |||||
final private function getWorkerState() { | |||||
$state = array(); | |||||
foreach ($this->getAutomaticStateProperties() as $property) { | |||||
$state[$property] = $this->$property; | |||||
} | |||||
$state += array( | |||||
'excludeMailRecipientPHIDs' => $this->getExcludeMailRecipientPHIDs(), | |||||
); | |||||
return $state + array( | |||||
'custom' => $this->getCustomWorkerState(), | |||||
); | |||||
} | |||||
/** | |||||
* Hook; return custom properties which need to be passed to workers. | |||||
* | |||||
* @return dict<string, wild> Custom properties. | |||||
* @task workers | |||||
*/ | |||||
protected function getCustomWorkerState() { | |||||
return array(); | |||||
} | |||||
/** | |||||
* Load editor state using a dictionary emitted by @{method:getWorkerState}. | |||||
* | |||||
* This method is used to load state when running worker operations. | |||||
* | |||||
* @param dict<string, wild> Editor state, from @{method:getWorkerState}. | |||||
* @return this | |||||
* @task workers | |||||
*/ | |||||
final public function loadWorkerState(array $state) { | |||||
foreach ($this->getAutomaticStateProperties() as $property) { | |||||
$this->$property = idx($state, $property); | |||||
} | |||||
$exclude = idx($state, 'excludeMailRecipientPHIDs', array()); | |||||
$this->setExcludeMailRecipientPHIDs($exclude); | |||||
$custom = idx($state, 'custom', array()); | |||||
$this->loadCustomWorkerState($custom); | |||||
return $this; | |||||
} | |||||
/** | |||||
* Hook; set custom properties on the editor from data emitted by | |||||
* @{method:getCustomWorkerState}. | |||||
* | |||||
* @param dict<string, wild> Custom state, | |||||
* from @{method:getCustomWorkerState}. | |||||
* @return this | |||||
* @task workers | |||||
*/ | |||||
protected function loadCustomWorkerState(array $state) { | |||||
return $this; | |||||
} | |||||
/** | |||||
* Get a list of object properties which should be automatically sent to | |||||
* workers in the state data. | |||||
* | |||||
* These properties will be automatically stored and loaded by the editor in | |||||
* the worker. | |||||
* | |||||
* @return list<string> List of properties. | |||||
* @task workers | |||||
*/ | |||||
private function getAutomaticStateProperties() { | |||||
return array( | |||||
'parentMessageID', | |||||
'disableEmail', | |||||
'isNewObject', | |||||
'heraldEmailPHIDs', | |||||
'heraldForcedEmailPHIDs', | |||||
'heraldHeader', | |||||
); | |||||
} | |||||
} | } |