diff --git a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php --- a/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php +++ b/src/applications/almanac/typeahead/AlmanacInterfaceDatasource.php @@ -3,6 +3,12 @@ final class AlmanacInterfaceDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: We should make this browsable, but need to make the result set + // orderable by device name. + return false; + } + public function getPlaceholderText() { return pht('Type an interface name...'); } diff --git a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php b/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php --- a/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionArcanistProjectDatasource.php @@ -3,6 +3,11 @@ final class DiffusionArcanistProjectDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: We should probably make this browsable, or maybe remove it. + return false; + } + public function getPlaceholderText() { return pht('Type an arcanist project name...'); } diff --git a/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php b/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php --- a/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php +++ b/src/applications/diffusion/typeahead/DiffusionSymbolDatasource.php @@ -3,6 +3,12 @@ final class DiffusionSymbolDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // This is slightly involved to make browsable, and browsing symbols + // does not seem likely to be very useful in any real software project. + return false; + } + public function getPlaceholderText() { return pht('Type a symbol name...'); } diff --git a/src/applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php b/src/applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php --- a/src/applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php +++ b/src/applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php @@ -3,6 +3,11 @@ final class HarbormasterBuildDependencyDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: This should be browsable, but fixing it is involved. + return false; + } + public function getPlaceholderText() { return pht('Type another build step name...'); } diff --git a/src/applications/legalpad/typeahead/LegalpadDocumentDatasource.php b/src/applications/legalpad/typeahead/LegalpadDocumentDatasource.php --- a/src/applications/legalpad/typeahead/LegalpadDocumentDatasource.php +++ b/src/applications/legalpad/typeahead/LegalpadDocumentDatasource.php @@ -2,6 +2,11 @@ final class LegalpadDocumentDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: This should be made browsable. + return false; + } + public function getPlaceholderText() { return pht('Type a document name...'); } diff --git a/src/applications/macro/typeahead/PhabricatorMacroDatasource.php b/src/applications/macro/typeahead/PhabricatorMacroDatasource.php --- a/src/applications/macro/typeahead/PhabricatorMacroDatasource.php +++ b/src/applications/macro/typeahead/PhabricatorMacroDatasource.php @@ -2,6 +2,11 @@ final class PhabricatorMacroDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: This should be made browsable. + return false; + } + public function getPlaceholderText() { return pht('Type a macro name...'); } diff --git a/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php b/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php --- a/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php +++ b/src/applications/mailinglists/typeahead/PhabricatorMailingListDatasource.php @@ -3,6 +3,11 @@ final class PhabricatorMailingListDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: Make this browsable if we don't delete it before then. + return false; + } + public function getPlaceholderText() { return pht('Type a mailing list name...'); } diff --git a/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php --- a/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php +++ b/src/applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php @@ -25,7 +25,7 @@ ->setName($name); } - return $results; + return $this->filterResultsAgainstTokens($results); } } diff --git a/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php b/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php --- a/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php +++ b/src/applications/maniphest/typeahead/ManiphestTaskStatusDatasource.php @@ -25,6 +25,6 @@ ->setName($name); } - return $results; + return $this->filterResultsAgainstTokens($results); } } diff --git a/src/applications/meta/typeahead/PhabricatorApplicationDatasource.php b/src/applications/meta/typeahead/PhabricatorApplicationDatasource.php --- a/src/applications/meta/typeahead/PhabricatorApplicationDatasource.php +++ b/src/applications/meta/typeahead/PhabricatorApplicationDatasource.php @@ -37,7 +37,7 @@ ->setImageSprite('phabricator-search-icon '.$img); } - return $results; + return $this->filterResultsAgainstTokens($results); } } diff --git a/src/applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php b/src/applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php --- a/src/applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php +++ b/src/applications/metamta/typeahead/PhabricatorMetaMTAApplicationEmailDatasource.php @@ -3,6 +3,11 @@ final class PhabricatorMetaMTAApplicationEmailDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: Make this browsable. + return false; + } + public function getPlaceholderText() { return pht('Type an application email address...'); } diff --git a/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php b/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php --- a/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php +++ b/src/applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php @@ -3,6 +3,11 @@ final class PhabricatorOwnersPackageDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // TODO: Make this browsable. + return false; + } + public function getPlaceholderText() { return pht('Type a package name...'); } diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php --- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php +++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php @@ -25,6 +25,7 @@ if (isset($sources[$class])) { $source = $sources[$class]; $source->setParameters($request->getRequestData()); + $source->setViewer($viewer); // NOTE: Wrapping the source in a Composite datasource ensures we perform // application visibility checks for the viewer, so we do not need to do @@ -40,6 +41,10 @@ $hard_limit = 1000; if ($is_browse) { + if (!$composite->isBrowsable()) { + return new Aphront404Response(); + } + $limit = 10; $offset = $request->getInt('offset'); diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -3,8 +3,20 @@ abstract class PhabricatorTypeaheadCompositeDatasource extends PhabricatorTypeaheadDatasource { + private $usable; + abstract public function getComponentDatasources(); + public function isBrowsable() { + foreach ($this->getUsableDatasources() as $datasource) { + if (!$datasource->isBrowsable()) { + return false; + } + } + + return parent::isBrowsable(); + } + public function getDatasourceApplicationClass() { return null; } @@ -43,26 +55,29 @@ } private function getUsableDatasources() { - $sources = $this->getComponentDatasources(); - - $usable = array(); - foreach ($sources as $source) { - $application_class = $source->getDatasourceApplicationClass(); - - if ($application_class) { - $result = id(new PhabricatorApplicationQuery()) - ->setViewer($this->getViewer()) - ->withClasses(array($application_class)) - ->execute(); - if (!$result) { - continue; + if ($this->usable === null) { + $sources = $this->getComponentDatasources(); + + $usable = array(); + foreach ($sources as $source) { + $application_class = $source->getDatasourceApplicationClass(); + + if ($application_class) { + $result = id(new PhabricatorApplicationQuery()) + ->setViewer($this->getViewer()) + ->withClasses(array($application_class)) + ->execute(); + if (!$result) { + continue; + } } - } - $usable[] = $source; + $usable[] = $source; + } + $this->usable = $usable; } - return $usable; + return $this->usable; } } diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -102,4 +102,75 @@ ->execute(); } + + /** + * Can the user browse through results from this datasource? + * + * Browsable datasources allow the user to switch from typeahead mode to + * a browse mode where they can scroll through all results. + * + * By default, datasources are browsable, but some datasources can not + * generate a meaningful result set or can't filter results on the server. + * + * @return bool + */ + public function isBrowsable() { + return true; + } + + + /** + * Filter a list of results, removing items which don't match the query + * tokens. + * + * This is useful for datasources which return a static list of hard-coded + * or configured results and can't easily do query filtering in a real + * query class. Instead, they can just build the entire result set and use + * this method to filter it. + * + * For datasources backed by database objects, this is often much less + * efficient than filtering at the query level. + * + * @param list List of typeahead results. + * @return list Filtered results. + */ + protected function filterResultsAgainstTokens(array $results) { + $tokens = $this->getTokens(); + if (!$tokens) { + return $results; + } + + $map = array(); + foreach ($tokens as $token) { + $map[$token] = strlen($token); + } + + foreach ($results as $key => $result) { + $rtokens = self::tokenizeString($result->getName()); + + // For each token in the query, we need to find a match somewhere + // in the result name. + foreach ($map as $token => $length) { + // Look for a match. + $match = false; + foreach ($rtokens as $rtoken) { + if (!strncmp($rtoken, $token, $length)) { + // This part of the result name has the query token as a prefix. + $match = true; + break; + } + } + + if (!$match) { + // We didn't find a match for this query token, so throw the result + // away. Try with the next result. + unset($results[$key]); + break; + } + } + } + + return $results; + } + } diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php @@ -3,6 +3,13 @@ final class PhabricatorTypeaheadMonogramDatasource extends PhabricatorTypeaheadDatasource { + public function isBrowsable() { + // This source isn't meaningfully browsable. Although it's technically + // possible to let users browse through every object on an install, there + // is no use case for it and it doesn't seem worth building. + return false; + } + public function getPlaceholderText() { return pht('Type an object name...'); }