public function save() { if (!$this->viewer) { throw new Exception("Must set user before saving question"); } if (!$this->question) { throw new Exception("Must set question before saving it"); } $viewer = $this->viewer; $question = $this->question; $question->save(); // search index $question->attachRelated(); PhabricatorSearchPonderIndexer::indexQuestion($question); // subscribe author and @mentions $subeditor = id(new PhabricatorSubscriptionsEditor())->setObject($question)->setUser($viewer); $subeditor->subscribeExplicit(array($question->getAuthorPHID())); $content = $question->getContent(); $at_mention_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(array($content)); $subeditor->subscribeImplicit($at_mention_phids); $subeditor->save(); if ($this->shouldEmail && $at_mention_phids) { id(new PonderMentionMail($question, $question, $viewer))->setToPHIDs($at_mention_phids)->send(); } }
public function save() { if (!$this->comment) { throw new Exception("Must set comment before saving it"); } if (!$this->question) { throw new Exception("Must set question before saving comment"); } if (!$this->targetPHID) { throw new Exception("Must set target before saving comment"); } if (!$this->viewer) { throw new Exception("Must set viewer before saving comment"); } $comment = $this->comment; $question = $this->question; $target = $this->targetPHID; $viewer = $this->viewer; $comment->save(); $question->attachRelated(); PhabricatorSearchPonderIndexer::indexQuestion($question); // subscribe author and @mentions $subeditor = id(new PhabricatorSubscriptionsEditor())->setObject($question)->setUser($viewer); $subeditor->subscribeExplicit(array($comment->getAuthorPHID())); $content = $comment->getContent(); $at_mention_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(array($content)); $subeditor->subscribeImplicit($at_mention_phids); $subeditor->save(); if ($this->shouldEmail) { // now load subscribers, including implicitly-added @mention victims $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID($question->getPHID()); // @mention emails (but not for anyone who has explicitly unsubscribed) if (array_intersect($at_mention_phids, $subscribers)) { id(new PonderMentionMail($question, $comment, $viewer))->setToPHIDs($at_mention_phids)->send(); } if ($target === $question->getPHID()) { $target = $question; } else { $answers_by_phid = mgroup($question->getAnswers(), 'getPHID'); $target = head($answers_by_phid[$target]); } // only send emails to others in the same thread $thread = mpull($target->getComments(), 'getAuthorPHID'); $thread[] = $target->getAuthorPHID(); $thread[] = $question->getAuthorPHID(); $other_subs = array_diff(array_intersect($thread, $subscribers), $at_mention_phids); // 'Comment' emails for subscribers who are in the same comment thread, // including the author of the parent question and/or answer, excluding // @mentions (and excluding the author, depending on their MetaMTA // settings). if ($other_subs) { id(new PonderCommentMail($question, $comment, $viewer))->setToPHIDs($other_subs)->send(); } } }
public function saveAnswer() { if (!$this->viewer) { throw new Exception("Must set user before saving question"); } if (!$this->question) { throw new Exception("Must set question before saving answer"); } if (!$this->answer) { throw new Exception("Must set answer before saving it"); } $question = $this->question; $answer = $this->answer; $viewer = $this->viewer; $conn = $answer->establishConnection('w'); $trans = $conn->openTransaction(); $trans->beginReadLocking(); $question->reload(); queryfx($conn, 'UPDATE %T as t SET t.`answerCount` = t.`answerCount` + 1 WHERE t.`PHID` = %s', $question->getTableName(), $question->getPHID()); $answer->setQuestionID($question->getID()); $answer->save(); $trans->endReadLocking(); $trans->saveTransaction(); $question->attachRelated(); PhabricatorSearchPonderIndexer::indexQuestion($question); // subscribe author and @mentions $subeditor = id(new PhabricatorSubscriptionsEditor())->setObject($question)->setUser($viewer); $subeditor->subscribeExplicit(array($answer->getAuthorPHID())); $content = $answer->getContent(); $at_mention_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions(array($content)); $subeditor->subscribeImplicit($at_mention_phids); $subeditor->save(); if ($this->shouldEmail) { // now load subscribers, including implicitly-added @mention victims $subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID($question->getPHID()); // @mention emails (but not for anyone who has explicitly unsubscribed) if (array_intersect($at_mention_phids, $subscribers)) { id(new PonderMentionMail($question, $answer, $viewer))->setToPHIDs($at_mention_phids)->send(); } $other_subs = array_diff($subscribers, $at_mention_phids); // 'Answered' emails for subscribers who are not @mentiond (and excluding // author depending on their MetaMTA settings). if ($other_subs) { id(new PonderAnsweredMail($question, $answer, $viewer))->setToPHIDs($other_subs)->send(); } } }
private function buildMentionTransaction(PhabricatorLiskDAO $object, array $xactions, array $blocks) { if (!$object instanceof PhabricatorSubscribableInterface) { return null; } $texts = array_mergev($blocks); $phids = PhabricatorMarkupEngine::extractPHIDsFromMentions($this->getActor(), $texts); $this->mentionedPHIDs = $phids; if ($object->getPHID()) { // Don't try to subscribe already-subscribed mentions: we want to generate // a dialog about an action having no effect if the user explicitly adds // existing CCs, but not if they merely mention existing subscribers. $phids = array_diff($phids, $this->subscribers); } foreach ($phids as $key => $phid) { if ($object->isAutomaticallySubscribed($phid)) { unset($phids[$key]); } } $phids = array_values($phids); if (!$phids) { return null; } $xaction = newv(get_class(head($xactions)), array()); $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); $xaction->setNewValue(array('+' => $phids)); return $xaction; }
public function save() { $revision = $this->revision; $action = $this->action; $actor_phid = $this->actorPHID; $actor = id(new PhabricatorUser())->loadOneWhere('PHID = %s', $actor_phid); $actor_is_author = $actor_phid == $revision->getAuthorPHID(); $allow_self_accept = PhabricatorEnv::getEnvConfig('differential.allow-self-accept', false); $revision_status = $revision->getStatus(); $revision->loadRelationships(); $reviewer_phids = $revision->getReviewers(); if ($reviewer_phids) { $reviewer_phids = array_combine($reviewer_phids, $reviewer_phids); } $metadata = array(); $inline_comments = array(); if ($this->attachInlineComments) { $inline_comments = id(new DifferentialInlineComment())->loadAllWhere('authorPHID = %s AND revisionID = %d AND commentID IS NULL', $this->actorPHID, $revision->getID()); } switch ($action) { case DifferentialAction::ACTION_COMMENT: if (!$this->message && !$inline_comments) { throw new DifferentialActionHasNoEffectException("You are submitting an empty comment with no action: " . "you must act on the revision or post a comment."); } break; case DifferentialAction::ACTION_RESIGN: if ($actor_is_author) { throw new Exception('You can not resign from your own revision!'); } if (empty($reviewer_phids[$actor_phid])) { throw new DifferentialActionHasNoEffectException("You can not resign from this revision because you are not " . "a reviewer."); } DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array($actor_phid), $add = array(), $actor_phid); break; case DifferentialAction::ACTION_ABANDON: if (!$actor_is_author) { throw new Exception('You can only abandon your own revisions.'); } if ($revision_status == ArcanistDifferentialRevisionStatus::CLOSED) { throw new DifferentialActionHasNoEffectException("You can not abandon this revision because it has already " . "been closed."); } if ($revision_status == ArcanistDifferentialRevisionStatus::ABANDONED) { throw new DifferentialActionHasNoEffectException("You can not abandon this revision because it has already " . "been abandoned."); } $revision->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED); break; case DifferentialAction::ACTION_ACCEPT: if ($actor_is_author && !$allow_self_accept) { throw new Exception('You can not accept your own revision.'); } if ($revision_status != ArcanistDifferentialRevisionStatus::NEEDS_REVIEW && $revision_status != ArcanistDifferentialRevisionStatus::NEEDS_REVISION) { switch ($revision_status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: throw new DifferentialActionHasNoEffectException("You can not accept this revision because someone else " . "already accepted it."); case ArcanistDifferentialRevisionStatus::ABANDONED: throw new DifferentialActionHasNoEffectException("You can not accept this revision because it has been " . "abandoned."); case ArcanistDifferentialRevisionStatus::CLOSED: throw new DifferentialActionHasNoEffectException("You can not accept this revision because it has already " . "been closed."); default: throw new Exception("Unexpected revision state '{$revision_status}'!"); } } $revision->setStatus(ArcanistDifferentialRevisionStatus::ACCEPTED); if (!isset($reviewer_phids[$actor_phid])) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array(), $add = array($actor_phid), $actor_phid); } break; case DifferentialAction::ACTION_REQUEST: if (!$actor_is_author) { throw new Exception('You must own a revision to request review.'); } switch ($revision_status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); break; case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: throw new DifferentialActionHasNoEffectException("You can not request review of this revision because it has " . "been abandoned."); case ArcanistDifferentialRevisionStatus::ABANDONED: throw new DifferentialActionHasNoEffectException("You can not request review of this revision because it has " . "been abandoned."); case ArcanistDifferentialRevisionStatus::CLOSED: throw new DifferentialActionHasNoEffectException("You can not request review of this revision because it has " . "already been closed."); default: throw new Exception("Unexpected revision state '{$revision_status}'!"); } list($added_reviewers, $ignored) = $this->alterReviewers(); if ($added_reviewers) { $key = DifferentialComment::METADATA_ADDED_REVIEWERS; $metadata[$key] = $added_reviewers; } break; case DifferentialAction::ACTION_REJECT: if ($actor_is_author) { throw new Exception('You can not request changes to your own revision.'); } switch ($revision_status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: // NOTE: We allow you to reject an already-rejected revision // because it doesn't create any ambiguity and avoids a rather // needless dialog. break; case ArcanistDifferentialRevisionStatus::ABANDONED: throw new DifferentialActionHasNoEffectException("You can not request changes to this revision because it has " . "been abandoned."); case ArcanistDifferentialRevisionStatus::CLOSED: throw new DifferentialActionHasNoEffectException("You can not request changes to this revision because it has " . "already been closed."); default: throw new Exception("Unexpected revision state '{$revision_status}'!"); } if (!isset($reviewer_phids[$actor_phid])) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array(), $add = array($actor_phid), $actor_phid); } $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVISION); break; case DifferentialAction::ACTION_RETHINK: if (!$actor_is_author) { throw new Exception("You can not plan changes to somebody else's revision"); } switch ($revision_status) { case ArcanistDifferentialRevisionStatus::ACCEPTED: case ArcanistDifferentialRevisionStatus::NEEDS_REVISION: case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW: break; case ArcanistDifferentialRevisionStatus::ABANDONED: throw new DifferentialActionHasNoEffectException("You can not plan changes to this revision because it has " . "been abandoned."); case ArcanistDifferentialRevisionStatus::CLOSED: throw new DifferentialActionHasNoEffectException("You can not plan changes to this revision because it has " . "already been closed."); default: throw new Exception("Unexpected revision state '{$revision_status}'!"); } $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVISION); break; case DifferentialAction::ACTION_RECLAIM: if (!$actor_is_author) { throw new Exception('You can not reclaim a revision you do not own.'); } if ($revision_status != ArcanistDifferentialRevisionStatus::ABANDONED) { throw new DifferentialActionHasNoEffectException("You can not reclaim this revision because it is not abandoned."); } $revision->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW); break; case DifferentialAction::ACTION_CLOSE: // NOTE: The daemons can mark things closed from any state. We treat // them as completely authoritative. if (!$this->isDaemonWorkflow) { if (!$actor_is_author) { throw new Exception("You can not mark a revision you don't own as closed."); } $status_closed = ArcanistDifferentialRevisionStatus::CLOSED; $status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED; if ($revision_status == $status_closed) { throw new DifferentialActionHasNoEffectException("You can not mark this revision as closed because it has " . "already been marked as closed."); } if ($revision_status != $status_accepted) { throw new DifferentialActionHasNoEffectException("You can not mark this revision as closed because it is " . "has not been accepted."); } } if (!$revision->getDateCommitted()) { $revision->setDateCommitted(time()); } $revision->setStatus(ArcanistDifferentialRevisionStatus::CLOSED); break; case DifferentialAction::ACTION_ADDREVIEWERS: list($added_reviewers, $ignored) = $this->alterReviewers(); if ($added_reviewers) { $key = DifferentialComment::METADATA_ADDED_REVIEWERS; $metadata[$key] = $added_reviewers; } else { $user_tried_to_add = count($this->getAddedReviewers()); if ($user_tried_to_add == 0) { throw new DifferentialActionHasNoEffectException("You can not add reviewers, because you did not specify any " . "reviewers."); } else { if ($user_tried_to_add == 1) { throw new DifferentialActionHasNoEffectException("You can not add that reviewer, because they are already an " . "author or reviewer."); } else { throw new DifferentialActionHasNoEffectException("You can not add those reviewers, because they are all already " . "authors or reviewers."); } } } break; case DifferentialAction::ACTION_ADDCCS: $added_ccs = $this->getAddedCCs(); $user_tried_to_add = count($added_ccs); $added_ccs = $this->filterAddedCCs($added_ccs); if ($added_ccs) { foreach ($added_ccs as $cc) { DifferentialRevisionEditor::addCC($revision, $cc, $this->actorPHID); } $key = DifferentialComment::METADATA_ADDED_CCS; $metadata[$key] = $added_ccs; } else { if ($user_tried_to_add == 0) { throw new DifferentialActionHasNoEffectException("You can not add CCs, because you did not specify any " . "CCs."); } else { if ($user_tried_to_add == 1) { throw new DifferentialActionHasNoEffectException("You can not add that CC, because they are already an " . "author, reviewer or CC."); } else { throw new DifferentialActionHasNoEffectException("You can not add those CCs, because they are all already " . "authors, reviewers or CCs."); } } } break; case DifferentialAction::ACTION_CLAIM: if ($actor_is_author) { throw new Exception("You can not commandeer your own revision."); } switch ($revision_status) { case ArcanistDifferentialRevisionStatus::CLOSED: throw new DifferentialActionHasNoEffectException("You can not commandeer this revision because it has " . "already been closed."); break; } $this->setAddedReviewers(array($revision->getAuthorPHID())); $this->setRemovedReviewers(array($actor_phid)); // NOTE: Set the new author PHID before calling addReviewers(), since it // doesn't permit the author to become a reviewer. $revision->setAuthorPHID($actor_phid); list($added_reviewers, $removed_reviewers) = $this->alterReviewers(); if ($added_reviewers) { $key = DifferentialComment::METADATA_ADDED_REVIEWERS; $metadata[$key] = $added_reviewers; } if ($removed_reviewers) { $key = DifferentialComment::METADATA_REMOVED_REVIEWERS; $metadata[$key] = $removed_reviewers; } break; default: throw new Exception('Unsupported action.'); } // Update information about reviewer in charge. if ($action == DifferentialAction::ACTION_ACCEPT || $action == DifferentialAction::ACTION_REJECT) { $revision->setLastReviewerPHID($actor_phid); } // TODO: Call beginReadLocking() prior to loading the revision. $revision->openTransaction(); // Always save the revision (even if we didn't actually change any of its // properties) so that it jumps to the top of the revision list when sorted // by "updated". Notably, this allows "ping" comments to push it to the // top of the action list. $revision->save(); if ($action != DifferentialAction::ACTION_RESIGN) { DifferentialRevisionEditor::addCC($revision, $this->actorPHID, $this->actorPHID); } $comment = id(new DifferentialComment())->setAuthorPHID($this->actorPHID)->setRevisionID($revision->getID())->setAction($action)->setContent((string) $this->message)->setMetadata($metadata); if ($this->contentSource) { $comment->setContentSource($this->contentSource); } $comment->save(); $changesets = array(); if ($inline_comments) { $load_ids = mpull($inline_comments, 'getChangesetID'); if ($load_ids) { $load_ids = array_unique($load_ids); $changesets = id(new DifferentialChangeset())->loadAllWhere('id in (%Ld)', $load_ids); } foreach ($inline_comments as $inline) { $inline->setCommentID($comment->getID()); $inline->save(); } } // Find any "@mentions" in the comment blocks. $content_blocks = array($comment->getContent()); foreach ($inline_comments as $inline) { $content_blocks[] = $inline->getContent(); } $mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($content_blocks); if ($mention_ccs) { $mention_ccs = $this->filterAddedCCs($mention_ccs); if ($mention_ccs) { $metadata = $comment->getMetadata(); $metacc = idx($metadata, DifferentialComment::METADATA_ADDED_CCS, array()); foreach ($mention_ccs as $cc_phid) { DifferentialRevisionEditor::addCC($revision, $cc_phid, $this->actorPHID); $metacc[] = $cc_phid; } $metadata[DifferentialComment::METADATA_ADDED_CCS] = $metacc; $comment->setMetadata($metadata); $comment->save(); } } $revision->saveTransaction(); $phids = array($this->actorPHID); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $actor_handle = $handles[$this->actorPHID]; $xherald_header = HeraldTranscript::loadXHeraldRulesHeader($revision->getPHID()); id(new DifferentialCommentMail($revision, $actor_handle, $comment, $changesets, $inline_comments))->setToPHIDs(array_merge($revision->getReviewers(), array($revision->getAuthorPHID())))->setCCPHIDs($revision->getCCPHIDs())->setChangedByCommit($this->getChangedByCommit())->setXHeraldRulesHeader($xherald_header)->setParentMessageID($this->parentMessageID)->send(); $event_data = array('revision_id' => $revision->getID(), 'revision_phid' => $revision->getPHID(), 'revision_name' => $revision->getTitle(), 'revision_author_phid' => $revision->getAuthorPHID(), 'action' => $comment->getAction(), 'feedback_content' => $comment->getContent(), 'actor_phid' => $this->actorPHID); id(new PhabricatorTimelineEvent('difx', $event_data))->recordEvent(); // TODO: Move to a daemon? id(new PhabricatorFeedStoryPublisher())->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_DIFFERENTIAL)->setStoryData($event_data)->setStoryTime(time())->setStoryAuthorPHID($this->actorPHID)->setRelatedPHIDs(array($revision->getPHID(), $this->actorPHID, $revision->getAuthorPHID()))->setPrimaryObjectPHID($revision->getPHID())->setSubscribedPHIDs(array_merge(array($revision->getAuthorPHID()), $revision->getReviewers(), $revision->getCCPHIDs()))->publish(); // TODO: Move to a daemon? PhabricatorSearchDifferentialIndexer::indexRevision($revision); return $comment; }
private function buildSubscribeTransaction(PhabricatorLiskDAO $object, array $xactions, array $changes) { if (!$object instanceof PhabricatorSubscribableInterface) { return null; } if ($this->shouldEnableMentions($object, $xactions)) { // Identify newly mentioned users. We ignore users who were previously // mentioned so that we don't re-subscribe users after an edit of text // which mentions them. $old_texts = mpull($changes, 'getOldValue'); $new_texts = mpull($changes, 'getNewValue'); $old_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions($this->getActor(), $old_texts); $new_phids = PhabricatorMarkupEngine::extractPHIDsFromMentions($this->getActor(), $new_texts); $phids = array_diff($new_phids, $old_phids); } else { $phids = array(); } $this->mentionedPHIDs = $phids; if ($object->getPHID()) { // Don't try to subscribe already-subscribed mentions: we want to generate // a dialog about an action having no effect if the user explicitly adds // existing CCs, but not if they merely mention existing subscribers. $phids = array_diff($phids, $this->subscribers); } if ($phids) { $users = id(new PhabricatorPeopleQuery())->setViewer($this->getActor())->withPHIDs($phids)->execute(); $users = mpull($users, null, 'getPHID'); foreach ($phids as $key => $phid) { // Do not subscribe mentioned users // who do not have VIEW Permissions if ($object instanceof PhabricatorPolicyInterface && !PhabricatorPolicyFilter::hasCapability($users[$phid], $object, PhabricatorPolicyCapability::CAN_VIEW)) { unset($phids[$key]); } else { if ($object->isAutomaticallySubscribed($phid)) { unset($phids[$key]); } } } $phids = array_values($phids); } // No else here to properly return null should we unset all subscriber if (!$phids) { return null; } $xaction = newv(get_class(head($xactions)), array()); $xaction->setTransactionType(PhabricatorTransactions::TYPE_SUBSCRIBERS); $xaction->setNewValue(array('+' => $phids)); return $xaction; }
public function save() { $revision = $this->revision; $action = $this->action; $actor_phid = $this->actorPHID; $actor_is_author = $actor_phid == $revision->getAuthorPHID(); $revision_status = $revision->getStatus(); $revision->loadRelationships(); $reviewer_phids = $revision->getReviewers(); if ($reviewer_phids) { $reviewer_phids = array_combine($reviewer_phids, $reviewer_phids); } $metadata = array(); switch ($action) { case DifferentialAction::ACTION_COMMENT: break; case DifferentialAction::ACTION_RESIGN: if ($actor_is_author) { throw new Exception('You can not resign from your own revision!'); } if (isset($reviewer_phids[$actor_phid])) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array($actor_phid), $add = array(), $actor_phid); } break; case DifferentialAction::ACTION_ABANDON: if (!$actor_is_author) { throw new Exception('You can only abandon your revisions.'); } if ($revision_status == DifferentialRevisionStatus::COMMITTED) { throw new Exception('You can not abandon a committed revision.'); } if ($revision_status == DifferentialRevisionStatus::ABANDONED) { $action = DifferentialAction::ACTION_COMMENT; break; } $revision->setStatus(DifferentialRevisionStatus::ABANDONED)->save(); break; case DifferentialAction::ACTION_ACCEPT: if ($actor_is_author) { throw new Exception('You can not accept your own revision.'); } if ($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW && $revision_status != DifferentialRevisionStatus::NEEDS_REVISION) { $action = DifferentialAction::ACTION_COMMENT; break; } $revision->setStatus(DifferentialRevisionStatus::ACCEPTED)->save(); if (!isset($reviewer_phids[$actor_phid])) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array(), $add = array($actor_phid), $actor_phid); } break; case DifferentialAction::ACTION_REQUEST: if (!$actor_is_author) { throw new Exception('You must own a revision to request review.'); } if ($revision_status != DifferentialRevisionStatus::NEEDS_REVISION && $revision_status != DifferentialRevisionStatus::ACCEPTED) { $action = DifferentialAction::ACTION_COMMENT; break; } $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW)->save(); break; case DifferentialAction::ACTION_REJECT: if ($actor_is_author) { throw new Exception('You can not request changes to your own revision.'); } if ($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW && $revision_status != DifferentialRevisionStatus::ACCEPTED) { $action = DifferentialAction::ACTION_COMMENT; break; } if (!isset($reviewer_phids[$actor_phid])) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array(), $add = array($actor_phid), $actor_phid); } $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVISION)->save(); break; case DifferentialAction::ACTION_RETHINK: if (!$actor_is_author) { throw new Exception("You can not plan changes to somebody else's revision"); } if ($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW && $revision_status != DifferentialRevisionStatus::ACCEPTED) { $action = DifferentialAction::ACTION_COMMENT; break; } $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVISION)->save(); break; case DifferentialAction::ACTION_RECLAIM: if (!$actor_is_author) { throw new Exception('You can not reclaim a revision you do not own.'); } if ($revision_status != DifferentialRevisionStatus::ABANDONED) { $action = DifferentialAction::ACTION_COMMENT; break; } $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW)->save(); break; case DifferentialAction::ACTION_COMMIT: if (!$actor_is_author) { throw new Exception('You can not commit a revision you do not own.'); } $revision->setStatus(DifferentialRevisionStatus::COMMITTED)->save(); break; case DifferentialAction::ACTION_ADDREVIEWERS: $added_reviewers = $this->getAddedReviewers(); foreach ($added_reviewers as $k => $user_phid) { if ($user_phid == $revision->getAuthorPHID()) { unset($added_reviewers[$k]); } if (!empty($reviewer_phids[$user_phid])) { unset($added_reviewers[$k]); } } $added_reviewers = array_unique($added_reviewers); if ($added_reviewers) { DifferentialRevisionEditor::alterReviewers($revision, $reviewer_phids, $rem = array(), $add = $added_reviewers, $actor_phid); $key = DifferentialComment::METADATA_ADDED_REVIEWERS; $metadata[$key] = $added_reviewers; } else { $action = DifferentialAction::ACTION_COMMENT; } break; case DifferentialAction::ACTION_ADDCCS: $added_ccs = $this->getAddedCCs(); $current_ccs = $revision->getCCPHIDs(); if ($current_ccs) { $current_ccs = array_fill_keys($current_ccs, true); foreach ($added_ccs as $k => $cc) { if (isset($current_ccs[$cc])) { unset($added_ccs[$k]); } } } if ($added_ccs) { foreach ($added_ccs as $cc) { DifferentialRevisionEditor::addCC($revision, $cc, $this->actorPHID); } $key = DifferentialComment::METADATA_ADDED_CCS; $metadata[$key] = $added_ccs; } else { $action = DifferentialAction::ACTION_COMMENT; } break; default: throw new Exception('Unsupported action.'); } if ($this->addCC) { DifferentialRevisionEditor::addCC($revision, $this->actorPHID, $this->actorPHID); } $inline_comments = array(); if ($this->attachInlineComments) { $inline_comments = id(new DifferentialInlineComment())->loadAllWhere('authorPHID = %s AND revisionID = %d AND commentID IS NULL', $this->actorPHID, $revision->getID()); } $comment = id(new DifferentialComment())->setAuthorPHID($this->actorPHID)->setRevisionID($revision->getID())->setAction($action)->setContent((string) $this->message)->setMetadata($metadata)->save(); $changesets = array(); if ($inline_comments) { $load_ids = mpull($inline_comments, 'getChangesetID'); if ($load_ids) { $load_ids = array_unique($load_ids); $changesets = id(new DifferentialChangeset())->loadAllWhere('id in (%Ld)', $load_ids); } foreach ($inline_comments as $inline) { $inline->setCommentID($comment->getID()); $inline->save(); } } // Find any "@mentions" in the comment blocks. $content_blocks = array($comment->getContent()); foreach ($inline_comments as $inline) { $content_blocks[] = $inline->getContent(); } $mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($content_blocks); if ($mention_ccs) { $current_ccs = $revision->getCCPHIDs(); if ($current_ccs) { $current_ccs = array_fill_keys($current_ccs, true); foreach ($mention_ccs as $key => $mention_cc) { if (isset($current_ccs[$mention_cc])) { unset($mention_ccs); } } } if ($mention_ccs) { $metadata = $comment->getMetadata(); $metacc = idx($metadata, DifferentialComment::METADATA_ADDED_CCS, array()); foreach ($mention_ccs as $cc_phid) { DifferentialRevisionEditor::addCC($revision, $cc_phid, $this->actorPHID); $metacc[] = $cc_phid; } $metadata[DifferentialComment::METADATA_ADDED_CCS] = $metacc; $comment->setMetadata($metadata); $comment->save(); } } $phids = array($this->actorPHID); $handles = id(new PhabricatorObjectHandleData($phids))->loadHandles(); $actor_handle = $handles[$this->actorPHID]; $xherald_header = HeraldTranscript::loadXHeraldRulesHeader($revision->getPHID()); id(new DifferentialCommentMail($revision, $actor_handle, $comment, $changesets, $inline_comments))->setToPHIDs(array_merge($revision->getReviewers(), array($revision->getAuthorPHID())))->setCCPHIDs($revision->getCCPHIDs())->setChangedByCommit($this->getChangedByCommit())->setXHeraldRulesHeader($xherald_header)->setParentMessageID($this->parentMessageID)->send(); $event_data = array('revision_id' => $revision->getID(), 'revision_phid' => $revision->getPHID(), 'revision_name' => $revision->getTitle(), 'revision_author_phid' => $revision->getAuthorPHID(), 'action' => $comment->getAction(), 'feedback_content' => $comment->getContent(), 'actor_phid' => $this->actorPHID); id(new PhabricatorTimelineEvent('difx', $event_data))->recordEvent(); // TODO: Move to a daemon? id(new PhabricatorFeedStoryPublisher())->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_DIFFERENTIAL)->setStoryData($event_data)->setStoryTime(time())->setStoryAuthorPHID($this->actorPHID)->setRelatedPHIDs(array($revision->getPHID(), $this->actorPHID, $revision->getAuthorPHID()))->publish(); // TODO: Move to a daemon? PhabricatorSearchDifferentialIndexer::indexRevision($revision); return $comment; }
public function addComment(PhabricatorAuditComment $comment) { $commit = $this->commit; $user = $this->user; $other_comments = id(new PhabricatorAuditComment())->loadAllWhere('targetPHID = %s', $commit->getPHID()); $inline_comments = array(); if ($this->attachInlineComments) { $inline_comments = id(new PhabricatorAuditInlineComment())->loadAllWhere('authorPHID = %s AND commitPHID = %s AND auditCommentID IS NULL', $user->getPHID(), $commit->getPHID()); } $comment->setActorPHID($user->getPHID())->setTargetPHID($commit->getPHID())->save(); $content_blocks = array($comment->getContent()); if ($inline_comments) { foreach ($inline_comments as $inline) { $inline->setAuditCommentID($comment->getID()); $inline->save(); $content_blocks[] = $inline->getContent(); } } $ccs = $this->ccs; $auditors = $this->auditors; $metadata = $comment->getMetadata(); $metacc = array(); // Find any "@mentions" in the content blocks. $mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($content_blocks); if ($mention_ccs) { $metacc = idx($metadata, PhabricatorAuditComment::METADATA_ADDED_CCS, array()); foreach ($mention_ccs as $cc_phid) { $metacc[] = $cc_phid; } } if ($metacc) { $ccs = array_merge($ccs, $metacc); } // When a user submits an audit comment, we update all the audit requests // they have authority over to reflect the most recent status. The general // idea here is that if audit has triggered for, e.g., several packages, but // a user owns all of them, they can clear the audit requirement in one go // without auditing the commit for each trigger. $audit_phids = self::loadAuditPHIDsForUser($this->user); $audit_phids = array_fill_keys($audit_phids, true); $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere('commitPHID = %s', $commit->getPHID()); $action = $comment->getAction(); // TODO: We should validate the action, currently we allow anyone to, e.g., // close an audit if they muck with form parameters. I'll followup with this // and handle the no-effect cases (e.g., closing and already-closed audit). $user_is_author = $user->getPHID() == $commit->getAuthorPHID(); if ($action == PhabricatorAuditActionConstants::CLOSE) { // "Close" means wipe out all the concerns. $concerned_status = PhabricatorAuditStatusConstants::CONCERNED; foreach ($requests as $request) { if ($request->getAuditStatus() == $concerned_status) { $request->setAuditStatus(PhabricatorAuditStatusConstants::CLOSED); $request->save(); } } } else { if ($action == PhabricatorAuditActionConstants::RESIGN) { // "Resign" has unusual rules for writing user rows, only affects the // user row (never package/project rows), and always affects the user // row (other actions don't, if they were able to affect a package/project // row). $user_request = null; foreach ($requests as $request) { if ($request->getAuditorPHID() == $user->getPHID()) { $user_request = $request; break; } } if (!$user_request) { $user_request = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($commit->getPHID())->setAuditorPHID($user->getPHID())->setAuditReasons(array("Resigned")); } $user_request->setAuditStatus(PhabricatorAuditStatusConstants::RESIGNED)->save(); $requests[] = $user_request; } else { $have_any_requests = false; foreach ($requests as $request) { if (empty($audit_phids[$request->getAuditorPHID()])) { continue; } $request_is_for_user = $request->getAuditorPHID() == $user->getPHID(); $have_any_requests = true; $new_status = null; switch ($action) { case PhabricatorAuditActionConstants::COMMENT: case PhabricatorAuditActionConstants::ADD_CCS: case PhabricatorAuditActionConstants::ADD_AUDITORS: // Commenting or adding cc's/auditors doesn't change status. break; case PhabricatorAuditActionConstants::ACCEPT: if (!$user_is_author || $request_is_for_user) { // When modifying your own commits, you act only on behalf of // yourself, not your packages/projects -- the idea being that // you can't accept your own commits. $new_status = PhabricatorAuditStatusConstants::ACCEPTED; } break; case PhabricatorAuditActionConstants::CONCERN: if (!$user_is_author || $request_is_for_user) { // See above. $new_status = PhabricatorAuditStatusConstants::CONCERNED; } break; default: throw new Exception("Unknown action '{$action}'!"); } if ($new_status !== null) { $request->setAuditStatus($new_status); $request->save(); } } // If the user has no current authority over any audit trigger, make a // new one to represent their audit state. if (!$have_any_requests) { $new_status = null; switch ($action) { case PhabricatorAuditActionConstants::COMMENT: case PhabricatorAuditActionConstants::ADD_CCS: case PhabricatorAuditActionConstants::ADD_AUDITORS: $new_status = PhabricatorAuditStatusConstants::AUDIT_NOT_REQUIRED; break; case PhabricatorAuditActionConstants::ACCEPT: $new_status = PhabricatorAuditStatusConstants::ACCEPTED; break; case PhabricatorAuditActionConstants::CONCERN: $new_status = PhabricatorAuditStatusConstants::CONCERNED; break; case PhabricatorAuditActionConstants::CLOSE: // Impossible to reach this block with 'close'. // Impossible to reach this block with 'close'. default: throw new Exception("Unknown or invalid action '{$action}'!"); } $request = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($commit->getPHID())->setAuditorPHID($user->getPHID())->setAuditStatus($new_status)->setAuditReasons(array("Voluntary Participant"))->save(); $requests[] = $request; } } } $requests_by_auditor = mpull($requests, null, 'getAuditorPHID'); $requests_phids = array_keys($requests_by_auditor); $ccs = array_diff($ccs, $requests_phids); $auditors = array_diff($auditors, $requests_phids); if ($action == PhabricatorAuditActionConstants::ADD_CCS) { if ($ccs) { $metadata[PhabricatorAuditComment::METADATA_ADDED_CCS] = $ccs; $comment->setMetaData($metadata); } else { $comment->setAction(PhabricatorAuditActionConstants::COMMENT); } } if ($action == PhabricatorAuditActionConstants::ADD_AUDITORS) { if ($auditors) { $metadata[PhabricatorAuditComment::METADATA_ADDED_AUDITORS] = $auditors; $comment->setMetaData($metadata); } else { $comment->setAction(PhabricatorAuditActionConstants::COMMENT); } } $comment->save(); if ($auditors) { foreach ($auditors as $auditor_phid) { $audit_requested = PhabricatorAuditStatusConstants::AUDIT_REQUESTED; $requests[] = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($commit->getPHID())->setAuditorPHID($auditor_phid)->setAuditStatus($audit_requested)->setAuditReasons(array('Added by ' . $user->getUsername()))->save(); } } if ($ccs) { foreach ($ccs as $cc_phid) { $audit_cc = PhabricatorAuditStatusConstants::CC; $requests[] = id(new PhabricatorRepositoryAuditRequest())->setCommitPHID($commit->getPHID())->setAuditorPHID($cc_phid)->setAuditStatus($audit_cc)->setAuditReasons(array('Added by ' . $user->getUsername()))->save(); } } $commit->updateAuditStatus($requests); $commit->save(); $this->publishFeedStory($comment, array_keys($audit_phids)); PhabricatorSearchCommitIndexer::indexCommit($commit); $this->sendMail($comment, $other_comments, $inline_comments, $requests); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new ManiphestTaskQuery())->setViewer($user)->withIDs(array($request->getStr('taskID')))->executeOne(); if (!$task) { return new Aphront404Response(); } $task_uri = '/' . $task->getMonogram(); $transactions = array(); $action = $request->getStr('action'); // Compute new CCs added by @mentions. Several things can cause CCs to // be added as side effects: mentions, explicit CCs, users who aren't // CC'd interacting with the task, and ownership changes. We build up a // list of all the CCs and then construct a transaction for them at the // end if necessary. $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions($user, array($request->getStr('comments'))); $cc_transaction = new ManiphestTransaction(); $cc_transaction->setTransactionType(ManiphestTransaction::TYPE_CCS); $transaction = new ManiphestTransaction(); $transaction->setTransactionType($action); switch ($action) { case ManiphestTransaction::TYPE_STATUS: $transaction->setNewValue($request->getStr('resolution')); break; case ManiphestTransaction::TYPE_OWNER: $assign_to = $request->getArr('assign_to'); $assign_to = reset($assign_to); $transaction->setNewValue($assign_to); break; case ManiphestTransaction::TYPE_PROJECTS: $projects = $request->getArr('projects'); $projects = array_merge($projects, $task->getProjectPHIDs()); $projects = array_filter($projects); $projects = array_unique($projects); // TODO: Bleh. $project_type = PhabricatorProjectObjectHasProjectEdgeType::EDGECONST; $transaction->setTransactionType(PhabricatorTransactions::TYPE_EDGE)->setMetadataValue('edge:type', $project_type)->setNewValue(array('+' => array_fuse($projects))); break; case ManiphestTransaction::TYPE_CCS: // Accumulate the new explicit CCs into the array that we'll add in // the CC transaction later. $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); // Throw away the primary transaction. $transaction = null; break; case ManiphestTransaction::TYPE_PRIORITY: $transaction->setNewValue($request->getInt('priority')); break; case PhabricatorTransactions::TYPE_COMMENT: // Nuke this, we're going to create it below. $transaction = null; break; default: throw new Exception('unknown action'); } if ($transaction) { $transactions[] = $transaction; } // When you interact with a task, we add you to the CC list so you get // further updates, and possibly assign the task to you if you took an // ownership action (closing it) but it's currently unowned. We also move // previous owners to CC if ownership changes. Detect all these conditions // and create side-effect transactions for them. $implicitly_claimed = false; if ($action == ManiphestTransaction::TYPE_OWNER) { if ($task->getOwnerPHID() == $transaction->getNewValue()) { // If this is actually no-op, don't generate the side effect. } else { // Otherwise, when a task is reassigned, move the previous owner to CC. $added_ccs[] = $task->getOwnerPHID(); } } if ($action == ManiphestTransaction::TYPE_STATUS) { $resolution = $request->getStr('resolution'); if (!$task->getOwnerPHID() && ManiphestTaskStatus::isClosedStatus($resolution)) { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setTransactionType(ManiphestTransaction::TYPE_OWNER); $assign->setNewValue($user->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; } } $user_owns_task = false; if ($implicitly_claimed) { $user_owns_task = true; } else { if ($action == ManiphestTransaction::TYPE_OWNER) { if ($transaction->getNewValue() == $user->getPHID()) { $user_owns_task = true; } } else { if ($task->getOwnerPHID() == $user->getPHID()) { $user_owns_task = true; } } } if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC unless they're aleady CC'd. if (!in_array($user->getPHID(), $task->getCCPHIDs())) { $added_ccs[] = $user->getPHID(); } } // Evade no-effect detection in the new editor stuff until we can switch // to subscriptions. $added_ccs = array_filter(array_diff($added_ccs, $task->getCCPHIDs())); if ($added_ccs) { // We've added CCs, so include a CC transaction. $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); $cc_transaction->setNewValue($all_ccs); $transactions[] = $cc_transaction; } $comments = $request->getStr('comments'); if (strlen($comments) || !$transactions) { $transactions[] = id(new ManiphestTransaction())->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)->attachComment(id(new ManiphestTransactionComment())->setContent($comments)); } $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditor())->setActor($user)->setContentSourceFromRequest($request)->setContinueOnMissingFields(true)->setContinueOnNoEffect($request->isContinueRequest()); try { $editor->applyTransactions($task, $transactions); } catch (PhabricatorApplicationTransactionNoEffectException $ex) { return id(new PhabricatorApplicationTransactionNoEffectResponse())->setCancelURI($task_uri)->setException($ex); } $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); } $event = new PhabricatorEvent(PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array('task' => $task, 'new' => false, 'transactions' => $transactions)); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); return id(new AphrontRedirectResponse())->setURI($task_uri); }
public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $task = id(new ManiphestTask())->load($request->getStr('taskID')); if (!$task) { return new Aphront404Response(); } $transactions = array(); $action = $request->getStr('action'); // If we have drag-and-dropped files, attach them first in a separate // transaction. These can come in on any transaction type, which is why we // handle them separately. $files = array(); // Look for drag-and-drop uploads first. $file_phids = $request->getArr('files'); if ($file_phids) { $files = id(new PhabricatorFile())->loadAllWhere('phid in (%Ls)', $file_phids); } // This means "attach a file" even though we store other types of data // as 'attached'. if ($action == ManiphestTransactionType::TYPE_ATTACH) { if (!empty($_FILES['file'])) { $err = idx($_FILES['file'], 'error'); if ($err != UPLOAD_ERR_NO_FILE) { $file = PhabricatorFile::newFromPHPUpload($_FILES['file'], array('authorPHID' => $user->getPHID())); $files[] = $file; } } } // If we had explicit or drag-and-drop files, create a transaction // for those before we deal with whatever else might have happened. $file_transaction = null; if ($files) { $files = mpull($files, 'getPHID', 'getPHID'); $new = $task->getAttached(); foreach ($files as $phid) { if (empty($new[PhabricatorPHIDConstants::PHID_TYPE_FILE])) { $new[PhabricatorPHIDConstants::PHID_TYPE_FILE] = array(); } $new[PhabricatorPHIDConstants::PHID_TYPE_FILE][$phid] = array(); } $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_ATTACH); $transaction->setNewValue($new); $transactions[] = $transaction; $file_transaction = $transaction; } // Compute new CCs added by @mentions. Several things can cause CCs to // be added as side effects: mentions, explicit CCs, users who aren't // CC'd interacting with the task, and ownership changes. We build up a // list of all the CCs and then construct a transaction for them at the // end if necessary. $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(array($request->getStr('comments'))); $cc_transaction = new ManiphestTransaction(); $cc_transaction->setAuthorPHID($user->getPHID())->setTransactionType(ManiphestTransactionType::TYPE_CCS); $force_cc_transaction = false; $transaction = new ManiphestTransaction(); $transaction->setAuthorPHID($user->getPHID())->setComments($request->getStr('comments'))->setTransactionType($action); switch ($action) { case ManiphestTransactionType::TYPE_STATUS: $transaction->setNewValue($request->getStr('resolution')); break; case ManiphestTransactionType::TYPE_OWNER: $assign_to = $request->getArr('assign_to'); $assign_to = reset($assign_to); $transaction->setNewValue($assign_to); break; case ManiphestTransactionType::TYPE_PROJECTS: $projects = $request->getArr('projects'); $projects = array_merge($projects, $task->getProjectPHIDs()); $projects = array_filter($projects); $projects = array_unique($projects); $transaction->setNewValue($projects); break; case ManiphestTransactionType::TYPE_CCS: // Accumulate the new explicit CCs into the array that we'll add in // the CC transaction later. $added_ccs = array_merge($added_ccs, $request->getArr('ccs')); // Transfer any comments over to the CC transaction. $cc_transaction->setComments($transaction->getComments()); // Make sure we include this transaction, even if the user didn't // actually add any CC's, because we'll discard their comment otherwise. $force_cc_transaction = true; // Throw away the primary transaction. $transaction = null; break; case ManiphestTransactionType::TYPE_PRIORITY: $transaction->setNewValue($request->getInt('priority')); break; case ManiphestTransactionType::TYPE_NONE: case ManiphestTransactionType::TYPE_ATTACH: // If we have a file transaction, just get rid of this secondary // transaction and put the comments on it instead. if ($file_transaction) { $file_transaction->setComments($transaction->getComments()); $transaction = null; } break; default: throw new Exception('unknown action'); } if ($transaction) { $transactions[] = $transaction; } // When you interact with a task, we add you to the CC list so you get // further updates, and possibly assign the task to you if you took an // ownership action (closing it) but it's currently unowned. We also move // previous owners to CC if ownership changes. Detect all these conditions // and create side-effect transactions for them. $implicitly_claimed = false; switch ($action) { case ManiphestTransactionType::TYPE_OWNER: if ($task->getOwnerPHID() == $transaction->getNewValue()) { // If this is actually no-op, don't generate the side effect. break; } // Otherwise, when a task is reassigned, move the previous owner to CC. $added_ccs[] = $task->getOwnerPHID(); break; case ManiphestTransactionType::TYPE_STATUS: if (!$task->getOwnerPHID() && $request->getStr('resolution') != ManiphestTaskStatus::STATUS_OPEN) { // Closing an unassigned task. Assign the user as the owner of // this task. $assign = new ManiphestTransaction(); $assign->setAuthorPHID($user->getPHID()); $assign->setTransactionType(ManiphestTransactionType::TYPE_OWNER); $assign->setNewValue($user->getPHID()); $transactions[] = $assign; $implicitly_claimed = true; } break; } $user_owns_task = false; if ($implicitly_claimed) { $user_owns_task = true; } else { if ($action == ManiphestTransactionType::TYPE_OWNER) { if ($transaction->getNewValue() == $user->getPHID()) { $user_owns_task = true; } } else { if ($task->getOwnerPHID() == $user->getPHID()) { $user_owns_task = true; } } } if (!$user_owns_task) { // If we aren't making the user the new task owner and they aren't the // existing task owner, add them to CC. $added_ccs[] = $user->getPHID(); } if ($added_ccs || $force_cc_transaction) { // We've added CCs, so include a CC transaction. It's safe to do this even // if we're just "adding" CCs which already exist, because the // ManiphestTransactionEditor is smart enough to ignore them. $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs); $cc_transaction->setNewValue($all_ccs); $transactions[] = $cc_transaction; } $content_source = PhabricatorContentSource::newForSource(PhabricatorContentSource::SOURCE_WEB, array('ip' => $request->getRemoteAddr())); foreach ($transactions as $transaction) { $transaction->setContentSource($content_source); } $editor = new ManiphestTransactionEditor(); $editor->applyTransactions($task, $transactions); $draft = id(new PhabricatorDraft())->loadOneWhere('authorPHID = %s AND draftKey = %s', $user->getPHID(), $task->getPHID()); if ($draft) { $draft->delete(); } return id(new AphrontRedirectResponse())->setURI('/T' . $task->getID()); }