diff --git a/src/aphront/AphrontRequest.php b/src/aphront/AphrontRequest.php --- a/src/aphront/AphrontRequest.php +++ b/src/aphront/AphrontRequest.php @@ -383,6 +383,15 @@ return $this->validateCSRF(); } + public function hasCSRF() { + try { + $this->validateCSRF(); + return true; + } catch (AphrontMalformedRequestException $ex) { + return false; + } + } + public function isFormOrHisecPost() { $post = $this->getExists(self::TYPE_FORM) && $this->isHTTPPost(); diff --git a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php --- a/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php +++ b/src/applications/dashboard/controller/panel/PhabricatorDashboardPanelTabsController.php @@ -140,7 +140,7 @@ case 'remove': return $this->handleRemoveOperation($panel, $target, $cancel_uri); case 'move': - break; + return $this->handleMoveOperation($panel, $target, $after, $cancel_uri); case 'rename': return $this->handleRenameOperation($panel, $target, $cancel_uri); } @@ -298,6 +298,89 @@ ->addSubmitButton(pht('Rename Tab')); } + private function handleMoveOperation( + PhabricatorDashboardPanel $panel, + $target, + $after, + $cancel_uri) { + $request = $this->getRequest(); + $viewer = $this->getViewer(); + + $move = $request->getStr('move'); + + $impl = $panel->getImplementation(); + $old_config = $impl->getPanelConfiguration($panel); + + $is_next = ($move === 'next'); + if ($target === $after) { + return $this->newDialog() + ->setTitle(pht('Impossible!')) + ->appendParagraph( + pht( + 'You can not move a tab relative to itself.')) + ->addCancelButton($cancel_uri); + } else if ($is_next && ((string)last_key($old_config) === $target)) { + return $this->newDialog() + ->setTitle(pht('Impossible!')) + ->appendParagraph( + pht( + 'This is already the last tab. It can not move any farther to '. + 'the right.')) + ->addCancelButton($cancel_uri); + } else if ((string)head_key($old_config) === $target) { + return $this->newDialog() + ->setTitle(pht('Impossible!')) + ->appendParagraph( + pht( + 'This is already the first tab. It can not move any farther to '. + 'the left.')) + ->addCancelButton($cancel_uri); + } + + if ($request->hasCSRF()) { + $new_config = array(); + foreach ($old_config as $old_key => $old_spec) { + $old_key = (string)$old_key; + + $is_after = ($old_key === $after); + + if (!$is_after) { + if ($old_key === $target) { + continue; + } + } + + if ($is_after && !$is_next) { + $new_config[$target] = $old_config[$target]; + } + + $new_config[$old_key] = $old_spec; + + if ($is_after && $is_next) { + $new_config[$target] = $old_config[$target]; + } + } + + $this->writePanelConfig($panel, $new_config); + + return id(new AphrontRedirectResponse())->setURI($cancel_uri); + } + + if ($is_next) { + $prompt = pht('Move this tab to the right?'); + } else { + $prompt = pht('Move this tab to the left?'); + } + + return $this->newEditDialog() + ->setTitle(pht('Move Tab')) + ->addHiddenInput('target', $target) + ->addHiddenInput('after', $after) + ->addHiddenInput('move', $move) + ->appendParagraph($prompt) + ->addCancelButton($cancel_uri) + ->addSubmitButton(pht('Move Tab')); + } private function writePanelConfig( PhabricatorDashboardPanel $panel, diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php --- a/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardTabsPanelType.php @@ -87,7 +87,15 @@ $selected = 0; - $last_idx = null; + $key_list = array_keys($config); + + $next_keys = array(); + $prev_keys = array(); + for ($ii = 0; $ii < count($key_list); $ii++) { + $next_keys[$key_list[$ii]] = idx($key_list, $ii + 1); + $prev_keys[$key_list[$ii]] = idx($key_list, $ii - 1); + } + foreach ($config as $idx => $tab_spec) { $panel_id = idx($tab_spec, 'panelID'); $subpanel = idx($panels, $panel_id); @@ -145,6 +153,42 @@ ->setHref($rename_tab_uri) ->setWorkflow(true)); + $move_uri = urisprintf('/dashboard/panel/tabs/%d/move/', $id); + + $prev_key = $prev_keys[$idx]; + $prev_params = array( + 'target' => $idx, + 'after' => $prev_key, + 'move' => 'prev', + 'contextPHID' => $context_phid, + ); + $prev_uri = new PhutilURI($move_uri, $prev_params); + + $next_key = $next_keys[$idx]; + $next_params = array( + 'target' => $idx, + 'after' => $next_key, + 'move' => 'next', + 'contextPHID' => $context_phid, + ); + $next_uri = new PhutilURI($move_uri, $next_params); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Move Tab Left')) + ->setIcon('fa-chevron-left') + ->setHref($prev_uri) + ->setWorkflow(true) + ->setDisabled(($prev_key === null) || !$can_edit)); + + $dropdown_menu->addAction( + id(new PhabricatorActionView()) + ->setName(pht('Move Tab Right')) + ->setIcon('fa-chevron-right') + ->setHref($next_uri) + ->setWorkflow(true) + ->setDisabled(($next_key === null) || !$can_edit)); + $dropdown_menu->addAction( id(new PhabricatorActionView()) ->setName(pht('Remove Tab')) @@ -177,15 +221,16 @@ } $list->addMenuItem($tab_view); - - $last_idx = $idx; } + if ($is_edit) { $actions = id(new PhabricatorActionListView()) ->setViewer($viewer); $add_last_uri = clone $add_uri; + + $last_idx = last_key($config); if ($last_idx) { $add_last_uri->replaceQueryParam('after', $last_idx); } diff --git a/src/applications/notification/controller/PhabricatorNotificationClearController.php b/src/applications/notification/controller/PhabricatorNotificationClearController.php --- a/src/applications/notification/controller/PhabricatorNotificationClearController.php +++ b/src/applications/notification/controller/PhabricatorNotificationClearController.php @@ -10,12 +10,7 @@ if ($request->isDialogFormPost()) { $should_clear = true; } else { - try { - $request->validateCSRF(); - $should_clear = true; - } catch (AphrontMalformedRequestException $ex) { - $should_clear = false; - } + $should_clear = $request->hasCSRF(); } if ($should_clear) {