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', | |||||
| ); | |||||
| } | |||||
| } | } | ||||