1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-22 21:40:55 +01:00

Modularize SearchEngine extensions

Summary:
Ref T9964. ApplicationSearch currently has a bunch of hard-coded `if ($object instanceof thing)` stuff.

Pull that out so it can live in extensions.

Test Plan:
 - Searched by spaces, subscribers, projects.

{F1023921}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9964

Differential Revision: https://secure.phabricator.com/D14764
This commit is contained in:
epriestley 2015-12-13 04:12:34 -08:00
parent 05a798e3ac
commit 1b325a0a89
10 changed files with 248 additions and 151 deletions

View file

@ -2836,9 +2836,10 @@ phutil_register_library_map(array(
'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php', 'PhabricatorProjectUserFunctionDatasource' => 'applications/project/typeahead/PhabricatorProjectUserFunctionDatasource.php',
'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php', 'PhabricatorProjectViewController' => 'applications/project/controller/PhabricatorProjectViewController.php',
'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php', 'PhabricatorProjectWatchController' => 'applications/project/controller/PhabricatorProjectWatchController.php',
'PhabricatorProjectsEditEngineExtension' => 'applications/project/editor/PhabricatorProjectsEditEngineExtension.php', 'PhabricatorProjectsEditEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsEditEngineExtension.php',
'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php', 'PhabricatorProjectsEditField' => 'applications/transactions/editfield/PhabricatorProjectsEditField.php',
'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php', 'PhabricatorProjectsPolicyRule' => 'applications/project/policyrule/PhabricatorProjectsPolicyRule.php',
'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php',
'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php',
'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php',
'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php',
@ -3132,12 +3133,13 @@ phutil_register_library_map(array(
'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php', 'PhabricatorSubscriptionsAddSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsAddSubscribersHeraldAction.php',
'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php', 'PhabricatorSubscriptionsApplication' => 'applications/subscriptions/application/PhabricatorSubscriptionsApplication.php',
'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php', 'PhabricatorSubscriptionsEditController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsEditController.php',
'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditEngineExtension.php', 'PhabricatorSubscriptionsEditEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsEditEngineExtension.php',
'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php', 'PhabricatorSubscriptionsEditor' => 'applications/subscriptions/editor/PhabricatorSubscriptionsEditor.php',
'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php', 'PhabricatorSubscriptionsHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsHeraldAction.php',
'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php', 'PhabricatorSubscriptionsListController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsListController.php',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSelfHeraldAction.php',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php', 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'applications/subscriptions/herald/PhabricatorSubscriptionsRemoveSubscribersHeraldAction.php',
'PhabricatorSubscriptionsSearchEngineExtension' => 'applications/subscriptions/engineextension/PhabricatorSubscriptionsSearchEngineExtension.php',
'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'applications/subscriptions/command/PhabricatorSubscriptionsSubscribeEmailCommand.php',
'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'applications/subscriptions/policyrule/PhabricatorSubscriptionsSubscribersPolicyRule.php',
'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php', 'PhabricatorSubscriptionsTransactionController' => 'applications/subscriptions/controller/PhabricatorSubscriptionsTransactionController.php',
@ -7104,6 +7106,7 @@ phutil_register_library_map(array(
'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension', 'PhabricatorProjectsEditEngineExtension' => 'PhabricatorEditEngineExtension',
'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorProjectsEditField' => 'PhabricatorTokenizerEditField',
'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorProjectsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorProtocolAdapter' => 'Phobject', 'PhabricatorProtocolAdapter' => 'Phobject',
'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorQuery' => 'Phobject', 'PhabricatorQuery' => 'Phobject',
@ -7459,6 +7462,7 @@ phutil_register_library_map(array(
'PhabricatorSubscriptionsListController' => 'PhabricatorController', 'PhabricatorSubscriptionsListController' => 'PhabricatorController',
'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsRemoveSelfHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction', 'PhabricatorSubscriptionsRemoveSubscribersHeraldAction' => 'PhabricatorSubscriptionsHeraldAction',
'PhabricatorSubscriptionsSearchEngineExtension' => 'PhabricatorSearchEngineExtension',
'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand', 'PhabricatorSubscriptionsSubscribeEmailCommand' => 'MetaMTAEmailTransactionCommand',
'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule', 'PhabricatorSubscriptionsSubscribersPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController', 'PhabricatorSubscriptionsTransactionController' => 'PhabricatorController',

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorProjectsSearchEngineExtension
extends PhabricatorSearchEngineExtension {
const EXTENSIONKEY = 'projects';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorProjectApplication');
}
public function getExtensionName() {
return pht('Support for Projects');
}
public function getExtensionOrder() {
return 2000;
}
public function supportsObject($object) {
return ($object instanceof PhabricatorProjectInterface);
}
public function applyConstraintsToQuery(
$object,
$query,
PhabricatorSavedQuery $saved,
array $map) {
if (!empty($map['projectPHIDs'])) {
$query->withEdgeLogicConstraints(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
$map['projectPHIDs']);
}
}
public function getSearchFields($object) {
$fields = array();
$fields[] = id(new PhabricatorProjectSearchField())
->setKey('projectPHIDs')
->setConduitKey('projects')
->setAliases(array('project', 'projects'))
->setLabel(pht('Projects'));
return $fields;
}
}

View file

@ -19,7 +19,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
private $application; private $application;
private $viewer; private $viewer;
private $errors = array(); private $errors = array();
private $customFields = false;
private $request; private $request;
private $context; private $context;
private $controller; private $controller;
@ -164,35 +163,9 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
return $query; return $query;
} }
if ($object instanceof PhabricatorSubscribableInterface) { $extensions = $this->getEngineExtensions();
if (!empty($map['subscriberPHIDs'])) { foreach ($extensions as $extension) {
$query->withEdgeLogicPHIDs( $extension->applyConstraintsToQuery($object, $query, $saved, $map);
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_OR,
$map['subscriberPHIDs']);
}
}
if ($object instanceof PhabricatorProjectInterface) {
if (!empty($map['projectPHIDs'])) {
$query->withEdgeLogicConstraints(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
$map['projectPHIDs']);
}
}
if ($object instanceof PhabricatorSpacesInterface) {
if (!empty($map['spacePHIDs'])) {
$query->withSpacePHIDs($map['spacePHIDs']);
} else {
// If the user doesn't search for objects in specific spaces, we
// default to "all active spaces you have permission to view".
$query->withSpaceIsArchived(false);
}
}
if ($object instanceof PhabricatorCustomFieldInterface) {
$this->applyCustomFieldsToQuery($query, $saved);
} }
$order = $saved->getParameter('order'); $order = $saved->getParameter('order');
@ -272,34 +245,15 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
$object = $this->newResultObject(); $object = $this->newResultObject();
if ($object) { if ($object) {
if ($object instanceof PhabricatorSubscribableInterface) { $extensions = $this->getEngineExtensions();
$fields[] = id(new PhabricatorSearchSubscribersField()) foreach ($extensions as $extension) {
->setLabel(pht('Subscribers')) $extension_fields = $extension->getSearchFields($object);
->setKey('subscriberPHIDs') foreach ($extension_fields as $extension_field) {
->setAliases(array('subscriber', 'subscribers')); $fields[] = $extension_field;
}
if ($object instanceof PhabricatorProjectInterface) {
$fields[] = id(new PhabricatorProjectSearchField())
->setKey('projectPHIDs')
->setAliases(array('project', 'projects'))
->setLabel(pht('Projects'));
}
if ($object instanceof PhabricatorSpacesInterface) {
if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) {
$fields[] = id(new PhabricatorSpacesSearchField())
->setKey('spacePHIDs')
->setAliases(array('space', 'spaces'))
->setLabel(pht('Spaces'));
} }
} }
} }
foreach ($this->buildCustomFieldSearchFields() as $custom_field) {
$fields[] = $custom_field;
}
$query = $this->newQuery(); $query = $this->newQuery();
if ($query && $this->shouldShowOrderField()) { if ($query && $this->shouldShowOrderField()) {
$orders = $query->getBuiltinOrders(); $orders = $query->getBuiltinOrders();
@ -1089,89 +1043,6 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
/* -( Application Search )------------------------------------------------- */ /* -( 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() {
$object = $this->newResultObject();
if ($object instanceof PhabricatorCustomFieldInterface) {
return $object;
}
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;
}
/**
* 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) {
$value = $field->applyApplicationSearchConstraintToQuery(
$this,
$query,
$saved->getParameter('custom:'.$field->getFieldIndex()));
}
}
private 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;
}
public function getSearchFieldsForConduit() { public function getSearchFieldsForConduit() {
$standard_fields = $this->buildSearchFields(); $standard_fields = $this->buildSearchFields();
@ -1302,24 +1173,37 @@ abstract class PhabricatorApplicationSearchEngine extends Phobject {
return $specifications; return $specifications;
} }
private function getConduitFieldExtensions() { private function getEngineExtensions() {
$extensions = PhabricatorSearchEngineExtension::getAllEnabledExtensions(); $extensions = PhabricatorSearchEngineExtension::getAllEnabledExtensions();
$object = $this->newQuery()->newResultObject();
$field_extensions = array();
foreach ($extensions as $key => $extension) { foreach ($extensions as $key => $extension) {
$extension->setViewer($this->requireViewer()); $extension
->setViewer($this->requireViewer())
->setSearchEngine($this);
}
$object = $this->newResultObject();
foreach ($extensions as $key => $extension) {
if (!$extension->supportsObject($object)) { if (!$extension->supportsObject($object)) {
continue; unset($extensions[$key]);
}
if ($extension->getFieldSpecificationsForConduit($object)) {
$field_extensions[$key] = $extension;
} }
} }
return $field_extensions; return $extensions;
}
private function getConduitFieldExtensions() {
$extensions = $this->getEngineExtensions();
$object = $this->newResultObject();
foreach ($extensions as $key => $extension) {
if (!$extension->getFieldSpecificationsForConduit($object)) {
unset($extensions[$key]);
}
}
return $extensions;
} }
private function setAutomaticConstraintsForConduit( private function setAutomaticConstraintsForConduit(

View file

@ -3,6 +3,7 @@
abstract class PhabricatorSearchEngineExtension extends Phobject { abstract class PhabricatorSearchEngineExtension extends Phobject {
private $viewer; private $viewer;
private $searchEngine;
final public function getExtensionKey() { final public function getExtensionKey() {
return $this->getPhobjectClassConstant('EXTENSIONKEY'); return $this->getPhobjectClassConstant('EXTENSIONKEY');
@ -17,10 +18,36 @@ abstract class PhabricatorSearchEngineExtension extends Phobject {
return $this->viewer; return $this->viewer;
} }
final public function setSearchEngine(
PhabricatorApplicationSearchEngine $engine) {
$this->searchEngine = $engine;
return $this;
}
final public function getSearchEngine() {
return $this->searchEngine;
}
abstract public function isExtensionEnabled(); abstract public function isExtensionEnabled();
abstract public function getExtensionName(); abstract public function getExtensionName();
abstract public function supportsObject($object); abstract public function supportsObject($object);
public function getExtensionOrder() {
return 5000;
}
public function getSearchFields($object) {
return array();
}
public function applyConstraintsToQuery(
$object,
$query,
PhabricatorSavedQuery $saved,
array $map) {
return;
}
public function getFieldSpecificationsForConduit($object) { public function getFieldSpecificationsForConduit($object) {
return array(); return array();
} }
@ -33,6 +60,7 @@ abstract class PhabricatorSearchEngineExtension extends Phobject {
return id(new PhutilClassMapQuery()) return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__) ->setAncestorClass(__CLASS__)
->setUniqueMethod('getExtensionKey') ->setUniqueMethod('getExtensionKey')
->setSortMethod('getExtensionOrder')
->execute(); ->execute();
} }

View file

@ -19,6 +19,7 @@ final class PhabricatorSearchEngineExtensionModule
$rows = array(); $rows = array();
foreach ($extensions as $extension) { foreach ($extensions as $extension) {
$rows[] = array( $rows[] = array(
$extension->getExtensionOrder(),
$extension->getExtensionKey(), $extension->getExtensionKey(),
get_class($extension), get_class($extension),
$extension->getExtensionName(), $extension->getExtensionName(),
@ -31,6 +32,7 @@ final class PhabricatorSearchEngineExtensionModule
$table = id(new AphrontTableView($rows)) $table = id(new AphrontTableView($rows))
->setHeaders( ->setHeaders(
array( array(
pht('Order'),
pht('Key'), pht('Key'),
pht('Class'), pht('Class'),
pht('Name'), pht('Name'),
@ -38,6 +40,7 @@ final class PhabricatorSearchEngineExtensionModule
)) ))
->setColumnClasses( ->setColumnClasses(
array( array(
null,
null, null,
null, null,
'wide pri', 'wide pri',

View file

@ -14,10 +14,43 @@ final class PhabricatorSpacesSearchEngineExtension
return pht('Support for Spaces'); return pht('Support for Spaces');
} }
public function getExtensionOrder() {
return 3000;
}
public function supportsObject($object) { public function supportsObject($object) {
return ($object instanceof PhabricatorSpacesInterface); return ($object instanceof PhabricatorSpacesInterface);
} }
public function getSearchFields($object) {
$fields = array();
if (PhabricatorSpacesNamespaceQuery::getSpacesExist()) {
$fields[] = id(new PhabricatorSpacesSearchField())
->setKey('spacePHIDs')
->setConduitKey('spaces')
->setAliases(array('space', 'spaces'))
->setLabel(pht('Spaces'));
}
return $fields;
}
public function applyConstraintsToQuery(
$object,
$query,
PhabricatorSavedQuery $saved,
array $map) {
if (!empty($map['spacePHIDs'])) {
$query->withSpacePHIDs($map['spacePHIDs']);
} else {
// If the user doesn't search for objects in specific spaces, we
// default to "all active spaces you have permission to view".
$query->withSpaceIsArchived(false);
}
}
public function getFieldSpecificationsForConduit($object) { public function getFieldSpecificationsForConduit($object) {
return array( return array(
'spacePHID' => array( 'spacePHID' => array(

View file

@ -34,8 +34,6 @@ final class PhabricatorSubscriptionsEditEngineExtension
$sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID( $sub_phids = PhabricatorSubscribersQuery::loadSubscribersForPHID(
$object_phid); $object_phid);
} else { } else {
// TODO: Allow applications to provide default subscribers? Maniphest
// does this at a minimum.
$sub_phids = array(); $sub_phids = array();
} }

View file

@ -0,0 +1,52 @@
<?php
final class PhabricatorSubscriptionsSearchEngineExtension
extends PhabricatorSearchEngineExtension {
const EXTENSIONKEY = 'subscriptions';
public function isExtensionEnabled() {
return PhabricatorApplication::isClassInstalled(
'PhabricatorSubscriptionsApplication');
}
public function getExtensionName() {
return pht('Support for Subscriptions');
}
public function getExtensionOrder() {
return 1000;
}
public function supportsObject($object) {
return ($object instanceof PhabricatorSubscribableInterface);
}
public function applyConstraintsToQuery(
$object,
$query,
PhabricatorSavedQuery $saved,
array $map) {
if (!empty($map['subscriberPHIDs'])) {
$query->withEdgeLogicPHIDs(
PhabricatorObjectHasSubscriberEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_OR,
$map['subscriberPHIDs']);
}
}
public function getSearchFields($object) {
$fields = array();
$fields[] = id(new PhabricatorSearchSubscribersField())
->setLabel(pht('Subscribers'))
->setKey('subscriberPHIDs')
->setConduitKey('subscribers')
->setAliases(array('subscriber', 'subscribers'));
return $fields;
}
}

View file

@ -17,6 +17,50 @@ final class PhabricatorCustomFieldSearchEngineExtension
return ($object instanceof PhabricatorCustomFieldInterface); return ($object instanceof PhabricatorCustomFieldInterface);
} }
public function getExtensionOrder() {
return 9000;
}
public function getSearchFields($object) {
$engine = $this->getSearchEngine();
$custom_fields = $this->getCustomFields($object);
$fields = array();
foreach ($custom_fields as $field) {
$fields[] = id(new PhabricatorSearchCustomFieldProxyField())
->setSearchEngine($engine)
->setCustomField($field);
}
return $fields;
}
public function applyConstraintsToQuery(
$object,
$query,
PhabricatorSavedQuery $saved,
array $map) {
$engine = $this->getSearchEngine();
$fields = $this->getCustomFields($object);
foreach ($fields as $field) {
$field->applyApplicationSearchConstraintToQuery(
$engine,
$query,
$saved->getParameter('custom:'.$field->getFieldIndex()));
}
}
private function getCustomFields($object) {
$fields = PhabricatorCustomField::getObjectFields(
$object,
PhabricatorCustomField::ROLE_APPLICATIONSEARCH);
$fields->setViewer($this->getViewer());
return $fields->getFields();
}
public function getFieldSpecificationsForConduit($object) { public function getFieldSpecificationsForConduit($object) {
$fields = PhabricatorCustomField::getObjectFields( $fields = PhabricatorCustomField::getObjectFields(
$object, $object,