diff --git a/src/applications/pholio/editor/PholioMockEditor.php b/src/applications/pholio/editor/PholioMockEditor.php index d721a60b2a..ff28ba73bd 100644 --- a/src/applications/pholio/editor/PholioMockEditor.php +++ b/src/applications/pholio/editor/PholioMockEditor.php @@ -19,7 +19,6 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { protected function didApplyTransactions( PhabricatorLiskDAO $object, array $xactions) { -// $this->sendMail($object, $xactions); // PholioIndexer::indexMock($mock); return; } @@ -84,92 +83,46 @@ final class PholioMockEditor extends PhabricatorApplicationTransactionEditor { return parent::mergeTransactions($u, $v); } + protected function supportsMail() { + return true; + } - private function sendMail( - PholioMock $mock, - array $xactions, - $is_new, - array $mentioned_phids) { + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + return id(new PholioReplyHandler()) + ->setMailReceiver($object); + } - $subscribed_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( - $mock->getPHID()); + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + $id = $object->getID(); + $name = $object->getName(); + $original_name = $object->getOriginalName(); - $email_to = array( - $mock->getAuthorPHID(), + return id(new PhabricatorMetaMTAMail()) + ->setSubject("M{$id}: {$name}") + ->addHeader('Thread-Topic', "M{$id}: {$original_name}"); + } + + protected function getMailTo(PhabricatorLiskDAO $object) { + return array( + $object->getAuthorPHID(), $this->requireActor()->getPHID(), ); - $email_cc = $subscribed_phids; - - $phids = array_merge($email_to, $email_cc); - $handles = id(new PhabricatorObjectHandleData($phids)) - ->setViewer($this->requireActor()) - ->loadHandles(); - - $mock_id = $mock->getID(); - $name = $mock->getName(); - $original_name = $mock->getOriginalName(); - - $thread_id = 'pholio-mock-'.$mock->getPHID(); - - $mail_tags = $this->getMailTags($mock, $xactions); - - $body = new PhabricatorMetaMTAMailBody(); - $body->addRawSection('lorem ipsum'); - - $mock_uri = PhabricatorEnv::getProductionURI('/M'.$mock->getID()); - - $body->addTextSection(pht('MOCK DETAIL'), $mock_uri); - - $reply_handler = $this->buildReplyHandler($mock); - - $template = id(new PhabricatorMetaMTAMail()) - ->setSubject("M{$mock_id}: {$name}") - ->setSubjectPrefix($this->getMailSubjectPrefix()) - ->setVarySubjectPrefix('[edit/create?]') - ->setFrom($this->requireActor()->getPHID()) - ->addHeader('Thread-Topic', "M{$mock_id}: {$original_name}") - ->setThreadID($thread_id, $is_new) - ->setRelatedPHID($mock->getPHID()) - ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) - ->setIsBulk(true) - ->setMailTags($mail_tags) - ->setBody($body->render()); - - // TODO - // ->setParentMessageID(...) - - $mails = $reply_handler->multiplexMail( - $template, - array_select_keys($handles, $email_to), - array_select_keys($handles, $email_cc)); - - foreach ($mails as $mail) { - $mail->saveAndSend(); - } - - $template->addTos($email_to); - $template->addCCs($email_cc); - - return $template; } - private function getMailTags(PholioMock $mock, array $xactions) { - assert_instances_of($xactions, 'PholioTransaction'); - $tags = array(); + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { - return $tags; + $body = parent::buildMailBody($object, $xactions); + $body->addTextSection( + pht('MOCK DETAIL'), + PhabricatorEnv::getProductionURI('/M'.$object->getID())); + + return $body; } - public function buildReplyHandler(PholioMock $mock) { - $handler_object = new PholioReplyHandler(); - $handler_object->setMailReceiver($mock); - - return $handler_object; - } - - private function getMailSubjectPrefix() { + protected function getMailSubjectPrefix() { return PhabricatorEnv::getEnvConfig('metamta.pholio.subject-prefix'); } - } diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index 1b8c8d3d6e..51a0e4cc3c 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -1,5 +1,8 @@ sortTransactions($xactions); $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) @@ -230,7 +237,15 @@ abstract class PhabricatorApplicationTransactionEditor } $object->saveTransaction(); - // TODO: Send mail. + + $this->loadHandles($xactions); + + + $mail = null; + if ($this->supportsMail()) { + $mail = $this->sendMail($object, $xactions); + } + // TODO: Index object. // TODO: Publish feed/notifications. @@ -239,6 +254,26 @@ abstract class PhabricatorApplicationTransactionEditor return $this; } + private function loadHandles(array $xactions) { + $phids = array(); + foreach ($xactions as $xaction) { + $phids[$xaction->getPHID()] = $xaction->getRequiredHandlePHIDs(); + } + $handles = array(); + $merged = array_mergev($phids); + if ($merged) { + $handles = id(new PhabricatorObjectHandleData($merged)) + ->setViewer($this->requireActor()) + ->loadHandles(); + } + foreach ($xactions as $xaction) { + $xaction->setHandles( + array_select_keys( + $handles, + $phids[$xaction->getPHID()])); + } + } + private function validateEditParameters( PhabricatorLiskDAO $object, array $xactions) { @@ -484,4 +519,168 @@ abstract class PhabricatorApplicationTransactionEditor } +/* -( Sending Mail )------------------------------------------------------- */ + + + /** + * @task mail + */ + protected function supportsMail() { + return false; + } + + + /** + * @task mail + */ + protected function sendMail( + PhabricatorLiskDAO $object, + array $xactions) { + + $email_to = $this->getMailTo($object); + $email_cc = $this->getMailCC($object); + + $phids = array_merge($email_to, $email_cc); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->setViewer($this->requireActor()) + ->loadHandles(); + + $template = $this->buildMailTemplate($object); + $body = $this->buildMailBody($object, $xactions); + + $mail_tags = $this->getMailTags($object, $xactions); + + $action = $this->getStrongestAction($object, $xactions); + + $template + ->setFrom($this->requireActor()->getPHID()) + ->setSubjectPrefix($this->getMailSubjectPrefix()) + ->setVarySubjectPrefix('['.$action.']') + ->setThreadID($object->getPHID(), $this->getIsNewObject()) + ->setRelatedPHID($object->getPHID()) + ->setExcludeMailRecipientPHIDs($this->getExcludeMailRecipientPHIDs()) + ->setMailTags($mail_tags) + ->setIsBulk(true) + ->setBody($body->render()); + + // TODO + // ->setParentMessageID(...) + + $mails = $this + ->buildReplyHandler($object) + ->multiplexMail( + $template, + array_select_keys($handles, $email_to), + array_select_keys($handles, $email_cc)); + + foreach ($mails as $mail) { + $mail->saveAndSend(); + } + + $template->addTos($email_to); + $template->addCCs($email_cc); + + return $template; + } + + + /** + * @task mail + */ + protected function getStrongestAction( + PhabricatorLiskDAO $object, + array $xactions) { + return last(msort($xactions, 'getActionStrength'))->getActionName(); + } + + + /** + * @task mail + */ + protected function buildReplyHandler(PhabricatorLiskDAO $object) { + throw new Exception("Capability not supported."); + } + + + /** + * @task mail + */ + protected function getMailSubjectPrefix() { + throw new Exception("Capability not supported."); + } + + + /** + * @task mail + */ + protected function getMailTags( + PhabricatorLiskDAO $object, + array $xactions) { + $tags = array(); + + foreach ($xactions as $xaction) { + $tags[] = $xaction->getMailTags(); + } + + return array_mergev($tags); + } + + + /** + * @task mail + */ + protected function buildMailTemplate(PhabricatorLiskDAO $object) { + throw new Exception("Capability not supported."); + } + + + /** + * @task mail + */ + protected function getMailTo(PhabricatorLiskDAO $object) { + throw new Exception("Capability not supported."); + } + + + /** + * @task mail + */ + protected function getMailCC(PhabricatorLiskDAO $object) { + if ($object instanceof PhabricatorSubscribableInterface) { + $phid = $object->getPHID(); + return PhabricatorSubscribersQuery::loadSubscribersForPHID($phid); + } + throw new Exception("Capability not supported."); + } + + + /** + * @task mail + */ + protected function buildMailBody( + PhabricatorLiskDAO $object, + array $xactions) { + + $headers = array(); + $comments = array(); + + foreach ($xactions as $xaction) { + $headers[] = id(clone $xaction)->setRenderingTarget('text')->getTitle(); + $comment = $xaction->getComment(); + if ($comment && strlen($comment->getContent())) { + $comments[] = $comment->getContent(); + } + } + + $body = new PhabricatorMetaMTAMailBody(); + $body->addRawSection(implode("\n", $headers)); + + foreach ($comments as $comment) { + $body->addRawSection($comment); + } + + return $body; + } + + } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 45c3e177f3..f669011711 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -6,6 +6,9 @@ abstract class PhabricatorApplicationTransaction const MARKUP_FIELD_COMMENT = 'markup:comment'; + const TARGET_TEXT = 'text'; + const TARGET_HTML = 'html'; + protected $phid; protected $objectPHID; protected $authorPHID; @@ -25,6 +28,7 @@ abstract class PhabricatorApplicationTransaction private $commentNotLoaded; private $handles; + private $renderingTarget = self::TARGET_HTML; abstract public function getApplicationTransactionType(); abstract public function getApplicationTransactionCommentObject(); @@ -77,6 +81,15 @@ abstract class PhabricatorApplicationTransaction /* -( Rendering )---------------------------------------------------------- */ + public function setRenderingTarget($rendering_target) { + $this->renderingTarget = $rendering_target; + return $this; + } + + public function getRenderingTarget() { + return $this->renderingTarget; + } + public function getRequiredHandlePHIDs() { $phids = array(); @@ -108,7 +121,11 @@ abstract class PhabricatorApplicationTransaction } protected function renderHandleLink($phid) { - return $this->getHandle($phid)->renderLink(); + if ($this->renderingTarget == self::TARGET_HTML) { + return $this->getHandle($phid)->renderLink(); + } else { + return $this->getHandle($phid)->getName(); + } } protected function renderHandleList(array $phids) { @@ -203,6 +220,32 @@ abstract class PhabricatorApplicationTransaction } } + public function getActionStrength() { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + return 0.5; + } + return 1.0; + } + + public function getActionName() { + switch ($this->getTransactionType()) { + case PhabricatorTransactions::TYPE_COMMENT: + return pht('Commented On'); + case PhabricatorTransactions::TYPE_VIEW_POLICY: + case PhabricatorTransactions::TYPE_EDIT_POLICY: + return pht('Changed Policy'); + case PhabricatorTransactions::TYPE_SUBSCRIBERS: + return pht('Changed Subscribers'); + default: + return pht('Updated'); + } + } + + public function getMailTags() { + return array(); + } + /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ diff --git a/webroot/rsrc/js/application/core/behavior-phabricator-nav.js b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js index cb7c6d5389..6f0e14da57 100644 --- a/webroot/rsrc/js/application/core/behavior-phabricator-nav.js +++ b/webroot/rsrc/js/application/core/behavior-phabricator-nav.js @@ -112,14 +112,16 @@ JX.behavior('phabricator-nav', function(config) { // When the user scrolls down on the desktop, we move the local nav up until // it hits the top of the page. - JX.Stratcom.listen(['scroll', 'resize'], null, function(e) { - if (JX.Device.getDevice() != 'desktop') { - return; - } + if (local) { + JX.Stratcom.listen(['scroll', 'resize'], null, function(e) { + if (JX.Device.getDevice() != 'desktop') { + return; + } - var y = Math.max(0, config.menuSize - JX.Vector.getScroll().y); - local.style.top = y + 'px'; - }); + var y = Math.max(0, config.menuSize - JX.Vector.getScroll().y); + local.style.top = y + 'px'; + }); + } // - Navigation Reset ----------------------------------------------------------