2013-04-14 15:53:20 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents an abstract search engine for an application. It supports
|
|
|
|
* creating and storing saved queries.
|
|
|
|
*
|
2013-05-27 22:42:18 +02:00
|
|
|
* @task builtin Builtin Queries
|
2013-05-27 22:42:44 +02:00
|
|
|
* @task uri Query URIs
|
2013-05-31 02:32:12 +02:00
|
|
|
* @task dates Date Filters
|
2013-05-27 22:42:18 +02:00
|
|
|
*
|
2013-04-14 15:53:20 +02:00
|
|
|
* @group search
|
|
|
|
*/
|
|
|
|
abstract class PhabricatorApplicationSearchEngine {
|
|
|
|
|
2013-05-27 22:42:18 +02:00
|
|
|
private $viewer;
|
2013-05-31 02:32:12 +02:00
|
|
|
private $errors = array();
|
2013-05-27 22:42:18 +02:00
|
|
|
|
|
|
|
public function setViewer(PhabricatorUser $viewer) {
|
|
|
|
$this->viewer = $viewer;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function requireViewer() {
|
|
|
|
if (!$this->viewer) {
|
|
|
|
throw new Exception("Call setViewer() before using an engine!");
|
|
|
|
}
|
|
|
|
return $this->viewer;
|
|
|
|
}
|
|
|
|
|
2013-04-14 15:53:20 +02:00
|
|
|
/**
|
|
|
|
* Create a saved query object from the request.
|
|
|
|
*
|
|
|
|
* @param AphrontRequest The search request.
|
|
|
|
* @return PhabricatorSavedQuery
|
|
|
|
*/
|
|
|
|
abstract public function buildSavedQueryFromRequest(
|
|
|
|
AphrontRequest $request);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes the saved query.
|
|
|
|
*
|
|
|
|
* @param PhabricatorSavedQuery The saved query to operate on.
|
|
|
|
* @return The result of the query.
|
|
|
|
*/
|
|
|
|
abstract public function buildQueryFromSavedQuery(
|
|
|
|
PhabricatorSavedQuery $saved);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Builds the search form using the request.
|
|
|
|
*
|
2013-05-27 22:43:47 +02:00
|
|
|
* @param AphrontFormView Form to populate.
|
2013-04-14 15:53:20 +02:00
|
|
|
* @param PhabricatorSavedQuery The query from which to build the form.
|
|
|
|
* @return void
|
|
|
|
*/
|
2013-05-27 22:43:47 +02:00
|
|
|
abstract public function buildSearchForm(
|
|
|
|
AphrontFormView $form,
|
|
|
|
PhabricatorSavedQuery $query);
|
2013-04-14 15:53:20 +02:00
|
|
|
|
2013-05-31 02:32:12 +02:00
|
|
|
public function getErrors() {
|
|
|
|
return $this->errors;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function addError($error) {
|
|
|
|
$this->errors[] = $error;
|
|
|
|
return $this;
|
|
|
|
}
|
2013-05-27 22:41:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Return an application URI corresponding to the results page of a query.
|
|
|
|
* Normally, this is something like `/application/query/QUERYKEY/`.
|
|
|
|
*
|
2013-05-27 22:43:02 +02:00
|
|
|
* @param string The query key to build a URI for.
|
|
|
|
* @return string URI where the query can be executed.
|
2013-05-27 22:42:44 +02:00
|
|
|
* @task uri
|
2013-05-27 22:41:39 +02:00
|
|
|
*/
|
2013-05-30 23:09:02 +02:00
|
|
|
public function getQueryResultsPageURI($query_key) {
|
|
|
|
return $this->getURI('query/'.$query_key.'/');
|
|
|
|
}
|
2013-05-27 22:42:18 +02:00
|
|
|
|
|
|
|
|
2013-05-27 22:42:44 +02:00
|
|
|
/**
|
|
|
|
* Return an application URI for query management. This is used when, e.g.,
|
|
|
|
* a query deletion operation is cancelled.
|
|
|
|
*
|
|
|
|
* @return string URI where queries can be managed.
|
|
|
|
* @task uri
|
|
|
|
*/
|
2013-05-30 23:09:02 +02:00
|
|
|
public function getQueryManagementURI() {
|
|
|
|
return $this->getURI('query/edit/');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return the URI to a path within the application. Used to construct default
|
|
|
|
* URIs for management and results.
|
|
|
|
*
|
|
|
|
* @return string URI to path.
|
|
|
|
* @task uri
|
|
|
|
*/
|
|
|
|
abstract protected function getURI($path);
|
2013-05-27 22:42:44 +02:00
|
|
|
|
|
|
|
|
2013-05-27 22:42:18 +02:00
|
|
|
public function newSavedQuery() {
|
|
|
|
return id(new PhabricatorSavedQuery())
|
|
|
|
->setEngineClassName(get_class($this));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-06-05 17:41:43 +02:00
|
|
|
public function addNavigationItems(PHUIListView $menu) {
|
2013-05-30 23:09:02 +02:00
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
|
2013-05-31 19:51:05 +02:00
|
|
|
$menu->newLabel(pht('Queries'));
|
2013-05-30 23:09:02 +02:00
|
|
|
|
2013-06-05 14:28:25 +02:00
|
|
|
$named_queries = $this->loadEnabledNamedQueries();
|
2013-05-30 23:09:02 +02:00
|
|
|
|
|
|
|
foreach ($named_queries as $query) {
|
|
|
|
$key = $query->getQueryKey();
|
|
|
|
$uri = $this->getQueryResultsPageURI($key);
|
2013-05-31 19:51:05 +02:00
|
|
|
$menu->newLink($query->getQueryName(), $uri, 'query/'.$key);
|
2013-05-30 23:09:02 +02:00
|
|
|
}
|
|
|
|
|
2013-05-31 19:51:20 +02:00
|
|
|
if ($viewer->isLoggedIn()) {
|
|
|
|
$manage_uri = $this->getQueryManagementURI();
|
|
|
|
$menu->newLink(pht('Edit Queries...'), $manage_uri, 'query/edit');
|
|
|
|
}
|
2013-05-30 23:09:02 +02:00
|
|
|
|
2013-05-31 19:51:05 +02:00
|
|
|
$menu->newLabel(pht('Search'));
|
2013-05-30 23:09:02 +02:00
|
|
|
$advanced_uri = $this->getQueryResultsPageURI('advanced');
|
2013-05-31 19:51:05 +02:00
|
|
|
$menu->newLink(pht('Advanced Search'), $advanced_uri, 'query/advanced');
|
2013-05-30 23:09:02 +02:00
|
|
|
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-06-05 14:28:25 +02:00
|
|
|
public function loadAllNamedQueries() {
|
|
|
|
$viewer = $this->requireViewer();
|
|
|
|
|
|
|
|
$named_queries = id(new PhabricatorNamedQueryQuery())
|
|
|
|
->setViewer($viewer)
|
|
|
|
->withUserPHIDs(array($viewer->getPHID()))
|
|
|
|
->withEngineClassNames(array(get_class($this)))
|
|
|
|
->execute();
|
|
|
|
$named_queries = mpull($named_queries, null, 'getQueryKey');
|
|
|
|
|
|
|
|
$builtin = $this->getBuiltinQueries($viewer);
|
|
|
|
$builtin = mpull($builtin, null, 'getQueryKey');
|
|
|
|
|
|
|
|
foreach ($named_queries as $key => $named_query) {
|
|
|
|
if ($named_query->getIsBuiltin()) {
|
|
|
|
if (isset($builtin[$key])) {
|
|
|
|
$named_queries[$key]->setQueryName($builtin[$key]->getQueryName());
|
|
|
|
unset($builtin[$key]);
|
|
|
|
} else {
|
|
|
|
unset($named_queries[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unset($builtin[$key]);
|
|
|
|
}
|
|
|
|
|
2013-06-06 01:22:27 +02:00
|
|
|
$named_queries = msort($named_queries, 'getSortKey');
|
|
|
|
|
2013-06-05 14:28:25 +02:00
|
|
|
return $named_queries + $builtin;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function loadEnabledNamedQueries() {
|
|
|
|
$named_queries = $this->loadAllNamedQueries();
|
|
|
|
foreach ($named_queries as $key => $named_query) {
|
|
|
|
if ($named_query->getIsBuiltin() && $named_query->getIsDisabled()) {
|
|
|
|
unset($named_queries[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return $named_queries;
|
|
|
|
}
|
|
|
|
|
2013-05-30 23:09:02 +02:00
|
|
|
|
2013-05-27 22:42:18 +02:00
|
|
|
/* -( Builtin Queries )---------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task builtin
|
|
|
|
*/
|
|
|
|
public function getBuiltinQueries() {
|
|
|
|
$names = $this->getBuiltinQueryNames();
|
|
|
|
|
|
|
|
$queries = array();
|
2013-06-06 01:22:27 +02:00
|
|
|
$sequence = 0;
|
2013-05-27 22:42:18 +02:00
|
|
|
foreach ($names as $key => $name) {
|
|
|
|
$queries[$key] = id(new PhabricatorNamedQuery())
|
2013-06-06 01:22:27 +02:00
|
|
|
->setUserPHID($this->requireViewer()->getPHID())
|
|
|
|
->setEngineClassName(get_class($this))
|
2013-05-27 22:42:18 +02:00
|
|
|
->setQueryName($name)
|
|
|
|
->setQueryKey($key)
|
2013-06-06 01:22:27 +02:00
|
|
|
->setSequence((1 << 24) + $sequence++)
|
|
|
|
->setIsBuiltin(true);
|
2013-05-27 22:42:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $queries;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-29 23:00:52 +02:00
|
|
|
/**
|
|
|
|
* @task builtin
|
|
|
|
*/
|
|
|
|
public function getBuiltinQuery($query_key) {
|
|
|
|
if (!$this->isBuiltinQuery($query_key)) {
|
|
|
|
throw new Exception("'{$query_key}' is not a builtin!");
|
|
|
|
}
|
|
|
|
return idx($this->getBuiltinQueries(), $query_key);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-05-27 22:42:18 +02:00
|
|
|
/**
|
|
|
|
* @task builtin
|
|
|
|
*/
|
|
|
|
protected function getBuiltinQueryNames() {
|
|
|
|
return array();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task builtin
|
|
|
|
*/
|
|
|
|
public function isBuiltinQuery($query_key) {
|
|
|
|
$builtins = $this->getBuiltinQueries();
|
|
|
|
return isset($builtins[$query_key]);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task builtin
|
|
|
|
*/
|
|
|
|
public function buildSavedQueryFromBuiltin($query_key) {
|
|
|
|
throw new Exception("Builtin '{$query_key}' is not supported!");
|
|
|
|
}
|
2013-05-31 02:32:12 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* -( Dates )-------------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task dates
|
|
|
|
*/
|
|
|
|
protected function parseDateTime($date_time) {
|
|
|
|
if (!strlen($date_time)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-06-03 21:58:11 +02:00
|
|
|
return PhabricatorTime::parseLocalTime($date_time, $this->requireViewer());
|
2013-05-31 02:32:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @task dates
|
|
|
|
*/
|
|
|
|
protected function buildDateRange(
|
|
|
|
AphrontFormView $form,
|
|
|
|
PhabricatorSavedQuery $saved_query,
|
|
|
|
$start_key,
|
|
|
|
$start_name,
|
|
|
|
$end_key,
|
|
|
|
$end_name) {
|
|
|
|
|
|
|
|
$start_str = $saved_query->getParameter($start_key);
|
|
|
|
$start = null;
|
|
|
|
if (strlen($start_str)) {
|
|
|
|
$start = $this->parseDateTime($start_str);
|
|
|
|
if (!$start) {
|
|
|
|
$this->addError(
|
|
|
|
pht(
|
|
|
|
'"%s" date can not be parsed.',
|
|
|
|
$start_name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$end_str = $saved_query->getParameter($end_key);
|
|
|
|
$end = null;
|
|
|
|
if (strlen($end_str)) {
|
|
|
|
$end = $this->parseDateTime($end_str);
|
|
|
|
if (!$end) {
|
|
|
|
$this->addError(
|
|
|
|
pht(
|
|
|
|
'"%s" date can not be parsed.',
|
|
|
|
$end_name));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($start && $end && ($start >= $end)) {
|
|
|
|
$this->addError(
|
|
|
|
pht(
|
|
|
|
'"%s" must be a date before "%s".',
|
|
|
|
$start_name,
|
|
|
|
$end_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
$form
|
|
|
|
->appendChild(
|
|
|
|
id(new PHUIFormFreeformDateControl())
|
|
|
|
->setName($start_key)
|
|
|
|
->setLabel($start_name)
|
|
|
|
->setValue($start_str))
|
|
|
|
->appendChild(
|
|
|
|
id(new AphrontFormTextControl())
|
|
|
|
->setName($end_key)
|
|
|
|
->setLabel($end_name)
|
|
|
|
->setValue($end_str));
|
|
|
|
}
|
2013-07-01 21:36:34 +02:00
|
|
|
|
|
|
|
|
|
|
|
/* -( Pagination )--------------------------------------------------------- */
|
|
|
|
|
|
|
|
|
|
|
|
public function getPageSize(PhabricatorSavedQuery $saved) {
|
|
|
|
return $saved->getParameter('limit', 100);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-04-14 15:53:20 +02:00
|
|
|
}
|