diff --git a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php index bae946d1fb..1b6788dd5e 100644 --- a/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php +++ b/src/applications/dashboard/paneltype/PhabricatorDashboardPanelTypeQuery.php @@ -1,118 +1,119 @@ array( 'name' => pht('Search For'), 'type' => 'search.application', ), 'key' => array( 'name' => pht('Query'), 'type' => 'search.query', 'control.application' => 'class', ), 'limit' => array( 'name' => pht('Limit'), 'caption' => pht('Leave this blank for the default number of items.'), 'type' => 'text', ), ); } public function initializeFieldsFromRequest( PhabricatorDashboardPanel $panel, PhabricatorCustomFieldList $field_list, AphrontRequest $request) { $map = array(); if (strlen($request->getStr('engine'))) { $map['class'] = $request->getStr('engine'); } if (strlen($request->getStr('query'))) { $map['key'] = $request->getStr('query'); } $full_map = array(); foreach ($map as $key => $value) { $full_map["std:dashboard:core:{$key}"] = $value; } foreach ($field_list->getFields() as $field) { $field_key = $field->getFieldKey(); if (isset($full_map[$field_key])) { $field->setValueFromStorage($full_map[$field_key]); } } } public function renderPanelContent( PhabricatorUser $viewer, PhabricatorDashboardPanel $panel, PhabricatorDashboardPanelRenderingEngine $engine) { $class = $panel->getProperty('class'); $engine = PhabricatorApplicationSearchEngine::getEngineByClassName($class); if (!$engine) { throw new Exception( pht( 'The application search engine "%s" is not known to Phabricator!', $class)); } $engine->setViewer($viewer); + $engine->setContext(PhabricatorApplicationSearchEngine::CONTEXT_PANEL); $key = $panel->getProperty('key'); if ($engine->isBuiltinQuery($key)) { $saved = $engine->buildSavedQueryFromBuiltin($key); } else { $saved = id(new PhabricatorSavedQueryQuery()) ->setViewer($viewer) ->withEngineClassNames(array($class)) ->withQueryKeys(array($key)) ->executeOne(); } if (!$saved) { throw new Exception( pht( 'Query "%s" is unknown to application search engine "%s"!', $key, $class)); } $query = $engine->buildQueryFromSavedQuery($saved); $pager = $engine->newPagerForSavedQuery($saved); if ($panel->getProperty('limit')) { $limit = (int)$panel->getProperty('limit'); if ($pager->getPageSize() !== 0xFFFF) { $pager->setPageSize($limit); } } $results = $engine->executeQuery($query, $pager); return $engine->renderResults($results, $saved); } } diff --git a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php index 4372996aa8..501e095aba 100644 --- a/src/applications/maniphest/query/ManiphestTaskSearchEngine.php +++ b/src/applications/maniphest/query/ManiphestTaskSearchEngine.php @@ -1,554 +1,559 @@ isBoardView = $is_board_view; return $this; } public function getIsBoardView() { return $this->isBoardView; } public function setBaseURI($base_uri) { $this->baseURI = $base_uri; return $this; } public function getBaseURI() { return $this->baseURI; } public function setShowBatchControls($show_batch_controls) { $this->showBatchControls = $show_batch_controls; return $this; } public function getResultTypeDescription() { return pht('Tasks'); } public function getApplicationClassName() { return 'PhabricatorApplicationManiphest'; } public function getCustomFieldObject() { return new ManiphestTask(); } public function buildSavedQueryFromRequest(AphrontRequest $request) { $saved = new PhabricatorSavedQuery(); $saved->setParameter( 'assignedPHIDs', $this->readUsersFromRequest($request, 'assigned')); $saved->setParameter('withUnassigned', $request->getBool('withUnassigned')); $saved->setParameter( 'authorPHIDs', $this->readUsersFromRequest($request, 'authors')); $saved->setParameter( 'subscriberPHIDs', $this->readPHIDsFromRequest($request, 'subscribers')); $saved->setParameter( 'statuses', $this->readListFromRequest($request, 'statuses')); $saved->setParameter( 'priorities', $this->readListFromRequest($request, 'priorities')); $saved->setParameter('group', $request->getStr('group')); $saved->setParameter('order', $request->getStr('order')); $ids = $request->getStrList('ids'); foreach ($ids as $key => $id) { $id = trim($id, ' Tt'); if (!$id || !is_numeric($id)) { unset($ids[$key]); } else { $ids[$key] = $id; } } $saved->setParameter('ids', $ids); $saved->setParameter('fulltext', $request->getStr('fulltext')); $saved->setParameter( 'allProjectPHIDs', $this->readPHIDsFromRequest($request, 'allProjects')); $saved->setParameter( 'withNoProject', $request->getBool('withNoProject')); $saved->setParameter( 'anyProjectPHIDs', $this->readPHIDsFromRequest($request, 'anyProjects')); $saved->setParameter( 'excludeProjectPHIDs', $this->readPHIDsFromRequest($request, 'excludeProjects')); $saved->setParameter( 'userProjectPHIDs', $this->readUsersFromRequest($request, 'userProjects')); $saved->setParameter('createdStart', $request->getStr('createdStart')); $saved->setParameter('createdEnd', $request->getStr('createdEnd')); $saved->setParameter('modifiedStart', $request->getStr('modifiedStart')); $saved->setParameter('modifiedEnd', $request->getStr('modifiedEnd')); $limit = $request->getInt('limit'); if ($limit > 0) { $saved->setParameter('limit', $limit); } $this->readCustomFieldsFromRequest($request, $saved); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new ManiphestTaskQuery()); $author_phids = $saved->getParameter('authorPHIDs'); if ($author_phids) { $query->withAuthors($author_phids); } $subscriber_phids = $saved->getParameter('subscriberPHIDs'); if ($subscriber_phids) { $query->withSubscribers($subscriber_phids); } $with_unassigned = $saved->getParameter('withUnassigned'); if ($with_unassigned) { $query->withOwners(array(null)); } else { $assigned_phids = $saved->getParameter('assignedPHIDs', array()); if ($assigned_phids) { $query->withOwners($assigned_phids); } } $statuses = $saved->getParameter('statuses'); if ($statuses) { $query->withStatuses($statuses); } $priorities = $saved->getParameter('priorities'); if ($priorities) { $query->withPriorities($priorities); } $order = $saved->getParameter('order'); $order = idx($this->getOrderValues(), $order); if ($order) { $query->setOrderBy($order); } else { $query->setOrderBy(head($this->getOrderValues())); } $group = $saved->getParameter('group'); $group = idx($this->getGroupValues(), $group); if ($group) { $query->setGroupBy($group); } else { $query->setGroupBy(head($this->getGroupValues())); } $ids = $saved->getParameter('ids'); if ($ids) { $query->withIDs($ids); } $fulltext = $saved->getParameter('fulltext'); if (strlen($fulltext)) { $query->withFullTextSearch($fulltext); } $with_no_project = $saved->getParameter('withNoProject'); if ($with_no_project) { $query->withAllProjects(array(ManiphestTaskOwner::PROJECT_NO_PROJECT)); } else { $project_phids = $saved->getParameter('allProjectPHIDs'); if ($project_phids) { $query->withAllProjects($project_phids); } } $any_project_phids = $saved->getParameter('anyProjectPHIDs'); if ($any_project_phids) { $query->withAnyProjects($any_project_phids); } $exclude_project_phids = $saved->getParameter('excludeProjectPHIDs'); if ($exclude_project_phids) { $query->withoutProjects($exclude_project_phids); } $user_project_phids = $saved->getParameter('userProjectPHIDs'); if ($user_project_phids) { $query->withAnyUserProjects($user_project_phids); } $start = $this->parseDateTime($saved->getParameter('createdStart')); $end = $this->parseDateTime($saved->getParameter('createdEnd')); if ($start) { $query->withDateCreatedAfter($start); } if ($end) { $query->withDateCreatedBefore($end); } $mod_start = $this->parseDateTime($saved->getParameter('modifiedStart')); $mod_end = $this->parseDateTime($saved->getParameter('modifiedEnd')); if ($mod_start) { $query->withDateModifiedAfter($mod_start); } if ($mod_end) { $query->withDateModifiedBefore($mod_end); } $this->applyCustomFieldsToQuery($query, $saved); return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $assigned_phids = $saved->getParameter('assignedPHIDs', array()); $author_phids = $saved->getParameter('authorPHIDs', array()); $all_project_phids = $saved->getParameter( 'allProjectPHIDs', array()); $any_project_phids = $saved->getParameter( 'anyProjectPHIDs', array()); $exclude_project_phids = $saved->getParameter( 'excludeProjectPHIDs', array()); $user_project_phids = $saved->getParameter( 'userProjectPHIDs', array()); $subscriber_phids = $saved->getParameter('subscriberPHIDs', array()); $all_phids = array_merge( $assigned_phids, $author_phids, $all_project_phids, $any_project_phids, $exclude_project_phids, $user_project_phids, $subscriber_phids); if ($all_phids) { $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->requireViewer()) ->withPHIDs($all_phids) ->execute(); } else { $handles = array(); } $assigned_handles = array_select_keys($handles, $assigned_phids); $author_handles = array_select_keys($handles, $author_phids); $all_project_handles = array_select_keys($handles, $all_project_phids); $any_project_handles = array_select_keys($handles, $any_project_phids); $exclude_project_handles = array_select_keys( $handles, $exclude_project_phids); $user_project_handles = array_select_keys($handles, $user_project_phids); $subscriber_handles = array_select_keys($handles, $subscriber_phids); $with_unassigned = $saved->getParameter('withUnassigned'); $with_no_projects = $saved->getParameter('withNoProject'); $statuses = $saved->getParameter('statuses', array()); $statuses = array_fuse($statuses); $status_control = id(new AphrontFormCheckboxControl()) ->setLabel(pht('Status')); foreach (ManiphestTaskStatus::getTaskStatusMap() as $status => $name) { $status_control->addCheckbox( 'statuses[]', $status, $name, isset($statuses[$status])); } $priorities = $saved->getParameter('priorities', array()); $priorities = array_fuse($priorities); $priority_control = id(new AphrontFormCheckboxControl()) ->setLabel(pht('Priority')); foreach (ManiphestTaskPriority::getTaskPriorityMap() as $pri => $name) { $priority_control->addCheckbox( 'priorities[]', $pri, $name, isset($priorities[$pri])); } $ids = $saved->getParameter('ids', array()); $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/accounts/') ->setName('assigned') ->setLabel(pht('Assigned To')) ->setValue($assigned_handles)) ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'withUnassigned', 1, pht('Show only unassigned tasks.'), $with_unassigned)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setName('allProjects') ->setLabel(pht('In All Projects')) ->setValue($all_project_handles)); if (!$this->getIsBoardView()) { $form ->appendChild( id(new AphrontFormCheckboxControl()) ->addCheckbox( 'withNoProject', 1, pht('Show only tasks with no projects.'), $with_no_projects)); } $form ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setName('anyProjects') ->setLabel(pht('In Any Project')) ->setValue($any_project_handles)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/projects/') ->setName('excludeProjects') ->setLabel(pht('Not In Projects')) ->setValue($exclude_project_handles)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/accounts/') ->setName('userProjects') ->setLabel(pht('In Users\' Projects')) ->setValue($user_project_handles)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/accounts/') ->setName('authors') ->setLabel(pht('Authors')) ->setValue($author_handles)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setDatasource('/typeahead/common/mailable/') ->setName('subscribers') ->setLabel(pht('Subscribers')) ->setValue($subscriber_handles)) ->appendChild($status_control) ->appendChild($priority_control); if (!$this->getIsBoardView()) { $form ->appendChild( id(new AphrontFormSelectControl()) ->setName('group') ->setLabel(pht('Group By')) ->setValue($saved->getParameter('group')) ->setOptions($this->getGroupOptions())) ->appendChild( id(new AphrontFormSelectControl()) ->setName('order') ->setLabel(pht('Order By')) ->setValue($saved->getParameter('order')) ->setOptions($this->getOrderOptions())); } $form ->appendChild( id(new AphrontFormTextControl()) ->setName('fulltext') ->setLabel(pht('Contains Words')) ->setValue($saved->getParameter('fulltext'))) ->appendChild( id(new AphrontFormTextControl()) ->setName('ids') ->setLabel(pht('Task IDs')) ->setValue(implode(', ', $ids))); $this->appendCustomFieldsToForm($form, $saved); $this->buildDateRange( $form, $saved, 'createdStart', pht('Created After'), 'createdEnd', pht('Created Before')); $this->buildDateRange( $form, $saved, 'modifiedStart', pht('Updated After'), 'modifiedEnd', pht('Updated Before')); if (!$this->getIsBoardView()) { $form ->appendChild( id(new AphrontFormTextControl()) ->setName('limit') ->setLabel(pht('Page Size')) ->setValue($saved->getParameter('limit', 100))); } } protected function getURI($path) { if ($this->baseURI) { return $this->baseURI.$path; } return '/maniphest/'.$path; } public function getBuiltinQueryNames() { $names = array(); if ($this->requireViewer()->isLoggedIn()) { $names['assigned'] = pht('Assigned'); $names['authored'] = pht('Authored'); $names['subscribed'] = pht('Subscribed'); } $names['open'] = pht('Open Tasks'); $names['all'] = pht('All Tasks'); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); $viewer_phid = $this->requireViewer()->getPHID(); switch ($query_key) { case 'all': return $query; case 'assigned': return $query ->setParameter('assignedPHIDs', array($viewer_phid)) ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'subscribed': return $query ->setParameter('subscriberPHIDs', array($viewer_phid)) ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'open': return $query ->setParameter( 'statuses', ManiphestTaskStatus::getOpenStatusConstants()); case 'authored': return $query ->setParameter('authorPHIDs', array($viewer_phid)) ->setParameter('order', 'created') ->setParameter('group', 'none'); } return parent::buildSavedQueryFromBuiltin($query_key); } private function getOrderOptions() { return array( 'priority' => pht('Priority'), 'updated' => pht('Date Updated'), 'created' => pht('Date Created'), 'title' => pht('Title'), ); } private function getOrderValues() { return array( 'priority' => ManiphestTaskQuery::ORDER_PRIORITY, 'updated' => ManiphestTaskQuery::ORDER_MODIFIED, 'created' => ManiphestTaskQuery::ORDER_CREATED, 'title' => ManiphestTaskQuery::ORDER_TITLE, ); } private function getGroupOptions() { return array( 'priority' => pht('Priority'), 'assigned' => pht('Assigned'), 'status' => pht('Status'), 'project' => pht('Project'), 'none' => pht('None'), ); } private function getGroupValues() { return array( 'priority' => ManiphestTaskQuery::GROUP_PRIORITY, 'assigned' => ManiphestTaskQuery::GROUP_OWNER, 'status' => ManiphestTaskQuery::GROUP_STATUS, 'project' => ManiphestTaskQuery::GROUP_PROJECT, 'none' => ManiphestTaskQuery::GROUP_NONE, ); } protected function renderResultList( array $tasks, PhabricatorSavedQuery $saved, array $handles) { $viewer = $this->requireViewer(); - $can_edit_priority = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this->getApplication(), - ManiphestCapabilityEditPriority::CAPABILITY); - - $can_bulk_edit = PhabricatorPolicyFilter::hasCapability( - $viewer, - $this->getApplication(), - ManiphestCapabilityBulkEdit::CAPABILITY); + if ($this->isPanelContext()) { + $can_edit_priority = false; + $can_bulk_edit = false; + } else { + $can_edit_priority = PhabricatorPolicyFilter::hasCapability( + $viewer, + $this->getApplication(), + ManiphestCapabilityEditPriority::CAPABILITY); + + $can_bulk_edit = PhabricatorPolicyFilter::hasCapability( + $viewer, + $this->getApplication(), + ManiphestCapabilityBulkEdit::CAPABILITY); + } return id(new ManiphestTaskResultListView()) ->setUser($viewer) ->setTasks($tasks) ->setSavedQuery($saved) ->setCanEditPriority($can_edit_priority) ->setCanBatchEdit($can_bulk_edit) ->setShowBatchControls($this->showBatchControls); } } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php index 0869739d6d..9dd2519ff0 100644 --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1,812 +1,825 @@ viewer = $viewer; return $this; } protected function requireViewer() { if (!$this->viewer) { throw new Exception('Call setViewer() before using an engine!'); } return $this->viewer; } + public function setContext($context) { + $this->context = $context; + return $this; + } + + public function isPanelContext() { + return ($this->context == self::CONTEXT_PANEL); + } + public function saveQuery(PhabricatorSavedQuery $query) { $query->setEngineClassName(get_class($this)); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); try { $query->save(); } catch (AphrontQueryDuplicateKeyException $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 */ abstract public function buildSavedQueryFromRequest( AphrontRequest $request); /** * Executes the saved query. * * @param PhabricatorSavedQuery The saved query to operate on. * @return The result of the query. */ abstract public function buildQueryFromSavedQuery( PhabricatorSavedQuery $saved); /** * Builds the search form using the request. * * @param AphrontFormView Form to populate. * @param PhabricatorSavedQuery The query from which to build the form. * @return void */ abstract public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $query); 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; } /* -( 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; } protected function getApplicationClassName() { throw new Exception(pht('Not implemented for this SearchEngine yet!')); } /* -( 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("'{$query_key}' is not a builtin!"); } 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("Builtin '{$query_key}' is not supported!"); } /* -( 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. * * @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 = PhabricatorPHIDConstants::PHID_TYPE_USER; foreach ($list as $item) { $type = phid_get_type($item); if ($type == $user_type) { $phids[] = $item; } else if (isset($allow_types[$type])) { $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 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) { return id(new AphrontFormDateControl()) ->setUser($this->requireViewer()) ->setName($key) ->setAllowNull(true) ->readValueFromRequest($request); } 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)); } /* -( 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)); } } /** * 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])); } } }