Page MenuHomestyx hydra

No OneTemporary

diff --git a/resources/sql/patches/137.auditmetadata.sql b/resources/sql/patches/137.auditmetadata.sql
new file mode 100644
index 0000000000..2f03fc1d7c
--- /dev/null
+++ b/resources/sql/patches/137.auditmetadata.sql
@@ -0,0 +1,5 @@
+ALTER TABLE phabricator_audit.audit_comment
+ ADD metadata LONGTEXT COLLATE utf8_bin NOT NULL;
+
+UPDATE phabricator_audit.audit_comment
+ SET metadata = '{}' WHERE metadata = '';
diff --git a/src/applications/audit/constants/action/PhabricatorAuditActionConstants.php b/src/applications/audit/constants/action/PhabricatorAuditActionConstants.php
index 7fc4db86f1..2d4a51b161 100644
--- a/src/applications/audit/constants/action/PhabricatorAuditActionConstants.php
+++ b/src/applications/audit/constants/action/PhabricatorAuditActionConstants.php
@@ -1,55 +1,61 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditActionConstants {
const CONCERN = 'concern';
const ACCEPT = 'accept';
const COMMENT = 'comment';
const RESIGN = 'resign';
const CLOSE = 'close';
+ const ADD_CCS = 'add_ccs';
+ const ADD_AUDITORS = 'add_auditors';
public static function getActionNameMap() {
static $map = array(
self::COMMENT => 'Comment',
self::CONCERN => 'Raise Concern',
self::ACCEPT => 'Accept Commit',
self::RESIGN => 'Resign from Audit',
self::CLOSE => 'Close Audit',
+ self::ADD_CCS => 'Add CCs',
+ self::ADD_AUDITORS => 'Add Auditors',
);
return $map;
}
public static function getActionName($constant) {
$map = self::getActionNameMap();
return idx($map, $constant, 'Unknown');
}
public static function getActionPastTenseVerb($action) {
static $map = array(
self::COMMENT => 'commented on',
self::CONCERN => 'raised a concern with',
self::ACCEPT => 'accepted',
self::RESIGN => 'resigned from',
self::CLOSE => 'closed',
+ self::ADD_CCS => 'added CCs to',
+ self::ADD_AUDITORS => 'added auditors to',
);
return idx($map, $action, 'updated');
}
}
diff --git a/src/applications/audit/constants/status/PhabricatorAuditStatusConstants.php b/src/applications/audit/constants/status/PhabricatorAuditStatusConstants.php
index 02e334f195..b90c0542c1 100644
--- a/src/applications/audit/constants/status/PhabricatorAuditStatusConstants.php
+++ b/src/applications/audit/constants/status/PhabricatorAuditStatusConstants.php
@@ -1,61 +1,63 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditStatusConstants {
const NONE = '';
const AUDIT_NOT_REQUIRED = 'audit-not-required';
const AUDIT_REQUIRED = 'audit-required';
const CONCERNED = 'concerned';
const ACCEPTED = 'accepted';
const AUDIT_REQUESTED = 'requested';
const RESIGNED = 'resigned';
const CLOSED = 'closed';
+ const CC = 'cc';
public static function getStatusNameMap() {
static $map = array(
self::NONE => 'Not Applicable',
self::AUDIT_NOT_REQUIRED => 'Audit Not Required',
self::AUDIT_REQUIRED => 'Audit Required',
self::CONCERNED => 'Concern Raised',
self::ACCEPTED => 'Accepted',
self::AUDIT_REQUESTED => 'Audit Requested',
self::RESIGNED => 'Resigned',
self::CLOSED => 'Closed',
+ self::CC => "Was CC'd",
);
return $map;
}
public static function getStatusName($code) {
return idx(self::getStatusNameMap(), $code, 'Unknown');
}
public static function getOpenStatusConstants() {
return array(
self::AUDIT_REQUIRED,
self::AUDIT_REQUESTED,
self::CONCERNED,
);
}
public static function isOpenStatus($status) {
return in_array($status, self::getOpenStatusConstants());
}
}
diff --git a/src/applications/audit/controller/addcomment/PhabricatorAuditAddCommentController.php b/src/applications/audit/controller/addcomment/PhabricatorAuditAddCommentController.php
index 2227249641..07becae652 100644
--- a/src/applications/audit/controller/addcomment/PhabricatorAuditAddCommentController.php
+++ b/src/applications/audit/controller/addcomment/PhabricatorAuditAddCommentController.php
@@ -1,64 +1,71 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditAddCommentController
extends PhabricatorAuditController {
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
if (!$request->isFormPost()) {
return new Aphront403Response();
}
$commit_phid = $request->getStr('commit');
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'phid = %s',
$commit_phid);
if (!$commit) {
return new Aphront404Response();
}
+ $phids = array($commit_phid);
+
+ $action = $request->getStr('action');
+
$comment = id(new PhabricatorAuditComment())
- ->setAction($request->getStr('action'))
+ ->setAction($action)
->setContent($request->getStr('content'));
+ $auditors = $request->getArr('auditors');
+ $ccs = $request->getArr('ccs');
id(new PhabricatorAuditCommentEditor($commit))
->setUser($user)
->setAttachInlineComments(true)
+ ->addAuditors($auditors)
+ ->addCCs($ccs)
->addComment($comment);
- $phids = array($commit_phid);
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$uri = $handles[$commit_phid]->getURI();
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'diffusion-audit-'.$commit->getID());
if ($draft) {
$draft->delete();
}
return id(new AphrontRedirectResponse())->setURI($uri);
}
}
diff --git a/src/applications/audit/controller/preview/PhabricatorAuditPreviewController.php b/src/applications/audit/controller/preview/PhabricatorAuditPreviewController.php
index 2a82dee89c..523b7337ad 100644
--- a/src/applications/audit/controller/preview/PhabricatorAuditPreviewController.php
+++ b/src/applications/audit/controller/preview/PhabricatorAuditPreviewController.php
@@ -1,62 +1,84 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditPreviewController
extends PhabricatorAuditController {
private $id;
public function willProcessRequest(array $data) {
$this->id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$commit = id(new PhabricatorRepositoryCommit())->load($this->id);
if (!$commit) {
return new Aphront404Response();
}
+ $action = $request->getStr('action');
+
$comment = id(new PhabricatorAuditComment())
->setActorPHID($user->getPHID())
->setTargetPHID($commit->getPHID())
- ->setAction($request->getStr('action'))
+ ->setAction($action)
->setContent($request->getStr('content'));
+ $phids = array(
+ $user->getPHID(),
+ $commit->getPHID(),
+ );
+
+ $auditors = $request->getStrList('auditors');
+ if ($action == PhabricatorAuditActionConstants::ADD_AUDITORS && $auditors) {
+ $comment->setMetadata(array(
+ PhabricatorAuditComment::METADATA_ADDED_AUDITORS => $auditors));
+ $phids = array_merge($phids, $auditors);
+ }
+
+ $ccs = $request->getStrList('ccs');
+ if ($action == PhabricatorAuditActionConstants::ADD_CCS && $ccs) {
+ $comment->setMetadata(array(
+ PhabricatorAuditComment::METADATA_ADDED_CCS => $ccs));
+ $phids = array_merge($phids, $ccs);
+ }
+
$view = id(new DiffusionCommentView())
->setUser($user)
->setComment($comment)
->setIsPreview(true);
- $phids = $view->getRequiredHandlePHIDs();
+ $phids = array_merge($phids, $view->getRequiredHandlePHIDs());
+
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
id(new PhabricatorDraft())
->setAuthorPHID($comment->getActorPHID())
->setDraftKey('diffusion-audit-'.$this->id)
->setDraft($comment->getContent())
->replace();
return id(new AphrontAjaxResponse())
->setContent($view->render());
}
}
diff --git a/src/applications/audit/controller/preview/__init__.php b/src/applications/audit/controller/preview/__init__.php
index 29abbc7cb5..fd54ac86a4 100644
--- a/src/applications/audit/controller/preview/__init__.php
+++ b/src/applications/audit/controller/preview/__init__.php
@@ -1,21 +1,22 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'aphront/response/404');
phutil_require_module('phabricator', 'aphront/response/ajax');
+phutil_require_module('phabricator', 'applications/audit/constants/action');
phutil_require_module('phabricator', 'applications/audit/controller/base');
phutil_require_module('phabricator', 'applications/audit/storage/auditcomment');
phutil_require_module('phabricator', 'applications/diffusion/view/comment');
phutil_require_module('phabricator', 'applications/draft/storage/draft');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/repository/storage/commit');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorAuditPreviewController.php');
diff --git a/src/applications/audit/editor/comment/PhabricatorAuditCommentEditor.php b/src/applications/audit/editor/comment/PhabricatorAuditCommentEditor.php
index 4dd7ee3718..a503774c9f 100644
--- a/src/applications/audit/editor/comment/PhabricatorAuditCommentEditor.php
+++ b/src/applications/audit/editor/comment/PhabricatorAuditCommentEditor.php
@@ -1,413 +1,519 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditCommentEditor {
private $commit;
private $user;
private $attachInlineComments;
+ private $auditors = array();
+ private $ccs = array();
public function __construct(PhabricatorRepositoryCommit $commit) {
$this->commit = $commit;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
+ public function addAuditors(array $auditor_phids) {
+ $this->auditors = array_merge($this->auditors, $auditor_phids);
+ return $this;
+ }
+
+ public function addCCs(array $cc_phids) {
+ $this->ccs = array_merge($this->ccs, $cc_phids);
+ return $this;
+ }
+
public function setAttachInlineComments($attach_inline_comments) {
$this->attachInlineComments = $attach_inline_comments;
return $this;
}
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 {
$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:
- // Comments don't change audit statuses.
+ 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;
case PhabricatorAuditActionConstants::RESIGN:
// NOTE: Resigning resigns ONLY your user request, not the requests
// of any projects or packages you are a member of.
if ($request_is_for_user) {
$new_status = PhabricatorAuditStatusConstants::RESIGNED;
}
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::RESIGN:
// If you're on an audit because of a package, we write an explicit
// resign row to remove it from your queue.
$new_status = PhabricatorAuditStatusConstants::RESIGNED;
break;
case PhabricatorAuditActionConstants::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);
+ $this->sendMail($comment, $other_comments, $inline_comments, $requests);
}
/**
* Load the PHIDs for all objects the user has the authority to act as an
* audit for. This includes themselves, and any packages they are an owner
* of.
*/
public static function loadAuditPHIDsForUser(PhabricatorUser $user) {
$phids = array();
// The user can audit on their own behalf.
$phids[$user->getPHID()] = true;
// The user can audit on behalf of all packages they own.
$owned_packages = PhabricatorOwnersOwner::loadAffiliatedPackages(
$user->getPHID());
if ($owned_packages) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'id IN (%Ld)',
mpull($owned_packages, 'getPackageID'));
foreach (mpull($packages, 'getPHID') as $phid) {
$phids[$phid] = true;
}
}
// The user can audit on behalf of all projects they are a member of.
$query = new PhabricatorProjectQuery();
$query->setMembers(array($user->getPHID()));
$projects = $query->execute();
foreach ($projects as $project) {
$phids[$project->getPHID()] = true;
}
return array_keys($phids);
}
private function publishFeedStory(
PhabricatorAuditComment $comment,
array $more_phids) {
$commit = $this->commit;
$user = $this->user;
$related_phids = array_merge(
array(
$user->getPHID(),
$commit->getPHID(),
),
$more_phids);
id(new PhabricatorFeedStoryPublisher())
->setRelatedPHIDs($related_phids)
->setStoryAuthorPHID($user->getPHID())
->setStoryTime(time())
->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_AUDIT)
->setStoryData(
array(
'commitPHID' => $commit->getPHID(),
'action' => $comment->getAction(),
'content' => $comment->getContent(),
))
->publish();
}
private function sendMail(
PhabricatorAuditComment $comment,
array $other_comments,
- array $inline_comments) {
+ array $inline_comments,
+ array $requests) {
+
assert_instances_of($other_comments, 'PhabricatorAuditComment');
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$commit = $this->commit;
$data = $commit->loadCommitData();
$summary = $data->getSummary();
$commit_phid = $commit->getPHID();
$phids = array($commit_phid);
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$handle = $handles[$commit_phid];
$name = $handle->getName();
$map = array(
PhabricatorAuditActionConstants::CONCERN => 'Raised Concern',
PhabricatorAuditActionConstants::ACCEPT => 'Accepted',
PhabricatorAuditActionConstants::RESIGN => 'Resigned',
PhabricatorAuditActionConstants::CLOSE => 'Closed',
+ PhabricatorAuditActionConstants::ADD_CCS => 'Added CCs',
+ PhabricatorAuditActionConstants::ADD_AUDITORS => 'Added Auditors',
);
$verb = idx($map, $comment->getAction(), 'Commented On');
$reply_handler = self::newReplyHandlerForCommit($commit);
$prefix = PhabricatorEnv::getEnvConfig('metamta.diffusion.subject-prefix');
$subject = "{$prefix} {$name}: {$summary}";
$vary_subject = "{$prefix} [{$verb}] {$name}: {$summary}";
$threading = self::getMailThreading($commit->getPHID());
list($thread_id, $thread_topic) = $threading;
$body = $this->renderMailBody(
$comment,
"{$name}: {$summary}",
$handle,
$reply_handler,
$inline_comments);
$email_to = array();
+ $email_cc = array();
$author_phid = $data->getCommitDetail('authorPHID');
if ($author_phid) {
$email_to[] = $author_phid;
}
$email_cc = array();
foreach ($other_comments as $other_comment) {
$email_cc[] = $other_comment->getActorPHID();
}
+ foreach ($requests as $request) {
+ if ($request->getAuditStatus() == PhabricatorAuditStatusConstants::CC) {
+ $email_cc[] = $request->getAuditorPHID();
+ }
+ }
+
$phids = array_merge($email_to, $email_cc);
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
// NOTE: Always set $is_new to false, because the "first" mail in the
// thread is the Herald notification of the commit.
$is_new = false;
$template = id(new PhabricatorMetaMTAMail())
->setSubject($subject)
->setVarySubject($subject)
->setFrom($comment->getActorPHID())
->setThreadID($thread_id, $is_new)
->addHeader('Thread-Topic', $thread_topic)
->setRelatedPHID($commit->getPHID())
->setIsBulk(true)
->setBody($body);
$mails = $reply_handler->multiplexMail(
$template,
array_select_keys($handles, $email_to),
array_select_keys($handles, $email_cc));
foreach ($mails as $mail) {
$mail->saveAndSend();
}
}
public static function getMailThreading($phid) {
return array(
'diffusion-audit-'.$phid,
'Diffusion Audit '.$phid,
);
}
public static function newReplyHandlerForCommit($commit) {
$reply_handler = PhabricatorEnv::newObjectFromConfig(
'metamta.diffusion.reply-handler');
$reply_handler->setMailReceiver($commit);
return $reply_handler;
}
private function renderMailBody(
PhabricatorAuditComment $comment,
$cname,
PhabricatorObjectHandle $handle,
PhabricatorMailReplyHandler $reply_handler,
array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$commit = $this->commit;
$user = $this->user;
$name = $user->getUsername();
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb(
$comment->getAction());
$body = array();
$body[] = "{$name} {$verb} commit {$cname}.";
if ($comment->getContent()) {
$body[] = $comment->getContent();
}
if ($inline_comments) {
$block = array();
$path_map = id(new DiffusionPathQuery())
->withPathIDs(mpull($inline_comments, 'getPathID'))
->execute();
$path_map = ipull($path_map, 'path', 'id');
foreach ($inline_comments as $inline) {
$path = idx($path_map, $inline->getPathID());
if ($path === null) {
continue;
}
$start = $inline->getLineNumber();
$len = $inline->getLineLength();
if ($len) {
$range = $start.'-'.($start + $len);
} else {
$range = $start;
}
$content = $inline->getContent();
$block[] = "{$path}:{$range} {$content}";
}
$body[] = "INLINE COMMENTS\n ".implode("\n ", $block);
}
$body[] = "COMMIT\n ".PhabricatorEnv::getProductionURI($handle->getURI());
$reply_instructions = $reply_handler->getReplyHandlerInstructions();
if ($reply_instructions) {
$body[] = "REPLY HANDLER ACTIONS\n ".$reply_instructions;
}
return implode("\n\n", $body)."\n";
}
}
diff --git a/src/applications/audit/editor/comment/__init__.php b/src/applications/audit/editor/comment/__init__.php
index 2610bb9286..ac18064b6f 100644
--- a/src/applications/audit/editor/comment/__init__.php
+++ b/src/applications/audit/editor/comment/__init__.php
@@ -1,28 +1,29 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/audit/constants/action');
phutil_require_module('phabricator', 'applications/audit/constants/status');
phutil_require_module('phabricator', 'applications/audit/storage/auditcomment');
phutil_require_module('phabricator', 'applications/audit/storage/inlinecommment');
phutil_require_module('phabricator', 'applications/diffusion/query/path');
phutil_require_module('phabricator', 'applications/feed/constants/story');
phutil_require_module('phabricator', 'applications/feed/publisher');
+phutil_require_module('phabricator', 'applications/markup/engine');
phutil_require_module('phabricator', 'applications/metamta/storage/mail');
phutil_require_module('phabricator', 'applications/owners/storage/owner');
phutil_require_module('phabricator', 'applications/owners/storage/package');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/project/query/project');
phutil_require_module('phabricator', 'applications/repository/storage/auditrequest');
phutil_require_module('phabricator', 'applications/search/index/indexer/repository');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phutil', 'utils');
phutil_require_source('PhabricatorAuditCommentEditor.php');
diff --git a/src/applications/audit/storage/auditcomment/PhabricatorAuditComment.php b/src/applications/audit/storage/auditcomment/PhabricatorAuditComment.php
index 9207ba6bf3..1a02343fc4 100644
--- a/src/applications/audit/storage/auditcomment/PhabricatorAuditComment.php
+++ b/src/applications/audit/storage/auditcomment/PhabricatorAuditComment.php
@@ -1,37 +1,44 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class PhabricatorAuditComment extends PhabricatorAuditDAO {
+ const METADATA_ADDED_AUDITORS = 'added-auditors';
+ const METADATA_ADDED_CCS = 'added-ccs';
+
protected $phid;
protected $actorPHID;
protected $targetPHID;
protected $action;
protected $content;
+ protected $metadata = array();
public function getConfiguration() {
return array(
+ self::CONFIG_SERIALIZATION => array(
+ 'metadata' => self::SERIALIZATION_JSON,
+ ),
self::CONFIG_AUX_PHID => true,
) + parent::getConfiguration();
}
public function generatePHID() {
return PhabricatorPHID::generateNewPHID('ACMT');
}
}
diff --git a/src/applications/diffusion/controller/commit/DiffusionCommitController.php b/src/applications/diffusion/controller/commit/DiffusionCommitController.php
index 2ab79be19a..fd2ba880c7 100644
--- a/src/applications/diffusion/controller/commit/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/commit/DiffusionCommitController.php
@@ -1,680 +1,724 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DiffusionCommitController extends DiffusionController {
const CHANGES_LIMIT = 100;
private $auditAuthorityPHIDs;
public function willProcessRequest(array $data) {
// This controller doesn't use blob/path stuff, just pass the dictionary
// in directly instead of using the AphrontRequest parsing mechanism.
$drequest = DiffusionRequest::newFromDictionary($data);
$this->diffusionRequest = $drequest;
}
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$user = $request->getUser();
$callsign = $drequest->getRepository()->getCallsign();
$content = array();
$content[] = $this->buildCrumbs(array(
'commit' => true,
));
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
if (!$commit) {
// TODO: Make more user-friendly.
throw new Exception('This commit has not parsed yet.');
}
$commit_data = $drequest->loadCommitData();
$commit->attachCommitData($commit_data);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
if ($is_foreign) {
$subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new AphrontErrorView();
$error_panel->setWidth(AphrontErrorView::WIDTH_WIDE);
$error_panel->setTitle('Commit Not Tracked');
$error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_panel->appendChild(
"This Diffusion repository is configured to track only one ".
"subdirectory of the entire Subversion repository, and this commit ".
"didn't affect the tracked subdirectory ('".
phutil_escape_html($subpath)."'), so no information is available.");
$content[] = $error_panel;
} else {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
require_celerity_resource('diffusion-commit-view-css');
require_celerity_resource('phabricator-remarkup-css');
$parent_query = DiffusionCommitParentsQuery::newFromDiffusionRequest(
$drequest);
$headsup_panel = new AphrontHeadsupView();
$headsup_panel->setHeader('Commit Detail');
$headsup_panel->setActionList(
$this->renderHeadsupActionList($commit));
$headsup_panel->setProperties(
$this->getCommitProperties(
$commit,
$commit_data,
$parent_query->loadParents()));
$headsup_panel->appendChild(
'<div class="diffusion-commit-message phabricator-remarkup">'.
$engine->markupText($commit_data->getCommitMessage()).
'</div>');
$content[] = $headsup_panel;
}
$query = new PhabricatorAuditQuery();
$query->withCommitPHIDs(array($commit->getPHID()));
$audit_requests = $query->execute();
$this->auditAuthorityPHIDs =
PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$content[] = $this->buildAuditTable($commit, $audit_requests);
$content[] = $this->buildComments($commit);
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$changes = $change_query->loadChanges();
$content[] = $this->buildMergesTable($commit);
$original_changes_count = count($changes);
if ($request->getStr('show_all') !== 'true' &&
$original_changes_count > self::CHANGES_LIMIT) {
$changes = array_slice($changes, 0, self::CHANGES_LIMIT);
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$count = count($changes);
$bad_commit = null;
if ($count == 0) {
$bad_commit = queryfx_one(
id(new PhabricatorRepository())->establishConnection('r'),
'SELECT * FROM %T WHERE fullCommitName = %s',
PhabricatorRepository::TABLE_BADCOMMIT,
'r'.$callsign.$commit->getCommitIdentifier());
}
if ($bad_commit) {
$error_panel = new AphrontErrorView();
$error_panel->setWidth(AphrontErrorView::WIDTH_WIDE);
$error_panel->setTitle('Bad Commit');
$error_panel->appendChild(
phutil_escape_html($bad_commit['description']));
$content[] = $error_panel;
} else if ($is_foreign) {
// Don't render anything else.
} else if (!count($changes)) {
$no_changes = new AphrontErrorView();
$no_changes->setWidth(AphrontErrorView::WIDTH_WIDE);
$no_changes->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle('Not Yet Parsed');
// TODO: This can also happen with weird SVN changes that don't do
// anything (or only alter properties?), although the real no-changes case
// is extremely rare and might be impossible to produce organically. We
// should probably write some kind of "Nothing Happened!" change into the
// DB once we parse these changes so we can distinguish between
// "not parsed yet" and "no changes".
$no_changes->appendChild(
"This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths).");
$content[] = $no_changes;
} else {
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
if ($count !== $original_changes_count) {
$show_all_button = phutil_render_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
phutil_escape_html('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle(sprintf(
"Showing only the first %d changes out of %s!",
self::CHANGES_LIMIT,
number_format($original_changes_count)));
$change_panel->appendChild($warning_view);
$change_panel->addButton($show_all_button);
}
$change_panel->appendChild($change_table);
$content[] = $change_panel;
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
$changes);
$vcs = $repository->getVersionControlSystem();
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$vcs_supports_directory_changes = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$vcs_supports_directory_changes = false;
break;
default:
throw new Exception("Unknown VCS.");
}
$references = array();
foreach ($changesets as $key => $changeset) {
$file_type = $changeset->getFileType();
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
if (!$vcs_supports_directory_changes) {
unset($changesets[$key]);
continue;
}
}
$references[$key] = $drequest->generateURI(
array(
'action' => 'rendering-ref',
'path' => $changeset->getFilename(),
));
}
// TODO: Some parts of the views still rely on properties of the
// DifferentialChangeset. Make the objects ephemeral to make sure we don't
// accidentally save them, and then set their ID to the appropriate ID for
// this application (the path IDs).
$pquery = new DiffusionPathIDQuery(mpull($changesets, 'getFilename'));
$path_ids = $pquery->loadPathIDs();
foreach ($changesets as $changeset) {
$changeset->makeEphemeral();
$changeset->setID($path_ids[$changeset->getFilename()]);
}
$change_list = new DifferentialChangesetListView();
$change_list->setChangesets($changesets);
$change_list->setRenderingReferences($references);
$change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
$change_list->setRepository($repository);
$change_list->setUser($user);
$change_list->setStandaloneURI(
'/diffusion/'.$callsign.'/diff/');
$change_list->setRawFileURIs(
// TODO: Implement this, somewhat tricky if there's an octopus merge
// or whatever?
null,
'/diffusion/'.$callsign.'/diff/?view=r');
$change_list->setInlineCommentControllerURI(
'/diffusion/inline/'.phutil_escape_uri($commit->getPHID()).'/');
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
$change_list =
'<div class="differential-primary-pane">'.
$change_list->render().
'</div>';
$content[] = $change_list;
}
$content[] = $this->buildAddCommentView($commit, $audit_requests);
return $this->buildStandardPageResponse(
$content,
array(
'title' => 'r'.$callsign.$commit->getCommitIdentifier(),
));
}
private function getCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$user = $this->getRequest()->getUser();
$task_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$commit->getPHID(),
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK);
$phids = $task_phids;
if ($data->getCommitDetail('authorPHID')) {
$phids[] = $data->getCommitDetail('authorPHID');
}
if ($data->getCommitDetail('reviewerPHID')) {
$phids[] = $data->getCommitDetail('reviewerPHID');
}
if ($data->getCommitDetail('differential.revisionPHID')) {
$phids[] = $data->getCommitDetail('differential.revisionPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
$handles = array();
if ($phids) {
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$props['Status'] = phutil_render_tag(
'strong',
array(),
phutil_escape_html($status));
}
$props['Committed'] = phabricator_datetime($commit->getEpoch(), $user);
$author_phid = $data->getCommitDetail('authorPHID');
if ($data->getCommitDetail('authorPHID')) {
$props['Author'] = $handles[$author_phid]->renderLink();
} else {
$props['Author'] = phutil_escape_html($data->getAuthorName());
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
$reviewer_name = $data->getCommitDetail('reviewerName');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
} else if ($reviewer_name) {
$props['Reviewer'] = phutil_escape_html($reviewer_name);
}
$revision_phid = $data->getCommitDetail('differential.revisionPHID');
if ($revision_phid) {
$props['Differential Revision'] = $handles[$revision_phid]->renderLink();
}
if ($parents) {
$parent_links = array();
foreach ($parents as $parent) {
$parent_links[] = $handles[$parent->getPHID()]->renderLink();
}
$props['Parents'] = implode(' &middot; ', $parent_links);
}
$request = $this->getDiffusionRequest();
$contains = DiffusionContainsQuery::newFromDiffusionRequest($request);
$branches = $contains->loadContainingBranches();
if ($branches) {
// TODO: Separate these into 'tracked' and other; link tracked branches.
$branches = implode(', ', array_keys($branches));
$branches = phutil_escape_html($branches);
$props['Branches'] = $branches;
}
if ($task_phids) {
$task_list = array();
foreach ($task_phids as $phid) {
$task_list[] = $handles[$phid]->renderLink();
}
$task_list = implode('<br />', $task_list);
$props['Tasks'] = $task_list;
}
return $props;
}
private function buildAuditTable(
PhabricatorRepositoryCommit $commit,
array $audits) {
assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits(array($commit));
$view->setUser($user);
$view->setShowDescriptions(false);
$phids = $view->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
$view->setAuthorityPHIDs($this->auditAuthorityPHIDs);
$panel = new AphrontPanelView();
$panel->setHeader('Audits');
$panel->setCaption('Audits you are responsible for are highlighted.');
$panel->appendChild($view);
return $panel;
}
private function buildComments(PhabricatorRepositoryCommit $commit) {
$user = $this->getRequest()->getUser();
$comments = id(new PhabricatorAuditComment())->loadAllWhere(
'targetPHID = %s ORDER BY dateCreated ASC',
$commit->getPHID());
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND auditCommentID IS NOT NULL',
$commit->getPHID());
$path_ids = mpull($inlines, 'getPathID');
$path_map = array();
if ($path_ids) {
$path_map = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
$path_map = ipull($path_map, 'path', 'id');
}
$view = new DiffusionCommentListView();
$view->setUser($user);
$view->setComments($comments);
$view->setInlineComments($inlines);
$view->setPathMap($path_map);
$phids = $view->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
return $view;
}
private function buildAddCommentView(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
// TODO: Make this comment panel hauntable
'haunt' => null,
));
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'diffusion-audit-'.$commit->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$actions = $this->getAuditActions($commit, $audit_requests);
$form = id(new AphrontFormView())
->setUser($user)
->setAction('/audit/addcomment/')
->addHiddenInput('commit', $commit->getPHID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel('Action')
->setName('action')
->setID('audit-action')
->setOptions($actions))
+ ->appendChild(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel('Add Auditors')
+ ->setName('auditors')
+ ->setControlID('add-auditors')
+ ->setControlStyle('display: none')
+ ->setID('add-auditors-tokenizer')
+ ->setDisableBehavior(true))
+ ->appendChild(
+ id(new AphrontFormTokenizerControl())
+ ->setLabel('Add CCs')
+ ->setName('ccs')
+ ->setControlID('add-ccs')
+ ->setControlStyle('display: none')
+ ->setID('add-ccs-tokenizer')
+ ->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTextAreaControl())
->setLabel('Comments')
->setName('content')
->setValue($draft)
->setID('audit-content')
->setCaption(phutil_render_tag(
'a',
array(
'href' => PhabricatorEnv::getDoclink(
'article/Remarkup_Reference.html'),
'tabindex' => '-1',
'target' => '_blank',
),
'Formatting Reference')))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? 'Submit' : 'Cook the Books'));
$panel = new AphrontPanelView();
$panel->setHeader($is_serious ? 'Audit Commit' : 'Creative Accounting');
$panel->appendChild($form);
require_celerity_resource('phabricator-transaction-view-css');
- Javelin::initBehavior('audit-preview', array(
+ Javelin::initBehavior(
+ 'differential-add-reviewers-and-ccs',
+ array(
+ 'dynamic' => array(
+ 'add-auditors-tokenizer' => array(
+ 'actions' => array('add_auditors' => 1),
+ 'src' => '/typeahead/common/users/',
+ 'row' => 'add-auditors',
+ 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
+ 'placeholder' => 'Type a user name...',
+ ),
+ 'add-ccs-tokenizer' => array(
+ 'actions' => array('add_ccs' => 1),
+ 'src' => '/typeahead/common/mailable/',
+ 'row' => 'add-ccs',
+ 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
+ 'placeholder' => 'Type a user or mailing list...',
+ ),
+ ),
+ 'select' => 'audit-action',
+ ));
+
+ Javelin::initBehavior('differential-feedback-preview', array(
'uri' => '/audit/preview/'.$commit->getID().'/',
'preview' => 'audit-preview',
'content' => 'audit-content',
'action' => 'audit-action',
+ 'previewTokenizers' => array(
+ 'auditors' => 'add-auditors-tokenizer',
+ 'ccs' => 'add-ccs-tokenizer',
+ ),
));
$preview_panel =
'<div class="aphront-panel-preview">
<div id="audit-preview">
<div class="aphront-panel-preview-loading-text">
Loading preview...
</div>
</div>
</div>';
$view = new AphrontNullView();
$view->appendChild($panel);
$view->appendChild($preview_panel);
return $view;
}
/**
* Return a map of available audit actions for rendering into a <select />.
* This shows the user valid actions, and does not show nonsense/invalid
* actions (like closing an already-closed commit, or resigning from a commit
* you have no association with).
*/
private function getAuditActions(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$user_is_author = ($commit->getAuthorPHID() == $user->getPHID());
$user_request = null;
foreach ($audit_requests as $audit_request) {
if ($audit_request->getAuditorPHID() == $user->getPHID()) {
$user_request = $audit_request;
break;
}
}
$actions = array();
$actions[PhabricatorAuditActionConstants::COMMENT] = true;
+ $actions[PhabricatorAuditActionConstants::ADD_CCS] = true;
+ $actions[PhabricatorAuditActionConstants::ADD_AUDITORS] = true;
// We allow you to accept your own commits. A use case here is that you
// notice an issue with your own commit and "Raise Concern" as an indicator
// to other auditors that you're on top of the issue, then later resolve it
// and "Accept". You can not accept on behalf of projects or packages,
// however.
$actions[PhabricatorAuditActionConstants::ACCEPT] = true;
$actions[PhabricatorAuditActionConstants::CONCERN] = true;
// To resign, a user must have authority on some request and not be the
// commit's author.
if (!$user_is_author) {
$may_resign = false;
foreach ($audit_requests as $request) {
if (empty($this->auditAuthorityPHIDs[$request->getAuditorPHID()])) {
continue;
}
$may_resign = true;
break;
}
// If the user has already resigned, don't show "Resign...".
$status_resigned = PhabricatorAuditStatusConstants::RESIGNED;
if ($user_request) {
if ($user_request->getAuditStatus() == $status_resigned) {
$may_resign = false;
}
}
if ($may_resign) {
$actions[PhabricatorAuditActionConstants::RESIGN] = true;
}
}
$status_concern = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED;
$concern_raised = ($commit->getAuditStatus() == $status_concern);
if ($user_is_author && $concern_raised) {
$actions[PhabricatorAuditActionConstants::CLOSE] = true;
}
foreach ($actions as $constant => $ignored) {
$actions[$constant] =
PhabricatorAuditActionConstants::getActionName($constant);
}
return $actions;
}
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
$drequest = $this->getDiffusionRequest();
$limit = 50;
$merge_query = DiffusionMergedCommitsQuery::newFromDiffusionRequest(
$drequest);
$merge_query->setLimit($limit + 1);
$merges = $merge_query->loadMergedCommits();
if (!$merges) {
return null;
}
$caption = null;
if (count($merges) > $limit) {
$merges = array_slice($merges, 0, $limit);
$caption =
"This commit merges more than {$limit} changes. Only the first ".
"{$limit} are shown.";
}
$history_table = new DiffusionHistoryTableView();
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($merges);
$phids = $history_table->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$history_table->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Merged Changes');
$panel->setCaption($caption);
$panel->appendChild($history_table);
return $panel;
}
private function renderHeadsupActionList(
PhabricatorRepositoryCommit $commit) {
$user = $this->getRequest()->getUser();
$actions = array();
require_celerity_resource('phabricator-flag-css');
$flag = PhabricatorFlagQuery::loadUserFlag($user, $commit->getPHID());
if ($flag) {
$class = PhabricatorFlagColor::getCSSClass($flag->getColor());
$color = PhabricatorFlagColor::getColorName($flag->getColor());
$action = new AphrontHeadsupActionView();
$action->setClass('flag-clear '.$class);
$action->setURI('/flag/delete/'.$flag->getID().'/');
$action->setName('Remove '.$color.' Flag');
$action->setWorkflow(true);
$actions[] = $action;
} else {
$action = new AphrontHeadsupActionView();
$action->setClass('phabricator-flag-ghost');
$action->setURI('/flag/edit/'.$commit->getPHID().'/');
$action->setName('Flag Commit');
$action->setWorkflow(true);
$actions[] = $action;
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$action = new AphrontHeadsupActionView();
$action->setName('Edit Maniphest Tasks');
$action->setURI('/search/attach/'.$commit->getPHID().'/TASK/edge/');
$action->setWorkflow(true);
$action->setClass('attach-maniphest');
$actions[] = $action;
if ($user->getIsAdmin()) {
$action = new AphrontHeadsupActionView();
$action->setName('MetaMTA Transcripts');
$action->setURI('/mail/?phid='.$commit->getPHID());
$action->setClass('transcripts-metamta');
$actions[] = $action;
}
$action = new AphrontHeadsupActionView();
$action->setName('Herald Transcripts');
$action->setURI('/herald/transcript/?phid='.$commit->getPHID());
$action->setClass('transcripts-herald');
$actions[] = $action;
$action_list = new AphrontHeadsupActionListView();
$action_list->setActions($actions);
return $action_list;
}
}
diff --git a/src/applications/diffusion/controller/commit/__init__.php b/src/applications/diffusion/controller/commit/__init__.php
index cfcfa786cb..8cfd49ef45 100644
--- a/src/applications/diffusion/controller/commit/__init__.php
+++ b/src/applications/diffusion/controller/commit/__init__.php
@@ -1,60 +1,61 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/audit/constants/action');
phutil_require_module('phabricator', 'applications/audit/constants/commitstatus');
phutil_require_module('phabricator', 'applications/audit/constants/status');
phutil_require_module('phabricator', 'applications/audit/editor/comment');
phutil_require_module('phabricator', 'applications/audit/query/audit');
phutil_require_module('phabricator', 'applications/audit/storage/auditcomment');
phutil_require_module('phabricator', 'applications/audit/storage/inlinecommment');
phutil_require_module('phabricator', 'applications/audit/view/list');
phutil_require_module('phabricator', 'applications/differential/constants/changetype');
phutil_require_module('phabricator', 'applications/differential/view/changesetlistview');
phutil_require_module('phabricator', 'applications/diffusion/controller/base');
phutil_require_module('phabricator', 'applications/diffusion/data/pathchange');
phutil_require_module('phabricator', 'applications/diffusion/query/contains/base');
phutil_require_module('phabricator', 'applications/diffusion/query/mergedcommits/base');
phutil_require_module('phabricator', 'applications/diffusion/query/parents/base');
phutil_require_module('phabricator', 'applications/diffusion/query/path');
phutil_require_module('phabricator', 'applications/diffusion/query/pathchange/base');
phutil_require_module('phabricator', 'applications/diffusion/query/pathid/base');
phutil_require_module('phabricator', 'applications/diffusion/request/base');
phutil_require_module('phabricator', 'applications/diffusion/view/commentlist');
phutil_require_module('phabricator', 'applications/diffusion/view/commitchangetable');
phutil_require_module('phabricator', 'applications/diffusion/view/historytable');
phutil_require_module('phabricator', 'applications/draft/storage/draft');
phutil_require_module('phabricator', 'applications/flag/constants/color');
phutil_require_module('phabricator', 'applications/flag/query/flag');
phutil_require_module('phabricator', 'applications/markup/engine');
phutil_require_module('phabricator', 'applications/phid/handle/data');
phutil_require_module('phabricator', 'applications/repository/constants/repositorytype');
phutil_require_module('phabricator', 'applications/repository/storage/repository');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'infrastructure/edges/constants/config');
phutil_require_module('phabricator', 'infrastructure/edges/query/edge');
phutil_require_module('phabricator', 'infrastructure/env');
phutil_require_module('phabricator', 'infrastructure/javelin/api');
phutil_require_module('phabricator', 'storage/queryfx');
phutil_require_module('phabricator', 'view/form/base');
phutil_require_module('phabricator', 'view/form/control/select');
phutil_require_module('phabricator', 'view/form/control/submit');
phutil_require_module('phabricator', 'view/form/control/textarea');
+phutil_require_module('phabricator', 'view/form/control/tokenizer');
phutil_require_module('phabricator', 'view/form/error');
phutil_require_module('phabricator', 'view/layout/headsup/action');
phutil_require_module('phabricator', 'view/layout/headsup/actionlist');
phutil_require_module('phabricator', 'view/layout/headsup/panel');
phutil_require_module('phabricator', 'view/layout/panel');
phutil_require_module('phabricator', 'view/null');
phutil_require_module('phabricator', 'view/utils');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionCommitController.php');
diff --git a/src/applications/diffusion/view/comment/DiffusionCommentView.php b/src/applications/diffusion/view/comment/DiffusionCommentView.php
index 8e1a8c611d..46aedc8219 100644
--- a/src/applications/diffusion/view/comment/DiffusionCommentView.php
+++ b/src/applications/diffusion/view/comment/DiffusionCommentView.php
@@ -1,198 +1,225 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DiffusionCommentView extends AphrontView {
private $user;
private $comment;
private $commentNumber;
private $handles;
private $isPreview;
private $pathMap;
private $inlineComments;
private $engine;
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setComment(PhabricatorAuditComment $comment) {
$this->comment = $comment;
return $this;
}
public function setCommentNumber($comment_number) {
$this->commentNumber = $comment_number;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setIsPreview($is_preview) {
$this->isPreview = $is_preview;
return $this;
}
public function setInlineComments(array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$this->inlineComments = $inline_comments;
return $this;
}
public function setPathMap(array $path_map) {
$this->pathMap = $path_map;
return $this;
}
public function getRequiredHandlePHIDs() {
return array($this->comment->getActorPHID());
}
private function getHandle($phid) {
if (empty($this->handles[$phid])) {
throw new Exception("Unloaded handle '{$phid}'!");
}
return $this->handles[$phid];
}
public function render() {
$comment = $this->comment;
$author = $this->getHandle($comment->getActorPHID());
$author_link = $author->renderLink();
$actions = $this->renderActions();
$content = $this->renderContent();
$classes = $this->renderClasses();
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setActions($actions)
->appendChild($content);
if ($this->isPreview) {
$xaction_view->setIsPreview(true);
} else {
$xaction_view
->setAnchor('comment-'.$this->commentNumber, '#'.$this->commentNumber)
->setEpoch($comment->getDateCreated());
}
foreach ($classes as $class) {
$xaction_view->addClass($class);
}
return $xaction_view->render();
}
private function renderActions() {
$comment = $this->comment;
$author = $this->getHandle($comment->getActorPHID());
$author_link = $author->renderLink();
$action = $comment->getAction();
$verb = PhabricatorAuditActionConstants::getActionPastTenseVerb($action);
+ $metadata = $comment->getMetadata();
+ $added_auditors = idx(
+ $metadata,
+ PhabricatorAuditComment::METADATA_ADDED_AUDITORS,
+ array());
+ $added_ccs = idx(
+ $metadata,
+ PhabricatorAuditComment::METADATA_ADDED_CCS,
+ array());
+
$actions = array();
- $actions[] = "{$author_link} ".phutil_escape_html($verb)." this commit.";
+ if ($action == PhabricatorAuditActionConstants::ADD_CCS) {
+ $rendered_ccs = $this->renderHandleList($added_ccs);
+ $actions[] = "{$author_link} added CCs: {$rendered_ccs}.";
+ } else if ($action == PhabricatorAuditActionConstants::ADD_AUDITORS) {
+ $rendered_auditors = $this->renderHandleList($added_auditors);
+ $actions[] = "{$author_link} added auditors: ".
+ "{$rendered_auditors}.";
+ } else {
+ $actions[] = "{$author_link} ".phutil_escape_html($verb)." this commit.";
+ }
foreach ($actions as $key => $action) {
$actions[$key] = '<div>'.$action.'</div>';
}
return $actions;
}
private function renderContent() {
$comment = $this->comment;
$engine = $this->getEngine();
if (!strlen($comment->getContent()) && empty($this->inlineComments)) {
return null;
} else {
return
'<div class="phabricator-remarkup">'.
$engine->markupText($comment->getContent()).
$this->renderSingleView($this->renderInlines()).
'</div>';
}
}
private function renderInlines() {
if (!$this->inlineComments) {
return null;
}
$inlines_by_path = mgroup($this->inlineComments, 'getPathID');
$view = new PhabricatorInlineSummaryView();
foreach ($inlines_by_path as $path_id => $inlines) {
$path = idx($this->pathMap, $path_id);
if ($path === null) {
continue;
}
$items = array();
foreach ($inlines as $inline) {
$items[] = array(
'id' => $inline->getID(),
'line' => $inline->getLineNumber(),
'length' => $inline->getLineLength(),
'content' => PhabricatorInlineSummaryView::renderCommentContent(
$inline,
$this->getEngine()),
);
}
$view->addCommentGroup($path, $items);
}
return $view;
}
private function getEngine() {
if (!$this->engine) {
$this->engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
}
return $this->engine;
}
+ private function renderHandleList(array $phids) {
+ $result = array();
+ foreach ($phids as $phid) {
+ $result[] = $this->handles[$phid]->renderLink();
+ }
+ return implode(', ', $result);
+ }
+
private function renderClasses() {
$comment = $this->comment;
$classes = array();
switch ($comment->getAction()) {
case PhabricatorAuditActionConstants::ACCEPT:
$classes[] = 'audit-accept';
break;
case PhabricatorAuditActionConstants::CONCERN:
$classes[] = 'audit-concern';
break;
}
return $classes;
}
}
diff --git a/src/applications/diffusion/view/comment/__init__.php b/src/applications/diffusion/view/comment/__init__.php
index 5e87e7e83d..b8d9fe82d0 100644
--- a/src/applications/diffusion/view/comment/__init__.php
+++ b/src/applications/diffusion/view/comment/__init__.php
@@ -1,19 +1,20 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
phutil_require_module('phabricator', 'applications/audit/constants/action');
+phutil_require_module('phabricator', 'applications/audit/storage/auditcomment');
phutil_require_module('phabricator', 'applications/markup/engine');
phutil_require_module('phabricator', 'infrastructure/diff/view/inline');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phabricator', 'view/layout/transaction');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionCommentView.php');
diff --git a/src/applications/diffusion/view/commentlist/DiffusionCommentListView.php b/src/applications/diffusion/view/commentlist/DiffusionCommentListView.php
index fec0b549d5..6c3aa00b69 100644
--- a/src/applications/diffusion/view/commentlist/DiffusionCommentListView.php
+++ b/src/applications/diffusion/view/commentlist/DiffusionCommentListView.php
@@ -1,94 +1,106 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DiffusionCommentListView extends AphrontView {
private $user;
private $comments;
private $inlineComments = array();
private $pathMap = array();
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setComments(array $comments) {
assert_instances_of($comments, 'PhabricatorAuditComment');
$this->comments = $comments;
return $this;
}
public function setInlineComments(array $inline_comments) {
assert_instances_of($inline_comments, 'PhabricatorInlineCommentInterface');
$this->inlineComments = $inline_comments;
return $this;
}
public function setPathMap(array $path_map) {
$this->pathMap = $path_map;
return $this;
}
public function getRequiredHandlePHIDs() {
$phids = array();
foreach ($this->comments as $comment) {
$phids[$comment->getActorPHID()] = true;
+ $metadata = $comment->getMetaData();
+
+ $ccs_key = PhabricatorAuditComment::METADATA_ADDED_CCS;
+ $added_ccs = idx($metadata, $ccs_key, array());
+ foreach ($added_ccs as $cc) {
+ $phids[$cc] = true;
+ }
+ $auditors_key = PhabricatorAuditComment::METADATA_ADDED_AUDITORS;
+ $added_auditors = idx($metadata, $auditors_key, array());
+ foreach ($added_auditors as $auditor) {
+ $phids[$auditor] = true;
+ }
}
foreach ($this->inlineComments as $comment) {
$phids[$comment->getAuthorPHID()] = true;
}
return array_keys($phids);
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function render() {
$inline_comments = mgroup($this->inlineComments, 'getAuditCommentID');
$num = 1;
$comments = array();
foreach ($this->comments as $comment) {
$inlines = idx($inline_comments, $comment->getID(), array());
$view = id(new DiffusionCommentView())
->setComment($comment)
->setInlineComments($inlines)
->setCommentNumber($num)
->setHandles($this->handles)
->setPathMap($this->pathMap)
->setUser($this->user);
$comments[] = $view->render();
++$num;
}
return
'<div class="diffusion-comment-list">'.
$this->renderSingleView($comments).
'</div>';
}
}
diff --git a/src/applications/diffusion/view/commentlist/__init__.php b/src/applications/diffusion/view/commentlist/__init__.php
index 740dae8035..aa7060f441 100644
--- a/src/applications/diffusion/view/commentlist/__init__.php
+++ b/src/applications/diffusion/view/commentlist/__init__.php
@@ -1,15 +1,16 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('phabricator', 'applications/audit/storage/auditcomment');
phutil_require_module('phabricator', 'applications/diffusion/view/comment');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phutil', 'utils');
phutil_require_source('DiffusionCommentListView.php');

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 29, 6:25 AM (1 d, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
108214
Default Alt Text
(72 KB)

Event Timeline