Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php
index 4b4b621895..bbc01164b7 100644
--- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php
+++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php
@@ -1,150 +1,153 @@
<?php
final class PhabricatorPasteSearchEngine
extends PhabricatorApplicationSearchEngine {
public function getResultTypeDescription() {
return pht('Pastes');
}
public function getApplicationClassName() {
return 'PhabricatorPasteApplication';
}
- public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ public function buildQueryFromParameters(array $map) {
$query = id(new PhabricatorPasteQuery())
- ->needContent(true)
- ->withAuthorPHIDs($saved->getParameter('authorPHIDs', array()))
- ->withLanguages($saved->getParameter('languages', array()));
+ ->needContent(true);
- $start = $this->parseDateTime($saved->getParameter('createdStart'));
- $end = $this->parseDateTime($saved->getParameter('createdEnd'));
+ if ($map['authorPHIDs']) {
+ $query->withAuthorPHIDs($map['authorPHIDs']);
+ }
+
+ if ($map['languages']) {
+ $query->withLanguages($map['languages']);
+ }
- if ($start) {
- $query->withDateCreatedAfter($start);
+ if ($map['createdStart']) {
+ $query->withDateCreatedAfter($map['createdStart']);
}
- if ($end) {
- $query->withDateCreatedBefore($end);
+ if ($map['createdEnd']) {
+ $query->withDateCreatedBefore($map['createdEnd']);
}
return $query;
}
protected function buildCustomSearchFields() {
return array(
id(new PhabricatorSearchUsersField())
->setAliases(array('authors'))
->setKey('authorPHIDs')
->setLabel(pht('Authors')),
id(new PhabricatorSearchStringListField())
->setKey('languages')
->setLabel(pht('Languages')),
id(new PhabricatorSearchDateField())
->setKey('createdStart')
->setLabel(pht('Created After')),
id(new PhabricatorSearchDateField())
->setKey('createdEnd')
->setLabel(pht('Created Before')),
);
}
protected function getURI($path) {
return '/paste/'.$path;
}
protected 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);
}
protected function getRequiredHandlePHIDsForResultList(
array $pastes,
PhabricatorSavedQuery $query) {
return mpull($pastes, 'getAuthorPHID');
}
protected function renderResultList(
array $pastes,
PhabricatorSavedQuery $query,
array $handles) {
assert_instances_of($pastes, 'PhabricatorPaste');
$viewer = $this->requireViewer();
$lang_map = PhabricatorEnv::getEnvConfig('pygments.dropdown-choices');
$list = new PHUIObjectItemListView();
$list->setUser($viewer);
foreach ($pastes as $paste) {
$created = phabricator_date($paste->getDateCreated(), $viewer);
$author = $handles[$paste->getAuthorPHID()]->renderLink();
$lines = phutil_split_lines($paste->getContent());
$preview = id(new PhabricatorSourceCodeView())
->setLimit(5)
->setLines($lines)
->setURI(new PhutilURI($paste->getURI()));
$source_code = phutil_tag(
'div',
array(
'class' => 'phabricator-source-code-summary',
),
$preview);
$created = phabricator_datetime($paste->getDateCreated(), $viewer);
$line_count = count($lines);
$line_count = pht(
'%s Line(s)',
new PhutilNumber($line_count));
$title = nonempty($paste->getTitle(), pht('(An Untitled Masterwork)'));
$item = id(new PHUIObjectItemView())
->setObjectName('P'.$paste->getID())
->setHeader($title)
->setHref('/P'.$paste->getID())
->setObject($paste)
->addByline(pht('Author: %s', $author))
->addIcon('none', $created)
->addIcon('none', $line_count)
->appendChild($source_code);
$lang_name = $paste->getLanguage();
if ($lang_name) {
$lang_name = idx($lang_map, $lang_name, $lang_name);
$item->addIcon('none', $lang_name);
}
$list->addItem($item);
}
return $list;
}
}
diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
index 7891ef5174..1f2c7a3b45 100644
--- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
+++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php
@@ -1,1075 +1,1091 @@
<?php
/**
* Represents an abstract search engine for an application. It supports
* creating and storing saved queries.
*
* @task construct Constructing Engines
* @task app Applications
* @task builtin Builtin Queries
* @task uri Query URIs
* @task dates Date Filters
* @task order Result Ordering
* @task read Reading Utilities
* @task exec Paging and Executing Queries
* @task render Rendering Results
*/
abstract class PhabricatorApplicationSearchEngine extends Phobject {
private $application;
private $viewer;
private $errors = array();
private $customFields = false;
private $request;
private $context;
const CONTEXT_LIST = 'list';
const CONTEXT_PANEL = 'panel';
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
protected function requireViewer() {
if (!$this->viewer) {
throw new PhutilInvalidStateException('setViewer');
}
return $this->viewer;
}
public function setContext($context) {
$this->context = $context;
return $this;
}
public function isPanelContext() {
return ($this->context == self::CONTEXT_PANEL);
}
public function canUseInPanelContext() {
return true;
}
public function saveQuery(PhabricatorSavedQuery $query) {
$query->setEngineClassName(get_class($this));
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
$query->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
// Ignore, this is just a repeated search.
}
unset($unguarded);
}
/**
* Create a saved query object from the request.
*
* @param AphrontRequest The search request.
* @return PhabricatorSavedQuery
*/
public function buildSavedQueryFromRequest(AphrontRequest $request) {
$fields = $this->buildSearchFields();
$viewer = $this->requireViewer();
$saved = new PhabricatorSavedQuery();
foreach ($fields as $field) {
$field->setViewer($viewer);
$value = $field->readValueFromRequest($request);
$saved->setParameter($field->getKey(), $value);
}
return $saved;
}
/**
* Executes the saved query.
*
* @param PhabricatorSavedQuery The saved query to operate on.
* @return The result of the query.
*/
- abstract public function buildQueryFromSavedQuery(
- PhabricatorSavedQuery $saved);
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $fields = $this->buildSearchFields();
+ $viewer = $this->requireViewer();
+
+ $parameters = array();
+ foreach ($fields as $field) {
+ $field->setViewer($viewer);
+ $field->readValueFromSavedQuery($saved);
+ $value = $field->getValueForQuery($field->getValue());
+ $parameters[$field->getKey()] = $value;
+ }
+
+ return $this->buildQueryFromParameters($parameters);
+ }
+
+ protected function buildQueryFromParameters(array $parameters) {
+ throw new PhutilMethodNotImplementedException();
+ }
/**
* Builds the search form using the request.
*
* @param AphrontFormView Form to populate.
* @param PhabricatorSavedQuery The query from which to build the form.
* @return void
*/
public function buildSearchForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$fields = $this->buildSearchFields();
$viewer = $this->requireViewer();
foreach ($fields as $field) {
$field->setViewer($viewer);
$field->readValueFromSavedQuery($saved);
}
foreach ($fields as $field) {
foreach ($field->getErrors() as $error) {
$this->addError(last($error));
}
}
foreach ($fields as $field) {
$field->appendToForm($form);
}
}
protected function buildSearchFields() {
$fields = array();
foreach ($this->buildCustomSearchFields() as $field) {
$fields[] = $field;
}
return $fields;
}
protected function buildCustomSearchFields() {
throw new PhutilMethodNotImplementedException();
}
public function getErrors() {
return $this->errors;
}
public function addError($error) {
$this->errors[] = $error;
return $this;
}
/**
* Return an application URI corresponding to the results page of a query.
* Normally, this is something like `/application/query/QUERYKEY/`.
*
* @param string The query key to build a URI for.
* @return string URI where the query can be executed.
* @task uri
*/
public function getQueryResultsPageURI($query_key) {
return $this->getURI('query/'.$query_key.'/');
}
/**
* 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
*/
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);
/**
* Return a human readable description of the type of objects this query
* searches for.
*
* For example, "Tasks" or "Commits".
*
* @return string Human-readable description of what this engine is used to
* find.
*/
abstract public function getResultTypeDescription();
public function newSavedQuery() {
return id(new PhabricatorSavedQuery())
->setEngineClassName(get_class($this));
}
public function addNavigationItems(PHUIListView $menu) {
$viewer = $this->requireViewer();
$menu->newLabel(pht('Queries'));
$named_queries = $this->loadEnabledNamedQueries();
foreach ($named_queries as $query) {
$key = $query->getQueryKey();
$uri = $this->getQueryResultsPageURI($key);
$menu->newLink($query->getQueryName(), $uri, 'query/'.$key);
}
if ($viewer->isLoggedIn()) {
$manage_uri = $this->getQueryManagementURI();
$menu->newLink(pht('Edit Queries...'), $manage_uri, 'query/edit');
}
$menu->newLabel(pht('Search'));
$advanced_uri = $this->getQueryResultsPageURI('advanced');
$menu->newLink(pht('Advanced Search'), $advanced_uri, 'query/advanced');
return $this;
}
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]);
}
$named_queries = msort($named_queries, 'getSortKey');
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;
}
protected function setQueryProjects(
PhabricatorCursorPagedPolicyAwareQuery $query,
PhabricatorSavedQuery $saved) {
$datasource = id(new PhabricatorProjectLogicalDatasource())
->setViewer($this->requireViewer());
$projects = $saved->getParameter('projects', array());
$constraints = $datasource->evaluateTokens($projects);
if ($constraints) {
$query->withEdgeLogicConstraints(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
$constraints);
}
}
/* -( Applications )------------------------------------------------------- */
protected function getApplicationURI($path = '') {
return $this->getApplication()->getApplicationURI($path);
}
protected function getApplication() {
if (!$this->application) {
$class = $this->getApplicationClassName();
$this->application = id(new PhabricatorApplicationQuery())
->setViewer($this->requireViewer())
->withClasses(array($class))
->withInstalled(true)
->executeOne();
if (!$this->application) {
throw new Exception(
pht(
'Application "%s" is not installed!',
$class));
}
}
return $this->application;
}
abstract public function getApplicationClassName();
/* -( Constructing Engines )----------------------------------------------- */
/**
* Load all available application search engines.
*
* @return list<PhabricatorApplicationSearchEngine> All available engines.
* @task construct
*/
public static function getAllEngines() {
$engines = id(new PhutilSymbolLoader())
->setAncestorClass(__CLASS__)
->loadObjects();
return $engines;
}
/**
* Get an engine by class name, if it exists.
*
* @return PhabricatorApplicationSearchEngine|null Engine, or null if it does
* not exist.
* @task construct
*/
public static function getEngineByClassName($class_name) {
return idx(self::getAllEngines(), $class_name);
}
/* -( Builtin Queries )---------------------------------------------------- */
/**
* @task builtin
*/
public function getBuiltinQueries() {
$names = $this->getBuiltinQueryNames();
$queries = array();
$sequence = 0;
foreach ($names as $key => $name) {
$queries[$key] = id(new PhabricatorNamedQuery())
->setUserPHID($this->requireViewer()->getPHID())
->setEngineClassName(get_class($this))
->setQueryName($name)
->setQueryKey($key)
->setSequence((1 << 24) + $sequence++)
->setIsBuiltin(true);
}
return $queries;
}
/**
* @task builtin
*/
public function getBuiltinQuery($query_key) {
if (!$this->isBuiltinQuery($query_key)) {
throw new Exception(pht("'%s' is not a builtin!", $query_key));
}
return idx($this->getBuiltinQueries(), $query_key);
}
/**
* @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(pht("Builtin '%s' is not supported!", $query_key));
}
/* -( Reading Utilities )--------------------------------------------------- */
/**
* Read a list of user PHIDs from a request in a flexible way. This method
* supports either of these forms:
*
* users[]=alincoln&users[]=htaft
* users=alincoln,htaft
*
* Additionally, users can be specified either by PHID or by name.
*
* The main goal of this flexibility is to allow external programs to generate
* links to pages (like "alincoln's open revisions") without needing to make
* API calls.
*
* @param AphrontRequest Request to read user PHIDs from.
* @param string Key to read in the request.
* @param list<const> Other permitted PHID types.
* @return list<phid> List of user PHIDs and selector functions.
* @task read
*/
protected function readUsersFromRequest(
AphrontRequest $request,
$key,
array $allow_types = array()) {
$list = $this->readListFromRequest($request, $key);
$phids = array();
$names = array();
$allow_types = array_fuse($allow_types);
$user_type = PhabricatorPeopleUserPHIDType::TYPECONST;
foreach ($list as $item) {
$type = phid_get_type($item);
if ($type == $user_type) {
$phids[] = $item;
} else if (isset($allow_types[$type])) {
$phids[] = $item;
} else {
if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) {
// If this is a function, pass it through unchanged; we'll evaluate
// it later.
$phids[] = $item;
} else {
$names[] = $item;
}
}
}
if ($names) {
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->requireViewer())
->withUsernames($names)
->execute();
foreach ($users as $user) {
$phids[] = $user->getPHID();
}
$phids = array_unique($phids);
}
return $phids;
}
/**
* Read a list of project PHIDs from a request in a flexible way.
*
* @param AphrontRequest Request to read user PHIDs from.
* @param string Key to read in the request.
* @return list<phid> List of projet PHIDs and selector functions.
* @task read
*/
protected function readProjectsFromRequest(AphrontRequest $request, $key) {
$list = $this->readListFromRequest($request, $key);
$phids = array();
$slugs = array();
$project_type = PhabricatorProjectProjectPHIDType::TYPECONST;
foreach ($list as $item) {
$type = phid_get_type($item);
if ($type == $project_type) {
$phids[] = $item;
} else {
if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) {
// If this is a function, pass it through unchanged; we'll evaluate
// it later.
$phids[] = $item;
} else {
$slugs[] = $item;
}
}
}
if ($slugs) {
$projects = id(new PhabricatorProjectQuery())
->setViewer($this->requireViewer())
->withSlugs($slugs)
->execute();
foreach ($projects as $project) {
$phids[] = $project->getPHID();
}
$phids = array_unique($phids);
}
return $phids;
}
/**
* Read a list of subscribers from a request in a flexible way.
*
* @param AphrontRequest Request to read PHIDs from.
* @param string Key to read in the request.
* @return list<phid> List of object PHIDs.
* @task read
*/
protected function readSubscribersFromRequest(
AphrontRequest $request,
$key) {
return $this->readUsersFromRequest(
$request,
$key,
array(
PhabricatorProjectProjectPHIDType::TYPECONST,
));
}
/**
* Read a list of generic PHIDs from a request in a flexible way. Like
* @{method:readUsersFromRequest}, this method supports either array or
* comma-delimited forms. Objects can be specified either by PHID or by
* object name.
*
* @param AphrontRequest Request to read PHIDs from.
* @param string Key to read in the request.
* @param list<const> Optional, list of permitted PHID types.
* @return list<phid> List of object PHIDs.
*
* @task read
*/
protected function readPHIDsFromRequest(
AphrontRequest $request,
$key,
array $allow_types = array()) {
$list = $this->readListFromRequest($request, $key);
$objects = id(new PhabricatorObjectQuery())
->setViewer($this->requireViewer())
->withNames($list)
->execute();
$list = mpull($objects, 'getPHID');
if (!$list) {
return array();
}
// If only certain PHID types are allowed, filter out all the others.
if ($allow_types) {
$allow_types = array_fuse($allow_types);
foreach ($list as $key => $phid) {
if (empty($allow_types[phid_get_type($phid)])) {
unset($list[$key]);
}
}
}
return $list;
}
/**
* Read a list of items from the request, in either array format or string
* format:
*
* list[]=item1&list[]=item2
* list=item1,item2
*
* This provides flexibility when constructing URIs, especially from external
* sources.
*
* @param AphrontRequest Request to read strings from.
* @param string Key to read in the request.
* @return list<string> List of values.
*/
protected function readListFromRequest(
AphrontRequest $request,
$key) {
$list = $request->getArr($key, null);
if ($list === null) {
$list = $request->getStrList($key);
}
if (!$list) {
return array();
}
return $list;
}
protected function readDateFromRequest(
AphrontRequest $request,
$key) {
$value = AphrontFormDateControlValue::newFromRequest($request, $key);
if ($value->isEmpty()) {
return null;
}
return $value->getDictionary();
}
protected function readBoolFromRequest(
AphrontRequest $request,
$key) {
if (!strlen($request->getStr($key))) {
return null;
}
return $request->getBool($key);
}
protected function getBoolFromQuery(PhabricatorSavedQuery $query, $key) {
$value = $query->getParameter($key);
if ($value === null) {
return $value;
}
return $value ? 'true' : 'false';
}
/* -( Dates )-------------------------------------------------------------- */
/**
* @task dates
*/
protected function parseDateTime($date_time) {
if (!strlen($date_time)) {
return null;
}
return PhabricatorTime::parseLocalTime($date_time, $this->requireViewer());
}
/**
* @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));
}
/* -( Result Ordering )---------------------------------------------------- */
/**
* Save order selection to a @{class:PhabricatorSavedQuery}.
*/
protected function saveQueryOrder(
PhabricatorSavedQuery $saved,
AphrontRequest $request) {
$saved->setParameter('order', $request->getStr('order'));
return $this;
}
/**
* Set query ordering from a saved value.
*/
protected function setQueryOrder(
PhabricatorCursorPagedPolicyAwareQuery $query,
PhabricatorSavedQuery $saved) {
$order = $saved->getParameter('order');
$builtin = $query->getBuiltinOrders();
if (strlen($order) && isset($builtin[$order])) {
$query->setOrder($order);
} else {
// If the order is invalid or not available, we choose the first
// builtin order. This isn't always the default order for the query,
// but is the first value in the "Order" dropdown, and makes the query
// behavior more consistent with the UI. In queries where the two
// orders differ, this order is the preferred order for humans.
$query->setOrder(head_key($builtin));
}
return $this;
}
protected function appendOrderFieldsToForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved,
PhabricatorCursorPagedPolicyAwareQuery $query) {
$orders = $query->getBuiltinOrders();
$orders = ipull($orders, 'name');
$form->appendControl(
id(new AphrontFormSelectControl())
->setLabel(pht('Order'))
->setName('order')
->setOptions($orders)
->setValue($saved->getParameter('order')));
}
/* -( Paging and Executing Queries )--------------------------------------- */
public function getPageSize(PhabricatorSavedQuery $saved) {
return $saved->getParameter('limit', 100);
}
public function shouldUseOffsetPaging() {
return false;
}
public function newPagerForSavedQuery(PhabricatorSavedQuery $saved) {
if ($this->shouldUseOffsetPaging()) {
$pager = new AphrontPagerView();
} else {
$pager = new AphrontCursorPagerView();
}
$page_size = $this->getPageSize($saved);
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);
}
return $pager;
}
public function executeQuery(
PhabricatorPolicyAwareQuery $query,
AphrontView $pager) {
$query->setViewer($this->requireViewer());
if ($this->shouldUseOffsetPaging()) {
$objects = $query->executeWithOffsetPager($pager);
} else {
$objects = $query->executeWithCursorPager($pager);
}
return $objects;
}
/* -( Rendering )---------------------------------------------------------- */
public function setRequest(AphrontRequest $request) {
$this->request = $request;
return $this;
}
public function getRequest() {
return $this->request;
}
public function renderResults(
array $objects,
PhabricatorSavedQuery $query) {
$phids = $this->getRequiredHandlePHIDsForResultList($objects, $query);
if ($phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->witHPHIDs($phids)
->execute();
} else {
$handles = array();
}
return $this->renderResultList($objects, $query, $handles);
}
protected function getRequiredHandlePHIDsForResultList(
array $objects,
PhabricatorSavedQuery $query) {
return array();
}
protected function renderResultList(
array $objects,
PhabricatorSavedQuery $query,
array $handles) {
throw new Exception(pht('Not supported here yet!'));
}
/* -( Application Search )------------------------------------------------- */
/**
* Retrieve an object to use to define custom fields for this search.
*
* To integrate with custom fields, subclasses should override this method
* and return an instance of the application object which implements
* @{interface:PhabricatorCustomFieldInterface}.
*
* @return PhabricatorCustomFieldInterface|null Object with custom fields.
* @task appsearch
*/
public function getCustomFieldObject() {
return null;
}
/**
* Get the custom fields for this search.
*
* @return PhabricatorCustomFieldList|null Custom fields, if this search
* supports custom fields.
* @task appsearch
*/
public function getCustomFieldList() {
if ($this->customFields === false) {
$object = $this->getCustomFieldObject();
if ($object) {
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
$fields->setViewer($this->requireViewer());
} else {
$fields = null;
}
$this->customFields = $fields;
}
return $this->customFields;
}
/**
* Moves data from the request into a saved query.
*
* @param AphrontRequest Request to read.
* @param PhabricatorSavedQuery Query to write to.
* @return void
* @task appsearch
*/
protected function readCustomFieldsFromRequest(
AphrontRequest $request,
PhabricatorSavedQuery $saved) {
$list = $this->getCustomFieldList();
if (!$list) {
return;
}
foreach ($list->getFields() as $field) {
$key = $this->getKeyForCustomField($field);
$value = $field->readApplicationSearchValueFromRequest(
$this,
$request);
$saved->setParameter($key, $value);
}
}
/**
* Applies data from a saved query to an executable query.
*
* @param PhabricatorCursorPagedPolicyAwareQuery Query to constrain.
* @param PhabricatorSavedQuery Saved query to read.
* @return void
*/
protected function applyCustomFieldsToQuery(
PhabricatorCursorPagedPolicyAwareQuery $query,
PhabricatorSavedQuery $saved) {
$list = $this->getCustomFieldList();
if (!$list) {
return;
}
foreach ($list->getFields() as $field) {
$key = $this->getKeyForCustomField($field);
$value = $field->applyApplicationSearchConstraintToQuery(
$this,
$query,
$saved->getParameter($key));
}
}
protected function applyOrderByToQuery(
PhabricatorCursorPagedPolicyAwareQuery $query,
array $standard_values,
$order) {
if (substr($order, 0, 7) === 'custom:') {
$list = $this->getCustomFieldList();
if (!$list) {
$query->setOrderBy(head($standard_values));
return;
}
foreach ($list->getFields() as $field) {
$key = $this->getKeyForCustomField($field);
if ($key === $order) {
$index = $field->buildOrderIndex();
if ($index === null) {
$query->setOrderBy(head($standard_values));
return;
}
$query->withApplicationSearchOrder(
$field,
$index,
false);
break;
}
}
} else {
$order = idx($standard_values, $order);
if ($order) {
$query->setOrderBy($order);
} else {
$query->setOrderBy(head($standard_values));
}
}
}
protected function getCustomFieldOrderOptions() {
$list = $this->getCustomFieldList();
if (!$list) {
return;
}
$custom_order = array();
foreach ($list->getFields() as $field) {
if ($field->shouldAppearInApplicationSearch()) {
if ($field->buildOrderIndex() !== null) {
$key = $this->getKeyForCustomField($field);
$custom_order[$key] = $field->getFieldName();
}
}
}
return $custom_order;
}
/**
* Get a unique key identifying a field.
*
* @param PhabricatorCustomField Field to identify.
* @return string Unique identifier, suitable for use as an input name.
*/
public function getKeyForCustomField(PhabricatorCustomField $field) {
return 'custom:'.$field->getFieldIndex();
}
/**
* Add inputs to an application search form so the user can query on custom
* fields.
*
* @param AphrontFormView Form to update.
* @param PhabricatorSavedQuery Values to prefill.
* @return void
*/
protected function appendCustomFieldsToForm(
AphrontFormView $form,
PhabricatorSavedQuery $saved) {
$list = $this->getCustomFieldList();
if (!$list) {
return;
}
$phids = array();
foreach ($list->getFields() as $field) {
$key = $this->getKeyForCustomField($field);
$value = $saved->getParameter($key);
$phids[$key] = $field->getRequiredHandlePHIDsForApplicationSearch($value);
}
$all_phids = array_mergev($phids);
$handles = array();
if ($all_phids) {
$handles = id(new PhabricatorHandleQuery())
->setViewer($this->requireViewer())
->withPHIDs($all_phids)
->execute();
}
foreach ($list->getFields() as $field) {
$key = $this->getKeyForCustomField($field);
$value = $saved->getParameter($key);
$field->appendToApplicationSearchForm(
$this,
$form,
$value,
array_select_keys($handles, $phids[$key]));
}
}
}
diff --git a/src/applications/search/field/PhabricatorSearchDateField.php b/src/applications/search/field/PhabricatorSearchDateField.php
index e7568f7c01..c67c7178cc 100644
--- a/src/applications/search/field/PhabricatorSearchDateField.php
+++ b/src/applications/search/field/PhabricatorSearchDateField.php
@@ -1,37 +1,41 @@
<?php
final class PhabricatorSearchDateField
extends PhabricatorSearchField {
protected function newControl() {
return new AphrontFormTextControl();
}
protected function getValueFromRequest(AphrontRequest $request, $key) {
return $request->getStr($key);
}
+ public function getValueForQuery($value) {
+ return $this->parseDateTime($value);
+ }
+
protected function validateControlValue($value) {
if (!strlen($value)) {
return;
}
$epoch = $this->parseDateTime($value);
if ($epoch) {
return;
}
$this->addError(
pht('Invalid'),
pht('Date value for "%s" can not be parsed.', $this->getLabel()));
}
protected function parseDateTime($value) {
if (!strlen($value)) {
return null;
}
return PhabricatorTime::parseLocalTime($value, $this->getViewer());
}
}
diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php
index 75a7453b4a..6944cf31fe 100644
--- a/src/applications/search/field/PhabricatorSearchField.php
+++ b/src/applications/search/field/PhabricatorSearchField.php
@@ -1,260 +1,264 @@
<?php
/**
* @task config Configuring Fields
* @task error Handling Errors
* @task io Reading and Writing Field Values
* @task util Utility Methods
*/
abstract class PhabricatorSearchField extends Phobject {
private $key;
private $viewer;
private $value;
private $label;
private $aliases = array();
private $errors = array();
/* -( Configuring Fields )------------------------------------------------- */
/**
* Set the primary key for the field, like `projectPHIDs`.
*
* You can set human-readable aliases with @{method:setAliases}.
*
* The key should be a short, unique (within a search engine) string which
* does not contain any special characters.
*
* @param string Unique key which identifies the field.
* @return this
* @task config
*/
public function setKey($key) {
$this->key = $key;
return $this;
}
/**
* Get the field's key.
*
* @return string Unique key for this field.
* @task config
*/
public function getKey() {
return $this->key;
}
/**
* Set a human-readable label for the field.
*
* This should be a short text string, like "Reviewers" or "Colors".
*
* @param string Short, human-readable field label.
* @return this
* task config
*/
public function setLabel($label) {
$this->label = $label;
return $this;
}
/**
* Get the field's human-readable label.
*
* @return string Short, human-readable field label.
* @task config
*/
public function getLabel() {
return $this->label;
}
/**
* Set the acting viewer.
*
* Engines do not need to do this explicitly; it will be done on their
* behalf by the caller.
*
* @param PhabricatorUser Viewer.
* @return this
* @task config
*/
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
/**
* Get the acting viewer.
*
* @return PhabricatorUser Viewer.
* @task config
*/
public function getViewer() {
return $this->viewer;
}
/**
* Provide alternate field aliases, usually more human-readable versions
* of the key.
*
* These aliases can be used when building GET requests, so you can provide
* an alias like `authors` to let users write `&authors=alincoln` instead of
* `&authorPHIDs=alincoln`. This is a little easier to use.
*
* @param list<string> List of aliases for this field.
* @return this
* @task config
*/
public function setAliases(array $aliases) {
$this->aliases = $aliases;
return $this;
}
/**
* Get aliases for this field.
*
* @return list<string> List of aliases for this field.
* @task config
*/
public function getAliases() {
return $this->aliases;
}
/* -( Handling Errors )---------------------------------------------------- */
protected function addError($short, $long) {
$this->errors[] = array($short, $long);
return $this;
}
public function getErrors() {
return $this->errors;
}
protected function validateControlValue($value) {
return;
}
protected function getShortError() {
$error = head($this->getErrors());
if ($error) {
return head($error);
}
return null;
}
/* -( Reading and Writing Field Values )----------------------------------- */
public function readValueFromRequest(AphrontRequest $request) {
$check = array_merge(array($this->getKey()), $this->getAliases());
foreach ($check as $key) {
if ($this->getValueExistsInRequest($request, $key)) {
return $this->getValueFromRequest($request, $key);
}
}
return $this->getDefaultValue();
}
protected function getValueExistsInRequest(AphrontRequest $request, $key) {
return $request->getExists($key);
}
abstract protected function getValueFromRequest(
AphrontRequest $request,
$key);
public function readValueFromSavedQuery(PhabricatorSavedQuery $saved) {
$value = $saved->getParameter(
$this->getKey(),
$this->getDefaultValue());
$this->value = $this->didReadValueFromSavedQuery($value);
$this->validateControlValue($value);
return $this;
}
protected function didReadValueFromSavedQuery($value) {
return $value;
}
public function getValue() {
return $this->value;
}
protected function getValueForControl() {
return $this->value;
}
protected function getDefaultValue() {
return null;
}
+ public function getValueForQuery($value) {
+ return $value;
+ }
+
/* -( Rendering Controls )------------------------------------------------- */
abstract protected function newControl();
protected function renderControl() {
// TODO: We should `setError($this->getShortError())` here, but it looks
// terrible in the form layout.
return $this->newControl()
->setValue($this->getValueForControl())
->setName($this->getKey())
->setLabel($this->getLabel());
}
public function appendToForm(AphrontFormView $form) {
$form->appendControl($this->renderControl());
return $this;
}
/* -( Utility Methods )----------------------------------------------------- */
/**
* Read a list of items from the request, in either array format or string
* format:
*
* list[]=item1&list[]=item2
* list=item1,item2
*
* This provides flexibility when constructing URIs, especially from external
* sources.
*
* @param AphrontRequest Request to read strings from.
* @param string Key to read in the request.
* @return list<string> List of values.
* @task utility
*/
protected function getListFromRequest(
AphrontRequest $request,
$key) {
$list = $request->getArr($key, null);
if ($list === null) {
$list = $request->getStrList($key);
}
if (!$list) {
return array();
}
return $list;
}
}
diff --git a/src/applications/search/field/PhabricatorSearchTokenizerField.php b/src/applications/search/field/PhabricatorSearchTokenizerField.php
index 1feb23f8a4..b2699bb846 100644
--- a/src/applications/search/field/PhabricatorSearchTokenizerField.php
+++ b/src/applications/search/field/PhabricatorSearchTokenizerField.php
@@ -1,21 +1,28 @@
<?php
abstract class PhabricatorSearchTokenizerField
extends PhabricatorSearchField {
protected function getDefaultValue() {
return array();
}
protected function getValueFromRequest(AphrontRequest $request, $key) {
return $this->getListFromRequest($request, $key);
}
+ public function getValueForQuery($value) {
+ return $this->newDatasource()
+ ->setViewer($this->getViewer())
+ ->evaluateTokens($value);
+ }
+
protected function newControl() {
return id(new AphrontFormTokenizerControl())
->setDatasource($this->newDatasource());
}
+
abstract protected function newDatasource();
}
diff --git a/src/applications/search/field/PhabricatorSearchUsersField.php b/src/applications/search/field/PhabricatorSearchUsersField.php
index 789ffb0a83..2c9620ac3c 100644
--- a/src/applications/search/field/PhabricatorSearchUsersField.php
+++ b/src/applications/search/field/PhabricatorSearchUsersField.php
@@ -1,55 +1,53 @@
<?php
final class PhabricatorSearchUsersField
extends PhabricatorSearchTokenizerField {
protected function getDefaultValue() {
return array();
}
protected function newDatasource() {
- // TODO: Make this use PhabricatorPeopleUserFunctionDatasource once field
- // support is a little more powerful.
- return new PhabricatorPeopleDatasource();
+ return new PhabricatorPeopleUserFunctionDatasource();
}
protected function getValueFromRequest(AphrontRequest $request, $key) {
$list = $this->getListFromRequest($request, $key);
$allow_types = array();
$phids = array();
$names = array();
$allow_types = array_fuse($allow_types);
$user_type = PhabricatorPeopleUserPHIDType::TYPECONST;
foreach ($list as $item) {
$type = phid_get_type($item);
if ($type == $user_type) {
$phids[] = $item;
} else if (isset($allow_types[$type])) {
$phids[] = $item;
} else {
if (PhabricatorTypeaheadDatasource::isFunctionToken($item)) {
// If this is a function, pass it through unchanged; we'll evaluate
// it later.
$phids[] = $item;
} else {
$names[] = $item;
}
}
}
if ($names) {
$users = id(new PhabricatorPeopleQuery())
->setViewer($this->getViewer())
->withUsernames($names)
->execute();
foreach ($users as $user) {
$phids[] = $user->getPHID();
}
$phids = array_unique($phids);
}
return $phids;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 30, 11:21 PM (16 h, 31 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
429957
Default Alt Text
(44 KB)

Event Timeline