Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/differential/customfield/DifferentialAuthorField.php b/src/applications/differential/customfield/DifferentialAuthorField.php
index 6e14c8bbae..bac57755ab 100644
--- a/src/applications/differential/customfield/DifferentialAuthorField.php
+++ b/src/applications/differential/customfield/DifferentialAuthorField.php
@@ -1,38 +1,38 @@
<?php
final class DifferentialAuthorField
extends DifferentialCustomField {
public function getFieldKey() {
return 'differential:author';
}
public function getFieldName() {
return pht('Author');
}
public function getFieldDescription() {
return pht('Stores the revision author.');
}
public function canDisableField() {
return false;
}
public function shouldAppearInPropertyView() {
return true;
}
public function renderPropertyViewLabel() {
return $this->getFieldName();
}
public function getRequiredHandlePHIDsForPropertyView() {
return array($this->getObject()->getAuthorPHID());
}
public function renderPropertyViewValue(array $handles) {
- return $handles[$this->getObject()->getAuthorPHID()]->renderLink();
+ return $handles[$this->getObject()->getAuthorPHID()]->renderHovercardLink();
}
}
diff --git a/src/applications/differential/view/DifferentialReviewersView.php b/src/applications/differential/view/DifferentialReviewersView.php
index 5dd7c09d73..ac4e482278 100644
--- a/src/applications/differential/view/DifferentialReviewersView.php
+++ b/src/applications/differential/view/DifferentialReviewersView.php
@@ -1,130 +1,130 @@
<?php
final class DifferentialReviewersView extends AphrontView {
private $reviewers;
private $handles;
private $diff;
public function setReviewers(array $reviewers) {
assert_instances_of($reviewers, 'DifferentialReviewer');
$this->reviewers = $reviewers;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setActiveDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function render() {
$viewer = $this->getUser();
$view = new PHUIStatusListView();
foreach ($this->reviewers as $reviewer) {
$phid = $reviewer->getReviewerPHID();
$handle = $this->handles[$phid];
// If we're missing either the diff or action information for the
// reviewer, render information as current.
$is_current = (!$this->diff) ||
(!$reviewer->getDiffID()) ||
($this->diff->getID() == $reviewer->getDiffID());
$item = new PHUIStatusItemView();
$item->setHighlighted($reviewer->hasAuthority($viewer));
switch ($reviewer->getStatus()) {
case DifferentialReviewerStatus::STATUS_ADDED:
$item->setIcon(
PHUIStatusItemView::ICON_OPEN,
'bluegrey',
pht('Review Requested'));
break;
case DifferentialReviewerStatus::STATUS_ACCEPTED:
if ($is_current) {
$item->setIcon(
PHUIStatusItemView::ICON_ACCEPT,
'green',
pht('Accepted'));
} else {
$item->setIcon(
PHUIStatusItemView::ICON_ACCEPT,
'dark',
pht('Accepted Prior Diff'));
}
break;
case DifferentialReviewerStatus::STATUS_ACCEPTED_OLDER:
$item->setIcon(
PHUIStatusItemView::ICON_ACCEPT,
'dark',
pht('Accepted Prior Diff'));
break;
case DifferentialReviewerStatus::STATUS_REJECTED:
if ($is_current) {
$item->setIcon(
PHUIStatusItemView::ICON_REJECT,
'red',
pht('Requested Changes'));
} else {
$item->setIcon(
PHUIStatusItemView::ICON_REJECT,
'dark',
pht('Requested Changes to Prior Diff'));
}
break;
case DifferentialReviewerStatus::STATUS_REJECTED_OLDER:
$item->setIcon(
PHUIStatusItemView::ICON_REJECT,
'dark',
pht('Rejected Prior Diff'));
break;
case DifferentialReviewerStatus::STATUS_COMMENTED:
if ($is_current) {
$item->setIcon(
PHUIStatusItemView::ICON_INFO,
'blue',
pht('Commented'));
} else {
$item->setIcon(
'info-dark',
pht('Commented Previously'));
}
break;
case DifferentialReviewerStatus::STATUS_BLOCKING:
$item->setIcon(
PHUIStatusItemView::ICON_MINUS,
'red',
pht('Blocking Review'));
break;
default:
$item->setIcon(
PHUIStatusItemView::ICON_QUESTION,
'bluegrey',
pht('%s?', $reviewer->getStatus()));
break;
}
- $item->setTarget($handle->renderLink());
+ $item->setTarget($handle->renderHovercardLink());
$view->addItem($item);
}
return $view;
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
index e5e5083664..b2b49b43d4 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -1,322 +1,329 @@
<?php
final class ManiphestTaskDetailController extends ManiphestController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$task = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withIDs(array($id))
->needSubscriberPHIDs(true)
->executeOne();
if (!$task) {
return new Aphront404Response();
}
$field_list = PhabricatorCustomField::getObjectFields(
$task,
PhabricatorCustomField::ROLE_VIEW);
$field_list
->setViewer($viewer)
->readFieldsFromStorage($task);
$e_commit = ManiphestTaskHasCommitEdgeType::EDGECONST;
$e_dep_on = ManiphestTaskDependsOnTaskEdgeType::EDGECONST;
$e_dep_by = ManiphestTaskDependedOnByTaskEdgeType::EDGECONST;
$e_rev = ManiphestTaskHasRevisionEdgeType::EDGECONST;
$e_mock = ManiphestTaskHasMockEdgeType::EDGECONST;
$phid = $task->getPHID();
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_commit,
$e_dep_on,
$e_dep_by,
$e_rev,
$e_mock,
));
$edges = idx($query->execute(), $phid);
$phids = array_fill_keys($query->getDestinationPHIDs(), true);
if ($task->getOwnerPHID()) {
$phids[$task->getOwnerPHID()] = true;
}
$phids[$task->getAuthorPHID()] = true;
$phids = array_keys($phids);
$handles = $viewer->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($viewer)
->setContextObject($task)
->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION);
$timeline = $this->buildTransactionTimeline(
$task,
new ManiphestTransactionQuery(),
$engine);
$actions = $this->buildActionView($task);
$monogram = $task->getMonogram();
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($monogram, '/'.$monogram);
$header = $this->buildHeaderView($task);
$properties = $this->buildPropertyView(
$task, $field_list, $edges, $actions, $handles);
$description = $this->buildDescriptionView($task, $engine);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
->addPropertyList($properties);
if ($description) {
$object_box->addPropertyList($description);
}
$title = pht('%s %s', $monogram, $task->getTitle());
$comment_view = id(new ManiphestEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($task);
$timeline->setQuoteRef($monogram);
$comment_view->setTransactionTimeline($timeline);
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$task->getPHID(),
))
->appendChild(
array(
$object_box,
$timeline,
$comment_view,
));
}
private function buildHeaderView(ManiphestTask $task) {
$view = id(new PHUIHeaderView())
->setHeader($task->getTitle())
->setUser($this->getRequest()->getUser())
->setPolicyObject($task);
$status = $task->getStatus();
$status_name = ManiphestTaskStatus::renderFullDescription($status);
$view->addProperty(PHUIHeaderView::PROPERTY_STATUS, $status_name);
return $view;
}
private function buildActionView(ManiphestTask $task) {
$viewer = $this->getRequest()->getUser();
$id = $task->getID();
$phid = $task->getPHID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$task,
PhabricatorPolicyCapability::CAN_EDIT);
$view = id(new PhabricatorActionListView())
->setUser($viewer)
->setObject($task);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Task'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("/task/edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Merge Duplicates In'))
->setHref("/search/attach/{$phid}/TASK/merge/")
->setWorkflow(true)
->setIcon('fa-compress')
->setDisabled(!$can_edit)
->setWorkflow(true));
$edit_config = id(new ManiphestEditEngine())
->setViewer($viewer)
->loadDefaultEditConfiguration();
$can_create = (bool)$edit_config;
if ($can_create) {
$form_key = $edit_config->getIdentifier();
$edit_uri = id(new PhutilURI("/task/edit/form/{$form_key}/"))
->setQueryParam('parent', $id)
->setQueryParam('template', $id)
->setQueryParam('status', ManiphestTaskStatus::getDefaultStatus());
$edit_uri = $this->getApplicationURI($edit_uri);
} else {
// TODO: This will usually give us a somewhat-reasonable error page, but
// could be a bit cleaner.
$edit_uri = "/task/edit/{$id}/";
$edit_uri = $this->getApplicationURI($edit_uri);
}
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Subtask'))
->setHref($edit_uri)
->setIcon('fa-level-down')
->setDisabled(!$can_create)
->setWorkflow(!$can_create));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Blocking Tasks'))
->setHref("/search/attach/{$phid}/TASK/blocks/")
->setWorkflow(true)
->setIcon('fa-link')
->setDisabled(!$can_edit)
->setWorkflow(true));
return $view;
}
private function buildPropertyView(
ManiphestTask $task,
PhabricatorCustomFieldList $field_list,
array $edges,
PhabricatorActionListView $actions,
$handles) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($task)
->setActionList($actions);
- $view->addProperty(
- pht('Assigned To'),
- $task->getOwnerPHID()
- ? $handles->renderHandle($task->getOwnerPHID())
- : phutil_tag('em', array(), pht('None')));
+ $owner_phid = $task->getOwnerPHID();
+ if ($owner_phid) {
+ $assigned_to = $handles
+ ->renderHandle($owner_phid)
+ ->setShowHovercard(true);
+ } else {
+ $assigned_to = phutil_tag('em', array(), pht('None'));
+ }
+
+ $view->addProperty(pht('Assigned To'), $assigned_to);
$view->addProperty(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
- $view->addProperty(
- pht('Author'),
- $handles->renderHandle($task->getAuthorPHID()));
+ $author = $handles
+ ->renderHandle($task->getAuthorPHID())
+ ->setShowHovercard(true);
+
+ $view->addProperty(pht('Author'), $author);
$source = $task->getOriginalEmailSource();
if ($source) {
$subject = '[T'.$task->getID().'] '.$task->getTitle();
$view->addProperty(
pht('From Email'),
phutil_tag(
'a',
array(
'href' => 'mailto:'.$source.'?subject='.$subject,
),
$source));
}
$edge_types = array(
ManiphestTaskDependedOnByTaskEdgeType::EDGECONST
=> pht('Blocks'),
ManiphestTaskDependsOnTaskEdgeType::EDGECONST
=> pht('Blocked By'),
ManiphestTaskHasRevisionEdgeType::EDGECONST
=> pht('Differential Revisions'),
ManiphestTaskHasMockEdgeType::EDGECONST
=> pht('Pholio Mocks'),
);
$revisions_commits = array();
$commit_phids = array_keys(
$edges[ManiphestTaskHasCommitEdgeType::EDGECONST]);
if ($commit_phids) {
$commit_drev = DiffusionCommitHasRevisionEdgeType::EDGECONST;
$drev_edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($commit_phids)
->withEdgeTypes(array($commit_drev))
->execute();
foreach ($commit_phids as $phid) {
$revisions_commits[$phid] = $handles->renderHandle($phid);
$revision_phid = key($drev_edges[$phid][$commit_drev]);
$revision_handle = $handles->getHandleIfExists($revision_phid);
if ($revision_handle) {
$task_drev = ManiphestTaskHasRevisionEdgeType::EDGECONST;
unset($edges[$task_drev][$revision_phid]);
$revisions_commits[$phid] = hsprintf(
'%s / %s',
$revision_handle->renderLink($revision_handle->getName()),
$revisions_commits[$phid]);
}
}
}
foreach ($edge_types as $edge_type => $edge_name) {
if ($edges[$edge_type]) {
$edge_handles = $viewer->loadHandles(array_keys($edges[$edge_type]));
$view->addProperty(
$edge_name,
$edge_handles->renderList());
}
}
if ($revisions_commits) {
$view->addProperty(
pht('Commits'),
phutil_implode_html(phutil_tag('br'), $revisions_commits));
}
$view->invokeWillRenderEvent();
$field_list->appendFieldsToPropertyList(
$task,
$viewer,
$view);
return $view;
}
private function buildDescriptionView(
ManiphestTask $task,
PhabricatorMarkupEngine $engine) {
$section = null;
if (strlen($task->getDescription())) {
$section = new PHUIPropertyListView();
$section->addSectionHeader(
pht('Description'),
PHUIPropertyListView::ICON_SUMMARY);
$section->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION)));
}
return $section;
}
}
diff --git a/src/applications/phid/PhabricatorObjectHandle.php b/src/applications/phid/PhabricatorObjectHandle.php
index 283ad0fb63..4eb6178338 100644
--- a/src/applications/phid/PhabricatorObjectHandle.php
+++ b/src/applications/phid/PhabricatorObjectHandle.php
@@ -1,338 +1,357 @@
<?php
final class PhabricatorObjectHandle
extends Phobject
implements PhabricatorPolicyInterface {
const AVAILABILITY_FULL = 'full';
const AVAILABILITY_NONE = 'none';
const AVAILABILITY_PARTIAL = 'partial';
const AVAILABILITY_DISABLED = 'disabled';
const STATUS_OPEN = 'open';
const STATUS_CLOSED = 'closed';
private $uri;
private $phid;
private $type;
private $name;
private $fullName;
private $title;
private $imageURI;
private $icon;
private $tagColor;
private $timestamp;
private $status = self::STATUS_OPEN;
private $availability = self::AVAILABILITY_FULL;
private $complete;
private $objectName;
private $policyFiltered;
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function getIcon() {
if ($this->getPolicyFiltered()) {
return 'fa-lock';
}
if ($this->icon) {
return $this->icon;
}
return $this->getTypeIcon();
}
public function setTagColor($color) {
static $colors;
if (!$colors) {
$colors = array_fuse(array_keys(PHUITagView::getShadeMap()));
}
if (isset($colors[$color])) {
$this->tagColor = $color;
}
return $this;
}
public function getTagColor() {
if ($this->getPolicyFiltered()) {
return 'disabled';
}
if ($this->tagColor) {
return $this->tagColor;
}
return 'blue';
}
public function getIconColor() {
if ($this->tagColor) {
return $this->tagColor;
}
return null;
}
public function getTypeIcon() {
if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeIcon();
}
return null;
}
public function setPolicyFiltered($policy_filered) {
$this->policyFiltered = $policy_filered;
return $this;
}
public function getPolicyFiltered() {
return $this->policyFiltered;
}
public function setObjectName($object_name) {
$this->objectName = $object_name;
return $this;
}
public function getObjectName() {
if (!$this->objectName) {
return $this->getName();
}
return $this->objectName;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
}
public function getURI() {
return $this->uri;
}
public function setPHID($phid) {
$this->phid = $phid;
return $this;
}
public function getPHID() {
return $this->phid;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
if ($this->name === null) {
if ($this->getPolicyFiltered()) {
return pht('Restricted %s', $this->getTypeName());
} else {
return pht('Unknown Object (%s)', $this->getTypeName());
}
}
return $this->name;
}
public function setAvailability($availability) {
$this->availability = $availability;
return $this;
}
public function getAvailability() {
return $this->availability;
}
public function isDisabled() {
return ($this->getAvailability() == self::AVAILABILITY_DISABLED);
}
public function setStatus($status) {
$this->status = $status;
return $this;
}
public function getStatus() {
return $this->status;
}
public function setFullName($full_name) {
$this->fullName = $full_name;
return $this;
}
public function getFullName() {
if ($this->fullName !== null) {
return $this->fullName;
}
return $this->getName();
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setType($type) {
$this->type = $type;
return $this;
}
public function getType() {
return $this->type;
}
public function setImageURI($uri) {
$this->imageURI = $uri;
return $this;
}
public function getImageURI() {
return $this->imageURI;
}
public function setTimestamp($timestamp) {
$this->timestamp = $timestamp;
return $this;
}
public function getTimestamp() {
return $this->timestamp;
}
public function getTypeName() {
if ($this->getPHIDType()) {
return $this->getPHIDType()->getTypeName();
}
return $this->getType();
}
/**
* Set whether or not the underlying object is complete. See
* @{method:isComplete} for an explanation of what it means to be complete.
*
* @param bool True if the handle represents a complete object.
* @return this
*/
public function setComplete($complete) {
$this->complete = $complete;
return $this;
}
/**
* Determine if the handle represents an object which was completely loaded
* (i.e., the underlying object exists) vs an object which could not be
* completely loaded (e.g., the type or data for the PHID could not be
* identified or located).
*
* Basically, @{class:PhabricatorHandleQuery} gives you back a handle for
* any PHID you give it, but it gives you a complete handle only for valid
* PHIDs.
*
* @return bool True if the handle represents a complete object.
*/
public function isComplete() {
return $this->complete;
}
public function renderLink($name = null) {
+ return $this->renderLinkWithAttributes($name, array());
+ }
+
+ public function renderHovercardLink($name = null) {
+ Javelin::initBehavior('phabricator-hovercards');
+
+ $attributes = array(
+ 'sigil' => 'hovercard',
+ 'meta' => array(
+ 'hoverPHID' => $this->getPHID(),
+ ),
+ );
+
+ return $this->renderLinkWithAttributes($name, $attributes);
+ }
+
+ private function renderLinkWithAttributes($name, array $attributes) {
if ($name === null) {
$name = $this->getLinkName();
}
$classes = array();
$classes[] = 'phui-handle';
$title = $this->title;
if ($this->status != self::STATUS_OPEN) {
$classes[] = 'handle-status-'.$this->status;
}
if ($this->availability != self::AVAILABILITY_FULL) {
$classes[] = 'handle-availability-'.$this->availability;
}
if ($this->getType() == PhabricatorPeopleUserPHIDType::TYPECONST) {
$classes[] = 'phui-link-person';
}
$uri = $this->getURI();
$icon = null;
if ($this->getPolicyFiltered()) {
$icon = id(new PHUIIconView())
->setIconFont('fa-lock lightgreytext');
}
- return phutil_tag(
+ $attributes = $attributes + array(
+ 'href' => $uri,
+ 'class' => implode(' ', $classes),
+ 'title' => $title,
+ );
+
+ return javelin_tag(
$uri ? 'a' : 'span',
- array(
- 'href' => $uri,
- 'class' => implode(' ', $classes),
- 'title' => $title,
- ),
+ $attributes,
array($icon, $name));
}
public function renderTag() {
return id(new PHUITagView())
->setType(PHUITagView::TYPE_OBJECT)
->setShade($this->getTagColor())
->setIcon($this->getIcon())
->setHref($this->getURI())
->setName($this->getLinkName());
}
public function getLinkName() {
switch ($this->getType()) {
case PhabricatorPeopleUserPHIDType::TYPECONST:
$name = $this->getName();
break;
default:
$name = $this->getFullName();
break;
}
return $name;
}
protected function getPHIDType() {
$types = PhabricatorPHIDType::getAllTypes();
return idx($types, $this->getType());
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_PUBLIC;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
// NOTE: Handles are always visible, they just don't get populated with
// data if the user can't see the underlying object.
return true;
}
public function describeAutomaticCapability($capability) {
return null;
}
}
diff --git a/src/applications/phid/view/PHUIHandleListView.php b/src/applications/phid/view/PHUIHandleListView.php
index 7ec0ce7606..cc9f3d00bd 100644
--- a/src/applications/phid/view/PHUIHandleListView.php
+++ b/src/applications/phid/view/PHUIHandleListView.php
@@ -1,51 +1,56 @@
<?php
/**
* Convenience class for rendering a list of handles.
*
* This class simplifies rendering a list of handles and improves loading and
* caching semantics in the rendering pipeline by delaying bulk loads until the
* last possible moment.
*/
final class PHUIHandleListView
extends AphrontTagView {
private $handleList;
private $asInline;
public function setHandleList(PhabricatorHandleList $list) {
$this->handleList = $list;
return $this;
}
public function setAsInline($inline) {
$this->asInline = $inline;
return $this;
}
public function getAsInline() {
return $this->asInline;
}
protected function getTagName() {
// TODO: It would be nice to render this with a proper <ul />, at least in
// block mode, but don't stir the waters up too much for now.
return 'span';
}
protected function getTagContent() {
+ $list = $this->handleList;
$items = array();
- foreach ($this->handleList as $handle) {
- $items[] = $handle->renderLink();
+ foreach ($list as $handle) {
+ $view = $list->renderHandle($handle->getPHID());
+
+ $view->setShowHovercard(true);
+
+ $items[] = $view;
}
if ($this->getAsInline()) {
$items = phutil_implode_html(', ', $items);
} else {
$items = phutil_implode_html(phutil_tag('br'), $items);
}
return $items;
}
}
diff --git a/src/applications/phid/view/PHUIHandleTagListView.php b/src/applications/phid/view/PHUIHandleTagListView.php
index f5ebe33d65..73cbef2350 100644
--- a/src/applications/phid/view/PHUIHandleTagListView.php
+++ b/src/applications/phid/view/PHUIHandleTagListView.php
@@ -1,111 +1,120 @@
<?php
final class PHUIHandleTagListView extends AphrontTagView {
private $handles;
private $annotations = array();
private $limit;
private $noDataString;
private $slim;
+ private $showHovercards;
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setAnnotations(array $annotations) {
$this->annotations = $annotations;
return $this;
}
public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
public function setNoDataString($no_data) {
$this->noDataString = $no_data;
return $this;
}
public function setSlim($slim) {
$this->slim = true;
return $this;
}
+ public function setShowHovercards($show_hovercards) {
+ $this->showHovercards = $show_hovercards;
+ return $this;
+ }
+
protected function getTagName() {
return 'ul';
}
protected function getTagAttributes() {
return array(
'class' => 'phabricator-handle-tag-list',
);
}
protected function getTagContent() {
$handles = $this->handles;
// If the list is empty, we may render a "No Projects" tag.
if (!$handles) {
if (strlen($this->noDataString)) {
$no_data_tag = $this->newPlaceholderTag()
->setName($this->noDataString);
return $this->newItem($no_data_tag);
}
}
if ($this->limit) {
$handles = array_slice($handles, 0, $this->limit);
}
$list = array();
foreach ($handles as $handle) {
$tag = $handle->renderTag();
+ if ($this->showHovercards) {
+ $tag->setPHID($handle->getPHID());
+ }
if ($this->slim) {
$tag->setSlimShady(true);
}
$list[] = $this->newItem(
array(
$tag,
idx($this->annotations, $handle->getPHID(), null),
));
}
if ($this->limit) {
if ($this->limit < count($this->handles)) {
$tip_text = implode(', ', mpull($this->handles, 'getName'));
$more = $this->newPlaceholderTag()
->setName("\xE2\x80\xA6")
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $tip_text,
'size' => 200,
));
$list[] = $this->newItem($more);
}
}
return $list;
}
private function newItem($content) {
return phutil_tag(
'li',
array(
'class' => 'phabricator-handle-tag-list-item',
),
$content);
}
private function newPlaceholderTag() {
return id(new PHUITagView())
->setType(PHUITagView::TYPE_OBJECT)
->setShade(PHUITagView::COLOR_DISABLED)
->setSlimShady($this->slim);
}
}
diff --git a/src/applications/phid/view/PHUIHandleView.php b/src/applications/phid/view/PHUIHandleView.php
index a4b766a991..f4bce39662 100644
--- a/src/applications/phid/view/PHUIHandleView.php
+++ b/src/applications/phid/view/PHUIHandleView.php
@@ -1,52 +1,73 @@
<?php
/**
* Convenience class for rendering a single handle.
*
* This class simplifies rendering a single handle, and improves loading and
* caching semantics in the rendering pipeline by loading data at the last
* moment.
*/
final class PHUIHandleView
extends AphrontView {
private $handleList;
private $handlePHID;
private $asTag;
private $useShortName;
+ private $showHovercard;
public function setHandleList(PhabricatorHandleList $list) {
$this->handleList = $list;
return $this;
}
public function setHandlePHID($phid) {
$this->handlePHID = $phid;
return $this;
}
public function setAsTag($tag) {
$this->asTag = $tag;
return $this;
}
public function setUseShortName($short) {
$this->useShortName = $short;
return $this;
}
+ public function setShowHovercard($hovercard) {
+ $this->showHovercard = $hovercard;
+ return $this;
+ }
+
public function render() {
$handle = $this->handleList[$this->handlePHID];
+
if ($this->asTag) {
- return $handle->renderTag();
- } else {
- if ($this->useShortName) {
- return $handle->renderLink($handle->getName());
- } else {
- return $handle->renderLink();
+ $tag = $handle->renderTag();
+
+ if ($this->showHovercard) {
+ $tag->setPHID($handle->getPHID());
}
+
+ return $tag;
}
+
+ if ($this->useShortName) {
+ $name = $handle->getName();
+ } else {
+ $name = null;
+ }
+
+ if ($this->showHovercard) {
+ $link = $handle->renderHovercardLink($name);
+ } else {
+ $link = $handle->renderLink($name);
+ }
+
+ return $link;
}
}
diff --git a/src/applications/project/events/PhabricatorProjectUIEventListener.php b/src/applications/project/events/PhabricatorProjectUIEventListener.php
index 326a9b9878..55a4a2c4a0 100644
--- a/src/applications/project/events/PhabricatorProjectUIEventListener.php
+++ b/src/applications/project/events/PhabricatorProjectUIEventListener.php
@@ -1,113 +1,114 @@
<?php
final class PhabricatorProjectUIEventListener
extends PhabricatorEventListener {
public function register() {
$this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES:
$this->handlePropertyEvent($event);
break;
}
}
private function handlePropertyEvent($event) {
$user = $event->getUser();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
// No object, or the object has no PHID yet..
return;
}
if (!($object instanceof PhabricatorProjectInterface)) {
// This object doesn't have projects.
return;
}
$project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs(
$object->getPHID(),
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST);
if ($project_phids) {
$project_phids = array_reverse($project_phids);
$handles = id(new PhabricatorHandleQuery())
->setViewer($user)
->withPHIDs($project_phids)
->execute();
} else {
$handles = array();
}
// If this object can appear on boards, build the workboard annotations.
// Some day, this might be a generic interface. For now, only tasks can
// appear on boards.
$can_appear_on_boards = ($object instanceof ManiphestTask);
$annotations = array();
if ($handles && $can_appear_on_boards) {
// TDOO: Generalize this UI and move it out of Maniphest.
require_celerity_resource('maniphest-task-summary-css');
$positions_query = id(new PhabricatorProjectColumnPositionQuery())
->setViewer($user)
->withBoardPHIDs($project_phids)
->withObjectPHIDs(array($object->getPHID()))
->needColumns(true);
// This is important because positions will be created "on demand"
// based on the set of columns. If we don't specify it, positions
// won't be created.
$columns = id(new PhabricatorProjectColumnQuery())
->setViewer($user)
->withProjectPHIDs($project_phids)
->execute();
if ($columns) {
$positions_query->withColumns($columns);
}
$positions = $positions_query->execute();
$positions = mpull($positions, null, 'getBoardPHID');
foreach ($project_phids as $project_phid) {
$handle = $handles[$project_phid];
$position = idx($positions, $project_phid);
if ($position) {
$column = $position->getColumn();
$column_name = pht('(%s)', $column->getDisplayName());
$column_link = phutil_tag(
'a',
array(
'href' => $handle->getURI().'board/',
'class' => 'maniphest-board-link',
),
$column_name);
$annotations[$project_phid] = array(
' ',
$column_link,
);
}
}
}
if ($handles) {
$list = id(new PHUIHandleTagListView())
->setHandles($handles)
- ->setAnnotations($annotations);
+ ->setAnnotations($annotations)
+ ->setShowHovercards(true);
} else {
$list = phutil_tag('em', array(), pht('None'));
}
$view = $event->getValue('view');
$view->addProperty(pht('Projects'), $list);
}
}
diff --git a/src/applications/subscriptions/view/SubscriptionListStringBuilder.php b/src/applications/subscriptions/view/SubscriptionListStringBuilder.php
index e8761e554e..7ccc729241 100644
--- a/src/applications/subscriptions/view/SubscriptionListStringBuilder.php
+++ b/src/applications/subscriptions/view/SubscriptionListStringBuilder.php
@@ -1,84 +1,84 @@
<?php
final class SubscriptionListStringBuilder extends Phobject {
private $handles;
private $objectPHID;
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function getHandles() {
return $this->handles;
}
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function buildTransactionString($change_type) {
$handles = $this->getHandles();
if (!$handles) {
return;
}
$list_uri = '/subscriptions/transaction/'.
$change_type.'/'.
$this->getObjectPHID().'/';
return $this->buildString($list_uri);
}
public function buildPropertyString() {
$handles = $this->getHandles();
if (!$handles) {
return phutil_tag('em', array(), pht('None'));
}
$list_uri = '/subscriptions/list/'.$this->getObjectPHID().'/';
return $this->buildString($list_uri);
}
private function buildString($list_uri) {
$handles = $this->getHandles();
// Always show this many subscribers.
$show_count = 3;
$subscribers_count = count($handles);
// It looks a bit silly to render "a, b, c, and 1 other", since we could
// have just put that other subscriber there in place of the "1 other"
// link. Instead, render "a, b, c, d" in this case, and then when we get one
// more render "a, b, c, and 2 others".
if ($subscribers_count <= ($show_count + 1)) {
- return phutil_implode_html(', ', mpull($handles, 'renderLink'));
+ return phutil_implode_html(', ', mpull($handles, 'renderHovercardLink'));
}
$show = array_slice($handles, 0, $show_count);
$show = array_values($show);
$not_shown_count = $subscribers_count - $show_count;
$not_shown_txt = pht('%d other(s)', $not_shown_count);
$not_shown_link = javelin_tag(
'a',
array(
'href' => $list_uri,
'sigil' => 'workflow',
),
$not_shown_txt);
return pht(
'%s, %s, %s and %s',
$show[0]->renderLink(),
$show[1]->renderLink(),
$show[2]->renderLink(),
$not_shown_link);
}
}
diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
index 855cf2b4a0..b9b1bf6505 100644
--- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
+++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldPHIDs.php
@@ -1,267 +1,267 @@
<?php
/**
* Common code for standard field types which store lists of PHIDs.
*/
abstract class PhabricatorStandardCustomFieldPHIDs
extends PhabricatorStandardCustomField {
public function buildFieldIndexes() {
$indexes = array();
$value = $this->getFieldValue();
if (is_array($value)) {
foreach ($value as $phid) {
$indexes[] = $this->newStringIndex($phid);
}
}
return $indexes;
}
public function readValueFromRequest(AphrontRequest $request) {
$value = $request->getArr($this->getFieldKey());
$this->setFieldValue($value);
}
public function getValueForStorage() {
$value = $this->getFieldValue();
if (!$value) {
return null;
}
return json_encode(array_values($value));
}
public function setValueFromStorage($value) {
// NOTE: We're accepting either a JSON string (a real storage value) or
// an array (from HTTP parameter prefilling). This is a little hacky, but
// should hold until this can get cleaned up more thoroughly.
// TODO: Clean this up.
$result = array();
if (!is_array($value)) {
$value = json_decode($value, true);
if (is_array($value)) {
$result = array_values($value);
}
}
$this->setFieldValue($value);
return $this;
}
public function readApplicationSearchValueFromRequest(
PhabricatorApplicationSearchEngine $engine,
AphrontRequest $request) {
return $request->getArr($this->getFieldKey());
}
public function applyApplicationSearchConstraintToQuery(
PhabricatorApplicationSearchEngine $engine,
PhabricatorCursorPagedPolicyAwareQuery $query,
$value) {
if ($value) {
$query->withApplicationSearchContainsConstraint(
$this->newStringIndex(null),
$value);
}
}
public function getRequiredHandlePHIDsForPropertyView() {
$value = $this->getFieldValue();
if ($value) {
return $value;
}
return array();
}
public function renderPropertyViewValue(array $handles) {
$value = $this->getFieldValue();
if (!$value) {
return null;
}
- $handles = mpull($handles, 'renderLink');
+ $handles = mpull($handles, 'renderHovercardLink');
$handles = phutil_implode_html(', ', $handles);
return $handles;
}
public function getRequiredHandlePHIDsForEdit() {
$value = $this->getFieldValue();
if ($value) {
return $value;
} else {
return array();
}
}
public function getApplicationTransactionRequiredHandlePHIDs(
PhabricatorApplicationTransaction $xaction) {
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
return array_merge($add, $rem);
}
public function getApplicationTransactionTitle(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && !$rem) {
return pht(
'%s updated %s, added %d: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($add),
$xaction->renderHandleList($add));
} else if ($rem && !$add) {
return pht(
'%s updated %s, removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($rem),
$xaction->renderHandleList($rem));
} else {
return pht(
'%s updated %s, added %s: %s; removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
phutil_count($add),
$xaction->renderHandleList($add),
phutil_count($rem),
$xaction->renderHandleList($rem));
}
}
public function getApplicationTransactionTitleForFeed(
PhabricatorApplicationTransaction $xaction) {
$author_phid = $xaction->getAuthorPHID();
$object_phid = $xaction->getObjectPHID();
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
if ($add && !$rem) {
return pht(
'%s updated %s for %s, added %d: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($add),
$xaction->renderHandleList($add));
} else if ($rem && !$add) {
return pht(
'%s updated %s for %s, removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($rem),
$xaction->renderHandleList($rem));
} else {
return pht(
'%s updated %s for %s, added %s: %s; removed %s: %s.',
$xaction->renderHandleLink($author_phid),
$this->getFieldName(),
$xaction->renderHandleLink($object_phid),
phutil_count($add),
$xaction->renderHandleList($add),
phutil_count($rem),
$xaction->renderHandleList($rem));
}
}
public function validateApplicationTransactions(
PhabricatorApplicationTransactionEditor $editor,
$type,
array $xactions) {
$errors = parent::validateApplicationTransactions(
$editor,
$type,
$xactions);
// If the user is adding PHIDs, make sure the new PHIDs are valid and
// visible to the actor. It's OK for a user to edit a field which includes
// some invalid or restricted values, but they can't add new ones.
foreach ($xactions as $xaction) {
$old = $this->decodeValue($xaction->getOldValue());
$new = $this->decodeValue($xaction->getNewValue());
$add = array_diff($new, $old);
$invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer(
$editor->getActor(),
$add);
if ($invalid) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Invalid'),
pht(
'Some of the selected PHIDs in field "%s" are invalid or '.
'restricted: %s.',
$this->getFieldName(),
implode(', ', $invalid)),
$xaction);
$errors[] = $error;
$this->setFieldError(pht('Invalid'));
}
}
return $errors;
}
public function shouldAppearInHerald() {
return true;
}
public function getHeraldFieldConditions() {
return array(
HeraldAdapter::CONDITION_INCLUDE_ALL,
HeraldAdapter::CONDITION_INCLUDE_ANY,
HeraldAdapter::CONDITION_INCLUDE_NONE,
HeraldAdapter::CONDITION_EXISTS,
HeraldAdapter::CONDITION_NOT_EXISTS,
);
}
public function getHeraldFieldValue() {
// If the field has a `null` value, make sure we hand an `array()` to
// Herald.
$value = parent::getHeraldFieldValue();
if ($value) {
return $value;
}
return array();
}
protected function decodeValue($value) {
$value = json_decode($value);
if (!is_array($value)) {
$value = array();
}
return $value;
}
protected function getHTTPParameterType() {
return new AphrontPHIDListHTTPParameterType();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Apr 29, 7:38 PM (1 d, 2 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
108474
Default Alt Text
(44 KB)

Event Timeline