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 @@ -134,10 +134,7 @@ $parent_xaction->setMetadataValue('blocker.new', true); } - id(new ManiphestTransactionEditor()) - ->setActor($this->getActor()) - ->setActingAsPHID($this->getActingAsPHID()) - ->setContentSource($this->getContentSource()) + $this->newSubEditor() ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) ->applyTransactions($blocked_task, array($parent_xaction)); 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 @@ -72,7 +72,7 @@ private $mailShouldSend = false; private $modularTypes; private $silent; - private $mustEncrypt; + private $mustEncrypt = array(); private $stampTemplates = array(); private $mailStamps = array(); private $oldTo = array(); @@ -90,6 +90,11 @@ private $cancelURI; private $extensions; + private $parentEditor; + private $subEditors = array(); + private $publishableObject; + private $publishableTransactions; + const STORAGE_ENCODING_BINARY = 'binary'; /** @@ -1272,10 +1277,9 @@ $herald_source = PhabricatorContentSource::newForSource( PhabricatorHeraldContentSource::SOURCECONST); - $herald_editor = newv(get_class($this), array()) + $herald_editor = $this->newEditorCopy() ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) - ->setParentMessageID($this->getParentMessageID()) ->setIsHeraldEditor(true) ->setActor($herald_actor) ->setActingAsPHID($herald_phid) @@ -1330,6 +1334,38 @@ } $this->heraldHeader = $herald_header; + // See PHI1134. If we're a subeditor, we don't publish information about + // the edit yet. Our parent editor still needs to finish applying + // transactions and execute Herald, which may change the information we + // publish. + + // For example, Herald actions may change the parent object's title or + // visibility, or Herald may apply rules like "Must Encrypt" that affect + // email. + + // Once the parent finishes work, it will queue its own publish step and + // then queue publish steps for its children. + + $this->publishableObject = $object; + $this->publishableTransactions = $xactions; + if (!$this->parentEditor) { + $this->queuePublishing(); + } + + return $xactions; + } + + final private function queuePublishing() { + $object = $this->publishableObject; + $xactions = $this->publishableTransactions; + + if (!$object) { + throw new Exception( + pht( + 'Editor method "queuePublishing()" was called, but no publishable '. + 'object is present. This Editor is not ready to publish.')); + } + // We're going to compute some of the data we'll use to publish these // transactions here, before queueing a worker. // @@ -1392,9 +1428,11 @@ 'priority' => PhabricatorWorker::PRIORITY_ALERTS, )); - $this->flushTransactionQueue($object); + foreach ($this->subEditors as $sub_editor) { + $sub_editor->queuePublishing(); + } - return $xactions; + $this->flushTransactionQueue($object); } protected function didCatchDuplicateKeyException( @@ -3818,6 +3856,11 @@ $this->mustEncrypt = $adapter->getMustEncryptReasons(); + // See PHI1134. Propagate "Must Encrypt" state to sub-editors. + foreach ($this->subEditors as $sub_editor) { + $sub_editor->mustEncrypt = $this->mustEncrypt; + } + $apply_xactions = $this->didApplyHeraldRules($object, $adapter, $xscript); assert_instances_of($apply_xactions, 'PhabricatorApplicationTransaction'); @@ -4034,15 +4077,10 @@ ->setOldValue($old_phids) ->setNewValue($new_phids); - $editor + $editor = $this->newSubEditor($editor) ->setContinueOnNoEffect(true) ->setContinueOnMissingFields(true) - ->setParentMessageID($this->getParentMessageID()) - ->setIsInverseEdgeEditor(true) - ->setIsSilent($this->getIsSilent()) - ->setActor($this->requireActor()) - ->setActingAsPHID($this->getActingAsPHID()) - ->setContentSource($this->getContentSource()); + ->setIsInverseEdgeEditor(true); $editor->applyTransactions($node, array($template)); } @@ -4551,23 +4589,41 @@ $xactions = $this->transactionQueue; $this->transactionQueue = array(); - $editor = $this->newQueueEditor(); + $editor = $this->newEditorCopy(); return $editor->applyTransactions($object, $xactions); } - private function newQueueEditor() { - $editor = id(newv(get_class($this), array())) + final protected function newSubEditor( + PhabricatorApplicationTransactionEditor $template = null) { + $editor = $this->newEditorCopy($template); + + $editor->parentEditor = $this; + $this->subEditors[] = $editor; + + return $editor; + } + + private function newEditorCopy( + PhabricatorApplicationTransactionEditor $template = null) { + if ($template === null) { + $template = newv(get_class($this), array()); + } + + $editor = id(clone $template) ->setActor($this->getActor()) ->setContentSource($this->getContentSource()) ->setContinueOnNoEffect($this->getContinueOnNoEffect()) ->setContinueOnMissingFields($this->getContinueOnMissingFields()) + ->setParentMessageID($this->getParentMessageID()) ->setIsSilent($this->getIsSilent()); if ($this->actingAsPHID !== null) { $editor->setActingAsPHID($this->actingAsPHID); } + $editor->mustEncrypt = $this->mustEncrypt; + return $editor; }