mirror of
https://we.phorge.it/source/phorge.git
synced 2025-02-22 11:39:03 +01:00
Summary: Fixes T4239. Currently, if you go to `/maniphest/?authors=alincoln`, operations dependent on the query key (like "Save Custom Query..." and "Export to Excel...") don't have a query key to work with. Make sure they have one. Also remove a stray `phlog()`. Test Plan: "Save Custom Query...", etc., now work on GET queries. Reviewers: btrahan, chad Reviewed By: btrahan CC: aran Maniphest Tasks: T4239 Differential Revision: https://secure.phabricator.com/D7777
380 lines
10 KiB
PHP
380 lines
10 KiB
PHP
<?php
|
|
|
|
final class PhabricatorApplicationSearchController
|
|
extends PhabricatorSearchBaseController {
|
|
|
|
private $searchEngine;
|
|
private $navigation;
|
|
private $queryKey;
|
|
private $preface;
|
|
|
|
public function setPreface($preface) {
|
|
$this->preface = $preface;
|
|
return $this;
|
|
}
|
|
|
|
public function getPreface() {
|
|
return $this->preface;
|
|
}
|
|
|
|
public function setQueryKey($query_key) {
|
|
$this->queryKey = $query_key;
|
|
return $this;
|
|
}
|
|
|
|
protected function getQueryKey() {
|
|
return $this->queryKey;
|
|
}
|
|
|
|
public function setNavigation(AphrontSideNavFilterView $navigation) {
|
|
$this->navigation = $navigation;
|
|
return $this;
|
|
}
|
|
|
|
protected function getNavigation() {
|
|
return $this->navigation;
|
|
}
|
|
|
|
public function setSearchEngine(
|
|
PhabricatorApplicationSearchEngine $search_engine) {
|
|
$this->searchEngine = $search_engine;
|
|
return $this;
|
|
}
|
|
|
|
protected function getSearchEngine() {
|
|
return $this->searchEngine;
|
|
}
|
|
|
|
protected function validateDelegatingController() {
|
|
$parent = $this->getDelegatingController();
|
|
|
|
if (!$parent) {
|
|
throw new Exception(
|
|
"You must delegate to this controller, not invoke it directly.");
|
|
}
|
|
|
|
$engine = $this->getSearchEngine();
|
|
if (!$engine) {
|
|
throw new Exception(
|
|
"Call setEngine() before delegating to this controller!");
|
|
}
|
|
|
|
$nav = $this->getNavigation();
|
|
if (!$nav) {
|
|
throw new Exception(
|
|
"Call setNavigation() before delegating to this controller!");
|
|
}
|
|
|
|
$engine->setViewer($this->getRequest()->getUser());
|
|
|
|
$parent = $this->getDelegatingController();
|
|
$interface = 'PhabricatorApplicationSearchResultsControllerInterface';
|
|
if (!$parent instanceof $interface) {
|
|
throw new Exception(
|
|
"Delegating controller must implement '{$interface}'.");
|
|
}
|
|
}
|
|
|
|
public function processRequest() {
|
|
$this->validateDelegatingController();
|
|
|
|
$key = $this->getQueryKey();
|
|
if ($key == 'edit') {
|
|
return $this->processEditRequest();
|
|
} else {
|
|
return $this->processSearchRequest();
|
|
}
|
|
}
|
|
|
|
private function processSearchRequest() {
|
|
$parent = $this->getDelegatingController();
|
|
$request = $this->getRequest();
|
|
$user = $request->getUser();
|
|
$engine = $this->getSearchEngine();
|
|
$nav = $this->getNavigation();
|
|
|
|
if ($request->isFormPost()) {
|
|
$saved_query = $engine->buildSavedQueryFromRequest($request);
|
|
$this->saveQuery($saved_query);
|
|
return id(new AphrontRedirectResponse())->setURI(
|
|
$engine->getQueryResultsPageURI($saved_query->getQueryKey()).'#R');
|
|
}
|
|
|
|
$named_query = null;
|
|
$run_query = true;
|
|
$query_key = $this->queryKey;
|
|
if ($this->queryKey == 'advanced') {
|
|
$run_query = false;
|
|
$query_key = $request->getStr('query');
|
|
} else if (!strlen($this->queryKey)) {
|
|
$found_query_data = false;
|
|
|
|
if ($request->isHTTPGet()) {
|
|
// If this is a GET request and it has some query data, don't
|
|
// do anything unless it's only before= or after=. We'll build and
|
|
// execute a query from it below. This allows external tools to build
|
|
// URIs like "/query/?users=a,b".
|
|
$pt_data = $request->getPassthroughRequestData();
|
|
|
|
foreach ($pt_data as $pt_key => $pt_value) {
|
|
if ($pt_key != 'before' && $pt_key != 'after') {
|
|
$found_query_data = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$found_query_data) {
|
|
// Otherwise, there's no query data so just run the user's default
|
|
// query for this application.
|
|
$query_key = head_key($engine->loadEnabledNamedQueries());
|
|
}
|
|
}
|
|
|
|
if ($engine->isBuiltinQuery($query_key)) {
|
|
$saved_query = $engine->buildSavedQueryFromBuiltin($query_key);
|
|
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
|
|
} else if ($query_key) {
|
|
$saved_query = id(new PhabricatorSavedQueryQuery())
|
|
->setViewer($user)
|
|
->withQueryKeys(array($query_key))
|
|
->executeOne();
|
|
|
|
if (!$saved_query) {
|
|
return new Aphront404Response();
|
|
}
|
|
|
|
$named_query = idx($engine->loadEnabledNamedQueries(), $query_key);
|
|
} else {
|
|
$saved_query = $engine->buildSavedQueryFromRequest($request);
|
|
|
|
// Save the query to generate a query key, so "Save Custom Query..." and
|
|
// other features like Maniphest's "Export..." work correctly.
|
|
$this->saveQuery($saved_query);
|
|
}
|
|
|
|
$nav->selectFilter(
|
|
'query/'.$saved_query->getQueryKey(),
|
|
'query/advanced');
|
|
|
|
$form = id(new AphrontFormView())
|
|
->setUser($user);
|
|
|
|
$engine->buildSearchForm($form, $saved_query);
|
|
|
|
$errors = $engine->getErrors();
|
|
if ($errors) {
|
|
$run_query = false;
|
|
$errors = id(new AphrontErrorView())
|
|
->setTitle(pht('Query Errors'))
|
|
->setErrors($errors);
|
|
}
|
|
|
|
$submit = id(new AphrontFormSubmitControl())
|
|
->setValue(pht('Execute Query'));
|
|
|
|
if ($run_query && !$named_query && $user->isLoggedIn()) {
|
|
$submit->addCancelButton(
|
|
'/search/edit/'.$saved_query->getQueryKey().'/',
|
|
pht('Save Custom Query...'));
|
|
}
|
|
|
|
$form->appendChild($submit);
|
|
$filter_view = id(new AphrontListFilterView())->appendChild($form);
|
|
|
|
if ($run_query && $named_query) {
|
|
if ($named_query->getIsBuiltin()) {
|
|
$description = pht(
|
|
'Showing results for query "%s".',
|
|
$named_query->getQueryName());
|
|
} else {
|
|
$description = pht(
|
|
'Showing results for saved query "%s".',
|
|
$named_query->getQueryName());
|
|
}
|
|
|
|
$filter_view->setCollapsed(
|
|
pht('Edit Query...'),
|
|
pht('Hide Query'),
|
|
$description,
|
|
$this->getApplicationURI('query/advanced/?query='.$query_key));
|
|
}
|
|
|
|
if ($this->getPreface()) {
|
|
$nav->appendChild($this->getPreface());
|
|
}
|
|
|
|
$nav->appendChild($filter_view);
|
|
|
|
if ($run_query) {
|
|
$nav->appendChild(
|
|
$anchor = id(new PhabricatorAnchorView())
|
|
->setAnchorName('R'));
|
|
|
|
$query = $engine->buildQueryFromSavedQuery($saved_query);
|
|
|
|
$pager = new AphrontCursorPagerView();
|
|
$pager->readFromRequest($request);
|
|
$page_size = $engine->getPageSize($saved_query);
|
|
if (is_finite($page_size)) {
|
|
$pager->setPageSize($page_size);
|
|
} else {
|
|
// Consider an INF pagesize to mean a large finite pagesize.
|
|
|
|
// TODO: It would be nice to handle this more gracefully, but math
|
|
// with INF seems to vary across PHP versions, systems, and runtimes.
|
|
$pager->setPageSize(0xFFFF);
|
|
}
|
|
$objects = $query->setViewer($request->getUser())
|
|
->executeWithCursorPager($pager);
|
|
|
|
$list = $parent->renderResultsList($objects, $saved_query);
|
|
|
|
$nav->appendChild($list);
|
|
|
|
// TODO: This is a bit hacky.
|
|
if ($list instanceof PHUIObjectItemListView) {
|
|
$list->setNoDataString(pht("No results found for this query."));
|
|
$list->setPager($pager);
|
|
} else {
|
|
if ($pager->willShowPagingControls()) {
|
|
$pager_box = id(new PHUIBoxView())
|
|
->addPadding(PHUI::PADDING_MEDIUM)
|
|
->addMargin(PHUI::MARGIN_LARGE)
|
|
->setShadow(true)
|
|
->appendChild($pager);
|
|
$nav->appendChild($pager_box);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($errors) {
|
|
$nav->appendChild($errors);
|
|
}
|
|
|
|
if ($named_query) {
|
|
$title = pht('Query: %s', $named_query->getQueryName());
|
|
} else {
|
|
$title = pht('Advanced Search');
|
|
}
|
|
|
|
$crumbs = $parent
|
|
->buildApplicationCrumbs()
|
|
->addCrumb(
|
|
id(new PhabricatorCrumbView())
|
|
->setName(pht("Search")));
|
|
|
|
$nav->setCrumbs($crumbs);
|
|
|
|
return $this->buildApplicationPage(
|
|
$nav,
|
|
array(
|
|
'title' => $title,
|
|
'device' => true,
|
|
));
|
|
}
|
|
|
|
private function processEditRequest() {
|
|
$parent = $this->getDelegatingController();
|
|
$request = $this->getRequest();
|
|
$user = $request->getUser();
|
|
$engine = $this->getSearchEngine();
|
|
$nav = $this->getNavigation();
|
|
|
|
$named_queries = $engine->loadAllNamedQueries();
|
|
|
|
$list_id = celerity_generate_unique_node_id();
|
|
|
|
$list = new PHUIObjectItemListView();
|
|
$list->setUser($user);
|
|
$list->setID($list_id);
|
|
|
|
Javelin::initBehavior(
|
|
'search-reorder-queries',
|
|
array(
|
|
'listID' => $list_id,
|
|
'orderURI' => '/search/order/'.get_class($engine).'/',
|
|
));
|
|
|
|
foreach ($named_queries as $named_query) {
|
|
$class = get_class($engine);
|
|
$key = $named_query->getQueryKey();
|
|
|
|
$item = id(new PHUIObjectItemView())
|
|
->setHeader($named_query->getQueryName())
|
|
->setHref($engine->getQueryResultsPageURI($key));
|
|
|
|
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
|
|
$icon = 'new';
|
|
} else {
|
|
$icon = 'delete';
|
|
}
|
|
|
|
$item->addAction(
|
|
id(new PHUIListItemView())
|
|
->setIcon($icon)
|
|
->setHref('/search/delete/'.$key.'/'.$class.'/')
|
|
->setWorkflow(true));
|
|
|
|
if ($named_query->getIsBuiltin()) {
|
|
if ($named_query->getIsDisabled()) {
|
|
$item->addIcon('delete-grey', pht('Disabled'));
|
|
$item->setDisabled(true);
|
|
} else {
|
|
$item->addIcon('lock-grey', pht('Builtin'));
|
|
}
|
|
} else {
|
|
$item->addAction(
|
|
id(new PHUIListItemView())
|
|
->setIcon('edit')
|
|
->setHref('/search/edit/'.$key.'/'));
|
|
}
|
|
|
|
$item->setGrippable(true);
|
|
$item->addSigil('named-query');
|
|
$item->setMetadata(
|
|
array(
|
|
'queryKey' => $named_query->getQueryKey(),
|
|
));
|
|
|
|
$list->addItem($item);
|
|
}
|
|
|
|
$list->setNoDataString(pht('No saved queries.'));
|
|
|
|
$crumbs = $parent
|
|
->buildApplicationCrumbs()
|
|
->addCrumb(
|
|
id(new PhabricatorCrumbView())
|
|
->setName(pht("Saved Queries"))
|
|
->setHref($engine->getQueryManagementURI()));
|
|
|
|
$nav->selectFilter('query/edit');
|
|
$nav->setCrumbs($crumbs);
|
|
$nav->appendChild($list);
|
|
|
|
return $parent->buildApplicationPage(
|
|
$nav,
|
|
array(
|
|
'title' => pht("Saved Queries"),
|
|
'device' => true,
|
|
));
|
|
}
|
|
|
|
private function saveQuery(PhabricatorSavedQuery $query) {
|
|
$query->setEngineClassName(get_class($this->getSearchEngine()));
|
|
|
|
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
|
try {
|
|
$query->save();
|
|
} catch (AphrontQueryDuplicateKeyException $ex) {
|
|
// Ignore, this is just a repeated search.
|
|
}
|
|
unset($unguarded);
|
|
}
|
|
|
|
protected function buildApplicationMenu() {
|
|
return $this->getDelegatingController()->buildApplicationMenu();
|
|
}
|
|
|
|
}
|