protected function saveComment(PhabricatorInlineCommentInterface $inline)
 {
     $inline->openTransaction();
     $inline->save();
     DifferentialDraft::markHasDraft($inline->getAuthorPHID(), $inline->getRevisionPHID(), $inline->getPHID());
     $inline->saveTransaction();
 }
 /**
  * @task internal
  */
 private function buildJoinsClause($conn_r)
 {
     $joins = array();
     if ($this->pathIDs) {
         $path_table = new DifferentialAffectedPath();
         $joins[] = qsprintf($conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName());
     }
     if ($this->commitHashes) {
         $joins[] = qsprintf($conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME);
     }
     if ($this->ccs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_ccs ON e_ccs.src = r.phid ' . 'AND e_ccs.type = %s ' . 'AND e_ccs.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER, $this->ccs);
     }
     if ($this->reviewers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid ' . 'AND e_reviewers.type = %s ' . 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER, $this->reviewers);
     }
     if ($this->draftAuthors) {
         $differential_draft = new DifferentialDraft();
         $joins[] = qsprintf($conn_r, 'JOIN %T has_draft ON has_draft.objectPHID = r.phid ' . 'AND has_draft.authorPHID IN (%Ls)', $differential_draft->getTableName(), $this->draftAuthors);
     }
     $joins = implode(' ', $joins);
     return $joins;
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     if (!$request->isFormPost()) {
         return new Aphront400Response();
     }
     $revision = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs(array($this->id))->needReviewerStatus(true)->needReviewerAuthority(true)->executeOne();
     if (!$revision) {
         return new Aphront404Response();
     }
     $type_action = DifferentialTransaction::TYPE_ACTION;
     $type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS;
     $type_edge = PhabricatorTransactions::TYPE_EDGE;
     $type_comment = PhabricatorTransactions::TYPE_COMMENT;
     $type_inline = DifferentialTransaction::TYPE_INLINE;
     $edge_reviewer = PhabricatorEdgeConfig::TYPE_DREV_HAS_REVIEWER;
     $xactions = array();
     $action = $request->getStr('action');
     switch ($action) {
         case DifferentialAction::ACTION_COMMENT:
         case DifferentialAction::ACTION_ADDREVIEWERS:
         case DifferentialAction::ACTION_ADDCCS:
             // These transaction types have no direct effect, they just
             // accompany other transaction types which can have an effect.
             break;
         default:
             $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_action)->setNewValue($request->getStr('action'));
             break;
     }
     $ccs = $request->getArr('ccs');
     if ($ccs) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_subscribers)->setNewValue(array('+' => $ccs));
     }
     $current_reviewers = mpull($revision->getReviewerStatus(), null, 'getReviewerPHID');
     $reviewer_edges = array();
     $add_reviewers = $request->getArr('reviewers');
     foreach ($add_reviewers as $reviewer_phid) {
         if (isset($current_reviewers[$reviewer_phid])) {
             continue;
         }
         $reviewer = new DifferentialReviewer($reviewer_phid, array('status' => DifferentialReviewerStatus::STATUS_ADDED));
         $reviewer_edges[$reviewer_phid] = array('data' => $reviewer->getEdgeData());
     }
     if ($add_reviewers) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_edge)->setMetadataValue('edge:type', $edge_reviewer)->setNewValue(array('+' => $reviewer_edges));
     }
     $inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments($viewer, $revision);
     foreach ($inlines as $inline) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_inline)->attachComment($inline);
     }
     // NOTE: If there are no other transactions, add an empty comment
     // transaction so that we'll raise a more user-friendly error message,
     // to the effect of "you can not post an empty comment".
     $no_xactions = !$xactions;
     $comment = $request->getStr('comment');
     if (strlen($comment) || $no_xactions) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_comment)->attachComment(id(new DifferentialTransactionComment())->setRevisionPHID($revision->getPHID())->setContent($comment));
     }
     $editor = id(new DifferentialTransactionEditor())->setActor($viewer)->setContentSourceFromRequest($request)->setContinueOnMissingFields(true)->setContinueOnNoEffect($request->isContinueRequest());
     $revision_uri = '/D' . $revision->getID();
     try {
         $editor->applyTransactions($revision, $xactions);
     } catch (PhabricatorApplicationTransactionNoEffectException $ex) {
         return id(new PhabricatorApplicationTransactionNoEffectResponse())->setCancelURI($revision_uri)->setException($ex);
     } catch (PhabricatorApplicationTransactionValidationException $ex) {
         // TODO: Provide a nice Response for rendering these in a clean way.
         throw $ex;
     }
     $user = $request->getUser();
     $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), 'differential-comment-' . $revision->getID());
     if ($draft) {
         $draft->delete();
     }
     DifferentialDraft::deleteAllDrafts($user->getPHID(), $revision->getPHID());
     return id(new AphrontRedirectResponse())->setURI('/D' . $revision->getID());
 }
 /**
  * @task internal
  */
 private function buildJoinsClause($conn_r)
 {
     $joins = array();
     if ($this->pathIDs) {
         $path_table = new DifferentialAffectedPath();
         $joins[] = qsprintf($conn_r, 'JOIN %T p ON p.revisionID = r.id', $path_table->getTableName());
     }
     if ($this->commitHashes) {
         $joins[] = qsprintf($conn_r, 'JOIN %T hash_rel ON hash_rel.revisionID = r.id', ArcanistDifferentialRevisionHash::TABLE_NAME);
     }
     if ($this->ccs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_ccs ON e_ccs.src = r.phid ' . 'AND e_ccs.type = %s ' . 'AND e_ccs.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, PhabricatorObjectHasSubscriberEdgeType::EDGECONST, $this->ccs);
     }
     if ($this->reviewers) {
         $joins[] = qsprintf($conn_r, 'JOIN %T e_reviewers ON e_reviewers.src = r.phid ' . 'AND e_reviewers.type = %s ' . 'AND e_reviewers.dst in (%Ls)', PhabricatorEdgeConfig::TABLE_NAME_EDGE, DifferentialRevisionHasReviewerEdgeType::EDGECONST, $this->reviewers);
     }
     if ($this->draftAuthors) {
         $differential_draft = new DifferentialDraft();
         $joins[] = qsprintf($conn_r, 'JOIN %T has_draft ON has_draft.objectPHID = r.phid ' . 'AND has_draft.authorPHID IN (%Ls)', $differential_draft->getTableName(), $this->draftAuthors);
     }
     if ($this->commitPHIDs) {
         $joins[] = qsprintf($conn_r, 'JOIN %T commits ON commits.revisionID = r.id', DifferentialRevision::TABLE_COMMIT);
     }
     $joins[] = $this->buildJoinClauseParts($conn_r);
     return $this->formatJoinClause($joins);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
     $revision = id(new DifferentialRevisionQuery())->setViewer($viewer)->withIDs(array($id))->executeOne();
     if (!$revision) {
         return new Aphront404Response();
     }
     $type_comment = PhabricatorTransactions::TYPE_COMMENT;
     $type_action = DifferentialTransaction::TYPE_ACTION;
     $type_edge = PhabricatorTransactions::TYPE_EDGE;
     $type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS;
     $xactions = array();
     $action = $request->getStr('action');
     switch ($action) {
         case DifferentialAction::ACTION_COMMENT:
         case DifferentialAction::ACTION_ADDREVIEWERS:
         case DifferentialAction::ACTION_ADDCCS:
             break;
         default:
             $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_action)->setNewValue($action);
             break;
     }
     $edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
     $reviewers = $request->getStrList('reviewers');
     if (DifferentialAction::allowReviewers($action) && $reviewers) {
         $faux_edges = array();
         foreach ($reviewers as $phid) {
             $faux_edges[$phid] = array('src' => $revision->getPHID(), 'type' => $edge_reviewer, 'dst' => $phid);
         }
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_edge)->setMetadataValue('edge:type', $edge_reviewer)->setOldValue(array())->setNewValue($faux_edges);
     }
     $ccs = $request->getStrList('ccs');
     if ($ccs) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_subscribers)->setOldValue(array())->setNewValue(array_fuse($ccs));
     }
     // Add a comment transaction if there's nothing, so we'll generate a
     // nonempty result.
     if (strlen($request->getStr('content')) || !$xactions) {
         $xactions[] = id(new DifferentialTransaction())->setTransactionType($type_comment)->attachComment(id(new ManiphestTransactionComment())->setContent($request->getStr('content')));
     }
     foreach ($xactions as $xaction) {
         $xaction->setAuthorPHID($viewer->getPHID());
     }
     $engine = new PhabricatorMarkupEngine();
     $engine->setViewer($request->getUser());
     foreach ($xactions as $xaction) {
         if ($xaction->hasComment()) {
             $engine->addObject($xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
         }
     }
     $engine->process();
     $phids = mpull($xactions, 'getRequiredHandlePHIDs');
     $phids = array_mergev($phids);
     $handles = id(new PhabricatorHandleQuery())->setViewer($viewer)->withPHIDs($phids)->execute();
     foreach ($xactions as $xaction) {
         $xaction->setHandles($handles);
     }
     $view = id(new DifferentialTransactionView())->setUser($viewer)->setTransactions($xactions)->setIsPreview(true);
     $metadata = array('reviewers' => $reviewers, 'ccs' => $ccs);
     if ($action != DifferentialAction::ACTION_COMMENT) {
         $metadata['action'] = $action;
     }
     $draft_key = 'differential-comment-' . $id;
     $draft = id(new PhabricatorDraft())->setAuthorPHID($viewer->getPHID())->setDraftKey($draft_key)->setDraft($request->getStr('content'))->setMetadata($metadata)->replaceOrDelete();
     if ($draft->isDeleted()) {
         DifferentialDraft::deleteHasDraft($viewer->getPHID(), $revision->getPHID(), $draft_key);
     } else {
         DifferentialDraft::markHasDraft($viewer->getPHID(), $revision->getPHID(), $draft_key);
     }
     return id(new AphrontAjaxResponse())->setContent((string) phutil_implode_html('', $view->buildEvents()));
 }
<?php

// Destroy duplicate drafts before storage adjustment adds a unique key to this
// table. See T1191. We retain the newest draft.
// (We can't easily do this in a single SQL statement because MySQL won't let us
// modify a table that's joined in a subquery.)
$table = new DifferentialDraft();
$conn_w = $table->establishConnection('w');
$duplicates = queryfx_all($conn_w, 'SELECT DISTINCT u.id id FROM %T u
    JOIN %T v
      ON u.objectPHID = v.objectPHID
      AND u.authorPHID = v.authorPHID
      AND u.draftKey = v.draftKey
      AND u.id < v.id', $table->getTableName(), $table->getTableName());
$duplicates = ipull($duplicates, 'id');
foreach (PhabricatorLiskDAO::chunkSQL($duplicates) as $chunk) {
    queryfx($conn_w, 'DELETE FROM %T WHERE id IN (%Q)', $table->getTableName(), $chunk);
}