diff --git a/src/applications/project/controller/PhabricatorProjectBoardViewController.php b/src/applications/project/controller/PhabricatorProjectBoardViewController.php index e993c1b85b..9b7b76e62c 100644 --- a/src/applications/project/controller/PhabricatorProjectBoardViewController.php +++ b/src/applications/project/controller/PhabricatorProjectBoardViewController.php @@ -1,827 +1,845 @@ getUser(); $response = $this->loadProject(); if ($response) { return $response; } $project = $this->getProject(); $this->readRequestState(); $columns = $this->loadColumns($project); // TODO: Expand the checks here if we add the ability // to hide the Backlog column if (!$columns) { $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); if (!$can_edit) { $content = $this->buildNoAccessContent($project); } else { $content = $this->buildInitializeContent($project); } if ($content instanceof AphrontResponse) { return $content; } $nav = $this->getProfileMenu(); $nav->selectFilter(PhabricatorProject::PANEL_WORKBOARD); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Workboard')); return $this->newPage() ->setTitle( array( pht('Workboard'), $project->getName(), )) ->setNavigation($nav) ->setCrumbs($crumbs) ->appendChild($content); } $board_uri = $this->getApplicationURI('board/'.$project->getID().'/'); $engine = id(new ManiphestTaskSearchEngine()) ->setViewer($viewer) ->setBaseURI($board_uri) ->setIsBoardView(true); if ($request->isFormPost()) { $saved = $engine->buildSavedQueryFromRequest($request); $engine->saveQuery($saved); $filter_form = id(new AphrontFormView()) ->setUser($viewer); $engine->buildSearchForm($filter_form, $saved); if ($engine->getErrors()) { return $this->newDialog() ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle(pht('Advanced Filter')) ->appendChild($filter_form->buildLayoutView()) ->setErrors($engine->getErrors()) ->setSubmitURI($board_uri) ->addSubmitButton(pht('Apply Filter')) ->addCancelButton($board_uri); } return id(new AphrontRedirectResponse())->setURI( $this->getURIWithState( $engine->getQueryResultsPageURI($saved->getQueryKey()))); } $query_key = $request->getURIData('queryKey'); if (!$query_key) { $query_key = 'open'; } $this->queryKey = $query_key; $custom_query = null; if ($engine->isBuiltinQuery($query_key)) { $saved = $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); $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 = $engine->buildQueryFromSavedQuery($saved); $tasks = $task_query ->withEdgeLogicPHIDs( PhabricatorProjectObjectHasProjectEdgeType::EDGECONST, PhabricatorQueryConstraint::OPERATOR_AND, array($project->getPHID())) ->setOrder(ManiphestTaskQuery::ORDER_PRIORITY) ->setViewer($viewer) ->execute(); $tasks = mpull($tasks, null, 'getPHID'); if ($tasks) { $positions = id(new PhabricatorProjectColumnPositionQuery()) ->setViewer($viewer) ->withObjectPHIDs(mpull($tasks, 'getPHID')) ->withColumns($columns) ->execute(); $positions = mpull($positions, null, 'getObjectPHID'); } else { $positions = array(); } $task_map = array(); foreach ($tasks as $task) { $task_phid = $task->getPHID(); if (empty($positions[$task_phid])) { // This shouldn't normally be possible because we create positions on // demand, but we might have raced as an object was removed from the // board. Just drop the task if we don't have a position for it. continue; } $position = $positions[$task_phid]; $task_map[$position->getColumnPHID()][] = $task_phid; } // If we're showing the board in "natural" order, sort columns by their // column positions. if ($this->sortKey == PhabricatorProjectColumn::ORDER_NATURAL) { foreach ($task_map as $column_phid => $task_phids) { $order = array(); foreach ($task_phids as $task_phid) { if (isset($positions[$task_phid])) { $order[$task_phid] = $positions[$task_phid]->getOrderingKey(); } else { $order[$task_phid] = 0; } } asort($order); $task_map[$column_phid] = array_keys($order); } } $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 = idx($task_map, $batch_column->getPHID(), array()); 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); } $batch_ids = mpull($batch_tasks, 'getID'); $batch_ids = implode(',', $batch_ids); $batch_uri = new PhutilURI('/maniphest/batch/'); $batch_uri->setQueryParam('board', $this->id); $batch_uri->setQueryParam('batch', $batch_ids); return id(new AphrontRedirectResponse()) ->setURI($batch_uri); } $board_id = celerity_generate_unique_node_id(); $board = id(new PHUIWorkboardView()) ->setUser($viewer) ->setID($board_id); $behavior_config = array( 'boardID' => $board_id, 'projectPHID' => $project->getPHID(), 'moveURI' => $this->getApplicationURI('move/'.$project->getID().'/'), 'createURI' => $this->getCreateURI(), 'order' => $this->sortKey, ); $this->initBehavior( 'project-boards', $behavior_config); $this->handles = ManiphestTaskListView::loadTaskHandles($viewer, $tasks); foreach ($columns as $column) { $task_phids = idx($task_map, $column->getPHID(), array()); $column_tasks = array_select_keys($tasks, $task_phids); $panel = id(new PHUIWorkpanelView()) ->setHeader($column->getDisplayName()) ->setSubHeader($column->getDisplayType()) ->addSigil('workpanel'); $header_icon = $column->getHeaderIcon(); if ($header_icon) { $panel->setHeaderIcon($header_icon); } if ($column->isHidden()) { $panel->addClass('project-panel-hidden'); } $column_menu = $this->buildColumnMenu($project, $column); $panel->addHeaderAction($column_menu); $tag_id = celerity_generate_unique_node_id(); $tag_content_id = celerity_generate_unique_node_id(); $count_tag = id(new PHUITagView()) ->setType(PHUITagView::TYPE_SHADE) ->setShade(PHUITagView::COLOR_BLUE) ->setID($tag_id) ->setName(phutil_tag('span', array('id' => $tag_content_id), '-')) ->setStyle('display: none'); $panel->setHeaderTag($count_tag); $cards = id(new PHUIObjectItemListView()) ->setUser($viewer) ->setFlush(true) ->setAllowEmptyList(true) ->addSigil('project-column') ->setMetadata( array( 'columnPHID' => $column->getPHID(), 'countTagID' => $tag_id, 'countTagContentID' => $tag_content_id, 'pointLimit' => $column->getPointLimit(), )); foreach ($column_tasks as $task) { $owner = null; if ($task->getOwnerPHID()) { $owner = $this->handles[$task->getOwnerPHID()]; } $can_edit = idx($task_can_edit_map, $task->getPHID(), false); $cards->addItem(id(new ProjectBoardTaskCard()) ->setViewer($viewer) ->setTask($task) ->setOwner($owner) ->setCanEdit($can_edit) ->getItem()); } $panel->setCards($cards); $board->addPanel($panel); } $sort_menu = $this->buildSortMenu( $viewer, $this->sortKey); $filter_menu = $this->buildFilterMenu( $viewer, $custom_query, $engine, $query_key); $manage_menu = $this->buildManageMenu($project, $this->showHidden); $header_link = phutil_tag( 'a', array( 'href' => $this->getApplicationURI('profile/'.$project->getID().'/'), ), $project->getName()); $header = id(new PHUIHeaderView()) ->setHeader($header_link) ->setUser($viewer) ->setNoBackground(true) ->addActionLink($sort_menu) ->addActionLink($filter_menu) ->addActionLink($manage_menu) ->setPolicyObject($project); $header_box = id(new PHUIBoxView()) ->appendChild($header) ->addClass('project-board-header'); $board_box = id(new PHUIBoxView()) ->appendChild($board) ->addClass('project-board-wrapper'); $nav = $this->getProfileMenu(); return $this->newPage() ->setTitle(pht('%s Board', $project->getName())) ->setPageObjectPHIDs(array($project->getPHID())) ->setShowFooter(false) ->setNavigation($nav) ->addQuicksandConfig( array( 'boardConfig' => $behavior_config, )) ->appendChild( array( $header_box, $board_box, )); } private function readRequestState() { $request = $this->getRequest(); $project = $this->getProject(); $this->showHidden = $request->getBool('hidden'); $this->id = $project->getID(); $sort_key = $request->getStr('order'); switch ($sort_key) { case PhabricatorProjectColumn::ORDER_NATURAL: case PhabricatorProjectColumn::ORDER_PRIORITY: break; default: $sort_key = PhabricatorProjectColumn::DEFAULT_ORDER; break; } $this->sortKey = $sort_key; } private function loadColumns(PhabricatorProject $project) { $viewer = $this->getViewer(); $column_query = id(new PhabricatorProjectColumnQuery()) ->setViewer($viewer) ->withProjectPHIDs(array($project->getPHID())); if (!$this->showHidden) { $column_query->withStatuses( array(PhabricatorProjectColumn::STATUS_ACTIVE)); } $columns = $column_query->execute(); $columns = mpull($columns, null, 'getSequence'); ksort($columns); return $columns; } private function buildSortMenu( PhabricatorUser $viewer, $sort_key) { $sort_icon = id(new PHUIIconView()) ->setIconFont('fa-sort-amount-asc bluegrey'); $named = array( PhabricatorProjectColumn::ORDER_NATURAL => pht('Natural'), PhabricatorProjectColumn::ORDER_PRIORITY => pht('Sort by Priority'), ); $base_uri = $this->getURIWithState(); $items = array(); foreach ($named as $key => $name) { $is_selected = ($key == $sort_key); if ($is_selected) { $active_order = $name; } $item = id(new PhabricatorActionView()) ->setIcon('fa-sort-amount-asc') ->setSelected($is_selected) ->setName($name); $uri = $base_uri->alter('order', $key); $item->setHref($uri); $items[] = $item; } $sort_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $sort_menu->addAction($item); } $sort_button = id(new PHUIButtonView()) ->setText(pht('Sort: %s', $active_order)) ->setIcon($sort_icon) ->setTag('a') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $sort_menu), )); return $sort_button; } private function buildFilterMenu( PhabricatorUser $viewer, $custom_query, PhabricatorApplicationSearchEngine $engine, $query_key) { $filter_icon = id(new PHUIIconView()) ->setIconFont('fa-search-plus bluegrey'); $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); $item->setHref($uri); $items[] = $item; } $items[] = id(new PhabricatorActionView()) ->setIcon('fa-cog') ->setHref($this->getApplicationURI('board/'.$this->id.'/filter/')) ->setWorkflow(true) ->setName(pht('Advanced Filter...')); $filter_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($items as $item) { $filter_menu->addAction($item); } $filter_button = id(new PHUIButtonView()) ->setText(pht('Filter: %s', $active_filter)) ->setIcon($filter_icon) ->setTag('a') ->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(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $project, PhabricatorPolicyCapability::CAN_EDIT); $manage_icon = id(new PHUIIconView()) ->setIconFont('fa-cog bluegrey'); $manage_items = array(); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Add Column')) ->setHref($this->getApplicationURI('board/'.$this->id.'/edit/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-exchange') ->setName(pht('Reorder Columns')) ->setHref($this->getApplicationURI('board/'.$this->id.'/reorder/')) ->setDisabled(!$can_edit) ->setWorkflow(true); if ($show_hidden) { $hidden_uri = $this->getURIWithState() ->setQueryParam('hidden', null); $hidden_icon = 'fa-eye-slash'; $hidden_text = pht('Hide Hidden Columns'); } else { $hidden_uri = $this->getURIWithState() ->setQueryParam('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); $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('batch', self::BATCH_EDIT_ALL); $can_batch_edit = PhabricatorPolicyFilter::hasCapability( $viewer, PhabricatorApplication::getByClass('PhabricatorManiphestApplication'), ManiphestBulkEditCapability::CAPABILITY); $manage_items[] = id(new PhabricatorActionView()) ->setIcon('fa-list-ul') ->setName(pht('Batch Edit Visible Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); $manage_menu = id(new PhabricatorActionListView()) ->setUser($viewer); foreach ($manage_items as $item) { $manage_menu->addAction($item); } $manage_button = id(new PHUIButtonView()) ->setText(pht('Manage Board')) ->setIcon($manage_icon) ->setTag('a') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $manage_menu), )); return $manage_button; } 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(); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-plus') ->setName(pht('Create Task...')) ->setHref($this->getCreateURI()) ->addSigil('column-add-task') ->setMetadata( array( 'columnPHID' => $column->getPHID(), )); $batch_edit_uri = $request->getRequestURI(); $batch_edit_uri->setQueryParam('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('Batch Edit Tasks...')) ->setHref($batch_edit_uri) ->setDisabled(!$can_batch_edit); $detail_uri = $this->getApplicationURI( 'board/'.$this->id.'/column/'.$column->getID().'/'); $column_items[] = id(new PhabricatorActionView()) ->setIcon('fa-columns') ->setName(pht('Column Details')) ->setHref($detail_uri); $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()) ->setIconFont('fa-caret-down') ->setHref('#') ->addSigil('boards-dropdown-menu') ->setMetadata( array( 'items' => hsprintf('%s', $column_menu), )); return $column_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. * @return PhutilURI URI with state parameters. */ private function getURIWithState($base = null) { if ($base === null) { $base = $this->getRequest()->getRequestURI(); } $base = new PhutilURI($base); if ($this->sortKey != PhabricatorProjectColumn::DEFAULT_ORDER) { $base->setQueryParam('order', $this->sortKey); } else { $base->setQueryParam('order', null); } $base->setQueryParam('hidden', $this->showHidden ? 'true' : null); return $base; } private function getCreateURI() { $viewer = $this->getViewer(); // TODO: This should be cleaned up, but maybe we're going to make options // for each column or board? $edit_config = id(new ManiphestEditEngine()) ->setViewer($viewer) ->loadDefaultEditConfiguration(); if ($edit_config) { $form_key = $edit_config->getIdentifier(); $create_uri = "/maniphest/task/edit/form/{$form_key}/"; } else { $create_uri = '/maniphest/task/edit/'; } return $create_uri; } 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/"); - switch ($type) { - case 'backlog-only': + $set_default = $request->getBool('default'); + if ($set_default) { + $this + ->getProfilePanelEngine() + ->adjustDefault(PhabricatorProject::PANEL_WORKBOARD); + } + + if ($request->isFormPost()) { + if ($type == 'backlog-only') { $column = PhabricatorProjectColumn::initializeNewColumn($viewer) ->setSequence(0) ->setProperty('isDefault', true) ->setProjectPHID($project->getPHID()) ->save(); $project->setHasWorkboard(1)->save(); return id(new AphrontRedirectResponse()) ->setURI($board_uri); - case 'import': + } else { return id(new AphrontRedirectResponse()) ->setURI($import_uri); + } } $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) ->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); } } diff --git a/src/applications/project/controller/PhabricatorProjectController.php b/src/applications/project/controller/PhabricatorProjectController.php index ebb3b31a2a..8687e0a3a7 100644 --- a/src/applications/project/controller/PhabricatorProjectController.php +++ b/src/applications/project/controller/PhabricatorProjectController.php @@ -1,149 +1,150 @@ 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() { if (!$this->profileMenu) { $engine = $this->getProfilePanelEngine(); if ($engine) { $this->profileMenu = $engine->buildNavigation(); } } return $this->profileMenu; } protected function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $project = $this->getProject(); if ($project) { $ancestors = $project->getAncestorProjects(); $ancestors = array_reverse($ancestors); $ancestors[] = $project; foreach ($ancestors as $ancestor) { $crumbs->addTextCrumb( $ancestor->getName(), $ancestor->getURI()); } } return $crumbs; } protected function getProfilePanelEngine() { if (!$this->profilePanelEngine) { $viewer = $this->getViewer(); $project = $this->getProject(); if ($project) { $engine = id(new PhabricatorProjectProfilePanelEngine()) ->setViewer($viewer) + ->setController($this) ->setProfileObject($project); $this->profilePanelEngine = $engine; } } return $this->profilePanelEngine; } protected function setProfilePanelEngine( PhabricatorProjectProfilePanelEngine $engine) { $this->profilePanelEngine = $engine; return $this; } } diff --git a/src/applications/search/engine/PhabricatorProfilePanelEngine.php b/src/applications/search/engine/PhabricatorProfilePanelEngine.php index e0f86766a1..3622e43976 100644 --- a/src/applications/search/engine/PhabricatorProfilePanelEngine.php +++ b/src/applications/search/engine/PhabricatorProfilePanelEngine.php @@ -1,953 +1,985 @@ viewer = $viewer; return $this; } public function getViewer() { return $this->viewer; } public function setProfileObject($profile_object) { $this->profileObject = $profile_object; return $this; } public function getProfileObject() { return $this->profileObject; } public function setController(PhabricatorController $controller) { $this->controller = $controller; return $this; } public function getController() { return $this->controller; } private function setDefaultPanel( PhabricatorProfilePanelConfiguration $default_panel) { $this->defaultPanel = $default_panel; return $this; } public function getDefaultPanel() { $this->loadPanels(); return $this->defaultPanel; } abstract protected function getPanelURI($path); protected function isPanelEngineConfigurable() { return PhabricatorEnv::getEnvConfig('phabricator.show-prototypes'); } public function buildResponse() { $controller = $this->getController(); $viewer = $controller->getViewer(); $this->setViewer($viewer); $request = $controller->getRequest(); $panel_action = $request->getURIData('panelAction'); // If the engine is not configurable, don't respond to any of the editing // or configuration routes. if (!$this->isPanelEngineConfigurable()) { switch ($panel_action) { case 'view': break; default: return new Aphront404Response(); } } $panel_id = $request->getURIData('panelID'); $panel_list = $this->loadPanels(); $selected_panel = null; if (strlen($panel_id)) { $panel_id_int = (int)$panel_id; foreach ($panel_list as $panel) { if ($panel_id_int) { if ((int)$panel->getID() === $panel_id_int) { $selected_panel = $panel; break; } } $builtin_key = $panel->getBuiltinKey(); if ($builtin_key === (string)$panel_id) { $selected_panel = $panel; break; } } } switch ($panel_action) { case 'view': case 'info': case 'hide': case 'default': case 'builtin': if (!$selected_panel) { return new Aphront404Response(); } break; } $navigation = $this->buildNavigation(); $navigation->selectFilter('panel.configure'); $crumbs = $controller->buildApplicationCrumbsForEditEngine(); switch ($panel_action) { case 'view': $content = $this->buildPanelViewContent($selected_panel); break; case 'configure': $content = $this->buildPanelConfigureContent($panel_list); $crumbs->addTextCrumb(pht('Configure Menu')); break; case 'reorder': $content = $this->buildPanelReorderContent($panel_list); break; case 'new': $panel_key = $request->getURIData('panelKey'); $content = $this->buildPanelNewContent($panel_key); break; case 'builtin': $content = $this->buildPanelBuiltinContent($selected_panel); break; case 'hide': $content = $this->buildPanelHideContent($selected_panel); break; case 'default': $content = $this->buildPanelDefaultContent( $selected_panel, $panel_list); break; case 'edit': $content = $this->buildPanelEditContent(); break; default: throw new Exception( pht( 'Unsupported panel action "%s".', $panel_action)); } if ($content instanceof AphrontResponse) { return $content; } if ($content instanceof AphrontResponseProducerInterface) { return $content; } return $controller->newPage() ->setTitle(pht('Profile Stuff')) ->setNavigation($navigation) ->setCrumbs($crumbs) ->appendChild($content); } public function buildNavigation() { if ($this->navigation) { return $this->navigation; } $nav = id(new AphrontSideNavFilterView()) ->setIsProfileMenu(true) ->setBaseURI(new PhutilURI($this->getPanelURI(''))); $panels = $this->getPanels(); foreach ($panels as $panel) { if ($panel->isDisabled()) { continue; } $items = $panel->buildNavigationMenuItems(); foreach ($items as $item) { $this->validateNavigationMenuItem($item); } // If the panel produced only a single item which does not otherwise // have a key, try to automatically assign it a reasonable key. This // makes selecting the correct item simpler. if (count($items) == 1) { $item = head($items); if ($item->getKey() === null) { $builtin_key = $panel->getBuiltinKey(); $panel_phid = $panel->getPHID(); if ($builtin_key !== null) { $item->setKey($builtin_key); } else if ($panel_phid !== null) { $item->setKey($panel_phid); } } } foreach ($items as $item) { $nav->addMenuItem($item); } } $more_items = $this->newAutomaticMenuItems($nav); foreach ($more_items as $item) { $nav->addMenuItem($item); } $nav->selectFilter(null); $this->navigation = $nav; return $this->navigation; } private function getPanels() { if ($this->panels === null) { $this->panels = $this->loadPanels(); } return $this->panels; } private function loadPanels() { $viewer = $this->getViewer(); $object = $this->getProfileObject(); $panels = $this->loadBuiltinProfilePanels(); $stored_panels = id(new PhabricatorProfilePanelConfigurationQuery()) ->setViewer($viewer) ->withProfilePHIDs(array($object->getPHID())) ->execute(); // Merge the stored panels into the builtin panels. If a builtin panel has // a stored version, replace the defaults with the stored changes. foreach ($stored_panels as $stored_panel) { $builtin_key = $stored_panel->getBuiltinKey(); if ($builtin_key !== null) { // If this builtin actually exists, replace the builtin with the // stored configuration. Otherwise, we're just going to drop the // stored config: it corresponds to an out-of-date or uninstalled // panel. if (isset($panels[$builtin_key])) { $panels[$builtin_key] = $stored_panel; } else { continue; } } else { $panels[] = $stored_panel; } } foreach ($panels as $panel) { $impl = $panel->getPanel(); $impl->setViewer($viewer); } $panels = msort($panels, 'getSortKey'); // Normalize keys since callers shouldn't rely on this array being // partially keyed. $panels = array_values($panels); // Make sure exactly one valid panel is marked as default. $default = null; $first = null; foreach ($panels as $panel) { if (!$panel->canMakeDefault()) { continue; } if ($panel->isDefault()) { $default = $panel; break; } if ($first === null) { $first = $panel; } } if (!$default) { $default = $first; } if ($default) { $this->setDefaultPanel($default); } return $panels; } private function loadBuiltinProfilePanels() { $object = $this->getProfileObject(); $builtins = $this->getBuiltinProfilePanels($object); $panels = PhabricatorProfilePanel::getAllPanels(); $order = 1; $map = array(); foreach ($builtins as $builtin) { $builtin_key = $builtin->getBuiltinKey(); if (!$builtin_key) { throw new Exception( pht( 'Object produced a builtin panel with no builtin panel key! '. 'Builtin panels must have a unique key.')); } if (isset($map[$builtin_key])) { throw new Exception( pht( 'Object produced two panels with the same builtin key ("%s"). '. 'Each panel must have a unique builtin key.', $builtin_key)); } $panel_key = $builtin->getPanelKey(); $panel = idx($panels, $panel_key); if (!$panel) { throw new Exception( pht( 'Builtin panel ("%s") specifies a bad panel key ("%s"); there '. 'is no corresponding panel implementation available.', $builtin_key, $panel_key)); } $builtin ->setProfilePHID($object->getPHID()) ->attachPanel($panel) ->attachProfileObject($object) ->setPanelOrder($order); $map[$builtin_key] = $builtin; $order++; } return $map; } private function validateNavigationMenuItem($item) { if (!($item instanceof PHUIListItemView)) { throw new Exception( pht( 'Expected buildNavigationMenuItems() to return a list of '. 'PHUIListItemView objects, but got a surprise.')); } } private function newAutomaticMenuItems(AphrontSideNavFilterView $nav) { $items = array(); // NOTE: We're adding a spacer item for the fixed footer, so that if the // menu taller than the page content you can still scroll down the page far // enough to access the last item without the content being obscured by the // fixed items. $items[] = id(new PHUIListItemView()) ->setHideInApplicationMenu(true) ->addClass('phui-profile-menu-spacer'); if ($this->isPanelEngineConfigurable()) { $viewer = $this->getViewer(); $object = $this->getProfileObject(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); $expanded_edit_icon = id(new PHUIIconCircleView()) ->addClass('phui-list-item-icon') ->addClass('phui-profile-menu-visible-when-expanded') ->setIconFont('fa-pencil'); $collapsed_edit_icon = id(new PHUIIconCircleView()) ->addClass('phui-list-item-icon') ->addClass('phui-profile-menu-visible-when-collapsed') ->setIconFont('fa-pencil') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Edit Menu'), 'align' => 'E', )); $items[] = id(new PHUIListItemView()) ->setName('Edit Menu') ->setKey('panel.configure') ->addIcon($expanded_edit_icon) ->addIcon($collapsed_edit_icon) ->addClass('phui-profile-menu-footer') ->addClass('phui-profile-menu-footer-1') ->setHref($this->getPanelURI('configure/')) ->setDisabled(!$can_edit) ->setWorkflow(!$can_edit); } $collapse_id = celerity_generate_unique_node_id(); $viewer = $this->getViewer(); $collapse_key = PhabricatorUserPreferences::PREFERENCE_PROFILE_MENU_COLLAPSED; $preferences = $viewer->loadPreferences(); $is_collapsed = $preferences->getPreference($collapse_key, false); if ($is_collapsed) { $nav->addClass('phui-profile-menu-collapsed'); } else { $nav->addClass('phui-profile-menu-expanded'); } if ($viewer->isLoggedIn()) { $settings_uri = '/settings/adjust/?key='.$collapse_key; } else { $settings_uri = null; } Javelin::initBehavior( 'phui-profile-menu', array( 'menuID' => $nav->getMainID(), 'collapseID' => $collapse_id, 'isCollapsed' => (bool)$is_collapsed, 'settingsURI' => $settings_uri, )); $collapse_icon = id(new PHUIIconCircleView()) ->addClass('phui-list-item-icon') ->addClass('phui-profile-menu-visible-when-expanded') ->setIconFont('fa-angle-left'); $expand_icon = id(new PHUIIconCircleView()) ->addClass('phui-list-item-icon') ->addClass('phui-profile-menu-visible-when-collapsed') ->addSigil('has-tooltip') ->setMetadata( array( 'tip' => pht('Expand'), 'align' => 'E', )) ->setIconFont('fa-angle-right'); $items[] = id(new PHUIListItemView()) ->setName('Collapse') ->addIcon($collapse_icon) ->addIcon($expand_icon) ->setID($collapse_id) ->addClass('phui-profile-menu-footer') ->addClass('phui-profile-menu-footer-2') ->setHideInApplicationMenu(true) ->setHref('#'); return $items; } public function getConfigureURI() { return $this->getPanelURI('configure/'); } private function buildPanelReorderContent(array $panels) { $viewer = $this->getViewer(); $object = $this->getProfileObject(); PhabricatorPolicyFilter::requireCapability( $viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); $controller = $this->getController(); $request = $controller->getRequest(); $request->validateCSRF(); $order = $request->getStrList('order'); $by_builtin = array(); $by_id = array(); foreach ($panels as $key => $panel) { $id = $panel->getID(); if ($id) { $by_id[$id] = $key; continue; } $builtin_key = $panel->getBuiltinKey(); if ($builtin_key) { $by_builtin[$builtin_key] = $key; continue; } } $key_order = array(); foreach ($order as $order_item) { if (isset($by_id[$order_item])) { $key_order[] = $by_id[$order_item]; continue; } if (isset($by_builtin[$order_item])) { $key_order[] = $by_builtin[$order_item]; continue; } } $panels = array_select_keys($panels, $key_order) + $panels; $type_order = PhabricatorProfilePanelConfigurationTransaction::TYPE_ORDER; $order = 1; foreach ($panels as $panel) { $xactions = array(); $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) ->setTransactionType($type_order) ->setNewValue($order); $editor = id(new PhabricatorProfilePanelEditor()) ->setContentSourceFromRequest($request) ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true) ->applyTransactions($panel, $xactions); $order++; } return id(new AphrontRedirectResponse()) ->setURI($this->getConfigureURI()); } private function buildPanelConfigureContent(array $panels) { $viewer = $this->getViewer(); $object = $this->getProfileObject(); PhabricatorPolicyFilter::requireCapability( $viewer, $object, PhabricatorPolicyCapability::CAN_EDIT); $list_id = celerity_generate_unique_node_id(); Javelin::initBehavior( 'reorder-profile-menu-items', array( 'listID' => $list_id, 'orderURI' => $this->getPanelURI('reorder/'), )); $list = id(new PHUIObjectItemListView()) ->setID($list_id); foreach ($panels as $panel) { $id = $panel->getID(); $builtin_key = $panel->getBuiltinKey(); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $panel, PhabricatorPolicyCapability::CAN_EDIT); $item = id(new PHUIObjectItemView()); $name = $panel->getDisplayName(); $type = $panel->getPanelTypeName(); if (!strlen(trim($name))) { $name = pht('Untitled "%s" Item', $type); } $item->setHeader($name); $item->addAttribute($type); if ($can_edit) { $item ->setGrippable(true) ->addSigil('profile-menu-item') ->setMetadata( array( 'key' => nonempty($id, $builtin_key), )); if ($id) { $default_uri = $this->getPanelURI("default/{$id}/"); } else { $default_uri = $this->getPanelURI("default/{$builtin_key}/"); } if ($panel->isDefault()) { $default_icon = 'fa-thumb-tack green'; $default_text = pht('Current Default'); } else if ($panel->canMakeDefault()) { $default_icon = 'fa-thumb-tack'; $default_text = pht('Make Default'); } else { $default_text = null; } if ($default_text !== null) { $item->addAction( id(new PHUIListItemView()) ->setHref($default_uri) ->setWorkflow(true) ->setName($default_text) ->setIcon($default_icon)); } if ($id) { $item->setHref($this->getPanelURI("edit/{$id}/")); $hide_uri = $this->getPanelURI("hide/{$id}/"); } else { $item->setHref($this->getPanelURI("builtin/{$builtin_key}/")); $hide_uri = $this->getPanelURI("hide/{$builtin_key}/"); } if ($panel->isDisabled()) { $hide_icon = 'fa-plus'; $hide_text = pht('Enable'); } else if ($panel->getBuiltinKey() !== null) { $hide_icon = 'fa-times'; $hide_text = pht('Disable'); } else { $hide_icon = 'fa-times'; $hide_text = pht('Delete'); } $item->addAction( id(new PHUIListItemView()) ->setHref($hide_uri) ->setWorkflow(true) ->setName($hide_text) ->setIcon($hide_icon)); } if ($panel->isDisabled()) { $item->setDisabled(true); } $list->addItem($item); } $action_view = id(new PhabricatorActionListView()) ->setUser($viewer); $panel_types = PhabricatorProfilePanel::getAllPanels(); $action_view->addAction( id(new PhabricatorActionView()) ->setLabel(true) ->setName(pht('Add New Menu Item...'))); foreach ($panel_types as $panel_type) { if (!$panel_type->canAddToObject($object)) { continue; } $panel_key = $panel_type->getPanelKey(); $action_view->addAction( id(new PhabricatorActionView()) ->setIcon($panel_type->getPanelTypeIcon()) ->setName($panel_type->getPanelTypeName()) ->setHref($this->getPanelURI("new/{$panel_key}/"))); } $action_view->addAction( id(new PhabricatorActionView()) ->setLabel(true) ->setName(pht('Documentation'))); $doc_link = PhabricatorEnv::getDoclink('Profile Menu User Guide'); $doc_name = pht('Profile Menu User Guide'); $action_view->addAction( id(new PhabricatorActionView()) ->setIcon('fa-book') ->setHref($doc_link) ->setName($doc_name)); $action_button = id(new PHUIButtonView()) ->setTag('a') ->setText(pht('Configure Menu')) ->setHref('#') ->setIconFont('fa-gear') ->setDropdownMenu($action_view); $header = id(new PHUIHeaderView()) ->setHeader(pht('Profile Menu Items')) ->addActionLink($action_button); $box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setObjectList($list); return $box; } private function buildPanelNewContent($panel_key) { $panel_types = PhabricatorProfilePanel::getAllPanels(); $panel_type = idx($panel_types, $panel_key); if (!$panel_type) { return new Aphront404Response(); } $object = $this->getProfileObject(); if (!$panel_type->canAddToObject($object)) { return new Aphront404Response(); } $configuration = PhabricatorProfilePanelConfiguration::initializeNewPanelConfiguration( $object, $panel_type); $viewer = $this->getViewer(); PhabricatorPolicyFilter::requireCapability( $viewer, $configuration, PhabricatorPolicyCapability::CAN_EDIT); $controller = $this->getController(); return id(new PhabricatorProfilePanelEditEngine()) ->setPanelEngine($this) ->setProfileObject($object) ->setNewPanelConfiguration($configuration) ->setController($controller) ->buildResponse(); } private function buildPanelEditContent() { $viewer = $this->getViewer(); $object = $this->getProfileObject(); $controller = $this->getController(); return id(new PhabricatorProfilePanelEditEngine()) ->setPanelEngine($this) ->setProfileObject($object) ->setController($controller) ->buildResponse(); } private function buildPanelBuiltinContent( PhabricatorProfilePanelConfiguration $configuration) { // If this builtin panel has already been persisted, redirect to the // edit page. $id = $configuration->getID(); if ($id) { return id(new AphrontRedirectResponse()) ->setURI($this->getPanelURI("edit/{$id}/")); } // Otherwise, act like we're creating a new panel, we're just starting // with the builtin template. $viewer = $this->getViewer(); PhabricatorPolicyFilter::requireCapability( $viewer, $configuration, PhabricatorPolicyCapability::CAN_EDIT); $object = $this->getProfileObject(); $controller = $this->getController(); return id(new PhabricatorProfilePanelEditEngine()) ->setIsBuiltin(true) ->setPanelEngine($this) ->setProfileObject($object) ->setNewPanelConfiguration($configuration) ->setController($controller) ->buildResponse(); } private function buildPanelHideContent( PhabricatorProfilePanelConfiguration $configuration) { $controller = $this->getController(); $request = $controller->getRequest(); $viewer = $this->getViewer(); PhabricatorPolicyFilter::requireCapability( $viewer, $configuration, PhabricatorPolicyCapability::CAN_EDIT); if ($configuration->getBuiltinKey() === null) { $new_value = null; $title = pht('Delete Menu Item'); $body = pht('Delete this menu item?'); $button = pht('Delete Menu Item'); } else if ($configuration->isDisabled()) { $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE; $title = pht('Enable Menu Item'); $body = pht( 'Enable this menu item? It will appear in the menu again.'); $button = pht('Enable Menu Item'); } else { $new_value = PhabricatorProfilePanelConfiguration::VISIBILITY_DISABLED; $title = pht('Disable Menu Item'); $body = pht( 'Disable this menu item? It will no longer appear in the menu, but '. 'you can re-enable it later.'); $button = pht('Disable Menu Item'); } $v_visibility = $configuration->getVisibility(); if ($request->isFormPost()) { if ($new_value === null) { $configuration->delete(); } else { $type_visibility = PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY; $xactions = array(); $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) ->setTransactionType($type_visibility) ->setNewValue($new_value); $editor = id(new PhabricatorProfilePanelEditor()) ->setContentSourceFromRequest($request) ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true) ->applyTransactions($configuration, $xactions); } return id(new AphrontRedirectResponse()) ->setURI($this->getConfigureURI()); } return $controller->newDialog() ->setTitle($title) ->appendParagraph($body) ->addCancelButton($this->getConfigureURI()) ->addSubmitButton($button); } private function buildPanelDefaultContent( PhabricatorProfilePanelConfiguration $configuration, array $panels) { $controller = $this->getController(); $request = $controller->getRequest(); $viewer = $this->getViewer(); PhabricatorPolicyFilter::requireCapability( $viewer, $configuration, PhabricatorPolicyCapability::CAN_EDIT); $done_uri = $this->getConfigureURI(); if (!$configuration->canMakeDefault()) { return $controller->newDialog() ->setTitle(pht('Not Defaultable')) ->appendParagraph( pht( 'This item can not be set as the default item. This is usually '. 'because the item has no page of its own, or links to an '. 'external page.')) ->addCancelButton($done_uri); } if ($configuration->isDefault()) { return $controller->newDialog() ->setTitle(pht('Already Default')) ->appendParagraph( pht( 'This item is already set as the default item for this menu.')) ->addCancelButton($done_uri); } + if ($request->isFormPost()) { + $key = $configuration->getID(); + if (!$key) { + $key = $configuration->getBuiltinKey(); + } + + $this->adjustDefault($key); + + return id(new AphrontRedirectResponse()) + ->setURI($done_uri); + } + + return $controller->newDialog() + ->setTitle(pht('Make Default')) + ->appendParagraph( + pht( + 'Set this item as the default for this menu? Users arriving on '. + 'this page will be shown the content of this item by default.')) + ->addCancelButton($done_uri) + ->addSubmitButton(pht('Make Default')); + } + + protected function newPanel() { + return PhabricatorProfilePanelConfiguration::initializeNewBuiltin(); + } + + public function adjustDefault($key) { + $controller = $this->getController(); + $request = $controller->getRequest(); + $viewer = $request->getViewer(); + + $panels = $this->loadPanels(); + + // To adjust the default panel, we first change any existing panels that + // are marked as defaults to "visible", then make the new default panel + // the default. + + $default = array(); + $visible = array(); + + foreach ($panels as $panel) { + $builtin_key = $panel->getBuiltinKey(); + $id = $panel->getID(); + + $is_target = + (($builtin_key !== null) && ($builtin_key === $key)) || + (($id !== null) && ($id === (int)$key)); + + if ($is_target) { + if (!$panel->isDefault()) { + $default[] = $panel; + } + } else { + if ($panel->isDefault()) { + $visible[] = $panel; + } + } + } + $type_visibility = PhabricatorProfilePanelConfigurationTransaction::TYPE_VISIBILITY; $v_visible = PhabricatorProfilePanelConfiguration::VISIBILITY_VISIBLE; $v_default = PhabricatorProfilePanelConfiguration::VISIBILITY_DEFAULT; - if ($request->isFormPost()) { - // First, mark any existing default panels as merely visible. - foreach ($panels as $panel) { - if (!$panel->isDefault()) { - continue; - } + $apply = array( + array($v_visible, $visible), + array($v_default, $default), + ); + foreach ($apply as $group) { + list($value, $panels) = $group; + foreach ($panels as $panel) { $xactions = array(); $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) ->setTransactionType($type_visibility) - ->setNewValue($v_visible); + ->setNewValue($value); $editor = id(new PhabricatorProfilePanelEditor()) ->setContentSourceFromRequest($request) ->setActor($viewer) ->setContinueOnMissingFields(true) ->setContinueOnNoEffect(true) ->applyTransactions($panel, $xactions); } - - // Now, make this panel the default. - $xactions = array(); - - $xactions[] = id(new PhabricatorProfilePanelConfigurationTransaction()) - ->setTransactionType($type_visibility) - ->setNewValue($v_default); - - $editor = id(new PhabricatorProfilePanelEditor()) - ->setContentSourceFromRequest($request) - ->setActor($viewer) - ->setContinueOnMissingFields(true) - ->setContinueOnNoEffect(true) - ->applyTransactions($configuration, $xactions); - - return id(new AphrontRedirectResponse()) - ->setURI($done_uri); } - return $controller->newDialog() - ->setTitle(pht('Make Default')) - ->appendParagraph( - pht( - 'Set this item as the default for this menu? Users arriving on '. - 'this page will be shown the content of this item by default.')) - ->addCancelButton($done_uri) - ->addSubmitButton(pht('Make Default')); - } - - protected function newPanel() { - return PhabricatorProfilePanelConfiguration::initializeNewBuiltin(); + return $this; } }