diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2617,6 +2617,9 @@ 'PhabricatorCountdownViewController' => 'applications/countdown/controller/PhabricatorCountdownViewController.php', 'PhabricatorCursorPagedPolicyAwareQuery' => 'infrastructure/query/policy/PhabricatorCursorPagedPolicyAwareQuery.php', 'PhabricatorCustomField' => 'infrastructure/customfield/field/PhabricatorCustomField.php', + 'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php', + 'PhabricatorCustomFieldApplicationSearchDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php', + 'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php', 'PhabricatorCustomFieldAttachment' => 'infrastructure/customfield/field/PhabricatorCustomFieldAttachment.php', 'PhabricatorCustomFieldConfigOptionType' => 'infrastructure/customfield/config/PhabricatorCustomFieldConfigOptionType.php', 'PhabricatorCustomFieldDataNotAvailableException' => 'infrastructure/customfield/exception/PhabricatorCustomFieldDataNotAvailableException.php', @@ -4387,6 +4390,7 @@ 'PhabricatorTypeaheadInvalidTokenException' => 'applications/typeahead/exception/PhabricatorTypeaheadInvalidTokenException.php', 'PhabricatorTypeaheadModularDatasourceController' => 'applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php', 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', + 'PhabricatorTypeaheadProxyDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php', 'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php', 'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php', @@ -8122,6 +8126,9 @@ 'PhabricatorCountdownViewController' => 'PhabricatorCountdownController', 'PhabricatorCursorPagedPolicyAwareQuery' => 'PhabricatorPolicyAwareQuery', 'PhabricatorCustomField' => 'Phobject', + 'PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorCustomFieldApplicationSearchDatasource' => 'PhabricatorTypeaheadProxyDatasource', + 'PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorCustomFieldAttachment' => 'Phobject', 'PhabricatorCustomFieldConfigOptionType' => 'PhabricatorConfigOptionType', 'PhabricatorCustomFieldDataNotAvailableException' => 'Exception', @@ -10182,6 +10189,7 @@ 'PhabricatorTypeaheadInvalidTokenException' => 'Exception', 'PhabricatorTypeaheadModularDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorTypeaheadProxyDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorTypeaheadResult' => 'Phobject', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', 'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource', diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php new file mode 100644 --- /dev/null +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadProxyDatasource.php @@ -0,0 +1,58 @@ +<?php + +abstract class PhabricatorTypeaheadProxyDatasource + extends PhabricatorTypeaheadCompositeDatasource { + + private $datasource; + + public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { + $this->datasource = $datasource; + $this->setParameters( + array( + 'class' => get_class($datasource), + 'parameters' => $datasource->getParameters(), + )); + return $this; + } + + public function getDatasource() { + if (!$this->datasource) { + $class = $this->getParameter('class'); + + $parent = 'PhabricatorTypeaheadDatasource'; + if (!is_subclass_of($class, $parent)) { + throw new Exception( + pht( + 'Configured datasource class "%s" must be a valid subclass of '. + '"%s".', + $class, + $parent)); + } + + $datasource = newv($class, array()); + $datasource->setParameters($this->getParameter('parameters', array())); + $this->datasource = $datasource; + } + + return $this->datasource; + } + + public function getComponentDatasources() { + return array( + $this->getDatasource(), + ); + } + + public function getDatasourceApplicationClass() { + return $this->getDatasource()->getDatasourceApplicationClass(); + } + + public function getBrowseTitle() { + return $this->getDatasource()->getBrowseTitle(); + } + + public function getPlaceholderText() { + return $this->getDatasource()->getPlaceholderText(); + } + +} diff --git a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource.php @@ -0,0 +1,70 @@ +<?php + +final class PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource + extends PhabricatorTypeaheadDatasource { + + public function getBrowseTitle() { + return pht('Browse Any'); + } + + public function getPlaceholderText() { + return pht('Type "any()"...'); + } + + public function getDatasourceApplicationClass() { + return null; + } + + public function getDatasourceFunctions() { + return array( + 'any' => array( + 'name' => pht('Any Value'), + 'summary' => pht('Find results with any value.'), + 'description' => pht( + "This function includes results which have any value. Use a query ". + "like this to find results with any value:\n\n%s". + '> any()'), + ), + ); + } + + public function loadResults() { + $results = array( + $this->newAnyFunction(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + $results[] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_ANY, + null); + } + + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $results = array(); + foreach ($argv_list as $argv) { + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->newAnyFunction()); + } + return $results; + } + + private function newAnyFunction() { + $name = pht('Any Value'); + return $this->newFunctionResult() + ->setName($name.' any') + ->setDisplayName($name) + ->setIcon('fa-circle-o') + ->setPHID('any()') + ->setUnique(true) + ->addAttribute(pht('Select results with any value.')); + } + +} diff --git a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchDatasource.php @@ -0,0 +1,17 @@ +<?php + +final class PhabricatorCustomFieldApplicationSearchDatasource + extends PhabricatorTypeaheadProxyDatasource { + + public function getComponentDatasources() { + $datasources = parent::getComponentDatasources(); + + $datasources[] = + new PhabricatorCustomFieldApplicationSearchAnyFunctionDatasource(); + $datasources[] = + new PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource(); + + return $datasources; + } + +} diff --git a/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php new file mode 100644 --- /dev/null +++ b/src/infrastructure/customfield/datasource/PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource.php @@ -0,0 +1,72 @@ +<?php + +final class PhabricatorCustomFieldApplicationSearchNoneFunctionDatasource + extends PhabricatorTypeaheadDatasource { + + public function getBrowseTitle() { + return pht('Browse No Value'); + } + + public function getPlaceholderText() { + return pht('Type "none()"...'); + } + + public function getDatasourceApplicationClass() { + return null; + } + + public function getDatasourceFunctions() { + return array( + 'none' => array( + 'name' => pht('No Value'), + 'summary' => pht('Find results with no value.'), + 'description' => pht( + "This function includes results which have no value. Use a query ". + "like this to find results with no value:\n\n%s\n\n", + 'If you combine this function with other constraints, results '. + 'which have no value or the specified values will be returned.', + '> any()'), + ), + ); + } + + public function loadResults() { + $results = array( + $this->newNoneFunction(), + ); + return $this->filterResultsAgainstTokens($results); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + $results[] = new PhabricatorQueryConstraint( + PhabricatorQueryConstraint::OPERATOR_NULL, + null); + } + + return $results; + } + + public function renderFunctionTokens($function, array $argv_list) { + $results = array(); + foreach ($argv_list as $argv) { + $results[] = PhabricatorTypeaheadTokenView::newFromTypeaheadResult( + $this->newNoneFunction()); + } + return $results; + } + + private function newNoneFunction() { + $name = pht('No Value'); + return $this->newFunctionResult() + ->setName($name.' none') + ->setDisplayName($name) + ->setIcon('fa-ban') + ->setPHID('none()') + ->setUnique(true) + ->addAttribute(pht('Select results with no value.')); + } + +} diff --git a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php --- a/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php +++ b/src/infrastructure/customfield/standard/PhabricatorStandardCustomFieldTokenizer.php @@ -33,12 +33,28 @@ $control = id(new AphrontFormTokenizerControl()) ->setLabel($this->getFieldName()) ->setName($this->getFieldKey()) - ->setDatasource($this->getDatasource()) + ->setDatasource($this->newApplicationSearchDatasource()) ->setValue(nonempty($value, array())); $form->appendControl($control); } + public function applyApplicationSearchConstraintToQuery( + PhabricatorApplicationSearchEngine $engine, + PhabricatorCursorPagedPolicyAwareQuery $query, + $value) { + if ($value) { + + $datasource = $this->newApplicationSearchDatasource() + ->setViewer($this->getViewer()); + $value = $datasource->evaluateTokens($value); + + $query->withApplicationSearchContainsConstraint( + $this->newStringIndex(null), + $value); + } + } + public function getHeraldFieldValueType($condition) { return id(new HeraldTokenizerFieldValue()) ->setKey('custom.'.$this->getFieldKey()) @@ -120,4 +136,11 @@ } } + protected function newApplicationSearchDatasource() { + $datasource = $this->getDatasource(); + + return id(new PhabricatorCustomFieldApplicationSearchDatasource()) + ->setDatasource($datasource); + } + }