',
'limit' => 'optional int (default '.$this->getDefaultLimit().')',
'after' => 'optional int',
'view' => 'optional string (data, html, html-summary)',
);
}
private function getSupportedViewTypes() {
return array(
'html' => 'Full HTML presentation of story',
'data' => 'Dictionary with various data of the story',
'html-summary' => 'Story contains only the title of the story',
);
}
public function defineErrorTypes() {
$view_types = array_keys($this->getSupportedViewTypes());
$view_types = implode(', ', $view_types);
return array(
'ERR-UNKNOWN-TYPE' =>
'Unsupported view type, possibles are: ' . $view_types
);
}
public function defineReturnType() {
return 'nonempty dict';
}
protected function execute(ConduitAPIRequest $request) {
$results = array();
$user = $request->getUser();
$view_type = $request->getValue('view');
if (!$view_type) {
$view_type = 'data';
}
$limit = $request->getValue('limit');
if (!$limit) {
$limit = $this->getDefaultLimit();
}
$filter_phids = $request->getValue('filter_phids');
if (!$filter_phids) {
$filter_phids = array();
}
$after = $request->getValue('after');
$query = id(new PhabricatorFeedQuery())
->setLimit($limit)
->setFilterPHIDs($filter_phids)
- ->setAfter($after);
+ ->setViewer($user)
+ ->setAfterID($after);
$stories = $query->execute();
if ($stories) {
$handle_phids = array_mergev(mpull($stories, 'getRequiredHandlePHIDs'));
$handles = id(new PhabricatorObjectHandleData($handle_phids))
->loadHandles();
foreach ($stories as $story) {
$story->setHandles($handles);
$story_data = $story->getStoryData();
$data = null;
$view = $story->renderView();
$view->setEpoch($story->getEpoch());
$view->setViewer($user);
switch ($view_type) {
case 'html':
$data = $view->render();
break;
case 'html-summary':
$view->setOneLineStory(true);
$data = $view->render();
break;
case 'data':
$data = array(
'class' => $story_data->getStoryType(),
'epoch' => $story_data->getEpoch(),
'authorPHID' => $story_data->getAuthorPHID(),
'chronologicalKey' => $story_data->getChronologicalKey(),
'data' => $story_data->getStoryData(),
);
break;
default:
throw new ConduitException('ERR-UNKNOWN-TYPE');
}
$results[$story_data->getPHID()] = $data;
}
}
return $results;
}
}
diff --git a/src/applications/directory/controller/PhabricatorDirectoryMainController.php b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
index 05a8fb37d7..bfa22e065f 100644
--- a/src/applications/directory/controller/PhabricatorDirectoryMainController.php
+++ b/src/applications/directory/controller/PhabricatorDirectoryMainController.php
@@ -1,889 +1,849 @@
filter = idx($data, 'filter');
$this->subfilter = idx($data, 'subfilter');
}
public function processRequest() {
$user = $this->getRequest()->getUser();
$nav = $this->buildNav();
$this->filter = $nav->selectFilter($this->filter, 'home');
switch ($this->filter) {
case 'jump':
case 'apps':
break;
case 'home':
case 'feed':
$project_query = new PhabricatorProjectQuery();
$project_query->setMembers(array($user->getPHID()));
$projects = $project_query->execute();
break;
default:
throw new Exception("Unknown filter '{$this->filter}'!");
}
switch ($this->filter) {
case 'feed':
return $this->buildFeedResponse($nav, $projects);
case 'jump':
return $this->buildJumpResponse($nav);
case 'apps':
return $this->buildAppsResponse($nav);
default:
return $this->buildMainResponse($nav, $projects);
}
}
private function buildMainResponse($nav, array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$unbreak_panel = $this->buildUnbreakNowPanel();
$triage_panel = $this->buildNeedsTriagePanel($projects);
$tasks_panel = $this->buildTasksPanel();
} else {
$unbreak_panel = null;
$triage_panel = null;
$tasks_panel = null;
}
$flagged_panel = $this->buildFlaggedPanel();
$jump_panel = $this->buildJumpPanel();
$revision_panel = $this->buildRevisionPanel();
$app_panel = $this->buildAppPanel();
$audit_panel = $this->buildAuditPanel();
$commit_panel = $this->buildCommitPanel();
$content = array(
$app_panel,
$jump_panel,
$unbreak_panel,
$triage_panel,
$revision_panel,
$tasks_panel,
$flagged_panel,
$audit_panel,
$commit_panel,
);
$nav->appendChild($content);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Phabricator',
));
}
private function buildJumpResponse($nav) {
$request = $this->getRequest();
if ($request->isFormPost()) {
$jump = $request->getStr('jump');
$response = PhabricatorJumpNavHandler::jumpPostResponse($jump);
if ($response) {
return $response;
} else {
$query = new PhabricatorSearchQuery();
$query->setQuery($jump);
$query->save();
return id(new AphrontRedirectResponse())
->setURI('/search/'.$query->getQueryKey().'/');
}
}
$nav->appendChild($this->buildJumpPanel());
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Jump Nav',
));
}
private function buildFeedResponse($nav, array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$subnav = new AphrontSideNavFilterView();
$subnav->setBaseURI(new PhutilURI('/feed/'));
$subnav->addFilter('all', 'All Activity', '/feed/');
$subnav->addFilter('projects', 'My Projects');
$nav->appendChild($subnav);
$filter = $subnav->selectFilter($this->subfilter, 'all');
switch ($filter) {
case 'all':
$view = $this->buildFeedView(array());
break;
case 'projects':
if ($projects) {
$phids = mpull($projects, 'getPHID');
$view = $this->buildFeedView($phids);
} else {
$view = new AphrontErrorView();
$view->setSeverity(AphrontErrorView::SEVERITY_NODATA);
$view->setTitle('No Projects');
$view->appendChild('You have not joined any projects.');
}
break;
}
$subnav->appendChild($view);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'Feed',
));
}
private function buildUnbreakNowPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_UNBREAK_NOW);
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No "Unbreak Now!" Tasks',
'Nothing appears to be critically broken right now.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Unbreak Now!');
$panel->setCaption('Open tasks with "Unbreak Now!" priority.');
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/view/all/',
'class' => 'grey button',
),
"View All Unbreak Now \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
return $panel;
}
private function buildFlaggedPanel() {
$user = $this->getRequest()->getUser();
$flag_query = id(new PhabricatorFlagQuery())
->withOwnerPHIDs(array($user->getPHID()))
->needHandles(true)
->setLimit(10);
$flags = $flag_query->execute();
if (!$flags) {
return $this->renderMiniPanel(
'No Flags',
"You haven't flagged anything.");
}
$panel = new AphrontPanelView();
$panel->setHeader('Flagged Objects');
$panel->setCaption("Objects you've flagged.");
$flag_view = new PhabricatorFlagListView();
$flag_view->setFlags($flags);
$flag_view->setUser($user);
$panel->appendChild($flag_view);
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/flag/',
'class' => 'grey button',
),
"View All Flags \xC2\xBB"));
return $panel;
}
private function buildNeedsTriagePanel(array $projects) {
assert_instances_of($projects, 'PhabricatorProject');
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
if ($projects) {
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->withPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
$task_query->withProjects(mpull($projects, 'getPHID'));
$task_query->withAnyProject(true);
$task_query->setLimit(10);
$tasks = $task_query->execute();
} else {
$tasks = array();
}
if (!$tasks) {
return $this->renderMiniPanel(
'No "Needs Triage" Tasks',
'No tasks in projects you are a member of '.
'need triage.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Needs Triage');
$panel->setCaption(
'Open tasks with "Needs Triage" priority in '.
'projects you are a member of.');
$panel->addButton(
phutil_render_tag(
'a',
array(
// TODO: This should filter to just your projects' need-triage
// tasks?
'href' => '/maniphest/view/projecttriage/',
'class' => 'grey button',
),
"View All Triage \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
return $panel;
}
private function buildRevisionPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$revision_query = new DifferentialRevisionQuery();
$revision_query->withStatus(DifferentialRevisionQuery::STATUS_OPEN);
$revision_query->withResponsibleUsers(array($user_phid));
$revision_query->needRelationships(true);
// NOTE: We need to unlimit this query to hit the responsible user
// fast-path.
$revision_query->setLimit(null);
$revisions = $revision_query->execute();
list($active, $waiting) = DifferentialRevisionQuery::splitResponsible(
$revisions,
$user_phid);
if (!$active) {
return $this->renderMiniPanel(
'No Waiting Revisions',
'No revisions are waiting on you.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Revisions Waiting on You');
$panel->setCaption('Revisions waiting for you for review or commit.');
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/differential/',
'class' => 'button grey',
),
"View Active Revisions \xC2\xBB"));
$fields =
$revision_view = id(new DifferentialRevisionListView())
->setRevisions($active)
->setFields(DifferentialRevisionListView::getDefaultFields())
->setUser($user);
$phids = array_merge(
array($user_phid),
$revision_view->getRequiredHandlePHIDs());
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$revision_view->setHandles($handles);
$panel->appendChild($revision_view);
return $panel;
}
private function buildTasksPanel() {
$user = $this->getRequest()->getUser();
$user_phid = $user->getPHID();
$task_query = new ManiphestTaskQuery();
$task_query->withStatus(ManiphestTaskQuery::STATUS_OPEN);
$task_query->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY);
$task_query->withOwners(array($user_phid));
$task_query->setLimit(10);
$tasks = $task_query->execute();
if (!$tasks) {
return $this->renderMiniPanel(
'No Assigned Tasks',
'You have no assigned tasks.');
}
$panel = new AphrontPanelView();
$panel->setHeader('Assigned Tasks');
$panel->setCaption('Tasks assigned to you.');
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/maniphest/',
'class' => 'button grey',
),
"View Active Tasks \xC2\xBB"));
$panel->appendChild($this->buildTaskListView($tasks));
return $panel;
}
private function buildTaskListView(array $tasks) {
assert_instances_of($tasks, 'ManiphestTask');
$user = $this->getRequest()->getUser();
$phids = array_merge(
array_filter(mpull($tasks, 'getOwnerPHID')),
array_mergev(mpull($tasks, 'getProjectPHIDs')));
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view = new ManiphestTaskListView();
$view->setTasks($tasks);
$view->setUser($user);
$view->setHandles($handles);
return $view;
}
private function buildFeedView(array $phids) {
$request = $this->getRequest();
$user = $request->getUser();
$user_phid = $user->getPHID();
$feed_query = new PhabricatorFeedQuery();
+ $feed_query->setViewer($user);
if ($phids) {
$feed_query->setFilterPHIDs($phids);
}
- // TODO: All this limit stuff should probably be consolidated into the
- // feed query?
+ $pager = new AphrontIDPagerView();
+ $pager->readFromRequest($request);
+ $pager->setPageSize(200);
- $old_link = null;
- $new_link = null;
-
- $feed_query->setAfter($request->getStr('after'));
- $feed_query->setBefore($request->getStr('before'));
- $limit = 500;
-
- // Grab one more story than we intend to display so we can figure out
- // if we need to render an "Older Posts" link or not (with reasonable
- // accuracy, at least).
- $feed_query->setLimit($limit + 1);
- $feed = $feed_query->execute();
- $extra_row = (count($feed) == $limit + 1);
-
- $have_new = ($request->getStr('before')) ||
- ($request->getStr('after') && $extra_row);
-
- $have_old = ($request->getStr('after')) ||
- ($request->getStr('before') && $extra_row) ||
- (!$request->getStr('before') &&
- !$request->getStr('after') &&
- $extra_row);
- $feed = array_slice($feed, 0, $limit, $preserve_keys = true);
-
- if ($have_old) {
- $old_link = phutil_render_tag(
- 'a',
- array(
- 'href' => '?before='.end($feed)->getChronologicalKey(),
- 'class' => 'phabricator-feed-older-link',
- ),
- "Older Stories \xC2\xBB");
- }
- if ($have_new) {
- $new_link = phutil_render_tag(
- 'a',
- array(
- 'href' => '?after='.reset($feed)->getChronologicalKey(),
- 'class' => 'phabricator-feed-newer-link',
- ),
- "\xC2\xAB Newer Stories");
- }
+ $feed = $feed_query->executeWithPager($pager);
$builder = new PhabricatorFeedBuilder($feed);
$builder->setUser($user);
$feed_view = $builder->buildView();
return
''.
'
'.
'
Feed
'.
''.
$feed_view->render().
'
'.
- $new_link.
- $old_link.
+ $pager->render().
'
'.
'
';
}
private function buildJumpPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$uniq_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'phabricator-autofocus',
array(
'id' => $uniq_id,
));
require_celerity_resource('phabricator-jump-nav');
$doc_href = PhabricatorEnv::getDocLink('article/Jump_Nav_User_Guide.html');
$doc_link = phutil_render_tag(
'a',
array(
'href' => $doc_href,
),
'Jump Nav User Guide');
$jump_input = phutil_render_tag(
'input',
array(
'type' => 'text',
'class' => 'phabricator-jump-nav',
'name' => 'jump',
'id' => $uniq_id,
));
$jump_caption = phutil_render_tag(
'p',
array(
'class' => 'phabricator-jump-nav-caption',
),
'Enter the name of an object like D123 to quickly jump to '.
'it. See '.$doc_link.' or type help.');
$panel = new AphrontPanelView();
$panel->addClass('aphront-unpadded-panel-view');
$panel->appendChild(
phabricator_render_form(
$user,
array(
'action' => '/jump/',
'method' => 'POST',
'class' => 'phabricator-jump-nav-form',
),
$jump_input.
$jump_caption));
return $panel;
}
private function buildAppPanel() {
require_celerity_resource('phabricator-app-buttons-css');
$nav_buttons = array();
$nav_buttons[] = array(
'Differential',
'/differential/',
'differential',
'Code Reviews');
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$nav_buttons[] = array(
'Maniphest',
'/maniphest/',
'maniphest',
'Tasks');
$nav_buttons[] = array(
'Create Task',
'/maniphest/task/create/',
'create-task');
}
$nav_buttons[] = array(
'Upload File',
'/file/',
'upload-file',
'Share Files');
$nav_buttons[] = array(
'Create Paste',
'/paste/',
'create-paste',
'Share Text');
if (PhabricatorEnv::getEnvConfig('phriction.enabled')) {
$nav_buttons[] = array(
'Phriction',
'/w/',
'phriction',
'Browse Wiki');
}
$nav_buttons[] = array(
'Diffusion',
'/diffusion/',
'diffusion',
'Browse Code');
$nav_buttons[] = array(
'Audit',
'/audit/',
'audit',
'Audit Code');
$view = new AphrontNullView();
$view->appendChild('');
return $view;
}
private function renderMiniPanel($title, $body) {
$panel = new AphrontMiniPanelView();
$panel->appendChild(
phutil_render_tag(
'p',
array(
),
''.$title.': '.$body));
return $panel;
}
public function buildAuditPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($user);
$query = new PhabricatorAuditQuery();
$query->withAuditorPHIDs($phids);
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
$query->withAwaitingUser($user);
$query->needCommitData(true);
$query->setLimit(10);
$audits = $query->execute();
$commits = $query->getCommits();
if (!$audits) {
return $this->renderMinipanel(
'No Audits',
'No commits are waiting for you to audit them.');
}
$view = new PhabricatorAuditListView();
$view->setAudits($audits);
$view->setCommits($commits);
$phids = $view->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Audits');
$panel->setCaption('Commits awaiting your audit.');
$panel->appendChild($view);
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Active Audits \xC2\xBB"));
return $panel;
}
public function buildCommitPanel() {
$request = $this->getRequest();
$user = $request->getUser();
$phids = array($user->getPHID());
$query = new PhabricatorAuditCommitQuery();
$query->withAuthorPHIDs($phids);
$query->withStatus(PhabricatorAuditQuery::STATUS_OPEN);
$query->needCommitData(true);
$query->setLimit(10);
$commits = $query->execute();
if (!$commits) {
return $this->renderMinipanel(
'No Problem Commits',
'No one has raised concerns with your commits.');
}
$view = new PhabricatorAuditCommitListView();
$view->setCommits($commits);
$view->setUser($user);
$phids = $view->getRequiredHandlePHIDs();
$handles = id(new PhabricatorObjectHandleData($phids))->loadHandles();
$view->setHandles($handles);
$panel = new AphrontPanelView();
$panel->setHeader('Problem Commits');
$panel->setCaption('Commits which auditors have raised concerns about.');
$panel->appendChild($view);
$panel->addButton(
phutil_render_tag(
'a',
array(
'href' => '/audit/',
'class' => 'button grey',
),
"View Problem Commits \xC2\xBB"));
return $panel;
}
public function buildAppsResponse(AphrontSideNavFilterView $nav) {
$user = $this->getRequest()->getUser();
$apps = array(
array(
'/repository/',
'Repositories',
'Configure tracked source code repositories.',
),
array(
'/herald/',
'Herald',
'Create notification rules. Watch for danger!',
),
array(
'/file/',
'Files',
'Upload and download files. Blob store for Pokemon pictures.',
),
array(
'/project/',
'Projects',
'Group stuff into big piles.',
),
array(
'/vote/',
'Slowvote',
'Create polls. Design by committee.',
),
array(
'/countdown/',
'Countdown',
'Count down to events. Utilize the full capabilities of your ALU.',
),
array(
'/people/',
'People',
'User directory. Sort of a social utility.',
),
array(
'/owners/',
'Owners',
'Keep track of who owns code. Adopt today!',
),
array(
'/conduit/',
'Conduit API Console',
'Web console for Conduit API.',
),
array(
'/daemon/',
'Daemon Console',
'Offline process management.',
),
array(
'/mail/',
'MetaMTA',
'Manage mail delivery. Yo dawg, we heard you like MTAs.',
array(
'admin' => true,
),
),
array(
'/phid/',
'PHID Manager',
'Debugging tool for PHIDs.',
),
array(
'/xhpast/',
'XHPAST',
'Web interface to PHP AST tool. Lex XHP AST & CTS FYI, LOL.',
),
array(
'http://www.phabricator.com/docs/phabricator/',
'Phabricator Ducks',
'Oops, that should say "Docs".',
array(
'new' => true,
),
),
array(
'http://www.phabricator.com/docs/arcanist/',
'Arcanist Docs',
'Words have never been so finely crafted.',
array(
'new' => true,
),
),
array(
'http://www.phabricator.com/docs/libphutil/',
'libphutil Docs',
'Soothing prose; seductive poetry.',
array(
'new' => true,
),
),
array(
'http://www.phabricator.com/docs/javelin/',
'Javelin Docs',
'O, what noble scribe hath penned these words?',
array(
'new' => true,
),
),
array(
'/uiexample/',
'UI Examples',
'Phabricator UI elements. A gallery of modern art.',
array(
'new' => true,
),
),
);
$out = array();
foreach ($apps as $app) {
if (empty($app[3])) {
$app[3] = array();
}
$app[3] += array(
'admin' => false,
'new' => false,
);
list($href, $name, $desc, $options) = $app;
if ($options['admin'] && !$user->getIsAdmin()) {
continue;
}
$link = phutil_render_tag(
'a',
array(
'href' => $href,
'target' => $options['new'] ? '_blank' : null,
),
phutil_escape_html($name));
$out[] =
''.
'
'.$link.'
'.
'
'.phutil_escape_html($desc).'
'.
'
';
}
require_celerity_resource('phabricator-directory-css');
$out =
''.
implode("\n", $out).
'
';
$nav->appendChild($out);
return $this->buildStandardPageResponse(
$nav,
array(
'title' => 'More Stuff',
));
}
}
diff --git a/src/applications/feed/PhabricatorFeedQuery.php b/src/applications/feed/PhabricatorFeedQuery.php
index 00ca3fcbec..f78dd74380 100644
--- a/src/applications/feed/PhabricatorFeedQuery.php
+++ b/src/applications/feed/PhabricatorFeedQuery.php
@@ -1,113 +1,89 @@
filterPHIDs = $phids;
return $this;
}
- public function setLimit($limit) {
- $this->limit = $limit;
- return $this;
- }
+ public function loadPage() {
- public function setAfter($after) {
- $this->after = $after;
- return $this;
- }
+ $story_table = new PhabricatorFeedStoryData();
+ $conn = $story_table->establishConnection('r');
- public function setBefore($before) {
- $this->before = $before;
- return $this;
+ $data = queryfx_all(
+ $conn,
+ 'SELECT story.* FROM %T story %Q %Q %Q %Q %Q',
+ $story_table->getTableName(),
+ $this->buildJoinClause($conn),
+ $this->buildWhereClause($conn),
+ $this->buildGroupClause($conn),
+ $this->buildOrderClause($conn),
+ $this->buildLimitClause($conn));
+
+ $results = PhabricatorFeedStory::loadAllFromRows($data);
+
+ return $this->processResults($results);
}
- public function execute() {
+ private function buildJoinClause(AphrontDatabaseConnection $conn_r) {
+ // NOTE: We perform this join unconditionally (even if we have no filter
+ // PHIDs) to omit rows which have no story references. These story data
+ // rows are notifications or realtime alerts.
$ref_table = new PhabricatorFeedStoryReference();
- $story_table = new PhabricatorFeedStoryData();
-
- $conn = $story_table->establishConnection('r');
+ return qsprintf(
+ $conn_r,
+ 'JOIN %T ref ON ref.chronologicalKey = story.chronologicalKey',
+ $ref_table->getTableName());
+ }
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
$where = array();
+
if ($this->filterPHIDs) {
$where[] = qsprintf(
- $conn,
+ $conn_r,
'ref.objectPHID IN (%Ls)',
$this->filterPHIDs);
}
- // For "before" queries, we can just add a constraint to the WHERE clause.
- // For "after" queries, we must also reverse the result ordering, since
- // otherwise we'll always grab the first page of results if there's a limit.
- // After MySQL applies the limit, we reverse the page in PHP (below) to
- // ensure consistent ordering.
-
- $order = 'DESC';
-
- if ($this->after) {
- $where[] = qsprintf(
- $conn,
- 'ref.chronologicalKey > %s',
- $this->after);
- $order = 'ASC';
- }
+ $where[] = $this->buildPagingClause($conn_r);
- if ($this->before) {
- $where[] = qsprintf(
- $conn,
- 'ref.chronologicalKey < %s',
- $this->before);
- }
+ return $this->formatWhereClause($where);
+ }
- if ($where) {
- $where = 'WHERE ('.implode(') AND (', $where).')';
- } else {
- $where = '';
- }
+ private function buildGroupClause(AphrontDatabaseConnection $conn_r) {
+ return qsprintf(
+ $conn_r,
+ 'GROUP BY ref.chronologicalKey');
+ }
- $data = queryfx_all(
- $conn,
- 'SELECT story.* FROM %T ref
- JOIN %T story ON ref.chronologicalKey = story.chronologicalKey
- %Q
- GROUP BY ref.chronologicalKey
- ORDER BY ref.chronologicalKey %Q
- LIMIT %d',
- $ref_table->getTableName(),
- $story_table->getTableName(),
- $where,
- $order,
- $this->limit);
-
- if ($order != 'DESC') {
- // If we did order ASC to pull 'after' data, reverse the result set so
- // that stories are returned in a consistent (descending) order.
- $data = array_reverse($data);
- }
+ protected function getPagingColumn() {
+ return 'ref.chronologicalKey';
+ }
- return PhabricatorFeedStory::loadAllFromRows($data);
+ protected function getPagingValue($item) {
+ return $item->getChronologicalKey();
}
}
diff --git a/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php b/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
index af5ba6b5da..e2f1dac839 100644
--- a/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
+++ b/src/applications/feed/controller/PhabricatorFeedPublicStreamController.php
@@ -1,53 +1,52 @@
getRequest();
+ $viewer = $request->getUser();
$query = new PhabricatorFeedQuery();
+ $query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder
->setFramed(true)
- ->setUser($request->getUser());
+ ->setUser($viewer);
$view = $builder->buildView();
return $this->buildStandardPageResponse(
$view,
array(
'title' => 'Public Feed',
'public' => true,
));
}
}
diff --git a/src/applications/feed/story/PhabricatorFeedStory.php b/src/applications/feed/story/PhabricatorFeedStory.php
index eb5e54a196..979e176612 100644
--- a/src/applications/feed/story/PhabricatorFeedStory.php
+++ b/src/applications/feed/story/PhabricatorFeedStory.php
@@ -1,197 +1,212 @@
List of @{class:PhabricatorFeedStoryData} rows from the
* database.
* @return list List of @{class:PhabricatorFeedStory}
* objects.
* @task load
*/
public static function loadAllFromRows(array $rows) {
$stories = array();
$data = id(new PhabricatorFeedStoryData())->loadAllFromArray($rows);
foreach ($data as $story_data) {
$class = $story_data->getStoryType();
$ok = false;
try {
$ok = is_subclass_of($class, 'PhabricatorFeedStory');
} catch (PhutilMissingSymbolException $ex) {
$ok = false;
}
// If the story type isn't a valid class or isn't a subclass of
// PhabricatorFeedStory, load it as PhabricatorFeedStoryUnknown.
if (!$ok) {
$class = 'PhabricatorFeedStoryUnknown';
}
$key = $story_data->getChronologicalKey();
$stories[$key] = newv($class, array($story_data));
}
return $stories;
}
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return PhabricatorEnv::getEnvConfig('feed.public')
+ ? PhabricatorPolicies::POLICY_PUBLIC
+ : PhabricatorPolicies::POLICY_USER;
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
public function setPrimaryObjectPHID($primary_object_phid) {
$this->primaryObjectPHID = $primary_object_phid;
return $this;
}
public function getPrimaryObjectPHID() {
return $this->primaryObjectPHID;
}
final public function __construct(PhabricatorFeedStoryData $data) {
$this->data = $data;
}
abstract public function renderView();
// TODO: Make abstract once all subclasses implement it.
public function renderNotificationView() {
return id(new PhabricatorFeedStoryUnknown($this->data))
->renderNotificationView();
}
public function getRequiredHandlePHIDs() {
return array();
}
public function setHasViewed($has_viewed) {
$this->hasViewed = $has_viewed;
return $this;
}
public function getHasViewed() {
return $this->hasViewed;
}
public function getRequiredObjectPHIDs() {
return array();
}
final public function setFramed($framed) {
$this->framed = $framed;
return $this;
}
final public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
final protected function getHandles() {
return $this->handles;
}
final protected function getHandle($phid) {
if (isset($this->handles[$phid])) {
if ($this->handles[$phid] instanceof PhabricatorObjectHandle) {
return $this->handles[$phid];
}
}
$handle = new PhabricatorObjectHandle();
$handle->setPHID($phid);
$handle->setName("Unloaded Object '{$phid}'");
return $handle;
}
final public function getStoryData() {
return $this->data;
}
final public function getEpoch() {
return $this->getStoryData()->getEpoch();
}
final public function getChronologicalKey() {
return $this->getStoryData()->getChronologicalKey();
}
final protected function renderHandleList(array $phids) {
$list = array();
foreach ($phids as $phid) {
$list[] = $this->linkTo($phid);
}
return implode(', ', $list);
}
final protected function linkTo($phid) {
$handle = $this->getHandle($phid);
// NOTE: We render our own link here to customize the styling and add
// the '_top' target for framed feeds.
return phutil_render_tag(
'a',
array(
'href' => $handle->getURI(),
'target' => $this->framed ? '_top' : null,
),
phutil_escape_html($handle->getLinkName()));
}
final protected function renderString($str) {
return ''.phutil_escape_html($str).'';
}
final protected function renderSummary($text, $len = 128) {
if ($len) {
$text = phutil_utf8_shorten($text, $len);
}
$text = phutil_escape_html($text);
$text = str_replace("\n", '
', $text);
return $text;
}
public function getNotificationAggregations() {
return array();
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 3a313dfc17..491f8a0a97 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,226 +1,229 @@
username = idx($data, 'username');
$this->page = idx($data, 'page');
}
public function processRequest() {
$viewer = $this->getRequest()->getUser();
$user = id(new PhabricatorUser())->loadOneWhere(
'userName = %s',
$this->username);
if (!$user) {
return new Aphront404Response();
}
require_celerity_resource('phabricator-profile-css');
$profile = id(new PhabricatorUserProfile())->loadOneWhere(
'userPHID = %s',
$user->getPHID());
if (!$profile) {
$profile = new PhabricatorUserProfile();
}
$username = phutil_escape_uri($user->getUserName());
$nav = new AphrontSideNavFilterView();
$nav->setBaseURI(new PhutilURI('/p/'.$username.'/'));
$nav->addFilter('feed', 'Feed');
$nav->addFilter('about', 'About');
$nav->addSpacer();
$nav->addLabel('Activity');
$external_arrow = "\xE2\x86\x97";
$nav->addFilter(
null,
"Revisions {$external_arrow}",
'/differential/filter/revisions/'.$username.'/');
$nav->addFilter(
null,
"Tasks {$external_arrow}",
'/maniphest/view/action/?users='.$user->getPHID());
$nav->addFilter(
null,
"Commits {$external_arrow}",
'/audit/view/author/'.$username.'/');
$oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere(
'userID = %d',
$user->getID());
$oauths = mpull($oauths, null, 'getOAuthProvider');
$providers = PhabricatorOAuthProvider::getAllProviders();
$added_spacer = false;
foreach ($providers as $provider) {
if (!$provider->isProviderEnabled()) {
continue;
}
$provider_key = $provider->getProviderKey();
if (!isset($oauths[$provider_key])) {
continue;
}
$name = $provider->getProviderName().' Profile';
$href = $oauths[$provider_key]->getAccountURI();
if ($href) {
if (!$added_spacer) {
$nav->addSpacer();
$nav->addLabel('Linked Accounts');
$added_spacer = true;
}
$nav->addFilter(null, $name.' '.$external_arrow, $href);
}
}
$this->page = $nav->selectFilter($this->page, 'feed');
switch ($this->page) {
case 'feed':
$content = $this->renderUserFeed($user);
break;
case 'about':
$content = $this->renderBasicInformation($user, $profile);
break;
default:
throw new Exception("Unknown page '{$this->page}'!");
}
$picture = $user->loadProfileImageURI();
$header = new PhabricatorProfileHeaderView();
$header
->setProfilePicture($picture)
->setName($user->getUserName().' ('.$user->getRealName().')')
->setDescription($profile->getTitle());
if ($user->getIsDisabled()) {
$header->setStatus('Disabled');
} else {
$statuses = id(new PhabricatorUserStatus())->loadCurrentStatuses(
array($user->getPHID()));
if ($statuses) {
$header->setStatus(reset($statuses)->getStatusDescription($viewer));
}
}
$header->appendChild($nav);
$nav->appendChild(
''.$content.'
');
if ($user->getPHID() == $viewer->getPHID()) {
$nav->addSpacer();
$nav->addFilter(null, 'Edit Profile...', '/settings/page/profile/');
}
if ($viewer->getIsAdmin()) {
$nav->addSpacer();
$nav->addFilter(
null,
'Administrate User...',
'/people/edit/'.$user->getID().'/');
}
return $this->buildStandardPageResponse(
$header,
array(
'title' => $user->getUsername(),
));
}
private function renderBasicInformation($user, $profile) {
$blurb = nonempty(
$profile->getBlurb(),
'//Nothing is known about this rare specimen.//');
$engine = PhabricatorMarkupEngine::newProfileMarkupEngine();
$blurb = $engine->markupText($blurb);
$viewer = $this->getRequest()->getUser();
$content =
'
| PHID |
'.phutil_escape_html($user->getPHID()).' |
| User Since |
'.phabricator_datetime($user->getDateCreated(),
$viewer).
' |
';
$content .=
'';
return $content;
}
private function renderUserFeed(PhabricatorUser $user) {
+ $viewer = $this->getRequest()->getUser();
+
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$user->getPHID(),
));
+ $query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
- $builder->setUser($this->getRequest()->getUser());
+ $builder->setUser($viewer);
$view = $builder->buildView();
return
'';
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index d0ec8d2988..ed51aa4f07 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,372 +1,367 @@
id = idx($data, 'id');
$this->page = idx($data, 'page');
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$project = id(new PhabricatorProject())->load($this->id);
if (!$project) {
return new Aphront404Response();
}
$profile = $project->loadProfile();
if (!$profile) {
$profile = new PhabricatorProjectProfile();
}
$picture = $profile->loadProfileImageURI();
$members = mpull($project->loadAffiliations(), null, 'getUserPHID');
$nav_view = new AphrontSideNavFilterView();
$uri = new PhutilURI('/project/view/'.$project->getID().'/');
$nav_view->setBaseURI($uri);
$external_arrow = "\xE2\x86\x97";
$tasks_uri = '/maniphest/view/all/?projects='.$project->getPHID();
$slug = PhabricatorSlug::normalize($project->getName());
$phriction_uri = '/w/projects/'.$slug;
$edit_uri = '/project/edit/'.$project->getID().'/';
$nav_view->addFilter('dashboard', 'Dashboard');
$nav_view->addSpacer();
$nav_view->addFilter('feed', 'Feed');
$nav_view->addFilter(null, 'Tasks '.$external_arrow, $tasks_uri);
$nav_view->addFilter(null, 'Wiki '.$external_arrow, $phriction_uri);
$nav_view->addFilter('people', 'People');
$nav_view->addFilter('about', 'About');
$nav_view->addSpacer();
$nav_view->addFilter(null, "Edit Project\xE2\x80\xA6", $edit_uri);
$this->page = $nav_view->selectFilter($this->page, 'dashboard');
require_celerity_resource('phabricator-profile-css');
switch ($this->page) {
case 'dashboard':
$content = $this->renderTasksPage($project, $profile);
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(
array(
$project->getPHID(),
));
+ $query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
$content .= $this->renderStories($stories);
break;
case 'about':
$content = $this->renderAboutPage($project, $profile);
break;
case 'people':
$content = $this->renderPeoplePage($project, $profile);
break;
case 'feed':
$content = $this->renderFeedPage($project, $profile);
break;
default:
throw new Exception("Unimplemented filter '{$this->page}'.");
}
$content = ''.$content.'
';
$nav_view->appendChild($content);
$header = new PhabricatorProfileHeaderView();
$header->setName($project->getName());
$header->setDescription(
phutil_utf8_shorten($profile->getBlurb(), 1024));
$header->setProfilePicture($picture);
$action = null;
if (empty($members[$user->getPHID()])) {
$action = phabricator_render_form(
$user,
array(
'action' => '/project/update/'.$project->getID().'/join/',
'method' => 'post',
),
phutil_render_tag(
'button',
array(
'class' => 'green',
),
'Join Project'));
} else {
$action = javelin_render_tag(
'a',
array(
'href' => '/project/update/'.$project->getID().'/leave/',
'sigil' => 'workflow',
'class' => 'grey button',
),
'Leave Project...');
}
$header->addAction($action);
$header->appendChild($nav_view);
return $this->buildStandardPageResponse(
$header,
array(
'title' => $project->getName().' Project',
));
}
private function renderAboutPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$viewer = $this->getRequest()->getUser();
$blurb = $profile->getBlurb();
$blurb = phutil_escape_html($blurb);
$blurb = str_replace("\n", '
', $blurb);
$phids = array_merge(
array($project->getAuthorPHID()),
$project->getSubprojectPHIDs()
);
$phids = array_unique($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$timestamp = phabricator_datetime($project->getDateCreated(), $viewer);
$about =
'
| Creator |
'.$handles[$project->getAuthorPHID()]->renderLink().' |
| Created |
'.$timestamp.' |
| PHID |
'.phutil_escape_html($project->getPHID()).' |
| Blurb |
'.$blurb.' |
';
if ($project->getSubprojectPHIDs()) {
$table = $this->renderSubprojectTable(
$handles,
$project->getSubprojectPHIDs());
$subproject_list = $table->render();
} else {
$subproject_list = 'No subprojects.
';
}
$about .=
''.
''.
'
'.
$subproject_list.
'
'.
'
';
return $about;
}
private function renderPeoplePage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$affiliations = $project->loadAffiliations();
$phids = mpull($affiliations, 'getUserPHID');
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$affiliated = array();
foreach ($affiliations as $affiliation) {
$user = $handles[$affiliation->getUserPHID()]->renderLink();
$role = phutil_escape_html($affiliation->getRole());
$affiliated[] = ''.$user.' — '.$role.'';
}
if ($affiliated) {
$affiliated = ''.implode("\n", $affiliated).'
';
} else {
$affiliated = 'No one is affiliated with this project.
';
}
return
''.
''.
'
'.
$affiliated.
'
'.
'
';
}
private function renderFeedPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = new PhabricatorFeedQuery();
$query->setFilterPHIDs(array($project->getPHID()));
+ $query->setViewer($this->getRequest()->getUser());
$stories = $query->execute();
if (!$stories) {
return 'There are no stories about this project.';
}
- $query = new PhabricatorFeedQuery();
- $query->setFilterPHIDs(
- array(
- $project->getPHID(),
- ));
- $stories = $query->execute();
-
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
''.
''.
'
'.
$view->render().
'
'.
'
';
}
private function renderTasksPage(
PhabricatorProject $project,
PhabricatorProjectProfile $profile) {
$query = id(new ManiphestTaskQuery())
->withProjects(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_filter($phids);
$handles = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$task_views = array();
foreach ($tasks as $task) {
$view = id(new ManiphestTaskSummaryView())
->setTask($task)
->setHandles($handles)
->setUser($this->getRequest()->getUser());
$task_views[] = $view->render();
}
if (empty($tasks)) {
$task_views = 'No open tasks.';
} else {
$task_views = implode('', $task_views);
}
$open = number_format($count);
$more_link = phutil_render_tag(
'a',
array(
'href' => '/maniphest/view/all/?projects='.$project->getPHID(),
),
"View All Open Tasks \xC2\xBB");
$content =
'
'.
'
'.
$task_views.
'
'.
$more_link.
'
'.
'
';
return $content;
}
private function renderSubprojectTable(
array $handles,
array $subprojects_phids) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$rows = array();
foreach ($subprojects_phids as $subproject_phid) {
$phid = $handles[$subproject_phid]->getPHID();
$rows[] = array(
phutil_escape_html($handles[$phid]->getFullName()),
phutil_render_tag(
'a',
array(
'class' => 'small grey button',
'href' => $handles[$phid]->getURI(),
),
'View Project Profile'),
);
}
$table = new AphrontTableView($rows);
$table->setHeaders(
array(
'Name',
'',
));
$table->setColumnClasses(
array(
'pri',
'action right',
));
return $table;
}
}
diff --git a/src/infrastructure/query/policy/PhabricatorIDPagedPolicyQuery.php b/src/infrastructure/query/policy/PhabricatorIDPagedPolicyQuery.php
index 258270b508..c0b924c3a3 100644
--- a/src/infrastructure/query/policy/PhabricatorIDPagedPolicyQuery.php
+++ b/src/infrastructure/query/policy/PhabricatorIDPagedPolicyQuery.php
@@ -1,129 +1,129 @@
getID();
}
protected function nextPage(array $page) {
if ($this->beforeID) {
$this->beforeID = $this->getPagingValue(head($page));
} else {
$this->afterID = $this->getPagingValue(last($page));
}
}
final public function setAfterID($object_id) {
$this->afterID = $object_id;
return $this;
}
final public function setBeforeID($object_id) {
$this->beforeID = $object_id;
return $this;
}
final protected function buildLimitClause(AphrontDatabaseConnection $conn_r) {
if ($this->getLimit()) {
return qsprintf($conn_r, 'LIMIT %d', $this->getLimit());
} else {
return '';
}
}
final protected function buildPagingClause(
AphrontDatabaseConnection $conn_r) {
if ($this->beforeID) {
return qsprintf(
$conn_r,
- '%C > %s',
+ '%Q > %s',
$this->getPagingColumn(),
$this->beforeID);
} else if ($this->afterID) {
return qsprintf(
$conn_r,
- '%C < %s',
+ '%Q < %s',
$this->getPagingColumn(),
$this->afterID);
}
return null;
}
final protected function buildOrderClause(AphrontDatabaseConnection $conn_r) {
if ($this->beforeID) {
return qsprintf(
$conn_r,
- 'ORDER BY %C ASC',
+ 'ORDER BY %Q ASC',
$this->getPagingColumn());
} else {
return qsprintf(
$conn_r,
- 'ORDER BY %C DESC',
+ 'ORDER BY %Q DESC',
$this->getPagingColumn());
}
}
final protected function processResults(array $results) {
if ($this->beforeID) {
$results = array_reverse($results, $preserve_keys = true);
}
return $results;
}
final public function executeWithPager(AphrontIDPagerView $pager) {
$this->setLimit($pager->getPageSize() + 1);
if ($pager->getAfterID()) {
$this->setAfterID($pager->getAfterID());
} else if ($pager->getBeforeID()) {
$this->setBeforeID($pager->getBeforeID());
}
$results = $this->execute();
$sliced_results = $pager->sliceResults($results);
if ($this->beforeID || (count($results) > $pager->getPageSize())) {
$pager->setNextPageID($this->getPagingValue(last($sliced_results)));
}
if ($this->afterID ||
($this->beforeID && (count($results) > $pager->getPageSize()))) {
$pager->setPrevPageID($this->getPagingValue(head($sliced_results)));
}
return $sliced_results;
}
}