diff --git a/src/applications/audit/controller/PhabricatorAuditListController.php b/src/applications/audit/controller/PhabricatorAuditListController.php index 41823d76a4..9933b1851a 100644 --- a/src/applications/audit/controller/PhabricatorAuditListController.php +++ b/src/applications/audit/controller/PhabricatorAuditListController.php @@ -1,502 +1,500 @@ filter = idx($data, 'filter'); $this->name = idx($data, 'name'); } public function processRequest() { $request = $this->getRequest(); $nav = $this->buildSideNavView(); if ($request->isFormPost()) { // If the list filter is POST'ed, redirect to GET so the page can be // bookmarked. $uri = $request->getRequestURI(); $phid = head($request->getArr('set_phid')); $user = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $phid); $uri = $request->getRequestURI(); if ($user) { $username = phutil_escape_uri($user->getUsername()); $uri = '/audit/view/'.$this->filter.'/'.$username.'/'; } else if ($phid) { $uri = $request->getRequestURI(); $uri = $uri->alter('phid', $phid); } return id(new AphrontRedirectResponse())->setURI($uri); } $this->filterStatus = $request->getStr('status', 'all'); $handle = $this->loadHandle(); $nav->appendChild($this->buildListFilters($handle)); $title = null; $message = null; if (!$handle) { switch ($this->filter) { case 'project': $title = pht('Choose A Project'); $message = pht('Choose a project to view audits for.'); break; case 'repository': $title = pht('Choose A Repository'); $message = pht('Choose a repository to view audits for.'); break; case 'package': case 'packagecommits': $title = pht('Choose a Package'); $message = pht('Choose a package to view audits for.'); break; } } if (!$message) { $nav->appendChild($this->buildViews($handle)); } else { $panel = id(new AphrontErrorView()) ->setSeverity(AphrontErrorView::SEVERITY_NODATA) ->setTitle($title) ->appendChild($message); $nav->appendChild($panel); } return $this->buildApplicationPage( $nav, array( 'title' => pht('Audits'), 'device' => true, )); } private function buildListFilters(PhabricatorObjectHandle $handle = null) { $request = $this->getRequest(); $user = $request->getUser(); $form = new AphrontFormView(); $form->setUser($user); $show_status = false; $show_user = false; $show_project = false; $show_package = false; $show_repository = false; switch ($this->filter) { case 'audits': case 'commits': $show_status = true; break; case 'active': $show_user = true; break; case 'author': case 'user': $show_user = true; $show_status = true; break; case 'project': $show_project = true; $show_status = true; break; case 'repository': $show_repository = true; $show_status = true; break; case 'package': case 'packagecommits': $show_package = true; $show_status = true; break; } if ($show_user || $show_project || $show_package || $show_repository) { if ($show_user) { $uri = '/typeahead/common/users/'; $label = pht('User'); } else if ($show_project) { $uri = '/typeahead/common/projects/'; $label = pht('Project'); } else if ($show_package) { $uri = '/typeahead/common/packages/'; $label = pht('Package'); } else if ($show_repository) { $uri = '/typeahead/common/repositories/'; $label = pht('Repository'); } $tok_value = null; if ($handle) { - $tok_value = array( - $handle->getPHID() => $handle->getFullName(), - ); + $tok_value = array($handle); } $form->appendChild( id(new AphrontFormTokenizerControl()) ->setName('set_phid') ->setLabel($label) ->setLimit(1) ->setDatasource($uri) ->setValue($tok_value)); } if ($show_status) { $form->appendChild( id(new AphrontFormToggleButtonsControl()) ->setName('status') ->setLabel(pht('Status')) ->setBaseURI($request->getRequestURI(), 'status') ->setValue($this->filterStatus) ->setButtons( array( 'all' => pht('All'), 'open' => pht('Open'), 'concern' => pht('Concern Raised'), ))); } $form->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Filter Audits'))); $view = new AphrontListFilterView(); $view->appendChild($form); return $view; } private function loadHandle() { $request = $this->getRequest(); $default = null; switch ($this->filter) { case 'user': case 'active': case 'author': $default = $request->getUser()->getPHID(); if ($this->name) { $user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $this->name); if ($user) { $default = $user->getPHID(); } } break; } $phid = $request->getStr('phid', $default); if (!$phid) { return null; } $phids = array($phid); $handles = $this->loadViewerHandles($phids); $handle = $handles[$phid]; $this->validateHandle($handle); return $handle; } private function validateHandle(PhabricatorObjectHandle $handle) { $type = $handle->getType(); switch ($this->filter) { case 'active': case 'user': case 'author': if ($type !== PhabricatorPeoplePHIDTypeUser::TYPECONST) { throw new Exception("PHID must be a user PHID!"); } break; case 'package': case 'packagecommits': if ($type !== PhabricatorOwnersPHIDTypePackage::TYPECONST) { throw new Exception("PHID must be a package PHID!"); } break; case 'project': if ($type !== PhabricatorProjectPHIDTypeProject::TYPECONST) { throw new Exception("PHID must be a project PHID!"); } break; case 'repository': if ($type !== PhabricatorRepositoryPHIDTypeRepository::TYPECONST) { throw new Exception("PHID must be a repository PHID!"); } break; case 'audits': case 'commits': break; default: throw new Exception("Unknown filter '{$this->filter}'!"); } } private function buildViews(PhabricatorObjectHandle $handle = null) { $views = array(); switch ($this->filter) { case 'active': $views[] = $this->buildCommitView($handle); $views[] = $this->buildAuditView($handle); break; case 'audits': case 'user': case 'package': case 'project': case 'repository': $views[] = $this->buildAuditView($handle); break; case 'commits': case 'packagecommits': case 'author': $views[] = $this->buildCommitView($handle); break; } return $views; } private function buildAuditView(PhabricatorObjectHandle $handle = null) { $request = $this->getRequest(); $query = new PhabricatorAuditQuery(); $use_pager = ($this->filter != 'active'); if ($use_pager) { $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'offset'); $pager->setOffset($request->getInt('offset')); $query->setOffset($pager->getOffset()); $query->setLimit($pager->getPageSize() + 1); } $awaiting = null; $phids = null; $repository_phids = null; switch ($this->filter) { case 'user': case 'active': $obj = id(new PhabricatorUser())->loadOneWhere( 'phid = %s', $handle->getPHID()); if (!$obj) { throw new Exception("Invalid user!"); } $phids = PhabricatorAuditCommentEditor::loadAuditPHIDsForUser($obj); $awaiting = $obj; break; case 'project': case 'package': $phids = array($handle->getPHID()); break; case 'repository': $repository_phids = array($handle->getPHID()); break; case 'audits'; break; default: throw new Exception("Unknown filter!"); } if ($phids) { $query->withAuditorPHIDs($phids); } if ($repository_phids) { $query->withRepositoryPHIDs($repository_phids); } if ($awaiting) { $query->withAwaitingUser($awaiting); } switch ($this->filter) { case 'active': $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); break; default: switch ($this->filterStatus) { case 'open': $query->withStatus(PhabricatorAuditQuery::STATUS_OPEN); break; case 'concern': $query->withStatus(PhabricatorAuditQuery::STATUS_CONCERN); break; } break; } if ($handle) { $handle_name = $handle->getFullName(); } else { $handle_name = null; } switch ($this->filter) { case 'active': $header = pht('Required Audits'); $nodata = pht('No commits require your audit.'); break; case 'user': $header = pht("Audits for %s", $handle_name); $nodata = pht("No matching audits by %s.", $handle_name); break; case 'audits': $header = pht('Audits'); $nodata = pht('No matching audits.'); break; case 'project': $header = pht("Audits in Project %s", $handle_name); $nodata = pht("No matching audits in project %s.", $handle_name); break; case 'package': $header = pht("Audits for Package %s", $handle_name); $nodata = pht("No matching audits in package %s.", $handle_name); break; case 'repository': $header = pht("Audits in Repository %s", $handle_name); $nodata = pht("No matching audits in repository %s.", $handle_name); break; } $query->needCommitData(true); $audits = $query->execute(); if ($use_pager) { $audits = $pager->sliceResults($audits); } $view = new PhabricatorAuditListView(); $view->setAudits($audits); $view->setCommits($query->getCommits()); $view->setUser($request->getUser()); $view->setNoDataString($nodata); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($view); $panel->setNoBackground(); if ($use_pager) { $panel->appendChild($pager); } return $panel; } private function buildCommitView(PhabricatorObjectHandle $handle = null) { $request = $this->getRequest(); $query = new PhabricatorAuditCommitQuery(); $query->needCommitData(true); $query->needAudits(true); $use_pager = ($this->filter != 'active'); if ($use_pager) { $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'offset'); $pager->setOffset($request->getInt('offset')); $query->setOffset($pager->getOffset()); $query->setLimit($pager->getPageSize() + 1); } switch ($this->filter) { case 'active': case 'author': $query->withAuthorPHIDs(array($handle->getPHID())); break; case 'packagecommits': $query->withPackagePHIDs(array($handle->getPHID())); break; } switch ($this->filter) { case 'active': $query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN); break; default: switch ($this->filterStatus) { case 'open': $query->withStatus(PhabricatorAuditCommitQuery::STATUS_OPEN); break; case 'concern': $query->withStatus(PhabricatorAuditCommitQuery::STATUS_CONCERN); break; } break; } if ($handle) { $handle_name = $handle->getName(); } else { $handle_name = null; } switch ($this->filter) { case 'active': $header = pht('Problem Commits'); $nodata = pht('None of your commits have open concerns.'); break; case 'author': $header = pht("Commits by %s", $handle_name); $nodata = pht("No matching commits by %s.", $handle_name); break; case 'commits': $header = pht("Commits"); $nodata = pht("No matching commits."); break; case 'packagecommits': $header = pht("Commits in Package %s", $handle_name); $nodata = pht("No matching commits in package %s.", $handle_name); break; } $commits = $query->execute(); if ($use_pager) { $commits = $pager->sliceResults($commits); } $view = new PhabricatorAuditCommitListView(); $view->setUser($request->getUser()); $view->setCommits($commits); $view->setNoDataString($nodata); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($view); $panel->setNoBackground(); if ($use_pager) { $panel->appendChild($pager); } return $panel; } } diff --git a/src/applications/conpherence/controller/ConpherenceNewController.php b/src/applications/conpherence/controller/ConpherenceNewController.php index 077ed2d222..b2a3cdd4a7 100644 --- a/src/applications/conpherence/controller/ConpherenceNewController.php +++ b/src/applications/conpherence/controller/ConpherenceNewController.php @@ -1,100 +1,102 @@ getRequest(); $user = $request->getUser(); $title = pht('New Message'); $participants = array(); + $participant_prefill = null; $message = ''; $e_participants = null; $e_message = null; // this comes from ajax requests from all over. should be a single phid. - $participant_prefill = $request->getStr('participant'); - if ($participant_prefill) { - $participants[] = $participant_prefill; - } if ($request->isFormPost()) { $participants = $request->getArr('participants'); $message = $request->getStr('message'); list($error_codes, $conpherence) = ConpherenceEditor::createConpherence( $user, $participants, $conpherence_title = null, $message, PhabricatorContentSource::newFromRequest($request)); if ($error_codes) { foreach ($error_codes as $error_code) { switch ($error_code) { case ConpherenceEditor::ERROR_EMPTY_MESSAGE: $e_message = true; break; case ConpherenceEditor::ERROR_EMPTY_PARTICIPANTS: $e_participants = true; break; } } } else { $uri = $this->getApplicationURI($conpherence->getID()); return id(new AphrontRedirectResponse()) ->setURI($uri); } + } else { + $participant_prefill = $request->getStr('participant'); + if ($participant_prefill) { + $participants[] = $participant_prefill; + } } + $participant_handles = array(); if ($participants) { - $handles = id(new PhabricatorHandleQuery()) + $participant_handles = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs($participants) ->execute(); - $participant_handles = mpull($handles, 'getFullName', 'getPHID'); } $submit_uri = $this->getApplicationURI('new/'); $cancel_uri = $this->getApplicationURI(); // TODO - we can get a better cancel_uri once we get better at crazy // ajax jonx T2086 if ($participant_prefill) { - $handle = $handles[$participant_prefill]; + $handle = $participant_handles[$participant_prefill]; $cancel_uri = $handle->getURI(); } $dialog = id(new AphrontDialogView()) ->setWidth(AphrontDialogView::WIDTH_FORM) ->setUser($user) ->setTitle($title) ->addCancelButton($cancel_uri) ->addSubmitButton(pht('Send Message')); $form = id(new PHUIFormLayoutView()) ->setUser($user) ->setFullWidth(true) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('participants') ->setValue($participant_handles) ->setUser($user) ->setDatasource('/typeahead/common/users/') ->setLabel(pht('To')) ->setError($e_participants)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setName('message') ->setValue($message) ->setLabel(pht('Message')) ->setError($e_message)); $dialog->appendChild($form); return id(new AphrontDialogResponse())->setDialog($dialog); } } diff --git a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php index d5669522e4..ca843e2c2b 100644 --- a/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php +++ b/src/applications/countdown/query/PhabricatorCountdownSearchEngine.php @@ -1,97 +1,96 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter('upcoming', $request->getBool('upcoming')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorCountdownQuery()); $author_phids = $saved->getParameter('authorPHIDs', array()); if ($author_phids) { $query->withAuthorPHIDs($author_phids); } if ($saved->getParameter('upcoming')) { $query->withUpcoming(true); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $upcoming = $saved_query->getParameter('upcoming'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'upcoming', 1, pht('Show only countdowns that are still counting down.'), $upcoming)); } protected function getURI($path) { return '/countdown/'.$path; } public function getBuiltinQueryNames() { $names = array( 'upcoming' => pht('Upcoming'), 'all' => pht('All'), ); if ($this->requireViewer()->getPHID()) { $names['authored'] = pht('Authored'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); case 'upcoming': return $query->setParameter('upcoming', true); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php b/src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php index bfbf83e012..01e83e4846 100644 --- a/src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialCCsFieldSpecification.php @@ -1,110 +1,110 @@ getCCPHIDs(); } public function renderLabelForRevisionView() { return 'CCs:'; } public function renderValueForRevisionView() { return $this->renderUserList($this->getCCPHIDs()); } private function getCCPHIDs() { $revision = $this->getRevision(); return $revision->getCCPHIDs(); } public function shouldAppearOnEdit() { return true; } protected function didSetRevision() { $this->ccs = $this->getCCPHIDs(); } public function getRequiredHandlePHIDsForRevisionEdit() { return $this->ccs; } public function getRequiredHandlePHIDsForCommitMessage() { return $this->ccs; } public function setValueFromRequest(AphrontRequest $request) { $this->ccs = $request->getArr('cc'); return $this; } public function renderEditControl() { $cc_map = array(); foreach ($this->ccs as $phid) { - $cc_map[$phid] = $this->getHandle($phid)->getFullName(); + $cc_map[] = $this->getHandle($phid); } return id(new AphrontFormTokenizerControl()) ->setLabel('CC') ->setName('cc') ->setUser($this->getUser()) ->setDatasource('/typeahead/common/mailable/') ->setValue($cc_map); } public function willWriteRevision(DifferentialRevisionEditor $editor) { $editor->setCCPHIDs($this->ccs); } public function shouldAppearOnCommitMessage() { return true; } public function getCommitMessageKey() { return 'ccPHIDs'; } public function setValueFromParsedCommitMessage($value) { $this->ccs = array_unique(nonempty($value, array())); return $this; } public function renderLabelForCommitMessage() { return 'CC'; } public function renderValueForCommitMessage($is_edit) { if (!$this->ccs) { return null; } $names = array(); foreach ($this->ccs as $phid) { $handle = $this->getHandle($phid); if ($handle->isComplete()) { $names[] = $handle->getName(); } } return implode(', ', $names); } public function getSupportedCommitMessageLabels() { return array( 'CC', 'CCs', ); } public function parseValueFromCommitMessage($value) { return $this->parseCommitMessageMailableList($value); } } diff --git a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php index b6d00ce7a8..b82267c7c2 100644 --- a/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php +++ b/src/applications/differential/field/specification/DifferentialReviewersFieldSpecification.php @@ -1,203 +1,203 @@ getReviewerPHIDs(); } public function renderLabelForRevisionView() { return pht('Reviewers'); } public function renderValueForRevisionView() { $reviewers = array(); foreach ($this->getRevision()->getReviewerStatus() as $reviewer) { if ($reviewer->isUser()) { $reviewers[] = $reviewer; } } if (!$reviewers) { // Renders "None". return $this->renderUserList(array()); } $view = id(new DifferentialReviewersView()) ->setUser($this->getUser()) ->setReviewers($reviewers) ->setHandles($this->getLoadedHandles()); $diff = $this->getRevision()->loadActiveDiff(); if ($diff) { $view->setActiveDiff($diff); } return $view; } private function getReviewerPHIDs() { $revision = $this->getRevision(); return $revision->getReviewers(); } public function shouldAppearOnEdit() { return true; } protected function didSetRevision() { $this->reviewers = $this->getReviewerPHIDs(); } public function getRequiredHandlePHIDsForRevisionEdit() { return $this->reviewers; } public function setValueFromRequest(AphrontRequest $request) { $this->reviewers = $request->getArr('reviewers'); return $this; } public function validateField() { if (!$this->hasRevision()) { return; } $self = PhabricatorEnv::getEnvConfig('differential.allow-self-accept'); if ($self) { return; } $author_phid = $this->getRevision()->getAuthorPHID(); if (!in_array($author_phid, $this->reviewers)) { return; } $this->error = 'Invalid'; throw new DifferentialFieldValidationException( "The owner of a revision may not be a reviewer."); } public function renderEditControl() { $reviewer_map = array(); foreach ($this->reviewers as $phid) { - $reviewer_map[$phid] = $this->getHandle($phid)->getFullName(); + $reviewer_map[] = $this->getHandle($phid); } return id(new AphrontFormTokenizerControl()) ->setLabel(pht('Reviewers')) ->setName('reviewers') ->setUser($this->getUser()) ->setDatasource('/typeahead/common/usersorprojects/') ->setValue($reviewer_map) ->setError($this->error); } public function willWriteRevision(DifferentialRevisionEditor $editor) { $editor->setReviewers($this->reviewers); } public function shouldAppearOnCommitMessage() { return true; } public function getCommitMessageKey() { return 'reviewerPHIDs'; } public function setValueFromParsedCommitMessage($value) { $this->reviewers = array_unique(nonempty($value, array())); return $this; } public function renderLabelForCommitMessage() { return 'Reviewers'; } public function getRequiredHandlePHIDsForCommitMessage() { return $this->reviewers; } public function renderValueForCommitMessage($is_edit) { if (!$this->reviewers) { return null; } $project_type = PhabricatorProjectPHIDTypeProject::TYPECONST; $names = array(); foreach ($this->reviewers as $phid) { $names[] = $this->getHandle($phid)->getObjectName(); } return implode(', ', $names); } public function getSupportedCommitMessageLabels() { return array( 'Reviewer', 'Reviewers', ); } public function parseValueFromCommitMessage($value) { return $this->parseCommitMessageUserOrProjectList($value); } public function shouldAppearOnRevisionList() { return true; } public function renderHeaderForRevisionList() { return 'Reviewers'; } public function renderValueForRevisionList(DifferentialRevision $revision) { $primary_reviewer = $revision->getPrimaryReviewer(); if ($primary_reviewer) { $names = array(); foreach ($revision->getReviewers() as $reviewer) { $names[] = $this->getHandle($reviewer)->renderLink(); } return phutil_implode_html(', ', $names); } else { return phutil_tag('em', array(), 'None'); } } public function getRequiredHandlePHIDsForRevisionList( DifferentialRevision $revision) { return $revision->getReviewers(); } public function renderValueForMail($phase) { if ($phase == DifferentialMailPhase::COMMENT) { return null; } if (!$this->reviewers) { return null; } $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getUser()) ->withPHIDs($this->reviewers) ->execute(); $handles = array_select_keys( $handles, array($this->getRevision()->getPrimaryReviewer())) + $handles; $names = mpull($handles, 'getObjectName'); return 'Reviewers: '.implode(', ', $names); } } diff --git a/src/applications/diffusion/controller/DiffusionCommitEditController.php b/src/applications/diffusion/controller/DiffusionCommitEditController.php index 06398d19e9..13e78ea1a0 100644 --- a/src/applications/diffusion/controller/DiffusionCommitEditController.php +++ b/src/applications/diffusion/controller/DiffusionCommitEditController.php @@ -1,98 +1,98 @@ getRequest()->getUser(); $this->diffusionRequest = DiffusionRequest::newFromDictionary($data); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $drequest = $this->getDiffusionRequest(); $callsign = $drequest->getRepository()->getCallsign(); $repository = $drequest->getRepository(); $commit = $drequest->loadCommit(); $page_title = pht('Edit Diffusion Commit'); if (!$commit) { return new Aphront404Response(); } $commit_phid = $commit->getPHID(); $edge_type = PhabricatorEdgeConfig::TYPE_COMMIT_HAS_PROJECT; $current_proj_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $commit_phid, $edge_type); $handles = $this->loadViewerHandles($current_proj_phids); - $proj_t_values = mpull($handles, 'getFullName', 'getPHID'); + $proj_t_values = $handles; if ($request->isFormPost()) { $proj_phids = $request->getArr('projects'); $new_proj_phids = array_values($proj_phids); $rem_proj_phids = array_diff($current_proj_phids, $new_proj_phids); $editor = id(new PhabricatorEdgeEditor()); $editor->setActor($user); foreach ($rem_proj_phids as $phid) { $editor->removeEdge($commit_phid, $edge_type, $phid); } foreach ($new_proj_phids as $phid) { $editor->addEdge($commit_phid, $edge_type, $phid); } $editor->save(); id(new PhabricatorSearchIndexer()) ->indexDocumentByPHID($commit->getPHID()); return id(new AphrontRedirectResponse()) ->setURI('/r'.$callsign.$commit->getCommitIdentifier()); } $tokenizer_id = celerity_generate_unique_node_id(); $form = id(new AphrontFormView()) ->setUser($user) ->setAction($request->getRequestURI()->getPath()) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($proj_t_values) ->setID($tokenizer_id) ->setCaption( javelin_tag( 'a', array( 'href' => '/project/create/', 'mustcapture' => true, 'sigil' => 'project-create', ), pht('Create New Project'))) ->setDatasource('/typeahead/common/projects/'));; Javelin::initBehavior('project-create', array( 'tokenizerID' => $tokenizer_id, )); $submit = id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton('/r'.$callsign.$commit->getCommitIdentifier()); $form->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($page_title) ->setForm($form); return $this->buildApplicationPage( array( $form_box, ), array( 'title' => $page_title, 'device' => true, )); } } diff --git a/src/applications/diffusion/controller/DiffusionLintController.php b/src/applications/diffusion/controller/DiffusionLintController.php index d7166047a5..881b26aeb5 100644 --- a/src/applications/diffusion/controller/DiffusionLintController.php +++ b/src/applications/diffusion/controller/DiffusionLintController.php @@ -1,354 +1,350 @@ getRequest(); $user = $this->getRequest()->getUser(); $drequest = $this->diffusionRequest; if ($request->getStr('lint') !== null) { $controller = new DiffusionLintDetailsController($request); $controller->setDiffusionRequest($drequest); $controller->setCurrentApplication($this->getCurrentApplication()); return $this->delegateToController($controller); } $owners = array(); if (!$drequest) { if (!$request->getArr('owner')) { - if ($user->isLoggedIn()) { - $owners[$user->getPHID()] = $user->getFullName(); - } + $owners = array($user->getPHID()); } else { - $phids = $request->getArr('owner'); - $phid = reset($phids); - $handles = $this->loadViewerHandles(array($phid)); - $owners[$phid] = $handles[$phid]->getFullName(); + $owners = array(head($request->getArr('owner'))); } + $owner_handles = $this->loadViewerHandles($owners); } - $codes = $this->loadLintCodes(array_keys($owners)); + $codes = $this->loadLintCodes($owners); if ($codes && !$drequest) { // TODO: Build some real Query classes for this stuff. $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere( 'id IN (%Ld)', array_unique(ipull($codes, 'branchID'))); $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->withIDs(mpull($branches, 'getRepositoryID')) ->execute(); $drequests = array(); foreach ($branches as $id => $branch) { if (empty($repositories[$branch->getRepositoryID()])) { continue; } $drequests[$id] = DiffusionRequest::newFromDictionary(array( 'user' => $user, 'repository' => $repositories[$branch->getRepositoryID()], 'branch' => $branch->getName(), )); } } $rows = array(); $total = 0; foreach ($codes as $code) { if (!$this->diffusionRequest) { $drequest = idx($drequests, $code['branchID']); } if (!$drequest) { continue; } $total += $code['n']; $rows[] = array( hsprintf( '%s', $drequest->generateURI(array( 'action' => 'lint', 'lint' => $code['code'], )), $code['n']), hsprintf( '%s', $drequest->generateURI(array( 'action' => 'browse', 'lint' => $code['code'], )), $code['files']), hsprintf( '%s', $drequest->generateURI(array('action' => 'lint')), $drequest->getCallsign()), ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']), $code['code'], $code['maxName'], $code['maxDescription'], ); } $table = id(new AphrontTableView($rows)) ->setHeaders(array( pht('Problems'), pht('Files'), pht('Repository'), pht('Severity'), pht('Code'), pht('Name'), pht('Example'), )) ->setColumnVisibility(array(true, true, !$this->diffusionRequest)) ->setColumnClasses(array('n', 'n', '', '', 'pri', '', '')); $content = array(); $link = null; if (!$this->diffusionRequest) { $form = id(new AphrontFormView()) ->setUser($user) ->setMethod('GET') ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setLimit(1) ->setName('owner') ->setLabel(pht('Owner')) - ->setValue($owners)) + ->setValue($owner_handles)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Filter')); $content[] = id(new AphrontListFilterView())->appendChild($form); } $content[] = id(new AphrontPanelView()) ->setNoBackground(true) ->setCaption($link) ->appendChild($table); $title = array('Lint'); $crumbs = $this->buildCrumbs( array( 'branch' => true, 'path' => true, 'view' => 'lint', )); if ($this->diffusionRequest) { $title[] = $drequest->getCallsign(); } else { $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('All Lint'))); } if ($this->diffusionRequest) { $branch = $drequest->loadBranch(); $header = id(new PHUIHeaderView()) ->setHeader($this->renderPathLinks($drequest, 'lint')) ->setUser($user) ->setPolicyObject($drequest->getRepository()); $actions = $this->buildActionView($drequest); $properties = $this->buildPropertyView( $drequest, $branch, $total); $object_box = id(new PHUIObjectBoxView()) ->setHeader($header) ->setActionList($actions) ->setPropertyList($properties); } else { $object_box = null; } return $this->buildApplicationPage( array( $crumbs, $object_box, $content, ), array( 'title' => $title, )); } private function loadLintCodes(array $owner_phids) { $drequest = $this->diffusionRequest; $conn = id(new PhabricatorRepository())->establishConnection('r'); $where = array('1 = 1'); if ($drequest) { $branch = $drequest->loadBranch(); if (!$branch) { return array(); } $where[] = qsprintf($conn, 'branchID = %d', $branch->getID()); if ($drequest->getPath() != '') { $path = '/'.$drequest->getPath(); $is_dir = (substr($path, -1) == '/'); $where[] = ($is_dir ? qsprintf($conn, 'path LIKE %>', $path) : qsprintf($conn, 'path = %s', $path)); } } if ($owner_phids) { $or = array(); $or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids); $paths = array(); $packages = id(new PhabricatorOwnersOwner()) ->loadAllWhere('userPHID IN (%Ls)', $owner_phids); if ($packages) { $paths = id(new PhabricatorOwnersPath())->loadAllWhere( 'packageID IN (%Ld)', mpull($packages, 'getPackageID')); } if ($paths) { $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs(mpull($paths, 'getRepositoryPHID')) ->execute(); $repositories = mpull($repositories, 'getID', 'getPHID'); $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere( 'repositoryID IN (%Ld)', $repositories); $branches = mgroup($branches, 'getRepositoryID'); } foreach ($paths as $path) { $branch = idx( $branches, idx( $repositories, $path->getRepositoryPHID())); if ($branch) { $condition = qsprintf( $conn, '(branchID IN (%Ld) AND path LIKE %>)', array_keys($branch), $path->getPath()); if ($path->getExcluded()) { $where[] = 'NOT '.$condition; } else { $or[] = $condition; } } } $where[] = '('.implode(' OR ', $or).')'; } return queryfx_all( $conn, 'SELECT branchID, code, MAX(severity) AS maxSeverity, MAX(name) AS maxName, MAX(description) AS maxDescription, COUNT(DISTINCT path) AS files, COUNT(*) AS n FROM %T WHERE %Q GROUP BY branchID, code ORDER BY n DESC', PhabricatorRepository::TABLE_LINTMESSAGE, implode(' AND ', $where)); } protected function buildActionView(DiffusionRequest $drequest) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $list_uri = $drequest->generateURI( array( 'action' => 'lint', 'lint' => '', )); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('View As List')) ->setHref($list_uri) ->setIcon('transcript')); $history_uri = $drequest->generateURI( array( 'action' => 'history', )); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('View History')) ->setHref($history_uri) ->setIcon('history')); $browse_uri = $drequest->generateURI( array( 'action' => 'browse', )); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Browse Content')) ->setHref($browse_uri) ->setIcon('file')); return $view; } protected function buildPropertyView( DiffusionRequest $drequest, PhabricatorRepositoryBranch $branch, $total) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorPropertyListView()) ->setUser($viewer); $callsign = $drequest->getRepository()->getCallsign(); $lint_commit = $branch->getLintCommit(); $view->addProperty( pht('Lint Commit'), phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'commit', 'commit' => $lint_commit, )), ), $drequest->getRepository()->formatCommitName($lint_commit))); $view->addProperty( pht('Total Messages'), pht('%s', new PhutilNumber($total))); return $view; } } diff --git a/src/applications/feed/query/PhabricatorFeedSearchEngine.php b/src/applications/feed/query/PhabricatorFeedSearchEngine.php index dfd626eb7c..c4048d7f53 100644 --- a/src/applications/feed/query/PhabricatorFeedSearchEngine.php +++ b/src/applications/feed/query/PhabricatorFeedSearchEngine.php @@ -1,128 +1,127 @@ setParameter( 'userPHIDs', $this->readUsersFromRequest($request, 'users')); $saved->setParameter( 'projectPHIDs', array_values($request->getArr('projectPHIDs'))); $saved->setParameter( 'viewerProjects', $request->getBool('viewerProjects')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorFeedQuery()); $phids = array(); $user_phids = $saved->getParameter('userPHIDs'); if ($user_phids) { $phids[] = $user_phids; } $proj_phids = $saved->getParameter('projectPHIDs'); if ($proj_phids) { $phids[] = $proj_phids; } $viewer_projects = $saved->getParameter('viewerProjects'); if ($viewer_projects) { $viewer = $this->requireViewer(); $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withMemberPHIDs(array($viewer->getPHID())) ->execute(); $phids[] = mpull($projects, 'getPHID'); } $phids = array_mergev($phids); if ($phids) { $query->setFilterPHIDs($phids); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $user_phids = $saved_query->getParameter('userPHIDs', array()); $proj_phids = $saved_query->getParameter('projectPHIDs', array()); $phids = array_merge($user_phids, $proj_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $tokens = mpull($handles, 'getFullName', 'getPHID'); - $user_tokens = array_select_keys($tokens, $user_phids); - $proj_tokens = array_select_keys($tokens, $proj_phids); + $user_handles = array_select_keys($handles, $user_phids); + $proj_handles = array_select_keys($handles, $proj_phids); $viewer_projects = $saved_query->getParameter('viewerProjects'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('users') ->setLabel(pht('Include Users')) - ->setValue($user_tokens)) + ->setValue($user_handles)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setName('projectPHIDs') ->setLabel(pht('Include Projects')) - ->setValue($proj_tokens)) + ->setValue($proj_handles)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'viewerProjects', 1, pht('Include stories about projects I am a member of.'), $viewer_projects)); } protected function getURI($path) { return '/feed/'.$path; } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Stories'), ); if ($this->requireViewer()->isLoggedIn()) { $names['projects'] = pht('Projects'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'projects': return $query->setParameter('viewerProjects', true); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/files/query/PhabricatorFileSearchEngine.php b/src/applications/files/query/PhabricatorFileSearchEngine.php index 2281fa8fce..c16d816685 100644 --- a/src/applications/files/query/PhabricatorFileSearchEngine.php +++ b/src/applications/files/query/PhabricatorFileSearchEngine.php @@ -1,117 +1,116 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter('explicit', $request->getBool('explicit')); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorFileQuery()) ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())); if ($saved->getParameter('explicit')) { $query->showOnlyExplicitUploads(true); } $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $explicit = $saved_query->getParameter('explicit'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'explicit', 1, pht('Show only manually uploaded files.'), $explicit)); $this->buildDateRange( $form, $saved_query, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); } protected function getURI($path) { return '/file/'.$path; } public function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } $names += array( 'all' => pht('All'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': $author_phid = array($this->requireViewer()->getPHID()); return $query ->setParameter('authorPHIDs', $author_phid) ->setParameter('explicit', true); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/herald/query/HeraldRuleSearchEngine.php b/src/applications/herald/query/HeraldRuleSearchEngine.php index ccafc360d5..2e6f658bc3 100644 --- a/src/applications/herald/query/HeraldRuleSearchEngine.php +++ b/src/applications/herald/query/HeraldRuleSearchEngine.php @@ -1,156 +1,155 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter('contentType', $request->getStr('contentType')); $saved->setParameter('ruleType', $request->getStr('ruleType')); $saved->setParameter( 'disabled', $this->readBoolFromRequest($request, 'disabled')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new HeraldRuleQuery()); $author_phids = $saved->getParameter('authorPHIDs'); if ($author_phids) { $query->withAuthorPHIDs($author_phids); } $content_type = $saved->getParameter('contentType'); $content_type = idx($this->getContentTypeValues(), $content_type); if ($content_type) { $query->withContentTypes(array($content_type)); } $rule_type = $saved->getParameter('ruleType'); $rule_type = idx($this->getRuleTypeValues(), $rule_type); if ($rule_type) { $query->withRuleTypes(array($rule_type)); } $disabled = $saved->getParameter('disabled'); if ($disabled !== null) { $query->withDisabled($disabled); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $content_type = $saved_query->getParameter('contentType'); $rule_type = $saved_query->getParameter('ruleType'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('contentType') ->setLabel(pht('Content Type')) ->setValue($content_type) ->setOptions($this->getContentTypeOptions())) ->appendChild( id(new AphrontFormSelectControl()) ->setName('ruleType') ->setLabel(pht('Rule Type')) ->setValue($rule_type) ->setOptions($this->getRuleTypeOptions())) ->appendChild( id(new AphrontFormSelectControl()) ->setName('disabled') ->setLabel(pht('Rule Status')) ->setValue($this->getBoolFromQuery($saved_query, 'disabled')) ->setOptions( array( '' => pht('Show Enabled and Disabled Rules'), 'false' => pht('Show Only Enabled Rules'), 'true' => pht('Show Only Disabled Rules'), ))); } protected function getURI($path) { return '/herald/'.$path; } public function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } $names['active'] = pht('Active'); $names['all'] = pht('All'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; case 'active': return $query->setParameter('disabled', false); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer_phid)) ->setParameter('disabled', false); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getContentTypeOptions() { return array( '' => pht('(All Content Types)'), ) + HeraldAdapter::getEnabledAdapterMap($this->requireViewer()); } private function getContentTypeValues() { return array_fuse( array_keys( HeraldAdapter::getEnabledAdapterMap($this->requireViewer()))); } private function getRuleTypeOptions() { return array( '' => pht('(All Rule Types)'), ) + HeraldRuleTypeConfig::getRuleTypeMap(); } private function getRuleTypeValues() { return array_fuse(array_keys(HeraldRuleTypeConfig::getRuleTypeMap())); } } diff --git a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php index c510ec7765..5d11671ab9 100644 --- a/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php +++ b/src/applications/legalpad/query/LegalpadDocumentSearchEngine.php @@ -1,107 +1,106 @@ setParameter( 'creatorPHIDs', $this->readUsersFromRequest($request, 'creators')); $saved->setParameter( 'contributorPHIDs', $this->readUsersFromRequest($request, 'contributors')); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new LegalpadDocumentQuery()) ->withCreatorPHIDs($saved->getParameter('creatorPHIDs', array())) ->withContributorPHIDs($saved->getParameter('contributorPHIDs', array())); $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $creator_phids = $saved_query->getParameter('creatorPHIDs', array()); $contributor_phids = $saved_query->getParameter( 'contributorPHIDs', array()); $phids = array_merge($creator_phids, $contributor_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $tokens = mpull($handles, 'getFullName', 'getPHID'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('creators') ->setLabel(pht('Creators')) - ->setValue(array_select_keys($tokens, $creator_phids))) + ->setValue(array_select_keys($handles, $creator_phids))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('contributors') ->setLabel(pht('Contributors')) - ->setValue(array_select_keys($tokens, $contributor_phids))); + ->setValue(array_select_keys($handles, $contributor_phids))); $this->buildDateRange( $form, $saved_query, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); } protected function getURI($path) { return '/legalpad/'.$path; } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Documents'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/macro/query/PhabricatorMacroSearchEngine.php b/src/applications/macro/query/PhabricatorMacroSearchEngine.php index c980979c29..933f1fc2c7 100644 --- a/src/applications/macro/query/PhabricatorMacroSearchEngine.php +++ b/src/applications/macro/query/PhabricatorMacroSearchEngine.php @@ -1,157 +1,156 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter('status', $request->getStr('status')); $saved->setParameter('names', $request->getStrList('names')); $saved->setParameter('nameLike', $request->getStr('nameLike')); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); $saved->setParameter('flagColor', $request->getStr('flagColor', '-1')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorMacroQuery()) ->withIDs($saved->getParameter('ids', array())) ->withPHIDs($saved->getParameter('phids', array())) ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())); $status = $saved->getParameter('status'); $options = PhabricatorMacroQuery::getStatusOptions(); if (empty($options[$status])) { $status = head_key($options); } $query->withStatus($status); $names = $saved->getParameter('names', array()); if ($names) { $query->withNames($names); } $like = $saved->getParameter('nameLike'); if (strlen($like)) { $query->withNameLike($like); } $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } $color = $saved->getParameter('flagColor'); if (strlen($color)) { $query->withFlagColor($color); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $status = $saved_query->getParameter('status'); $names = implode(', ', $saved_query->getParameter('names', array())); $like = $saved_query->getParameter('nameLike'); $color = $saved_query->getParameter('flagColor', "-1"); $form ->appendChild( id(new AphrontFormSelectControl()) ->setName('status') ->setLabel(pht('Status')) ->setOptions(PhabricatorMacroQuery::getStatusOptions()) ->setValue($status)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormTextControl()) ->setName('nameLike') ->setLabel(pht('Name Contains')) ->setValue($like)) ->appendChild( id(new AphrontFormTextControl()) ->setName('names') ->setLabel(pht('Exact Names')) ->setValue($names)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('flagColor') ->setLabel(pht('Marked with Flag')) ->setOptions(PhabricatorMacroQuery::getFlagColorsOptions()) ->setValue($color)); $this->buildDateRange( $form, $saved_query, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); } protected function getURI($path) { return '/macro/'.$path; } public function getBuiltinQueryNames() { $names = array( 'active' => pht('Active'), 'all' => pht('All'), ); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'active': return $query; case 'all': return $query->setParameter( 'status', PhabricatorMacroQuery::STATUS_ANY); case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/maniphest/controller/ManiphestReportController.php b/src/applications/maniphest/controller/ManiphestReportController.php index a86173c6a3..5b0979fd7f 100644 --- a/src/applications/maniphest/controller/ManiphestReportController.php +++ b/src/applications/maniphest/controller/ManiphestReportController.php @@ -1,756 +1,752 @@ view = idx($data, 'view'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($request->isFormPost()) { $uri = $request->getRequestURI(); $project = head($request->getArr('set_project')); $project = nonempty($project, null); $uri = $uri->alter('project', $project); $window = $request->getStr('set_window'); $uri = $uri->alter('window', $window); return id(new AphrontRedirectResponse())->setURI($uri); } $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI('/maniphest/report/')); $nav->addLabel(pht('Open Tasks')); $nav->addFilter('user', pht('By User')); $nav->addFilter('project', pht('By Project')); $nav->addLabel(pht('Burnup')); $nav->addFilter('burn', pht('Burnup Rate')); $this->view = $nav->selectFilter($this->view, 'user'); require_celerity_resource('maniphest-report-css'); switch ($this->view) { case 'burn': $core = $this->renderBurn(); break; case 'user': case 'project': $core = $this->renderOpenTasks(); break; default: return new Aphront404Response(); } $nav->appendChild($core); $nav->setCrumbs( $this->buildApplicationCrumbs() ->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Reports')))); return $this->buildApplicationPage( $nav, array( 'title' => pht('Maniphest Reports'), )); } public function renderBurn() { $request = $this->getRequest(); $user = $request->getUser(); $handle = null; $project_phid = $request->getStr('project'); if ($project_phid) { $phids = array($project_phid); $handles = $this->loadViewerHandles($phids); $handle = $handles[$project_phid]; } $table = new ManiphestTransaction(); $conn = $table->establishConnection('r'); $joins = ''; if ($project_phid) { $joins = qsprintf( $conn, 'JOIN %T t ON x.objectPHID = t.phid JOIN %T p ON p.taskPHID = t.phid AND p.projectPHID = %s', id(new ManiphestTask())->getTableName(), id(new ManiphestTaskProject())->getTableName(), $project_phid); } $data = queryfx_all( $conn, 'SELECT x.oldValue, x.newValue, x.dateCreated FROM %T x %Q WHERE transactionType = %s ORDER BY x.dateCreated ASC', $table->getTableName(), $joins, ManiphestTransaction::TYPE_STATUS); $stats = array(); $day_buckets = array(); $open_tasks = array(); foreach ($data as $key => $row) { // NOTE: Hack to avoid json_decode(). $oldv = trim($row['oldValue'], '"'); $newv = trim($row['newValue'], '"'); $old_is_open = ($oldv === (string)ManiphestTaskStatus::STATUS_OPEN); $new_is_open = ($newv === (string)ManiphestTaskStatus::STATUS_OPEN); $is_open = ($new_is_open && !$old_is_open); $is_close = ($old_is_open && !$new_is_open); $data[$key]['_is_open'] = $is_open; $data[$key]['_is_close'] = $is_close; if (!$is_open && !$is_close) { // This is either some kind of bogus event, or a resolution change // (e.g., resolved -> invalid). Just skip it. continue; } $day_bucket = phabricator_format_local_time( $row['dateCreated'], $user, 'Yz'); $day_buckets[$day_bucket] = $row['dateCreated']; if (empty($stats[$day_bucket])) { $stats[$day_bucket] = array( 'open' => 0, 'close' => 0, ); } $stats[$day_bucket][$is_close ? 'close' : 'open']++; } $template = array( 'open' => 0, 'close' => 0, ); $rows = array(); $rowc = array(); $last_month = null; $last_month_epoch = null; $last_week = null; $last_week_epoch = null; $week = null; $month = null; $last = last_key($stats) - 1; $period = $template; foreach ($stats as $bucket => $info) { $epoch = $day_buckets[$bucket]; $week_bucket = phabricator_format_local_time( $epoch, $user, 'YW'); if ($week_bucket != $last_week) { if ($week) { $rows[] = $this->formatBurnRow( 'Week of '.phabricator_date($last_week_epoch, $user), $week); $rowc[] = 'week'; } $week = $template; $last_week = $week_bucket; $last_week_epoch = $epoch; } $month_bucket = phabricator_format_local_time( $epoch, $user, 'Ym'); if ($month_bucket != $last_month) { if ($month) { $rows[] = $this->formatBurnRow( phabricator_format_local_time($last_month_epoch, $user, 'F, Y'), $month); $rowc[] = 'month'; } $month = $template; $last_month = $month_bucket; $last_month_epoch = $epoch; } $rows[] = $this->formatBurnRow(phabricator_date($epoch, $user), $info); $rowc[] = null; $week['open'] += $info['open']; $week['close'] += $info['close']; $month['open'] += $info['open']; $month['close'] += $info['close']; $period['open'] += $info['open']; $period['close'] += $info['close']; } if ($week) { $rows[] = $this->formatBurnRow( pht('Week To Date'), $week); $rowc[] = 'week'; } if ($month) { $rows[] = $this->formatBurnRow( pht('Month To Date'), $month); $rowc[] = 'month'; } $rows[] = $this->formatBurnRow( pht('All Time'), $period); $rowc[] = 'aggregate'; $rows = array_reverse($rows); $rowc = array_reverse($rowc); $table = new AphrontTableView($rows); $table->setRowClasses($rowc); $table->setHeaders( array( pht('Period'), pht('Opened'), pht('Closed'), pht('Change'), )); $table->setColumnClasses( array( 'right wide', 'n', 'n', 'n', )); if ($handle) { $inst = pht( "NOTE: This table reflects tasks currently in ". "the project. If a task was opened in the past but added to ". "the project recently, it is counted on the day it was ". "opened, not the day it was categorized. If a task was part ". "of this project in the past but no longer is, it is not ". "counted at all."); $header = pht("Task Burn Rate for Project %s", $handle->renderLink()); $caption = hsprintf("

%s

", $inst); } else { $header = pht("Task Burn Rate for All Tasks"); $caption = null; } $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->setCaption($caption); $panel->appendChild($table); $tokens = array(); if ($handle) { - $tokens = array( - $handle->getPHID() => $handle->getFullName(), - ); + $tokens = array($handle); } $filter = $this->renderReportFilters($tokens, $has_window = false); $id = celerity_generate_unique_node_id(); $chart = phutil_tag( 'div', array( 'id' => $id, 'style' => 'border: 1px solid #6f6f6f; '. 'margin: 1em 2em; '. 'height: 400px; ', ), ''); list($burn_x, $burn_y) = $this->buildSeries($data); require_celerity_resource('raphael-core'); require_celerity_resource('raphael-g'); require_celerity_resource('raphael-g-line'); Javelin::initBehavior('line-chart', array( 'hardpoint' => $id, 'x' => array( $burn_x, ), 'y' => array( $burn_y, ), 'xformat' => 'epoch', )); return array($filter, $chart, $panel); } private function renderReportFilters(array $tokens, $has_window) { $request = $this->getRequest(); $user = $request->getUser(); $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/searchproject/') ->setLabel(pht('Project')) ->setLimit(1) ->setName('set_project') ->setValue($tokens)); if ($has_window) { list($window_str, $ignored, $window_error) = $this->getWindow(); $form ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Recently Means')) ->setName('set_window') ->setCaption( pht('Configure the cutoff for the "Recently Closed" column.')) ->setValue($window_str) ->setError($window_error)); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Filter By Project'))); $filter = new AphrontListFilterView(); $filter->appendChild($form); return $filter; } private function buildSeries(array $data) { $out = array(); $counter = 0; foreach ($data as $row) { $t = (int)$row['dateCreated']; if ($row['_is_close']) { --$counter; $out[$t] = $counter; } else if ($row['_is_open']) { ++$counter; $out[$t] = $counter; } } return array(array_keys($out), array_values($out)); } private function formatBurnRow($label, $info) { $delta = $info['open'] - $info['close']; $fmt = number_format($delta); if ($delta > 0) { $fmt = '+'.$fmt; $fmt = hsprintf('%s', $fmt); } else { $fmt = hsprintf('%s', $fmt); } return array( $label, number_format($info['open']), number_format($info['close']), $fmt); } public function renderOpenTasks() { $request = $this->getRequest(); $user = $request->getUser(); $query = id(new ManiphestTaskQuery()) ->setViewer($user) ->withStatus(ManiphestTaskQuery::STATUS_OPEN); $project_phid = $request->getStr('project'); $project_handle = null; if ($project_phid) { $phids = array($project_phid); $handles = $this->loadViewerHandles($phids); $project_handle = $handles[$project_phid]; $query->withAnyProjects($phids); } $tasks = $query->execute(); $recently_closed = $this->loadRecentlyClosedTasks(); $date = phabricator_date(time(), $user); switch ($this->view) { case 'user': $result = mgroup($tasks, 'getOwnerPHID'); $leftover = idx($result, '', array()); unset($result['']); $result_closed = mgroup($recently_closed, 'getOwnerPHID'); $leftover_closed = idx($result_closed, '', array()); unset($result_closed['']); $base_link = '/maniphest/?assigned='; $leftover_name = phutil_tag('em', array(), pht('(Up For Grabs)')); $col_header = pht('User'); $header = pht('Open Tasks by User and Priority (%s)', $date); break; case 'project': $result = array(); $leftover = array(); foreach ($tasks as $task) { $phids = $task->getProjectPHIDs(); if ($phids) { foreach ($phids as $project_phid) { $result[$project_phid][] = $task; } } else { $leftover[] = $task; } } $result_closed = array(); $leftover_closed = array(); foreach ($recently_closed as $task) { $phids = $task->getProjectPHIDs(); if ($phids) { foreach ($phids as $project_phid) { $result_closed[$project_phid][] = $task; } } else { $leftover_closed[] = $task; } } $base_link = '/maniphest/?allProjects[]='; $leftover_name = phutil_tag('em', array(), pht('(No Project)')); $col_header = pht('Project'); $header = pht('Open Tasks by Project and Priority (%s)', $date); break; } $phids = array_keys($result); $handles = $this->loadViewerHandles($phids); $handles = msort($handles, 'getName'); $order = $request->getStr('order', 'name'); list($order, $reverse) = AphrontTableView::parseSort($order); require_celerity_resource('aphront-tooltip-css'); Javelin::initBehavior('phabricator-tooltips', array()); $rows = array(); $pri_total = array(); foreach (array_merge($handles, array(null)) as $handle) { if ($handle) { if (($project_handle) && ($project_handle->getPHID() == $handle->getPHID())) { // If filtering by, e.g., "bugs", don't show a "bugs" group. continue; } $tasks = idx($result, $handle->getPHID(), array()); $name = phutil_tag( 'a', array( 'href' => $base_link.$handle->getPHID(), ), $handle->getName()); $closed = idx($result_closed, $handle->getPHID(), array()); } else { $tasks = $leftover; $name = $leftover_name; $closed = $leftover_closed; } $taskv = $tasks; $tasks = mgroup($tasks, 'getPriority'); $row = array(); $row[] = $name; $total = 0; foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $label) { $n = count(idx($tasks, $pri, array())); if ($n == 0) { $row[] = '-'; } else { $row[] = number_format($n); } $total += $n; } $row[] = number_format($total); list($link, $oldest_all) = $this->renderOldest($taskv); $row[] = $link; $normal_or_better = array(); foreach ($taskv as $id => $task) { // TODO: This is sort of a hard-code for the default "normal" status. // When reports are more powerful, this should be made more general. if ($task->getPriority() < 50) { continue; } $normal_or_better[$id] = $task; } list($link, $oldest_pri) = $this->renderOldest($normal_or_better); $row[] = $link; if ($closed) { $task_ids = implode(',', mpull($closed, 'getID')); $row[] = phutil_tag( 'a', array( 'href' => '/maniphest/?ids='.$task_ids, 'target' => '_blank', ), number_format(count($closed))); } else { $row[] = '-'; } switch ($order) { case 'total': $row['sort'] = $total; break; case 'oldest-all': $row['sort'] = $oldest_all; break; case 'oldest-pri': $row['sort'] = $oldest_pri; break; case 'closed': $row['sort'] = count($closed); break; case 'name': default: $row['sort'] = $handle ? $handle->getName() : '~'; break; } $rows[] = $row; } $rows = isort($rows, 'sort'); foreach ($rows as $k => $row) { unset($rows[$k]['sort']); } if ($reverse) { $rows = array_reverse($rows); } $cname = array($col_header); $cclass = array('pri right wide'); $pri_map = ManiphestTaskPriority::getShortNameMap(); foreach ($pri_map as $pri => $label) { $cname[] = $label; $cclass[] = 'n'; } $cname[] = 'Total'; $cclass[] = 'n'; $cname[] = javelin_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Oldest open task.'), 'size' => 200, ), ), pht('Oldest (All)')); $cclass[] = 'n'; $cname[] = javelin_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Oldest open task, excluding those with Low or '. 'Wishlist priority.'), 'size' => 200, ), ), pht('Oldest (Pri)')); $cclass[] = 'n'; list($ignored, $window_epoch) = $this->getWindow(); $edate = phabricator_datetime($window_epoch, $user); $cname[] = javelin_tag( 'span', array( 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => pht('Closed after %s', $edate), 'size' => 260 ), ), pht('Recently Closed')); $cclass[] = 'n'; $table = new AphrontTableView($rows); $table->setHeaders($cname); $table->setColumnClasses($cclass); $table->makeSortable( $request->getRequestURI(), 'order', $order, $reverse, array( 'name', null, null, null, null, null, null, 'total', 'oldest-all', 'oldest-pri', 'closed', )); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($table); $tokens = array(); if ($project_handle) { - $tokens = array( - $project_handle->getPHID() => $project_handle->getFullName(), - ); + $tokens = array($project_handle); } $filter = $this->renderReportFilters($tokens, $has_window = true); return array($filter, $panel); } /** * Load all the tasks that have been recently closed. */ private function loadRecentlyClosedTasks() { list($ignored, $window_epoch) = $this->getWindow(); $table = new ManiphestTask(); $xtable = new ManiphestTransaction(); $conn_r = $table->establishConnection('r'); $tasks = queryfx_all( $conn_r, 'SELECT t.* FROM %T t JOIN %T x ON x.objectPHID = t.phid WHERE t.status != 0 AND x.oldValue IN (null, %s, %s) AND x.newValue NOT IN (%s, %s) AND t.dateModified >= %d AND x.dateCreated >= %d', $table->getTableName(), $xtable->getTableName(), // TODO: Gross. This table is not meant to be queried like this. Build // real stats tables. json_encode((int)ManiphestTaskStatus::STATUS_OPEN), json_encode((string)ManiphestTaskStatus::STATUS_OPEN), json_encode((int)ManiphestTaskStatus::STATUS_OPEN), json_encode((string)ManiphestTaskStatus::STATUS_OPEN), $window_epoch, $window_epoch); return id(new ManiphestTask())->loadAllFromArray($tasks); } /** * Parse the "Recently Means" filter into: * * - A string representation, like "12 AM 7 days ago" (default); * - a locale-aware epoch representation; and * - a possible error. */ private function getWindow() { $request = $this->getRequest(); $user = $request->getUser(); $window_str = $this->getRequest()->getStr('window', '12 AM 7 days ago'); $error = null; $window_epoch = null; // Do locale-aware parsing so that the user's timezone is assumed for // time windows like "3 PM", rather than assuming the server timezone. $window_epoch = PhabricatorTime::parseLocalTime($window_str, $user); if (!$window_epoch) { $error = 'Invalid'; $window_epoch = time() - (60 * 60 * 24 * 7); } // If the time ends up in the future, convert it to the corresponding time // and equal distance in the past. This is so users can type "6 days" (which // means "6 days from now") and get the behavior of "6 days ago", rather // than no results (because the window epoch is in the future). This might // be a little confusing because it casues "tomorrow" to mean "yesterday" // and "2022" (or whatever) to mean "ten years ago", but these inputs are // nonsense anyway. if ($window_epoch > time()) { $window_epoch = time() - ($window_epoch - time()); } return array($window_str, $window_epoch, $error); } private function renderOldest(array $tasks) { assert_instances_of($tasks, 'ManiphestTask'); $oldest = null; foreach ($tasks as $id => $task) { if (($oldest === null) || ($task->getDateCreated() < $tasks[$oldest]->getDateCreated())) { $oldest = $id; } } if ($oldest === null) { return array('-', 0); } $oldest = $tasks[$oldest]; $raw_age = (time() - $oldest->getDateCreated()); $age = number_format($raw_age / (24 * 60 * 60)).' d'; $link = javelin_tag( 'a', array( 'href' => '/T'.$oldest->getID(), 'sigil' => 'has-tooltip', 'meta' => array( 'tip' => 'T'.$oldest->getID().': '.$oldest->getTitle(), ), 'target' => '_blank', ), $age); return array($link, $raw_age); } } diff --git a/src/applications/maniphest/controller/ManiphestTaskEditController.php b/src/applications/maniphest/controller/ManiphestTaskEditController.php index 95d904ff77..73a2445d6b 100644 --- a/src/applications/maniphest/controller/ManiphestTaskEditController.php +++ b/src/applications/maniphest/controller/ManiphestTaskEditController.php @@ -1,605 +1,601 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $files = array(); $parent_task = null; $template_id = null; if ($this->id) { $task = id(new ManiphestTaskQuery()) ->setViewer($user) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if (!$task) { return new Aphront404Response(); } } else { $task = new ManiphestTask(); $task->setPriority(ManiphestTaskPriority::getDefaultPriority()); $task->setAuthorPHID($user->getPHID()); // These allow task creation with defaults. if (!$request->isFormPost()) { $task->setTitle($request->getStr('title')); $default_projects = $request->getStr('projects'); if ($default_projects) { $task->setProjectPHIDs(explode(';', $default_projects)); } $task->setDescription($request->getStr('description')); $assign = $request->getStr('assign'); if (strlen($assign)) { $assign_user = id(new PhabricatorUser())->loadOneWhere( 'username = %s', $assign); if ($assign_user) { $task->setOwnerPHID($assign_user->getPHID()); } } } $file_phids = $request->getArr('files', array()); if (!$file_phids) { // Allow a single 'file' key instead, mostly since Mac OS X urlencodes // square brackets in URLs when passed to 'open', so you can't 'open' // a URL like '?files[]=xyz' and have PHP interpret it correctly. $phid = $request->getStr('file'); if ($phid) { $file_phids = array($phid); } } if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($user) ->withPHIDs($file_phids) ->execute(); } $template_id = $request->getInt('template'); // You can only have a parent task if you're creating a new task. $parent_id = $request->getInt('parent'); if ($parent_id) { $parent_task = id(new ManiphestTaskQuery()) ->setViewer($user) ->withIDs(array($parent_id)) ->executeOne(); if (!$template_id) { $template_id = $parent_id; } } } $errors = array(); $e_title = true; $field_list = PhabricatorCustomField::getObjectFields( $task, PhabricatorCustomField::ROLE_EDIT); foreach ($field_list->getFields() as $field) { $field->setObject($task); $field->setViewer($user); } $field_list->readFieldsFromStorage($task); $aux_fields = $field_list->getFields(); if ($request->isFormPost()) { $changes = array(); $new_title = $request->getStr('title'); $new_desc = $request->getStr('description'); $new_status = $request->getStr('status'); if (!$task->getID()) { $workflow = 'create'; } else { $workflow = ''; } $changes[ManiphestTransaction::TYPE_TITLE] = $new_title; $changes[ManiphestTransaction::TYPE_DESCRIPTION] = $new_desc; $changes[ManiphestTransaction::TYPE_STATUS] = $new_status; $owner_tokenizer = $request->getArr('assigned_to'); $owner_phid = reset($owner_tokenizer); if (!strlen($new_title)) { $e_title = pht('Required'); $errors[] = pht('Title is required.'); } $old_values = array(); foreach ($aux_fields as $aux_arr_key => $aux_field) { // TODO: This should be buildFieldTransactionsFromRequest() once we // switch to ApplicationTransactions properly. $aux_old_value = $aux_field->getOldValueForApplicationTransactions(); $aux_field->readValueFromRequest($request); $aux_new_value = $aux_field->getNewValueForApplicationTransactions(); // TODO: We're faking a call to the ApplicaitonTransaction validation // logic here. We need valid objects to pass, but they aren't used // in a meaningful way. For now, build User objects. Once the Maniphest // objects exist, this will switch over automatically. This is a big // hack but shouldn't be long for this world. $placeholder_editor = new PhabricatorUserProfileEditor(); $field_errors = $aux_field->validateApplicationTransactions( $placeholder_editor, PhabricatorTransactions::TYPE_CUSTOMFIELD, array( id(new ManiphestTransaction()) ->setOldValue($aux_old_value) ->setNewValue($aux_new_value), )); foreach ($field_errors as $error) { $errors[] = $error->getMessage(); } $old_values[$aux_field->getFieldKey()] = $aux_old_value; } if ($errors) { $task->setTitle($new_title); $task->setDescription($new_desc); $task->setPriority($request->getInt('priority')); $task->setOwnerPHID($owner_phid); $task->setCCPHIDs($request->getArr('cc')); $task->setProjectPHIDs($request->getArr('projects')); } else { $changes[ManiphestTransaction::TYPE_PRIORITY] = $request->getInt('priority'); $changes[ManiphestTransaction::TYPE_OWNER] = $owner_phid; $changes[ManiphestTransaction::TYPE_CCS] = $request->getArr('cc'); $changes[ManiphestTransaction::TYPE_PROJECTS] = $request->getArr('projects'); $changes[PhabricatorTransactions::TYPE_VIEW_POLICY] = $request->getStr('viewPolicy'); $changes[PhabricatorTransactions::TYPE_EDIT_POLICY] = $request->getStr('editPolicy'); if ($files) { $file_map = mpull($files, 'getPHID'); $file_map = array_fill_keys($file_map, array()); $changes[ManiphestTransaction::TYPE_ATTACH] = array( PhabricatorFilePHIDTypeFile::TYPECONST => $file_map, ); } $template = new ManiphestTransaction(); $transactions = array(); foreach ($changes as $type => $value) { $transaction = clone $template; $transaction->setTransactionType($type); $transaction->setNewValue($value); $transactions[] = $transaction; } if ($aux_fields) { foreach ($aux_fields as $aux_field) { $transaction = clone $template; $transaction->setTransactionType( PhabricatorTransactions::TYPE_CUSTOMFIELD); $aux_key = $aux_field->getFieldKey(); $transaction->setMetadataValue('customfield:key', $aux_key); $old = idx($old_values, $aux_key); $new = $aux_field->getNewValueForApplicationTransactions(); $transaction->setOldValue($old); $transaction->setNewValue($new); $transactions[] = $transaction; } } if ($transactions) { $is_new = !$task->getID(); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_WILLEDITTASK, array( 'task' => $task, 'new' => $is_new, 'transactions' => $transactions, )); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); $task = $event->getValue('task'); $transactions = $event->getValue('transactions'); $editor = id(new ManiphestTransactionEditorPro()) ->setActor($user) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->applyTransactions($task, $transactions); $event = new PhabricatorEvent( PhabricatorEventType::TYPE_MANIPHEST_DIDEDITTASK, array( 'task' => $task, 'new' => $is_new, 'transactions' => $transactions, )); $event->setUser($user); $event->setAphrontRequest($request); PhutilEventEngine::dispatchEvent($event); } if ($parent_task) { id(new PhabricatorEdgeEditor()) ->setActor($user) ->addEdge( $parent_task->getPHID(), PhabricatorEdgeConfig::TYPE_TASK_DEPENDS_ON_TASK, $task->getPHID()) ->save(); $workflow = $parent_task->getID(); } if ($request->isAjax()) { return id(new AphrontAjaxResponse())->setContent( array( 'tasks' => $this->renderSingleTask($task), )); } $redirect_uri = '/T'.$task->getID(); if ($workflow) { $redirect_uri .= '?workflow='.$workflow; } return id(new AphrontRedirectResponse()) ->setURI($redirect_uri); } } else { if (!$task->getID()) { $task->setCCPHIDs(array( $user->getPHID(), )); if ($template_id) { $template_task = id(new ManiphestTaskQuery()) ->setViewer($user) ->withIDs(array($template_id)) ->executeOne(); if ($template_task) { $task->setCCPHIDs($template_task->getCCPHIDs()); $task->setProjectPHIDs($template_task->getProjectPHIDs()); $task->setOwnerPHID($template_task->getOwnerPHID()); $task->setPriority($template_task->getPriority()); $template_fields = PhabricatorCustomField::getObjectFields( $template_task, PhabricatorCustomField::ROLE_EDIT); $fields = $template_fields->getFields(); foreach ($fields as $key => $field) { if (!$field->shouldCopyWhenCreatingSimilarTask()) { unset($fields[$key]); } if (empty($aux_fields[$key])) { unset($fields[$key]); } } if ($fields) { id(new PhabricatorCustomFieldList($fields)) ->readFieldsFromStorage($template_task); foreach ($fields as $key => $field) { $aux_fields[$key]->setValueFromStorage( $field->getValueForStorage()); } } } } } } $phids = array_merge( array($task->getOwnerPHID()), $task->getCCPHIDs(), $task->getProjectPHIDs()); if ($parent_task) { $phids[] = $parent_task->getPHID(); } $phids = array_filter($phids); $phids = array_unique($phids); $handles = $this->loadViewerHandles($phids); - $tvalues = mpull($handles, 'getFullName', 'getPHID'); - $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle(pht('Form Errors')); } $priority_map = ManiphestTaskPriority::getTaskPriorityMap(); if ($task->getOwnerPHID()) { - $assigned_value = array( - $task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(), - ); + $assigned_value = array($handles[$task->getOwnerPHID()]); } else { $assigned_value = array(); } if ($task->getCCPHIDs()) { - $cc_value = array_select_keys($tvalues, $task->getCCPHIDs()); + $cc_value = array_select_keys($handles, $task->getCCPHIDs()); } else { $cc_value = array(); } if ($task->getProjectPHIDs()) { - $projects_value = array_select_keys($tvalues, $task->getProjectPHIDs()); + $projects_value = array_select_keys($handles, $task->getProjectPHIDs()); } else { $projects_value = array(); } $cancel_id = nonempty($task->getID(), $template_id); if ($cancel_id) { $cancel_uri = '/T'.$cancel_id; } else { $cancel_uri = '/maniphest/'; } if ($task->getID()) { $button_name = pht('Save Task'); $header_name = pht('Edit Task'); } else if ($parent_task) { $cancel_uri = '/T'.$parent_task->getID(); $button_name = pht('Create Task'); $header_name = pht('Create New Subtask'); } else { $button_name = pht('Create Task'); $header_name = pht('Create New Task'); } require_celerity_resource('maniphest-task-edit-css'); $project_tokenizer_id = celerity_generate_unique_node_id(); if ($request->isAjax()) { $form = new PHUIFormLayoutView(); } else { $form = new AphrontFormView(); $form ->setUser($user) ->addHiddenInput('template', $template_id); } if ($parent_task) { $form ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Parent Task')) ->setValue($handles[$parent_task->getPHID()]->getFullName())) ->addHiddenInput('parent', $parent_task->getID()); } $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Title')) ->setName('title') ->setError($e_title) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($task->getTitle())); if ($task->getID()) { // Only show this in "edit" mode, not "create" mode, since creating a // non-open task is kind of silly and it would just clutter up the // "create" interface. $form ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($task->getStatus()) ->setOptions(ManiphestTaskStatus::getTaskStatusMap())); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->setObject($task) ->execute(); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Assigned To')) ->setName('assigned_to') ->setValue($assigned_value) ->setUser($user) ->setDatasource('/typeahead/common/users/') ->setLimit(1)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CC')) ->setName('cc') ->setValue($cc_value) ->setUser($user) ->setDatasource('/typeahead/common/mailable/')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Priority')) ->setName('priority') ->setOptions($priority_map) ->setValue($task->getPriority())) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($task) ->setPolicies($policies) ->setName('viewPolicy')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_EDIT) ->setPolicyObject($task) ->setPolicies($policies) ->setCaption( pht( 'NOTE: These policy controls still have some rough edges and '. 'are not yet fully functional.')) ->setName('editPolicy')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Projects')) ->setName('projects') ->setValue($projects_value) ->setID($project_tokenizer_id) ->setCaption( javelin_tag( 'a', array( 'href' => '/project/create/', 'mustcapture' => true, 'sigil' => 'project-create', ), pht('Create New Project'))) ->setDatasource('/typeahead/common/projects/')); foreach ($aux_fields as $aux_field) { $aux_control = $aux_field->renderEditControl(); $form->appendChild($aux_control); } require_celerity_resource('aphront-error-view-css'); Javelin::initBehavior('project-create', array( 'tokenizerID' => $project_tokenizer_id, )); if ($files) { $file_display = mpull($files, 'getName'); $file_display = phutil_implode_html(phutil_tag('br'), $file_display); $form->appendChild( id(new AphrontFormMarkupControl()) ->setLabel(pht('Files')) ->setValue($file_display)); foreach ($files as $ii => $file) { $form->addHiddenInput('files['.$ii.']', $file->getPHID()); } } $description_control = new PhabricatorRemarkupControl(); // "Upsell" creating tasks via email in create flows if the instance is // configured for this awesomeness. $email_create = PhabricatorEnv::getEnvConfig( 'metamta.maniphest.public-create-email'); if (!$task->getID() && $email_create) { $email_hint = pht( 'You can also create tasks by sending an email to: %s', phutil_tag('tt', array(), $email_create)); $description_control->setCaption($email_hint); } $description_control ->setLabel(pht('Description')) ->setName('description') ->setID('description-textarea') ->setValue($task->getDescription()) ->setUser($user); $form ->appendChild($description_control); if ($request->isAjax()) { $dialog = id(new AphrontDialogView()) ->setUser($user) ->setWidth(AphrontDialogView::WIDTH_FULL) ->setTitle($header_name) ->appendChild( array( $error_view, $form, )) ->addCancelButton($cancel_uri) ->addSubmitButton($button_name); return id(new AphrontDialogResponse())->setDialog($dialog); } $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue($button_name)); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($header_name) ->setFormError($error_view) ->setForm($form); $preview = id(new PHUIRemarkupPreviewPanel()) ->setHeader(pht('Description Preview')) ->setControlID('description-textarea') ->setPreviewURI($this->getApplicationURI('task/descriptionpreview/')); if ($task->getID()) { $page_objects = array( $task->getPHID() ); } else { $page_objects = array(); } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($header_name)); return $this->buildApplicationPage( array( $crumbs, $form_box, $preview, ), array( 'title' => $header_name, 'pageObjects' => $page_objects, 'device' => true, )); } } diff --git a/src/applications/owners/controller/PhabricatorOwnersEditController.php b/src/applications/owners/controller/PhabricatorOwnersEditController.php index 33a1aabeb7..77c3f582b5 100644 --- a/src/applications/owners/controller/PhabricatorOwnersEditController.php +++ b/src/applications/owners/controller/PhabricatorOwnersEditController.php @@ -1,281 +1,278 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->id) { $package = id(new PhabricatorOwnersPackage())->load($this->id); if (!$package) { return new Aphront404Response(); } } else { $package = new PhabricatorOwnersPackage(); $package->setPrimaryOwnerPHID($user->getPHID()); } $e_name = true; $e_primary = true; $errors = array(); if ($request->isFormPost()) { $package->setName($request->getStr('name')); $package->setDescription($request->getStr('description')); $old_auditing_enabled = $package->getAuditingEnabled(); $package->setAuditingEnabled( ($request->getStr('auditing') === 'enabled') ? 1 : 0); $primary = $request->getArr('primary'); $primary = reset($primary); $old_primary = $package->getPrimaryOwnerPHID(); $package->setPrimaryOwnerPHID($primary); $owners = $request->getArr('owners'); if ($primary) { array_unshift($owners, $primary); } $owners = array_unique($owners); $paths = $request->getArr('path'); $repos = $request->getArr('repo'); $excludes = $request->getArr('exclude'); $path_refs = array(); for ($ii = 0; $ii < count($paths); $ii++) { if (empty($paths[$ii]) || empty($repos[$ii])) { continue; } $path_refs[] = array( 'repositoryPHID' => $repos[$ii], 'path' => $paths[$ii], 'excluded' => $excludes[$ii], ); } if (!strlen($package->getName())) { $e_name = pht('Required'); $errors[] = pht('Package name is required.'); } else { $e_name = null; } if (!$package->getPrimaryOwnerPHID()) { $e_primary = pht('Required'); $errors[] = pht('Package must have a primary owner.'); } else { $e_primary = null; } if (!$path_refs) { $errors[] = pht('Package must include at least one path.'); } if (!$errors) { $package->attachUnsavedOwners($owners); $package->attachUnsavedPaths($path_refs); $package->attachOldAuditingEnabled($old_auditing_enabled); $package->attachOldPrimaryOwnerPHID($old_primary); $package->attachActorPHID($user->getPHID()); try { $package->save(); return id(new AphrontRedirectResponse()) ->setURI('/owners/package/'.$package->getID().'/'); } catch (AphrontQueryDuplicateKeyException $ex) { $e_name = pht('Duplicate'); $errors[] = pht('Package name must be unique.'); } } } else { $owners = $package->loadOwners(); $owners = mpull($owners, 'getUserPHID'); $paths = $package->loadPaths(); $path_refs = array(); foreach ($paths as $path) { $path_refs[] = array( 'repositoryPHID' => $path->getRepositoryPHID(), 'path' => $path->getPath(), 'excluded' => $path->getExcluded(), ); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setTitle(pht('Package Errors')); $error_view->setErrors($errors); } $handles = $this->loadViewerHandles($owners); $primary = $package->getPrimaryOwnerPHID(); if ($primary && isset($handles[$primary])) { - $token_primary_owner = array( - $primary => $handles[$primary]->getFullName(), - ); + $handle_primary_owner = array($handles[$primary]); } else { - $token_primary_owner = array(); + $handle_primary_owner = array(); } - $token_all_owners = array_select_keys($handles, $owners); - $token_all_owners = mpull($token_all_owners, 'getFullName'); + $handles_all_owners = array_select_keys($handles, $owners); if ($package->getID()) { $title = pht('Edit Package'); $side_nav_filter = 'edit/'.$this->id; } else { $title = pht('New Package'); $side_nav_filter = 'new'; } $this->setSideNavFilter($side_nav_filter); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->execute(); $default_paths = array(); foreach ($repos as $repo) { $default_path = $repo->getDetail('default-owners-path'); if ($default_path) { $default_paths[$repo->getPHID()] = $default_path; } } $repos = mpull($repos, 'getCallsign', 'getPHID'); $template = new AphrontTypeaheadTemplateView(); $template = $template->render(); Javelin::initBehavior( 'owners-path-editor', array( 'root' => 'path-editor', 'table' => 'paths', 'add_button' => 'addpath', 'repositories' => $repos, 'input_template' => $template, 'pathRefs' => $path_refs, 'completeURI' => '/diffusion/services/path/complete/', 'validateURI' => '/diffusion/services/path/validate/', 'repositoryDefaultPaths' => $default_paths, )); require_celerity_resource('owners-path-editor-css'); $cancel_uri = $package->getID() ? '/owners/package/'.$package->getID().'/' : '/owners/'; $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($package->getName()) ->setError($e_name)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/usersorprojects/') ->setLabel(pht('Primary Owner')) ->setName('primary') ->setLimit(1) - ->setValue($token_primary_owner) + ->setValue($handle_primary_owner) ->setError($e_primary)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/usersorprojects/') ->setLabel(pht('Owners')) ->setName('owners') - ->setValue($token_all_owners)) + ->setValue($handles_all_owners)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('auditing') ->setLabel(pht('Auditing')) ->setCaption( pht('With auditing enabled, all future commits that touch '. 'this package will be reviewed to make sure an owner '. 'of the package is involved and the commit message has '. 'a valid revision, reviewed by, and author.')) ->setOptions(array( 'disabled' => pht('Disabled'), 'enabled' => pht('Enabled'), )) ->setValue( $package->getAuditingEnabled() ? 'enabled' : 'disabled')) ->appendChild( id(new AphrontFormInsetView()) ->setTitle(pht('Paths')) ->addDivAttributes(array('id' => 'path-editor')) ->setRightButton(javelin_tag( 'a', array( 'href' => '#', 'class' => 'button green', 'sigil' => 'addpath', 'mustcapture' => true, ), pht('Add New Path'))) ->setDescription( pht('Specify the files and directories which comprise '. 'this package.')) ->setContent(javelin_tag( 'table', array( 'class' => 'owners-path-editor-table', 'sigil' => 'paths', ), ''))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Description')) ->setName('description') ->setValue($package->getDescription())) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton($cancel_uri) ->setValue(pht('Save Package'))); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormError($error_view) ->setForm($form); $nav = $this->buildSideNavView(); $nav->appendChild($form_box); return $this->buildApplicationPage( array( $nav, ), array( 'title' => $title, 'device' => true, )); } protected function getExtraPackageViews(AphrontSideNavFilterView $view) { if ($this->id) { $view->addFilter('edit/'.$this->id, pht('Edit')); } else { $view->addFilter('new', pht('New')); } } } diff --git a/src/applications/owners/controller/PhabricatorOwnersListController.php b/src/applications/owners/controller/PhabricatorOwnersListController.php index 0d6130a59c..14431faa9e 100644 --- a/src/applications/owners/controller/PhabricatorOwnersListController.php +++ b/src/applications/owners/controller/PhabricatorOwnersListController.php @@ -1,349 +1,347 @@ view = idx($data, 'view', 'owned'); $this->setSideNavFilter('view/'.$this->view); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $package = new PhabricatorOwnersPackage(); $owner = new PhabricatorOwnersOwner(); $path = new PhabricatorOwnersPath(); $repository_phid = ''; if ($request->getStr('repository') != '') { $repository_phid = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->withCallsigns(array($request->getStr('repository'))) ->executeOne() ->getPHID(); } switch ($this->view) { case 'search': $packages = array(); $conn_r = $package->establishConnection('r'); $where = array('1 = 1'); $join = array(); $having = ''; if ($request->getStr('name')) { $where[] = qsprintf( $conn_r, 'p.name LIKE %~', $request->getStr('name')); } if ($repository_phid || $request->getStr('path')) { $join[] = qsprintf( $conn_r, 'JOIN %T path ON path.packageID = p.id', $path->getTableName()); if ($repository_phid) { $where[] = qsprintf( $conn_r, 'path.repositoryPHID = %s', $repository_phid); } if ($request->getStr('path')) { $where[] = qsprintf( $conn_r, '(path.path LIKE %~ AND NOT path.excluded) OR %s LIKE CONCAT(REPLACE(path.path, %s, %s), %s)', $request->getStr('path'), $request->getStr('path'), '_', '\_', '%'); $having = 'HAVING MAX(path.excluded) = 0'; } } if ($request->getArr('owner')) { $join[] = qsprintf( $conn_r, 'JOIN %T o ON o.packageID = p.id', $owner->getTableName()); $where[] = qsprintf( $conn_r, 'o.userPHID IN (%Ls)', $request->getArr('owner')); } $data = queryfx_all( $conn_r, 'SELECT p.* FROM %T p %Q WHERE %Q GROUP BY p.id %Q', $package->getTableName(), implode(' ', $join), '('.implode(') AND (', $where).')', $having); $packages = $package->loadAllFromArray($data); $header = pht('Search Results'); $nodata = pht('No packages match your query.'); break; case 'owned': $data = queryfx_all( $package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID WHERE o.userPHID = %s GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $user->getPHID()); $packages = $package->loadAllFromArray($data); $header = pht('Owned Packages'); $nodata = pht('No owned packages'); break; case 'projects': $projects = id(new PhabricatorProjectQuery()) ->setViewer($user) ->withMemberPHIDs(array($user->getPHID())) ->withStatus(PhabricatorProjectQuery::STATUS_ANY) ->execute(); $owner_phids = mpull($projects, 'getPHID'); if ($owner_phids) { $data = queryfx_all( $package->establishConnection('r'), 'SELECT p.* FROM %T p JOIN %T o ON p.id = o.packageID WHERE o.userPHID IN (%Ls) GROUP BY p.id', $package->getTableName(), $owner->getTableName(), $owner_phids); } else { $data = array(); } $packages = $package->loadAllFromArray($data); $header = pht('Owned Packages'); $nodata = pht('No owned packages'); break; case 'all': $packages = $package->loadAll(); $header = pht('All Packages'); $nodata = pht('There are no defined packages.'); break; } $content = $this->renderPackageTable( $packages, $header, $nodata); $filter = new AphrontListFilterView(); $owners_search_value = array(); if ($request->getArr('owner')) { $phids = $request->getArr('owner'); $phid = reset($phids); $handles = $this->loadViewerHandles(array($phid)); - $owners_search_value = array( - $phid => $handles[$phid]->getFullName(), - ); + $owners_search_value = array($handles[$phid]); } $callsigns = array('' => pht('(Any Repository)')); $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->setOrder(PhabricatorRepositoryQuery::ORDER_CALLSIGN) ->execute(); foreach ($repositories as $repository) { $callsigns[$repository->getCallsign()] = $repository->getCallsign().': '.$repository->getName(); } $form = id(new AphrontFormView()) ->setUser($user) ->setAction('/owners/view/search/') ->setMethod('GET') ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($request->getStr('name'))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/usersorprojects/') ->setLimit(1) ->setName('owner') ->setLabel(pht('Owner')) ->setValue($owners_search_value)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('repository') ->setLabel(pht('Repository')) ->setOptions($callsigns) ->setValue($request->getStr('repository'))) ->appendChild( id(new AphrontFormTextControl()) ->setName('path') ->setLabel(pht('Path')) ->setValue($request->getStr('path'))) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Search for Packages'))); $filter->appendChild($form); $nav = $this->buildSideNavView(); $nav->appendChild($filter); $nav->appendChild($content); return $this->buildApplicationPage( array( $nav, ), array( 'title' => pht('Package Index'), 'device' => true, )); } private function renderPackageTable(array $packages, $header, $nodata) { assert_instances_of($packages, 'PhabricatorOwnersPackage'); if ($packages) { $package_ids = mpull($packages, 'getID'); $owners = id(new PhabricatorOwnersOwner())->loadAllWhere( 'packageID IN (%Ld)', $package_ids); $paths = id(new PhabricatorOwnersPath())->loadAllWhere( 'packageID in (%Ld)', $package_ids); $phids = array(); foreach ($owners as $owner) { $phids[$owner->getUserPHID()] = true; } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $repository_phids = array(); foreach ($paths as $path) { $repository_phids[$path->getRepositoryPHID()] = true; } if ($repository_phids) { $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getRequest()->getUser()) ->withPHIDs(array_keys($repository_phids)) ->execute(); } else { $repositories = array(); } $repositories = mpull($repositories, null, 'getPHID'); $owners = mgroup($owners, 'getPackageID'); $paths = mgroup($paths, 'getPackageID'); } else { $handles = array(); $repositories = array(); $owners = array(); $paths = array(); } $rows = array(); foreach ($packages as $package) { $pkg_owners = idx($owners, $package->getID(), array()); foreach ($pkg_owners as $key => $owner) { $pkg_owners[$key] = $handles[$owner->getUserPHID()]->renderLink(); if ($owner->getUserPHID() == $package->getPrimaryOwnerPHID()) { $pkg_owners[$key] = phutil_tag('strong', array(), $pkg_owners[$key]); } } $pkg_owners = phutil_implode_html(phutil_tag('br'), $pkg_owners); $pkg_paths = idx($paths, $package->getID(), array()); foreach ($pkg_paths as $key => $path) { $repo = idx($repositories, $path->getRepositoryPHID()); if ($repo) { $href = DiffusionRequest::generateDiffusionURI( array( 'callsign' => $repo->getCallsign(), 'branch' => $repo->getDefaultBranch(), 'path' => $path->getPath(), 'action' => 'browse', )); $pkg_paths[$key] = hsprintf( '%s %s%s', ($path->getExcluded() ? "\xE2\x80\x93" : '+'), phutil_tag('strong', array(), $repo->getName()), phutil_tag( 'a', array( 'href' => (string) $href, ), $path->getPath())); } else { $pkg_paths[$key] = $path->getPath(); } } $pkg_paths = phutil_implode_html(phutil_tag('br'), $pkg_paths); $rows[] = array( phutil_tag( 'a', array( 'href' => '/owners/package/'.$package->getID().'/', ), $package->getName()), $pkg_owners, $pkg_paths, phutil_tag( 'a', array( 'href' => '/audit/view/packagecommits/?phid='.$package->getPHID(), ), pht('Related Commits')) ); } $table = new AphrontTableView($rows); $table->setHeaders( array( pht('Name'), pht('Owners'), pht('Paths'), pht('Related Commits'), )); $table->setColumnClasses( array( 'pri', '', 'wide wrap', 'narrow', )); $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($table); $panel->setNoBackground(); return $panel; } protected function getExtraPackageViews(AphrontSideNavFilterView $view) { if ($this->view == 'search') { $view->addFilter('view/search', pht('Search Results')); } } } diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php index 4101458ee2..542931edb9 100644 --- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php +++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php @@ -1,130 +1,129 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $languages = $request->getStrList('languages'); if ($request->getBool('noLanguage')) { $languages[] = null; } $saved->setParameter('languages', $languages); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorPasteQuery()) ->needContent(true) ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())) ->withLanguages($saved->getParameter('languages', array())); $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $languages = $saved_query->getParameter('languages', array()); $no_language = false; foreach ($languages as $key => $language) { if ($language === null) { $no_language = true; unset($languages[$key]); continue; } } $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormTextControl()) ->setName('languages') ->setLabel(pht('Languages')) ->setValue(implode(', ', $languages))) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'noLanguage', 1, pht('Find Pastes with no specified language.'), $no_language)); $this->buildDateRange( $form, $saved_query, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); } protected function getURI($path) { return '/paste/'.$path; } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Pastes'), ); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/people/controller/PhabricatorPeopleLogsController.php b/src/applications/people/controller/PhabricatorPeopleLogsController.php index 9776e862e4..f43be11c23 100644 --- a/src/applications/people/controller/PhabricatorPeopleLogsController.php +++ b/src/applications/people/controller/PhabricatorPeopleLogsController.php @@ -1,235 +1,231 @@ getRequest(); $user = $request->getUser(); $filter_activity = $request->getStr('activity'); $filter_ip = $request->getStr('ip'); $filter_session = $request->getStr('session'); $filter_user = $request->getArr('user', array()); $filter_actor = $request->getArr('actor', array()); $user_value = array(); $actor_value = array(); $phids = array_merge($filter_user, $filter_actor); if ($phids) { $handles = $this->loadViewerHandles($phids); if ($filter_user) { $filter_user = reset($filter_user); - $user_value = array( - $filter_user => $handles[$filter_user]->getFullName(), - ); + $user_value = array($handles[$filter_user]); } if ($filter_actor) { $filter_actor = reset($filter_actor); - $actor_value = array( - $filter_actor => $handles[$filter_actor]->getFullName(), - ); + $actor_value = array($handles[$filter_actor]); } } $form = new AphrontFormView(); $form ->setUser($user) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Filter Actor')) ->setName('actor') ->setLimit(1) ->setValue($actor_value) ->setDatasource('/typeahead/common/accounts/')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Filter User')) ->setName('user') ->setLimit(1) ->setValue($user_value) ->setDatasource('/typeahead/common/accounts/')) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Show Activity')) ->setName('activity') ->setValue($filter_activity) ->setOptions( array( '' => pht('All Activity'), 'admin' => pht('Admin Activity'), ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Filter IP')) ->setName('ip') ->setValue($filter_ip) ->setCaption( pht('Enter an IP (or IP prefix) to show only activity by that '. 'remote address.'))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Filter Session')) ->setName('session') ->setValue($filter_session)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Filter Logs'))); $log_table = new PhabricatorUserLog(); $conn_r = $log_table->establishConnection('r'); $where_clause = array(); $where_clause[] = '1 = 1'; if ($filter_user) { $where_clause[] = qsprintf( $conn_r, 'userPHID = %s', $filter_user); } if ($filter_actor) { $where_clause[] = qsprintf( $conn_r, 'actorPHID = %s', $filter_actor); } if ($filter_activity == 'admin') { $where_clause[] = qsprintf( $conn_r, 'action NOT IN (%Ls)', array( PhabricatorUserLog::ACTION_LOGIN, PhabricatorUserLog::ACTION_LOGOUT, PhabricatorUserLog::ACTION_LOGIN_FAILURE, )); } if ($filter_ip) { $where_clause[] = qsprintf( $conn_r, 'remoteAddr LIKE %>', $filter_ip); } if ($filter_session) { $where_clause[] = qsprintf( $conn_r, 'session = %s', $filter_session); } $where_clause = '('.implode(') AND (', $where_clause).')'; $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setOffset($request->getInt('page')); $pager->setPageSize(500); $logs = $log_table->loadAllWhere( '(%Q) ORDER BY dateCreated DESC LIMIT %d, %d', $where_clause, $pager->getOffset(), $pager->getPageSize() + 1); $logs = $pager->sliceResults($logs); $phids = array(); foreach ($logs as $log) { $phids[$log->getActorPHID()] = true; $phids[$log->getUserPHID()] = true; } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); $rows = array(); foreach ($logs as $log) { $rows[] = array( phabricator_date($log->getDateCreated(), $user), phabricator_time($log->getDateCreated(), $user), $log->getAction(), $log->getActorPHID() ? $handles[$log->getActorPHID()]->getName() : null, $handles[$log->getUserPHID()]->getName(), json_encode($log->getOldValue(), true), json_encode($log->getNewValue(), true), phutil_tag( 'a', array( 'href' => $request ->getRequestURI() ->alter('ip', $log->getRemoteAddr()), ), $log->getRemoteAddr()), phutil_tag( 'a', array( 'href' => $request ->getRequestURI() ->alter('session', $log->getSession()), ), $log->getSession()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( pht('Date'), pht('Time'), pht('Action'), pht('Actor'), pht('User'), pht('Old'), pht('New'), pht('IP'), pht('Session'), )); $table->setColumnClasses( array( '', 'right', '', '', '', 'wrap', 'wrap', '', 'wide', )); $panel = new AphrontPanelView(); $panel->setHeader(pht('Activity Logs')); $panel->setNoBackground(); $panel->appendChild($table); $panel->appendChild($pager); $filter = new AphrontListFilterView(); $filter->appendChild($form); $crumbs = $this->buildApplicationCrumbs($this->buildSideNavView()); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Activity Logs')) ->setHref('/people/logs/')); $nav = $this->buildSideNavView(); $nav->selectFilter('logs'); $nav->appendChild( array( $filter, $panel, )); $nav->setCrumbs($crumbs); return $this->buildApplicationPage( $nav, array( 'title' => pht('Activity Logs'), 'device' => true, )); } } diff --git a/src/applications/pholio/controller/PholioMockEditController.php b/src/applications/pholio/controller/PholioMockEditController.php index e27c0d9e70..a3dde9a4b8 100644 --- a/src/applications/pholio/controller/PholioMockEditController.php +++ b/src/applications/pholio/controller/PholioMockEditController.php @@ -1,349 +1,347 @@ id = idx($data, 'id'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->id) { $mock = id(new PholioMockQuery()) ->setViewer($user) ->needImages(true) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->withIDs(array($this->id)) ->executeOne(); if (!$mock) { return new Aphront404Response(); } $title = pht('Edit Mock'); $is_new = false; $mock_images = $mock->getImages(); $files = mpull($mock_images, 'getFile'); $mock_images = mpull($mock_images, null, 'getFilePHID'); } else { $mock = id(new PholioMock()) ->setAuthorPHID($user->getPHID()) ->attachImages(array()) ->setViewPolicy(PhabricatorPolicies::POLICY_USER); $title = pht('Create Mock'); $is_new = true; $files = array(); $mock_images = array(); } $e_name = true; $e_images = true; $errors = array(); $v_name = $mock->getName(); $v_desc = $mock->getDescription(); $v_view = $mock->getViewPolicy(); $v_cc = PhabricatorSubscribersQuery::loadSubscribersForPHID( $mock->getPHID()); if ($request->isFormPost()) { $xactions = array(); $type_name = PholioTransactionType::TYPE_NAME; $type_desc = PholioTransactionType::TYPE_DESCRIPTION; $type_view = PhabricatorTransactions::TYPE_VIEW_POLICY; $type_cc = PhabricatorTransactions::TYPE_SUBSCRIBERS; $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_view = $request->getStr('can_view'); $v_cc = $request->getArr('cc'); $mock_xactions = array(); $mock_xactions[$type_name] = $v_name; $mock_xactions[$type_desc] = $v_desc; $mock_xactions[$type_view] = $v_view; $mock_xactions[$type_cc] = array('=' => $v_cc); if (!strlen($request->getStr('name'))) { $e_name = 'Required'; $errors[] = pht('You must give the mock a name.'); } $file_phids = $request->getArr('file_phids'); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($user) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); $files = array_select_keys($files, $file_phids); } else { $files = array(); } if (!$files) { $e_images = pht('Required'); $errors[] = pht('You must add at least one image to the mock.'); } else { $mock->setCoverPHID(head($files)->getPHID()); } if (!$errors) { foreach ($mock_xactions as $type => $value) { $xactions[$type] = id(new PholioTransaction()) ->setTransactionType($type) ->setNewValue($value); } $order = $request->getStrList('imageOrder'); $sequence_map = array_flip($order); $replaces = $request->getArr('replaces'); $replaces_map = array_flip($replaces); /** * Foreach file posted, check to see whether we are replacing an image, * adding an image, or simply updating image metadata. Create * transactions for these cases as appropos. */ foreach ($files as $file_phid => $file) { $replaces_image_phid = null; if (isset($replaces_map[$file_phid])) { $old_file_phid = $replaces_map[$file_phid]; $old_image = idx($mock_images, $old_file_phid); if ($old_image) { $replaces_image_phid = $old_image->getPHID(); } } $existing_image = idx($mock_images, $file_phid); $title = (string)$request->getStr('title_'.$file_phid); $description = (string)$request->getStr('description_'.$file_phid); $sequence = $sequence_map[$file_phid]; if ($replaces_image_phid) { $replace_image = id(new PholioImage()) ->setReplacesImagePHID($replaces_image_phid) ->setFilePhid($file_phid) ->setName(strlen($title) ? $title : $file->getName()) ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransactionType::TYPE_IMAGE_REPLACE) ->setNewValue($replace_image); } else if (!$existing_image) { // this is an add $add_image = id(new PholioImage()) ->setFilePhid($file_phid) ->setName(strlen($title) ? $title : $file->getName()) ->setDescription($description) ->setSequence($sequence); $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) ->setNewValue( array('+' => array($add_image))); } else { $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransactionType::TYPE_IMAGE_NAME) ->setNewValue( array($existing_image->getPHID() => $title)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransactionType::TYPE_IMAGE_DESCRIPTION) ->setNewValue( array($existing_image->getPHID() => $description)); $xactions[] = id(new PholioTransaction()) ->setTransactionType( PholioTransactionType::TYPE_IMAGE_SEQUENCE) ->setNewValue( array($existing_image->getPHID() => $sequence)); } } foreach ($mock_images as $file_phid => $mock_image) { if (!isset($files[$file_phid]) && !isset($replaces[$file_phid])) { // this is an outright delete $xactions[] = id(new PholioTransaction()) ->setTransactionType(PholioTransactionType::TYPE_IMAGE_FILE) ->setNewValue( array('-' => array($mock_image))); } } $mock->openTransaction(); $editor = id(new PholioMockEditor()) ->setContentSourceFromRequest($request) ->setContinueOnNoEffect(true) ->setActor($user); $xactions = $editor->applyTransactions($mock, $xactions); $mock->saveTransaction(); return id(new AphrontRedirectResponse()) ->setURI('/M'.$mock->getID()); } } if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } else { $error_view = null; } if ($this->id) { $submit = id(new AphrontFormSubmitControl()) ->addCancelButton('/M'.$this->id) ->setValue(pht('Save')); } else { $submit = id(new AphrontFormSubmitControl()) ->addCancelButton($this->getApplicationURI()) ->setValue(pht('Create')); } $policies = id(new PhabricatorPolicyQuery()) ->setViewer($user) ->setObject($mock) ->execute(); // NOTE: Make this show up correctly on the rendered form. $mock->setViewPolicy($v_view); $handles = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs($v_cc) ->execute(); - $cc_tokens = mpull($handles, 'getFullName', 'getPHID'); - $image_elements = array(); foreach ($mock_images as $mock_image) { $image_elements[] = id(new PholioUploadedImageView()) ->setUser($user) ->setImage($mock_image); } $list_id = celerity_generate_unique_node_id(); $drop_id = celerity_generate_unique_node_id(); $order_id = celerity_generate_unique_node_id(); $list_control = phutil_tag( 'div', array( 'id' => $list_id, 'class' => 'pholio-edit-list', ), $image_elements); $drop_control = phutil_tag( 'div', array( 'id' => $drop_id, 'class' => 'pholio-edit-drop', ), 'Drag and drop images here to add them to the mock.'); $order_control = phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'imageOrder', 'id' => $order_id, )); Javelin::initBehavior( 'pholio-mock-edit', array( 'listID' => $list_id, 'dropID' => $drop_id, 'orderID' => $order_id, 'uploadURI' => '/file/dropupload/', 'renderURI' => $this->getApplicationURI('image/upload/'), 'pht' => array( 'uploading' => pht('Uploading Image...'), 'uploaded' => pht('Upload Complete...'), 'undo' => pht('Undo'), 'removed' => pht('This image will be removed from the mock.'), ), )); require_celerity_resource('pholio-edit-css'); $form = id(new AphrontFormView()) ->setUser($user) ->appendChild($order_control) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setValue($v_name) ->setLabel(pht('Name')) ->setError($e_name)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setName('description') ->setValue($v_desc) ->setLabel(pht('Description')) ->setUser($user)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('CC')) ->setName('cc') - ->setValue($cc_tokens) + ->setValue($handles) ->setUser($user) ->setDatasource('/typeahead/common/mailable/')) ->appendChild( id(new AphrontFormPolicyControl()) ->setUser($user) ->setCapability(PhabricatorPolicyCapability::CAN_VIEW) ->setPolicyObject($mock) ->setPolicies($policies) ->setName('can_view')) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($list_control)) ->appendChild( id(new AphrontFormMarkupControl()) ->setValue($drop_control) ->setError($e_images)) ->appendChild($submit); $form_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setFormError($error_view) ->setForm($form); $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName($title) ->setHref($this->getApplicationURI())); $content = array( $crumbs, $form_box, ); return $this->buildApplicationPage( $content, array( 'title' => $title, 'device' => true, )); } } diff --git a/src/applications/pholio/query/PholioMockSearchEngine.php b/src/applications/pholio/query/PholioMockSearchEngine.php index e64a44ef86..5da9d405ad 100644 --- a/src/applications/pholio/query/PholioMockSearchEngine.php +++ b/src/applications/pholio/query/PholioMockSearchEngine.php @@ -1,79 +1,77 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PholioMockQuery()) ->needCoverFiles(true) ->needImages(true) ->needTokenCounts(true) ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())); return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)); - + ->setValue($author_handles)); } protected function getURI($path) { return '/pholio/'.$path; } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Mocks'), ); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/ponder/query/PonderQuestionSearchEngine.php b/src/applications/ponder/query/PonderQuestionSearchEngine.php index 57c3faaba0..ec715b028a 100644 --- a/src/applications/ponder/query/PonderQuestionSearchEngine.php +++ b/src/applications/ponder/query/PonderQuestionSearchEngine.php @@ -1,131 +1,127 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter( 'answererPHIDs', $this->readUsersFromRequest($request, 'answerers')); $saved->setParameter('status', $request->getStr('status')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PonderQuestionQuery()); $author_phids = $saved->getParameter('authorPHIDs'); if ($author_phids) { $query->withAuthorPHIDs($author_phids); } $answerer_phids = $saved->getParameter('answererPHIDs'); if ($answerer_phids) { $query->withAnswererPHIDs($answerer_phids); } $status = $saved->getParameter('status'); if ($status != null) { switch ($status) { case 0: $query->withStatus(PonderQuestionQuery::STATUS_OPEN); break; case 1: $query->withStatus(PonderQuestionQuery::STATUS_CLOSED); break; } } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $author_phids = $saved_query->getParameter('authorPHIDs', array()); $answerer_phids = $saved_query->getParameter('answererPHIDs', array()); $status = $saved_query->getParameter( 'status', PonderQuestionStatus::STATUS_OPEN); $phids = array_merge($author_phids, $answerer_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $tokens = mpull($handles, 'getFullName', 'getPHID'); - - $author_tokens = array_select_keys($tokens, $author_phids); - $answerer_tokens = array_select_keys($tokens, $answerer_phids); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue(array_select_keys($handles, $author_phids))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('answerers') ->setLabel(pht('Answered By')) - ->setValue($answerer_tokens)) + ->setValue(array_select_keys($handles, $answerer_phids))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setValue($status) ->setOptions(PonderQuestionStatus::getQuestionStatusMap())); } protected function getURI($path) { return '/ponder/'.$path; } public function getBuiltinQueryNames() { $names = array( 'open' => pht('Open Questions'), 'all' => pht('All Questions'), ); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); $names['answered'] = pht('Answered'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'open': return $query->setParameter('status', PonderQuestionQuery::STATUS_OPEN); case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); case 'answered': return $query->setParameter( 'answererPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 448627ecf0..f10129bb29 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -1,115 +1,114 @@ setParameter( 'memberPHIDs', $this->readUsersFromRequest($request, 'members')); $saved->setParameter('status', $request->getStr('status')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorProjectQuery()); $member_phids = $saved->getParameter('memberPHIDs', array()); if ($member_phids && is_array($member_phids)) { $query->withMemberPHIDs($member_phids); } $status = $saved->getParameter('status'); $status = idx($this->getStatusValues(), $status); if ($status) { $query->withStatus($status); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('memberPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $member_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $member_tokens = mpull($handles, 'getFullName', 'getPHID'); $status = $saved_query->getParameter('status'); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('members') ->setLabel(pht('Members')) - ->setValue($member_tokens)) + ->setValue($member_handles)) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel(pht('Status')) ->setName('status') ->setOptions($this->getStatusOptions()) ->setValue($status)); } protected function getURI($path) { return '/project/'.$path; } public function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['joined'] = pht('Joined'); } $names['active'] = pht('Active'); $names['all'] = pht('All'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; case 'active': return $query ->setParameter('status', 'active'); case 'joined': return $query ->setParameter('memberPHIDs', array($viewer_phid)) ->setParameter('status', 'active'); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( 'active' => pht('Show Only Active Projects'), 'all' => pht('Show All Projects'), ); } private function getStatusValues() { return array( 'active' => PhabricatorProjectQuery::STATUS_ACTIVE, 'all' => PhabricatorProjectQuery::STATUS_ANY, ); } } diff --git a/src/applications/releeph/controller/project/ReleephProjectEditController.php b/src/applications/releeph/controller/project/ReleephProjectEditController.php index d39c6fcb52..47a42f46ea 100644 --- a/src/applications/releeph/controller/project/ReleephProjectEditController.php +++ b/src/applications/releeph/controller/project/ReleephProjectEditController.php @@ -1,394 +1,391 @@ getRequest(); $e_name = true; $e_trunk_branch = true; $e_branch_template = false; $errors = array(); $project_name = $request->getStr('name', $this->getReleephProject()->getName()); $trunk_branch = $request->getStr('trunkBranch', $this->getReleephProject()->getTrunkBranch()); $branch_template = $request->getStr('branchTemplate'); if ($branch_template === null) { $branch_template = $this->getReleephProject()->getDetail('branchTemplate'); } $pick_failure_instructions = $request->getStr('pickFailureInstructions', $this->getReleephProject()->getDetail('pick_failure_instructions')); $commit_author = $request->getStr('commitWithAuthor', $this->getReleephProject()->getDetail('commitWithAuthor')); $test_paths = $request->getStr('testPaths'); if ($test_paths !== null) { $test_paths = array_filter(explode("\n", $test_paths)); } else { $test_paths = $this->getReleephProject()->getDetail('testPaths', array()); } $release_counter = $request->getInt( 'releaseCounter', $this->getReleephProject()->getCurrentReleaseNumber()); $arc_project_id = $this->getReleephProject()->getArcanistProjectID(); if ($request->isFormPost()) { $pusher_phids = $request->getArr('pushers'); if (!$project_name) { $e_name = pht('Required'); $errors[] = pht('Your releeph project should have a simple descriptive name'); } if (!$trunk_branch) { $e_trunk_branch = pht('Required'); $errors[] = pht('You must specify which branch you will be picking from.'); } if ($release_counter && !is_int($release_counter)) { $errors[] = pht("Release counter must be a positive integer!"); } $other_releeph_projects = id(new ReleephProject()) ->loadAllWhere('id <> %d', $this->getReleephProject()->getID()); $other_releeph_project_names = mpull($other_releeph_projects, 'getName', 'getID'); if (in_array($project_name, $other_releeph_project_names)) { $errors[] = pht("Releeph project name %s is already taken", $project_name); } foreach ($test_paths as $test_path) { $result = @preg_match($test_path, ''); $is_a_valid_regexp = $result !== false; if (!$is_a_valid_regexp) { $errors[] = pht('Please provide a valid regular expression: '. '%s is not valid', $test_path); } } $project = $this->getReleephProject() ->setTrunkBranch($trunk_branch) ->setDetail('pushers', $pusher_phids) ->setDetail('pick_failure_instructions', $pick_failure_instructions) ->setDetail('branchTemplate', $branch_template) ->setDetail('commitWithAuthor', $commit_author) ->setDetail('testPaths', $test_paths); if ($release_counter) { $project->setDetail('releaseCounter', $release_counter); } $fake_commit_handle = ReleephBranchTemplate::getFakeCommitHandleFor($arc_project_id); if ($branch_template) { list($branch_name, $template_errors) = id(new ReleephBranchTemplate()) ->setCommitHandle($fake_commit_handle) ->setReleephProjectName($project_name) ->interpolate($branch_template); if ($template_errors) { $e_branch_template = pht('Whoopsies!'); foreach ($template_errors as $template_error) { $errors[] = "Template error: {$template_error}"; } } } if (!$errors) { $project->save(); return id(new AphrontRedirectResponse()) ->setURI('/releeph/project/'); } } $error_view = null; if ($errors) { $error_view = new AphrontErrorView(); $error_view->setErrors($errors); $error_view->setTitle(pht('Form Errors')); } $projects = mpull( id(new PhabricatorProject())->loadAll(), 'getName', 'getID'); $projects[0] = '-'; // no project associated, that's ok $pusher_phids = $request->getArr( 'pushers', $this->getReleephProject()->getDetail('pushers', array())); $handles = id(new PhabricatorHandleQuery()) ->setViewer($request->getUser()) ->withPHIDs($pusher_phids) ->execute(); - $pusher_tokens = array(); - foreach ($pusher_phids as $phid) { - $pusher_tokens[$phid] = $handles[$phid]->getFullName(); - } + $pusher_handles = array_select_keys($handles, $pusher_phids); $basic_inset = id(new AphrontFormInsetView()) ->setTitle(pht('Basics')) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Name')) ->setName('name') ->setValue($project_name) ->setError($e_name) ->setCaption(pht('A name like "Thrift" but not "Thrift releases".'))) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Repository')) ->setValue( $this ->getReleephProject() ->loadPhabricatorRepository() ->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Arc Project')) ->setValue( $this->getReleephProject()->loadArcanistProject()->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel(pht('Releeph Project PHID')) ->setValue( $this->getReleephProject()->getPHID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Trunk')) ->setValue($trunk_branch) ->setName('trunkBranch') ->setError($e_trunk_branch)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel(pht('Release counter')) ->setValue($release_counter) ->setName('releaseCounter') ->setCaption( pht("Used by the command line branch cutter's %%N field"))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Pick Instructions')) ->setValue($pick_failure_instructions) ->setName('pickFailureInstructions') ->setCaption( pht("Instructions for pick failures, which will be used " . "in emails generated by failed picks"))) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel(pht('Tests paths')) ->setValue(implode("\n", $test_paths)) ->setName('testPaths') ->setCaption( pht('List of strings that all test files contain in their path '. 'in this project. One string per line. '. 'Examples: \'__tests__\', \'/javatests/\'...'))); $pushers_inset = id(new AphrontFormInsetView()) ->setTitle(pht('Pushers')) ->appendChild( pht('Pushers are allowed to approve Releeph requests to be committed. '. 'to this project\'s branches. If you leave this blank then anyone '. 'is allowed to approve requests.')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel(pht('Pushers')) ->setName('pushers') ->setDatasource('/typeahead/common/users/') - ->setValue($pusher_tokens)); + ->setValue($pusher_handles)); $commit_author_inset = $this->buildCommitAuthorInset($commit_author); // Build the Template inset $help_markup = PhabricatorMarkupEngine::renderOneObject( id(new PhabricatorMarkupOneOff())->setContent($this->getBranchHelpText()), 'default', $request->getUser()); $branch_template_input = id(new AphrontFormTextControl()) ->setName('branchTemplate') ->setValue($branch_template) ->setLabel('Template') ->setError($e_branch_template) ->setCaption( pht("Leave this blank to use your installation's default.")); $branch_template_preview = id(new ReleephBranchPreviewView()) ->setLabel(pht('Preview')) ->addControl('template', $branch_template_input) ->addStatic('arcProjectID', $arc_project_id) ->addStatic('isSymbolic', false) ->addStatic('projectName', $this->getReleephProject()->getName()); $template_inset = id(new AphrontFormInsetView()) ->setTitle(pht('Branch Cutting')) ->appendChild( pht('Provide a pattern for creating new branches.')) ->appendChild($branch_template_input) ->appendChild($branch_template_preview) ->appendChild($help_markup); // Build the form $form = id(new AphrontFormView()) ->setUser($request->getUser()) ->appendChild($basic_inset) ->appendChild($pushers_inset) ->appendChild($commit_author_inset) ->appendChild($template_inset); $form ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/releeph/project/') ->setValue(pht('Save'))); $panel = id(new AphrontPanelView()) ->setHeader(pht('Edit Releeph Project')) ->appendChild($form) ->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array('title' => pht('Edit Releeph Project'))); } private function buildCommitAuthorInset($current) { $vcs_type = $this->getReleephProject() ->loadPhabricatorRepository() ->getVersionControlSystem(); switch ($vcs_type) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return; break; } $vcs_name = PhabricatorRepositoryType::getNameForRepositoryType($vcs_type); // pht? $help_markup = hsprintf(<<arc releeph, they will be listed as the committer of the code committed to release branches. %s allows you to specify a separate author when committing code. Some tools use the author of a commit (rather than the committer) when they need to notify someone about a build or test failure. Releeph can use one of the following to set the author of the commits it makes: EOTEXT , $vcs_name); $trunk = $this->getReleephProject()->getTrunkBranch(); $options = array( array( 'value' => ReleephProject::COMMIT_AUTHOR_FROM_DIFF, 'label' => pht('Original Author'), 'caption' => pht('The author of the original commit in: %s.', $trunk), ), array( 'value' => ReleephProject::COMMIT_AUTHOR_REQUESTOR, 'label' => pht('Requestor'), 'caption' => pht('The person who requested that this code go into the release.'), ), array( 'value' => ReleephProject::COMMIT_AUTHOR_NONE, 'label' => pht('None'), 'caption' => pht('Only record the default committer information.'), ), ); if (!$current) { $current = ReleephProject::COMMIT_AUTHOR_FROM_DIFF; } $control = id(new AphrontFormRadioButtonControl()) ->setLabel(pht('Author')) ->setName('commitWithAuthor') ->setValue($current); foreach ($options as $dict) { $control->addButton($dict['value'], $dict['label'], $dict['caption']); } return id(new AphrontFormInsetView()) ->setTitle(pht('Authors')) ->appendChild($help_markup) ->appendChild($control); } private function getBranchHelpText() { return << releases/2012-30-16-rHERGE32cd512a52b7 Include a second hierarchy if you share your repository with other projects: lang=none releases/%P/%p-release-%Y%m%d-%V => releases/Tintin/tintin-release-20121116-32cd512a52b7 Keep your branch names simple, avoiding strange punctuation, most of which is forbidden or escaped anyway: lang=none, counterexample releases//..clown-releases..//`date --iso=seconds`-$(sudo halt) Include the date early in your template, in an order which sorts properly: lang=none releases/%Y%m%d-%v => releases/20121116-rHERGE32cd512a52b7 (good!) releases/%V-%m.%d.%Y => releases/32cd512a52b7-11.16.2012 (awful!) EOTEXT; } } diff --git a/src/applications/releeph/query/ReleephRequestSearchEngine.php b/src/applications/releeph/query/ReleephRequestSearchEngine.php index cc38899a8c..53db39f630 100644 --- a/src/applications/releeph/query/ReleephRequestSearchEngine.php +++ b/src/applications/releeph/query/ReleephRequestSearchEngine.php @@ -1,172 +1,171 @@ branch = $branch; return $this; } public function getBranch() { return $this->branch; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function buildSavedQueryFromRequest(AphrontRequest $request) { $saved = new PhabricatorSavedQuery(); $saved->setParameter('status', $request->getStr('status')); $saved->setParameter('severity', $request->getStr('severity')); $saved->setParameter( 'requestorPHIDs', $this->readUsersFromRequest($request, 'requestors')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new ReleephRequestQuery()) ->withBranchIDs(array($this->getBranch()->getID())); $status = $saved->getParameter('status'); $status = idx($this->getStatusValues(), $status); if ($status) { $query->withStatus($status); } $severity = $saved->getParameter('severity'); if ($severity) { $query->withSeverities(array($severity)); } $requestor_phids = $saved->getParameter('requestorPHIDs'); if ($requestor_phids) { $query->withRequestorPHIDs($requestor_phids); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('requestorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $requestor_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $requestor_tokens = mpull($handles, 'getFullName', 'getPHID'); $form ->appendChild( id(new AphrontFormSelectControl()) ->setName('status') ->setLabel(pht('Status')) ->setValue($saved_query->getParameter('status')) ->setOptions($this->getStatusOptions())) ->appendChild( id(new AphrontFormSelectControl()) ->setName('severity') ->setLabel(pht('Severity')) ->setValue($saved_query->getParameter('severity')) ->setOptions($this->getSeverityOptions())) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('requestors') ->setLabel(pht('Requestors')) - ->setValue($requestor_tokens)); + ->setValue($requestor_handles)); } protected function getURI($path) { return $this->baseURI.$path; } public function getBuiltinQueryNames() { $names = array( 'open' => pht('Open Requests'), 'all' => pht('All Requests'), ); if ($this->requireViewer()->isLoggedIn()) { $names['requested'] = pht('Requested'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'open': return $query->setParameter('status', 'open'); case 'all': return $query; case 'requested': return $query->setParameter( 'requestorPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( '' => pht('(All Requests)'), 'open' => pht('Open Requests'), 'requested' => pht('Pull Requested'), 'needs-pull' => pht('Needs Pull'), 'rejected' => pht('Rejected'), 'abandoned' => pht('Abandoned'), 'pulled' => pht('Pulled'), 'needs-revert' => pht('Needs Revert'), 'reverted' => pht('Reverted'), ); } private function getStatusValues() { return array( 'open' => ReleephRequestQuery::STATUS_OPEN, 'requested' => ReleephRequestQuery::STATUS_REQUESTED, 'needs-pull' => ReleephRequestQuery::STATUS_NEEDS_PULL, 'rejected' => ReleephRequestQuery::STATUS_REJECTED, 'abandoned' => ReleephRequestQuery::STATUS_ABANDONED, 'pulled' => ReleephRequestQuery::STATUS_PULLED, 'needs-revert' => ReleephRequestQuery::STATUS_NEEDS_REVERT, 'reverted' => ReleephRequestQuery::STATUS_REVERTED, ); } private function getSeverityOptions() { if (ReleephDefaultFieldSelector::isFacebook()) { return array( '' => pht('(All Severities)'), 11 => 'HOTFIX', 12 => 'PIGGYBACK', 13 => 'RELEASE', 14 => 'DAILY', 15 => 'PARKING', ); } else { return array( '' => pht('(All Severities)'), ReleephSeverityFieldSpecification::HOTFIX => pht('Hotfix'), ReleephSeverityFieldSpecification::RELEASE => pht('Release'), ); } } } diff --git a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php index 15a0b6f970..2330177d42 100644 --- a/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php +++ b/src/applications/repository/controller/PhabricatorRepositoryArcanistProjectEditController.php @@ -1,116 +1,116 @@ id = $data['id']; } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); $project = id(new PhabricatorRepositoryArcanistProject())->load($this->id); if (!$project) { return new Aphront404Response(); } $repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->execute(); $repos = array( 0 => 'None', ); foreach ($repositories as $repository) { $callsign = $repository->getCallsign(); $name = $repository->getname(); $repos[$repository->getID()] = "r{$callsign} ({$name})"; } // note "None" will still be first thanks to 'r' prefix asort($repos); if ($request->isFormPost()) { $indexed = $request->getStrList('symbolIndexLanguages'); $indexed = array_map('strtolower', $indexed); $project->setSymbolIndexLanguages($indexed); $project->setSymbolIndexProjects($request->getArr('symbolIndexProjects')); $repo_id = $request->getInt('repository', 0); if (isset($repos[$repo_id])) { $project->setRepositoryID($repo_id); $project->save(); return id(new AphrontRedirectResponse()) ->setURI('/repository/'); } } $langs = $project->getSymbolIndexLanguages(); if ($langs) { $langs = implode(', ', $langs); } else { $langs = null; } if ($project->getSymbolIndexProjects()) { - $uses = id(new PhabricatorRepositoryArcanistProject())->loadAllWhere( - 'phid in (%Ls)', - $project->getSymbolIndexProjects()); - $uses = mpull($uses, 'getName', 'getPHID'); + $uses = id(new PhabricatorHandleQuery()) + ->setViewer($user) + ->withPHIDs($project->getSymbolIndexProjects()) + ->execute(); } else { $uses = array(); } $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('Name') ->setValue($project->getName())) ->appendChild( id(new AphrontFormStaticControl()) ->setLabel('PHID') ->setValue($project->getPHID())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Repository') ->setOptions($repos) ->setName('repository') ->setValue($project->getRepositoryID())) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Indexed Languages') ->setName('symbolIndexLanguages') ->setCaption( hsprintf('Separate with commas, for example: php, py')) ->setValue($langs)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Uses Symbols From') ->setName('symbolIndexProjects') ->setDatasource('/typeahead/common/arcanistprojects/') ->setValue($uses)) ->appendChild( id(new AphrontFormSubmitControl()) ->addCancelButton('/repository/') ->setValue('Save')); $panel = new AphrontPanelView(); $panel->setWidth(AphrontPanelView::WIDTH_WIDE); $panel->setHeader('Edit Arcanist Project'); $panel->appendChild($form); return $this->buildStandardPageResponse( $panel, array( 'title' => 'Edit Project', )); } } diff --git a/src/applications/search/controller/PhabricatorSearchController.php b/src/applications/search/controller/PhabricatorSearchController.php index 79f566a128..f9296b8a7b 100644 --- a/src/applications/search/controller/PhabricatorSearchController.php +++ b/src/applications/search/controller/PhabricatorSearchController.php @@ -1,300 +1,296 @@ key = idx($data, 'key'); } public function processRequest() { $request = $this->getRequest(); $user = $request->getUser(); if ($this->key) { $query = id(new PhabricatorSearchQuery())->loadOneWhere( 'queryKey = %s', $this->key); if (!$query) { return new Aphront404Response(); } } else { $query = new PhabricatorSearchQuery(); if ($request->isFormPost()) { $query_str = $request->getStr('query'); $pref_jump = PhabricatorUserPreferences::PREFERENCE_SEARCHBAR_JUMP; if ($request->getStr('jump') != 'no' && $user && $user->loadPreferences()->getPreference($pref_jump, 1)) { $response = PhabricatorJumpNavHandler::getJumpResponse( $user, $query_str); } else { $response = null; } if ($response) { return $response; } else { $query->setQuery($query_str); if ($request->getStr('scope')) { switch ($request->getStr('scope')) { case PhabricatorSearchScope::SCOPE_OPEN_REVISIONS: $query->setParameter('open', 1); $query->setParameter( 'type', DifferentialPHIDTypeRevision::TYPECONST); break; case PhabricatorSearchScope::SCOPE_OPEN_TASKS: $query->setParameter('open', 1); $query->setParameter( 'type', ManiphestPHIDTypeTask::TYPECONST); break; case PhabricatorSearchScope::SCOPE_WIKI: $query->setParameter( 'type', PhrictionPHIDTypeDocument::TYPECONST); break; case PhabricatorSearchScope::SCOPE_COMMITS: $query->setParameter( 'type', PhabricatorRepositoryPHIDTypeCommit::TYPECONST); break; default: break; } } else { if (strlen($request->getStr('type'))) { $query->setParameter('type', $request->getStr('type')); } if ($request->getArr('author')) { $query->setParameter('author', $request->getArr('author')); } if ($request->getArr('owner')) { $query->setParameter('owner', $request->getArr('owner')); } if ($request->getArr('subscribers')) { $query->setParameter('subscribers', $request->getArr('subscribers')); } if ($request->getInt('open')) { $query->setParameter('open', $request->getInt('open')); } if ($request->getArr('project')) { $query->setParameter('project', $request->getArr('project')); } } $query->save(); return id(new AphrontRedirectResponse()) ->setURI('/search/'.$query->getQueryKey().'/'); } } } $options = array( '' => 'All Documents', ) + PhabricatorSearchAbstractDocument::getSupportedTypes(); $status_options = array( 0 => 'Open and Closed Documents', 1 => 'Open Documents', ); $phids = array_merge( $query->getParameter('author', array()), $query->getParameter('owner', array()), $query->getParameter('subscribers', array()), $query->getParameter('project', array())); $handles = $this->loadViewerHandles($phids); $author_value = array_select_keys( $handles, $query->getParameter('author', array())); - $author_value = mpull($author_value, 'getFullName', 'getPHID'); $owner_value = array_select_keys( $handles, $query->getParameter('owner', array())); - $owner_value = mpull($owner_value, 'getFullName', 'getPHID'); $subscribers_value = array_select_keys( $handles, $query->getParameter('subscribers', array())); - $subscribers_value = mpull($subscribers_value, 'getFullName', 'getPHID'); $project_value = array_select_keys( $handles, $query->getParameter('project', array())); - $project_value = mpull($project_value, 'getFullName', 'getPHID'); $search_form = new AphrontFormView(); $search_form ->setUser($user) ->setAction('/search/') ->appendChild( phutil_tag( 'input', array( 'type' => 'hidden', 'name' => 'jump', 'value' => 'no', ))) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Search') ->setName('query') ->setValue($query->getQuery())) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Document Type') ->setName('type') ->setOptions($options) ->setValue($query->getParameter('type'))) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Document Status') ->setName('open') ->setOptions($status_options) ->setValue($query->getParameter('open'))) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('author') ->setLabel('Author') ->setDatasource('/typeahead/common/users/') ->setValue($author_value)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('owner') ->setLabel('Owner') ->setDatasource('/typeahead/common/searchowner/') ->setValue($owner_value) ->setCaption( 'Tip: search for "Up For Grabs" to find unowned documents.')) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('subscribers') ->setLabel('Subscribers') ->setDatasource('/typeahead/common/users/') ->setValue($subscribers_value)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setName('project') ->setLabel('Project') ->setDatasource('/typeahead/common/projects/') ->setValue($project_value)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Search')); $search_panel = new AphrontListFilterView(); $search_panel->appendChild($search_form); require_celerity_resource('phabricator-search-results-css'); if ($query->getID()) { $limit = 20; $pager = new AphrontPagerView(); $pager->setURI($request->getRequestURI(), 'page'); $pager->setPageSize($limit); $pager->setOffset($request->getInt('page')); $query->setParameter('limit', $limit + 1); $query->setParameter('offset', $pager->getOffset()); $engine = PhabricatorSearchEngineSelector::newSelector()->newEngine(); $results = $engine->executeSearch($query); $results = $pager->sliceResults($results); // If there are any objects which match the query by name, and we're // not paging through the results, prefix the results with the named // objects. if (!$request->getInt('page')) { $named = id(new PhabricatorObjectQuery()) ->setViewer($user) ->withNames(array($query->getQuery())) ->execute(); if ($named) { $results = array_merge(array_keys($named), $results); } } if ($results) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($user) ->withPHIDs($results) ->execute(); $objects = id(new PhabricatorObjectQuery()) ->setViewer($user) ->withPHIDs($results) ->execute(); $results = array(); foreach ($handles as $phid => $handle) { $view = id(new PhabricatorSearchResultView()) ->setHandle($handle) ->setQuery($query) ->setObject(idx($objects, $phid)); $results[] = $view->render(); } $results = hsprintf( '
'. '%s'. '
%s
'. '
', phutil_implode_html("\n", $results), $pager->render()); } else { $results = hsprintf( '
'. '

