Page MenuHomec4science

ManiphestTransactionDetailView.php
No OneTemporary

File Metadata

Created
Sun, Feb 23, 02:15

ManiphestTransactionDetailView.php

<?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.
*/
/**
* @group maniphest
*/
final class ManiphestTransactionDetailView extends ManiphestView {
private $transactions;
private $handles;
private $markupEngine;
private $forEmail;
private $preview;
private $commentNumber;
private $rangeSpecification;
private $renderSummaryOnly;
private $renderFullSummary;
private $user;
private $auxiliaryFields;
public function setAuxiliaryFields(array $fields) {
$this->auxiliaryFields = mpull($fields, null, 'getAuxiliaryKey');
return $this;
}
public function getAuxiliaryField($key) {
return idx($this->auxiliaryFields, $key);
}
public function setTransactionGroup(array $transactions) {
$this->transactions = $transactions;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhutilMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setPreview($preview) {
$this->preview = $preview;
return $this;
}
public function setRenderSummaryOnly($render_summary_only) {
$this->renderSummaryOnly = $render_summary_only;
return $this;
}
public function getRenderSummaryOnly() {
return $this->renderSummaryOnly;
}
public function setRenderFullSummary($render_full_summary) {
$this->renderFullSummary = $render_full_summary;
return $this;
}
public function getRenderFullSummary() {
return $this->renderFullSummary;
}
public function setCommentNumber($comment_number) {
$this->commentNumber = $comment_number;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setRangeSpecification($range) {
$this->rangeSpecification = $range;
return $this;
}
public function getRangeSpecification() {
return $this->rangeSpecification;
}
public function renderForEmail($with_date) {
$this->forEmail = true;
$transaction = reset($this->transactions);
$author = $this->renderHandles(array($transaction->getAuthorPHID()));
$action = null;
$descs = array();
$comments = null;
foreach ($this->transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
if ($action === null) {
$action = $verb;
}
$desc = $author.' '.$desc.'.';
if ($with_date) {
// NOTE: This is going into a (potentially multi-recipient) email so
// we can't use a single user's timezone preferences. Use the server's
// instead, but make the timezone explicit.
$datetime = date('M jS \a\t g:i A T', $transaction->getDateCreated());
$desc = "On {$datetime}, {$desc}";
}
$descs[] = $desc;
if ($transaction->hasComments()) {
$comments = $transaction->getComments();
}
}
$descs = implode("\n", $descs);
if ($comments) {
$descs .= "\n".$comments;
}
foreach ($this->transactions as $transaction) {
$supplemental = $this->renderSupplementalInfoForEmail($transaction);
if ($supplemental) {
$descs .= "\n\n".$supplemental;
}
}
$this->forEmail = false;
return array($action, $descs);
}
public function render() {
if (!$this->user) {
throw new Exception("Call setUser() before render()!");
}
$handles = $this->handles;
$transactions = $this->transactions;
require_celerity_resource('maniphest-transaction-detail-css');
$comment_transaction = null;
foreach ($this->transactions as $transaction) {
if ($transaction->hasComments()) {
$comment_transaction = $transaction;
break;
}
}
$any_transaction = reset($transactions);
$author = $this->handles[$any_transaction->getAuthorPHID()];
$more_classes = array();
$descs = array();
foreach ($transactions as $transaction) {
list($verb, $desc, $classes) = $this->describeAction($transaction);
if ($desc === null) {
continue;
}
$more_classes = array_merge($more_classes, $classes);
$full_summary = null;
if ($this->getRenderFullSummary()) {
$full_summary = $this->renderFullSummary($transaction);
}
$descs[] = javelin_render_tag(
'div',
array(
'sigil' => 'maniphest-transaction-description',
),
$author->renderLink().' '.$desc.'.'.$full_summary);
}
if ($this->getRenderSummaryOnly()) {
return implode("\n", $descs);
}
if ($comment_transaction && $comment_transaction->hasComments()) {
$comments = $comment_transaction->getCache();
if (!strlen($comments)) {
$comments = $comment_transaction->getComments();
if (strlen($comments)) {
$comments = $this->markupEngine->markupText($comments);
$comment_transaction->setCache($comments);
if ($comment_transaction->getID() && !$this->preview) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$comment_transaction->save();
unset($unguarded);
}
}
}
$comment_block =
'<div class="maniphest-transaction-comments phabricator-remarkup">'.
$comments.
'</div>';
} else {
$comment_block = null;
}
$source_transaction = nonempty($comment_transaction, $any_transaction);
$xaction_view = id(new PhabricatorTransactionView())
->setUser($this->user)
->setImageURI($author->getImageURI())
->setContentSource($source_transaction->getContentSource())
->setActions($descs);
foreach ($more_classes as $class) {
$xaction_view->addClass($class);
}
if ($this->preview) {
$xaction_view->setIsPreview($this->preview);
} else {
$xaction_view->setEpoch($any_transaction->getDateCreated());
if ($this->commentNumber) {
$anchor_name = 'comment-'.$this->commentNumber;
$anchor_text = 'T'.$any_transaction->getTaskID().'#'.$anchor_name;
$xaction_view->setAnchor($anchor_name, $anchor_text);
}
}
$xaction_view->appendChild($comment_block);
return $xaction_view->render();
}
private function renderSupplementalInfoForEmail($transaction) {
$handles = $this->handles;
$type = $transaction->getTransactionType();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
return "NEW DESCRIPTION\n ".trim($new)."\n\n".
"PREVIOUS DESCRIPTION\n ".trim($old);
case ManiphestTransactionType::TYPE_ATTACH:
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
$attach_types = array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_FILE,
);
foreach ($attach_types as $type) {
$old = array_keys(idx($old_raw, $type, array()));
$new = array_keys(idx($new_raw, $type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
if (!$added) {
break;
}
$links = array();
foreach (array_select_keys($handles, $added) as $handle) {
$links[] = ' '.PhabricatorEnv::getProductionURI($handle->getURI());
}
$links = implode("\n", $links);
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$title = 'ATTACHED REVISIONS';
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$title = 'ATTACHED FILES';
break;
}
return $title."\n".$links;
default:
break;
}
return null;
}
private function describeAction($transaction) {
$verb = null;
$desc = null;
$classes = array();
$handles = $this->handles;
$type = $transaction->getTransactionType();
$author_phid = $transaction->getAuthorPHID();
$new = $transaction->getNewValue();
$old = $transaction->getOldValue();
switch ($type) {
case ManiphestTransactionType::TYPE_TITLE:
$verb = 'Retitled';
$desc = 'changed the title from '.$this->renderString($old).
' to '.$this->renderString($new);
break;
case ManiphestTransactionType::TYPE_DESCRIPTION:
$verb = 'Edited';
if ($this->forEmail || $this->getRenderFullSummary()) {
$desc = 'updated the task description';
} else {
$desc = 'updated the task description; '.
$this->renderExpandLink($transaction);
}
break;
case ManiphestTransactionType::TYPE_NONE:
$verb = 'Commented On';
$desc = 'added a comment';
break;
case ManiphestTransactionType::TYPE_OWNER:
if ($transaction->getAuthorPHID() == $new) {
$verb = 'Claimed';
$desc = 'claimed this task';
$classes[] = 'claimed';
} else if (!$new) {
$verb = 'Up For Grabs';
$desc = 'placed this task up for grabs';
$classes[] = 'upforgrab';
} else if (!$old) {
$verb = 'Assigned';
$desc = 'assigned this task to '.$this->renderHandles(array($new));
$classes[] = 'assigned';
} else {
$verb = 'Reassigned';
$desc = 'reassigned this task from '.
$this->renderHandles(array($old)).
' to '.
$this->renderHandles(array($new));
$classes[] = 'reassigned';
}
break;
case ManiphestTransactionType::TYPE_CCS:
if ($this->preview) {
$verb = 'Changed CC';
$desc = 'changed CCs..';
break;
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
$verb = 'Added CC';
if (count($added) == 1) {
$desc = 'added '.$this->renderHandles($added).' to CC';
} else {
$desc = 'added CCs: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed CC';
if (count($removed) == 1) {
$desc = 'removed '.$this->renderHandles($removed).' from CC';
} else {
$desc = 'removed CCs: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed CC';
$desc = 'changed CCs, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_PROJECTS:
if ($this->preview) {
$verb = 'Changed Projects';
$desc = 'changed projects..';
break;
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
if ($added && !$removed) {
$verb = 'Added Project';
if (count($added) == 1) {
$desc = 'added project '.$this->renderHandles($added);
} else {
$desc = 'added projects: '.$this->renderHandles($added);
}
} else if ($removed && !$added) {
$verb = 'Removed Project';
if (count($removed) == 1) {
$desc = 'removed project '.$this->renderHandles($removed);
} else {
$desc = 'removed projectss: '.$this->renderHandles($removed);
}
} else {
$verb = 'Changed Projects';
$desc = 'changed projects, added: '.$this->renderHandles($added).'; '.
'removed: '.$this->renderHandles($removed);
}
break;
case ManiphestTransactionType::TYPE_STATUS:
if ($new == ManiphestTaskStatus::STATUS_OPEN) {
if ($old) {
$verb = 'Reopened';
$desc = 'reopened this task';
$classes[] = 'reopened';
} else {
$verb = 'Created';
$desc = 'created this task';
$classes[] = 'created';
}
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_SPITE) {
$verb = 'Spited';
$desc = 'closed this task out of spite';
$classes[] = 'spited';
} else if ($new == ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) {
$verb = 'Merged';
$desc = 'closed this task as a duplicate';
$classes[] = 'duplicate';
} else {
$verb = 'Closed';
$full = idx(ManiphestTaskStatus::getTaskStatusMap(), $new, '???');
$desc = 'closed this task as "'.$full.'"';
$classes[] = 'closed';
}
break;
case ManiphestTransactionType::TYPE_PRIORITY:
$old_name = ManiphestTaskPriority::getTaskPriorityName($old);
$new_name = ManiphestTaskPriority::getTaskPriorityName($new);
if ($old == ManiphestTaskPriority::PRIORITY_TRIAGE) {
$verb = 'Triaged';
$desc = 'triaged this task as "'.$new_name.'" priority';
} else if ($old > $new) {
$verb = 'Lowered Priority';
$desc = 'lowered the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
} else {
$verb = 'Raised Priority';
$desc = 'raised the priority of this task from "'.$old_name.'" to '.
'"'.$new_name.'"';
}
if ($new == ManiphestTaskPriority::PRIORITY_UNBREAK_NOW) {
$classes[] = 'unbreaknow';
}
break;
case ManiphestTransactionType::TYPE_ATTACH:
if ($this->preview) {
$verb = 'Changed Attached';
$desc = 'changed attachments..';
break;
}
$old_raw = nonempty($old, array());
$new_raw = nonempty($new, array());
foreach (array(
PhabricatorPHIDConstants::PHID_TYPE_DREV,
PhabricatorPHIDConstants::PHID_TYPE_TASK,
PhabricatorPHIDConstants::PHID_TYPE_FILE) as $type) {
$old = array_keys(idx($old_raw, $type, array()));
$new = array_keys(idx($new_raw, $type, array()));
if ($old != $new) {
break;
}
}
$added = array_diff($new, $old);
$removed = array_diff($old, $new);
$add_desc = $this->renderHandles($added);
$rem_desc = $this->renderHandles($removed);
switch ($type) {
case PhabricatorPHIDConstants::PHID_TYPE_DREV:
$singular = 'Differential Revision';
$plural = 'Differential Revisions';
break;
case PhabricatorPHIDConstants::PHID_TYPE_FILE:
$singular = 'file';
$plural = 'files';
break;
case PhabricatorPHIDConstants::PHID_TYPE_TASK:
$singular = 'Maniphest Task';
$plural = 'Maniphest Tasks';
$dependency = true;
break;
}
if ($added && !$removed) {
$verb = 'Attached';
if (count($added) == 1) {
$desc = 'attached '.$singular.': '.$add_desc;
} else {
$desc = 'attached '.$plural.': '.$add_desc;
}
} else if ($removed && !$added) {
$verb = 'Detached';
if (count($removed) == 1) {
$desc = 'detached '.$singular.': '.$rem_desc;
} else {
$desc = 'detached '.$plural.': '.$rem_desc;
}
} else {
$verb = 'Changed Attached';
$desc = 'changed attached '.$plural.', added: '.$add_desc.'; '.
'removed: '.$rem_desc;
}
break;
case ManiphestTransactionType::TYPE_AUXILIARY:
$aux_key = $transaction->getMetadataValue('aux:key');
$aux_field = $this->getAuxiliaryField($aux_key);
$verb = null;
if ($aux_field) {
$verb = $aux_field->renderTransactionEmailVerb($transaction);
}
if ($verb === null) {
if ($old === null) {
$verb = "Set Field";
} else if ($new === null) {
$verb = "Removed Field";
} else {
$verb = "Updated Field";
}
}
$desc = null;
if ($aux_field) {
$use_field = $aux_field;
} else {
$use_field = id(new ManiphestAuxiliaryFieldDefaultSpecification())
->setFieldType(
ManiphestAuxiliaryFieldDefaultSpecification::TYPE_STRING);
}
$desc = $use_field->renderTransactionDescription(
$transaction,
$this->forEmail
? ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_TEXT
: ManiphestAuxiliaryFieldSpecification::RENDER_TARGET_HTML);
break;
default:
return array($type, ' brazenly '.$type."'d", $classes);
}
return array($verb, $desc, $classes);
}
private function renderFullSummary($transaction) {
switch ($transaction->getTransactionType()) {
case ManiphestTransactionType::TYPE_DESCRIPTION:
$id = $transaction->getID();
$old_text = wordwrap($transaction->getOldValue(), 80);
$new_text = wordwrap($transaction->getNewValue(), 80);
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent($old_text,
$new_text);
$whitespace_mode = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
$parser = new DifferentialChangesetParser();
$parser->setChangeset($changeset);
$parser->setRenderingReference($id);
$parser->setWhitespaceMode($whitespace_mode);
$spec = $this->getRangeSpecification();
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$output = $parser->render($range_s, $range_e, $mask);
return $output;
}
return null;
}
private function renderExpandLink($transaction) {
$id = $transaction->getID();
Javelin::initBehavior('maniphest-transaction-expand');
return javelin_render_tag(
'a',
array(
'href' => '/maniphest/task/descriptionchange/'.$id.'/',
'sigil' => 'maniphest-expand-transaction',
'mustcapture' => true,
),
'show details');
}
private function renderHandles($phids) {
$links = array();
foreach ($phids as $phid) {
if ($this->forEmail) {
$links[] = $this->handles[$phid]->getName();
} else {
$links[] = $this->handles[$phid]->renderLink();
}
}
return implode(', ', $links);
}
private function renderString($string) {
if ($this->forEmail) {
return '"'.$string.'"';
} else {
return '"'.phutil_escape_html($string).'"';
}
}
}

Event Timeline