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 @@ 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 @@ 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 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 Other permitted PHID types. * @return list 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 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 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 Optional, list of permitted PHID types. * @return list 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 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 @@ 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 @@ 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 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 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 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 @@ 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 @@ 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; } }