diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 7f32556969..d390ac084b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2498,8 +2498,10 @@ phutil_register_library_map(array( 'PhabricatorSearchCheckboxesField' => 'applications/search/field/PhabricatorSearchCheckboxesField.php', 'PhabricatorSearchConfigOptions' => 'applications/search/config/PhabricatorSearchConfigOptions.php', 'PhabricatorSearchController' => 'applications/search/controller/PhabricatorSearchController.php', + 'PhabricatorSearchCustomFieldProxyField' => 'applications/search/field/PhabricatorSearchCustomFieldProxyField.php', 'PhabricatorSearchDAO' => 'applications/search/storage/PhabricatorSearchDAO.php', 'PhabricatorSearchDatasource' => 'applications/search/typeahead/PhabricatorSearchDatasource.php', + 'PhabricatorSearchDatasourceField' => 'applications/search/field/PhabricatorSearchDatasourceField.php', 'PhabricatorSearchDateField' => 'applications/search/field/PhabricatorSearchDateField.php', 'PhabricatorSearchDeleteController' => 'applications/search/controller/PhabricatorSearchDeleteController.php', 'PhabricatorSearchDocument' => 'applications/search/storage/document/PhabricatorSearchDocument.php', @@ -2518,13 +2520,16 @@ phutil_register_library_map(array( 'PhabricatorSearchManagementInitWorkflow' => 'applications/search/management/PhabricatorSearchManagementInitWorkflow.php', 'PhabricatorSearchManagementWorkflow' => 'applications/search/management/PhabricatorSearchManagementWorkflow.php', 'PhabricatorSearchOrderController' => 'applications/search/controller/PhabricatorSearchOrderController.php', + 'PhabricatorSearchOwnersField' => 'applications/search/field/PhabricatorSearchOwnersField.php', 'PhabricatorSearchPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorSearchPreferencesSettingsPanel.php', 'PhabricatorSearchProjectsField' => 'applications/search/field/PhabricatorSearchProjectsField.php', 'PhabricatorSearchRelationship' => 'applications/search/constants/PhabricatorSearchRelationship.php', 'PhabricatorSearchResultView' => 'applications/search/view/PhabricatorSearchResultView.php', 'PhabricatorSearchSelectController' => 'applications/search/controller/PhabricatorSearchSelectController.php', + 'PhabricatorSearchSelectField' => 'applications/search/field/PhabricatorSearchSelectField.php', 'PhabricatorSearchSpacesField' => 'applications/search/field/PhabricatorSearchSpacesField.php', 'PhabricatorSearchStringListField' => 'applications/search/field/PhabricatorSearchStringListField.php', + 'PhabricatorSearchTextField' => 'applications/search/field/PhabricatorSearchTextField.php', 'PhabricatorSearchThreeStateField' => 'applications/search/field/PhabricatorSearchThreeStateField.php', 'PhabricatorSearchTokenizerField' => 'applications/search/field/PhabricatorSearchTokenizerField.php', 'PhabricatorSearchUsersField' => 'applications/search/field/PhabricatorSearchUsersField.php', @@ -5998,8 +6003,10 @@ phutil_register_library_map(array( 'PhabricatorSearchCheckboxesField' => 'PhabricatorSearchField', 'PhabricatorSearchConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorSearchController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchCustomFieldProxyField' => 'PhabricatorSearchField', 'PhabricatorSearchDAO' => 'PhabricatorLiskDAO', 'PhabricatorSearchDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorSearchDatasourceField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchDateField' => 'PhabricatorSearchField', 'PhabricatorSearchDeleteController' => 'PhabricatorSearchBaseController', 'PhabricatorSearchDocument' => 'PhabricatorSearchDAO', @@ -6016,12 +6023,15 @@ phutil_register_library_map(array( 'PhabricatorSearchManagementInitWorkflow' => 'PhabricatorSearchManagementWorkflow', 'PhabricatorSearchManagementWorkflow' => 'PhabricatorManagementWorkflow', 'PhabricatorSearchOrderController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchOwnersField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchPreferencesSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorSearchProjectsField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchResultView' => 'AphrontView', 'PhabricatorSearchSelectController' => 'PhabricatorSearchBaseController', + 'PhabricatorSearchSelectField' => 'PhabricatorSearchField', 'PhabricatorSearchSpacesField' => 'PhabricatorSearchTokenizerField', 'PhabricatorSearchStringListField' => 'PhabricatorSearchField', + 'PhabricatorSearchTextField' => 'PhabricatorSearchField', 'PhabricatorSearchThreeStateField' => 'PhabricatorSearchField', 'PhabricatorSearchTokenizerField' => 'PhabricatorSearchField', 'PhabricatorSearchUsersField' => 'PhabricatorSearchTokenizerField', diff --git a/src/applications/project/query/PhabricatorProjectSearchEngine.php b/src/applications/project/query/PhabricatorProjectSearchEngine.php index 2b44d579ac..50e7d17340 100644 --- a/src/applications/project/query/PhabricatorProjectSearchEngine.php +++ b/src/applications/project/query/PhabricatorProjectSearchEngine.php @@ -11,133 +11,66 @@ final class PhabricatorProjectSearchEngine return 'PhabricatorProjectApplication'; } - public function getCustomFieldObject() { + public function newResultObject() { return new PhabricatorProject(); } - public function buildSavedQueryFromRequest(AphrontRequest $request) { - $saved = new PhabricatorSavedQuery(); - - $saved->setParameter( - 'memberPHIDs', - $this->readUsersFromRequest($request, 'members')); - - $saved->setParameter('status', $request->getStr('status')); - $saved->setParameter('name', $request->getStr('name')); - - $saved->setParameter( - 'icons', - $this->readListFromRequest($request, 'icons')); - - $saved->setParameter( - 'colors', - $this->readListFromRequest($request, 'colors')); - - $this->readCustomFieldsFromRequest($request, $saved); - - return $saved; + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorSearchTextField()) + ->setLabel(pht('Name')) + ->setKey('name'), + id(new PhabricatorSearchUsersField()) + ->setLabel(pht('Members')) + ->setKey('memberPHIDs') + ->setAliases(array('member', 'members')), + id(new PhabricatorSearchSelectField()) + ->setLabel(pht('Status')) + ->setKey('status') + ->setOptions($this->getStatusOptions()), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Icons')) + ->setKey('icons') + ->setOptions($this->getIconOptions()), + id(new PhabricatorSearchCheckboxesField()) + ->setLabel(pht('Colors')) + ->setKey('colors') + ->setOptions($this->getColorOptions()), + ); } - public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { + + public function buildQueryFromParameters(array $map) { $query = id(new PhabricatorProjectQuery()) ->needImages(true); - $member_phids = $saved->getParameter('memberPHIDs', array()); - if ($member_phids && is_array($member_phids)) { - $query->withMemberPHIDs($member_phids); - } - - $status = $saved->getParameter('status'); - $status = idx($this->getStatusValues(), $status); - if ($status) { - $query->withStatus($status); - } - - $name = $saved->getParameter('name'); - if (strlen($name)) { - $tokens = PhabricatorTypeaheadDatasource::tokenizeString($name); + if (strlen($map['name'])) { + $tokens = PhabricatorTypeaheadDatasource::tokenizeString($map['name']); $query->withNameTokens($tokens); } - $icons = $saved->getParameter('icons'); - if ($icons) { - $query->withIcons($icons); + if ($map['memberPHIDs']) { + $query->withMemberPHIDs($map['memberPHIDs']); } - $colors = $saved->getParameter('colors'); - if ($colors) { - $query->withColors($colors); + if ($map['status']) { + $status = idx($this->getStatusValues(), $map['status']); + if ($status) { + $query->withStatus($status); + } } - $this->applyCustomFieldsToQuery($query, $saved); + if ($map['icons']) { + $query->withIcons($map['icons']); + } + + if ($map['colors']) { + $query->withColors($map['colors']); + } return $query; } - public function buildSearchForm( - AphrontFormView $form, - PhabricatorSavedQuery $saved) { - - $member_phids = $saved->getParameter('memberPHIDs', array()); - - $status = $saved->getParameter('status'); - $name_match = $saved->getParameter('name'); - - $icons = array_fuse($saved->getParameter('icons', array())); - $colors = array_fuse($saved->getParameter('colors', array())); - - $icon_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Icons')); - foreach (PhabricatorProjectIcon::getIconMap() as $icon => $name) { - $image = id(new PHUIIconView()) - ->setIconFont($icon); - - $icon_control->addCheckbox( - 'icons[]', - $icon, - array($image, ' ', $name), - isset($icons[$icon])); - } - - $color_control = id(new AphrontFormCheckboxControl()) - ->setLabel(pht('Colors')); - foreach (PhabricatorProjectIcon::getColorMap() as $color => $name) { - $tag = id(new PHUITagView()) - ->setType(PHUITagView::TYPE_SHADE) - ->setShade($color) - ->setName($name); - - $color_control->addCheckbox( - 'colors[]', - $color, - $tag, - isset($colors[$color])); - } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('name') - ->setLabel(pht('Name')) - ->setValue($name_match)) - ->appendControl( - id(new AphrontFormTokenizerControl()) - ->setDatasource(new PhabricatorPeopleDatasource()) - ->setName('members') - ->setLabel(pht('Members')) - ->setValue($member_phids)) - ->appendChild( - id(new AphrontFormSelectControl()) - ->setLabel(pht('Status')) - ->setName('status') - ->setOptions($this->getStatusOptions()) - ->setValue($status)) - ->appendChild($icon_control) - ->appendChild($color_control); - - $this->appendCustomFieldsToForm($form, $saved); - } - protected function getURI($path) { return '/project/'.$path; } @@ -192,14 +125,36 @@ final class PhabricatorProjectSearchEngine ); } - private function getColorValues() {} + private function getIconOptions() { + $options = array(); - private function getIconValues() {} + foreach (PhabricatorProjectIcon::getIconMap() as $icon => $name) { + $options[$icon] = array( + id(new PHUIIconView()) + ->setIconFont($icon), + ' ', + $name, + ); + } - protected function getRequiredHandlePHIDsForResultList( - array $projects, - PhabricatorSavedQuery $query) { - return mpull($projects, 'getPHID'); + return $options; + } + + private function getColorOptions() { + $options = array(); + + foreach (PhabricatorProjectIcon::getColorMap() as $color => $name) { + $options[$color] = array( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setShade($color) + ->setName($name), + ' ', + $name, + ); + } + + return $options; } protected function renderResultList( @@ -208,6 +163,7 @@ final class PhabricatorProjectSearchEngine array $handles) { assert_instances_of($projects, 'PhabricatorProject'); $viewer = $this->requireViewer(); + $handles = $viewer->loadHandles(mpull($projects, 'getPHID')); $list = new PHUIObjectItemListView(); $list->setUser($viewer); diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index bfe4ef4d40..569b1ba234 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -127,6 +127,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } } + if ($object instanceof PhabricatorCustomFieldInterface) { + $this->applyCustomFieldsToQuery($query, $saved); + } + return $query; } @@ -192,6 +196,10 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } } + foreach ($this->buildCustomFieldSearchFields() as $custom_field) { + $fields[] = $custom_field; + } + return $fields; } @@ -1094,14 +1102,22 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject { } - /** - * 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 buildCustomFieldSearchFields() { + $list = $this->getCustomFieldList(); + if (!$list) { + return array(); + } + + $fields = array(); + foreach ($list->getFields() as $field) { + $fields[] = id(new PhabricatorSearchCustomFieldProxyField()) + ->setSearchEngine($this) + ->setCustomField($field); + } + return $fields; + } + + // TODO: Remove. protected function appendCustomFieldsToForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { diff --git a/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php new file mode 100644 index 0000000000..15cbfcb09e --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php @@ -0,0 +1,58 @@ +searchEngine = $engine; + return $this; + } + + public function getSearchEngine() { + return $this->searchEngine; + } + + public function setCustomField(PhabricatorCustomField $field) { + $this->customField = $field; + $this->setKey('custom:'.$field->getFieldIndex()); + + $aliases = array(); + $aliases[] = $field->getFieldKey(); + $this->setAliases($aliases); + + return $this; + } + + public function getCustomField() { + return $this->customField; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueExistsInRequest(AphrontRequest $request, $key) { + // TODO: For historical reasons, the keys we look for don't line up with + // the keys that CustomFields use. Just skip the check for existence and + // always read the value. It would be vaguely nice to make rendering more + // consistent instead. + return true; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $this->getCustomField()->readApplicationSearchValueFromRequest( + $this->getSearchEngine(), + $request); + } + + public function appendToForm(AphrontFormView $form) { + return $this->getCustomField()->appendToApplicationSearchForm( + $this->getSearchEngine(), + $form, + $this->getValue()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchDatasourceField.php b/src/applications/search/field/PhabricatorSearchDatasourceField.php new file mode 100644 index 0000000000..2e05d60643 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchDatasourceField.php @@ -0,0 +1,17 @@ +datasource); + } + + public function setDatasource(PhabricatorTypeaheadDatasource $datasource) { + $this->datasource = $datasource; + return $this; + } + +} diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php index 6944cf31fe..8a3e06ee08 100644 --- a/src/applications/search/field/PhabricatorSearchField.php +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -209,7 +209,9 @@ abstract class PhabricatorSearchField extends Phobject { /* -( Rendering Controls )------------------------------------------------- */ - abstract protected function newControl(); + protected function newControl() { + throw new PhutilMethodNotImplementedException(); + } protected function renderControl() { diff --git a/src/applications/search/field/PhabricatorSearchOwnersField.php b/src/applications/search/field/PhabricatorSearchOwnersField.php new file mode 100644 index 0000000000..6d7d8db407 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchOwnersField.php @@ -0,0 +1,18 @@ +getUsersFromRequest($request, $key); + } + + protected function newDatasource() { + return new PhabricatorPeopleOwnerDatasource(); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchSelectField.php b/src/applications/search/field/PhabricatorSearchSelectField.php new file mode 100644 index 0000000000..ca68415583 --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchSelectField.php @@ -0,0 +1,30 @@ +options = $options; + return $this; + } + + public function getOptions() { + return $this->options; + } + + protected function getDefaultValue() { + return null; + } + + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $request->getStr($key); + } + + protected function newControl() { + return id(new AphrontFormSelectControl()) + ->setOptions($this->getOptions()); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchTextField.php b/src/applications/search/field/PhabricatorSearchTextField.php new file mode 100644 index 0000000000..e3cf1f845c --- /dev/null +++ b/src/applications/search/field/PhabricatorSearchTextField.php @@ -0,0 +1,18 @@ +getStr($key); + } + + protected function newControl() { + return new AphrontFormTextControl(); + } + +} diff --git a/src/applications/search/field/PhabricatorSearchTokenizerField.php b/src/applications/search/field/PhabricatorSearchTokenizerField.php index b2699bb846..0210cb8f56 100644 --- a/src/applications/search/field/PhabricatorSearchTokenizerField.php +++ b/src/applications/search/field/PhabricatorSearchTokenizerField.php @@ -25,4 +25,44 @@ abstract class PhabricatorSearchTokenizerField abstract protected function newDatasource(); + + protected function getUsersFromRequest(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; + } + } diff --git a/src/applications/search/field/PhabricatorSearchUsersField.php b/src/applications/search/field/PhabricatorSearchUsersField.php index 2c9620ac3c..889afe99be 100644 --- a/src/applications/search/field/PhabricatorSearchUsersField.php +++ b/src/applications/search/field/PhabricatorSearchUsersField.php @@ -7,47 +7,12 @@ final class PhabricatorSearchUsersField return array(); } + protected function getValueFromRequest(AphrontRequest $request, $key) { + return $this->getUsersFromRequest($request, $key); + } + protected function newDatasource() { 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; - } - }