Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php
index f26f4603e1..adc272a125 100644
--- a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php
+++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php
@@ -1,90 +1,91 @@
<?php
final class PhabricatorFavoritesMainMenuBarExtension
extends PhabricatorMainMenuBarExtension {
const MAINMENUBARKEY = 'favorites';
public function isExtensionEnabledForViewer(PhabricatorUser $viewer) {
return PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorFavoritesApplication',
$viewer);
}
public function getExtensionOrder() {
return 1100;
}
public function buildMainMenus() {
$viewer = $this->getViewer();
$dropdown = $this->newDropdown($viewer);
if (!$dropdown) {
- return null;
+ return array();
}
$favorites_menu = id(new PHUIButtonView())
->setTag('a')
->setHref('#')
->setIcon('fa-star')
->addClass('phabricator-core-user-menu')
->setNoCSS(true)
->setDropdown(true)
->setDropdownMenu($dropdown)
->setAuralLabel(pht('Favorites Menu'));
return array(
$favorites_menu,
);
}
private function newDropdown(PhabricatorUser $viewer) {
$applications = id(new PhabricatorApplicationQuery())
->setViewer($viewer)
->withClasses(array('PhabricatorFavoritesApplication'))
->withInstalled(true)
->execute();
$favorites = head($applications);
if (!$favorites) {
return null;
}
$menu_engine = id(new PhabricatorFavoritesProfileMenuEngine())
->setViewer($viewer)
->setProfileObject($favorites)
->setCustomPHID($viewer->getPHID());
$controller = $this->getController();
if ($controller) {
$menu_engine->setController($controller);
}
- $filter_view = $menu_engine->buildNavigation();
+ $filter_view = $menu_engine->newProfileMenuItemViewList()
+ ->newNavigationView();
$menu_view = $filter_view->getMenu();
$item_views = $menu_view->getItems();
$view = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($item_views as $item) {
$action = id(new PhabricatorActionView())
->setName($item->getName())
->setHref($item->getHref())
->setType($item->getType());
$view->addAction($action);
}
if ($viewer->isLoggedIn()) {
$view->addAction(
id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER));
$view->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Favorites'))
->setHref('/favorites/menu/configure/'));
}
return $view;
}
}
diff --git a/src/applications/home/controller/PhabricatorHomeController.php b/src/applications/home/controller/PhabricatorHomeController.php
index 9cd3b5b91d..e47d513946 100644
--- a/src/applications/home/controller/PhabricatorHomeController.php
+++ b/src/applications/home/controller/PhabricatorHomeController.php
@@ -1,44 +1,4 @@
<?php
-abstract class PhabricatorHomeController extends PhabricatorController {
-
- private $home;
- private $profileMenu;
-
- public function buildApplicationMenu() {
- $menu = $this->newApplicationMenu();
-
- $profile_menu = $this->getProfileMenu();
- if ($profile_menu) {
- $menu->setProfileMenu($profile_menu);
- }
-
- return $menu;
- }
-
- protected function getProfileMenu() {
- if (!$this->profileMenu) {
- $viewer = $this->getViewer();
- $applications = id(new PhabricatorApplicationQuery())
- ->setViewer($viewer)
- ->withClasses(array('PhabricatorHomeApplication'))
- ->withInstalled(true)
- ->execute();
- $home = head($applications);
- if (!$home) {
- return null;
- }
-
- $engine = id(new PhabricatorHomeProfileMenuEngine())
- ->setViewer($viewer)
- ->setController($this)
- ->setProfileObject($home)
- ->setCustomPHID($viewer->getPHID());
-
- $this->profileMenu = $engine->buildNavigation();
- }
-
- return $this->profileMenu;
- }
-
-}
+abstract class PhabricatorHomeController
+ extends PhabricatorController {}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php
index f3e95eeb66..f98970ef73 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileBadgesController.php
@@ -1,127 +1,128 @@
<?php
final class PhabricatorPeopleProfileBadgesController
extends PhabricatorPeopleProfileController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProfileImage(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$class = 'PhabricatorBadgesApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return new Aphront404Response();
}
$this->setUser($user);
$title = array(pht('Badges'), $user->getUsername());
$header = $this->buildProfileHeader();
$badges = $this->buildBadgesView($user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Badges'));
$crumbs->setBorder(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_BADGES);
+ $nav = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_BADGES);
// Best option?
$badges = id(new PhabricatorBadgesQuery())
->setViewer($viewer)
->withStatuses(array(
PhabricatorBadgesBadge::STATUS_ACTIVE,
))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->setLimit(1)
->execute();
$button = id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-plus')
->setText(pht('Award Badge'))
->setWorkflow(true)
->setHref('/badges/award/'.$user->getID().'/');
if ($badges) {
$header->addActionLink($button);
}
$view = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setFooter(array(
$this->buildBadgesView($user)
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function buildBadgesView(PhabricatorUser $user) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$pager = id(new AphrontCursorPagerView())
->readFromRequest($request);
$query = id(new PhabricatorBadgesAwardQuery())
->setViewer($viewer)
->withRecipientPHIDs(array($user->getPHID()))
->withBadgeStatuses(array(PhabricatorBadgesBadge::STATUS_ACTIVE));
$awards = $query->executeWithCursorPager($pager);
if ($awards) {
$flex = new PHUIBadgeBoxView();
foreach ($awards as $award) {
$badge = $award->getBadge();
$awarder_info = array();
$awarder_phid = $award->getAwarderPHID();
$awarder_handle = $viewer->renderHandle($awarder_phid);
$awarded_date = phabricator_date($award->getDateCreated(), $viewer);
$awarder_info = pht(
'Awarded by %s',
$awarder_handle->render());
$item = id(new PHUIBadgeView())
->setIcon($badge->getIcon())
->setHeader($badge->getName())
->setSubhead($badge->getFlavor())
->setQuality($badge->getQuality())
->setHref($badge->getViewURI())
->addByLine($awarder_info)
->addByLine($awarded_date);
$flex->addItem($item);
}
} else {
$flex = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('User has not been awarded any badges.'));
}
return array(
$flex,
$pager,
);
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php
index c18c5f4d96..430e11311e 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileCommitsController.php
@@ -1,70 +1,71 @@
<?php
final class PhabricatorPeopleProfileCommitsController
extends PhabricatorPeopleProfileController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProfile(true)
->needProfileImage(true)
->needAvailability(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$class = 'PhabricatorDiffusionApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return new Aphront404Response();
}
$this->setUser($user);
$title = array(pht('Recent Commits'), $user->getUsername());
$header = $this->buildProfileHeader();
$commits = $this->buildCommitsView($user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Recent Commits'));
$crumbs->setBorder(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_COMMITS);
+ $nav = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_COMMITS);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setFooter(array(
$commits,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function buildCommitsView(PhabricatorUser $user) {
$viewer = $this->getViewer();
$commits = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withAuthorPHIDs(array($user->getPHID()))
->needCommitData(true)
->setLimit(100)
->execute();
$list = id(new DiffusionCommitListView())
->setViewer($viewer)
->setCommits($commits)
->setNoDataString(pht('No recent commits.'));
return $list;
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileController.php b/src/applications/people/controller/PhabricatorPeopleProfileController.php
index 91afda123b..1d6f0fc74c 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileController.php
@@ -1,141 +1,132 @@
<?php
abstract class PhabricatorPeopleProfileController
extends PhabricatorPeopleController {
private $user;
- private $profileMenu;
public function shouldRequireAdmin() {
return false;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function getUser() {
return $this->user;
}
- public function buildApplicationMenu() {
- $menu = $this->newApplicationMenu();
-
- $profile_menu = $this->getProfileMenu();
- if ($profile_menu) {
- $menu->setProfileMenu($profile_menu);
- }
-
- return $menu;
- }
-
- protected function getProfileMenu() {
- if (!$this->profileMenu) {
- $user = $this->getUser();
- if ($user) {
- $viewer = $this->getViewer();
-
- $engine = id(new PhabricatorPeopleProfileMenuEngine())
- ->setViewer($viewer)
- ->setProfileObject($user);
-
- $this->profileMenu = $engine->buildNavigation();
- }
- }
-
- return $this->profileMenu;
- }
-
protected function buildApplicationCrumbs() {
$crumbs = parent::buildApplicationCrumbs();
$user = $this->getUser();
if ($user) {
$crumbs->addTextCrumb(
$user->getUsername(),
urisprintf('/p/%s/', $user->getUsername()));
}
return $crumbs;
}
public function buildProfileHeader() {
$user = $this->user;
$viewer = $this->getViewer();
$profile = $user->loadUserProfile();
$picture = $user->getProfileImageURI();
$profile_icon = PhabricatorPeopleIconSet::getIconIcon($profile->getIcon());
$profile_title = $profile->getDisplayTitle();
$tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE);
$tags = array();
if ($user->getIsAdmin()) {
$tags[] = id(clone $tag)
->setName(pht('Administrator'))
->setColor('blue');
}
// "Disabled" gets a stronger status tag below.
if (!$user->getIsApproved()) {
$tags[] = id(clone $tag)
->setName('Not Approved')
->setColor('yellow');
}
if ($user->getIsSystemAgent()) {
$tags[] = id(clone $tag)
->setName(pht('Bot'))
->setColor('orange');
}
if ($user->getIsMailingList()) {
$tags[] = id(clone $tag)
->setName(pht('Mailing List'))
->setColor('orange');
}
if (!$user->getIsEmailVerified()) {
$tags[] = id(clone $tag)
->setName(pht('Email Not Verified'))
->setColor('violet');
}
$header = id(new PHUIHeaderView())
->setHeader($user->getFullName())
->setImage($picture)
->setProfileHeader(true)
->addClass('people-profile-header');
foreach ($tags as $tag) {
$header->addTag($tag);
}
require_celerity_resource('project-view-css');
if ($user->getIsDisabled()) {
$header->setStatus('fa-ban', 'red', pht('Disabled'));
} else {
$header->setStatus($profile_icon, 'bluegrey', $profile_title);
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$user,
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_edit) {
$id = $user->getID();
$header->setImageEditURL($this->getApplicationURI("picture/{$id}/"));
}
return $header;
}
+ final protected function newNavigation(
+ PhabricatorUser $user,
+ $item_identifier) {
+
+ $viewer = $this->getViewer();
+
+ $engine = id(new PhabricatorPeopleProfileMenuEngine())
+ ->setViewer($viewer)
+ ->setController($this)
+ ->setProfileObject($user);
+
+ $view_list = $engine->newProfileMenuItemViewList();
+
+ $view_list->setSelectedViewWithItemIdentifier($item_identifier);
+
+ $navigation = $view_list->newNavigationView();
+
+ return $navigation;
+ }
+
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
index 835935f775..5db38adafb 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileManageController.php
@@ -1,211 +1,212 @@
<?php
final class PhabricatorPeopleProfileManageController
extends PhabricatorPeopleProfileController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProfile(true)
->needProfileImage(true)
->needAvailability(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$this->setUser($user);
$header = $this->buildProfileHeader();
$curtain = $this->buildCurtain($user);
$properties = $this->buildPropertyView($user);
$name = $user->getUsername();
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE);
+ $nav = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_MANAGE);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Manage'));
$crumbs->setBorder(true);
$timeline = $this->buildTransactionTimeline(
$user,
new PhabricatorPeopleTransactionQuery());
$timeline->setShouldTerminate(true);
$manage = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setCurtain($curtain)
->addPropertySection(pht('Details'), $properties)
->setMainColumn($timeline);
return $this->newPage()
->setTitle(
array(
pht('Manage User'),
$user->getUsername(),
))
->setNavigation($nav)
->setCrumbs($crumbs)
->appendChild($manage);
}
private function buildPropertyView(PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($user);
$field_list = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_VIEW);
$field_list->appendFieldsToPropertyList($user, $viewer, $view);
return $view;
}
private function buildCurtain(PhabricatorUser $user) {
$viewer = $this->getViewer();
$is_self = ($user->getPHID() === $viewer->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$user,
PhabricatorPolicyCapability::CAN_EDIT);
$is_admin = $viewer->getIsAdmin();
$can_admin = ($is_admin && !$is_self);
$has_disable = $this->hasApplicationCapability(
PeopleDisableUsersCapability::CAPABILITY);
$can_disable = ($has_disable && !$is_self);
$id = $user->getID();
$welcome_engine = id(new PhabricatorPeopleWelcomeMailEngine())
->setSender($viewer)
->setRecipient($user);
$can_welcome = $welcome_engine->canSendMail();
$curtain = $this->newCurtainView($user);
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setName(pht('Edit Profile'))
->setHref($this->getApplicationURI('editprofile/'.$id.'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-picture-o')
->setName(pht('Edit Profile Picture'))
->setHref($this->getApplicationURI('picture/'.$id.'/'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-wrench')
->setName(pht('Edit Settings'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)
->setHref('/settings/user/'.$user->getUsername().'/'));
if ($user->getIsAdmin()) {
$empower_icon = 'fa-arrow-circle-o-down';
$empower_name = pht('Remove Administrator');
} else {
$empower_icon = 'fa-arrow-circle-o-up';
$empower_name = pht('Make Administrator');
}
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon($empower_icon)
->setName($empower_name)
->setDisabled(!$can_admin)
->setWorkflow(true)
->setHref($this->getApplicationURI('empower/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-tag')
->setName(pht('Change Username'))
->setDisabled(!$is_admin)
->setWorkflow(true)
->setHref($this->getApplicationURI('rename/'.$id.'/')));
if ($user->getIsDisabled()) {
$disable_icon = 'fa-check-circle-o';
$disable_name = pht('Enable User');
} else {
$disable_icon = 'fa-ban';
$disable_name = pht('Disable User');
}
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-envelope')
->setName(pht('Send Welcome Email'))
->setWorkflow(true)
->setDisabled(!$can_welcome)
->setHref($this->getApplicationURI('welcome/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER));
if (!$user->getIsApproved()) {
$approve_action = id(new PhabricatorActionView())
->setIcon('fa-thumbs-up')
->setName(pht('Approve User'))
->setWorkflow(true)
->setDisabled(!$is_admin)
->setHref("/people/approve/{$id}/via/profile/");
if ($is_admin) {
$approve_action->setColor(PhabricatorActionView::GREEN);
}
$curtain->addAction($approve_action);
}
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon($disable_icon)
->setName($disable_name)
->setDisabled(!$can_disable)
->setWorkflow(true)
->setHref($this->getApplicationURI('disable/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-times')
->setName(pht('Delete User'))
->setDisabled(!$can_admin)
->setWorkflow(true)
->setHref($this->getApplicationURI('delete/'.$id.'/')));
$curtain->addAction(
id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER));
return $curtain;
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php
index 55baf0140f..0c7b6f6a1f 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileRevisionsController.php
@@ -1,78 +1,79 @@
<?php
final class PhabricatorPeopleProfileRevisionsController
extends PhabricatorPeopleProfileController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProfile(true)
->needProfileImage(true)
->needAvailability(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$class = 'PhabricatorDifferentialApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return new Aphront404Response();
}
$this->setUser($user);
$title = array(pht('Recent Revisions'), $user->getUsername());
$header = $this->buildProfileHeader();
$commits = $this->buildRevisionsView($user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Recent Revisions'));
$crumbs->setBorder(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS);
+ $nav = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_REVISIONS);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setFooter(array(
$commits,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function buildRevisionsView(PhabricatorUser $user) {
$viewer = $this->getViewer();
$revisions = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withAuthors(array($user->getPHID()))
->needFlags(true)
->needDrafts(true)
->needReviewers(true)
->setLimit(100)
->execute();
$list = id(new DifferentialRevisionListView())
->setViewer($viewer)
->setNoBox(true)
->setRevisions($revisions)
->setNoDataString(pht('No recent revisions.'));
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Recent Revisions'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
return $view;
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php
index b843af8fc7..bc4e1432f1 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileTasksController.php
@@ -1,82 +1,83 @@
<?php
final class PhabricatorPeopleProfileTasksController
extends PhabricatorPeopleProfileController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withIDs(array($id))
->needProfile(true)
->needProfileImage(true)
->needAvailability(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$class = 'PhabricatorManiphestApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return new Aphront404Response();
}
$this->setUser($user);
$title = array(pht('Assigned Tasks'), $user->getUsername());
$header = $this->buildProfileHeader();
$tasks = $this->buildTasksView($user);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Assigned Tasks'));
$crumbs->setBorder(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_TASKS);
+ $nav = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_TASKS);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setFooter(array(
$tasks,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function buildTasksView(PhabricatorUser $user) {
$viewer = $this->getViewer();
$open = ManiphestTaskStatus::getOpenStatusConstants();
$tasks = id(new ManiphestTaskQuery())
->setViewer($viewer)
->withOwners(array($user->getPHID()))
->withStatuses($open)
->needProjectPHIDs(true)
->setLimit(100)
->setGroupBy(ManiphestTaskQuery::GROUP_PRIORITY)
->execute();
$handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks);
$list = id(new ManiphestTaskListView())
->setUser($viewer)
->setHandles($handles)
->setTasks($tasks)
->setNoDataString(pht('No open, assigned tasks.'));
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Assigned Tasks'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->appendChild($list);
return $view;
}
}
diff --git a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
index 6a4d68d6aa..b5c0e2b816 100644
--- a/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
+++ b/src/applications/people/controller/PhabricatorPeopleProfileViewController.php
@@ -1,274 +1,275 @@
<?php
final class PhabricatorPeopleProfileViewController
extends PhabricatorPeopleProfileController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$username = $request->getURIData('username');
$user = id(new PhabricatorPeopleQuery())
->setViewer($viewer)
->withUsernames(array($username))
->needProfileImage(true)
->needAvailability(true)
->executeOne();
if (!$user) {
return new Aphront404Response();
}
$this->setUser($user);
$header = $this->buildProfileHeader();
$properties = $this->buildPropertyView($user);
$name = $user->getUsername();
$feed = $this->buildPeopleFeed($user, $viewer);
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIcon('fa-list-ul'))
->setText(pht('View All'))
->setHref('/feed/?userPHIDs='.$user->getPHID());
$feed_header = id(new PHUIHeaderView())
->setHeader(pht('Recent Activity'))
->addActionLink($view_all);
$feed = id(new PHUIObjectBoxView())
->setHeader($feed_header)
->addClass('project-view-feed')
->appendChild($feed);
$projects = $this->buildProjectsView($user);
$calendar = $this->buildCalendarDayView($user);
$home = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setMainColumn(
array(
$properties,
$feed,
))
->setSideColumn(
array(
$projects,
$calendar,
));
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorPeopleProfileMenuEngine::ITEM_PROFILE);
+ $navigation = $this->newNavigation(
+ $user,
+ PhabricatorPeopleProfileMenuEngine::ITEM_PROFILE);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
return $this->newPage()
->setTitle($user->getUsername())
- ->setNavigation($nav)
+ ->setNavigation($navigation)
->setCrumbs($crumbs)
->setPageObjectPHIDs(
array(
$user->getPHID(),
))
->appendChild(
array(
$home,
));
}
private function buildPropertyView(
PhabricatorUser $user) {
$viewer = $this->getRequest()->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($user);
$field_list = PhabricatorCustomField::getObjectFields(
$user,
PhabricatorCustomField::ROLE_VIEW);
$field_list->appendFieldsToPropertyList($user, $viewer, $view);
if (!$view->hasAnyProperties()) {
return null;
}
$header = id(new PHUIHeaderView())
->setHeader(pht('User Details'));
$view = id(new PHUIObjectBoxView())
->appendChild($view)
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('project-view-properties');
return $view;
}
private function buildProjectsView(
PhabricatorUser $user) {
$viewer = $this->getViewer();
$projects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withMemberPHIDs(array($user->getPHID()))
->needImages(true)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->execute();
$header = id(new PHUIHeaderView())
->setHeader(pht('Projects'));
if (!empty($projects)) {
$limit = 5;
$render_phids = array_slice($projects, 0, $limit);
$list = id(new PhabricatorProjectListView())
->setUser($viewer)
->setProjects($render_phids);
if (count($projects) > $limit) {
$header_text = pht(
'Projects (%s)',
phutil_count($projects));
$header = id(new PHUIHeaderView())
->setHeader($header_text)
->addActionLink(
id(new PHUIButtonView())
->setTag('a')
->setIcon('fa-list-ul')
->setText(pht('View All'))
->setHref('/project/?member='.$user->getPHID()));
}
} else {
$list = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NODATA)
->appendChild(pht('User does not belong to any projects.'));
}
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($list)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $box;
}
private function buildCalendarDayView(PhabricatorUser $user) {
$viewer = $this->getViewer();
$class = 'PhabricatorCalendarApplication';
if (!PhabricatorApplication::isClassInstalledForViewer($class, $viewer)) {
return null;
}
// Don't show calendar information for disabled users, since it's probably
// not useful or accurate and may be misleading.
if ($user->getIsDisabled()) {
return null;
}
$midnight = PhabricatorTime::getTodayMidnightDateTime($viewer);
$week_end = clone $midnight;
$week_end = $week_end->modify('+3 days');
$range_start = $midnight->format('U');
$range_end = $week_end->format('U');
$events = id(new PhabricatorCalendarEventQuery())
->setViewer($viewer)
->withDateRange($range_start, $range_end)
->withInvitedPHIDs(array($user->getPHID()))
->withIsCancelled(false)
->needRSVPs(array($viewer->getPHID()))
->execute();
$event_views = array();
foreach ($events as $event) {
$viewer_is_invited = $event->isRSVPInvited($viewer->getPHID());
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$event,
PhabricatorPolicyCapability::CAN_EDIT);
$epoch_min = $event->getStartDateTimeEpoch();
$epoch_max = $event->getEndDateTimeEpoch();
$event_view = id(new AphrontCalendarEventView())
->setCanEdit($can_edit)
->setEventID($event->getID())
->setEpochRange($epoch_min, $epoch_max)
->setIsAllDay($event->getIsAllDay())
->setIcon($event->getIcon())
->setViewerIsInvited($viewer_is_invited)
->setName($event->getName())
->setDatetimeSummary($event->renderEventDate($viewer, true))
->setURI($event->getURI());
$event_views[] = $event_view;
}
$event_views = msort($event_views, 'getEpochStart');
$day_view = id(new PHUICalendarWeekView())
->setViewer($viewer)
->setView('week')
->setEvents($event_views)
->setWeekLength(3)
->render();
$header = id(new PHUIHeaderView())
->setHeader(pht('Calendar'))
->setHref(
urisprintf(
'/calendar/?invited=%s#R',
$user->getUsername()));
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($day_view)
->addClass('calendar-profile-box')
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
return $box;
}
private function buildPeopleFeed(
PhabricatorUser $user,
$viewer) {
$query = new PhabricatorFeedQuery();
$query->withFilterPHIDs(
array(
$user->getPHID(),
));
$query->setLimit(100);
$query->setViewer($viewer);
$stories = $query->execute();
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($viewer);
$builder->setShowHovercards(true);
$builder->setNoDataString(pht('To begin on such a grand journey, '.
'requires but just a single step.'));
$view = $builder->buildView();
return $view->render();
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
index bd14033d3f..3ee0213daa 100644
--- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php
@@ -1,1517 +1,1510 @@
<?php
final class PhabricatorProjectBoardViewController
extends PhabricatorProjectBoardController {
const BATCH_EDIT_ALL = 'all';
private $id;
private $slug;
private $queryKey;
private $sortKey;
private $showHidden;
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getUser();
$response = $this->loadProject();
if ($response) {
return $response;
}
$project = $this->getProject();
$this->readRequestState();
$board_uri = $this->getApplicationURI('board/'.$project->getID().'/');
$search_engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer)
->setBaseURI($board_uri)
->setIsBoardView(true);
if ($request->isFormPost()
&& !$request->getBool('initialize')
&& !$request->getStr('move')
&& !$request->getStr('queryColumnID')) {
$saved = $search_engine->buildSavedQueryFromRequest($request);
$search_engine->saveQuery($saved);
$filter_form = id(new AphrontFormView())
->setUser($viewer);
$search_engine->buildSearchForm($filter_form, $saved);
if ($search_engine->getErrors()) {
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle(pht('Advanced Filter'))
->appendChild($filter_form->buildLayoutView())
->setErrors($search_engine->getErrors())
->setSubmitURI($board_uri)
->addSubmitButton(pht('Apply Filter'))
->addCancelButton($board_uri);
}
return id(new AphrontRedirectResponse())->setURI(
$this->getURIWithState(
$search_engine->getQueryResultsPageURI($saved->getQueryKey())));
}
$query_key = $this->getDefaultFilter($project);
$request_query = $request->getStr('filter');
if (strlen($request_query)) {
$query_key = $request_query;
}
$uri_query = $request->getURIData('queryKey');
if (strlen($uri_query)) {
$query_key = $uri_query;
}
$this->queryKey = $query_key;
$custom_query = null;
if ($search_engine->isBuiltinQuery($query_key)) {
$saved = $search_engine->buildSavedQueryFromBuiltin($query_key);
} else {
$saved = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($query_key))
->executeOne();
if (!$saved) {
return new Aphront404Response();
}
$custom_query = $saved;
}
if ($request->getURIData('filter')) {
$filter_form = id(new AphrontFormView())
->setUser($viewer);
$search_engine->buildSearchForm($filter_form, $saved);
return $this->newDialog()
->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle(pht('Advanced Filter'))
->appendChild($filter_form->buildLayoutView())
->setSubmitURI($board_uri)
->addSubmitButton(pht('Apply Filter'))
->addCancelButton($board_uri);
}
$task_query = $search_engine->buildQueryFromSavedQuery($saved);
$select_phids = array($project->getPHID());
if ($project->getHasSubprojects() || $project->getHasMilestones()) {
$descendants = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withAncestorProjectPHIDs($select_phids)
->execute();
foreach ($descendants as $descendant) {
$select_phids[] = $descendant->getPHID();
}
}
$tasks = $task_query
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_ANCESTOR,
array($select_phids))
->setOrder(ManiphestTaskQuery::ORDER_PRIORITY)
->setViewer($viewer)
->execute();
$tasks = mpull($tasks, null, 'getPHID');
$board_phid = $project->getPHID();
// Regardless of display order, pass tasks to the layout engine in ID order
// so layout is consistent.
$board_tasks = msort($tasks, 'getID');
$layout_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setBoardPHIDs(array($board_phid))
->setObjectPHIDs(array_keys($board_tasks))
->setFetchAllBoards(true)
->executeLayout();
$columns = $layout_engine->getColumns($board_phid);
if (!$columns || !$project->getHasWorkboard()) {
$has_normal_columns = false;
foreach ($columns as $column) {
if (!$column->getProxyPHID()) {
$has_normal_columns = true;
break;
}
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
if (!$has_normal_columns) {
if (!$can_edit) {
$content = $this->buildNoAccessContent($project);
} else {
$content = $this->buildInitializeContent($project);
}
} else {
if (!$can_edit) {
$content = $this->buildDisabledContent($project);
} else {
$content = $this->buildEnableContent($project);
}
}
if ($content instanceof AphrontResponse) {
return $content;
}
- $nav = $this->newWorkboardProfileMenu();
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_WORKBOARD);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Workboard'));
return $this->newPage()
->setTitle(
array(
$project->getDisplayName(),
pht('Workboard'),
))
->setNavigation($nav)
->setCrumbs($crumbs)
->appendChild($content);
}
// If the user wants to turn a particular column into a query, build an
// apropriate filter and redirect them to the query results page.
$query_column_id = $request->getInt('queryColumnID');
if ($query_column_id) {
$column_id_map = mpull($columns, null, 'getID');
$query_column = idx($column_id_map, $query_column_id);
if (!$query_column) {
return new Aphront404Response();
}
// Create a saved query to combine the active filter on the workboard
// with the column filter. If the user currently has constraints on the
// board, we want to add a new column or project constraint, not
// completely replace the constraints.
$saved_query = $saved->newCopy();
if ($query_column->getProxyPHID()) {
$project_phids = $saved_query->getParameter('projectPHIDs');
if (!$project_phids) {
$project_phids = array();
}
$project_phids[] = $query_column->getProxyPHID();
$saved_query->setParameter('projectPHIDs', $project_phids);
} else {
$saved_query->setParameter(
'columnPHIDs',
array($query_column->getPHID()));
}
$search_engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer);
$search_engine->saveQuery($saved_query);
$query_key = $saved_query->getQueryKey();
$query_uri = new PhutilURI("/maniphest/query/{$query_key}/#R");
return id(new AphrontRedirectResponse())
->setURI($query_uri);
}
$task_can_edit_map = id(new PhabricatorPolicyFilter())
->setViewer($viewer)
->requireCapabilities(array(PhabricatorPolicyCapability::CAN_EDIT))
->apply($tasks);
// If this is a batch edit, select the editable tasks in the chosen column
// and ship the user into the batch editor.
$batch_edit = $request->getStr('batch');
if ($batch_edit) {
if ($batch_edit !== self::BATCH_EDIT_ALL) {
$column_id_map = mpull($columns, null, 'getID');
$batch_column = idx($column_id_map, $batch_edit);
if (!$batch_column) {
return new Aphront404Response();
}
$batch_task_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$batch_column->getPHID());
foreach ($batch_task_phids as $key => $batch_task_phid) {
if (empty($task_can_edit_map[$batch_task_phid])) {
unset($batch_task_phids[$key]);
}
}
$batch_tasks = array_select_keys($tasks, $batch_task_phids);
} else {
$batch_tasks = $task_can_edit_map;
}
if (!$batch_tasks) {
$cancel_uri = $this->getURIWithState($board_uri);
return $this->newDialog()
->setTitle(pht('No Editable Tasks'))
->appendParagraph(
pht(
'The selected column contains no visible tasks which you '.
'have permission to edit.'))
->addCancelButton($board_uri);
}
// Create a saved query to hold the working set. This allows us to get
// around URI length limitations with a long "?ids=..." query string.
// For details, see T10268.
$search_engine = id(new ManiphestTaskSearchEngine())
->setViewer($viewer);
$saved_query = $search_engine->newSavedQuery();
$saved_query->setParameter('ids', mpull($batch_tasks, 'getID'));
$search_engine->saveQuery($saved_query);
$query_key = $saved_query->getQueryKey();
$bulk_uri = new PhutilURI("/maniphest/bulk/query/{$query_key}/");
$bulk_uri->replaceQueryParam('board', $this->id);
return id(new AphrontRedirectResponse())
->setURI($bulk_uri);
}
$move_id = $request->getStr('move');
if (strlen($move_id)) {
$column_id_map = mpull($columns, null, 'getID');
$move_column = idx($column_id_map, $move_id);
if (!$move_column) {
return new Aphront404Response();
}
$move_task_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$move_column->getPHID());
foreach ($move_task_phids as $key => $move_task_phid) {
if (empty($task_can_edit_map[$move_task_phid])) {
unset($move_task_phids[$key]);
}
}
$move_tasks = array_select_keys($tasks, $move_task_phids);
$cancel_uri = $this->getURIWithState($board_uri);
if (!$move_tasks) {
return $this->newDialog()
->setTitle(pht('No Movable Tasks'))
->appendParagraph(
pht(
'The selected column contains no visible tasks which you '.
'have permission to move.'))
->addCancelButton($cancel_uri);
}
$move_project_phid = $project->getPHID();
$move_column_phid = null;
$move_project = null;
$move_column = null;
$columns = null;
$errors = array();
if ($request->isFormOrHiSecPost()) {
$move_project_phid = head($request->getArr('moveProjectPHID'));
if (!$move_project_phid) {
$move_project_phid = $request->getStr('moveProjectPHID');
}
if (!$move_project_phid) {
if ($request->getBool('hasProject')) {
$errors[] = pht('Choose a project to move tasks to.');
}
} else {
$target_project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withPHIDs(array($move_project_phid))
->executeOne();
if (!$target_project) {
$errors[] = pht('You must choose a valid project.');
} else if (!$project->getHasWorkboard()) {
$errors[] = pht(
'You must choose a project with a workboard.');
} else {
$move_project = $target_project;
}
}
if ($move_project) {
$move_engine = id(new PhabricatorBoardLayoutEngine())
->setViewer($viewer)
->setBoardPHIDs(array($move_project->getPHID()))
->setFetchAllBoards(true)
->executeLayout();
$columns = $move_engine->getColumns($move_project->getPHID());
$columns = mpull($columns, null, 'getPHID');
foreach ($columns as $key => $column) {
if ($column->isHidden()) {
unset($columns[$key]);
}
}
$move_column_phid = $request->getStr('moveColumnPHID');
if (!$move_column_phid) {
if ($request->getBool('hasColumn')) {
$errors[] = pht('Choose a column to move tasks to.');
}
} else {
if (empty($columns[$move_column_phid])) {
$errors[] = pht(
'Choose a valid column on the target workboard to move '.
'tasks to.');
} else if ($columns[$move_column_phid]->getID() == $move_id) {
$errors[] = pht(
'You can not move tasks from a column to itself.');
} else {
$move_column = $columns[$move_column_phid];
}
}
}
}
if ($move_column && $move_project) {
foreach ($move_tasks as $move_task) {
$xactions = array();
// If we're switching projects, get out of the old project first
// and move to the new project.
if ($move_project->getID() != $project->getID()) {
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_EDGE)
->setMetadataValue(
'edge:type',
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST)
->setNewValue(
array(
'-' => array(
$project->getPHID() => $project->getPHID(),
),
'+' => array(
$move_project->getPHID() => $move_project->getPHID(),
),
));
}
$xactions[] = id(new ManiphestTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_COLUMNS)
->setNewValue(
array(
array(
'columnPHID' => $move_column->getPHID(),
),
));
$editor = id(new ManiphestTransactionEditor())
->setActor($viewer)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
->setCancelURI($cancel_uri);
$editor->applyTransactions($move_task, $xactions);
}
return id(new AphrontRedirectResponse())
->setURI($cancel_uri);
}
if ($move_project) {
$column_form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormSelectControl())
->setName('moveColumnPHID')
->setLabel(pht('Move to Column'))
->setValue($move_column_phid)
->setOptions(mpull($columns, 'getDisplayName', 'getPHID')));
return $this->newDialog()
->setTitle(pht('Move Tasks'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->setErrors($errors)
->addHiddenInput('move', $move_id)
->addHiddenInput('moveProjectPHID', $move_project->getPHID())
->addHiddenInput('hasColumn', true)
->addHiddenInput('hasProject', true)
->appendParagraph(
pht(
'Choose a column on the %s workboard to move tasks to:',
$viewer->renderHandle($move_project->getPHID())))
->appendForm($column_form)
->addSubmitButton(pht('Move Tasks'))
->addCancelButton($cancel_uri);
}
if ($move_project_phid) {
$move_project_phid_value = array($move_project_phid);
} else {
$move_project_phid_value = array();
}
$project_form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormTokenizerControl())
->setName('moveProjectPHID')
->setLimit(1)
->setLabel(pht('Move to Project'))
->setValue($move_project_phid_value)
->setDatasource(new PhabricatorProjectDatasource()));
return $this->newDialog()
->setTitle(pht('Move Tasks'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->setErrors($errors)
->addHiddenInput('move', $move_id)
->addHiddenInput('hasProject', true)
->appendForm($project_form)
->addSubmitButton(pht('Continue'))
->addCancelButton($cancel_uri);
}
$board_id = celerity_generate_unique_node_id();
$board = id(new PHUIWorkboardView())
->setUser($viewer)
->setID($board_id)
->addSigil('jx-workboard')
->setMetadata(
array(
'boardPHID' => $project->getPHID(),
));
$visible_columns = array();
$column_phids = array();
$visible_phids = array();
foreach ($columns as $column) {
if (!$this->showHidden) {
if ($column->isHidden()) {
continue;
}
}
$proxy = $column->getProxy();
if ($proxy && !$proxy->isMilestone()) {
// TODO: For now, don't show subproject columns because we can't
// handle tasks with multiple positions yet.
continue;
}
$task_phids = $layout_engine->getColumnObjectPHIDs(
$board_phid,
$column->getPHID());
$column_tasks = array_select_keys($tasks, $task_phids);
$column_phid = $column->getPHID();
$visible_columns[$column_phid] = $column;
$column_phids[$column_phid] = $column_tasks;
foreach ($column_tasks as $phid => $task) {
$visible_phids[$phid] = $phid;
}
}
$rendering_engine = id(new PhabricatorBoardRenderingEngine())
->setViewer($viewer)
->setObjects(array_select_keys($tasks, $visible_phids))
->setEditMap($task_can_edit_map)
->setExcludedProjectPHIDs($select_phids);
$templates = array();
$all_tasks = array();
$column_templates = array();
$sounds = array();
foreach ($visible_columns as $column_phid => $column) {
$column_tasks = $column_phids[$column_phid];
$panel = id(new PHUIWorkpanelView())
->setHeader($column->getDisplayName())
->setSubHeader($column->getDisplayType())
->addSigil('workpanel');
$proxy = $column->getProxy();
if ($proxy) {
$proxy_id = $proxy->getID();
$href = $this->getApplicationURI("view/{$proxy_id}/");
$panel->setHref($href);
}
$header_icon = $column->getHeaderIcon();
if ($header_icon) {
$panel->setHeaderIcon($header_icon);
}
$display_class = $column->getDisplayClass();
if ($display_class) {
$panel->addClass($display_class);
}
if ($column->isHidden()) {
$panel->addClass('project-panel-hidden');
}
$column_menu = $this->buildColumnMenu($project, $column);
$panel->addHeaderAction($column_menu);
if ($column->canHaveTrigger()) {
$trigger_menu = $this->buildTriggerMenu($column);
$panel->addHeaderAction($trigger_menu);
}
$count_tag = id(new PHUITagView())
->setType(PHUITagView::TYPE_SHADE)
->setColor(PHUITagView::COLOR_BLUE)
->addSigil('column-points')
->setName(
javelin_tag(
'span',
array(
'sigil' => 'column-points-content',
),
pht('-')))
->setStyle('display: none');
$panel->setHeaderTag($count_tag);
$cards = id(new PHUIObjectItemListView())
->setUser($viewer)
->setFlush(true)
->setAllowEmptyList(true)
->addSigil('project-column')
->setItemClass('phui-workcard')
->setMetadata(
array(
'columnPHID' => $column->getPHID(),
'pointLimit' => $column->getPointLimit(),
));
$card_phids = array();
foreach ($column_tasks as $task) {
$object_phid = $task->getPHID();
$card = $rendering_engine->renderCard($object_phid);
$templates[$object_phid] = hsprintf('%s', $card->getItem());
$card_phids[] = $object_phid;
$all_tasks[$object_phid] = $task;
}
$panel->setCards($cards);
$board->addPanel($panel);
$drop_effects = $column->getDropEffects();
$drop_effects = mpull($drop_effects, 'toDictionary');
$preview_effect = null;
if ($column->canHaveTrigger()) {
$trigger = $column->getTrigger();
if ($trigger) {
$preview_effect = $trigger->getPreviewEffect()
->toDictionary();
foreach ($trigger->getSoundEffects() as $sound) {
$sounds[] = $sound;
}
}
}
$column_templates[] = array(
'columnPHID' => $column_phid,
'effects' => $drop_effects,
'cardPHIDs' => $card_phids,
'triggerPreviewEffect' => $preview_effect,
);
}
$order_key = $this->sortKey;
$ordering_map = PhabricatorProjectColumnOrder::getEnabledOrders();
$ordering = id(clone $ordering_map[$order_key])
->setViewer($viewer);
$headers = $ordering->getHeadersForObjects($all_tasks);
$headers = mpull($headers, 'toDictionary');
$vectors = $ordering->getSortVectorsForObjects($all_tasks);
$vector_map = array();
foreach ($vectors as $task_phid => $vector) {
$vector_map[$task_phid][$order_key] = $vector;
}
$header_keys = $ordering->getHeaderKeysForObjects($all_tasks);
$order_maps = array();
$order_maps[] = $ordering->toDictionary();
$properties = array();
foreach ($all_tasks as $task) {
$properties[$task->getPHID()] =
PhabricatorBoardResponseEngine::newTaskProperties($task);
}
$behavior_config = array(
'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'),
'uploadURI' => '/file/dropupload/',
'coverURI' => $this->getApplicationURI('cover/'),
'chunkThreshold' => PhabricatorFileStorageEngine::getChunkThreshold(),
'pointsEnabled' => ManiphestTaskPoints::getIsEnabled(),
'boardPHID' => $project->getPHID(),
'order' => $this->sortKey,
'orders' => $order_maps,
'headers' => $headers,
'headerKeys' => $header_keys,
'templateMap' => $templates,
'orderMaps' => $vector_map,
'propertyMaps' => $properties,
'columnTemplates' => $column_templates,
'boardID' => $board_id,
'projectPHID' => $project->getPHID(),
'preloadSounds' => $sounds,
);
$this->initBehavior('project-boards', $behavior_config);
$sort_menu = $this->buildSortMenu(
$viewer,
$project,
$this->sortKey,
$ordering_map);
$filter_menu = $this->buildFilterMenu(
$viewer,
$project,
$custom_query,
$search_engine,
$query_key);
$manage_menu = $this->buildManageMenu($project, $this->showHidden);
$header_link = phutil_tag(
'a',
array(
'href' => $this->getApplicationURI('profile/'.$project->getID().'/'),
),
$project->getName());
$board_box = id(new PHUIBoxView())
->appendChild($board)
->addClass('project-board-wrapper');
- $nav = $this->newWorkboardProfileMenu();
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_WORKBOARD);
$divider = id(new PHUIListItemView())
->setType(PHUIListItemView::TYPE_DIVIDER);
$fullscreen = $this->buildFullscreenMenu();
$crumbs = $this->newWorkboardCrumbs();
$crumbs->addTextCrumb(pht('Workboard'));
$crumbs->setBorder(true);
$crumbs->addAction($sort_menu);
$crumbs->addAction($filter_menu);
$crumbs->addAction($divider);
$crumbs->addAction($manage_menu);
$crumbs->addAction($fullscreen);
$page = $this->newPage()
->setTitle(
array(
$project->getDisplayName(),
pht('Workboard'),
))
->setPageObjectPHIDs(array($project->getPHID()))
->setShowFooter(false)
->setNavigation($nav)
->setCrumbs($crumbs)
->addQuicksandConfig(
array(
'boardConfig' => $behavior_config,
))
->appendChild(
array(
$board_box,
));
$background = $project->getDisplayWorkboardBackgroundColor();
require_celerity_resource('phui-workboard-color-css');
if ($background !== null) {
$background_color_class = "phui-workboard-{$background}";
$page->addClass('phui-workboard-color');
$page->addClass($background_color_class);
} else {
$page->addClass('phui-workboard-no-color');
}
return $page;
}
private function readRequestState() {
$request = $this->getRequest();
$project = $this->getProject();
$this->showHidden = $request->getBool('hidden');
$this->id = $project->getID();
$sort_key = $this->getDefaultSort($project);
$request_sort = $request->getStr('order');
if ($this->isValidSort($request_sort)) {
$sort_key = $request_sort;
}
$this->sortKey = $sort_key;
}
private function getDefaultSort(PhabricatorProject $project) {
$default_sort = $project->getDefaultWorkboardSort();
if ($this->isValidSort($default_sort)) {
return $default_sort;
}
return PhabricatorProjectColumnNaturalOrder::ORDERKEY;
}
private function getDefaultFilter(PhabricatorProject $project) {
$default_filter = $project->getDefaultWorkboardFilter();
if (strlen($default_filter)) {
return $default_filter;
}
return 'open';
}
private function isValidSort($sort) {
$map = PhabricatorProjectColumnOrder::getEnabledOrders();
return isset($map[$sort]);
}
private function buildSortMenu(
PhabricatorUser $viewer,
PhabricatorProject $project,
$sort_key,
array $ordering_map) {
$base_uri = $this->getURIWithState();
$items = array();
foreach ($ordering_map as $key => $ordering) {
// TODO: It would be desirable to build a real "PHUIIconView" here, but
// the pathway for threading that through all the view classes ends up
// being fairly complex, since some callers read the icon out of other
// views. For now, just stick with a string.
$ordering_icon = $ordering->getMenuIconIcon();
$ordering_name = $ordering->getDisplayName();
$is_selected = ($key === $sort_key);
if ($is_selected) {
$active_name = $ordering_name;
$active_icon = $ordering_icon;
}
$item = id(new PhabricatorActionView())
->setIcon($ordering_icon)
->setSelected($is_selected)
->setName($ordering_name);
$uri = $base_uri->alter('order', $key);
$item->setHref($uri);
$items[] = $item;
}
$id = $project->getID();
$save_uri = "default/{$id}/sort/";
$save_uri = $this->getApplicationURI($save_uri);
$save_uri = $this->getURIWithState($save_uri, $force = true);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))
->setHref($save_uri)
->setWorkflow(true)
->setDisabled(!$can_edit);
$sort_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
$sort_menu->addAction($item);
}
$sort_button = id(new PHUIListItemView())
->setName($active_name)
->setIcon($active_icon)
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $sort_menu),
));
return $sort_button;
}
private function buildFilterMenu(
PhabricatorUser $viewer,
PhabricatorProject $project,
$custom_query,
PhabricatorApplicationSearchEngine $engine,
$query_key) {
$named = array(
'open' => pht('Open Tasks'),
'all' => pht('All Tasks'),
);
if ($viewer->isLoggedIn()) {
$named['assigned'] = pht('Assigned to Me');
}
if ($custom_query) {
$named[$custom_query->getQueryKey()] = pht('Custom Filter');
}
$items = array();
foreach ($named as $key => $name) {
$is_selected = ($key == $query_key);
if ($is_selected) {
$active_filter = $name;
}
$is_custom = false;
if ($custom_query) {
$is_custom = ($key == $custom_query->getQueryKey());
}
$item = id(new PhabricatorActionView())
->setIcon('fa-search')
->setSelected($is_selected)
->setName($name);
if ($is_custom) {
$uri = $this->getApplicationURI(
'board/'.$this->id.'/filter/query/'.$key.'/');
$item->setWorkflow(true);
} else {
$uri = $engine->getQueryResultsPageURI($key);
}
$uri = $this->getURIWithState($uri)
->removeQueryParam('filter');
$item->setHref($uri);
$items[] = $item;
}
$id = $project->getID();
$filter_uri = $this->getApplicationURI("board/{$id}/filter/");
$filter_uri = $this->getURIWithState($filter_uri, $force = true);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-cog')
->setHref($filter_uri)
->setWorkflow(true)
->setName(pht('Advanced Filter...'));
$save_uri = "default/{$id}/filter/";
$save_uri = $this->getApplicationURI($save_uri);
$save_uri = $this->getURIWithState($save_uri, $force = true);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$items[] = id(new PhabricatorActionView())
->setIcon('fa-floppy-o')
->setName(pht('Save as Default'))
->setHref($save_uri)
->setWorkflow(true)
->setDisabled(!$can_edit);
$filter_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($items as $item) {
$filter_menu->addAction($item);
}
$filter_button = id(new PHUIListItemView())
->setName($active_filter)
->setIcon('fa-search')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $filter_menu),
));
return $filter_button;
}
private function buildManageMenu(
PhabricatorProject $project,
$show_hidden) {
$request = $this->getRequest();
$viewer = $request->getUser();
$id = $project->getID();
$manage_uri = $this->getApplicationURI("board/{$id}/manage/");
$add_uri = $this->getApplicationURI("board/{$id}/edit/");
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$manage_items = array();
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-plus')
->setName(pht('Add Column'))
->setHref($add_uri)
->setDisabled(!$can_edit)
->setWorkflow(true);
$reorder_uri = $this->getApplicationURI("board/{$id}/reorder/");
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-exchange')
->setName(pht('Reorder Columns'))
->setHref($reorder_uri)
->setDisabled(!$can_edit)
->setWorkflow(true);
if ($show_hidden) {
$hidden_uri = $this->getURIWithState()
->removeQueryParam('hidden');
$hidden_icon = 'fa-eye-slash';
$hidden_text = pht('Hide Hidden Columns');
} else {
$hidden_uri = $this->getURIWithState()
->replaceQueryParam('hidden', 'true');
$hidden_icon = 'fa-eye';
$hidden_text = pht('Show Hidden Columns');
}
$manage_items[] = id(new PhabricatorActionView())
->setIcon($hidden_icon)
->setName($hidden_text)
->setHref($hidden_uri);
$manage_items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$background_uri = $this->getApplicationURI("board/{$id}/background/");
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-paint-brush')
->setName(pht('Change Background Color'))
->setHref($background_uri)
->setDisabled(!$can_edit)
->setWorkflow(false);
$manage_uri = $this->getApplicationURI("board/{$id}/manage/");
$manage_items[] = id(new PhabricatorActionView())
->setIcon('fa-gear')
->setName(pht('Manage Workboard'))
->setHref($manage_uri);
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->replaceQueryParam('batch', self::BATCH_EDIT_ALL);
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
ManiphestBulkEditCapability::CAPABILITY);
$manage_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($manage_items as $item) {
$manage_menu->addAction($item);
}
$manage_button = id(new PHUIListItemView())
->setIcon('fa-cog')
->setHref('#')
->addSigil('boards-dropdown-menu')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Manage'),
'align' => 'S',
'items' => hsprintf('%s', $manage_menu),
));
return $manage_button;
}
private function buildFullscreenMenu() {
$up = id(new PHUIListItemView())
->setIcon('fa-arrows-alt')
->setHref('#')
->addClass('phui-workboard-expand-icon')
->addSigil('jx-toggle-class')
->addSigil('has-tooltip')
->setMetaData(array(
'tip' => pht('Fullscreen'),
'map' => array(
'phabricator-standard-page' => 'phui-workboard-fullscreen',
),
));
return $up;
}
private function buildColumnMenu(
PhabricatorProject $project,
PhabricatorProjectColumn $column) {
$request = $this->getRequest();
$viewer = $request->getUser();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$column_items = array();
if ($column->getProxyPHID()) {
$default_phid = $column->getProxyPHID();
} else {
$default_phid = $column->getProjectPHID();
}
$specs = id(new ManiphestEditEngine())
->setViewer($viewer)
->newCreateActionSpecifications(array());
foreach ($specs as $spec) {
$column_items[] = id(new PhabricatorActionView())
->setIcon($spec['icon'])
->setName($spec['name'])
->setHref($spec['uri'])
->setDisabled($spec['disabled'])
->addSigil('column-add-task')
->setMetadata(
array(
'createURI' => $spec['uri'],
'columnPHID' => $column->getPHID(),
'boardPHID' => $project->getPHID(),
'projectPHID' => $default_phid,
));
}
$column_items[] = id(new PhabricatorActionView())
->setType(PhabricatorActionView::TYPE_DIVIDER);
$batch_edit_uri = $request->getRequestURI();
$batch_edit_uri->replaceQueryParam('batch', $column->getID());
$can_batch_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
PhabricatorApplication::getByClass('PhabricatorManiphestApplication'),
ManiphestBulkEditCapability::CAPABILITY);
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-list-ul')
->setName(pht('Bulk Edit Tasks...'))
->setHref($batch_edit_uri)
->setDisabled(!$can_batch_edit);
$batch_move_uri = $request->getRequestURI();
$batch_move_uri->replaceQueryParam('move', $column->getID());
$column_items[] = id(new PhabricatorActionView())
->setIcon('fa-arrow-right')
->setName(pht('Move Tasks to Column...'))
->setHref($batch_move_uri)
->setWorkflow(true);
$query_uri = $request->getRequestURI();
$query_uri->replaceQueryParam('queryColumnID', $column->getID());
$column_items[] = id(new PhabricatorActionView())
->setName(pht('View as Query'))
->setIcon('fa-search')
->setHref($query_uri);
$edit_uri = 'board/'.$this->id.'/edit/'.$column->getID().'/';
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Edit Column'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI($edit_uri))
->setDisabled(!$can_edit)
->setWorkflow(true);
$can_hide = ($can_edit && !$column->isDefaultColumn());
$hide_uri = 'board/'.$this->id.'/hide/'.$column->getID().'/';
$hide_uri = $this->getApplicationURI($hide_uri);
$hide_uri = $this->getURIWithState($hide_uri);
if (!$column->isHidden()) {
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Hide Column'))
->setIcon('fa-eye-slash')
->setHref($hide_uri)
->setDisabled(!$can_hide)
->setWorkflow(true);
} else {
$column_items[] = id(new PhabricatorActionView())
->setName(pht('Show Column'))
->setIcon('fa-eye')
->setHref($hide_uri)
->setDisabled(!$can_hide)
->setWorkflow(true);
}
$column_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($column_items as $item) {
$column_menu->addAction($item);
}
$column_button = id(new PHUIIconView())
->setIcon('fa-pencil')
->setHref('#')
->addSigil('boards-dropdown-menu')
->setMetadata(
array(
'items' => hsprintf('%s', $column_menu),
));
return $column_button;
}
private function buildTriggerMenu(PhabricatorProjectColumn $column) {
$viewer = $this->getViewer();
$trigger = $column->getTrigger();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$column,
PhabricatorPolicyCapability::CAN_EDIT);
$trigger_items = array();
if (!$trigger) {
$set_uri = $this->getApplicationURI(
new PhutilURI(
'trigger/edit/',
array(
'columnPHID' => $column->getPHID(),
)));
$trigger_items[] = id(new PhabricatorActionView())
->setIcon('fa-cogs')
->setName(pht('New Trigger...'))
->setHref($set_uri)
->setDisabled(!$can_edit);
} else {
$trigger_items[] = id(new PhabricatorActionView())
->setIcon('fa-cogs')
->setName(pht('View Trigger'))
->setHref($trigger->getURI())
->setDisabled(!$can_edit);
}
$remove_uri = $this->getApplicationURI(
new PhutilURI(
urisprintf(
'column/remove/%d/',
$column->getID())));
$trigger_items[] = id(new PhabricatorActionView())
->setIcon('fa-times')
->setName(pht('Remove Trigger'))
->setHref($remove_uri)
->setWorkflow(true)
->setDisabled(!$can_edit || !$trigger);
$trigger_menu = id(new PhabricatorActionListView())
->setUser($viewer);
foreach ($trigger_items as $item) {
$trigger_menu->addAction($item);
}
if ($trigger) {
$trigger_icon = 'fa-cogs';
} else {
$trigger_icon = 'fa-cogs grey';
}
$trigger_button = id(new PHUIIconView())
->setIcon($trigger_icon)
->setHref('#')
->addSigil('boards-dropdown-menu')
->addSigil('trigger-preview')
->setMetadata(
array(
'items' => hsprintf('%s', $trigger_menu),
'columnPHID' => $column->getPHID(),
));
return $trigger_button;
}
/**
* Add current state parameters (like order and the visibility of hidden
* columns) to a URI.
*
* This allows actions which toggle or adjust one piece of state to keep
* the rest of the board state persistent. If no URI is provided, this method
* starts with the request URI.
*
* @param string|null URI to add state parameters to.
* @param bool True to explicitly include all state.
* @return PhutilURI URI with state parameters.
*/
private function getURIWithState($base = null, $force = false) {
$project = $this->getProject();
if ($base === null) {
$base = $this->getRequest()->getPath();
}
$base = new PhutilURI($base);
if ($force || ($this->sortKey != $this->getDefaultSort($project))) {
if ($this->sortKey !== null) {
$base->replaceQueryParam('order', $this->sortKey);
} else {
$base->removeQueryParam('order');
}
} else {
$base->removeQueryParam('order');
}
if ($force || ($this->queryKey != $this->getDefaultFilter($project))) {
if ($this->queryKey !== null) {
$base->replaceQueryParam('filter', $this->queryKey);
} else {
$base->removeQueryParam('filter');
}
} else {
$base->removeQueryParam('filter');
}
if ($this->showHidden) {
$base->replaceQueryParam('hidden', 'true');
} else {
$base->removeQueryParam('hidden');
}
return $base;
}
private function buildInitializeContent(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$type = $request->getStr('initialize-type');
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
$board_uri = $this->getApplicationURI("board/{$id}/");
$import_uri = $this->getApplicationURI("board/{$id}/import/");
$set_default = $request->getBool('default');
if ($set_default) {
$this
->getProfileMenuEngine()
->adjustDefault(PhabricatorProject::ITEM_WORKBOARD);
}
if ($request->isFormPost()) {
if ($type == 'backlog-only') {
$column = PhabricatorProjectColumn::initializeNewColumn($viewer)
->setSequence(0)
->setProperty('isDefault', true)
->setProjectPHID($project->getPHID())
->save();
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(
PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE)
->setNewValue(1);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($board_uri);
} else {
return id(new AphrontRedirectResponse())
->setURI($import_uri);
}
}
// TODO: Tailor this UI if the project is already a parent project. We
// should not offer options for creating a parent project workboard, since
// they can't have their own columns.
$new_selector = id(new AphrontFormRadioButtonControl())
->setLabel(pht('Columns'))
->setName('initialize-type')
->setValue('backlog-only')
->addButton(
'backlog-only',
pht('New Empty Board'),
pht('Create a new board with just a backlog column.'))
->addButton(
'import',
pht('Import Columns'),
pht('Import board columns from another project.'));
$default_checkbox = id(new AphrontFormCheckboxControl())
->setLabel(pht('Make Default'))
->addCheckbox(
'default',
1,
pht('Make the workboard the default view for this project.'),
true);
$form = id(new AphrontFormView())
->setUser($viewer)
->addHiddenInput('initialize', 1)
->appendRemarkupInstructions(
pht('The workboard for this project has not been created yet.'))
->appendControl($new_selector)
->appendControl($default_checkbox)
->appendControl(
id(new AphrontFormSubmitControl())
->addCancelButton($profile_uri)
->setValue(pht('Create Workboard')));
$box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Create Workboard'))
->setForm($form);
return $box;
}
private function buildNoAccessContent(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
return $this->newDialog()
->setTitle(pht('Unable to Create Workboard'))
->appendParagraph(
pht(
'The workboard for this project has not been created yet, '.
'but you do not have permission to create it. Only users '.
'who can edit this project can create a workboard for it.'))
->addCancelButton($profile_uri);
}
private function buildEnableContent(PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $this->getViewer();
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
$board_uri = $this->getApplicationURI("board/{$id}/");
if ($request->isFormPost()) {
$xactions = array();
$xactions[] = id(new PhabricatorProjectTransaction())
->setTransactionType(
PhabricatorProjectWorkboardTransaction::TRANSACTIONTYPE)
->setNewValue(1);
id(new PhabricatorProjectTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($project, $xactions);
return id(new AphrontRedirectResponse())
->setURI($board_uri);
}
return $this->newDialog()
->setTitle(pht('Workboard Disabled'))
->addHiddenInput('initialize', 1)
->appendParagraph(
pht(
'This workboard has been disabled, but can be restored to its '.
'former glory.'))
->addCancelButton($profile_uri)
->addSubmitButton(pht('Enable Workboard'));
}
private function buildDisabledContent(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$profile_uri = $this->getApplicationURI("profile/{$id}/");
return $this->newDialog()
->setTitle(pht('Workboard Disabled'))
->appendParagraph(
pht(
'This workboard has been disabled, and you do not have permission '.
'to enable it. Only users who can edit this project can restore '.
'the workboard.'))
->addCancelButton($profile_uri);
}
- private function newWorkboardProfileMenu() {
- $default_item = id(new PhabricatorProfileMenuItemConfiguration())
- ->setBuiltinKey(PhabricatorProject::ITEM_WORKBOARD);
-
- $menu = parent::getProfileMenu($default_item);
-
- $menu->addClass('project-board-nav');
-
- return $menu;
- }
-
}
diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php
index eb4ab70711..7781f4c73b 100644
--- a/src/applications/project/controller/PhabricatorProjectController.php
+++ b/src/applications/project/controller/PhabricatorProjectController.php
@@ -1,210 +1,205 @@
<?php
abstract class PhabricatorProjectController extends PhabricatorController {
private $project;
private $profileMenu;
private $profileMenuEngine;
protected function setProject(PhabricatorProject $project) {
$this->project = $project;
return $this;
}
protected function getProject() {
return $this->project;
}
protected function loadProject() {
$viewer = $this->getViewer();
$request = $this->getRequest();
$id = nonempty(
$request->getURIData('projectID'),
$request->getURIData('id'));
$slug = $request->getURIData('slug');
if ($slug) {
$normal_slug = PhabricatorSlug::normalizeProjectSlug($slug);
$is_abnormal = ($slug !== $normal_slug);
$normal_uri = "/tag/{$normal_slug}/";
} else {
$is_abnormal = false;
}
$query = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->needMembers(true)
->needWatchers(true)
->needImages(true)
->needSlugs(true);
if ($slug) {
$query->withSlugs(array($slug));
} else {
$query->withIDs(array($id));
}
$policy_exception = null;
try {
$project = $query->executeOne();
} catch (PhabricatorPolicyException $ex) {
$policy_exception = $ex;
$project = null;
}
if (!$project) {
// This project legitimately does not exist, so just 404 the user.
if (!$policy_exception) {
return new Aphront404Response();
}
// Here, the project exists but the user can't see it. If they are
// using a non-canonical slug to view the project, redirect to the
// canonical slug. If they're already using the canonical slug, rethrow
// the exception to give them the policy error.
if ($is_abnormal) {
return id(new AphrontRedirectResponse())->setURI($normal_uri);
} else {
throw $policy_exception;
}
}
// The user can view the project, but is using a noncanonical slug.
// Redirect to the canonical slug.
$primary_slug = $project->getPrimarySlug();
if ($slug && ($slug !== $primary_slug)) {
$primary_uri = "/tag/{$primary_slug}/";
return id(new AphrontRedirectResponse())->setURI($primary_uri);
}
$this->setProject($project);
return null;
}
- public function buildApplicationMenu() {
- $menu = $this->newApplicationMenu();
-
- $profile_menu = $this->getProfileMenu();
- if ($profile_menu) {
- $menu->setProfileMenu($profile_menu);
- }
-
- $menu->setSearchEngine(new PhabricatorProjectSearchEngine());
-
- return $menu;
- }
-
- protected function getProfileMenu($default_item = null) {
- if (!$this->profileMenu) {
- $engine = $this->getProfileMenuEngine();
- if ($engine) {
- $this->profileMenu = $engine->buildNavigation($default_item);
- }
- }
-
- return $this->profileMenu;
- }
-
protected function buildApplicationCrumbs() {
return $this->newApplicationCrumbs('profile');
}
protected function newWorkboardCrumbs() {
return $this->newApplicationCrumbs('workboard');
}
private function newApplicationCrumbs($mode) {
$crumbs = parent::buildApplicationCrumbs();
$project = $this->getProject();
if ($project) {
$ancestors = $project->getAncestorProjects();
$ancestors = array_reverse($ancestors);
$ancestors[] = $project;
foreach ($ancestors as $ancestor) {
if ($ancestor->getPHID() === $project->getPHID()) {
// Link the current project's crumb to its profile no matter what,
// since we're already on the right context page for it and linking
// to the current page isn't helpful.
$crumb_uri = $ancestor->getProfileURI();
} else {
switch ($mode) {
case 'workboard':
$crumb_uri = $ancestor->getWorkboardURI();
break;
case 'profile':
default:
$crumb_uri = $ancestor->getProfileURI();
break;
}
}
$crumbs->addTextCrumb($ancestor->getName(), $crumb_uri);
}
}
return $crumbs;
}
protected function getProfileMenuEngine() {
if (!$this->profileMenuEngine) {
$viewer = $this->getViewer();
$project = $this->getProject();
if ($project) {
$engine = id(new PhabricatorProjectProfileMenuEngine())
->setViewer($viewer)
->setController($this)
->setProfileObject($project);
$this->profileMenuEngine = $engine;
}
}
return $this->profileMenuEngine;
}
protected function setProfileMenuEngine(
PhabricatorProjectProfileMenuEngine $engine) {
$this->profileMenuEngine = $engine;
return $this;
}
protected function newCardResponse(
$board_phid,
$object_phid,
PhabricatorProjectColumnOrder $ordering = null,
$sounds = array()) {
$viewer = $this->getViewer();
$request = $this->getRequest();
$visible_phids = $request->getStrList('visiblePHIDs');
if (!$visible_phids) {
$visible_phids = array();
}
$engine = id(new PhabricatorBoardResponseEngine())
->setViewer($viewer)
->setBoardPHID($board_phid)
->setObjectPHID($object_phid)
->setVisiblePHIDs($visible_phids)
->setSounds($sounds);
if ($ordering) {
$engine->setOrdering($ordering);
}
return $engine->buildResponse();
}
public function renderHashtags(array $tags) {
$result = array();
foreach ($tags as $key => $tag) {
$result[] = '#'.$tag;
}
return implode(', ', $result);
}
+ final protected function newNavigation(
+ PhabricatorProject $project,
+ $item_identifier) {
+
+ $engine = $this->getProfileMenuEngine();
+
+ $view_list = $engine->newProfileMenuItemViewList();
+
+ $view_list->setSelectedViewWithItemIdentifier($item_identifier);
+
+ $navigation = $view_list->newNavigationView();
+
+ if ($item_identifier === PhabricatorProject::ITEM_WORKBOARD) {
+ $navigation->addClass('project-board-nav');
+ }
+
+ return $navigation;
+ }
+
}
diff --git a/src/applications/project/controller/PhabricatorProjectManageController.php b/src/applications/project/controller/PhabricatorProjectManageController.php
index 2c76c63606..eadda5bf86 100644
--- a/src/applications/project/controller/PhabricatorProjectManageController.php
+++ b/src/applications/project/controller/PhabricatorProjectManageController.php
@@ -1,156 +1,157 @@
<?php
final class PhabricatorProjectManageController
extends PhabricatorProjectController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$response = $this->loadProject();
if ($response) {
return $response;
}
$viewer = $request->getUser();
$project = $this->getProject();
$id = $project->getID();
$picture = $project->getProfileImageURI();
$header = id(new PHUIHeaderView())
->setHeader(pht('Project History'))
->setUser($viewer)
->setPolicyObject($project);
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
} else {
$header->setStatus('fa-ban', 'red', pht('Archived'));
}
$curtain = $this->buildCurtain($project);
$properties = $this->buildPropertyListView($project);
$timeline = $this->buildTransactionTimeline(
$project,
new PhabricatorProjectTransactionQuery());
$timeline->setShouldTerminate(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorProject::ITEM_MANAGE);
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_MANAGE);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Manage'));
$crumbs->setBorder(true);
require_celerity_resource('project-view-css');
$manage = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->addPropertySection(pht('Details'), $properties)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setMainColumn(
array(
$timeline,
));
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle(
array(
$project->getDisplayName(),
pht('Manage'),
))
->appendChild(
array(
$manage,
));
}
private function buildCurtain(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$curtain = $this->newCurtainView($project);
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Details'))
->setIcon('fa-pencil')
->setHref($this->getApplicationURI("edit/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Menu'))
->setIcon('fa-th-list')
->setHref($this->getApplicationURI("{$id}/item/configure/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Edit Picture'))
->setIcon('fa-picture-o')
->setHref($this->getApplicationURI("picture/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
if ($project->isArchived()) {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Activate Project'))
->setIcon('fa-check')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Archive Project'))
->setIcon('fa-ban')
->setHref($this->getApplicationURI("archive/{$id}/"))
->setDisabled(!$can_edit)
->setWorkflow(true));
}
return $curtain;
}
private function buildPropertyListView(
PhabricatorProject $project) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
$view->addProperty(
pht('Looks Like'),
$viewer->renderHandle($project->getPHID())->setAsTag(true));
$slugs = $project->getSlugs();
$tags = mpull($slugs, 'getSlug');
$view->addProperty(
pht('Hashtags'),
$this->renderHashtags($tags));
$field_list = PhabricatorCustomField::getObjectFields(
$project,
PhabricatorCustomField::ROLE_VIEW);
$field_list->appendFieldsToPropertyList($project, $viewer, $view);
return $view;
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectMembersViewController.php b/src/applications/project/controller/PhabricatorProjectMembersViewController.php
index ad553eb664..ee0b71cefa 100644
--- a/src/applications/project/controller/PhabricatorProjectMembersViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectMembersViewController.php
@@ -1,228 +1,229 @@
<?php
final class PhabricatorProjectMembersViewController
extends PhabricatorProjectController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$project = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withIDs(array($id))
->needMembers(true)
->needWatchers(true)
->needImages(true)
->executeOne();
if (!$project) {
return new Aphront404Response();
}
$this->setProject($project);
$title = pht('Members and Watchers');
$curtain = $this->buildCurtainView($project);
$member_list = id(new PhabricatorProjectMemberListView())
->setUser($viewer)
->setProject($project)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setUserPHIDs($project->getMemberPHIDs())
->setShowNote(true);
$watcher_list = id(new PhabricatorProjectWatcherListView())
->setUser($viewer)
->setProject($project)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setUserPHIDs($project->getWatcherPHIDs())
->setShowNote(true);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorProject::ITEM_MEMBERS);
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_MEMBERS);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Members'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader($title)
->setHeaderIcon('fa-group');
require_celerity_resource('project-view-css');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setMainColumn(array(
$member_list,
$watcher_list,
));
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle(array($project->getName(), $title))
->appendChild($view);
}
private function buildCurtainView(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
$curtain = $this->newCurtainView();
$is_locked = $project->getIsMembershipLocked();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$supports_edit = $project->supportsEditMembers();
$can_join = $supports_edit && PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_JOIN);
$can_leave = $supports_edit && (!$is_locked || $can_edit);
$viewer_phid = $viewer->getPHID();
if (!$project->isUserMember($viewer_phid)) {
$curtain->addAction(
id(new PhabricatorActionView())
->setHref('/project/update/'.$project->getID().'/join/')
->setIcon('fa-plus')
->setDisabled(!$can_join)
->setWorkflow(true)
->setName(pht('Join Project')));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setHref('/project/update/'.$project->getID().'/leave/')
->setIcon('fa-times')
->setDisabled(!$can_leave)
->setWorkflow(true)
->setName(pht('Leave Project')));
}
if (!$project->isUserWatcher($viewer->getPHID())) {
$curtain->addAction(
id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/watch/'.$project->getID().'/')
->setIcon('fa-eye')
->setName(pht('Watch Project')));
} else {
$curtain->addAction(
id(new PhabricatorActionView())
->setWorkflow(true)
->setHref('/project/unwatch/'.$project->getID().'/')
->setIcon('fa-eye-slash')
->setName(pht('Unwatch Project')));
}
$can_silence = $project->isUserMember($viewer_phid);
$is_silenced = $this->isProjectSilenced($project);
if ($is_silenced) {
$silence_text = pht('Enable Mail');
} else {
$silence_text = pht('Disable Mail');
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName($silence_text)
->setIcon('fa-envelope-o')
->setHref("/project/silence/{$id}/")
->setWorkflow(true)
->setDisabled(!$can_silence));
$can_add = $can_edit && $supports_edit;
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Add Members'))
->setIcon('fa-user-plus')
->setHref("/project/members/{$id}/add/")
->setWorkflow(true)
->setDisabled(!$can_add));
$can_lock = $can_edit && $supports_edit && $this->hasApplicationCapability(
ProjectCanLockProjectsCapability::CAPABILITY);
if ($is_locked) {
$lock_name = pht('Unlock Project');
$lock_icon = 'fa-unlock';
} else {
$lock_name = pht('Lock Project');
$lock_icon = 'fa-lock';
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName($lock_name)
->setIcon($lock_icon)
->setHref($this->getApplicationURI("lock/{$id}/"))
->setDisabled(!$can_lock)
->setWorkflow(true));
if ($project->isMilestone()) {
$icon_key = PhabricatorProjectIconSet::getMilestoneIconKey();
$header = PhabricatorProjectIconSet::getIconName($icon_key);
$note = pht(
'Members of the parent project are members of this project.');
$show_join = false;
} else if ($project->getHasSubprojects()) {
$header = pht('Parent Project');
$note = pht(
'Members of all subprojects are members of this project.');
$show_join = false;
} else if ($project->getIsMembershipLocked()) {
$header = pht('Locked Project');
$note = pht(
'Users with access may join this project, but may not leave.');
$show_join = true;
} else {
$header = pht('Normal Project');
$note = pht('Users with access may join and leave this project.');
$show_join = true;
}
$curtain->newPanel()
->setHeaderText($header)
->appendChild($note);
if ($show_join) {
$descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions(
$viewer,
$project);
$curtain->newPanel()
->setHeaderText(pht('Joinable By'))
->appendChild($descriptions[PhabricatorPolicyCapability::CAN_JOIN]);
}
return $curtain;
}
private function isProjectSilenced(PhabricatorProject $project) {
$viewer = $this->getViewer();
$viewer_phid = $viewer->getPHID();
if (!$viewer_phid) {
return false;
}
$edge_type = PhabricatorProjectSilencedEdgeType::EDGECONST;
$silenced = PhabricatorEdgeQuery::loadDestinationPHIDs(
$project->getPHID(),
$edge_type);
$silenced = array_fuse($silenced);
return isset($silenced[$viewer_phid]);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectProfileController.php b/src/applications/project/controller/PhabricatorProjectProfileController.php
index 6e1e7677e6..67b94f7fa9 100644
--- a/src/applications/project/controller/PhabricatorProjectProfileController.php
+++ b/src/applications/project/controller/PhabricatorProjectProfileController.php
@@ -1,314 +1,315 @@
<?php
final class PhabricatorProjectProfileController
extends PhabricatorProjectController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$response = $this->loadProject();
if ($response) {
return $response;
}
$viewer = $request->getUser();
$project = $this->getProject();
$id = $project->getID();
$picture = $project->getProfileImageURI();
$icon = $project->getDisplayIconIcon();
$icon_name = $project->getDisplayIconName();
$tag = id(new PHUITagView())
->setIcon($icon)
->setName($icon_name)
->addClass('project-view-header-tag')
->setType(PHUITagView::TYPE_SHADE);
$header = id(new PHUIHeaderView())
->setHeader(array($project->getDisplayName(), $tag))
->setUser($viewer)
->setPolicyObject($project)
->setProfileHeader(true);
if ($project->getStatus() == PhabricatorProjectStatus::STATUS_ACTIVE) {
$header->setStatus('fa-check', 'bluegrey', pht('Active'));
} else {
$header->setStatus('fa-ban', 'red', pht('Archived'));
}
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
if ($can_edit) {
$header->setImageEditURL($this->getApplicationURI("picture/{$id}/"));
}
$properties = $this->buildPropertyListView($project);
$watch_action = $this->renderWatchAction($project);
$header->addActionLink($watch_action);
$subtype = $project->newSubtypeObject();
if ($subtype && $subtype->hasTagView()) {
$subtype_tag = $subtype->newTagView();
$header->addTag($subtype_tag);
}
$milestone_list = $this->buildMilestoneList($project);
$subproject_list = $this->buildSubprojectList($project);
$member_list = id(new PhabricatorProjectMemberListView())
->setUser($viewer)
->setProject($project)
->setLimit(5)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setUserPHIDs($project->getMemberPHIDs());
$watcher_list = id(new PhabricatorProjectWatcherListView())
->setUser($viewer)
->setProject($project)
->setLimit(5)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setUserPHIDs($project->getWatcherPHIDs());
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorProject::ITEM_PROFILE);
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_PROFILE);
$stories = id(new PhabricatorFeedQuery())
->setViewer($viewer)
->withFilterPHIDs(
array(
$project->getPHID(),
))
->setLimit(50)
->execute();
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIcon('fa-list-ul'))
->setText(pht('View All'))
->setHref('/feed/?projectPHIDs='.$project->getPHID());
$feed_header = id(new PHUIHeaderView())
->setHeader(pht('Recent Activity'))
->addActionLink($view_all);
$feed = $this->renderStories($stories);
$feed = id(new PHUIObjectBoxView())
->setHeader($feed_header)
->addClass('project-view-feed')
->appendChild($feed);
require_celerity_resource('project-view-css');
$home = id(new PHUITwoColumnView())
->setHeader($header)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setMainColumn(
array(
$properties,
$feed,
))
->setSideColumn(
array(
$milestone_list,
$subproject_list,
$member_list,
$watcher_list,
));
$crumbs = $this->buildApplicationCrumbs();
$crumbs->setBorder(true);
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle($project->getDisplayName())
->setPageObjectPHIDs(array($project->getPHID()))
->appendChild($home);
}
private function buildPropertyListView(
PhabricatorProject $project) {
$request = $this->getRequest();
$viewer = $request->getUser();
$view = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($project);
$field_list = PhabricatorCustomField::getObjectFields(
$project,
PhabricatorCustomField::ROLE_VIEW);
$field_list->appendFieldsToPropertyList($project, $viewer, $view);
if (!$view->hasAnyProperties()) {
return null;
}
$header = id(new PHUIHeaderView())
->setHeader(pht('Details'));
$view = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($view)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addClass('project-view-properties');
return $view;
}
private function renderStories(array $stories) {
assert_instances_of($stories, 'PhabricatorFeedStory');
$builder = new PhabricatorFeedBuilder($stories);
$builder->setUser($this->getRequest()->getUser());
$builder->setShowHovercards(true);
$view = $builder->buildView();
return $view;
}
private function renderWatchAction(PhabricatorProject $project) {
$viewer = $this->getViewer();
$id = $project->getID();
if (!$viewer->isLoggedIn()) {
$is_watcher = false;
$is_ancestor = false;
} else {
$viewer_phid = $viewer->getPHID();
$is_watcher = $project->isUserWatcher($viewer_phid);
$is_ancestor = $project->isUserAncestorWatcher($viewer_phid);
}
if ($is_ancestor && !$is_watcher) {
$watch_icon = 'fa-eye';
$watch_text = pht('Watching Ancestor');
$watch_href = "/project/watch/{$id}/?via=profile";
$watch_disabled = true;
} else if (!$is_watcher) {
$watch_icon = 'fa-eye';
$watch_text = pht('Watch Project');
$watch_href = "/project/watch/{$id}/?via=profile";
$watch_disabled = false;
} else {
$watch_icon = 'fa-eye-slash';
$watch_text = pht('Unwatch Project');
$watch_href = "/project/unwatch/{$id}/?via=profile";
$watch_disabled = false;
}
$watch_icon = id(new PHUIIconView())
->setIcon($watch_icon);
return id(new PHUIButtonView())
->setTag('a')
->setWorkflow(true)
->setIcon($watch_icon)
->setText($watch_text)
->setHref($watch_href)
->setDisabled($watch_disabled);
}
private function buildMilestoneList(PhabricatorProject $project) {
if (!$project->getHasMilestones()) {
return null;
}
$viewer = $this->getViewer();
$id = $project->getID();
$milestones = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withIsMilestone(true)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->setOrderVector(array('milestoneNumber', 'id'))
->execute();
if (!$milestones) {
return null;
}
$milestone_list = id(new PhabricatorProjectListView())
->setUser($viewer)
->setProjects($milestones)
->renderList();
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIcon('fa-list-ul'))
->setText(pht('View All'))
->setHref("/project/subprojects/{$id}/");
$header = id(new PHUIHeaderView())
->setHeader(pht('Milestones'))
->addActionLink($view_all);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($milestone_list);
}
private function buildSubprojectList(PhabricatorProject $project) {
if (!$project->getHasSubprojects()) {
return null;
}
$viewer = $this->getViewer();
$id = $project->getID();
$limit = 25;
$subprojects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withStatuses(
array(
PhabricatorProjectStatus::STATUS_ACTIVE,
))
->withIsMilestone(false)
->setLimit($limit)
->execute();
if (!$subprojects) {
return null;
}
$subproject_list = id(new PhabricatorProjectListView())
->setUser($viewer)
->setProjects($subprojects)
->renderList();
$view_all = id(new PHUIButtonView())
->setTag('a')
->setIcon(
id(new PHUIIconView())
->setIcon('fa-list-ul'))
->setText(pht('View All'))
->setHref("/project/subprojects/{$id}/");
$header = id(new PHUIHeaderView())
->setHeader(pht('Subprojects'))
->addActionLink($view_all);
return id(new PHUIObjectBoxView())
->setHeader($header)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($subproject_list);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
index 36736c78ba..be787e7a4f 100644
--- a/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
+++ b/src/applications/project/controller/PhabricatorProjectSubprojectsController.php
@@ -1,226 +1,227 @@
<?php
final class PhabricatorProjectSubprojectsController
extends PhabricatorProjectController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$response = $this->loadProject();
if ($response) {
return $response;
}
$project = $this->getProject();
$id = $project->getID();
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$allows_subprojects = $project->supportsSubprojects();
$allows_milestones = $project->supportsMilestones();
$subproject_list = null;
$milestone_list = null;
if ($allows_subprojects) {
$subprojects = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withIsMilestone(false)
->execute();
$subproject_list = id(new PHUIObjectBoxView())
->setHeaderText(pht('%s Subprojects', $project->getName()))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList(
id(new PhabricatorProjectListView())
->setUser($viewer)
->setProjects($subprojects)
->setNoDataString(pht('This project has no subprojects.'))
->renderList());
} else {
$subprojects = array();
}
if ($allows_milestones) {
$milestones = id(new PhabricatorProjectQuery())
->setViewer($viewer)
->withParentProjectPHIDs(array($project->getPHID()))
->needImages(true)
->withIsMilestone(true)
->setOrderVector(array('milestoneNumber', 'id'))
->execute();
$milestone_list = id(new PHUIObjectBoxView())
->setHeaderText(pht('%s Milestones', $project->getName()))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList(
id(new PhabricatorProjectListView())
->setUser($viewer)
->setProjects($milestones)
->setNoDataString(pht('This project has no milestones.'))
->renderList());
} else {
$milestones = array();
}
$curtain = $this->buildCurtainView(
$project,
$milestones,
$subprojects);
- $nav = $this->getProfileMenu();
- $nav->selectFilter(PhabricatorProject::ITEM_SUBPROJECTS);
+ $nav = $this->newNavigation(
+ $project,
+ PhabricatorProject::ITEM_SUBPROJECTS);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(pht('Subprojects'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Subprojects and Milestones'))
->setHeaderIcon('fa-sitemap');
require_celerity_resource('project-view-css');
// This page isn't reachable via UI, but make it pretty anyways.
$info_view = null;
if (!$milestone_list && !$subproject_list) {
$info_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
->appendChild(pht('Milestone projects do not support subprojects '.
'or milestones.'));
}
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->addClass('project-view-home')
->addClass('project-view-people-home')
->setMainColumn(array(
$info_view,
$subproject_list,
$milestone_list,
));
return $this->newPage()
->setNavigation($nav)
->setCrumbs($crumbs)
->setTitle(array($project->getName(), pht('Subprojects')))
->appendChild($view);
}
private function buildCurtainView(
PhabricatorProject $project,
array $milestones,
array $subprojects) {
$viewer = $this->getViewer();
$id = $project->getID();
$can_create = $this->hasApplicationCapability(
ProjectCreateProjectsCapability::CAPABILITY);
$can_edit = PhabricatorPolicyFilter::hasCapability(
$viewer,
$project,
PhabricatorPolicyCapability::CAN_EDIT);
$allows_subprojects = $project->supportsSubprojects();
$allows_milestones = $project->supportsMilestones();
$curtain = $this->newCurtainView();
$can_subproject = ($can_create && $can_edit && $allows_subprojects);
// If we're offering to create the first subproject, we're going to warn
// the user about the effects before moving forward.
if ($can_subproject && !$subprojects) {
$subproject_href = "/project/warning/{$id}/";
$subproject_disabled = false;
$subproject_workflow = true;
} else {
$subproject_href = "/project/edit/?parent={$id}";
$subproject_disabled = !$can_subproject;
$subproject_workflow = !$can_subproject;
}
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Create Subproject'))
->setIcon('fa-plus')
->setHref($subproject_href)
->setDisabled($subproject_disabled)
->setWorkflow($subproject_workflow));
if ($allows_milestones && $milestones) {
$milestone_text = pht('Create Next Milestone');
} else {
$milestone_text = pht('Create Milestone');
}
$can_milestone = ($can_create && $can_edit && $allows_milestones);
$milestone_href = "/project/edit/?milestone={$id}";
$curtain->addAction(
id(new PhabricatorActionView())
->setName($milestone_text)
->setIcon('fa-plus')
->setHref($milestone_href)
->setDisabled(!$can_milestone)
->setWorkflow(!$can_milestone));
if (!$project->supportsSubprojects()) {
$note = pht(
'This project is a milestone, and milestones may not have '.
'subprojects.');
} else {
if (!$subprojects) {
$note = pht('Subprojects can be created for this project.');
} else {
$note = pht('This project has subprojects.');
}
}
$curtain->newPanel()
->setHeaderText(pht('Subprojects'))
->appendChild($note);
if (!$project->supportsSubprojects()) {
$note = pht(
'This project is already a milestone, and milestones may not '.
'have their own milestones.');
} else {
if (!$milestones) {
$note = pht('Milestones can be created for this project.');
} else {
$note = pht('This project has milestones.');
}
}
$curtain->newPanel()
->setHeaderText(pht('Milestones'))
->appendChild($note);
return $curtain;
}
private function renderStatus($icon, $target, $note) {
$item = id(new PHUIStatusItemView())
->setIcon($icon)
->setTarget(phutil_tag('strong', array(), $target))
->setNote($note);
return id(new PHUIStatusListView())
->addItem($item);
}
}
diff --git a/src/applications/project/controller/PhabricatorProjectViewController.php b/src/applications/project/controller/PhabricatorProjectViewController.php
index c5c536bfa1..beb622ea50 100644
--- a/src/applications/project/controller/PhabricatorProjectViewController.php
+++ b/src/applications/project/controller/PhabricatorProjectViewController.php
@@ -1,48 +1,48 @@
<?php
final class PhabricatorProjectViewController
extends PhabricatorProjectController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$request = $this->getRequest();
$viewer = $request->getViewer();
$response = $this->loadProject();
if ($response) {
return $response;
}
$project = $this->getProject();
$engine = $this->getProfileMenuEngine();
- $default = $engine->getDefaultItem();
+ $default = $engine->getDefaultMenuItemConfiguration();
// If defaults are broken somehow, serve the manage page. See T13033 for
// discussion.
if ($default) {
$default_key = $default->getBuiltinKey();
} else {
$default_key = PhabricatorProject::ITEM_MANAGE;
}
switch ($default_key) {
case PhabricatorProject::ITEM_WORKBOARD:
$controller_object = new PhabricatorProjectBoardViewController();
break;
case PhabricatorProject::ITEM_PROFILE:
$controller_object = new PhabricatorProjectProfileController();
break;
case PhabricatorProject::ITEM_MANAGE:
$controller_object = new PhabricatorProjectManageController();
break;
default:
return $engine->buildResponse();
}
return $this->delegateToController($controller_object);
}
}
diff --git a/src/applications/search/controller/PhabricatorApplicationSearchController.php b/src/applications/search/controller/PhabricatorApplicationSearchController.php
index 4bf4929f4b..cab5ae61ff 100644
--- a/src/applications/search/controller/PhabricatorApplicationSearchController.php
+++ b/src/applications/search/controller/PhabricatorApplicationSearchController.php
@@ -1,993 +1,991 @@
<?php
final class PhabricatorApplicationSearchController
extends PhabricatorSearchBaseController {
private $searchEngine;
private $navigation;
private $queryKey;
private $preface;
private $activeQuery;
public function setPreface($preface) {
$this->preface = $preface;
return $this;
}
public function getPreface() {
return $this->preface;
}
public function setQueryKey($query_key) {
$this->queryKey = $query_key;
return $this;
}
protected function getQueryKey() {
return $this->queryKey;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
protected function getNavigation() {
return $this->navigation;
}
public function setSearchEngine(
PhabricatorApplicationSearchEngine $search_engine) {
$this->searchEngine = $search_engine;
return $this;
}
protected function getSearchEngine() {
return $this->searchEngine;
}
protected function getActiveQuery() {
if (!$this->activeQuery) {
throw new Exception(pht('There is no active query yet.'));
}
return $this->activeQuery;
}
protected function validateDelegatingController() {
$parent = $this->getDelegatingController();
if (!$parent) {
throw new Exception(
pht('You must delegate to this controller, not invoke it directly.'));
}
$engine = $this->getSearchEngine();
if (!$engine) {
throw new PhutilInvalidStateException('setEngine');
}
$engine->setViewer($this->getRequest()->getUser());
$parent = $this->getDelegatingController();
}
public function processRequest() {
$this->validateDelegatingController();
$query_action = $this->getRequest()->getURIData('queryAction');
if ($query_action == 'export') {
return $this->processExportRequest();
}
$key = $this->getQueryKey();
if ($key == 'edit') {
return $this->processEditRequest();
} else {
return $this->processSearchRequest();
}
}
private function processSearchRequest() {
$parent = $this->getDelegatingController();
$request = $this->getRequest();
$user = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
if (!$nav) {
$nav = $this->buildNavigation();
}
if ($request->isFormPost()) {
$saved_query = $engine->buildSavedQueryFromRequest($request);
$engine->saveQuery($saved_query);
return id(new AphrontRedirectResponse())->setURI(
$engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R');
}
$named_query = null;
$run_query = true;
$query_key = $this->queryKey;
if ($this->queryKey == 'advanced') {
$run_query = false;
$query_key = $request->getStr('query');
} else if (!strlen($this->queryKey)) {
$found_query_data = false;
if ($request->isHTTPGet() || $request->isQuicksand()) {
// If this is a GET request and it has some query data, don't
// do anything unless it's only before= or after=. We'll build and
// execute a query from it below. This allows external tools to build
// URIs like "/query/?users=a,b".
$pt_data = $request->getPassthroughRequestData();
$exempt = array(
'before' => true,
'after' => true,
'nux' => true,
'overheated' => true,
);
foreach ($pt_data as $pt_key => $pt_value) {
if (isset($exempt[$pt_key])) {
continue;
}
$found_query_data = true;
break;
}
}
if (!$found_query_data) {
// Otherwise, there's no query data so just run the user's default
// query for this application.
$query_key = $engine->getDefaultQueryKey();
}
}
if ($engine->isBuiltinQuery($query_key)) {
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
} else if ($query_key) {
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($user)
->withQueryKeys(array($query_key))
->executeOne();
if (!$saved_query) {
return new Aphront404Response();
}
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
} else {
$saved_query = $engine->buildSavedQueryFromRequest($request);
// Save the query to generate a query key, so "Save Custom Query..." and
// other features like "Bulk Edit" and "Export Data" work correctly.
$engine->saveQuery($saved_query);
}
$this->activeQuery = $saved_query;
$nav->selectFilter(
'query/'.$saved_query->getQueryKey(),
'query/advanced');
$form = id(new AphrontFormView())
->setUser($user)
->setAction($request->getPath());
$engine->buildSearchForm($form, $saved_query);
$errors = $engine->getErrors();
if ($errors) {
$run_query = false;
}
$submit = id(new AphrontFormSubmitControl())
->setValue(pht('Search'));
if ($run_query && !$named_query && $user->isLoggedIn()) {
$save_button = id(new PHUIButtonView())
->setTag('a')
->setHref('/search/edit/key/'.$saved_query->getQueryKey().'/')
->setText(pht('Save Query'))
->setIcon('fa-floppy-o');
$submit->addButton($save_button);
}
// TODO: A "Create Dashboard Panel" action goes here somewhere once
// we sort out T5307.
$form->appendChild($submit);
$body = array();
if ($this->getPreface()) {
$body[] = $this->getPreface();
}
if ($named_query) {
$title = $named_query->getQueryName();
} else {
$title = pht('Advanced Search');
}
$header = id(new PHUIHeaderView())
->setHeader($title)
->setProfileHeader(true);
$box = id(new PHUIObjectBoxView())
->setHeader($header)
->addClass('application-search-results');
if ($run_query || $named_query) {
$box->setShowHide(
pht('Edit Query'),
pht('Hide Query'),
$form,
$this->getApplicationURI('query/advanced/?query='.$query_key),
(!$named_query ? true : false));
} else {
$box->setForm($form);
}
$body[] = $box;
$more_crumbs = null;
if ($run_query) {
$exec_errors = array();
$box->setAnchor(
id(new PhabricatorAnchorView())
->setAnchorName('R'));
try {
$engine->setRequest($request);
$query = $engine->buildQueryFromSavedQuery($saved_query);
$pager = $engine->newPagerForSavedQuery($saved_query);
$pager->readFromRequest($request);
$query->setReturnPartialResultsOnOverheat(true);
$objects = $engine->executeQuery($query, $pager);
$force_nux = $request->getBool('nux');
if (!$objects || $force_nux) {
$nux_view = $this->renderNewUserView($engine, $force_nux);
} else {
$nux_view = null;
}
$is_overflowing =
$pager->willShowPagingControls() &&
$engine->getResultBucket($saved_query);
$force_overheated = $request->getBool('overheated');
$is_overheated = $query->getIsOverheated() || $force_overheated;
if ($nux_view) {
$box->appendChild($nux_view);
} else {
$list = $engine->renderResults($objects, $saved_query);
if (!($list instanceof PhabricatorApplicationSearchResultView)) {
throw new Exception(
pht(
'SearchEngines must render a "%s" object, but this engine '.
'(of class "%s") rendered something else ("%s").',
'PhabricatorApplicationSearchResultView',
get_class($engine),
phutil_describe_type($list)));
}
if ($list->getObjectList()) {
$box->setObjectList($list->getObjectList());
}
if ($list->getTable()) {
$box->setTable($list->getTable());
}
if ($list->getInfoView()) {
$box->setInfoView($list->getInfoView());
}
if ($is_overflowing) {
$box->appendChild($this->newOverflowingView());
}
if ($list->getContent()) {
$box->appendChild($list->getContent());
}
if ($is_overheated) {
$box->appendChild($this->newOverheatedView($objects));
}
$result_header = $list->getHeader();
if ($result_header) {
$box->setHeader($result_header);
$header = $result_header;
}
$actions = $list->getActions();
if ($actions) {
foreach ($actions as $action) {
$header->addActionLink($action);
}
}
$use_actions = $engine->newUseResultsActions($saved_query);
// TODO: Eventually, modularize all this stuff.
$builtin_use_actions = $this->newBuiltinUseActions();
if ($builtin_use_actions) {
foreach ($builtin_use_actions as $builtin_use_action) {
$use_actions[] = $builtin_use_action;
}
}
if ($use_actions) {
$use_dropdown = $this->newUseResultsDropdown(
$saved_query,
$use_actions);
$header->addActionLink($use_dropdown);
}
$more_crumbs = $list->getCrumbs();
if ($pager->willShowPagingControls()) {
$pager_box = id(new PHUIBoxView())
->setColor(PHUIBoxView::GREY)
->addClass('application-search-pager')
->appendChild($pager);
$body[] = $pager_box;
}
}
} catch (PhabricatorTypeaheadInvalidTokenException $ex) {
$exec_errors[] = pht(
'This query specifies an invalid parameter. Review the '.
'query parameters and correct errors.');
} catch (PhutilSearchQueryCompilerSyntaxException $ex) {
$exec_errors[] = $ex->getMessage();
} catch (PhabricatorSearchConstraintException $ex) {
$exec_errors[] = $ex->getMessage();
} catch (PhabricatorInvalidQueryCursorException $ex) {
$exec_errors[] = $ex->getMessage();
}
// The engine may have encountered additional errors during rendering;
// merge them in and show everything.
foreach ($engine->getErrors() as $error) {
$exec_errors[] = $error;
}
$errors = $exec_errors;
}
if ($errors) {
$box->setFormErrors($errors, pht('Query Errors'));
}
$crumbs = $parent
->buildApplicationCrumbs()
->setBorder(true);
if ($more_crumbs) {
$query_uri = $engine->getQueryResultsPageURI($saved_query->getQueryKey());
$crumbs->addTextCrumb($title, $query_uri);
foreach ($more_crumbs as $crumb) {
$crumbs->addCrumb($crumb);
}
} else {
$crumbs->addTextCrumb($title);
}
require_celerity_resource('application-search-view-css');
return $this->newPage()
- ->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Query: %s', $title))
->setCrumbs($crumbs)
->setNavigation($nav)
->addClass('application-search-view')
->appendChild($body);
}
private function processExportRequest() {
$viewer = $this->getViewer();
$engine = $this->getSearchEngine();
$request = $this->getRequest();
if (!$this->canExport()) {
return new Aphront404Response();
}
$query_key = $this->getQueryKey();
if ($engine->isBuiltinQuery($query_key)) {
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
} else if ($query_key) {
$saved_query = id(new PhabricatorSavedQueryQuery())
->setViewer($viewer)
->withQueryKeys(array($query_key))
->executeOne();
} else {
$saved_query = null;
}
if (!$saved_query) {
return new Aphront404Response();
}
$cancel_uri = $engine->getQueryResultsPageURI($query_key);
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
if ($named_query) {
$filename = $named_query->getQueryName();
$sheet_title = $named_query->getQueryName();
} else {
$filename = $engine->getResultTypeDescription();
$sheet_title = $engine->getResultTypeDescription();
}
$filename = phutil_utf8_strtolower($filename);
$filename = PhabricatorFile::normalizeFileName($filename);
$all_formats = PhabricatorExportFormat::getAllExportFormats();
$available_options = array();
$unavailable_options = array();
$formats = array();
$unavailable_formats = array();
foreach ($all_formats as $key => $format) {
if ($format->isExportFormatEnabled()) {
$available_options[$key] = $format->getExportFormatName();
$formats[$key] = $format;
} else {
$unavailable_options[$key] = pht(
'%s (Not Available)',
$format->getExportFormatName());
$unavailable_formats[$key] = $format;
}
}
$format_options = $available_options + $unavailable_options;
// Try to default to the format the user used last time. If you just
// exported to Excel, you probably want to export to Excel again.
$format_key = $this->readExportFormatPreference();
if (!isset($formats[$format_key])) {
$format_key = head_key($format_options);
}
// Check if this is a large result set or not. If we're exporting a
// large amount of data, we'll build the actual export file in the daemons.
$threshold = 1000;
$query = $engine->buildQueryFromSavedQuery($saved_query);
$pager = $engine->newPagerForSavedQuery($saved_query);
$pager->setPageSize($threshold + 1);
$objects = $engine->executeQuery($query, $pager);
$object_count = count($objects);
$is_large_export = ($object_count > $threshold);
$errors = array();
$e_format = null;
if ($request->isFormPost()) {
$format_key = $request->getStr('format');
if (isset($unavailable_formats[$format_key])) {
$unavailable = $unavailable_formats[$format_key];
$instructions = $unavailable->getInstallInstructions();
$markup = id(new PHUIRemarkupView($viewer, $instructions))
->setRemarkupOption(
PHUIRemarkupView::OPTION_PRESERVE_LINEBREAKS,
false);
return $this->newDialog()
->setTitle(pht('Export Format Not Available'))
->appendChild($markup)
->addCancelButton($cancel_uri, pht('Done'));
}
$format = idx($formats, $format_key);
if (!$format) {
$e_format = pht('Invalid');
$errors[] = pht('Choose a valid export format.');
}
if (!$errors) {
$this->writeExportFormatPreference($format_key);
$export_engine = id(new PhabricatorExportEngine())
->setViewer($viewer)
->setSearchEngine($engine)
->setSavedQuery($saved_query)
->setTitle($sheet_title)
->setFilename($filename)
->setExportFormat($format);
if ($is_large_export) {
$job = $export_engine->newBulkJob($request);
return id(new AphrontRedirectResponse())
->setURI($job->getMonitorURI());
} else {
$file = $export_engine->exportFile();
return $file->newDownloadResponse();
}
}
}
$export_form = id(new AphrontFormView())
->setViewer($viewer)
->appendControl(
id(new AphrontFormSelectControl())
->setName('format')
->setLabel(pht('Format'))
->setError($e_format)
->setValue($format_key)
->setOptions($format_options));
if ($is_large_export) {
$submit_button = pht('Continue');
} else {
$submit_button = pht('Download Data');
}
return $this->newDialog()
->setTitle(pht('Export Results'))
->setErrors($errors)
->appendForm($export_form)
->addCancelButton($cancel_uri)
->addSubmitButton($submit_button);
}
private function processEditRequest() {
$parent = $this->getDelegatingController();
$request = $this->getRequest();
$viewer = $request->getUser();
$engine = $this->getSearchEngine();
$nav = $this->getNavigation();
if (!$nav) {
$nav = $this->buildNavigation();
}
$named_queries = $engine->loadAllNamedQueries();
$can_global = $viewer->getIsAdmin();
$groups = array(
'personal' => array(
'name' => pht('Personal Saved Queries'),
'items' => array(),
'edit' => true,
),
'global' => array(
'name' => pht('Global Saved Queries'),
'items' => array(),
'edit' => $can_global,
),
);
foreach ($named_queries as $named_query) {
if ($named_query->isGlobal()) {
$group = 'global';
} else {
$group = 'personal';
}
$groups[$group]['items'][] = $named_query;
}
$default_key = $engine->getDefaultQueryKey();
$lists = array();
foreach ($groups as $group) {
$lists[] = $this->newQueryListView(
$group['name'],
$group['items'],
$default_key,
$group['edit']);
}
$crumbs = $parent
->buildApplicationCrumbs()
->addTextCrumb(pht('Saved Queries'), $engine->getQueryManagementURI())
->setBorder(true);
$nav->selectFilter('query/edit');
$header = id(new PHUIHeaderView())
->setHeader(pht('Saved Queries'))
->setProfileHeader(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($lists);
return $this->newPage()
- ->setApplicationMenu($this->buildApplicationMenu())
->setTitle(pht('Saved Queries'))
->setCrumbs($crumbs)
->setNavigation($nav)
->appendChild($view);
}
private function newQueryListView(
$list_name,
array $named_queries,
$default_key,
$can_edit) {
$engine = $this->getSearchEngine();
$viewer = $this->getViewer();
$list = id(new PHUIObjectItemListView())
->setViewer($viewer);
if ($can_edit) {
$list_id = celerity_generate_unique_node_id();
$list->setID($list_id);
Javelin::initBehavior(
'search-reorder-queries',
array(
'listID' => $list_id,
'orderURI' => '/search/order/'.get_class($engine).'/',
));
}
foreach ($named_queries as $named_query) {
$class = get_class($engine);
$key = $named_query->getQueryKey();
$item = id(new PHUIObjectItemView())
->setHeader($named_query->getQueryName())
->setHref($engine->getQueryResultsPageURI($key));
if ($named_query->getIsDisabled()) {
if ($can_edit) {
$item->setDisabled(true);
} else {
// If an item is disabled and you don't have permission to edit it,
// just skip it.
continue;
}
}
if ($can_edit) {
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
$icon = 'fa-plus';
$disable_name = pht('Enable');
} else {
$icon = 'fa-times';
if ($named_query->getIsBuiltin()) {
$disable_name = pht('Disable');
} else {
$disable_name = pht('Delete');
}
}
if ($named_query->getID()) {
$disable_href = '/search/delete/id/'.$named_query->getID().'/';
} else {
$disable_href = '/search/delete/key/'.$key.'/'.$class.'/';
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($icon)
->setHref($disable_href)
->setRenderNameAsTooltip(true)
->setName($disable_name)
->setWorkflow(true));
}
$default_disabled = $named_query->getIsDisabled();
$default_icon = 'fa-thumb-tack';
if ($default_key === $key) {
$default_color = 'green';
} else {
$default_color = null;
}
$item->addAction(
id(new PHUIListItemView())
->setIcon("{$default_icon} {$default_color}")
->setHref('/search/default/'.$key.'/'.$class.'/')
->setRenderNameAsTooltip(true)
->setName(pht('Make Default'))
->setWorkflow(true)
->setDisabled($default_disabled));
if ($can_edit) {
if ($named_query->getIsBuiltin()) {
$edit_icon = 'fa-lock lightgreytext';
$edit_disabled = true;
$edit_name = pht('Builtin');
$edit_href = null;
} else {
$edit_icon = 'fa-pencil';
$edit_disabled = false;
$edit_name = pht('Edit');
$edit_href = '/search/edit/id/'.$named_query->getID().'/';
}
$item->addAction(
id(new PHUIListItemView())
->setIcon($edit_icon)
->setHref($edit_href)
->setRenderNameAsTooltip(true)
->setName($edit_name)
->setDisabled($edit_disabled));
}
$item->setGrippable($can_edit);
$item->addSigil('named-query');
$item->setMetadata(
array(
'queryKey' => $named_query->getQueryKey(),
));
$list->addItem($item);
}
$list->setNoDataString(pht('No saved queries.'));
return id(new PHUIObjectBoxView())
->setHeaderText($list_name)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setObjectList($list);
}
public function buildApplicationMenu() {
$menu = $this->getDelegatingController()
->buildApplicationMenu();
if ($menu instanceof PHUIApplicationMenuView) {
$menu->setSearchEngine($this->getSearchEngine());
}
return $menu;
}
private function buildNavigation() {
$viewer = $this->getViewer();
$engine = $this->getSearchEngine();
$nav = id(new AphrontSideNavFilterView())
->setUser($viewer)
->setBaseURI(new PhutilURI($this->getApplicationURI()));
$engine->addNavigationItems($nav->getMenu());
return $nav;
}
private function renderNewUserView(
PhabricatorApplicationSearchEngine $engine,
$force_nux) {
// Don't render NUX if the user has clicked away from the default page.
if (strlen($this->getQueryKey())) {
return null;
}
// Don't put NUX in panels because it would be weird.
if ($engine->isPanelContext()) {
return null;
}
// Try to render the view itself first, since this should be very cheap
// (just returning some text).
$nux_view = $engine->renderNewUserView();
if (!$nux_view) {
return null;
}
$query = $engine->newQuery();
if (!$query) {
return null;
}
// Try to load any object at all. If we can, the application has seen some
// use so we just render the normal view.
if (!$force_nux) {
$object = $query
->setViewer(PhabricatorUser::getOmnipotentUser())
->setLimit(1)
->setReturnPartialResultsOnOverheat(true)
->execute();
if ($object) {
return null;
}
}
return $nux_view;
}
private function newUseResultsDropdown(
PhabricatorSavedQuery $query,
array $dropdown_items) {
$viewer = $this->getViewer();
$action_list = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($dropdown_items as $dropdown_item) {
$action_list->addAction($dropdown_item);
}
return id(new PHUIButtonView())
->setTag('a')
->setHref('#')
->setText(pht('Use Results'))
->setIcon('fa-bars')
->setDropdownMenu($action_list)
->addClass('dropdown');
}
private function newOverflowingView() {
$message = pht(
'The query matched more than one page of results. Results are '.
'paginated before bucketing, so later pages may contain additional '.
'results in any bucket.');
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setFlush(true)
->setTitle(pht('Buckets Overflowing'))
->setErrors(
array(
$message,
));
}
public static function newOverheatedError($has_results) {
$overheated_link = phutil_tag(
'a',
array(
'href' => 'https://phurl.io/u/overheated',
'target' => '_blank',
),
pht('Learn More'));
if ($has_results) {
$message = pht(
'This query took too long, so only some results are shown. %s',
$overheated_link);
} else {
$message = pht(
'This query took too long. %s',
$overheated_link);
}
return $message;
}
private function newOverheatedView(array $results) {
$message = self::newOverheatedError((bool)$results);
return id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setFlush(true)
->setTitle(pht('Query Overheated'))
->setErrors(
array(
$message,
));
}
private function newBuiltinUseActions() {
$actions = array();
$request = $this->getRequest();
$viewer = $request->getUser();
$is_dev = PhabricatorEnv::getEnvConfig('phabricator.developer-mode');
$engine = $this->getSearchEngine();
$engine_class = get_class($engine);
$query_key = $this->getActiveQuery()->getQueryKey();
$can_use = $engine->canUseInPanelContext();
$is_installed = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorDashboardApplication',
$viewer);
if ($can_use && $is_installed) {
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-dashboard')
->setName(pht('Add to Dashboard'))
->setWorkflow(true)
->setHref("/dashboard/panel/install/{$engine_class}/{$query_key}/");
}
if ($this->canExport()) {
$export_uri = $engine->getExportURI($query_key);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-download')
->setName(pht('Export Data'))
->setWorkflow(true)
->setHref($export_uri);
}
if ($is_dev) {
$engine = $this->getSearchEngine();
$nux_uri = $engine->getQueryBaseURI();
$nux_uri = id(new PhutilURI($nux_uri))
->replaceQueryParam('nux', true);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-user-plus')
->setName(pht('DEV: New User State'))
->setHref($nux_uri);
}
if ($is_dev) {
$overheated_uri = $this->getRequest()->getRequestURI()
->replaceQueryParam('overheated', true);
$actions[] = id(new PhabricatorActionView())
->setIcon('fa-fire')
->setName(pht('DEV: Overheated State'))
->setHref($overheated_uri);
}
return $actions;
}
private function canExport() {
$engine = $this->getSearchEngine();
if (!$engine->canExport()) {
return false;
}
// Don't allow logged-out users to perform exports. There's no technical
// or policy reason they can't, but we don't normally give them access
// to write files or jobs. For now, just err on the side of caution.
$viewer = $this->getViewer();
if (!$viewer->getPHID()) {
return false;
}
return true;
}
private function readExportFormatPreference() {
$viewer = $this->getViewer();
$export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
return $viewer->getUserSetting($export_key);
}
private function writeExportFormatPreference($value) {
$viewer = $this->getViewer();
$request = $this->getRequest();
if (!$viewer->isLoggedIn()) {
return;
}
$export_key = PhabricatorPolicyFavoritesSetting::SETTINGKEY;
$preferences = PhabricatorUserPreferences::loadUserPreferences($viewer);
$editor = id(new PhabricatorUserPreferencesEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
$xactions = array();
$xactions[] = $preferences->newTransaction($export_key, $value);
$editor->applyTransactions($preferences, $xactions);
}
}
diff --git a/src/view/page/PhabricatorStandardPageView.php b/src/view/page/PhabricatorStandardPageView.php
index cfb1b4abbe..5498a1fb34 100644
--- a/src/view/page/PhabricatorStandardPageView.php
+++ b/src/view/page/PhabricatorStandardPageView.php
@@ -1,900 +1,889 @@
<?php
/**
* This is a standard Phabricator page with menus, Javelin, DarkConsole, and
* basic styles.
*/
final class PhabricatorStandardPageView extends PhabricatorBarePageView
implements AphrontResponseProducerInterface {
private $baseURI;
private $applicationName;
private $glyph;
private $menuContent;
private $showChrome = true;
private $classes = array();
private $disableConsole;
private $pageObjects = array();
private $applicationMenu;
private $showFooter = true;
private $showDurableColumn = true;
private $quicksandConfig = array();
private $tabs;
private $crumbs;
private $navigation;
public function setShowFooter($show_footer) {
$this->showFooter = $show_footer;
return $this;
}
public function getShowFooter() {
return $this->showFooter;
}
- public function setApplicationMenu($application_menu) {
- // NOTE: For now, this can either be a PHUIListView or a
- // PHUIApplicationMenuView.
-
- $this->applicationMenu = $application_menu;
- return $this;
- }
-
- public function getApplicationMenu() {
- return $this->applicationMenu;
- }
-
public function setApplicationName($application_name) {
$this->applicationName = $application_name;
return $this;
}
public function setDisableConsole($disable) {
$this->disableConsole = $disable;
return $this;
}
public function getApplicationName() {
return $this->applicationName;
}
public function setBaseURI($base_uri) {
$this->baseURI = $base_uri;
return $this;
}
public function getBaseURI() {
return $this->baseURI;
}
public function setShowChrome($show_chrome) {
$this->showChrome = $show_chrome;
return $this;
}
public function getShowChrome() {
return $this->showChrome;
}
public function addClass($class) {
$this->classes[] = $class;
return $this;
}
public function setPageObjectPHIDs(array $phids) {
$this->pageObjects = $phids;
return $this;
}
public function setShowDurableColumn($show) {
$this->showDurableColumn = $show;
return $this;
}
public function getShowDurableColumn() {
$request = $this->getRequest();
if (!$request) {
return false;
}
$viewer = $request->getUser();
if (!$viewer->isLoggedIn()) {
return false;
}
$conpherence_installed = PhabricatorApplication::isClassInstalledForViewer(
'PhabricatorConpherenceApplication',
$viewer);
if (!$conpherence_installed) {
return false;
}
if ($this->isQuicksandBlacklistURI()) {
return false;
}
return true;
}
private function isQuicksandBlacklistURI() {
$request = $this->getRequest();
if (!$request) {
return false;
}
$patterns = $this->getQuicksandURIPatternBlacklist();
$path = $request->getRequestURI()->getPath();
foreach ($patterns as $pattern) {
if (preg_match('(^'.$pattern.'$)', $path)) {
return true;
}
}
return false;
}
public function getDurableColumnVisible() {
$column_key = PhabricatorConpherenceColumnVisibleSetting::SETTINGKEY;
return (bool)$this->getUserPreference($column_key, false);
}
public function getDurableColumnMinimize() {
$column_key = PhabricatorConpherenceColumnMinimizeSetting::SETTINGKEY;
return (bool)$this->getUserPreference($column_key, false);
}
public function addQuicksandConfig(array $config) {
$this->quicksandConfig = $config + $this->quicksandConfig;
return $this;
}
public function getQuicksandConfig() {
return $this->quicksandConfig;
}
public function setCrumbs(PHUICrumbsView $crumbs) {
$this->crumbs = $crumbs;
return $this;
}
public function getCrumbs() {
return $this->crumbs;
}
public function setTabs(PHUIListView $tabs) {
$tabs->setType(PHUIListView::TABBAR_LIST);
$tabs->addClass('phabricator-standard-page-tabs');
$this->tabs = $tabs;
return $this;
}
public function getTabs() {
return $this->tabs;
}
public function setNavigation(AphrontSideNavFilterView $navigation) {
$this->navigation = $navigation;
return $this;
}
public function getNavigation() {
return $this->navigation;
}
public function getTitle() {
$glyph_key = PhabricatorTitleGlyphsSetting::SETTINGKEY;
$glyph_on = PhabricatorTitleGlyphsSetting::VALUE_TITLE_GLYPHS;
$glyph_setting = $this->getUserPreference($glyph_key, $glyph_on);
$use_glyph = ($glyph_setting == $glyph_on);
$title = parent::getTitle();
$prefix = null;
if ($use_glyph) {
$prefix = $this->getGlyph();
} else {
$application_name = $this->getApplicationName();
if (strlen($application_name)) {
$prefix = '['.$application_name.']';
}
}
if (strlen($prefix)) {
$title = $prefix.' '.$title;
}
return $title;
}
protected function willRenderPage() {
parent::willRenderPage();
if (!$this->getRequest()) {
throw new Exception(
pht(
'You must set the %s to render a %s.',
'Request',
__CLASS__));
}
$console = $this->getConsole();
require_celerity_resource('phabricator-core-css');
require_celerity_resource('phabricator-zindex-css');
require_celerity_resource('phui-button-css');
require_celerity_resource('phui-spacing-css');
require_celerity_resource('phui-form-css');
require_celerity_resource('phabricator-standard-page-view');
require_celerity_resource('conpherence-durable-column-view');
require_celerity_resource('font-lato');
Javelin::initBehavior('workflow', array());
$request = $this->getRequest();
$user = null;
if ($request) {
$user = $request->getUser();
}
if ($user) {
if ($user->isUserActivated()) {
$offset = $user->getTimeZoneOffset();
$ignore_key = PhabricatorTimezoneIgnoreOffsetSetting::SETTINGKEY;
$ignore = $user->getUserSetting($ignore_key);
Javelin::initBehavior(
'detect-timezone',
array(
'offset' => $offset,
'uri' => '/settings/timezone/',
'message' => pht(
'Your browser timezone setting differs from the timezone '.
'setting in your profile, click to reconcile.'),
'ignoreKey' => $ignore_key,
'ignore' => $ignore,
));
if ($user->getIsAdmin()) {
$server_https = $request->isHTTPS();
$server_protocol = $server_https ? 'HTTPS' : 'HTTP';
$client_protocol = $server_https ? 'HTTP' : 'HTTPS';
$doc_name = 'Configuring a Preamble Script';
$doc_href = PhabricatorEnv::getDoclink($doc_name);
Javelin::initBehavior(
'setup-check-https',
array(
'server_https' => $server_https,
'doc_name' => pht('See Documentation'),
'doc_href' => $doc_href,
'message' => pht(
'Phabricator thinks you are using %s, but your '.
'client is convinced that it is using %s. This is a serious '.
'misconfiguration with subtle, but significant, consequences.',
$server_protocol, $client_protocol),
));
}
}
Javelin::initBehavior('lightbox-attachments');
}
Javelin::initBehavior('aphront-form-disable-on-submit');
Javelin::initBehavior('toggle-class', array());
Javelin::initBehavior('history-install');
Javelin::initBehavior('phabricator-gesture');
$current_token = null;
if ($user) {
$current_token = $user->getCSRFToken();
}
Javelin::initBehavior(
'refresh-csrf',
array(
'tokenName' => AphrontRequest::getCSRFTokenName(),
'header' => AphrontRequest::getCSRFHeaderName(),
'viaHeader' => AphrontRequest::getViaHeaderName(),
'current' => $current_token,
));
Javelin::initBehavior('device');
Javelin::initBehavior(
'high-security-warning',
$this->getHighSecurityWarningConfig());
if (PhabricatorEnv::isReadOnly()) {
Javelin::initBehavior(
'read-only-warning',
array(
'message' => PhabricatorEnv::getReadOnlyMessage(),
'uri' => PhabricatorEnv::getReadOnlyURI(),
));
}
if ($console) {
require_celerity_resource('aphront-dark-console-css');
$headers = array();
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
}
if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
$headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
}
Javelin::initBehavior(
'dark-console',
$this->getConsoleConfig());
}
if ($user) {
$viewer = $user;
} else {
$viewer = new PhabricatorUser();
}
$menu = id(new PhabricatorMainMenuView())
->setUser($viewer);
if ($this->getController()) {
$menu->setController($this->getController());
}
- $application_menu = $this->getApplicationMenu();
+ $application_menu = $this->applicationMenu;
if ($application_menu) {
if ($application_menu instanceof PHUIApplicationMenuView) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$application_menu->setCrumbs($crumbs);
}
$application_menu = $application_menu->buildListView();
}
$menu->setApplicationMenu($application_menu);
}
$this->menuContent = $menu->render();
}
protected function getHead() {
$monospaced = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
if ($user) {
$monospaced = $user->getUserSetting(
PhabricatorMonospacedFontSetting::SETTINGKEY);
}
}
$response = CelerityAPI::getStaticResourceResponse();
$font_css = null;
if (!empty($monospaced)) {
// We can't print this normally because escaping quotation marks will
// break the CSS. Instead, filter it strictly and then mark it as safe.
$monospaced = new PhutilSafeHTML(
PhabricatorMonospacedFontSetting::filterMonospacedCSSRule(
$monospaced));
$font_css = hsprintf(
'<style type="text/css">'.
'.PhabricatorMonospaced, '.
'.phabricator-remarkup .remarkup-code-block '.
'.remarkup-code { font: %s !important; } '.
'</style>',
$monospaced);
}
return hsprintf(
'%s%s%s',
parent::getHead(),
$font_css,
$response->renderSingleResource('javelin-magical-init', 'phabricator'));
}
public function setGlyph($glyph) {
$this->glyph = $glyph;
return $this;
}
public function getGlyph() {
return $this->glyph;
}
protected function willSendResponse($response) {
$request = $this->getRequest();
$response = parent::willSendResponse($response);
$console = $request->getApplicationConfiguration()->getConsole();
if ($console) {
$response = PhutilSafeHTML::applyFunction(
'str_replace',
hsprintf('<darkconsole />'),
$console->render($request),
$response);
}
return $response;
}
protected function getBody() {
$user = null;
$request = $this->getRequest();
if ($request) {
$user = $request->getUser();
}
$header_chrome = null;
if ($this->getShowChrome()) {
$header_chrome = $this->menuContent;
}
$classes = array();
$classes[] = 'main-page-frame';
$developer_warning = null;
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode') &&
DarkConsoleErrorLogPluginAPI::getErrors()) {
$developer_warning = phutil_tag_div(
'aphront-developer-error-callout',
pht(
'This page raised PHP errors. Find them in DarkConsole '.
'or the error log.'));
}
$main_page = phutil_tag(
'div',
array(
'id' => 'phabricator-standard-page',
'class' => 'phabricator-standard-page',
),
array(
$developer_warning,
$header_chrome,
phutil_tag(
'div',
array(
'id' => 'phabricator-standard-page-body',
'class' => 'phabricator-standard-page-body',
),
$this->renderPageBodyContent()),
));
$durable_column = null;
if ($this->getShowDurableColumn()) {
$is_visible = $this->getDurableColumnVisible();
$is_minimize = $this->getDurableColumnMinimize();
$durable_column = id(new ConpherenceDurableColumnView())
->setSelectedConpherence(null)
->setUser($user)
->setQuicksandConfig($this->buildQuicksandConfig())
->setVisible($is_visible)
->setMinimize($is_minimize)
->setInitialLoad(true);
if ($is_minimize) {
$this->classes[] = 'minimize-column';
}
}
Javelin::initBehavior('quicksand-blacklist', array(
'patterns' => $this->getQuicksandURIPatternBlacklist(),
));
return phutil_tag(
'div',
array(
'class' => implode(' ', $classes),
'id' => 'main-page-frame',
),
array(
$main_page,
$durable_column,
));
}
private function renderPageBodyContent() {
$console = $this->getConsole();
$body = parent::getBody();
$footer = $this->renderFooter();
$nav = $this->getNavigation();
$tabs = $this->getTabs();
if ($nav) {
$crumbs = $this->getCrumbs();
if ($crumbs) {
$nav->setCrumbs($crumbs);
}
$nav->appendChild($body);
$nav->appendFooter($footer);
$content = phutil_implode_html('', array($nav->render()));
} else {
$content = array();
$crumbs = $this->getCrumbs();
if ($crumbs) {
if ($this->getTabs()) {
$crumbs->setBorder(true);
}
$content[] = $crumbs;
}
$tabs = $this->getTabs();
if ($tabs) {
$content[] = $tabs;
}
$content[] = $body;
$content[] = $footer;
$content = phutil_implode_html('', $content);
}
return array(
($console ? hsprintf('<darkconsole />') : null),
$content,
);
}
protected function getTail() {
$request = $this->getRequest();
$user = $request->getUser();
$tail = array(
parent::getTail(),
);
$response = CelerityAPI::getStaticResourceResponse();
if ($request->isHTTPS()) {
$with_protocol = 'https';
} else {
$with_protocol = 'http';
}
$servers = PhabricatorNotificationServerRef::getEnabledClientServers(
$with_protocol);
if ($servers) {
if ($user && $user->isLoggedIn()) {
// TODO: We could tell the browser about all the servers and let it
// do random reconnects to improve reliability.
shuffle($servers);
$server = head($servers);
$client_uri = $server->getWebsocketURI();
Javelin::initBehavior(
'aphlict-listen',
array(
'websocketURI' => (string)$client_uri,
) + $this->buildAphlictListenConfigData());
CelerityAPI::getStaticResourceResponse()
->addContentSecurityPolicyURI('connect-src', $client_uri);
}
}
$tail[] = $response->renderHTMLFooter($this->getFrameable());
return $tail;
}
protected function getBodyClasses() {
$classes = array();
if (!$this->getShowChrome()) {
$classes[] = 'phabricator-chromeless-page';
}
$agent = AphrontRequest::getHTTPHeader('User-Agent');
// Try to guess the device resolution based on UA strings to avoid a flash
// of incorrectly-styled content.
$device_guess = 'device-desktop';
if (preg_match('@iPhone|iPod|(Android.*Chrome/[.0-9]* Mobile)@', $agent)) {
$device_guess = 'device-phone device';
} else if (preg_match('@iPad|(Android.*Chrome/)@', $agent)) {
$device_guess = 'device-tablet device';
}
$classes[] = $device_guess;
if (preg_match('@Windows@', $agent)) {
$classes[] = 'platform-windows';
} else if (preg_match('@Macintosh@', $agent)) {
$classes[] = 'platform-mac';
} else if (preg_match('@X11@', $agent)) {
$classes[] = 'platform-linux';
}
if ($this->getRequest()->getStr('__print__')) {
$classes[] = 'printable';
}
if ($this->getRequest()->getStr('__aural__')) {
$classes[] = 'audible';
}
$classes[] = 'phui-theme-'.PhabricatorEnv::getEnvConfig('ui.header-color');
foreach ($this->classes as $class) {
$classes[] = $class;
}
return implode(' ', $classes);
}
private function getConsole() {
if ($this->disableConsole) {
return null;
}
return $this->getRequest()->getApplicationConfiguration()->getConsole();
}
private function getConsoleConfig() {
$user = $this->getRequest()->getUser();
$headers = array();
if (DarkConsoleXHProfPluginAPI::isProfilerStarted()) {
$headers[DarkConsoleXHProfPluginAPI::getProfilerHeader()] = 'page';
}
if (DarkConsoleServicesPlugin::isQueryAnalyzerRequested()) {
$headers[DarkConsoleServicesPlugin::getQueryAnalyzerHeader()] = true;
}
if ($user) {
$setting_tab = PhabricatorDarkConsoleTabSetting::SETTINGKEY;
$setting_visible = PhabricatorDarkConsoleVisibleSetting::SETTINGKEY;
$tab = $user->getUserSetting($setting_tab);
$visible = $user->getUserSetting($setting_visible);
} else {
$tab = null;
$visible = true;
}
return array(
// NOTE: We use a generic label here to prevent input reflection
// and mitigate compression attacks like BREACH. See discussion in
// T3684.
'uri' => pht('Main Request'),
'selected' => $tab,
'visible' => $visible,
'headers' => $headers,
);
}
private function getHighSecurityWarningConfig() {
$user = $this->getRequest()->getUser();
$show = false;
if ($user->hasSession()) {
$hisec = ($user->getSession()->getHighSecurityUntil() - time());
if ($hisec > 0) {
$show = true;
}
}
return array(
'show' => $show,
'uri' => '/auth/session/downgrade/',
'message' => pht(
'Your session is in high security mode. When you '.
'finish using it, click here to leave.'),
);
}
private function renderFooter() {
if (!$this->getShowChrome()) {
return null;
}
if (!$this->getShowFooter()) {
return null;
}
$items = PhabricatorEnv::getEnvConfig('ui.footer-items');
if (!$items) {
return null;
}
$foot = array();
foreach ($items as $item) {
$name = idx($item, 'name', pht('Unnamed Footer Item'));
$href = idx($item, 'href');
if (!PhabricatorEnv::isValidURIForLink($href)) {
$href = null;
}
if ($href !== null) {
$tag = 'a';
} else {
$tag = 'span';
}
$foot[] = phutil_tag(
$tag,
array(
'href' => $href,
),
$name);
}
$foot = phutil_implode_html(" \xC2\xB7 ", $foot);
return phutil_tag(
'div',
array(
'class' => 'phabricator-standard-page-footer grouped',
),
$foot);
}
public function renderForQuicksand() {
parent::willRenderPage();
$response = $this->renderPageBodyContent();
$response = $this->willSendResponse($response);
$extra_config = $this->getQuicksandConfig();
return array(
'content' => hsprintf('%s', $response),
) + $this->buildQuicksandConfig()
+ $extra_config;
}
private function buildQuicksandConfig() {
$viewer = $this->getRequest()->getUser();
$controller = $this->getController();
$dropdown_query = id(new AphlictDropdownDataQuery())
->setViewer($viewer);
$dropdown_query->execute();
$hisec_warning_config = $this->getHighSecurityWarningConfig();
$console_config = null;
$console = $this->getConsole();
if ($console) {
$console_config = $this->getConsoleConfig();
}
$upload_enabled = false;
if ($controller) {
$upload_enabled = $controller->isGlobalDragAndDropUploadEnabled();
}
$application_class = null;
$application_search_icon = null;
$application_help = null;
$controller = $this->getController();
if ($controller) {
$application = $controller->getCurrentApplication();
if ($application) {
$application_class = get_class($application);
if ($application->getApplicationSearchDocumentTypes()) {
$application_search_icon = $application->getIcon();
}
$help_items = $application->getHelpMenuItems($viewer);
if ($help_items) {
$help_list = id(new PhabricatorActionListView())
->setViewer($viewer);
foreach ($help_items as $help_item) {
$help_list->addAction($help_item);
}
$application_help = $help_list->getDropdownMenuMetadata();
}
}
}
return array(
'title' => $this->getTitle(),
'bodyClasses' => $this->getBodyClasses(),
'aphlictDropdownData' => array(
$dropdown_query->getNotificationData(),
$dropdown_query->getConpherenceData(),
),
'globalDragAndDrop' => $upload_enabled,
'hisecWarningConfig' => $hisec_warning_config,
'consoleConfig' => $console_config,
'applicationClass' => $application_class,
'applicationSearchIcon' => $application_search_icon,
'helpItems' => $application_help,
) + $this->buildAphlictListenConfigData();
}
private function buildAphlictListenConfigData() {
$user = $this->getRequest()->getUser();
$subscriptions = $this->pageObjects;
$subscriptions[] = $user->getPHID();
return array(
'pageObjects' => array_fill_keys($this->pageObjects, true),
'subscriptions' => $subscriptions,
);
}
private function getQuicksandURIPatternBlacklist() {
$applications = PhabricatorApplication::getAllApplications();
$blacklist = array();
foreach ($applications as $application) {
$blacklist[] = $application->getQuicksandURIPatternBlacklist();
}
// See T4340. Currently, Phortune and Auth both require pulling in external
// Javascript (for Stripe card management and Recaptcha, respectively).
// This can put us in a position where the user loads a page with a
// restrictive Content-Security-Policy, then uses Quicksand to navigate to
// a page which needs to load external scripts. For now, just blacklist
// these entire applications since we aren't giving up anything
// significant by doing so.
$blacklist[] = array(
'/phortune/.*',
'/auth/.*',
);
return array_mergev($blacklist);
}
private function getUserPreference($key, $default = null) {
$request = $this->getRequest();
if (!$request) {
return $default;
}
$user = $request->getUser();
if (!$user) {
return $default;
}
return $user->getUserSetting($key);
}
public function produceAphrontResponse() {
$controller = $this->getController();
- if (!$this->getApplicationMenu()) {
- $application_menu = $controller->buildApplicationMenu();
- if ($application_menu) {
- $this->setApplicationMenu($application_menu);
- }
- }
-
$viewer = $this->getUser();
if ($viewer && $viewer->getPHID()) {
$object_phids = $this->pageObjects;
foreach ($object_phids as $object_phid) {
PhabricatorFeedStoryNotification::updateObjectNotificationViews(
$viewer,
$object_phid);
}
}
if ($this->getRequest()->isQuicksand()) {
$content = $this->renderForQuicksand();
$response = id(new AphrontAjaxResponse())
->setContent($content);
} else {
+ // See T13247. Try to find some navigational menu items to create a
+ // mobile navigation menu from.
+ $application_menu = $controller->buildApplicationMenu();
+ if (!$application_menu) {
+ $application_menu = $this->getNavigation()->getMenu();
+ }
+ $this->applicationMenu = $application_menu;
+
$content = $this->render();
$response = id(new AphrontWebpageResponse())
->setContent($content)
->setFrameable($this->getFrameable());
}
return $response;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jul 2, 6:40 PM (19 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
165092
Default Alt Text
(173 KB)

Event Timeline