No search results.

'. '
'); } $results = id(new PHUIBoxView()) ->addMargin(PHUI::MARGIN_LARGE) ->addPadding(PHUI::PADDING_LARGE) ->setShadow(true) ->appendChild($results) ->addClass('phabricator-search-result-box'); } else { $results = null; } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addCrumb( id(new PhabricatorCrumbView()) ->setName(pht('Search'))); return $this->buildApplicationPage( array( $crumbs, $search_panel, $results, ), array( 'title' => pht('Search Results'), 'device' => true, )); } } diff --git a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php index 39bac60ae2..f3693962f6 100644 --- a/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php +++ b/src/applications/slowvote/query/PhabricatorSlowvoteSearchEngine.php @@ -1,91 +1,90 @@ setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter('voted', $request->getBool('voted')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorSlowvoteQuery()) ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array())); if ($saved->getParameter('voted')) { $query->withVotesByViewer(true); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $phids = $saved_query->getParameter('authorPHIDs', array()); - $handles = id(new PhabricatorHandleQuery()) + $author_handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($phids) ->execute(); - $author_tokens = mpull($handles, 'getFullName', 'getPHID'); $voted = $saved_query->getParameter('voted', false); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/users/') ->setName('authors') ->setLabel(pht('Authors')) - ->setValue($author_tokens)) + ->setValue($author_handles)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'voted', 1, pht("Show only polls I've voted in."), $voted)); } protected function getURI($path) { return '/vote/'.$path; } public function getBuiltinQueryNames() { $names = array( 'all' => pht('All Polls'), ); if ($this->requireViewer()->isLoggedIn()) { $names['authored'] = pht('Authored'); $names['voted'] = pht('Voted In'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': return $query; case 'authored': return $query->setParameter( 'authorPHIDs', array($this->requireViewer()->getPHID())); case 'voted': return $query->setParameter('voted', true); } return parent::buildSavedQueryFromBuiltin($query_key); } } diff --git a/src/view/form/control/AphrontFormTokenizerControl.php b/src/view/form/control/AphrontFormTokenizerControl.php index c9a56f96c4..bf1773ef28 100644 --- a/src/view/form/control/AphrontFormTokenizerControl.php +++ b/src/view/form/control/AphrontFormTokenizerControl.php @@ -1,113 +1,106 @@ datasource = $datasource; return $this; } public function setDisableBehavior($disable) { $this->disableBehavior = $disable; return $this; } protected function getCustomControlClass() { return 'aphront-form-control-tokenizer'; } public function setLimit($limit) { $this->limit = $limit; return $this; } public function setPlaceholder($placeholder) { $this->placeholder = $placeholder; return $this; } protected function renderInput() { $name = $this->getName(); $values = nonempty($this->getValue(), array()); - // TODO: Convert tokenizers to always take raw handles. For now, we - // accept either a list of handles or a `map`. - try { - assert_instances_of($values, 'PhabricatorObjectHandle'); - $values = mpull($values, 'getFullName', 'getPHID'); - } catch (InvalidArgumentException $ex) { - // Ignore this, just use the values as provided. - } - + assert_instances_of($values, 'PhabricatorObjectHandle'); + $values = mpull($values, 'getFullName', 'getPHID'); if ($this->getID()) { $id = $this->getID(); } else { $id = celerity_generate_unique_node_id(); } if (!$this->placeholder) { $this->placeholder = $this->getDefaultPlaceholder(); } $template = new AphrontTokenizerTemplateView(); $template->setName($name); $template->setID($id); $template->setValue($values); $username = null; if ($this->user) { $username = $this->user->getUsername(); } if (!$this->disableBehavior) { Javelin::initBehavior('aphront-basic-tokenizer', array( 'id' => $id, 'src' => $this->datasource, 'value' => $values, 'limit' => $this->limit, 'ondemand' => PhabricatorEnv::getEnvConfig('tokenizer.ondemand'), 'username' => $username, 'placeholder' => $this->placeholder, )); } return $template->render(); } private function getDefaultPlaceholder() { $datasource = $this->datasource; $matches = null; if (!preg_match('@^/typeahead/common/(.*)/$@', $datasource, $matches)) { return null; } $request = $matches[1]; $map = array( 'users' => pht('Type a user name...'), 'authors' => pht('Type a user name...'), 'usersorprojects' => pht('Type a user or project name...'), 'searchowner' => pht('Type a user name...'), 'accounts' => pht('Type a user name...'), 'mailable' => pht('Type a user or mailing list...'), 'allmailable' => pht('Type a user or mailing list...'), 'searchproject' => pht('Type a project name...'), 'projects' => pht('Type a project name...'), 'repositories' => pht('Type a repository name...'), 'packages' => pht('Type a package name...'), 'arcanistproject' => pht('Type an arc project name...'), 'accountsorprojects' => pht('Type a user or project name...'), ); return idx($map, $request); } }