diff --git a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php --- a/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php +++ b/src/applications/favorites/engineextension/PhabricatorFavoritesMainMenuBarExtension.php @@ -54,6 +54,11 @@ ->setProfileObject($favorites) ->setCustomPHID($viewer->getPHID()); + $controller = $this->getController(); + if ($controller) { + $menu_engine->setController($controller); + } + $filter_view = $menu_engine->buildNavigation(); $menu_view = $filter_view->getMenu(); diff --git a/src/applications/home/controller/PhabricatorHomeController.php b/src/applications/home/controller/PhabricatorHomeController.php --- a/src/applications/home/controller/PhabricatorHomeController.php +++ b/src/applications/home/controller/PhabricatorHomeController.php @@ -31,6 +31,7 @@ $engine = id(new PhabricatorHomeProfileMenuEngine()) ->setViewer($viewer) + ->setController($this) ->setProfileObject($home) ->setCustomPHID($viewer->getPHID()); diff --git a/src/applications/project/controller/PhabricatorProjectBoardController.php b/src/applications/project/controller/PhabricatorProjectBoardController.php --- a/src/applications/project/controller/PhabricatorProjectBoardController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardController.php @@ -1,14 +1,4 @@ selectFilter(PhabricatorProject::ITEM_WORKBOARD); - $menu->addClass('project-board-nav'); - - return $menu; - } -} + extends PhabricatorProjectController {} diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -172,8 +172,7 @@ return $content; } - $nav = $this->getProfileMenu(); - $nav->selectFilter(PhabricatorProject::ITEM_WORKBOARD); + $nav = $this->newWorkboardProfileMenu(); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); @@ -720,7 +719,7 @@ ->appendChild($board) ->addClass('project-board-wrapper'); - $nav = $this->getProfileMenu(); + $nav = $this->newWorkboardProfileMenu(); $divider = id(new PHUIListItemView()) ->setType(PHUIListItemView::TYPE_DIVIDER); @@ -1504,4 +1503,15 @@ ->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 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -97,11 +97,11 @@ return $menu; } - protected function getProfileMenu() { + protected function getProfileMenu($default_item = null) { if (!$this->profileMenu) { $engine = $this->getProfileMenuEngine(); if ($engine) { - $this->profileMenu = $engine->buildNavigation(); + $this->profileMenu = $engine->buildNavigation($default_item); } } diff --git a/src/applications/search/engine/PhabricatorProfileMenuEngine.php b/src/applications/search/engine/PhabricatorProfileMenuEngine.php --- a/src/applications/search/engine/PhabricatorProfileMenuEngine.php +++ b/src/applications/search/engine/PhabricatorProfileMenuEngine.php @@ -183,7 +183,7 @@ break; } - $navigation = $this->buildNavigation(); + $navigation = $this->buildNavigation($selected_item); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); @@ -223,8 +223,6 @@ switch ($item_action) { case 'view': if ($selected_item) { - $navigation->selectFilter($selected_item->getDefaultMenuItemKey()); - try { $content = $this->buildItemViewContent($selected_item); } catch (Exception $ex) { @@ -335,7 +333,9 @@ return $page; } - public function buildNavigation() { + public function buildNavigation( + PhabricatorProfileMenuItemConfiguration $selected_item = null) { + if ($this->navigation) { return $this->navigation; } @@ -398,6 +398,12 @@ $nav->selectFilter(null); + $navigation_items = $nav->getMenu()->getItems(); + $select_key = $this->pickHighlightedMenuItem( + $navigation_items, + $selected_item); + $nav->selectFilter($select_key); + $this->navigation = $nav; return $this->navigation; } @@ -1367,4 +1373,97 @@ return null; } + private function pickHighlightedMenuItem( + array $items, + PhabricatorProfileMenuItemConfiguration $selected_item = null) { + + assert_instances_of($items, 'PHUIListItemView'); + + $default_key = null; + if ($selected_item) { + $default_key = $selected_item->getDefaultMenuItemKey(); + } + + $controller = $this->getController(); + + // In some rare cases, when like building the "Favorites" menu on a + // 404 page, we may not have a controller. Just accept whatever default + // behavior we'd otherwise end up with. + if (!$controller) { + return $default_key; + } + + $request = $controller->getRequest(); + + // See T12949. If one of the menu items is a link to the same URI that + // the page was accessed with, we want to highlight that item. For example, + // this allows you to add links to a menu that apply filters to a + // workboard. + + $matches = array(); + foreach ($items as $item) { + $href = $item->getHref(); + if ($this->isMatchForRequestURI($request, $href)) { + $matches[] = $item; + } + } + + foreach ($matches as $match) { + if ($match->getKey() === $default_key) { + return $default_key; + } + } + + if ($matches) { + return head($matches)->getKey(); + } + + return $default_key; + } + + private function isMatchForRequestURI(AphrontRequest $request, $item_uri) { + $request_uri = $request->getAbsoluteRequestURI(); + $item_uri = new PhutilURI($item_uri); + + // If the request URI and item URI don't have matching paths, they + // do not match. + if ($request_uri->getPath() !== $item_uri->getPath()) { + return false; + } + + // If the request URI and item URI don't have matching parameters, they + // also do not match. We're specifically trying to let "?filter=X" work + // on Workboards, among other use cases, so this is important. + $request_params = $request_uri->getQueryParamsAsPairList(); + $item_params = $item_uri->getQueryParamsAsPairList(); + if ($request_params !== $item_params) { + return false; + } + + // If the paths and parameters match, the item domain must be: empty; or + // match the request domain; or match the production domain. + + $request_domain = $request_uri->getDomain(); + + $production_uri = PhabricatorEnv::getProductionURI('/'); + $production_domain = id(new PhutilURI($production_uri)) + ->getDomain(); + + $allowed_domains = array( + '', + $request_domain, + $production_domain, + ); + $allowed_domains = array_fuse($allowed_domains); + + $item_domain = $item_uri->getDomain(); + $item_domain = (string)$item_domain; + + if (isset($allowed_domains[$item_domain])) { + return true; + } + + return false; + } + }