diff --git a/src/applications/files/controller/PhabricatorFileCommentController.php b/src/applications/files/controller/PhabricatorFileCommentController.php index 16e6e3f3a9..c527e6fc12 100644 --- a/src/applications/files/controller/PhabricatorFileCommentController.php +++ b/src/applications/files/controller/PhabricatorFileCommentController.php @@ -1,69 +1,68 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $file = id(new PhabricatorFileQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$file) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $view_uri = $file->getInfoURI(); $xactions = array(); $xactions[] = id(new PhabricatorFileTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PhabricatorFileTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorFileEditor()) ->setActor($user) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($file, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php index 823e150010..f0f557d67f 100644 --- a/src/applications/legalpad/controller/LegalpadDocumentCommentController.php +++ b/src/applications/legalpad/controller/LegalpadDocumentCommentController.php @@ -1,79 +1,78 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $document = id(new LegalpadDocumentQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needDocumentBodies(true) ->executeOne(); if (!$document) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $document_uri = $this->getApplicationURI('view/'.$document->getID()); $comment = $request->getStr('comment'); $xactions = array(); if (strlen($comment)) { $xactions[] = id(new LegalpadTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new LegalpadTransactionComment()) ->setDocumentID($document->getID()) ->setLineNumber(0) ->setLineLength(0) ->setContent($comment)); } $editor = id(new LegalpadDocumentEditor()) ->setActor($user) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect($request->isContinueRequest()) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($document, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($document_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse())->setURI($document_uri); } } } diff --git a/src/applications/macro/controller/PhabricatorMacroCommentController.php b/src/applications/macro/controller/PhabricatorMacroCommentController.php index a53adc689e..f438e9b31a 100644 --- a/src/applications/macro/controller/PhabricatorMacroCommentController.php +++ b/src/applications/macro/controller/PhabricatorMacroCommentController.php @@ -1,70 +1,69 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $macro = id(new PhabricatorMacroQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$macro) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $view_uri = $this->getApplicationURI('/view/'.$macro->getID().'/'); $xactions = array(); $xactions[] = id(new PhabricatorMacroTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PhabricatorMacroTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorMacroEditor()) ->setActor($user) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($macro, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/paste/controller/PhabricatorPasteCommentController.php b/src/applications/paste/controller/PhabricatorPasteCommentController.php index 44727ecef5..cd35c4a36e 100644 --- a/src/applications/paste/controller/PhabricatorPasteCommentController.php +++ b/src/applications/paste/controller/PhabricatorPasteCommentController.php @@ -1,70 +1,69 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $paste = id(new PhabricatorPasteQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$paste) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $view_uri = $paste->getURI(); $xactions = array(); $xactions[] = id(new PhabricatorPasteTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PhabricatorPasteTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorPasteEditor()) ->setActor($user) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($paste, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/pholio/controller/PholioMockCommentController.php b/src/applications/pholio/controller/PholioMockCommentController.php index a862e89adc..d0c55ec66a 100644 --- a/src/applications/pholio/controller/PholioMockCommentController.php +++ b/src/applications/pholio/controller/PholioMockCommentController.php @@ -1,91 +1,90 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $mock = id(new PholioMockQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->needImages(true) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $mock_uri = '/M'.$mock->getID(); $comment = $request->getStr('comment'); $xactions = array(); $inline_comments = id(new PholioTransactionComment())->loadAllWhere( 'authorphid = %s AND transactionphid IS NULL AND imageid IN (%Ld)', $user->getPHID(), mpull($mock->getImages(), 'getID')); if (!$inline_comments || strlen($comment)) { $xactions[] = id(new PholioTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PholioTransactionComment()) ->setContent($comment)); } foreach ($inline_comments as $inline_comment) { $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransactionType::TYPE_INLINE) ->attachComment($inline_comment); } $editor = id(new PholioMockEditor()) ->setActor($user) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect($request->isContinueRequest()) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($mock, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($mock_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { $xaction_view = id(new PholioTransactionView()) ->setMock($mock); return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) ->setTransactionView($xaction_view) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse())->setURI($mock_uri); } } } diff --git a/src/applications/ponder/controller/PonderAnswerCommentController.php b/src/applications/ponder/controller/PonderAnswerCommentController.php index 6c22111833..b59dd4259c 100644 --- a/src/applications/ponder/controller/PonderAnswerCommentController.php +++ b/src/applications/ponder/controller/PonderAnswerCommentController.php @@ -1,71 +1,70 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $answer = id(new PonderAnswerQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$answer) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); // $draft = PhabricatorDraft::buildFromRequest($request); $qid = $answer->getQuestion()->getID(); $aid = $answer->getID(); $view_uri = "Q{$qid}#A{$aid}"; $xactions = array(); $xactions[] = id(new PonderAnswerTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PonderAnswerTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PonderAnswerEditor()) ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($answer, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } // if ($draft) { // $draft->replaceOrDelete(); // } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($viewer) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/ponder/controller/PonderQuestionCommentController.php b/src/applications/ponder/controller/PonderQuestionCommentController.php index 814a8857e8..f4d56db622 100644 --- a/src/applications/ponder/controller/PonderQuestionCommentController.php +++ b/src/applications/ponder/controller/PonderQuestionCommentController.php @@ -1,70 +1,69 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $question = id(new PonderQuestionQuery()) ->setViewer($viewer) ->withIDs(array($this->id)) ->executeOne(); if (!$question) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); // $draft = PhabricatorDraft::buildFromRequest($request); $qid = $question->getID(); $view_uri = "/Q{$qid}"; $xactions = array(); $xactions[] = id(new PonderQuestionTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PonderQuestionTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PonderQuestionEditor()) ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($question, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } // if ($draft) { // $draft->replaceOrDelete(); // } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($viewer) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/releeph/controller/request/ReleephRequestCommentController.php b/src/applications/releeph/controller/request/ReleephRequestCommentController.php index 5e7e6e7b2c..4c728341af 100644 --- a/src/applications/releeph/controller/request/ReleephRequestCommentController.php +++ b/src/applications/releeph/controller/request/ReleephRequestCommentController.php @@ -1,70 +1,69 @@ requestID = $data['requestID']; } public function processRequest() { $request = $this->getRequest(); $viewer = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $pull = id(new ReleephRequestQuery()) ->setViewer($viewer) ->withIDs(array($this->requestID)) ->executeOne(); if (!$pull) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $view_uri = $this->getApplicationURI('/'.$pull->getMonogram()); $xactions = array(); $xactions[] = id(new ReleephRequestTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new ReleephRequestTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new ReleephRequestTransactionalEditor()) ->setActor($viewer) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($pull, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($viewer) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php b/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php index f0046df374..588f06b649 100644 --- a/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php +++ b/src/applications/slowvote/controller/PhabricatorSlowvoteCommentController.php @@ -1,70 +1,69 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if (!$request->isFormPost()) { return new Aphront400Response(); } $poll = id(new PhabricatorSlowvoteQuery()) ->setViewer($user) ->withIDs(array($this->id)) ->executeOne(); if (!$poll) { return new Aphront404Response(); } $is_preview = $request->isPreviewRequest(); $draft = PhabricatorDraft::buildFromRequest($request); $view_uri = '/V'.$poll->getID(); $xactions = array(); $xactions[] = id(new PhabricatorSlowvoteTransaction()) ->setTransactionType(PhabricatorTransactions::TYPE_COMMENT) ->attachComment( id(new PhabricatorSlowvoteTransactionComment()) ->setContent($request->getStr('comment'))); $editor = id(new PhabricatorSlowvoteEditor()) ->setActor($user) ->setContinueOnNoEffect($request->isContinueRequest()) ->setContentSourceFromRequest($request) ->setIsPreview($is_preview); try { $xactions = $editor->applyTransactions($poll, $xactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse()) ->setCancelURI($view_uri) ->setException($ex); } if ($draft) { $draft->replaceOrDelete(); } if ($request->isAjax() && $is_preview) { return id(new PhabricatorApplicationTransactionResponse()) ->setViewer($user) ->setTransactions($xactions) - ->setIsPreview($is_preview) - ->setAnchorOffset($request->getStr('anchor')); + ->setIsPreview($is_preview); } else { return id(new AphrontRedirectResponse()) ->setURI($view_uri); } } } diff --git a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php index cf936d9a11..7d4066cfda 100644 --- a/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php +++ b/src/applications/transactions/response/PhabricatorApplicationTransactionResponse.php @@ -1,93 +1,79 @@ transactionView = $transaction_view; return $this; } public function getTransactionView() { return $this->transactionView; } protected function buildProxy() { return new AphrontAjaxResponse(); } - public function setAnchorOffset($anchor_offset) { - $this->anchorOffset = $anchor_offset; - return $this; - } - - public function getAnchorOffset() { - return $this->anchorOffset; - } - public function setTransactions($transactions) { assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); $this->transactions = $transactions; return $this; } public function getTransactions() { return $this->transactions; } public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } public function reduceProxyResponse() { if ($this->transactionView) { $view = $this->transactionView; } else if ($this->getTransactions()) { $view = head($this->getTransactions()) ->getApplicationTransactionViewObject(); } else { $view = new PhabricatorApplicationTransactionView(); } $view ->setUser($this->getViewer()) ->setTransactions($this->getTransactions()) ->setIsPreview($this->isPreview); - if ($this->getAnchorOffset()) { - $view->setAnchorOffset($this->getAnchorOffset()); - } - if ($this->isPreview) { $xactions = mpull($view->buildEvents(), 'render'); } else { $xactions = mpull($view->buildEvents(), 'render', 'getTransactionPHID'); } $content = array( 'xactions' => $xactions, 'spacer' => PHUITimelineView::renderSpacer(), ); return $this->getProxy()->setContent($content); } } diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index 0f916b2365..b651f387cc 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -1,1120 +1,1128 @@ ignoreOnNoEffect = $ignore; return $this; } public function getIgnoreOnNoEffect() { return $this->ignoreOnNoEffect; } public function shouldGenerateOldValue() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_BUILDABLE: case PhabricatorTransactions::TYPE_TOKEN: case PhabricatorTransactions::TYPE_CUSTOMFIELD: return false; } return true; } abstract public function getApplicationTransactionType(); private function getApplicationObjectTypeName() { $types = PhabricatorPHIDType::getAllTypes(); $type = idx($types, $this->getApplicationTransactionType()); if ($type) { return $type->getTypeName(); } return pht('Object'); } public function getApplicationTransactionCommentObject() { throw new PhutilMethodNotImplementedException(); } public function getApplicationTransactionViewObject() { return new PhabricatorApplicationTransactionView(); } public function getMetadataValue($key, $default = null) { return idx($this->metadata, $key, $default); } public function setMetadataValue($key, $value) { $this->metadata[$key] = $value; return $this; } public function generatePHID() { $type = PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST; $subtype = $this->getApplicationTransactionType(); return PhabricatorPHID::generateNewPHID($type, $subtype); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'oldValue' => self::SERIALIZATION_JSON, 'newValue' => self::SERIALIZATION_JSON, 'metadata' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function setContentSource(PhabricatorContentSource $content_source) { $this->contentSource = $content_source->serialize(); return $this; } public function getContentSource() { return PhabricatorContentSource::newFromSerialized($this->contentSource); } public function hasComment() { return $this->getComment() && strlen($this->getComment()->getContent()); } public function getComment() { if ($this->commentNotLoaded) { throw new Exception('Comment for this transaction was not loaded.'); } return $this->comment; } public function attachComment( PhabricatorApplicationTransactionComment $comment) { $this->comment = $comment; $this->commentNotLoaded = false; return $this; } public function setCommentNotLoaded($not_loaded) { $this->commentNotLoaded = $not_loaded; return $this; } public function attachObject($object) { $this->object = $object; return $this; } public function getObject() { return $this->assertAttached($this->object); } public function getRemarkupBlocks() { $blocks = array(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { $custom_blocks = $field->getApplicationTransactionRemarkupBlocks( $this); foreach ($custom_blocks as $custom_block) { $blocks[] = $custom_block; } } break; } if ($this->getComment()) { $blocks[] = $this->getComment()->getContent(); } return $blocks; } public function setOldValue($value) { $this->oldValueHasBeenSet = true; $this->writeField('oldValue', $value); return $this; } public function hasOldValue() { return $this->oldValueHasBeenSet; } /* -( Rendering )---------------------------------------------------------- */ public function setRenderingTarget($rendering_target) { $this->renderingTarget = $rendering_target; return $this; } public function getRenderingTarget() { return $this->renderingTarget; } public function attachViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } public function getViewer() { return $this->assertAttached($this->viewer); } public function getRequiredHandlePHIDs() { $phids = array(); $old = $this->getOldValue(); $new = $this->getNewValue(); $phids[] = array($this->getAuthorPHID()); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { $phids[] = $field->getApplicationTransactionRequiredHandlePHIDs( $this); } break; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $phids[] = $old; $phids[] = $new; break; case PhabricatorTransactions::TYPE_EDGE: $phids[] = ipull($old, 'dst'); $phids[] = ipull($new, 'dst'); break; case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) { $phids[] = array($old); } if (!PhabricatorPolicyQuery::isGlobalPolicy($new)) { $phids[] = array($new); } break; case PhabricatorTransactions::TYPE_TOKEN: break; case PhabricatorTransactions::TYPE_BUILDABLE: $phid = $this->getMetadataValue('harbormaster:buildablePHID'); if ($phid) { $phids[] = array($phid); } break; } if ($this->getComment()) { $phids[] = array($this->getComment()->getAuthorPHID()); } return array_mergev($phids); } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function getHandle($phid) { if (empty($this->handles[$phid])) { throw new Exception( pht( 'Transaction ("%s", of type "%s") requires a handle ("%s") that it '. 'did not load.', $this->getPHID(), $this->getTransactionType(), $phid)); } return $this->handles[$phid]; } public function getHandleIfExists($phid) { return idx($this->handles, $phid); } public function getHandles() { if ($this->handles === null) { throw new Exception( 'Transaction requires handles and it did not load them.' ); } return $this->handles; } public function renderHandleLink($phid) { if ($this->renderingTarget == self::TARGET_HTML) { return $this->getHandle($phid)->renderLink(); } else { return $this->getHandle($phid)->getLinkName(); } } public function renderHandleList(array $phids) { $links = array(); foreach ($phids as $phid) { $links[] = $this->renderHandleLink($phid); } if ($this->renderingTarget == self::TARGET_HTML) { return phutil_implode_html(', ', $links); } else { return implode(', ', $links); } } private function renderSubscriberList(array $phids, $change_type) { if ($this->getRenderingTarget() == self::TARGET_TEXT) { return $this->renderHandleList($phids); } else { $handles = array_select_keys($this->getHandles(), $phids); return id(new SubscriptionListStringBuilder()) ->setHandles($handles) ->setObjectPHID($this->getPHID()) ->buildTransactionString($change_type); } } protected function renderPolicyName($phid, $state = 'old') { $policy = PhabricatorPolicy::newFromPolicyAndHandle( $phid, $this->getHandleIfExists($phid)); if ($this->renderingTarget == self::TARGET_HTML) { switch ($policy->getType()) { case PhabricatorPolicyType::TYPE_CUSTOM: $policy->setHref('/transactions/'.$state.'/'.$this->getPHID().'/'); $policy->setWorkflow(true); break; default: break; } $output = $policy->renderDescription(); } else { $output = hsprintf('%s', $policy->getFullName()); } return $output; } public function getIcon() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $comment = $this->getComment(); if ($comment && $comment->getIsRemoved()) { return 'fa-eraser'; } return 'fa-comment'; case PhabricatorTransactions::TYPE_SUBSCRIBERS: return 'fa-envelope'; case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: return 'fa-lock'; case PhabricatorTransactions::TYPE_EDGE: return 'fa-link'; case PhabricatorTransactions::TYPE_BUILDABLE: return 'fa-wrench'; case PhabricatorTransactions::TYPE_TOKEN: return 'fa-trophy'; } return 'fa-pencil'; } public function getToken() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: $old = $this->getOldValue(); $new = $this->getNewValue(); if ($new) { $icon = substr($new, 10); } else { $icon = substr($old, 10); } return array($icon, !$this->getNewValue()); } return array(null, null); } public function getColor() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT; $comment = $this->getComment(); if ($comment && $comment->getIsRemoved()) { return 'black'; } break; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_PASSED: return 'green'; case HarbormasterBuildable::STATUS_FAILED: return 'red'; } break; } return null; } protected function getTransactionCustomField() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $key = $this->getMetadataValue('customfield:key'); if (!$key) { return null; } $field = PhabricatorCustomField::getObjectField( $this->getObject(), PhabricatorCustomField::ROLE_APPLICATIONTRANSACTIONS, $key); if (!$field) { return null; } $field->setViewer($this->getViewer()); return $field; } return null; } public function shouldHide() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: if ($this->getOldValue() === null) { return true; } else { return false; } break; case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->shouldHideInApplicationTransactions($this); } case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObject::EDGECONST: return true; break; case PhabricatorObjectMentionedByObject::EDGECONST: + $new = ipull($this->getNewValue(), 'dst'); + $old = ipull($this->getOldValue(), 'dst'); + $add = array_diff($new, $old); + $add_value = reset($add); + $add_handle = $this->getHandle($add_value); + if ($add_handle->getPolicyFiltered()) { + return true; + } return false; break; default: break; } break; } return false; } public function shouldHideForMail(array $xactions) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_FAILED: // For now, only ever send mail when builds fail. We might let // you customize this later, but in most cases this is probably // completely uninteresting. return false; } return true; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObject::EDGECONST: case PhabricatorObjectMentionedByObject::EDGECONST: return true; break; default: break; } break; } return $this->shouldHide(); } public function shouldHideForFeed() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_TOKEN: return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_FAILED: // For now, don't notify on build passes either. These are pretty // high volume and annoying, with very little present value. We // might want to turn them back on in the specific case of // build successes on the current document? return false; } return true; case PhabricatorTransactions::TYPE_EDGE: $edge_type = $this->getMetadataValue('edge:type'); switch ($edge_type) { case PhabricatorObjectMentionsObject::EDGECONST: case PhabricatorObjectMentionedByObject::EDGECONST: return true; break; default: break; } break; } return $this->shouldHide(); } public function getTitleForMail() { return id(clone $this)->setRenderingTarget('text')->getTitle(); } public function getBodyForMail() { $comment = $this->getComment(); if ($comment && strlen($comment->getContent())) { return $comment->getContent(); } return null; } public function getNoEffectDescription() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('You can not post an empty comment.'); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( 'This %s already has that view policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( 'This %s already has that edit policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( 'This %s already has that join policy.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( 'All users are already subscribed to this %s.', $this->getApplicationObjectTypeName()); case PhabricatorTransactions::TYPE_EDGE: return pht('Edges already exist; transaction has no effect.'); } return pht('Transaction has no effect.'); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment.', $this->renderHandleLink($author_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( '%s changed the join policy of this %s from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName(), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case PhabricatorTransactions::TYPE_SUBSCRIBERS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s edited subscriber(s), added %d: %s; removed %d: %s.', $this->renderHandleLink($author_phid), count($add), $this->renderSubscriberList($add, 'add'), count($rem), $this->renderSubscriberList($rem, 'rem')); } else if ($add) { return pht( '%s added %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($add), $this->renderSubscriberList($add, 'add')); } else if ($rem) { return pht( '%s removed %d subscriber(s): %s.', $this->renderHandleLink($author_phid), count($rem), $this->renderSubscriberList($rem, 'rem')); } else { // This is used when rendering previews, before the user actually // selects any CCs. return pht( '%s updated subscribers...', $this->renderHandleLink($author_phid)); } break; case PhabricatorTransactions::TYPE_EDGE: $new = ipull($new, 'dst'); $old = ipull($old, 'dst'); $add = array_diff($new, $old); $rem = array_diff($old, $new); $type = $this->getMetadata('edge:type'); $type = head($type); $type_obj = PhabricatorEdgeType::getByConstant($type); if ($add && $rem) { return $type_obj->getTransactionEditString( $this->renderHandleLink($author_phid), new PhutilNumber(count($add) + count($rem)), new PhutilNumber(count($add)), $this->renderHandleList($add), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } else if ($add) { return $type_obj->getTransactionAddString( $this->renderHandleLink($author_phid), new PhutilNumber(count($add)), $this->renderHandleList($add)); } else if ($rem) { return $type_obj->getTransactionRemoveString( $this->renderHandleLink($author_phid), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } else { return pht( '%s edited edge metadata.', $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionTitle($this); } else { return pht( '%s edited a custom field.', $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_TOKEN: if ($old && $new) { return pht( '%s updated a token.', $this->renderHandleLink($author_phid)); } else if ($old) { return pht( '%s rescinded a token.', $this->renderHandleLink($author_phid)); } else { return pht( '%s awarded a token.', $this->renderHandleLink($author_phid)); } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_BUILDING: return pht( '%s started building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); case HarbormasterBuildable::STATUS_PASSED: return pht( '%s completed building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); case HarbormasterBuildable::STATUS_FAILED: return pht( '%s failed to build %s!', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); default: return null; } default: return pht( '%s edited this %s.', $this->renderHandleLink($author_phid), $this->getApplicationObjectTypeName()); } } public function getTitleForFeed(PhabricatorFeedStory $story) { $author_phid = $this->getAuthorPHID(); $object_phid = $this->getObjectPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht( '%s added a comment to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_VIEW_POLICY: return pht( '%s changed the visibility for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDIT_POLICY: return pht( '%s changed the edit policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht( '%s changed the join policy for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht( '%s updated subscribers of %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); case PhabricatorTransactions::TYPE_EDGE: $new = ipull($new, 'dst'); $old = ipull($old, 'dst'); $add = array_diff($new, $old); $rem = array_diff($old, $new); $type = $this->getMetadata('edge:type'); $type = head($type); $type_obj = PhabricatorEdgeType::getByConstant($type); if ($add && $rem) { return $type_obj->getFeedEditString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), new PhutilNumber(count($add) + count($rem)), new PhutilNumber(count($add)), $this->renderHandleList($add), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } else if ($add) { return $type_obj->getFeedAddString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), new PhutilNumber(count($add)), $this->renderHandleList($add)); } else if ($rem) { return $type_obj->getFeedRemoveString( $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } else { return pht( '%s edited edge metadata for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionTitleForFeed($this, $story); } else { return pht( '%s edited a custom field on %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($object_phid)); } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_BUILDING: return pht( '%s started building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); case HarbormasterBuildable::STATUS_PASSED: return pht( '%s completed building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); case HarbormasterBuildable::STATUS_FAILED: return pht( '%s failed to build %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); default: return null; } } return $this->getTitle(); } public function getMarkupFieldsForFeed(PhabricatorFeedStory $story) { $fields = array(); switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); if (strlen($text)) { $fields[] = 'comment/'.$this->getID(); } break; } return $fields; } public function getMarkupTextForFeed(PhabricatorFeedStory $story, $field) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); return PhabricatorMarkupEngine::summarize($text); } return null; } public function getBodyForFeed(PhabricatorFeedStory $story) { $old = $this->getOldValue(); $new = $this->getNewValue(); $body = null; switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: $text = $this->getComment()->getContent(); if (strlen($text)) { $body = $story->getMarkupFieldOutput('comment/'.$this->getID()); } break; } return $body; } public function getActionStrength() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return 0.5; case PhabricatorTransactions::TYPE_SUBSCRIBERS: $old = $this->getOldValue(); $new = $this->getNewValue(); $add = array_diff($old, $new); $rem = array_diff($new, $old); // If this action is the actor subscribing or unsubscribing themselves, // it is less interesting. In particular, if someone makes a comment and // also implicitly subscribes themselves, we should treat the // transaction group as "comment", not "subscribe". In this specific // case (one affected user, and that affected user it the actor), // decrease the action strength. if ((count($add) + count($rem)) != 1) { // Not exactly one CC change. break; } $affected_phid = head(array_merge($add, $rem)); if ($affected_phid != $this->getAuthorPHID()) { // Affected user is someone else. break; } // Make this weaker than TYPE_COMMENT. return 0.25; } return 1.0; } public function isCommentTransaction() { if ($this->hasComment()) { return true; } switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return true; } return false; } public function getActionName() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_COMMENT: return pht('Commented On'); case PhabricatorTransactions::TYPE_VIEW_POLICY: case PhabricatorTransactions::TYPE_EDIT_POLICY: case PhabricatorTransactions::TYPE_JOIN_POLICY: return pht('Changed Policy'); case PhabricatorTransactions::TYPE_SUBSCRIBERS: return pht('Changed Subscribers'); case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { case HarbormasterBuildable::STATUS_PASSED: return pht('Build Passed'); case HarbormasterBuildable::STATUS_FAILED: return pht('Build Failed'); default: return pht('Build Status'); } default: return pht('Updated'); } } public function getMailTags() { return array(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionHasChangeDetails($this); } break; } return false; } public function renderChangeDetails(PhabricatorUser $viewer) { switch ($this->getTransactionType()) { case PhabricatorTransactions::TYPE_CUSTOMFIELD: $field = $this->getTransactionCustomField(); if ($field) { return $field->getApplicationTransactionChangeDetails($this, $viewer); } break; } return $this->renderTextCorpusChangeDetails( $viewer, $this->getOldValue(), $this->getNewValue()); } public function renderTextCorpusChangeDetails( PhabricatorUser $viewer, $old, $new) { require_celerity_resource('differential-changeset-view-css'); $view = id(new PhabricatorApplicationTransactionTextDiffDetailView()) ->setUser($viewer) ->setOldText($old) ->setNewText($new); return $view->render(); } public function attachTransactionGroup(array $group) { assert_instances_of($group, 'PhabricatorApplicationTransaction'); $this->transactionGroup = $group; return $this; } public function getTransactionGroup() { return $this->transactionGroup; } /** * Should this transaction be visually grouped with an existing transaction * group? * * @param list List of transactions. * @return bool True to display in a group with the other transactions. */ public function shouldDisplayGroupWith(array $group) { $this_source = null; if ($this->getContentSource()) { $this_source = $this->getContentSource()->getSource(); } foreach ($group as $xaction) { // Don't group transactions by different authors. if ($xaction->getAuthorPHID() != $this->getAuthorPHID()) { return false; } // Don't group transactions for different objects. if ($xaction->getObjectPHID() != $this->getObjectPHID()) { return false; } // Don't group anything into a group which already has a comment. if ($xaction->isCommentTransaction()) { return false; } // Don't group transactions from different content sources. $other_source = null; if ($xaction->getContentSource()) { $other_source = $xaction->getContentSource()->getSource(); } if ($other_source != $this_source) { return false; } // Don't group transactions which happened more than 2 minutes apart. $apart = abs($xaction->getDateCreated() - $this->getDateCreated()); if ($apart > (60 * 2)) { return false; } } return true; } public function renderExtraInformationLink() { $herald_xscript_id = $this->getMetadataValue('herald:transcriptID'); if ($herald_xscript_id) { return phutil_tag( 'a', array( 'href' => '/herald/transcript/'.$herald_xscript_id.'/', ), pht('View Herald Transcript')); } return null; } /* -( PhabricatorPolicyInterface Implementation )-------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return ($viewer->getPHID() == $this->getAuthorPHID()); } public function describeAutomaticCapability($capability) { // TODO: (T603) Exact policies are unclear here. return null; } /* -( PhabricatorDestructibleInterface )----------------------------------- */ public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { $this->openTransaction(); $comment_template = null; try { $comment_template = $this->getApplicationTransactionCommentObject(); } catch (Exception $ex) { // Continue; no comments for these transactions. } if ($comment_template) { $comments = $comment_template->loadAllWhere( 'transactionPHID = %s', $this->getPHID()); foreach ($comments as $comment) { $engine->destroyObject($comment); } } $this->delete(); $this->saveTransaction(); } } diff --git a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php index 2a1d32a242..4a1f5cfac3 100644 --- a/src/applications/transactions/view/PhabricatorApplicationTransactionView.php +++ b/src/applications/transactions/view/PhabricatorApplicationTransactionView.php @@ -1,451 +1,441 @@ quoteRef = $quote_ref; return $this; } public function getQuoteRef() { return $this->quoteRef; } public function setQuoteTargetID($quote_target_id) { $this->quoteTargetID = $quote_target_id; return $this; } public function getQuoteTargetID() { return $this->quoteTargetID; } public function setObjectPHID($object_phid) { $this->objectPHID = $object_phid; return $this; } public function getObjectPHID() { return $this->objectPHID; } public function setIsPreview($is_preview) { $this->isPreview = $is_preview; return $this; } public function setShowEditActions($show_edit_actions) { $this->showEditActions = $show_edit_actions; return $this; } public function getShowEditActions() { return $this->showEditActions; } - public function setAnchorOffset($anchor_offset) { - $this->anchorOffset = $anchor_offset; - return $this; - } - public function setMarkupEngine(PhabricatorMarkupEngine $engine) { $this->engine = $engine; return $this; } public function setTransactions(array $transactions) { assert_instances_of($transactions, 'PhabricatorApplicationTransaction'); $this->transactions = $transactions; return $this; } public function setShouldTerminate($term) { $this->shouldTerminate = $term; return $this; } public function buildEvents($with_hiding = false) { $user = $this->getUser(); - $anchor = $this->anchorOffset; - $xactions = $this->transactions; $xactions = $this->filterHiddenTransactions($xactions); $xactions = $this->groupRelatedTransactions($xactions); $groups = $this->groupDisplayTransactions($xactions); // If the viewer has interacted with this object, we hide things from // before their most recent interaction by default. This tends to make // very long threads much more manageable, because you don't have to // scroll through a lot of history and can focus on just new stuff. $show_group = null; if ($with_hiding) { // Find the most recent comment by the viewer. $group_keys = array_keys($groups); $group_keys = array_reverse($group_keys); // If we would only hide a small number of transactions, don't hide // anything. Just don't examine the last few keys. Also, we always // want to show the most recent pieces of activity, so don't examine // the first few keys either. $group_keys = array_slice($group_keys, 2, -2); $type_comment = PhabricatorTransactions::TYPE_COMMENT; foreach ($group_keys as $group_key) { $group = $groups[$group_key]; foreach ($group as $xaction) { if ($xaction->getAuthorPHID() == $user->getPHID() && $xaction->getTransactionType() == $type_comment) { // This is the most recent group where the user commented. $show_group = $group_key; break 2; } } } } $events = array(); $hide_by_default = ($show_group !== null); foreach ($groups as $group_key => $group) { if ($hide_by_default && ($show_group === $group_key)) { $hide_by_default = false; } $group_event = null; foreach ($group as $xaction) { - $event = $this->renderEvent($xaction, $group, $anchor); + $event = $this->renderEvent($xaction, $group); $event->setHideByDefault($hide_by_default); - $anchor++; if (!$group_event) { $group_event = $event; } else { $group_event->addEventToGroup($event); } } $events[] = $group_event; } return $events; } public function render() { if (!$this->getObjectPHID()) { throw new Exception('Call setObjectPHID() before render()!'); } $view = new PHUITimelineView(); $view->setShouldTerminate($this->shouldTerminate); $events = $this->buildEvents($with_hiding = true); foreach ($events as $event) { $view->addEvent($event); } if ($this->getShowEditActions()) { Javelin::initBehavior('phabricator-transaction-list'); } return $view->render(); } protected function getOrBuildEngine() { if (!$this->engine) { $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = id(new PhabricatorMarkupEngine()) ->setViewer($this->getUser()); foreach ($this->transactions as $xaction) { if (!$xaction->hasComment()) { continue; } $engine->addObject($xaction->getComment(), $field); } $engine->process(); $this->engine = $engine; } return $this->engine; } private function buildChangeDetailsLink( PhabricatorApplicationTransaction $xaction) { return javelin_tag( 'a', array( 'href' => '/transactions/detail/'.$xaction->getPHID().'/', 'sigil' => 'workflow', ), pht('(Show Details)')); } private function buildExtraInformationLink( PhabricatorApplicationTransaction $xaction) { $link = $xaction->renderExtraInformationLink(); if (!$link) { return null; } return phutil_tag( 'span', array( 'class' => 'phui-timeline-extra-information', ), array(" \xC2\xB7 ", $link)); } protected function shouldGroupTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { return false; } protected function renderTransactionContent( PhabricatorApplicationTransaction $xaction) { $field = PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT; $engine = $this->getOrBuildEngine(); $comment = $xaction->getComment(); if ($comment) { if ($comment->getIsRemoved()) { return javelin_tag( 'span', array( 'class' => 'comment-deleted', 'sigil' => 'transaction-comment', 'meta' => array('phid' => $comment->getTransactionPHID()), ), pht( 'This comment was removed by %s.', $xaction->getHandle($comment->getAuthorPHID())->renderLink())); } else if ($comment->getIsDeleted()) { return javelin_tag( 'span', array( 'class' => 'comment-deleted', 'sigil' => 'transaction-comment', 'meta' => array('phid' => $comment->getTransactionPHID()), ), pht('This comment has been deleted.')); } else if ($xaction->hasComment()) { return javelin_tag( 'span', array( 'class' => 'transaction-comment', 'sigil' => 'transaction-comment', 'meta' => array('phid' => $comment->getTransactionPHID()), ), $engine->getOutput($comment, $field)); } else { // This is an empty, non-deleted comment. Usually this happens when // rendering previews. return null; } } return null; } private function filterHiddenTransactions(array $xactions) { foreach ($xactions as $key => $xaction) { if ($xaction->shouldHide()) { unset($xactions[$key]); } } return $xactions; } private function groupRelatedTransactions(array $xactions) { $last = null; $last_key = null; $groups = array(); foreach ($xactions as $key => $xaction) { if ($last && $this->shouldGroupTransactions($last, $xaction)) { $groups[$last_key][] = $xaction; unset($xactions[$key]); } else { $last = $xaction; $last_key = $key; } } foreach ($xactions as $key => $xaction) { $xaction->attachTransactionGroup(idx($groups, $key, array())); } return $xactions; } private function groupDisplayTransactions(array $xactions) { $groups = array(); $group = array(); foreach ($xactions as $xaction) { if ($xaction->shouldDisplayGroupWith($group)) { $group[] = $xaction; } else { if ($group) { $groups[] = $group; } $group = array($xaction); } } if ($group) { $groups[] = $group; } foreach ($groups as $key => $group) { $group = msort($group, 'getActionStrength'); $group = array_reverse($group); $groups[$key] = $group; } return $groups; } private function renderEvent( PhabricatorApplicationTransaction $xaction, - array $group, - $anchor) { + array $group) { $viewer = $this->getUser(); $event = id(new PHUITimelineEventView()) ->setUser($viewer) ->setTransactionPHID($xaction->getPHID()) ->setUserHandle($xaction->getHandle($xaction->getAuthorPHID())) ->setIcon($xaction->getIcon()) ->setColor($xaction->getColor()); list($token, $token_removed) = $xaction->getToken(); if ($token) { $event->setToken($token, $token_removed); } if (!$this->shouldSuppressTitle($xaction, $group)) { $title = $xaction->getTitle(); if ($xaction->hasChangeDetails()) { if (!$this->isPreview) { $details = $this->buildChangeDetailsLink($xaction); $title = array( $title, ' ', $details, ); } } if (!$this->isPreview) { $more = $this->buildExtraInformationLink($xaction); if ($more) { $title = array($title, ' ', $more); } } $event->setTitle($title); } if ($this->isPreview) { $event->setIsPreview(true); } else { $event ->setDateCreated($xaction->getDateCreated()) ->setContentSource($xaction->getContentSource()) - ->setAnchor($anchor); + ->setAnchor($xaction->getID()); } $transaction_type = $xaction->getTransactionType(); $comment_type = PhabricatorTransactions::TYPE_COMMENT; $is_normal_comment = ($transaction_type == $comment_type); if ($this->getShowEditActions() && !$this->isPreview && $is_normal_comment) { $has_deleted_comment = $xaction->getComment() && $xaction->getComment()->getIsDeleted(); $has_removed_comment = $xaction->getComment() && $xaction->getComment()->getIsRemoved(); if ($xaction->getCommentVersion() > 1 && !$has_removed_comment) { $event->setIsEdited(true); } // If we have a place for quoted text to go and this is a quotable // comment, pass the quote target ID to the event view. if ($this->getQuoteTargetID()) { if ($xaction->hasComment()) { if (!$has_removed_comment && !$has_deleted_comment) { $event->setQuoteTargetID($this->getQuoteTargetID()); $event->setQuoteRef($this->getQuoteRef()); } } } $can_edit = PhabricatorPolicyCapability::CAN_EDIT; if ($xaction->hasComment() || $has_deleted_comment) { $has_edit_capability = PhabricatorPolicyFilter::hasCapability( $viewer, $xaction, $can_edit); if ($has_edit_capability && !$has_removed_comment) { $event->setIsEditable(true); } if ($has_edit_capability || $viewer->getIsAdmin()) { if (!$has_removed_comment) { $event->setIsRemovable(true); } } } } $comment = $this->renderTransactionContent($xaction); if ($comment) { $event->appendChild($comment); } return $event; } private function shouldSuppressTitle( PhabricatorApplicationTransaction $xaction, array $group) { // This is a little hard-coded, but we don't have any other reasonable // cases for now. Suppress "commented on" if there are other actions in // the display group. if (count($group) > 1) { $type_comment = PhabricatorTransactions::TYPE_COMMENT; if ($xaction->getTransactionType() == $type_comment) { return true; } } return false; } }