diff --git a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
index 611b762dd..41e6ce160 100644
--- a/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
+++ b/src/applications/daemon/controller/PhabricatorWorkerTaskDetailController.php
@@ -1,258 +1,259 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$task = id(new PhabricatorWorkerActiveTask())->load($this->id);
if (!$task) {
$task = id(new PhabricatorWorkerArchiveTask())->load($this->id);
}
if (!$task) {
$title = pht('Task Does Not Exist');
$error_view = new AphrontErrorView();
$error_view->setTitle(pht('No Such Task'));
$error_view->appendChild(phutil_tag(
'p',
array(),
pht('This task may have recently been garbage collected.')));
$error_view->setSeverity(AphrontErrorView::SEVERITY_NODATA);
$content = $error_view;
} else {
$title = pht('Task %d', $task->getID());
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Task %d (%s)',
$task->getID(),
$task->getTaskClass()));
$actions = $this->buildActionListView($task);
$properties = $this->buildPropertyListView($task);
$retry_head = id(new PhabricatorHeaderView())
->setHeader(pht('Retries'));
$retry_info = $this->buildRetryListView($task);
$content = array(
$header,
$actions,
$properties,
$retry_head,
$retry_info,
);
}
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title));
return $this->buildApplicationPage(
array(
$crumbs,
$content,
),
array(
'title' => $title,
'dust' => true,
'device' => true,
));
}
private function buildActionListView(PhabricatorWorkerTask $task) {
- $user = $this->getRequest()->getUser();
-
- $view = new PhabricatorActionListView();
- $view->setUser($user);
-
+ $request = $this->getRequest();
+ $user = $request->getUser();
$id = $task->getID();
+ $view = id(new PhabricatorActionListView())
+ ->setUser($user)
+ ->setObjectURI($request->getRequestURI());
+
if ($task->isArchived()) {
$result_success = PhabricatorWorkerArchiveTask::RESULT_SUCCESS;
$can_retry = ($task->getResult() != $result_success);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Retry Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/retry/'))
->setIcon('undo')
->setWorkflow(true)
->setDisabled(!$can_retry));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Cancel Task'))
->setHref($this->getApplicationURI('/task/'.$id.'/cancel/'))
->setIcon('delete')
->setWorkflow(true));
}
$can_release = (!$task->isArchived()) &&
($task->getLeaseOwner());
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Free Lease'))
->setHref($this->getApplicationURI('/task/'.$id.'/release/'))
->setIcon('unlock')
->setWorkflow(true)
->setDisabled(!$can_release));
return $view;
}
private function buildPropertyListView(PhabricatorWorkerTask $task) {
$view = new PhabricatorPropertyListView();
if ($task->isArchived()) {
switch ($task->getResult()) {
case PhabricatorWorkerArchiveTask::RESULT_SUCCESS:
$status = pht('Complete');
break;
case PhabricatorWorkerArchiveTask::RESULT_FAILURE:
$status = pht('Failed');
break;
case PhabricatorWorkerArchiveTask::RESULT_CANCELLED:
$status = pht('Cancelled');
break;
default:
throw new Exception("Unknown task status!");
}
} else {
$status = pht('Queued');
}
$view->addProperty(
pht('Task Status'),
$status);
$view->addProperty(
pht('Task Class'),
$task->getTaskClass());
if ($task->getLeaseExpires()) {
if ($task->getLeaseExpires() > time()) {
$lease_status = pht('Leased');
} else {
$lease_status = pht('Lease Expired');
}
} else {
$lease_status = phutil_tag('em', array(), pht('Not Leased'));
}
$view->addProperty(
pht('Lease Status'),
$lease_status);
$view->addProperty(
pht('Lease Owner'),
$task->getLeaseOwner()
? $task->getLeaseOwner()
: phutil_tag('em', array(), pht('None')));
if ($task->getLeaseExpires() && $task->getLeaseOwner()) {
$expires = ($task->getLeaseExpires() - time());
$expires = phabricator_format_relative_time_detailed($expires);
} else {
$expires = phutil_tag('em', array(), pht('None'));
}
$view->addProperty(
pht('Lease Expires'),
$expires);
if ($task->isArchived()) {
$duration = number_format($task->getDuration()).' us';
} else {
$duration = phutil_tag('em', array(), pht('Not Completed'));
}
$view->addProperty(
pht('Duration'),
$duration);
$data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
$task->setData($data->getData());
$worker = $task->getWorkerInstance();
$data = $worker->renderForDisplay();
$view->addProperty(
pht('Data'),
$data);
return $view;
}
private function buildRetryListView(PhabricatorWorkerTask $task) {
$view = new PhabricatorPropertyListView();
$data = id(new PhabricatorWorkerTaskData())->load($task->getDataID());
$task->setData($data->getData());
$worker = $task->getWorkerInstance();
$view->addProperty(
pht('Failure Count'),
$task->getFailureCount());
$retry_count = $worker->getMaximumRetryCount();
if ($retry_count === null) {
$max_retries = phutil_tag('em', array(), pht('Retries Forever'));
$retry_count = INF;
} else {
$max_retries = $retry_count;
}
$view->addProperty(
pht('Maximum Retries'),
$max_retries);
$projection = clone $task;
$projection->makeEphemeral();
$next = array();
for ($ii = $task->getFailureCount(); $ii < $retry_count; $ii++) {
$projection->setFailureCount($ii);
$next[] = $worker->getWaitBeforeRetry($projection);
if (count($next) > 10) {
break;
}
}
if ($next) {
$cumulative = 0;
foreach ($next as $key => $duration) {
if ($duration === null) {
$duration = 60;
}
$cumulative += $duration;
$next[$key] = phabricator_format_relative_time($cumulative);
}
if ($ii != $retry_count) {
$next[] = '...';
}
$retries_in = implode(', ', $next);
} else {
$retries_in = pht('No More Retries');
}
$view->addProperty(
pht('Retries After'),
$retries_in);
return $view;
}
}
diff --git a/src/applications/differential/events/DifferentialPeopleMenuEventListener.php b/src/applications/differential/events/DifferentialPeopleMenuEventListener.php
index 33551ce59..a0a88b85d 100644
--- a/src/applications/differential/events/DifferentialPeopleMenuEventListener.php
+++ b/src/applications/differential/events/DifferentialPeopleMenuEventListener.php
@@ -1,39 +1,38 @@
listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionsEvent($event);
break;
}
}
private function handleActionsEvent($event) {
$person = $event->getValue('object');
if (!($person instanceof PhabricatorUser)) {
return;
}
$href = '/differential/?authorPHIDs[]='.$person->getPHID();
$actions = $event->getValue('actions');
$actions[] = id(new PhabricatorActionView())
- ->setUser($event->getUser())
->setRenderAsForm(true)
->setIcon('differential-dark')
->setIconSheet(PHUIIconView::SPRITE_APPS)
->setName(pht('View Revisions'))
->setHref($href);
$event->setValue('actions', $actions);
}
}
diff --git a/src/applications/differential/view/DifferentialRevisionDetailView.php b/src/applications/differential/view/DifferentialRevisionDetailView.php
index 364fe796a..c4de9d98c 100644
--- a/src/applications/differential/view/DifferentialRevisionDetailView.php
+++ b/src/applications/differential/view/DifferentialRevisionDetailView.php
@@ -1,134 +1,135 @@
diff = $diff;
return $this;
}
private function getDiff() {
return $this->diff;
}
public function setRevision(DifferentialRevision $revision) {
$this->revision = $revision;
return $this;
}
public function setActions(array $actions) {
$this->actions = $actions;
return $this;
}
private function getActions() {
return $this->actions;
}
public function setAuxiliaryFields(array $fields) {
assert_instances_of($fields, 'DifferentialFieldSpecification');
$this->auxiliaryFields = $fields;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
$revision = $this->revision;
$user = $this->getUser();
$header = $this->renderHeader($revision);
$actions = id(new PhabricatorActionListView())
->setUser($user)
- ->setObject($revision);
+ ->setObject($revision)
+ ->setObjectURI($this->getRequest()->getRequestURI());
foreach ($this->getActions() as $action) {
$obj = id(new PhabricatorActionView())
->setIcon(idx($action, 'icon', 'edit'))
->setName($action['name'])
->setHref(idx($action, 'href'))
->setWorkflow(idx($action, 'sigil') == 'workflow')
->setRenderAsForm(!empty($action['instant']))
->setUser($user)
->setDisabled(idx($action, 'disabled', false));
$actions->addAction($obj);
}
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($revision);
$status = $revision->getStatus();
$local_vcs = $this->getDiff()->getSourceControlSystem();
$next_step = null;
if ($status == ArcanistDifferentialRevisionStatus::ACCEPTED) {
switch ($local_vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$bookmark = $this->getDiff()->getBookmark();
$next_step = ($bookmark != ''
? csprintf('arc land %s', $bookmark)
: 'arc land');
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$branch = $this->getDiff()->getBranch();
$next_step = ($branch != ''
? csprintf('arc land %s', $branch)
: 'arc land');
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
$next_step = 'arc commit';
break;
}
}
if ($next_step) {
$next_step = phutil_tag('tt', array(), $next_step);
$properties->addProperty(pht('Next Step'), $next_step);
}
foreach ($this->auxiliaryFields as $field) {
$value = $field->renderValueForRevisionView();
if ($value !== null) {
$label = rtrim($field->renderLabelForRevisionView(), ':');
$properties->addProperty($label, $value);
}
}
$properties->setHasKeyboardShortcuts(true);
return hsprintf(
'%s%s%s',
$header->render(),
$actions->render(),
$properties->render());
}
private function renderHeader(DifferentialRevision $revision) {
$view = id(new PhabricatorHeaderView())
->setHeader($revision->getTitle($revision));
$view->addTag(self::renderTagForRevision($revision));
return $view;
}
public static function renderTagForRevision(
DifferentialRevision $revision) {
$status = $revision->getStatus();
$status_name =
ArcanistDifferentialRevisionStatus::getNameForRevisionStatus($status);
$status_color =
DifferentialRevisionStatus::getRevisionStatusTagColor($status);
return id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName($status_name)
->setBackgroundColor($status_color);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
index 28f6015fa..bcdc75507 100644
--- a/src/applications/diffusion/controller/DiffusionBrowseFileController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseFileController.php
@@ -1,977 +1,980 @@
getRequest();
$drequest = $this->getDiffusionRequest();
$before = $request->getStr('before');
if ($before) {
return $this->buildBeforeResponse($before);
}
$path = $drequest->getPath();
$selected = $request->getStr('view');
$preferences = $request->getUser()->loadPreferences();
if (!$selected) {
$selected = $preferences->getPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
'highlighted');
} else if ($request->isFormPost() && $selected != 'raw') {
$preferences->setPreference(
PhabricatorUserPreferences::PREFERENCE_DIFFUSION_VIEW,
$selected);
$preferences->save();
return id(new AphrontRedirectResponse())
->setURI($request->getRequestURI()->alter('view', $selected));
}
$needs_blame = ($selected == 'plainblame');
if ($selected == 'blame' && $request->isAjax()) {
$needs_blame = true;
}
$file_content = DiffusionFileContent::newFromConduit(
$this->callConduitWithDiffusionRequest(
'diffusion.filecontentquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'needsBlame' => $needs_blame,
)));
$data = $file_content->getCorpus();
if ($selected === 'raw') {
return $this->buildRawResponse($path, $data);
}
$this->loadLintMessages();
// Build the content of the file.
$corpus = $this->buildCorpus(
$selected,
$file_content,
$needs_blame,
$drequest,
$path,
$data);
if ($request->isAjax()) {
return id(new AphrontAjaxResponse())->setContent($corpus);
}
require_celerity_resource('diffusion-source-css');
if ($this->corpusType == 'text') {
$view_select_panel = $this->renderViewSelectPanel($selected);
} else {
$view_select_panel = null;
}
// Render the page.
$content = array();
$follow = $request->getStr('follow');
if ($follow) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$notice->setTitle(pht('Unable to Continue'));
switch ($follow) {
case 'first':
$notice->appendChild(
pht("Unable to continue tracing the history of this file because ".
"this commit is the first commit in the repository."));
break;
case 'created':
$notice->appendChild(
pht("Unable to continue tracing the history of this file because ".
"this commit created the file."));
break;
}
$content[] = $notice;
}
$renamed = $request->getStr('renamed');
if ($renamed) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('File Renamed'));
$notice->appendChild(
pht("File history passes through a rename from '%s' to '%s'.",
$drequest->getPath(), $renamed));
$content[] = $notice;
}
$content[] = $view_select_panel;
$content[] = $corpus;
$content[] = $this->buildOpenRevisions();
$nav = $this->buildSideNav('browse', true);
$nav->appendChild($content);
$crumbs = $this->buildCrumbs(
array(
'branch' => true,
'path' => true,
'view' => 'browse',
));
$nav->setCrumbs($crumbs);
$basename = basename($this->getDiffusionRequest()->getPath());
return $this->buildApplicationPage(
$nav,
array(
'title' => $basename,
));
}
private function loadLintMessages() {
$drequest = $this->getDiffusionRequest();
$branch = $drequest->loadBranch();
if (!$branch || !$branch->getLintCommit()) {
return;
}
$this->lintCommit = $branch->getLintCommit();
$conn = id(new PhabricatorRepository())->establishConnection('r');
$where = '';
if ($drequest->getLint()) {
$where = qsprintf(
$conn,
'AND code = %s',
$drequest->getLint());
}
$this->lintMessages = queryfx_all(
$conn,
'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s',
PhabricatorRepository::TABLE_LINTMESSAGE,
$branch->getID(),
$where,
'/'.$drequest->getPath());
}
private function buildCorpus(
$selected,
DiffusionFileContent $file_content,
$needs_blame,
DiffusionRequest $drequest,
$path,
$data) {
if (ArcanistDiffUtils::isHeuristicBinaryFile($data)) {
$file = $this->loadFileForData($path, $data);
$file_uri = $file->getBestURI();
if ($file->isViewableImage()) {
$this->corpusType = 'image';
return $this->buildImageCorpus($file_uri);
} else {
$this->corpusType = 'binary';
return $this->buildBinaryCorpus($file_uri, $data);
}
}
switch ($selected) {
case 'plain':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
$corpus = phutil_tag(
'textarea',
array(
'style' => $style,
),
$file_content->getCorpus());
break;
case 'plainblame':
$style =
"margin: 1em 2em; width: 90%; height: 80em; font-family: monospace";
$text_list = $file_content->getTextList();
$rev_list = $file_content->getRevList();
$blame_dict = $file_content->getBlameDict();
$rows = array();
foreach ($text_list as $k => $line) {
$rev = $rev_list[$k];
$author = $blame_dict[$rev]['author'];
$rows[] =
sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
}
$corpus = phutil_tag(
'textarea',
array(
'style' => $style,
),
implode("\n", $rows));
break;
case 'highlighted':
case 'blame':
default:
require_celerity_resource('syntax-highlighting-css');
$text_list = $file_content->getTextList();
$rev_list = $file_content->getRevList();
$blame_dict = $file_content->getBlameDict();
$text_list = implode("\n", $text_list);
$text_list = PhabricatorSyntaxHighlighter::highlightWithFilename(
$path,
$text_list);
$text_list = explode("\n", $text_list);
$rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict,
$needs_blame, $drequest, $selected);
$corpus_table = javelin_tag(
'table',
array(
'class' => "diffusion-source remarkup-code PhabricatorMonospaced",
'sigil' => 'diffusion-source',
),
$rows);
if ($this->getRequest()->isAjax()) {
return $corpus_table;
}
$id = celerity_generate_unique_node_id();
$projects = $drequest->loadArcanistProjects();
$langs = array();
foreach ($projects as $project) {
$ls = $project->getSymbolIndexLanguages();
if (!$ls) {
continue;
}
$dep_projects = $project->getSymbolIndexProjects();
$dep_projects[] = $project->getPHID();
foreach ($ls as $lang) {
if (!isset($langs[$lang])) {
$langs[$lang] = array();
}
$langs[$lang] += $dep_projects + array($project);
}
}
$lang = last(explode('.', $drequest->getPath()));
if (isset($langs[$lang])) {
Javelin::initBehavior(
'repository-crossreference',
array(
'container' => $id,
'lang' => $lang,
'projects' => $langs[$lang],
));
}
$corpus = phutil_tag(
'div',
array(
'style' => 'padding: 0 2em;',
'id' => $id,
),
$corpus_table);
Javelin::initBehavior('load-blame', array('id' => $id));
break;
}
return $corpus;
}
private function renderViewSelectPanel($selected) {
$toggle_blame = array(
'highlighted' => 'blame',
'blame' => 'highlighted',
'plain' => 'plainblame',
'plainblame' => 'plain',
'raw' => 'raw', // not a real case.
);
$toggle_highlight = array(
'highlighted' => 'plain',
'blame' => 'plainblame',
'plain' => 'highlighted',
'plainblame' => 'blame',
'raw' => 'raw', // not a real case.
);
$user = $this->getRequest()->getUser();
$base_uri = $this->getRequest()->getRequestURI();
$blame_on = ($selected == 'blame' || $selected == 'plainblame');
if ($blame_on) {
$blame_text = pht('Disable Blame');
} else {
$blame_text = pht('Enable Blame');
}
$blame_button = $this->createViewAction(
$blame_text,
$base_uri->alter('view', $toggle_blame[$selected]),
$user);
$highlight_on = ($selected == 'blame' || $selected == 'highlighted');
if ($highlight_on) {
$highlight_text = pht('Disable Highlighting');
} else {
$highlight_text = pht('Enable Highlighting');
}
$highlight_button = $this->createViewAction(
$highlight_text,
$base_uri->alter('view', $toggle_highlight[$selected]),
$user);
$href = null;
if ($this->getRequest()->getStr('lint') !== null) {
$lint_text = pht('Hide %d Lint Message(s)', count($this->lintMessages));
$href = $base_uri->alter('lint', null);
} else if ($this->lintCommit === null) {
$lint_text = pht('Lint not Available');
} else {
$lint_text = pht(
'Show %d Lint Message(s)',
count($this->lintMessages));
$href = $this->getDiffusionRequest()->generateURI(array(
'action' => 'browse',
'commit' => $this->lintCommit,
))->alter('lint', '');
}
$lint_button = $this->createViewAction(
$lint_text,
$href,
$user);
if (!$href) {
$lint_button->setDisabled(true);
}
$raw_button = $this->createViewAction(
pht('View Raw File'),
$base_uri->alter('view', 'raw'),
$user,
'file');
$edit_button = $this->createEditAction();
return id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->addAction($blame_button)
->addAction($highlight_button)
->addAction($lint_button)
->addAction($raw_button)
->addAction($edit_button);
}
private function createViewAction(
$localized_text,
$href,
$user,
$icon = null) {
return id(new PhabricatorActionView())
->setName($localized_text)
->setIcon($icon)
->setUser($user)
->setRenderAsForm(true)
->setHref($href);
}
private function createEditAction() {
$request = $this->getRequest();
$user = $request->getUser();
$drequest = $this->getDiffusionRequest();
$repository = $drequest->getRepository();
$path = $drequest->getPath();
$line = nonempty((int)$drequest->getLine(), 1);
$callsign = $repository->getCallsign();
$editor_link = $user->loadEditorLink($path, $line, $callsign);
$action = id(new PhabricatorActionView())
->setName(pht('Open in Editor'))
->setIcon('edit');
$action->setHref($editor_link);
$action->setDisabled(!$editor_link);
return $action;
}
private function buildDisplayRows(
array $text_list,
array $rev_list,
array $blame_dict,
$needs_blame,
DiffusionRequest $drequest,
$selected) {
$handles = array();
if ($blame_dict) {
$epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
$epoch_min = min($epoch_list);
$epoch_max = max($epoch_list);
$epoch_range = ($epoch_max - $epoch_min) + 1;
$author_phids = ipull(ifilter($blame_dict, 'authorPHID'), 'authorPHID');
$handles = $this->loadViewerHandles($author_phids);
}
$line_arr = array();
$line_str = $drequest->getLine();
$ranges = explode(',', $line_str);
foreach ($ranges as $range) {
if (strpos($range, '-') !== false) {
list($min, $max) = explode('-', $range, 2);
$line_arr[] = array(
'min' => min($min, $max),
'max' => max($min, $max),
);
} else if (strlen($range)) {
$line_arr[] = array(
'min' => $range,
'max' => $range,
);
}
}
$display = array();
$line_number = 1;
$last_rev = null;
$color = null;
foreach ($text_list as $k => $line) {
$display_line = array(
'epoch' => null,
'commit' => null,
'author' => null,
'target' => null,
'highlighted' => null,
'line' => $line_number,
'data' => $line,
);
if ($selected == 'blame') {
// If the line's rev is same as the line above, show empty content
// with same color; otherwise generate blame info. The newer a change
// is, the more saturated the color.
$rev = idx($rev_list, $k, $last_rev);
if ($last_rev == $rev) {
$display_line['color'] = $color;
} else {
$blame = $blame_dict[$rev];
if (!isset($blame['epoch'])) {
$color = '#ffd'; // Render as warning.
} else {
$color_ratio = ($blame['epoch'] - $epoch_min) / $epoch_range;
$color_value = 0xE6 * (1.0 - $color_ratio);
$color = sprintf(
'#%02x%02x%02x',
$color_value,
0xF6,
$color_value);
}
$display_line['epoch'] = idx($blame, 'epoch');
$display_line['color'] = $color;
$display_line['commit'] = $rev;
$author_phid = idx($blame, 'authorPHID');
if ($author_phid && $handles[$author_phid]) {
$author_link = $handles[$author_phid]->renderLink();
} else {
$author_link = phutil_tag(
'span',
array(
),
$blame['author']);
}
$display_line['author'] = $author_link;
$last_rev = $rev;
}
}
if ($line_arr) {
if ($line_number == $line_arr[0]['min']) {
$display_line['target'] = true;
}
foreach ($line_arr as $range) {
if ($line_number >= $range['min'] &&
$line_number <= $range['max']) {
$display_line['highlighted'] = true;
}
}
}
$display[] = $display_line;
++$line_number;
}
$commits = array_filter(ipull($display, 'commit'));
if ($commits) {
$commits = id(new PhabricatorAuditCommitQuery())
->withIdentifiers($drequest->getRepository()->getID(), $commits)
->needCommitData(true)
->execute();
$commits = mpull($commits, null, 'getCommitIdentifier');
}
$revision_ids = id(new DifferentialRevision())
->loadIDsByCommitPHIDs(mpull($commits, 'getPHID'));
$revisions = array();
if ($revision_ids) {
$revisions = id(new DifferentialRevision())->loadAllWhere(
'id IN (%Ld)',
$revision_ids);
}
$request = $this->getRequest();
$user = $request->getUser();
Javelin::initBehavior('phabricator-oncopy', array());
$engine = null;
$inlines = array();
if ($this->getRequest()->getStr('lint') !== null && $this->lintMessages) {
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($this->lintMessages as $message) {
$inline = id(new PhabricatorAuditInlineComment())
->setID($message['id'])
->setSyntheticAuthor(
ArcanistLintSeverity::getStringForSeverity($message['severity']).
' '.$message['code'].' ('.$message['name'].')')
->setLineNumber($message['line'])
->setContent($message['description']);
$inlines[$message['line']][] = $inline;
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
require_celerity_resource('differential-changeset-view-css');
}
$rows = $this->renderInlines(
idx($inlines, 0, array()),
($selected == 'blame'),
$engine);
foreach ($display as $line) {
$line_href = $drequest->generateURI(
array(
'action' => 'browse',
'line' => $line['line'],
'stable' => true,
));
$blame = array();
$style = null;
if (array_key_exists('color', $line)) {
if ($line['color']) {
$style = 'background: '.$line['color'].';';
}
$before_link = null;
$commit_link = null;
$revision_link = null;
if (idx($line, 'commit')) {
$commit = $line['commit'];
$summary = 'Unknown';
if (idx($commits, $commit)) {
$summary = $commits[$commit]->getCommitData()->getSummary();
}
$tooltip = phabricator_date(
$line['epoch'],
$user)." \xC2\xB7 ".$summary;
Javelin::initBehavior('phabricator-tooltips', array());
require_celerity_resource('aphront-tooltip-css');
$commit_link = javelin_tag(
'a',
array(
'href' => $drequest->generateURI(
array(
'action' => 'commit',
'commit' => $line['commit'],
)),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
phutil_utf8_shorten($line['commit'], 9, ''));
$revision_id = null;
if (idx($commits, $commit)) {
$revision_id = idx($revision_ids, $commits[$commit]->getPHID());
}
if ($revision_id) {
$revision = idx($revisions, $revision_id);
if (!$revision) {
$tooltip = pht('(Invalid revision)');
} else {
$tooltip =
phabricator_date($revision->getDateModified(), $user).
" \xC2\xB7 ".
$revision->getTitle();
}
$revision_link = javelin_tag(
'a',
array(
'href' => '/D'.$revision_id,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $tooltip,
'align' => 'E',
'size' => 600,
),
),
'D'.$revision_id);
}
$uri = $line_href->alter('before', $commit);
$before_link = javelin_tag(
'a',
array(
'href' => $uri->setQueryParam('view', 'blame'),
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Skip Past This Commit'),
'align' => 'E',
'size' => 300,
),
),
"\xC2\xAB");
}
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-blame-link',
'style' => $style,
),
$before_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => $style,
),
$commit_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-rev-link',
'style' => $style,
),
$revision_link);
$blame[] = phutil_tag(
'th',
array(
'class' => 'diffusion-author-link',
'style' => $style,
),
idx($line, 'author'));
}
$line_link = phutil_tag(
'a',
array(
'href' => $line_href,
),
$line['line']);
$blame[] = javelin_tag(
'th',
array(
'class' => 'diffusion-line-link',
'sigil' => 'diffusion-line-link',
'style' => $style,
),
$line_link);
Javelin::initBehavior('diffusion-line-linker');
if ($line['target']) {
Javelin::initBehavior(
'diffusion-jump-to',
array(
'target' => 'scroll_target',
));
$anchor_text = phutil_tag(
'a',
array(
'id' => 'scroll_target',
),
'');
} else {
$anchor_text = null;
}
$blame[] = phutil_tag(
'td',
array(
),
array(
$anchor_text,
// NOTE: See phabricator-oncopy behavior.
"\xE2\x80\x8B",
// TODO: [HTML] Not ideal.
phutil_safe_html($line['data']),
));
$rows[] = phutil_tag(
'tr',
array(
'class' => ($line['highlighted'] ? 'highlighted' : null),
),
$blame);
$rows = array_merge($rows, $this->renderInlines(
idx($inlines, $line['line'], array()),
($selected == 'blame'),
$engine));
}
return $rows;
}
private function renderInlines(array $inlines, $needs_blame, $engine) {
$rows = array();
foreach ($inlines as $inline) {
$inline_view = id(new DifferentialInlineCommentView())
->setMarkupEngine($engine)
->setInlineComment($inline)
->render();
$row = array_fill(0, ($needs_blame ? 5 : 1), phutil_tag('th'));
$row[] = phutil_tag('td', array(), $inline_view);
$rows[] = phutil_tag('tr', array('class' => 'inline'), $row);
}
return $rows;
}
private function loadFileForData($path, $data) {
return PhabricatorFile::buildFromFileDataOrHash(
$data,
array(
'name' => basename($path),
'ttl' => time() + 60 * 60 * 24,
));
}
private function buildRawResponse($path, $data) {
$file = $this->loadFileForData($path, $data);
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
private function buildImageCorpus($file_uri) {
$properties = new PhabricatorPropertyListView();
$properties->addProperty(
pht('Image'),
phutil_tag(
'img',
array(
'src' => $file_uri,
)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->addAction($this->createEditAction());
return array($actions, $properties);
}
private function buildBinaryCorpus($file_uri, $data) {
$properties = new PhabricatorPropertyListView();
$size = strlen($data);
$properties->addTextContent(
pht(
'This is a binary file. It is %s byte(s) in length.',
new PhutilNumber($size)));
$actions = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->addAction($this->createEditAction())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Download Binary File...'))
->setIcon('download')
->setHref($file_uri));
return array($actions, $properties);
}
private function buildBeforeResponse($before) {
$request = $this->getRequest();
$drequest = $this->getDiffusionRequest();
// NOTE: We need to get the grandparent so we can capture filename changes
// in the parent.
$parent = $this->loadParentRevisionOf($before);
$old_filename = null;
$was_created = false;
if ($parent) {
$grandparent = $this->loadParentRevisionOf(
$parent->getCommitIdentifier());
if ($grandparent) {
$rename_query = new DiffusionRenameHistoryQuery();
$rename_query->setRequest($drequest);
$rename_query->setOldCommit($grandparent->getCommitIdentifier());
$rename_query->setViewer($request->getUser());
$old_filename = $rename_query->loadOldFilename();
$was_created = $rename_query->getWasCreated();
}
}
$follow = null;
if ($was_created) {
// If the file was created in history, that means older commits won't
// have it. Since we know it existed at 'before', it must have been
// created then; jump there.
$target_commit = $before;
$follow = 'created';
} else if ($parent) {
// If we found a parent, jump to it. This is the normal case.
$target_commit = $parent->getCommitIdentifier();
} else {
// If there's no parent, this was probably created in the initial commit?
// And the "was_created" check will fail because we can't identify the
// grandparent. Keep the user at 'before'.
$target_commit = $before;
$follow = 'first';
}
$path = $drequest->getPath();
$renamed = null;
if ($old_filename !== null &&
$old_filename !== '/'.$path) {
$renamed = $path;
$path = $old_filename;
}
$line = null;
// If there's a follow error, drop the line so the user sees the message.
if (!$follow) {
$line = $this->getBeforeLineNumber($target_commit);
}
$before_uri = $drequest->generateURI(
array(
'action' => 'browse',
'commit' => $target_commit,
'line' => $line,
'path' => $path,
));
$before_uri->setQueryParams($request->getRequestURI()->getQueryParams());
$before_uri = $before_uri->alter('before', null);
$before_uri = $before_uri->alter('renamed', $renamed);
$before_uri = $before_uri->alter('follow', $follow);
return id(new AphrontRedirectResponse())->setURI($before_uri);
}
private function getBeforeLineNumber($target_commit) {
$drequest = $this->getDiffusionRequest();
$line = $drequest->getLine();
if (!$line) {
return null;
}
$raw_diff = $this->callConduitWithDiffusionRequest(
'diffusion.rawdiffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
'againstCommit' => $target_commit));
$old_line = 0;
$new_line = 0;
foreach (explode("\n", $raw_diff) as $text) {
if ($text[0] == '-' || $text[0] == ' ') {
$old_line++;
}
if ($text[0] == '+' || $text[0] == ' ') {
$new_line++;
}
if ($new_line == $line) {
return $old_line;
}
}
// We didn't find the target line.
return $line;
}
private function loadParentRevisionOf($commit) {
$drequest = $this->getDiffusionRequest();
$user = $this->getRequest()->getUser();
$before_req = DiffusionRequest::newFromDictionary(
array(
'user' => $user,
'repository' => $drequest->getRepository(),
'commit' => $commit,
));
$parents = DiffusionQuery::callConduitWithDiffusionRequest(
$user,
$before_req,
'diffusion.commitparentsquery',
array(
'commit' => $commit));
return head($parents);
}
}
diff --git a/src/applications/diffusion/controller/DiffusionCommitController.php b/src/applications/diffusion/controller/DiffusionCommitController.php
index 245085783..6317a4b4e 100644
--- a/src/applications/diffusion/controller/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/DiffusionCommitController.php
@@ -1,949 +1,950 @@
getRequest()->getUser();
$drequest = DiffusionRequest::newFromDictionary($data);
$this->diffusionRequest = $drequest;
}
public function processRequest() {
$drequest = $this->getDiffusionRequest();
$request = $this->getRequest();
$user = $request->getUser();
if ($request->getStr('diff')) {
return $this->buildRawDiffResponse($drequest);
}
$callsign = $drequest->getRepository()->getCallsign();
$content = array();
$repository = $drequest->getRepository();
$commit = $drequest->loadCommit();
if (!$commit) {
$exists = $this->callConduitWithDiffusionRequest(
'diffusion.existsquery',
array('commit' => $drequest->getCommit()));
if (!$exists) {
return new Aphront404Response();
}
return $this->buildStandardPageResponse(
id(new AphrontErrorView())
->setTitle(pht('Error displaying commit.'))
->appendChild(pht('Failed to load the commit because the commit has '.
'not been parsed yet.')),
array('title' => pht('Commit Still Parsing')));
}
$commit_data = $drequest->loadCommitData();
$commit->attachCommitData($commit_data);
$top_anchor = id(new PhabricatorAnchorView())
->setAnchorName('top')
->setNavigationMarker(true);
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
$changesets = null;
if ($is_foreign) {
$subpath = $commit_data->getCommitDetail('svn-subpath');
$error_panel = new AphrontErrorView();
$error_panel->setTitle(pht('Commit Not Tracked'));
$error_panel->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$error_panel->appendChild(
pht("This Diffusion repository is configured to track only one ".
"subdirectory of the entire Subversion repository, and this commit ".
"didn't affect the tracked subdirectory ('%s'), so no ".
"information is available.", $subpath));
$content[] = $error_panel;
$content[] = $top_anchor;
} else {
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$engine->setConfig('viewer', $user);
require_celerity_resource('diffusion-commit-view-css');
require_celerity_resource('phabricator-remarkup-css');
$parents = $this->callConduitWithDiffusionRequest(
'diffusion.commitparentsquery',
array('commit' => $drequest->getCommit()));
$headsup_view = id(new PhabricatorHeaderView())
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')));
$headsup_actions = $this->renderHeadsupActionList($commit, $repository);
$commit_properties = $this->loadCommitProperties(
$commit,
$commit_data,
$parents);
$property_list = id(new PhabricatorPropertyListView())
->setHasKeyboardShortcuts(true)
->setUser($user)
->setObject($commit);
foreach ($commit_properties as $key => $value) {
$property_list->addProperty($key, $value);
}
$message = $commit_data->getCommitMessage();
$revision = $commit->getCommitIdentifier();
$message = $repository->linkBugtraq($message, $revision);
$message = $engine->markupText($message);
$property_list->invokeWillRenderEvent();
$property_list->addTextContent(
phutil_tag(
'div',
array(
'class' => 'diffusion-commit-message phabricator-remarkup',
),
$message));
$content[] = $top_anchor;
$content[] = $headsup_view;
$content[] = $headsup_actions;
$content[] = $property_list;
}
$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);
$hard_limit = 1000;
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
$drequest);
$change_query->setLimit($hard_limit + 1);
$changes = $change_query->loadChanges();
$was_limited = (count($changes) > $hard_limit);
if ($was_limited) {
$changes = array_slice($changes, 0, $hard_limit);
}
$content[] = $this->buildMergesTable($commit);
$owners_paths = array();
if ($this->highlightedAudits) {
$packages = id(new PhabricatorOwnersPackage())->loadAllWhere(
'phid IN (%Ls)',
mpull($this->highlightedAudits, 'getAuditorPHID'));
if ($packages) {
$owners_paths = id(new PhabricatorOwnersPath())->loadAllWhere(
'repositoryPHID = %s AND packageID IN (%Ld)',
$repository->getPHID(),
mpull($packages, 'getID'));
}
}
$change_table = new DiffusionCommitChangeTableView();
$change_table->setDiffusionRequest($drequest);
$change_table->setPathChanges($changes);
$change_table->setOwnersPaths($owners_paths);
$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->setTitle(pht('Bad Commit'));
$error_panel->appendChild($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->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$no_changes->setTitle(pht('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(
pht("This commit hasn't been fully parsed yet (or doesn't affect any ".
"paths)."));
$content[] = $no_changes;
} else if ($was_limited) {
$huge_commit = new AphrontErrorView();
$huge_commit->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$huge_commit->setTitle(pht('Enormous Commit'));
$huge_commit->appendChild(
pht(
'This commit is enormous, and affects more than %d files. '.
'Changes are not shown.',
$hard_limit));
$content[] = $huge_commit;
} else {
$change_panel = new AphrontPanelView();
$change_panel->setHeader("Changes (".number_format($count).")");
$change_panel->setID('toc');
if ($count > self::CHANGES_LIMIT) {
$show_all_button = phutil_tag(
'a',
array(
'class' => 'button green',
'href' => '?show_all=true',
),
pht('Show All Changes'));
$warning_view = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle('Very Large Commit')
->appendChild(phutil_tag(
'p',
array(),
pht("This commit is very large. Load each file individually.")));
$change_panel->appendChild($warning_view);
$change_panel->addButton($show_all_button);
}
$change_panel->appendChild($change_table);
$change_panel->setNoBackground();
$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).
$path_ids = array_flip(mpull($changes, 'getPath'));
foreach ($changesets as $changeset) {
$changeset->makeEphemeral();
$changeset->setID($path_ids[$changeset->getFilename()]);
}
if ($count <= self::CHANGES_LIMIT) {
$visible_changesets = $changesets;
} else {
$visible_changesets = array();
$inlines = id(new PhabricatorAuditInlineComment())->loadAllWhere(
'commitPHID = %s AND (auditCommentID IS NOT NULL OR authorPHID = %s)',
$commit->getPHID(),
$user->getPHID());
$path_ids = mpull($inlines, null, 'getPathID');
foreach ($changesets as $key => $changeset) {
if (array_key_exists($changeset->getID(), $path_ids)) {
$visible_changesets[$key] = $changeset;
}
}
}
$change_list_title = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$change_list = new DifferentialChangesetListView();
$change_list->setTitle($change_list_title);
$change_list->setChangesets($changesets);
$change_list->setVisibleChangesets($visible_changesets);
$change_list->setRenderingReferences($references);
$change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
$change_list->setRepository($repository);
$change_list->setUser($user);
// pick the first branch for "Browse in Diffusion" View Option
$branches = $commit_data->getCommitDetail('seenOnBranches', array());
$first_branch = reset($branches);
$change_list->setBranch($first_branch);
$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/edit/'.phutil_escape_uri($commit->getPHID()).'/');
$change_references = array();
foreach ($changesets as $key => $changeset) {
$change_references[$changeset->getID()] = $references[$key];
}
$change_table->setRenderingReferences($change_references);
$content[] = $change_list->render();
}
$content[] = $this->renderAddCommentPanel($commit, $audit_requests);
$commit_id = 'r'.$callsign.$commit->getCommitIdentifier();
$short_name = DiffusionView::nameCommit(
$repository,
$commit->getCommitIdentifier());
$crumbs = $this->buildCrumbs(array(
'commit' => true,
));
$prefs = $user->loadPreferences();
$pref_filetree = PhabricatorUserPreferences::PREFERENCE_DIFF_FILETREE;
$pref_collapse = PhabricatorUserPreferences::PREFERENCE_NAV_COLLAPSED;
$show_filetree = $prefs->getPreference($pref_filetree);
$collapsed = $prefs->getPreference($pref_collapse);
if ($changesets && $show_filetree) {
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setAnchorName('top')
->setTitle($short_name)
->setBaseURI(new PhutilURI('/'.$commit_id))
->build($changesets)
->setCrumbs($crumbs)
->setCollapsed((bool)$collapsed)
->appendChild($content);
$content = $nav;
} else {
$content = array($crumbs, $content);
}
return $this->buildApplicationPage(
$content,
array(
'title' => $commit_id,
'pageObjects' => array($commit->getPHID()),
'dust' => true,
));
}
private function loadCommitProperties(
PhabricatorRepositoryCommit $commit,
PhabricatorRepositoryCommitData $data,
array $parents) {
assert_instances_of($parents, 'PhabricatorRepositoryCommit');
$user = $this->getRequest()->getUser();
$commit_phid = $commit->getPHID();
$edge_query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($commit_phid))
->withEdgeTypes(array(
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT,
PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV,
));
$edges = $edge_query->execute();
$task_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_TASK]);
$proj_phids = array_keys(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT]);
$revision_phid = key(
$edges[$commit_phid][PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV]);
$phids = $edge_query->getDestinationPHIDs(array($commit_phid));
if ($data->getCommitDetail('authorPHID')) {
$phids[] = $data->getCommitDetail('authorPHID');
}
if ($data->getCommitDetail('reviewerPHID')) {
$phids[] = $data->getCommitDetail('reviewerPHID');
}
if ($data->getCommitDetail('committerPHID')) {
$phids[] = $data->getCommitDetail('committerPHID');
}
if ($parents) {
foreach ($parents as $parent) {
$phids[] = $parent->getPHID();
}
}
$handles = array();
if ($phids) {
$handles = $this->loadViewerHandles($phids);
}
$props = array();
if ($commit->getAuditStatus()) {
$status = PhabricatorAuditCommitStatusConstants::getStatusName(
$commit->getAuditStatus());
$props['Status'] = phutil_tag(
'strong',
array(),
$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'] = $data->getAuthorName();
}
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
if ($reviewer_phid) {
$props['Reviewer'] = $handles[$reviewer_phid]->renderLink();
}
$committer = $data->getCommitDetail('committer');
if ($committer) {
$committer_phid = $data->getCommitDetail('committerPHID');
if ($data->getCommitDetail('committerPHID')) {
$props['Committer'] = $handles[$committer_phid]->renderLink();
} else {
$props['Committer'] = $committer;
}
}
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'] = phutil_implode_html(" \xC2\xB7 ", $parent_links);
}
$request = $this->getDiffusionRequest();
$props['Branches'] = phutil_tag(
'span',
array(
'id' => 'commit-branches',
),
pht('Unknown'));
$props['Tags'] = phutil_tag(
'span',
array(
'id' => 'commit-tags',
),
pht('Unknown'));
$callsign = $request->getRepository()->getCallsign();
$root = '/diffusion/'.$callsign.'/commit/'.$commit->getCommitIdentifier();
Javelin::initBehavior(
'diffusion-commit-branches',
array(
$root.'/branches/' => 'commit-branches',
$root.'/tags/' => 'commit-tags',
));
$refs = $this->buildRefs($request);
if ($refs) {
$props['References'] = $refs;
}
if ($task_phids) {
$task_list = array();
foreach ($task_phids as $phid) {
$task_list[] = $handles[$phid]->renderLink();
}
$task_list = phutil_implode_html(phutil_tag('br'), $task_list);
$props['Tasks'] = $task_list;
}
if ($proj_phids) {
$proj_list = array();
foreach ($proj_phids as $phid) {
$proj_list[] = $handles[$phid]->renderLink();
}
$proj_list = phutil_implode_html(phutil_tag('br'), $proj_list);
$props['Projects'] = $proj_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->setShowCommits(false);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
$view->setAuthorityPHIDs($this->auditAuthorityPHIDs);
$this->highlightedAudits = $view->getHighlightedAudits();
$panel = new AphrontPanelView();
$panel->setHeader(pht('Audits'));
$panel->setCaption(pht('Audits you are responsible for are highlighted.'));
$panel->appendChild($view);
$panel->setNoBackground();
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');
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
foreach ($comments as $comment) {
$engine->addObject(
$comment,
PhabricatorAuditComment::MARKUP_FIELD_BODY);
}
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$view = new DiffusionCommentListView();
$view->setMarkupEngine($engine);
$view->setUser($user);
$view->setComments($comments);
$view->setInlineComments($inlines);
$view->setPathMap($path_map);
$phids = $view->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$view->setHandles($handles);
return $view;
}
private function renderAddCommentPanel(
PhabricatorRepositoryCommit $commit,
array $audit_requests) {
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
$user = $this->getRequest()->getUser();
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$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(pht('Action'))
->setName('action')
->setID('audit-action')
->setOptions($actions))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add Auditors'))
->setName('auditors')
->setControlID('add-auditors')
->setControlStyle('display: none')
->setID('add-auditors-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add CCs'))
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle('display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Comments'))
->setName('content')
->setValue($draft)
->setID('audit-content')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Cook the Books')));
$header = new PhabricatorHeaderView();
$header->setHeader(
$is_serious ? pht('Audit Commit') : pht('Creative Accounting'));
require_celerity_resource('phabricator-transaction-view-css');
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' => pht('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' => pht('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',
),
'inline' => 'inline-comment-preview',
'inlineuri' => '/diffusion/inline/preview/'.$commit->getPHID().'/',
));
$preview_panel = hsprintf(
'
');
// TODO: This is pretty awkward, unify the CSS between Diffusion and
// Differential better.
require_celerity_resource('differential-core-view-css');
return phutil_tag(
'div',
array(
'id' => $pane_id,
),
hsprintf(
'',
id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true)
->render(),
$header,
$form,
$preview_panel));
}
/**
* Return a map of available audit actions for rendering into a .
* 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;
$authority_map = array_fill_keys($this->auditAuthorityPHIDs, true);
foreach ($audit_requests as $request) {
if (empty($authority_map[$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);
$can_close_option = PhabricatorEnv::getEnvConfig(
'audit.can-author-close-audit');
if ($can_close_option && $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;
$merges = array();
try {
$merges = $this->callConduitWithDiffusionRequest(
'diffusion.mergedcommitsquery',
array(
'commit' => $drequest->getCommit(),
'limit' => $limit + 1));
} catch (ConduitException $ex) {
if ($ex->getMessage() != 'ERR-UNSUPPORTED-VCS') {
throw $ex;
}
}
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->setUser($this->getRequest()->getUser());
$history_table->setDiffusionRequest($drequest);
$history_table->setHistory($merges);
$history_table->loadRevisions();
$phids = $history_table->getRequiredHandlePHIDs();
$handles = $this->loadViewerHandles($phids);
$history_table->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader(pht('Merged Changes'));
$panel->setCaption($caption);
$panel->appendChild($history_table);
$panel->setNoBackground();
return $panel;
}
private function renderHeadsupActionList(
PhabricatorRepositoryCommit $commit,
PhabricatorRepository $repository) {
$request = $this->getRequest();
$user = $request->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
- ->setObject($commit);
+ ->setObject($commit)
+ ->setObjectURI($request->getRequestURI());
// TODO -- integrate permissions into whether or not this action is shown
$uri = '/diffusion/'.$repository->getCallSign().'/commit/'.
$commit->getCommitIdentifier().'/edit/';
$action = id(new PhabricatorActionView())
->setName(pht('Edit Commit'))
->setHref($uri)
->setIcon('edit');
$actions->addAction($action);
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$maniphest = 'PhabricatorApplicationManiphest';
if (PhabricatorApplication::isClassInstalled($maniphest)) {
$action = id(new PhabricatorActionView())
->setName(pht('Edit Maniphest Tasks'))
->setIcon('attach')
->setHref('/search/attach/'.$commit->getPHID().'/TASK/edge/')
->setWorkflow(true);
$actions->addAction($action);
}
if ($user->getIsAdmin()) {
$action = id(new PhabricatorActionView())
->setName(pht('MetaMTA Transcripts'))
->setIcon('file')
->setHref('/mail/?phid='.$commit->getPHID());
$actions->addAction($action);
}
$action = id(new PhabricatorActionView())
->setName(pht('Herald Transcripts'))
->setIcon('file')
->setHref('/herald/transcript/?phid='.$commit->getPHID())
->setWorkflow(true);
$actions->addAction($action);
$action = id(new PhabricatorActionView())
->setName(pht('Download Raw Diff'))
->setHref($request->getRequestURI()->alter('diff', true))
->setIcon('download');
$actions->addAction($action);
return $actions;
}
private function buildRefs(DiffusionRequest $request) {
// this is git-only, so save a conduit round trip and just get out of
// here if the repository isn't git
$type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
$repository = $request->getRepository();
if ($repository->getVersionControlSystem() != $type_git) {
return null;
}
$results = $this->callConduitWithDiffusionRequest(
'diffusion.refsquery',
array('commit' => $request->getCommit()));
$ref_links = array();
foreach ($results as $ref_data) {
$ref_links[] = phutil_tag('a',
array('href' => $ref_data['href']),
$ref_data['ref']);
}
return phutil_implode_html(', ', $ref_links);
}
private function buildRawDiffResponse(DiffusionRequest $drequest) {
$raw_diff = $this->callConduitWithDiffusionRequest(
'diffusion.rawdiffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath()));
$file = PhabricatorFile::buildFromFileDataOrHash(
$raw_diff,
array(
'name' => $drequest->getCommit().'.diff',
));
return id(new AphrontRedirectResponse())->setURI($file->getBestURI());
}
}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
index 146e64604..407884938 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
@@ -1,161 +1,163 @@
getRequest();
$user = $request->getUser();
$drequest = $this->diffusionRequest;
$repository = $drequest->getRepository();
$content = array();
$crumbs = $this->buildCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Edit')));
$content[] = $crumbs;
$title = pht('Edit %s', $repository->getName());
$content[] = id(new PhabricatorHeaderView())
->setHeader($title);
$content[] = $this->buildBasicActions($repository);
$content[] = $this->buildBasicProperties($repository);
$content[] = id(new PhabricatorHeaderView())
->setHeader(pht('Text Encoding'));
$content[] = $this->buildEncodingActions($repository);
$content[] = $this->buildEncodingProperties($repository);
$content[] = id(new PhabricatorHeaderView())
->setHeader(pht('Edit History'));
$xactions = id(new PhabricatorRepositoryTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($repository->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
$content[] = $xaction_view;
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
private function buildBasicActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$repository,
PhabricatorPolicyCapability::CAN_EDIT);
$edit = id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Basic Information'))
->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/'))
->setDisabled(!$can_edit);
$view->addAction($edit);
return $view;
}
private function buildBasicProperties(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$view->addProperty(pht('Name'), $repository->getName());
$view->addProperty(pht('ID'), $repository->getID());
$view->addProperty(pht('PHID'), $repository->getPHID());
$type = PhabricatorRepositoryType::getNameForRepositoryType(
$repository->getVersionControlSystem());
$view->addProperty(pht('Type'), $type);
$view->addProperty(pht('Callsign'), $repository->getCallsign());
$description = $repository->getDetail('description');
$view->addSectionHeader(pht('Description'));
if (!strlen($description)) {
$description = phutil_tag('em', array(), pht('No description provided.'));
} else {
$description = PhabricatorMarkupEngine::renderOneObject(
$repository,
'description',
$user);
}
$view->addTextContent($description);
return $view;
}
private function buildEncodingActions(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorActionListView())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$repository,
PhabricatorPolicyCapability::CAN_EDIT);
$edit = id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Text Encoding'))
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/encoding/'))
->setDisabled(!$can_edit);
$view->addAction($edit);
return $view;
}
private function buildEncodingProperties(PhabricatorRepository $repository) {
$user = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($user);
$encoding = $repository->getDetail('encoding');
if (!$encoding) {
$encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)'));
}
$view->addProperty(pht('Encoding'), $encoding);
return $view;
}
}
diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php
index a0508de6c..f3c1be76d 100644
--- a/src/applications/drydock/controller/DrydockLeaseViewController.php
+++ b/src/applications/drydock/controller/DrydockLeaseViewController.php
@@ -1,130 +1,131 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$lease = id(new DrydockLease())->load($this->id);
if (!$lease) {
return new Aphront404Response();
}
$lease_uri = $this->getApplicationURI('lease/'.$lease->getID().'/');
$title = pht('Lease %d', $lease->getID());
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($lease);
$properties = $this->buildPropertyListView($lease);
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($lease_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$logs = id(new DrydockLogQuery())
->withLeaseIDs(array($lease->getID()))
->executeWithOffsetPager($pager);
$log_table = $this->buildLogTableView($logs);
$log_table->appendChild($pager);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($lease_uri));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$log_table,
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockLease $lease) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($lease);
$id = $lease->getID();
$can_release = ($lease->getStatus() == DrydockLeaseStatus::STATUS_ACTIVE);
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Release Lease'))
->setIcon('delete')
->setHref($this->getApplicationURI("/lease/{$id}/release/"))
->setWorkflow(true)
->setDisabled(!$can_release));
return $view;
}
private function buildPropertyListView(DrydockLease $lease) {
$view = new PhabricatorPropertyListView();
switch ($lease->getStatus()) {
case DrydockLeaseStatus::STATUS_ACTIVE:
$status = pht('Active');
break;
case DrydockLeaseStatus::STATUS_RELEASED:
$status = pht('Released');
break;
case DrydockLeaseStatus::STATUS_EXPIRED:
$status = pht('Expired');
break;
case DrydockLeaseStatus::STATUS_PENDING:
$status = pht('Pending');
break;
case DrydockLeaseStatus::STATUS_BROKEN:
$status = pht('Broken');
break;
default:
$status = pht('Unknown');
break;
}
$view->addProperty(
pht('Status'),
$status);
$view->addProperty(
pht('Resource Type'),
$lease->getResourceType());
$view->addProperty(
pht('Resource'),
$lease->getResourceID());
$attributes = $lease->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
}
return $view;
}
}
diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php
index 45ed050c5..422ee38b5 100644
--- a/src/applications/drydock/controller/DrydockResourceViewController.php
+++ b/src/applications/drydock/controller/DrydockResourceViewController.php
@@ -1,121 +1,122 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$resource = id(new DrydockResource())->load($this->id);
if (!$resource) {
return new Aphront404Response();
}
$title = 'Resource '.$resource->getID().' '.$resource->getName();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionListView($resource);
$properties = $this->buildPropertyListView($resource);
$resource_uri = 'resource/'.$resource->getID().'/';
$resource_uri = $this->getApplicationURI($resource_uri);
$leases = id(new DrydockLeaseQuery())
->withResourceIDs(array($resource->getID()))
->needResources(true)
->execute();
$lease_header = id(new PhabricatorHeaderView())
->setHeader(pht('Leases'));
$lease_list = $this->buildLeaseListView($leases);
$lease_list->setNoDataString(pht('This resource has no leases.'));
$pager = new AphrontPagerView();
$pager->setURI(new PhutilURI($resource_uri), 'offset');
$pager->setOffset($request->getInt('offset'));
$logs = id(new DrydockLogQuery())
->withResourceIDs(array($resource->getID()))
->executeWithOffsetPager($pager);
$log_table = $this->buildLogTableView($logs);
$log_table->appendChild($pager);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Resource %d', $resource->getID())));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$lease_header,
$lease_list,
$log_table,
),
array(
'device' => true,
'title' => $title,
));
}
private function buildActionListView(DrydockResource $resource) {
$view = id(new PhabricatorActionListView())
->setUser($this->getRequest()->getUser())
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($resource);
$can_close = ($resource->getStatus() == DrydockResourceStatus::STATUS_OPEN);
$uri = '/resource/'.$resource->getID().'/close/';
$uri = $this->getApplicationURI($uri);
$view->addAction(
id(new PhabricatorActionView())
->setHref($uri)
->setName(pht('Close Resource'))
->setIcon('delete')
->setWorkflow(true)
->setDisabled(!$can_close));
return $view;
}
private function buildPropertyListView(DrydockResource $resource) {
$view = new PhabricatorPropertyListView();
$status = $resource->getStatus();
$status = DrydockResourceStatus::getNameForStatus($status);
$view->addProperty(
pht('Status'),
$status);
$view->addProperty(
pht('Resource Type'),
$resource->getType());
$attributes = $resource->getAttributes();
if ($attributes) {
$view->addSectionHeader(pht('Attributes'));
foreach ($attributes as $key => $value) {
$view->addProperty($key, $value);
}
}
return $view;
}
}
diff --git a/src/applications/files/controller/PhabricatorFileInfoController.php b/src/applications/files/controller/PhabricatorFileInfoController.php
index 20126841b..1cb6050ff 100644
--- a/src/applications/files/controller/PhabricatorFileInfoController.php
+++ b/src/applications/files/controller/PhabricatorFileInfoController.php
@@ -1,168 +1,169 @@
phid = $data['phid'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$file = id(new PhabricatorFileQuery())
->setViewer($user)
->withPHIDs(array($this->phid))
->executeOne();
if (!$file) {
return new Aphront404Response();
}
$this->loadHandles(array($file->getAuthorPHID()));
$phid = $file->getPHID();
$header = id(new PhabricatorHeaderView())
->setHeader($file->getName());
$ttl = $file->getTTL();
if ($ttl !== null) {
$ttl_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_OBJECT)
->setName(pht("Temporary"));
$header->addTag($ttl_tag);
}
$actions = $this->buildActionView($file);
$properties = $this->buildPropertyView($file);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('F'.$file->getID())
->setHref($this->getApplicationURI("/info/{$phid}/")));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
),
array(
'title' => $file->getName(),
'device' => true,
));
}
private function buildActionView(PhabricatorFile $file) {
$request = $this->getRequest();
$user = $request->getUser();
$id = $file->getID();
$view = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($file);
if ($file->isViewableInBrowser()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('View File'))
->setIcon('preview')
->setHref($file->getViewURI()));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setRenderAsForm(true)
->setDownload(true)
->setName(pht('Download File'))
->setIcon('download')
->setHref($file->getViewURI()));
}
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete File'))
->setIcon('delete')
->setHref($this->getApplicationURI("/delete/{$id}/"))
->setWorkflow(true));
return $view;
}
private function buildPropertyView(PhabricatorFile $file) {
$request = $this->getRequest();
$user = $request->getUser();
$view = id(new PhabricatorPropertyListView());
if ($file->getAuthorPHID()) {
$view->addProperty(
pht('Author'),
$this->getHandle($file->getAuthorPHID())->renderLink());
}
$view->addProperty(
pht('Created'),
phabricator_datetime($file->getDateCreated(), $user));
$view->addProperty(
pht('Size'),
phabricator_format_bytes($file->getByteSize()));
$view->addSectionHeader(pht('Technical Details'));
$view->addProperty(
pht('Mime Type'),
$file->getMimeType());
$view->addProperty(
pht('Engine'),
$file->getStorageEngine());
$view->addProperty(
pht('Format'),
$file->getStorageFormat());
$view->addProperty(
pht('Handle'),
$file->getStorageHandle());
$metadata = $file->getMetadata();
if (!empty($metadata)) {
$view->addSectionHeader(pht('Metadata'));
foreach ($metadata as $key => $value) {
$view->addProperty(
PhabricatorFile::getMetadataName($key),
$value);
}
}
if ($file->isViewableImage()) {
$image = phutil_tag(
'img',
array(
'src' => $file->getViewURI(),
'class' => 'phabricator-property-list-image',
));
$linked_image = phutil_tag(
'a',
array(
'href' => $file->getViewURI(),
),
$image);
$view->addImageContent($linked_image);
}
return $view;
}
}
diff --git a/src/applications/legalpad/controller/LegalpadDocumentViewController.php b/src/applications/legalpad/controller/LegalpadDocumentViewController.php
index 5e49dad15..8aa70d236 100644
--- a/src/applications/legalpad/controller/LegalpadDocumentViewController.php
+++ b/src/applications/legalpad/controller/LegalpadDocumentViewController.php
@@ -1,223 +1,224 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$document = id(new LegalpadDocumentQuery())
->setViewer($user)
->withIDs(array($this->id))
->needDocumentBodies(true)
->needContributors(true)
->executeOne();
if (!$document) {
return new Aphront404Response();
}
$xactions = id(new LegalpadTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($document->getPHID()))
->execute();
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$document->getPHID());
$document_body = $document->getDocumentBody();
$phids = array();
$phids[] = $document_body->getCreatorPHID();
foreach ($subscribers as $subscriber) {
$phids[] = $subscriber;
}
foreach ($document->getContributors() as $contributor) {
$phids[] = $contributor;
}
$this->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject(
$document_body,
LegalpadDocumentBody::MARKUP_FIELD_TEXT);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$title = $document_body->getTitle();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionView($document);
$properties = $this->buildPropertyView($document, $engine);
$comment_form_id = celerity_generate_unique_node_id();
$xaction_view = id(new LegalpadTransactionView())
->setUser($this->getRequest()->getUser())
->setTransactions($xactions)
->setMarkupEngine($engine);
$add_comment = $this->buildAddCommentView($document, $comment_form_id);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('L'.$document->getID())
->setHref($this->getApplicationURI('view/'.$document->getID())));
$content = array(
$crumbs,
$header,
$actions,
$properties,
$this->buildDocument($engine, $document_body),
$xaction_view,
$add_comment,
);
return $this->buildApplicationPage(
$content,
array(
'title' => $title,
'device' => true,
'dust' => true,
'pageObjects' => array($document->getPHID()),
));
}
private function buildDocument(
PhabricatorMarkupEngine
$engine, LegalpadDocumentBody $body) {
require_celerity_resource('legalpad-documentbody-css');
return phutil_tag(
'div',
array(
'class' => 'legalpad-documentbody'
),
$engine->getOutput($body, LegalpadDocumentBody::MARKUP_FIELD_TEXT));
}
private function buildActionView(LegalpadDocument $document) {
$user = $this->getRequest()->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($document);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Document'))
->setHref($this->getApplicationURI('/edit/'.$document->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
LegalpadDocument $document,
PhabricatorMarkupEngine $engine) {
$user = $this->getRequest()->getUser();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($document);
$properties->addProperty(
pht('Last Updated'),
phabricator_datetime($document->getDateModified(), $user));
$properties->addProperty(
pht('Updated By'),
$this->getHandle(
$document->getDocumentBody()->getCreatorPHID())->renderLink());
$properties->addProperty(
pht('Versions'),
$document->getVersions());
$contributor_view = array();
foreach ($document->getContributors() as $contributor) {
$contributor_view[] = $this->getHandle($contributor)->renderLink();
}
$contributor_view = phutil_implode_html(', ', $contributor_view);
$properties->addProperty(
pht('Contributors'),
$contributor_view);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$document);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->invokeWillRenderEvent();
return $properties;
}
private function buildAddCommentView(
LegalpadDocument $document,
$comment_form_id) {
$user = $this->getRequest()->getUser();
$draft = PhabricatorDraft::newFromUserAndKey($user, $document->getPHID());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$title = $is_serious
? pht('Add Comment')
: pht('Debate Legislation');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$button_name = $is_serious
? pht('Add Comment')
: pht('Commence Filibuster');
$form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setFormID($comment_form_id)
->setDraft($draft)
->setSubmitButtonName($button_name)
->setAction($this->getApplicationURI('/comment/'.$document->getID().'/'))
->setRequestURI($this->getRequest()->getRequestURI());
return array(
$header,
$form,
);
}
}
diff --git a/src/applications/macro/controller/PhabricatorMacroViewController.php b/src/applications/macro/controller/PhabricatorMacroViewController.php
index a65c87bea..b521a6758 100644
--- a/src/applications/macro/controller/PhabricatorMacroViewController.php
+++ b/src/applications/macro/controller/PhabricatorMacroViewController.php
@@ -1,160 +1,162 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$macro = id(new PhabricatorMacroQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$macro) {
return new Aphront404Response();
}
$file = $macro->getFile();
$title_short = pht('Macro "%s"', $macro->getName());
$title_long = pht('Image Macro "%s"', $macro->getName());
$actions = $this->buildActionView($macro);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setHref($this->getApplicationURI('/view/'.$macro->getID().'/'))
->setName($title_short));
$properties = $this->buildPropertyView($macro, $file);
$xactions = id(new PhabricatorMacroTransactionQuery())
->setViewer($request->getUser())
->withObjectPHIDs(array($macro->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$timeline = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
$header = id(new PhabricatorHeaderView())
->setHeader($title_long);
if ($macro->getIsDisabled()) {
$header->addTag(
id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName(pht('Macro Disabled'))
->setBackgroundColor(PhabricatorTagView::COLOR_BLACK));
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$add_comment_header = id(new PhabricatorHeaderView())
->setHeader(
$is_serious
? pht('Add Comment')
: pht('Grovel in Awe'));
$submit_button_name = $is_serious
? pht('Add Comment')
: pht('Lavish Praise');
$draft = PhabricatorDraft::newFromUserAndKey($user, $macro->getPHID());
$add_comment_form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setDraft($draft)
->setAction($this->getApplicationURI('/comment/'.$macro->getID().'/'))
->setSubmitButtonName($submit_button_name);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$timeline,
$add_comment_header,
$add_comment_form,
),
array(
'title' => $title_short,
'device' => true,
));
}
private function buildActionView(PhabricatorFileImageMacro $macro) {
- $view = new PhabricatorActionListView();
- $view->setUser($this->getRequest()->getUser());
- $view->setObject($macro);
- $view->addAction(
- id(new PhabricatorActionView())
+ $request = $this->getRequest();
+ $view = id(new PhabricatorActionListView())
+ ->setUser($request->getUser())
+ ->setObject($macro)
+ ->setObjectURI($request->getRequestURI())
+ ->addAction(
+ id(new PhabricatorActionView())
->setName(pht('Edit Macro'))
->setHref($this->getApplicationURI('/edit/'.$macro->getID().'/'))
->setIcon('edit'));
if ($macro->getIsDisabled()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Restore Macro'))
->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
->setWorkflow(true)
->setIcon('undo'));
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Disable Macro'))
->setHref($this->getApplicationURI('/disable/'.$macro->getID().'/'))
->setWorkflow(true)
->setIcon('delete'));
}
return $view;
}
private function buildPropertyView(
PhabricatorFileImageMacro $macro,
PhabricatorFile $file = null) {
$view = id(new PhabricatorPropertyListView())
->setUser($this->getRequest()->getUser())
->setObject($macro);
$view->invokeWillRenderEvent();
if ($file) {
$view->addImageContent(
phutil_tag(
'img',
array(
'src' => $file->getViewURI(),
'class' => 'phabricator-image-macro-hero',
)));
}
return $view;
}
}
diff --git a/src/applications/maniphest/controller/ManiphestTaskDetailController.php b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
index 0c0945982..94d57d2eb 100644
--- a/src/applications/maniphest/controller/ManiphestTaskDetailController.php
+++ b/src/applications/maniphest/controller/ManiphestTaskDetailController.php
@@ -1,587 +1,587 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$e_title = null;
$priority_map = ManiphestTaskPriority::getTaskPriorityMap();
$task = id(new ManiphestTask())->load($this->id);
if (!$task) {
return new Aphront404Response();
}
$workflow = $request->getStr('workflow');
$parent_task = null;
if ($workflow && is_numeric($workflow)) {
$parent_task = id(new ManiphestTask())->load($workflow);
}
$transactions = id(new ManiphestTransaction())->loadAllWhere(
'taskID = %d ORDER BY id ASC',
$task->getID());
$extensions = ManiphestTaskExtensions::newExtensions();
$aux_fields = $extensions->loadFields($task, $user);
$e_commit = PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT;
$e_dep_on = PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK;
$e_dep_by = PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK;
$e_rev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV;
$phid = $task->getPHID();
$query = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($phid))
->withEdgeTypes(
array(
$e_commit,
$e_dep_on,
$e_dep_by,
$e_rev,
));
$edges = idx($query->execute(), $phid);
$phids = array_fill_keys($query->getDestinationPHIDs(), true);
foreach ($transactions as $transaction) {
foreach ($transaction->extractPHIDs() as $phid) {
$phids[$phid] = true;
}
}
foreach ($task->getCCPHIDs() as $phid) {
$phids[$phid] = true;
}
foreach ($task->getProjectPHIDs() as $phid) {
$phids[$phid] = true;
}
if ($task->getOwnerPHID()) {
$phids[$task->getOwnerPHID()] = true;
}
$phids[$task->getAuthorPHID()] = true;
$attached = $task->getAttached();
foreach ($attached as $type => $list) {
foreach ($list as $phid => $info) {
$phids[$phid] = true;
}
}
if ($parent_task) {
$phids[$parent_task->getPHID()] = true;
}
$phids = array_keys($phids);
$phids = array_merge(
$phids,
array_mergev(mpull($aux_fields, 'getRequiredHandlePHIDs')));
$this->loadHandles($phids);
$handles = $this->getLoadedHandles();
foreach ($aux_fields as $aux_field) {
$aux_field->setHandles($handles);
}
$context_bar = null;
if ($parent_task) {
$context_bar = new AphrontContextBarView();
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/?parent='.$parent_task->getID(),
'class' => 'green button',
),
pht('Create Another Subtask')));
$context_bar->appendChild(hsprintf(
'Created a subtask of %s',
$this->getHandle($parent_task->getPHID())->renderLink()));
} else if ($workflow == 'create') {
$context_bar = new AphrontContextBarView();
$context_bar->addButton(phutil_tag('label', array(), 'Create Another'));
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/?template='.$task->getID(),
'class' => 'green button',
),
pht('Similar Task')));
$context_bar->addButton(phutil_tag(
'a',
array(
'href' => '/maniphest/task/create/',
'class' => 'green button',
),
pht('Empty Task')));
$context_bar->appendChild(pht('New task created.'));
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($user);
$engine->addObject($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION);
foreach ($transactions as $xaction) {
if ($xaction->hasComments()) {
$engine->addObject($xaction, ManiphestTransaction::MARKUP_FIELD_BODY);
}
}
foreach ($aux_fields as $aux_field) {
foreach ($aux_field->getMarkupFields() as $markup_field) {
$engine->addObject($aux_field, $markup_field);
$aux_field->setMarkupEngine($engine);
}
}
$engine->process();
$transaction_types = ManiphestTransactionType::getTransactionTypeMap();
$resolution_types = ManiphestTaskStatus::getTaskStatusMap();
if ($task->getStatus() == ManiphestTaskStatus::STATUS_OPEN) {
$resolution_types = array_select_keys(
$resolution_types,
array(
ManiphestTaskStatus::STATUS_CLOSED_RESOLVED,
ManiphestTaskStatus::STATUS_CLOSED_WONTFIX,
ManiphestTaskStatus::STATUS_CLOSED_INVALID,
ManiphestTaskStatus::STATUS_CLOSED_SPITE,
));
} else {
$resolution_types = array(
ManiphestTaskStatus::STATUS_OPEN => 'Reopened',
);
$transaction_types[ManiphestTransactionType::TYPE_STATUS] =
'Reopen Task';
unset($transaction_types[ManiphestTransactionType::TYPE_PRIORITY]);
unset($transaction_types[ManiphestTransactionType::TYPE_OWNER]);
}
$default_claim = array(
$user->getPHID() => $user->getUsername().' ('.$user->getRealName().')',
);
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
$task->getPHID());
if ($draft) {
$draft_text = $draft->getDraft();
} else {
$draft_text = null;
}
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
if ($is_serious) {
// Prevent tasks from being closed "out of spite" in serious business
// installs.
unset($resolution_types[ManiphestTaskStatus::STATUS_CLOSED_SPITE]);
}
$comment_form = new AphrontFormView();
$comment_form
->setUser($user)
->setAction('/maniphest/transaction/save/')
->setEncType('multipart/form-data')
->addHiddenInput('taskID', $task->getID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setOptions($transaction_types)
->setID('transaction-action'))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Resolution'))
->setName('resolution')
->setControlID('resolution')
->setControlStyle('display: none')
->setOptions($resolution_types))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Assign To'))
->setName('assign_to')
->setControlID('assign_to')
->setControlStyle('display: none')
->setID('assign-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('CCs'))
->setName('ccs')
->setControlID('ccs')
->setControlStyle('display: none')
->setID('cc-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Priority'))
->setName('priority')
->setOptions($priority_map)
->setControlID('priority')
->setControlStyle('display: none')
->setValue($task->getPriority()))
->appendChild(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Projects'))
->setName('projects')
->setControlID('projects')
->setControlStyle('display: none')
->setID('projects-tokenizer')
->setDisableBehavior(true))
->appendChild(
id(new AphrontFormFileControl())
->setLabel(pht('File'))
->setName('file')
->setControlID('file')
->setControlStyle('display: none'))
->appendChild(
id(new PhabricatorRemarkupControl())
->setLabel(pht('Comments'))
->setName('comments')
->setValue($draft_text)
->setID('transaction-comments')
->setUser($user))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue($is_serious ? pht('Submit') : pht('Avast!')));
$control_map = array(
ManiphestTransactionType::TYPE_STATUS => 'resolution',
ManiphestTransactionType::TYPE_OWNER => 'assign_to',
ManiphestTransactionType::TYPE_CCS => 'ccs',
ManiphestTransactionType::TYPE_PRIORITY => 'priority',
ManiphestTransactionType::TYPE_PROJECTS => 'projects',
ManiphestTransactionType::TYPE_ATTACH => 'file',
);
$tokenizer_map = array(
ManiphestTransactionType::TYPE_PROJECTS => array(
'id' => 'projects-tokenizer',
'src' => '/typeahead/common/projects/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a project name...'),
),
ManiphestTransactionType::TYPE_OWNER => array(
'id' => 'assign-tokenizer',
'src' => '/typeahead/common/users/',
'value' => $default_claim,
'limit' => 1,
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user name...'),
),
ManiphestTransactionType::TYPE_CCS => array(
'id' => 'cc-tokenizer',
'src' => '/typeahead/common/mailable/',
'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'),
'placeholder' => pht('Type a user or mailing list...'),
),
);
Javelin::initBehavior('maniphest-transaction-controls', array(
'select' => 'transaction-action',
'controlMap' => $control_map,
'tokenizers' => $tokenizer_map,
));
Javelin::initBehavior('maniphest-transaction-preview', array(
'uri' => '/maniphest/transaction/preview/'.$task->getID().'/',
'preview' => 'transaction-preview',
'comments' => 'transaction-comments',
'action' => 'transaction-action',
'map' => $control_map,
'tokenizers' => $tokenizer_map,
));
$comment_header = id(new PhabricatorHeaderView())
->setHeader($is_serious ? pht('Add Comment') : pht('Weigh In'));
$preview_panel = hsprintf(
'',
pht('Loading preview...'));
$transaction_view = new ManiphestTransactionListView();
$transaction_view->setTransactions($transactions);
$transaction_view->setHandles($this->getLoadedHandles());
$transaction_view->setUser($user);
$transaction_view->setAuxiliaryFields($aux_fields);
$transaction_view->setMarkupEngine($engine);
$object_name = 'T'.$task->getID();
$actions = $this->buildActionView($task);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($object_name)
->setHref('/'.$object_name))
->setActionList($actions)
->addAction(
id(new PHUIListItemView())
->setHref($this->getApplicationURI('/task/create/'))
->setName(pht('Create Task'))
->setIcon('create'));
$header = $this->buildHeaderView($task);
$properties = $this->buildPropertyView($task, $aux_fields, $edges, $engine);
return $this->buildApplicationPage(
array(
$crumbs,
$context_bar,
$header,
$actions,
$properties,
$transaction_view,
$comment_header,
$comment_form,
$preview_panel,
),
array(
'title' => 'T'.$task->getID().' '.$task->getTitle(),
'pageObjects' => array($task->getPHID()),
'device' => true,
));
}
private function buildHeaderView(ManiphestTask $task) {
$view = id(new PhabricatorHeaderView())
->setHeader($task->getTitle());
$view->addTag(ManiphestView::renderTagForTask($task));
return $view;
}
private function buildActionView(ManiphestTask $task) {
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_cc = in_array($viewer_phid, $task->getCCPHIDs());
$id = $task->getID();
$phid = $task->getPHID();
- $view = new PhabricatorActionListView();
- $view->setUser($viewer);
- $view->setObject($task);
-
- $view->addAction(
+ $view = id(new PhabricatorActionListView())
+ ->setUser($viewer)
+ ->setObject($task)
+ ->setObjectURI($this->getRequest()->getRequestURI())
+ ->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Task'))
->setIcon('edit')
->setHref($this->getApplicationURI("/task/edit/{$id}/")));
if ($task->getOwnerPHID() === $viewer_phid) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Automatically Subscribed'))
->setDisabled(true)
->setIcon('enable'));
} else {
$action = $viewer_is_cc ? 'rem' : 'add';
$name = $viewer_is_cc ? pht('Unsubscribe') : pht('Subscribe');
$icon = $viewer_is_cc ? 'disable' : 'check';
$view->addAction(
id(new PhabricatorActionView())
->setName($name)
->setHref("/maniphest/subscribe/{$action}/{$id}/")
->setRenderAsForm(true)
->setUser($viewer)
->setIcon($icon));
}
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Merge Duplicates'))
->setHref("/search/attach/{$phid}/TASK/merge/")
->setWorkflow(true)
->setIcon('merge'));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Subtask'))
->setHref($this->getApplicationURI("/task/create/?parent={$id}"))
->setIcon('fork'));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Dependencies'))
->setHref("/search/attach/{$phid}/TASK/dependencies/")
->setWorkflow(true)
->setIcon('link'));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Differential Revisions'))
->setHref("/search/attach/{$phid}/DREV/")
->setWorkflow(true)
->setIcon('attach'));
return $view;
}
private function buildPropertyView(
ManiphestTask $task,
array $aux_fields,
array $edges,
PhabricatorMarkupEngine $engine) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($task);
$view->addProperty(
pht('Assigned To'),
$task->getOwnerPHID()
? $this->getHandle($task->getOwnerPHID())->renderLink()
: phutil_tag('em', array(), pht('None')));
$view->addProperty(
pht('Priority'),
ManiphestTaskPriority::getTaskPriorityName($task->getPriority()));
$view->addProperty(
pht('Subscribers'),
$task->getCCPHIDs()
? $this->renderHandlesForPHIDs($task->getCCPHIDs(), ',')
: phutil_tag('em', array(), pht('None')));
$view->addProperty(
pht('Author'),
$this->getHandle($task->getAuthorPHID())->renderLink());
$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));
}
$view->addProperty(
pht('Projects'),
$task->getProjectPHIDs()
? $this->renderHandlesForPHIDs($task->getProjectPHIDs(), ',')
: phutil_tag('em', array(), pht('None')));
foreach ($aux_fields as $aux_field) {
$value = $aux_field->renderForDetailView();
if (strlen($value)) {
$view->addProperty($aux_field->getLabel(), $value);
}
}
$edge_types = array(
PhabricatorEdgeConfig::TYPE_TASK_DEPENDED_ON_BY_TASK
=> pht('Dependent Tasks'),
PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK
=> pht('Depends On'),
PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV
=> pht('Differential Revisions'),
);
$revisions_commits = array();
$handles = $this->getLoadedHandles();
$commit_phids = array_keys(
$edges[PhabricatorEdgeConfig::TYPE_TASK_HAS_COMMIT]);
if ($commit_phids) {
$commit_drev = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_DREV;
$drev_edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs($commit_phids)
->withEdgeTypes(array($commit_drev))
->execute();
foreach ($commit_phids as $phid) {
$revisions_commits[$phid] = $handles[$phid]->renderLink();
$revision_phid = key($drev_edges[$phid][$commit_drev]);
$revision_handle = idx($handles, $revision_phid);
if ($revision_handle) {
$task_drev = PhabricatorEdgeConfig::TYPE_TASK_HAS_RELATED_DREV;
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]) {
$view->addProperty(
$edge_name,
$this->renderHandlesForPHIDs(array_keys($edges[$edge_type])));
}
}
if ($revisions_commits) {
$view->addProperty(
pht('Commits'),
phutil_implode_html(phutil_tag('br'), $revisions_commits));
}
$attached = $task->getAttached();
$file_infos = idx($attached, PhabricatorPHIDConstants::PHID_TYPE_FILE);
if ($file_infos) {
$file_phids = array_keys($file_infos);
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
$file_view = new PhabricatorFileLinkListView();
$file_view->setFiles($files);
$view->addProperty(
pht('Files'),
$file_view->render());
}
$view->invokeWillRenderEvent();
if (strlen($task->getDescription())) {
$view->addSectionHeader(pht('Description'));
$view->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($task, ManiphestTask::MARKUP_FIELD_DESCRIPTION)));
}
return $view;
}
}
diff --git a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
index 2e4d1cb3a..1ce99f771 100644
--- a/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
+++ b/src/applications/meta/controller/PhabricatorApplicationDetailViewController.php
@@ -1,122 +1,123 @@
application = $data['application'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$selected = PhabricatorApplication::getByClass($this->application);
if (!$selected) {
return new Aphront404Response();
}
$title = $selected->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Applications'))
->setHref($this->getApplicationURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$status_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE);
if ($selected->isInstalled()) {
$status_tag->setName(pht('Installed'));
$status_tag->setBackgroundColor(PhabricatorTagView::COLOR_GREEN);
} else {
$status_tag->setName(pht('Uninstalled'));
$status_tag->setBackgroundColor(PhabricatorTagView::COLOR_RED);
}
if ($selected->isBeta()) {
$beta_tag = id(new PhabricatorTagView())
->setType(PhabricatorTagView::TYPE_STATE)
->setName(pht('Beta'))
->setBackgroundColor(PhabricatorTagView::COLOR_GREY);
$header->addTag($beta_tag);
}
$header->addTag($status_tag);
$properties = $this->buildPropertyView($selected);
$actions = $this->buildActionView($user, $selected);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
),
array(
'title' => $title,
'device' => true,
));
}
private function buildPropertyView(PhabricatorApplication $selected) {
$properties = id(new PhabricatorPropertyListView())
->addProperty(
pht('Description'), $selected->getShortDescription());
return $properties;
}
private function buildActionView(
PhabricatorUser $user, PhabricatorApplication $selected) {
$view = id(new PhabricatorActionListView())
- ->setUser($user);
+ ->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI());
if ($selected->canUninstall()) {
if ($selected->isInstalled()) {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Uninstall'))
->setIcon('delete')
->setWorkflow(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/uninstall/')));
} else {
$action = id(new PhabricatorActionView())
->setName(pht('Install'))
->setIcon('new')
->setWorkflow(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/install/'));
$beta_enabled = PhabricatorEnv::getEnvConfig(
'phabricator.show-beta-applications');
if ($selected->isBeta() && !$beta_enabled) {
$action->setDisabled(true);
}
$view->addAction($action);
}
} else {
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Uninstall'))
->setIcon('delete')
->setWorkflow(true)
->setDisabled(true)
->setHref(
$this->getApplicationURI(get_class($selected).'/uninstall/')));
}
return $view;
}
}
diff --git a/src/applications/paste/controller/PhabricatorPasteViewController.php b/src/applications/paste/controller/PhabricatorPasteViewController.php
index 4d218e925..72ddf6ad1 100644
--- a/src/applications/paste/controller/PhabricatorPasteViewController.php
+++ b/src/applications/paste/controller/PhabricatorPasteViewController.php
@@ -1,158 +1,159 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$paste = id(new PhabricatorPasteQuery())
->setViewer($user)
->withIDs(array($this->id))
->needContent(true)
->executeOne();
if (!$paste) {
return new Aphront404Response();
}
$file = id(new PhabricatorFile())->loadOneWhere(
'phid = %s',
$paste->getFilePHID());
if (!$file) {
return new Aphront400Response();
}
$forks = id(new PhabricatorPasteQuery())
->setViewer($user)
->withParentPHIDs(array($paste->getPHID()))
->execute();
$fork_phids = mpull($forks, 'getPHID');
$this->loadHandles(
array_merge(
array(
$paste->getAuthorPHID(),
$paste->getParentPHID(),
),
$fork_phids));
$header = $this->buildHeaderView($paste);
$actions = $this->buildActionView($user, $paste, $file);
$properties = $this->buildPropertyView($paste, $fork_phids);
$source_code = $this->buildSourceCodeView($paste);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView())
->setActionList($actions)
->addCrumb(
id(new PhabricatorCrumbView())
->setName('P'.$paste->getID())
->setHref('/P'.$paste->getID()));
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$source_code,
),
array(
'title' => $paste->getFullName(),
'device' => true,
'pageObjects' => array($paste->getPHID()),
));
}
private function buildHeaderView(PhabricatorPaste $paste) {
return id(new PhabricatorHeaderView())
->setHeader($paste->getTitle());
}
private function buildActionView(
PhabricatorUser $user,
PhabricatorPaste $paste,
PhabricatorFile $file) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$paste,
PhabricatorPolicyCapability::CAN_EDIT);
$can_fork = $user->isLoggedIn();
$fork_uri = $this->getApplicationURI('/create/?parent='.$paste->getID());
return id(new PhabricatorActionListView())
->setUser($user)
->setObject($paste)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Fork This Paste'))
->setIcon('fork')
->setDisabled(!$can_fork)
->setWorkflow(!$can_fork)
->setHref($fork_uri))
->addAction(
id(new PhabricatorActionView())
->setName(pht('View Raw File'))
->setIcon('file')
->setHref($file->getBestURI()))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Paste'))
->setIcon('edit')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref($this->getApplicationURI('/edit/'.$paste->getID().'/')));
}
private function buildPropertyView(
PhabricatorPaste $paste,
array $child_phids) {
$user = $this->getRequest()->getUser();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($paste);
$properties->addProperty(
pht('Author'),
$this->getHandle($paste->getAuthorPHID())->renderLink());
$properties->addProperty(
pht('Created'),
phabricator_datetime($paste->getDateCreated(), $user));
if ($paste->getParentPHID()) {
$properties->addProperty(
pht('Forked From'),
$this->getHandle($paste->getParentPHID())->renderLink());
}
if ($child_phids) {
$properties->addProperty(
pht('Forks'),
$this->renderHandlesForPHIDs($child_phids));
}
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$paste);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
return $properties;
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 9819a6016..436d295d3 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,134 +1,135 @@
username = idx($data, 'username');
}
public function processRequest() {
$viewer = $this->getRequest()->getUser();
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($this->username))
->executeOne();
if (!$user) {
return new Aphront404Response();
}
require_celerity_resource('phabricator-profile-css');
$profile = $user->loadUserProfile();
$username = phutil_escape_uri($user->getUserName());
$picture = $user->loadProfileImageURI();
$header = id(new PhabricatorHeaderView())
->setHeader($user->getUserName().' ('.$user->getRealName().')')
->setSubheader($profile->getTitle())
->setImage($picture);
$actions = id(new PhabricatorActionListView())
->setObject($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setUser($viewer);
$can_edit = ($user->getPHID() == $viewer->getPHID());
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Profile'))
->setHref($this->getApplicationURI('editprofile/'.$user->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('image')
->setName(pht('Edit Profile Picture'))
->setHref($this->getApplicationURI('picture/'.$user->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($viewer->getIsAdmin()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('blame')
->setName(pht('Administrate User'))
->setHref($this->getApplicationURI('edit/'.$user->getID().'/')));
}
$properties = $this->buildPropertyView($user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($user->getUsername()));
$feed = $this->renderUserFeed($user);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$feed,
),
array(
'title' => $user->getUsername(),
'device' => true,
'dust' => true,
));
}
private function buildPropertyView(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($user);
$fields = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_VIEW);
foreach ($fields as $field) {
$field->setViewer($viewer);
}
$view->applyCustomFields($fields);
return $view;
}
private function renderUserFeed(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$user->getPHID(),
));
$query->setLimit(100);
$query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($viewer);
$view = $builder->buildView();
return hsprintf(
'
%s
',
$view->render());
}
}
diff --git a/src/applications/phame/controller/blog/PhameBlogViewController.php b/src/applications/phame/controller/blog/PhameBlogViewController.php
index 9c0805932..8d6342e23 100644
--- a/src/applications/phame/controller/blog/PhameBlogViewController.php
+++ b/src/applications/phame/controller/blog/PhameBlogViewController.php
@@ -1,196 +1,197 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$blog = id(new PhameBlogQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$posts = id(new PhamePostQuery())
->setViewer($user)
->withBlogPHIDs(array($blog->getPHID()))
->executeWithCursorPager($pager);
$nav = $this->renderSideNavFilterView(null);
$header = id(new PhabricatorHeaderView())
->setHeader($blog->getName());
$handle_phids = array_merge(
mpull($posts, 'getBloggerPHID'),
mpull($posts, 'getBlogPHID'));
$this->loadHandles($handle_phids);
$actions = $this->renderActions($blog, $user);
$properties = $this->renderProperties($blog, $user);
$post_list = $this->renderPostList(
$posts,
$user,
pht('This blog has no visible posts.'));
require_celerity_resource('phame-css');
$post_list = id(new PHUIBoxView())
->addPadding(PHUI::PADDING_LARGE)
->addClass('phame-post-list')
->appendChild($post_list);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($blog->getName())
->setHref($this->getApplicationURI()));
$nav->appendChild(
array(
$crumbs,
$header,
$actions,
$properties,
$post_list,
));
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
'title' => $blog->getName(),
'dust' => true,
));
}
private function renderProperties(PhameBlog $blog, PhabricatorUser $user) {
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
$properties = new PhabricatorPropertyListView();
$properties->addProperty(
pht('Skin'),
$blog->getSkin());
$properties->addProperty(
pht('Domain'),
$blog->getDomain());
$feed_uri = PhabricatorEnv::getProductionURI(
$this->getApplicationURI('blog/feed/'.$blog->getID().'/'));
$properties->addProperty(
pht('Atom URI'),
javelin_tag('a',
array(
'href' => $feed_uri,
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => pht('Atom URI does not support custom domains.'),
'size' => 320,
)
),
$feed_uri));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$blog);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$properties->addProperty(
pht('Joinable By'),
$descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user)
->addObject($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)
->process();
$properties->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($blog, PhameBlog::MARKUP_FIELD_DESCRIPTION)));
return $properties;
}
private function renderActions(PhameBlog $blog, PhabricatorUser $user) {
$actions = id(new PhabricatorActionListView())
->setObject($blog)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$blog,
PhabricatorPolicyCapability::CAN_EDIT);
$can_join = PhabricatorPolicyFilter::hasCapability(
$user,
$blog,
PhabricatorPolicyCapability::CAN_JOIN);
$must_use_form = $blog->getDomain();
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('new')
->setHref($this->getApplicationURI('post/edit/?blog='.$blog->getID()))
->setName(pht('Write Post'))
->setDisabled(!$can_join)
->setWorkflow(!$can_join));
$actions->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setIcon('world')
->setHref($this->getApplicationURI('live/'.$blog->getID().'/'))
->setRenderAsForm($must_use_form)
->setName(pht('View Live')));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
->setName('Edit Blog')
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->setHref($this->getApplicationURI('blog/delete/'.$blog->getID().'/'))
->setName('Delete Blog')
->setDisabled(!$can_edit)
->setWorkflow(true));
return $actions;
}
}
diff --git a/src/applications/phame/controller/post/PhamePostViewController.php b/src/applications/phame/controller/post/PhamePostViewController.php
index 43f3de762..6adf240b9 100644
--- a/src/applications/phame/controller/post/PhamePostViewController.php
+++ b/src/applications/phame/controller/post/PhamePostViewController.php
@@ -1,211 +1,212 @@
id = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$post = id(new PhamePostQuery())
->setViewer($user)
->withIDs(array($this->id))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$nav = $this->renderSideNavFilterView();
$this->loadHandles(
array(
$post->getBlogPHID(),
$post->getBloggerPHID(),
));
$actions = $this->renderActions($post, $user);
$properties = $this->renderProperties($post, $user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($post->getTitle())
->setHref($this->getApplicationURI('post/view/'.$post->getID().'/')));
$nav->appendChild($crumbs);
$nav->appendChild(
id(new PhabricatorHeaderView())
->setHeader($post->getTitle()));
if ($post->isDraft()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setTitle(pht('Draft Post'))
->appendChild(
pht('Only you can see this draft until you publish it. '.
'Use "Preview / Publish" to publish this post.')));
}
if (!$post->getBlog()) {
$nav->appendChild(
id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_WARNING)
->setTitle(pht('Not On A Blog'))
->appendChild(
pht('This post is not associated with a blog (the blog may have '.
'been deleted). Use "Move Post" to move it to a new blog.')));
}
$nav->appendChild(
array(
$actions,
$properties,
));
return $this->buildApplicationPage(
$nav,
array(
'title' => $post->getTitle(),
'device' => true,
'dust' => true,
));
}
private function renderActions(
PhamePost $post,
PhabricatorUser $user) {
$actions = id(new PhabricatorActionListView())
->setObject($post)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setUser($user);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$post,
PhabricatorPolicyCapability::CAN_EDIT);
$id = $post->getID();
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setHref($this->getApplicationURI('post/edit/'.$id.'/'))
->setName(pht('Edit Post'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('move')
->setHref($this->getApplicationURI('post/move/'.$id.'/'))
->setName(pht('Move Post'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($post->isDraft()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('preview')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Preview / Publish')));
} else {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('unpublish')
->setHref($this->getApplicationURI('post/unpublish/'.$id.'/'))
->setName(pht('Unpublish'))
->setWorkflow(true));
}
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('delete')
->setHref($this->getApplicationURI('post/delete/'.$id.'/'))
->setName(pht('Delete Post'))
->setDisabled(!$can_edit)
->setWorkflow(true));
$blog = $post->getBlog();
$can_view_live = $blog && !$post->isDraft();
$must_use_form = $blog && $blog->getDomain();
if ($can_view_live) {
$live_uri = 'live/'.$blog->getID().'/post/'.$post->getPhameTitle();
} else {
$live_uri = 'post/notlive/'.$post->getID().'/';
}
$live_uri = $this->getApplicationURI($live_uri);
$actions->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setIcon('world')
->setHref($live_uri)
->setName(pht('View Live'))
->setRenderAsForm($must_use_form)
->setDisabled(!$can_view_live)
->setWorkflow(!$can_view_live));
return $actions;
}
private function renderProperties(
PhamePost $post,
PhabricatorUser $user) {
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($post);
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$post);
$properties->addProperty(
pht('Blog'),
$post->getBlogPHID()
? $this->getHandle($post->getBlogPHID())->renderLink()
: null);
$properties->addProperty(
pht('Blogger'),
$this->getHandle($post->getBloggerPHID())->renderLink());
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->addProperty(
pht('Published'),
$post->isDraft()
? pht('Draft')
: phabricator_datetime($post->getDatePublished(), $user));
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user)
->addObject($post, PhamePost::MARKUP_FIELD_BODY)
->process();
$properties->invokeWillRenderEvent();
$properties->addTextContent(
phutil_tag(
'div',
array(
'class' => 'phabricator-remarkup',
),
$engine->getOutput($post, PhamePost::MARKUP_FIELD_BODY)));
return $properties;
}
}
diff --git a/src/applications/phlux/controller/PhluxViewController.php b/src/applications/phlux/controller/PhluxViewController.php
index 50574d6cc..1df396747 100644
--- a/src/applications/phlux/controller/PhluxViewController.php
+++ b/src/applications/phlux/controller/PhluxViewController.php
@@ -1,99 +1,100 @@
key = $data['key'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$var = id(new PhluxVariableQuery())
->setViewer($user)
->withKeys(array($this->key))
->executeOne();
if (!$var) {
return new Aphront404Response();
}
$crumbs = $this->buildApplicationCrumbs();
$title = $var->getVariableKey();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName($title)
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($request->getRequestURI())
->setObject($var);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$var,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Variable'))
->setHref($this->getApplicationURI('/edit/'.$var->getVariableKey().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$display_value = json_encode($var->getVariableValue());
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$var);
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($var)
->addProperty(pht('Value'), $display_value)
->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW])
->addProperty(
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
$xactions = id(new PhluxTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($var->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
}
diff --git a/src/applications/pholio/controller/PholioMockViewController.php b/src/applications/pholio/controller/PholioMockViewController.php
index 396157b55..3a214e5b7 100644
--- a/src/applications/pholio/controller/PholioMockViewController.php
+++ b/src/applications/pholio/controller/PholioMockViewController.php
@@ -1,196 +1,197 @@
id = $data['id'];
$this->imageID = idx($data, 'imageID');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$mock = id(new PholioMockQuery())
->setViewer($user)
->withIDs(array($this->id))
->needImages(true)
->needCoverFiles(true)
->executeOne();
if (!$mock) {
return new Aphront404Response();
}
$xactions = id(new PholioTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($mock->getPHID()))
->execute();
$phids = array($mock->getAuthorPHID());
$this->loadHandles($phids);
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$engine->addObject($mock, PholioMock::MARKUP_FIELD_DESCRIPTION);
foreach ($xactions as $xaction) {
if ($xaction->getComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$title = $mock->getName();
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = $this->buildActionView($mock);
$properties = $this->buildPropertyView($mock, $engine);
require_celerity_resource('pholio-css');
require_celerity_resource('pholio-inline-comments-css');
$comment_form_id = celerity_generate_unique_node_id();
$output = id(new PholioMockImagesView())
->setRequestURI($request->getRequestURI())
->setCommentFormID($comment_form_id)
->setUser($user)
->setMock($mock)
->setImageID($this->imageID);
$xaction_view = id(new PholioTransactionView())
->setUser($this->getRequest()->getUser())
->setTransactions($xactions)
->setMarkupEngine($engine);
$add_comment = $this->buildAddCommentView($mock, $comment_form_id);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNav());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('M'.$mock->getID())
->setHref('/M'.$mock->getID()));
$content = array(
$crumbs,
$header,
$actions,
$properties,
$output->render(),
$xaction_view,
$add_comment,
);
return $this->buildApplicationPage(
$content,
array(
'title' => 'M'.$mock->getID().' '.$title,
'device' => true,
'pageObjects' => array($mock->getPHID()),
));
}
private function buildActionView(PholioMock $mock) {
$user = $this->getRequest()->getUser();
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($mock);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$mock,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('edit')
->setName(pht('Edit Mock'))
->setHref($this->getApplicationURI('/edit/'.$mock->getID().'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
return $actions;
}
private function buildPropertyView(
PholioMock $mock,
PhabricatorMarkupEngine $engine) {
$user = $this->getRequest()->getUser();
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->setObject($mock);
$properties->addProperty(
pht('Author'),
$this->getHandle($mock->getAuthorPHID())->renderLink());
$properties->addProperty(
pht('Created'),
phabricator_datetime($mock->getDateCreated(), $user));
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$user,
$mock);
$properties->addProperty(
pht('Visible To'),
$descriptions[PhabricatorPolicyCapability::CAN_VIEW]);
$properties->invokeWillRenderEvent();
$properties->addImageContent(
$engine->getOutput($mock, PholioMock::MARKUP_FIELD_DESCRIPTION));
return $properties;
}
private function buildAddCommentView(PholioMock $mock, $comment_form_id) {
$user = $this->getRequest()->getUser();
$draft = PhabricatorDraft::newFromUserAndKey($user, $mock->getPHID());
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$title = $is_serious
? pht('Add Comment')
: pht('History Beckons');
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$button_name = $is_serious
? pht('Add Comment')
: pht('Answer The Call');
$form = id(new PhabricatorApplicationTransactionCommentView())
->setUser($user)
->setFormID($comment_form_id)
->setDraft($draft)
->setSubmitButtonName($button_name)
->setAction($this->getApplicationURI('/comment/'.$mock->getID().'/'))
->setRequestURI($this->getRequest()->getRequestURI());
return array(
$header,
$form,
);
}
}
diff --git a/src/applications/phortune/controller/PhortuneAccountViewController.php b/src/applications/phortune/controller/PhortuneAccountViewController.php
index 9ff257307..2faeb83c7 100644
--- a/src/applications/phortune/controller/PhortuneAccountViewController.php
+++ b/src/applications/phortune/controller/PhortuneAccountViewController.php
@@ -1,178 +1,180 @@
accountID = $data['accountID'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$account = id(new PhortuneAccountQuery())
->setViewer($user)
->withIDs(array($this->accountID))
->executeOne();
if (!$account) {
return new Aphront404Response();
}
$title = $account->getName();
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Account'))
->setHref($request->getRequestURI()));
$header = id(new PhabricatorHeaderView())
->setHeader($title);
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Account'))
->setIcon('edit')
->setHref('#')
->setDisabled(true))
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Members'))
->setIcon('transcript')
->setHref('#')
->setDisabled(true));
$crumbs->setActionList($actions);
$properties = id(new PhabricatorPropertyListView())
->setObject($account)
->setUser($user);
$properties->addProperty(pht('Balance'), $account->getBalanceInCents());
$payment_methods = $this->buildPaymentMethodsSection($account);
$purchase_history = $this->buildPurchaseHistorySection($account);
$account_history = $this->buildAccountHistorySection($account);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$payment_methods,
$purchase_history,
$account_history,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
private function buildPaymentMethodsSection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Payment Methods'));
$id = $account->getID();
$add_uri = $this->getApplicationURI($id.'/paymentmethod/edit/');
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Payment Method'))
->setIcon('new')
->setHref($add_uri));
$list = id(new PhabricatorObjectItemListView())
->setUser($user)
->setNoDataString(
pht('No payment methods associated with this account.'));
$methods = id(new PhortunePaymentMethodQuery())
->setViewer($user)
->withAccountPHIDs(array($account->getPHID()))
->withStatus(PhortunePaymentMethodQuery::STATUS_OPEN)
->execute();
if ($methods) {
$this->loadHandles(mpull($methods, 'getAuthorPHID'));
}
foreach ($methods as $method) {
$item = new PhabricatorObjectItemView();
$item->setHeader($method->getBrand().' / '.$method->getLastFourDigits());
switch ($method->getStatus()) {
case PhortunePaymentMethod::STATUS_ACTIVE:
$item->addAttribute(pht('Active'));
$item->setBarColor('green');
break;
}
$item->addAttribute(
pht(
'Added %s by %s',
phabricator_datetime($method->getDateCreated(), $user),
$this->getHandle($method->getAuthorPHID())->renderLink()));
$list->addItem($item);
}
return array(
$header,
$actions,
$list,
);
}
private function buildPurchaseHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Purchase History'));
return array(
$header,
);
}
private function buildAccountHistorySection(PhortuneAccount $account) {
$request = $this->getRequest();
$user = $request->getUser();
$header = id(new PhabricatorHeaderView())
->setHeader(pht('Account History'));
$xactions = id(new PhortuneAccountTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($account->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
return array(
$header,
$xaction_view,
);
}
}
diff --git a/src/applications/phortune/controller/PhortuneProductViewController.php b/src/applications/phortune/controller/PhortuneProductViewController.php
index 851aea4a7..9481c7147 100644
--- a/src/applications/phortune/controller/PhortuneProductViewController.php
+++ b/src/applications/phortune/controller/PhortuneProductViewController.php
@@ -1,97 +1,98 @@
productID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$product = id(new PhortuneProductQuery())
->setViewer($user)
->withIDs(array($this->productID))
->executeOne();
if (!$product) {
return new Aphront404Response();
}
$title = pht('Product: %s', $product->getProductName());
$header = id(new PhabricatorHeaderView())
->setHeader($product->getProductName());
$account = $this->loadActiveAccount($user);
$edit_uri = $this->getApplicationURI('product/edit/'.$product->getID().'/');
$cart_uri = $this->getApplicationURI(
$account->getID().'/buy/'.$product->getID().'/');
$actions = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($request->getRequestURI())
->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Product'))
->setHref($edit_uri)
->setIcon('edit'))
->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setName(pht('Purchase'))
->setHref($cart_uri)
->setIcon('new')
->setRenderAsForm(true));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('Products'))
->setHref($this->getApplicationURI('product/')));
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName(pht('#%d', $product->getID()))
->setHref($request->getRequestURI()));
$properties = id(new PhabricatorPropertyListView())
->setUser($user)
->addProperty(pht('Type'), $product->getTypeName())
->addProperty(
pht('Price'),
PhortuneCurrency::newFromUSDCents($product->getPriceInCents())
->formatForDisplay());
$xactions = id(new PhortuneProductTransactionQuery())
->setViewer($user)
->withObjectPHIDs(array($product->getPHID()))
->execute();
$engine = id(new PhabricatorMarkupEngine())
->setViewer($user);
$xaction_view = id(new PhabricatorApplicationTransactionView())
->setUser($user)
->setTransactions($xactions)
->setMarkupEngine($engine);
return $this->buildApplicationPage(
array(
$crumbs,
$header,
$actions,
$properties,
$xaction_view,
),
array(
'title' => $title,
'device' => true,
'dust' => true,
));
}
}
diff --git a/src/applications/phrequent/event/PhrequentUIEventListener.php b/src/applications/phrequent/event/PhrequentUIEventListener.php
index 3f563c03a..df8146c55 100644
--- a/src/applications/phrequent/event/PhrequentUIEventListener.php
+++ b/src/applications/phrequent/event/PhrequentUIEventListener.php
@@ -1,127 +1,125 @@
listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
$this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES:
$this->handlePropertyEvent($event);
break;
}
}
private function handleActionEvent($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 PhrequentTrackableInterface)) {
// This object isn't a time trackable object.
return;
}
$tracking = PhrequentUserTimeQuery::isUserTrackingObject(
$user,
$object->getPHID());
if (!$tracking) {
$track_action = id(new PhabricatorActionView())
- ->setUser($user)
->setName(pht('Start Tracking Time'))
->setIcon('history')
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/phrequent/track/start/'.$object->getPHID().'/');
} else {
$track_action = id(new PhabricatorActionView())
- ->setUser($user)
->setName(pht('Stop Tracking Time'))
->setIcon('history')
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/phrequent/track/stop/'.$object->getPHID().'/');
}
if (!$user->isLoggedIn()) {
$track_action->setDisabled(true);
}
$actions = $event->getValue('actions');
$actions[] = $track_action;
$event->setValue('actions', $actions);
}
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 PhrequentTrackableInterface)) {
// This object isn't a time trackable object.
return;
}
$depth = false;
$stack = PhrequentUserTimeQuery::loadUserStack($user);
if ($stack) {
$stack = array_values($stack);
for ($ii = 0; $ii < count($stack); $ii++) {
if ($stack[$ii]->getObjectPHID() == $object->getPHID()) {
$depth = ($ii + 1);
break;
}
}
}
$time_spent = PhrequentUserTimeQuery::getTotalTimeSpentOnObject(
$object->getPHID());
if (!$depth && !$time_spent) {
return;
}
require_celerity_resource('phrequent-css');
$property = array();
if ($depth == 1) {
$property[] = phutil_tag(
'div',
array(
'class' => 'phrequent-tracking-property phrequent-active',
),
pht('Currently Tracking'));
} else if ($depth > 1) {
$property[] = phutil_tag(
'div',
array(
'class' => 'phrequent-tracking-property phrequent-on-stack',
),
pht('On Stack'));
}
if ($time_spent) {
$property[] = phabricator_format_relative_time_detailed($time_spent);
}
$view = $event->getValue('view');
$view->addProperty(pht('Time Spent'), $property);
}
}
diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php
index c35ff083f..0527d31fa 100644
--- a/src/applications/phriction/controller/PhrictionDocumentController.php
+++ b/src/applications/phriction/controller/PhrictionDocumentController.php
@@ -1,462 +1,463 @@
slug = $data['slug'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$slug = PhabricatorSlug::normalize($this->slug);
if ($slug != $this->slug) {
$uri = PhrictionDocument::getSlugURI($slug);
// Canonicalize pages to their one true URI.
return id(new AphrontRedirectResponse())->setURI($uri);
}
require_celerity_resource('phriction-document-css');
$document = id(new PhrictionDocument())->loadOneWhere(
'slug = %s',
$slug);
$version_note = null;
$core_content = '';
$move_notice = '';
$properties = null;
if (!$document) {
$document = new PhrictionDocument();
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if (!$project) {
return new Aphront404Response();
}
}
$create_uri = '/phriction/edit/?slug='.$slug;
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NODATA);
$notice->setTitle(pht('No content here!'));
$notice->appendChild(
pht(
'No document found at %s. You can '.
'create a new document here.',
phutil_tag('tt', array(), $slug),
$create_uri));
$core_content = $notice;
$page_title = pht('Page Not Found');
} else {
$version = $request->getInt('v');
if ($version) {
$content = id(new PhrictionContent())->loadOneWhere(
'documentID = %d AND version = %d',
$document->getID(),
$version);
if (!$content) {
return new Aphront404Response();
}
if ($content->getID() != $document->getContentID()) {
$vdate = phabricator_datetime($content->getDateCreated(), $user);
$version_note = new AphrontErrorView();
$version_note->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$version_note->setTitle('Older Version');
$version_note->appendChild(
pht('You are viewing an older version of this document, as it '.
'appeared on %s.', $vdate));
}
} else {
$content = id(new PhrictionContent())->load($document->getContentID());
}
$page_title = $content->getTitle();
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$document->getPHID());
$properties = $this
->buildPropertyListView($document, $content, $slug, $subscribers);
$doc_status = $document->getStatus();
$current_status = $content->getChangeType();
if ($current_status == PhrictionChangeType::CHANGE_EDIT ||
$current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$core_content = $content->renderContent($user);
} else if ($current_status == PhrictionChangeType::CHANGE_DELETE) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Deleted'));
$notice->appendChild(
pht('This document has been deleted. You can edit it to put new '.
'content here, or use history to revert to an earlier version.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_STUB) {
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Empty Document'));
$notice->appendChild(
pht('This document is empty. You can edit it to put some proper '.
'content here.'));
$core_content = $notice->render();
} else if ($current_status == PhrictionChangeType::CHANGE_MOVE_AWAY) {
$new_doc_id = $content->getChangeRef();
$new_doc = new PhrictionDocument();
$new_doc->load($new_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($new_doc->getSlug());
$notice = new AphrontErrorView();
$notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
$notice->setTitle(pht('Document Moved'));
$notice->appendChild(phutil_tag('p', array(),
pht('This document has been moved to %s. You can edit it to put new '.
'content here, or use history to revert to an earlier version.',
phutil_tag('a', array('href' => $slug_uri), $slug_uri))));
$core_content = $notice->render();
} else {
throw new Exception("Unknown document status '{$doc_status}'!");
}
$move_notice = null;
if ($current_status == PhrictionChangeType::CHANGE_MOVE_HERE) {
$from_doc_id = $content->getChangeRef();
$from_doc = id(new PhrictionDocument())->load($from_doc_id);
$slug_uri = PhrictionDocument::getSlugURI($from_doc->getSlug());
$move_notice = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->appendChild(pht('This document was moved from %s',
phutil_tag('a', array('href' => $slug_uri), $slug_uri)))
->render();
}
}
if ($version_note) {
$version_note = $version_note->render();
}
$children = $this->renderDocumentChildren($slug);
$actions = $this->buildActionView($user, $document);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setActionList($actions);
$crumb_views = $this->renderBreadcrumbs($slug);
foreach ($crumb_views as $view) {
$crumbs->addCrumb($view);
}
$header = id(new PhabricatorHeaderView())
->setHeader($page_title);
$page_content = id(new PHUIDocumentView())
->setOffset(true)
->appendChild(
array(
$header,
$actions,
$properties,
$move_notice,
$core_content,
));
$core_page = phutil_tag(
'div',
array(
'class' => 'phriction-offset'
),
array(
$page_content,
$children,
));
return $this->buildApplicationPage(
array(
$crumbs->render(),
$core_page,
),
array(
'pageObjects' => array($document->getPHID()),
'title' => $page_title,
'device' => true,
'dust' => true,
));
}
private function buildPropertyListView(
PhrictionDocument $document,
PhrictionContent $content,
$slug,
array $subscribers) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($document);
$project_phid = null;
if (PhrictionDocument::isProjectSlug($slug)) {
$project = id(new PhabricatorProject())->loadOneWhere(
'phrictionSlug = %s',
PhrictionDocument::getProjectSlugIdentifier($slug));
if ($project) {
$project_phid = $project->getPHID();
}
}
$phids = array_filter(
array(
$content->getAuthorPHID(),
$project_phid,
));
if ($subscribers) {
$phids = array_merge($phids, $subscribers);
}
$this->loadHandles($phids);
$project_info = null;
if ($project_phid) {
$view->addProperty(
pht('Project Info'),
$this->getHandle($project_phid)->renderLink());
}
$view->addProperty(
pht('Last Author'),
$this->getHandle($content->getAuthorPHID())->renderLink());
$age = time() - $content->getDateCreated();
$age = floor($age / (60 * 60 * 24));
if ($age < 1) {
$when = pht('Today');
} else if ($age == 1) {
$when = pht('Yesterday');
} else {
$when = pht("%d Days Ago", $age);
}
$view->addProperty(pht('Last Updated'), $when);
if ($subscribers) {
$subscribers = $this->renderHandlesForPHIDs($subscribers, ',');
$view->addProperty(pht('Subscribers'), $subscribers);
}
return $view;
}
private function buildActionView(
PhabricatorUser $user,
PhrictionDocument $document) {
$can_edit = PhabricatorPolicyFilter::hasCapability(
$user,
$document,
PhabricatorPolicyCapability::CAN_EDIT);
$slug = PhabricatorSlug::normalize($this->slug);
$action_view = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI())
->setObject($document);
if (!$document->getID()) {
return $action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Create This Document'))
->setIcon('create')
->setHref('/phriction/edit/?slug='.$slug));
}
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Document'))
->setIcon('edit')
->setHref('/phriction/edit/'.$document->getID().'/'));
if ($document->getStatus() == PhrictionDocumentStatus::STATUS_EXISTS) {
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Move Document'))
->setIcon('move')
->setHref('/phriction/move/'.$document->getID().'/')
->setWorkflow(true));
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('Delete Document'))
->setIcon('delete')
->setHref('/phriction/delete/'.$document->getID().'/')
->setWorkflow(true));
}
return
$action_view->addAction(
id(new PhabricatorActionView())
->setName(pht('View History'))
->setIcon('history')
->setHref(PhrictionDocument::getSlugURI($slug, 'history')));
}
private function renderDocumentChildren($slug) {
$document_dao = new PhrictionDocument();
$content_dao = new PhrictionContent();
$conn = $document_dao->establishConnection('r');
$limit = 250;
$d_child = PhabricatorSlug::getDepth($slug) + 1;
$d_grandchild = PhabricatorSlug::getDepth($slug) + 2;
// Select children and grandchildren.
$children = queryfx_all(
$conn,
'SELECT d.slug, d.depth, c.title FROM %T d JOIN %T c
ON d.contentID = c.id
WHERE d.slug LIKE %> AND d.depth IN (%d, %d)
AND d.status IN (%Ld)
ORDER BY d.depth, c.title LIMIT %d',
$document_dao->getTableName(),
$content_dao->getTableName(),
($slug == '/' ? '' : $slug),
$d_child,
$d_grandchild,
array(
PhrictionDocumentStatus::STATUS_EXISTS,
PhrictionDocumentStatus::STATUS_STUB,
),
$limit);
if (!$children) {
return;
}
// We're going to render in one of three modes to try to accommodate
// different information scales:
//
// - If we found fewer than $limit rows, we know we have all the children
// and grandchildren and there aren't all that many. We can just render
// everything.
// - If we found $limit rows but the results included some grandchildren,
// we just throw them out and render only the children, as we know we
// have them all.
// - If we found $limit rows and the results have no grandchildren, we
// have a ton of children. Render them and then let the user know that
// this is not an exhaustive list.
if (count($children) == $limit) {
$more_children = true;
foreach ($children as $child) {
if ($child['depth'] == $d_grandchild) {
$more_children = false;
}
}
$show_grandchildren = false;
} else {
$show_grandchildren = true;
$more_children = false;
}
$grandchildren = array();
foreach ($children as $key => $child) {
if ($child['depth'] == $d_child) {
continue;
} else {
unset($children[$key]);
if ($show_grandchildren) {
$ancestors = PhabricatorSlug::getAncestry($child['slug']);
$grandchildren[end($ancestors)][] = $child;
}
}
}
// Fill in any missing children.
$known_slugs = ipull($children, null, 'slug');
foreach ($grandchildren as $slug => $ignored) {
if (empty($known_slugs[$slug])) {
$children[] = array(
'slug' => $slug,
'depth' => $d_child,
'title' => PhabricatorSlug::getDefaultTitle($slug),
'empty' => true,
);
}
}
$children = isort($children, 'title');
$list = array();
foreach ($children as $child) {
$list[] = hsprintf('');
$list[] = $this->renderChildDocumentLink($child);
$grand = idx($grandchildren, $child['slug'], array());
if ($grand) {
$list[] = hsprintf('');
foreach ($grand as $grandchild) {
$list[] = hsprintf('- ');
$list[] = $this->renderChildDocumentLink($grandchild);
$list[] = hsprintf('
');
}
$list[] = hsprintf('
');
}
$list[] = hsprintf('');
}
if ($more_children) {
$list[] = phutil_tag('li', array(), pht('More...'));
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'phriction-children-header',
),
pht('Document Hierarchy')),
phutil_tag(
'div',
array(
'class' => 'phriction-children',
),
phutil_tag('ul', array(), $list)),
);
return id(new PHUIDocumentView())
->setOffset(true)
->appendChild($content);
}
private function renderChildDocumentLink(array $info) {
$title = nonempty($info['title'], pht('(Untitled Document)'));
$item = phutil_tag(
'a',
array(
'href' => PhrictionDocument::getSlugURI($info['slug']),
),
$title);
if (isset($info['empty'])) {
$item = phutil_tag('em', array(), $item);
}
return $item;
}
protected function getDocumentSlug() {
return $this->slug;
}
}
diff --git a/src/applications/ponder/controller/PonderQuestionViewController.php b/src/applications/ponder/controller/PonderQuestionViewController.php
index 059a290f5..b7dc15efd 100644
--- a/src/applications/ponder/controller/PonderQuestionViewController.php
+++ b/src/applications/ponder/controller/PonderQuestionViewController.php
@@ -1,138 +1,136 @@
questionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$question = PonderQuestionQuery::loadSingle($user, $this->questionID);
if (!$question) {
return new Aphront404Response();
}
$question->attachRelated();
$question->attachVotes($user->getPHID());
$object_phids = array($user->getPHID(), $question->getAuthorPHID());
$answers = $question->getAnswers();
$comments = $question->getComments();
foreach ($comments as $comment) {
$object_phids[] = $comment->getAuthorPHID();
}
foreach ($answers as $answer) {
$object_phids[] = $answer->getAuthorPHID();
$comments = $answer->getComments();
foreach ($comments as $comment) {
$object_phids[] = $comment->getAuthorPHID();
}
}
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$question->getPHID());
$object_phids = array_merge($object_phids, $subscribers);
$this->loadHandles($object_phids);
$handles = $this->getLoadedHandles();
$detail_panel = new PonderQuestionDetailView();
$detail_panel
->setQuestion($question)
->setUser($user)
->setHandles($handles);
$responses_panel = new PonderAnswerListView();
$responses_panel
->setQuestion($question)
->setHandles($handles)
->setUser($user)
->setAnswers($answers);
$answer_add_panel = new PonderAddAnswerView();
$answer_add_panel
->setQuestion($question)
->setUser($user)
->setActionURI("/ponder/answer/add/");
$header = id(new PhabricatorHeaderView())
->setHeader($question->getTitle());
$actions = $this->buildActionListView($question);
$properties = $this->buildPropertyListView($question, $subscribers);
$crumbs = $this->buildApplicationCrumbs($this->buildSideNavView());
$crumbs->setActionList($actions);
$crumbs->addCrumb(
id(new PhabricatorCrumbView())
->setName('Q'.$this->questionID)
->setHref('/Q'.$this->questionID));
$nav = $this->buildSideNavView($question);
$nav->appendChild(
array(
$crumbs,
$header,
$actions,
$properties,
$detail_panel,
$responses_panel,
$answer_add_panel
));
$nav->selectFilter(null);
return $this->buildApplicationPage(
$nav,
array(
'device' => true,
'title' => 'Q'.$question->getID().' '.$question->getTitle(),
'dust' => true,
));
}
private function buildActionListView(PonderQuestion $question) {
- $viewer = $this->getRequest()->getUser();
- $view = new PhabricatorActionListView();
-
- $view->setUser($viewer);
- $view->setObject($question);
-
- return $view;
+ $request = $this->getRequest();
+ return id(new PhabricatorActionListView())
+ ->setUser($request->getUser())
+ ->setObject($question)
+ ->setObjectURI($request->getRequestURI());
}
private function buildPropertyListView(
PonderQuestion $question,
array $subscribers) {
$viewer = $this->getRequest()->getUser();
$view = id(new PhabricatorPropertyListView())
->setUser($viewer)
->setObject($question);
$view->addProperty(
pht('Author'),
$this->getHandle($question->getAuthorPHID())->renderLink());
$view->addProperty(
pht('Created'),
phabricator_datetime($question->getDateCreated(), $viewer));
if ($subscribers) {
$subscribers = $this->renderHandlesForPHIDs($subscribers);
}
$view->addProperty(
pht('Subscribers'),
nonempty($subscribers, phutil_tag('em', array(), pht('None'))));
return $view;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index f4f47024d..e6559f2bb 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,278 +1,279 @@
id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$query = id(new PhabricatorProjectQuery())
->setViewer($user)
->withIDs(array($this->id))
->needMembers(true);
$project = $query->executeOne();
$this->project = $project;
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
$nav_view = $this->buildLocalNavigation($project);
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
require_celerity_resource('phabricator-profile-css');
$tasks = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
$query->setLimit(50);
$query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$feed = $this->renderStories($stories);
$about = $this->renderAboutPage($project, $profile);
$people = $this->renderPeoplePage($project, $profile);
$col1 = hsprintf('%s%s', $about, $people);
$content = id(new AphrontMultiColumnView())
->addColumn($col1)
->addColumn($feed)
->setFluidLayout(true);
$content = hsprintf(
'%s%s
',
$tasks,
$content);
$header = id(new PhabricatorHeaderView())
->setHeader($project->getName())
->setSubheader(phutil_utf8_shorten($profile->getBlurb(), 1024))
->setImage($picture);
$action = null;
if (!$project->isUserMember($user->getPHID())) {
$can_join = PhabricatorPolicyFilter::hasCapability(
$user,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$action = id(new PhabricatorActionView())
->setUser($user)
->setRenderAsForm(true)
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('new')
->setDisabled(!$can_join)
->setName(pht('Join Project'));
} else {
$action = id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('delete')
->setName(pht('Leave Project...'));
}
$action_list = id(new PhabricatorActionListView())
->setUser($user)
+ ->setObjectURI($request->getRequestURI())
->addAction($action);
$nav_view->appendChild($header);
$nav_view->appendChild($action_list);
$nav_view->appendChild($content);
return $this->buildApplicationPage(
$nav_view,
array(
'title' => pht('%s Project', $project->getName()),
'device' => true,
'dust' => true,
));
}
private function renderAboutPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$viewer = $this->getRequest()->getUser();
$blurb = $profile->getBlurb();
$blurb = phutil_escape_html_newlines($blurb);
$phids = array($project->getAuthorPHID());
$phids = array_unique($phids);
$handles = $this->loadViewerHandles($phids);
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
$about = hsprintf(
'
%s |
%s |
%s |
%s |
PHID |
%s |
%s |
%s |
',
pht('About This Project'),
pht('Creator'),
$handles[$project->getAuthorPHID()]->renderLink(),
pht('Created'),
$timestamp,
$project->getPHID(),
pht('Blurb'),
$blurb);
return $about;
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$member_phids = $project->getMemberPHIDs();
$handles = $this->loadViewerHandles($member_phids);
$affiliated = array();
foreach ($handles as $phids => $handle) {
$affiliated[] = phutil_tag('li', array(), $handle->renderLink());
}
if ($affiliated) {
$affiliated = phutil_tag('ul', array(), $affiliated);
} else {
$affiliated = hsprintf('%s
', pht(
'No one is affiliated with this project.'));
}
return hsprintf(
'',
pht('People'),
$affiliated);
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
$query->setViewer($this->getRequest()->getUser());
$query->setLimit(100);
$stories = $query->execute();
if (!$stories) {
return pht('There are no stories about this project.');
}
return $this->renderStories($stories);
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$view = $builder->buildView();
return hsprintf(
''.
'%s'.
'
',
$view->render());
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$user = $this->getRequest()->getUser();
$query = id(new ManiphestTaskQuery())
->withAnyProjects(array($project->getPHID()))
->withStatus(ManiphestTaskQuery::STATUS_OPEN)
->setOrderBy(ManiphestTaskQuery::ORDER_PRIORITY)
->setLimit(10)
->setCalculateRows(true);
$tasks = $query->execute();
$count = $query->getRowCount();
$phids = mpull($tasks, 'getOwnerPHID');
$phids = array_merge(
$phids,
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$phids = array_filter($phids);
$handles = $this->loadViewerHandles($phids);
$task_list = new ManiphestTaskListView();
$task_list->setUser($user);
$task_list->setTasks($tasks);
$task_list->setHandles($handles);
$open = number_format($count);
$more_link = phutil_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
pht("View All Open Tasks \xC2\xBB"));
$content = hsprintf(
'',
pht('Open Tasks (%s)', $open),
$task_list,
$more_link);
return $content;
}
public function buildApplicationMenu() {
return $this->buildLocalNavigation($this->project)->getMenu();
}
}
diff --git a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
index e67aa9cc7..8d9e07fe2 100644
--- a/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
+++ b/src/applications/subscriptions/events/PhabricatorSubscriptionsUIEventListener.php
@@ -1,120 +1,117 @@
listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
$this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES:
$this->handlePropertyEvent($event);
break;
}
}
private function handleActionEvent($event) {
$user = $event->getUser();
$object = $event->getValue('object');
if (!$object || !$object->getPHID()) {
// No object, or the object has no PHID yet. No way to subscribe.
return;
}
if (!($object instanceof PhabricatorSubscribableInterface)) {
// This object isn't subscribable.
return;
}
if ($object->isAutomaticallySubscribed($user->getPHID())) {
$sub_action = id(new PhabricatorActionView())
->setWorkflow(true)
- ->setUser($user)
->setDisabled(true)
->setRenderAsForm(true)
->setHref('/subscriptions/add/'.$object->getPHID().'/')
->setName(pht('Automatically Subscribed'))
->setIcon('enable');
} else {
$subscribed = false;
if ($user->isLoggedIn()) {
$src_phid = $object->getPHID();
$dst_phid = $user->getPHID();
$edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_SUBSCRIBER;
$edges = id(new PhabricatorEdgeQuery())
->withSourcePHIDs(array($src_phid))
->withEdgeTypes(array($edge_type))
->withDestinationPHIDs(array($user->getPHID()))
->execute();
$subscribed = isset($edges[$src_phid][$edge_type][$dst_phid]);
}
if ($subscribed) {
$sub_action = id(new PhabricatorActionView())
- ->setUser($user)
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/subscriptions/delete/'.$object->getPHID().'/')
->setName(pht('Unsubscribe'))
->setIcon('disable');
} else {
$sub_action = id(new PhabricatorActionView())
- ->setUser($user)
->setWorkflow(true)
->setRenderAsForm(true)
->setHref('/subscriptions/add/'.$object->getPHID().'/')
->setName(pht('Subscribe'))
->setIcon('check');
}
if (!$user->isLoggedIn()) {
$sub_action->setDisabled(true);
}
}
$actions = $event->getValue('actions');
$actions[] = $sub_action;
$event->setValue('actions', $actions);
}
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 PhabricatorSubscribableInterface)) {
// This object isn't subscribable.
return;
}
$subscribers = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object->getPHID());
if ($subscribers) {
$handles = id(new PhabricatorObjectHandleData($subscribers))
->setViewer($user)
->loadHandles();
$sub_view = array();
foreach ($subscribers as $subscriber) {
$sub_view[] = $handles[$subscriber]->renderLink();
}
$sub_view = phutil_implode_html(', ', $sub_view);
} else {
$sub_view = phutil_tag('em', array(), pht('None'));
}
$view = $event->getValue('view');
$view->addProperty(pht('Subscribers'), $sub_view);
}
}
diff --git a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php
index a55ac4eb5..fba8e4e16 100644
--- a/src/applications/tokens/event/PhabricatorTokenUIEventListener.php
+++ b/src/applications/tokens/event/PhabricatorTokenUIEventListener.php
@@ -1,127 +1,125 @@
listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
$this->listen(PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES);
}
public function handleEvent(PhutilEvent $event) {
switch ($event->getType()) {
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
$this->handleActionEvent($event);
break;
case PhabricatorEventType::TYPE_UI_WILLRENDERPROPERTIES:
$this->handlePropertyEvent($event);
break;
}
}
private function handleActionEvent($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 PhabricatorTokenReceiverInterface)) {
// This object isn't a token receiver.
return;
}
$current = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->withAuthorPHIDs(array($user->getPHID()))
->withObjectPHIDs(array($object->getPHID()))
->execute();
if (!$current) {
$token_action = id(new PhabricatorActionView())
- ->setUser($user)
->setWorkflow(true)
->setHref('/token/give/'.$object->getPHID().'/')
->setName(pht('Award Token'))
->setIcon('like');
} else {
$token_action = id(new PhabricatorActionView())
- ->setUser($user)
->setWorkflow(true)
->setHref('/token/give/'.$object->getPHID().'/')
->setName(pht('Rescind Token'))
->setIcon('dislike');
}
if (!$user->isLoggedIn()) {
$token_action->setDisabled(true);
}
$actions = $event->getValue('actions');
$actions[] = $token_action;
$event->setValue('actions', $actions);
}
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 PhabricatorTokenReceiverInterface)) {
// This object isn't a token receiver.
return;
}
$limit = 1;
$tokens_given = id(new PhabricatorTokenGivenQuery())
->setViewer($user)
->withObjectPHIDs(array($object->getPHID()))
->execute();
if (!$tokens_given) {
return;
}
$tokens = id(new PhabricatorTokenQuery())
->setViewer($user)
->withPHIDs(mpull($tokens_given, 'getTokenPHID'))
->execute();
$tokens = mpull($tokens, null, 'getPHID');
$author_phids = mpull($tokens_given, 'getAuthorPHID');
$handles = id(new PhabricatorObjectHandleData($author_phids))
->setViewer($user)
->loadHandles();
Javelin::initBehavior('phabricator-tooltips');
$list = array();
foreach ($tokens_given as $token_given) {
if (!idx($tokens, $token_given->getTokenPHID())) {
continue;
}
$token = $tokens[$token_given->getTokenPHID()];
$list[] = javelin_tag(
'span',
array(
'sigil' => 'has-tooltip',
'meta' => array(
'tip' => $handles[$token_given->getAuthorPHID()]->getName(),
),
),
$token->renderIcon());
}
$view = $event->getValue('view');
$view->addProperty(pht('Tokens'), $list);
}
}
diff --git a/src/applications/uiexample/examples/PhabricatorActionListExample.php b/src/applications/uiexample/examples/PhabricatorActionListExample.php
index 1fe7698eb..8d1cd11fa 100644
--- a/src/applications/uiexample/examples/PhabricatorActionListExample.php
+++ b/src/applications/uiexample/examples/PhabricatorActionListExample.php
@@ -1,111 +1,112 @@
PhabricatorActionListView to render object actions.');
}
public function renderExample() {
$request = $this->getRequest();
$user = $request->getUser();
$notices = array();
if ($request->isFormPost()) {
$notices[] = 'You just submitted a valid form POST.';
}
if ($request->isJavelinWorkflow()) {
$notices[] = 'You just submitted a Workflow request.';
}
if ($notices) {
$notices = id(new AphrontErrorView())
->setSeverity(AphrontErrorView::SEVERITY_NOTICE)
->setErrors($notices);
} else {
$notices = null;
}
if ($request->isJavelinWorkflow()) {
$dialog = new AphrontDialogView();
$dialog->setUser($user);
$dialog->setTitle('Request Information');
$dialog->appendChild($notices);
$dialog->addCancelButton($request->getRequestURI(), 'Close');
return id(new AphrontDialogResponse())->setDialog($dialog);
}
- $view = new PhabricatorActionListView();
- $view->setUser($user);
+ $view = id(new PhabricatorActionListView())
+ ->setUser($user)
+ ->setObjectURI($this->getRequest()->getRequestURI());
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setName('Normal Action')
->setIcon('file'));
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setDisabled(true)
->setName('Disabled Action')
->setIcon('file'));
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setRenderAsForm(true)
->setName('Form Action')
->setIcon('file'));
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setRenderAsForm(true)
->setDisabled(true)
->setName('Disabled Form Action')
->setIcon('file'));
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setWorkflow(true)
->setName('Workflow Action')
->setIcon('file'));
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref($request->getRequestURI())
->setRenderAsForm(true)
->setWorkflow(true)
->setName('Form + Workflow Action')
->setIcon('file'));
foreach (PhabricatorActionView::getAvailableIcons() as $icon) {
$view->addAction(
id(new PhabricatorActionView())
->setUser($user)
->setHref('#')
->setName('Icon "'.$icon.'"')
->setIcon($icon));
}
return array(
$view,
hsprintf(''),
$notices,
);
}
}
diff --git a/src/view/layout/PhabricatorActionListView.php b/src/view/layout/PhabricatorActionListView.php
index bed429153..a7a1dc6d7 100644
--- a/src/view/layout/PhabricatorActionListView.php
+++ b/src/view/layout/PhabricatorActionListView.php
@@ -1,56 +1,67 @@
object = $object;
return $this;
}
+ public function setObjectURI($uri) {
+ $this->objectURI = $uri;
+ return $this;
+ }
+
public function addAction(PhabricatorActionView $view) {
$this->actions[] = $view;
return $this;
}
public function setID($id) {
$this->id = $id;
return $this;
}
public function render() {
if (!$this->user) {
throw new Exception(pht("Call setUser() before render()!"));
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS,
array(
'object' => $this->object,
'actions' => $this->actions,
));
$event->setUser($this->user);
PhutilEventEngine::dispatchEvent($event);
$actions = $event->getValue('actions');
if (!$actions) {
return null;
}
+ foreach ($actions as $action) {
+ $action->setObjectURI($this->objectURI);
+ $action->setUser($this->user);
+ }
+
require_celerity_resource('phabricator-action-list-view-css');
return phutil_tag(
'ul',
array(
'class' => 'phabricator-action-list-view',
'id' => $this->id
),
$actions);
}
}
diff --git a/src/view/layout/PhabricatorActionView.php b/src/view/layout/PhabricatorActionView.php
index 697b595c6..30038a414 100644
--- a/src/view/layout/PhabricatorActionView.php
+++ b/src/view/layout/PhabricatorActionView.php
@@ -1,159 +1,184 @@
objectURI = $object_uri;
+ return $this;
+ }
+ public function getObjectURI() {
+ return $this->objectURI;
+ }
public function setDownload($download) {
$this->download = $download;
return $this;
}
public function getDownload() {
return $this->download;
}
public function setHref($href) {
$this->href = $href;
return $this;
}
+ /**
+ * If the user is not logged in and the action is relatively complicated,
+ * give them a generic login link that will re-direct to the page they're
+ * viewing.
+ */
+ public function getHref() {
+ if ($this->workflow || $this->renderAsForm) {
+ if (!$this->user || !$this->user->isLoggedIn()) {
+ return id(new PhutilURI('/auth/start/'))
+ ->setQueryParam('next', (string)$this->getObjectURI());
+ }
+ }
+
+ return $this->href;
+ }
+
public function setIcon($icon) {
$this->icon = $icon;
return $this;
}
public function setIconSheet($sheet) {
$this->iconSheet = $sheet;
return $this;
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function setDisabled($disabled) {
$this->disabled = $disabled;
return $this;
}
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function setRenderAsForm($form) {
$this->renderAsForm = $form;
return $this;
}
public function render() {
$icon = null;
if ($this->icon) {
$sheet = nonempty($this->iconSheet, PHUIIconView::SPRITE_ICONS);
$suffix = '';
if ($this->disabled) {
$suffix = '-grey';
}
$icon = id(new PHUIIconView())
->addClass('phabricator-action-view-icon')
->setSpriteIcon($this->icon.$suffix)
->setSpriteSheet($sheet);
}
if ($this->href) {
if ($this->renderAsForm) {
if (!$this->user) {
throw new Exception(
'Call setUser() when rendering an action as a form.');
}
$item = javelin_tag(
'button',
array(
'class' => 'phabricator-action-view-item',
),
$this->name);
$sigils = array();
if ($this->workflow) {
$sigils[] = 'workflow';
}
if ($this->download) {
$sigils[] = 'download';
}
$item = phabricator_form(
$this->user,
array(
- 'action' => $this->href,
+ 'action' => $this->getHref(),
'method' => 'POST',
'sigil' => implode(' ', $sigils),
),
$item);
} else {
$item = javelin_tag(
'a',
array(
- 'href' => $this->href,
+ 'href' => $this->getHref(),
'class' => 'phabricator-action-view-item',
'sigil' => $this->workflow ? 'workflow' : null,
),
$this->name);
}
} else {
$item = phutil_tag(
'span',
array(
'class' => 'phabricator-action-view-item',
),
$this->name);
}
$classes = array();
$classes[] = 'phabricator-action-view';
if ($this->disabled) {
$classes[] = 'phabricator-action-view-disabled';
}
return phutil_tag(
'li',
array(
'class' => implode(' ', $classes),
),
array($icon, $item));
}
public static function getAvailableIcons() {
$root = dirname(phutil_get_library_root('phabricator'));
$path = $root.'/resources/sprite/manifest/icons.json';
$data = Filesystem::readFile($path);
$manifest = json_decode($data, true);
$results = array();
$prefix = 'icons-';
foreach ($manifest['sprites'] as $sprite) {
$name = $sprite['name'];
if (preg_match('/-(white|grey)$/', $name)) {
continue;
}
if (!strncmp($name, $prefix, strlen($prefix))) {
$results[] = substr($name, strlen($prefix));
}
}
return $results;
}
}