diff --git a/src/applications/conpherence/query/ConpherenceThreadQuery.php b/src/applications/conpherence/query/ConpherenceThreadQuery.php index 9d54ecf27e..8474eb8d20 100644 --- a/src/applications/conpherence/query/ConpherenceThreadQuery.php +++ b/src/applications/conpherence/query/ConpherenceThreadQuery.php @@ -1,383 +1,402 @@ needFilePHIDs = $need_file_phids; return $this; } public function needParticipantCache($participant_cache) { $this->needParticipantCache = $participant_cache; return $this; } public function needParticipants($need) { $this->needParticipants = $need; return $this; } public function needWidgetData($need_widget_data) { $this->needWidgetData = $need_widget_data; return $this; } public function needTransactions($need_transactions) { $this->needTransactions = $need_transactions; return $this; } public function withIDs(array $ids) { $this->ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withParticipantPHIDs(array $phids) { $this->participantPHIDs = $phids; return $this; } public function withIsRoom($bool) { $this->isRoom = $bool; return $this; } public function setAfterTransactionID($id) { $this->afterTransactionID = $id; return $this; } public function setBeforeTransactionID($id) { $this->beforeTransactionID = $id; return $this; } public function setTransactionLimit($transaction_limit) { $this->transactionLimit = $transaction_limit; return $this; } public function getTransactionLimit() { return $this->transactionLimit; } + public function withFulltext($query) { + $this->fulltext = $query; + return $this; + } + protected function loadPage() { $table = new ConpherenceThread(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT conpherence_thread.* FROM %T conpherence_thread %Q %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinClause($conn_r), $this->buildWhereClause($conn_r), $this->buildGroupClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $conpherences = $table->loadAllFromArray($data); if ($conpherences) { $conpherences = mpull($conpherences, null, 'getPHID'); $this->loadParticipantsAndInitHandles($conpherences); if ($this->needParticipantCache) { $this->loadCoreHandles($conpherences, 'getRecentParticipantPHIDs'); } if ($this->needWidgetData || $this->needParticipants) { $this->loadCoreHandles($conpherences, 'getParticipantPHIDs'); } if ($this->needTransactions) { $this->loadTransactionsAndHandles($conpherences); } if ($this->needFilePHIDs || $this->needWidgetData) { $this->loadFilePHIDs($conpherences); } if ($this->needWidgetData) { $this->loadWidgetData($conpherences); } } return $conpherences; } protected function buildGroupClause(AphrontDatabaseConnection $conn_r) { - if ($this->participantPHIDs !== null) { + if ($this->participantPHIDs !== null || strlen($this->fulltext)) { return 'GROUP BY conpherence_thread.id'; } else { return $this->buildApplicationSearchGroupClause($conn_r); } } protected function buildJoinClause(AphrontDatabaseConnection $conn_r) { $joins = array(); if ($this->participantPHIDs !== null) { $joins[] = qsprintf( $conn_r, 'JOIN %T p ON p.conpherencePHID = conpherence_thread.phid', id(new ConpherenceParticipant())->getTableName()); } $viewer = $this->getViewer(); if ($this->shouldJoinForViewer($viewer)) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T v ON v.conpherencePHID = conpherence_thread.phid '. 'AND v.participantPHID = %s', id(new ConpherenceParticipant())->getTableName(), $viewer->getPHID()); } + if (strlen($this->fulltext)) { + $joins[] = qsprintf( + $conn_r, + 'JOIN %T idx ON idx.threadPHID = conpherence_thread.phid', + id(new ConpherenceIndex())->getTableName()); + } $joins[] = $this->buildApplicationSearchJoinClause($conn_r); return implode(' ', $joins); } private function shouldJoinForViewer(PhabricatorUser $viewer) { if ($viewer->isLoggedIn() && $this->ids === null && $this->phids === null) { return true; } return false; } protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); $where[] = $this->buildPagingClause($conn_r); if ($this->ids !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.phid IN (%Ls)', $this->phids); } if ($this->participantPHIDs !== null) { $where[] = qsprintf( $conn_r, 'p.participantPHID IN (%Ls)', $this->participantPHIDs); } if ($this->isRoom !== null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = %d', (int)$this->isRoom); } + if (strlen($this->fulltext)) { + $where[] = qsprintf( + $conn_r, + 'MATCH(idx.corpus) AGAINST (%s IN BOOLEAN MODE)', + $this->fulltext); + } + $viewer = $this->getViewer(); if ($this->shouldJoinForViewer($viewer)) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = 1 OR v.participantPHID IS NOT NULL'); } else if ($this->phids === null && $this->ids === null) { $where[] = qsprintf( $conn_r, 'conpherence_thread.isRoom = 1'); } return $this->formatWhereClause($where); } private function loadParticipantsAndInitHandles(array $conpherences) { $participants = id(new ConpherenceParticipant()) ->loadAllWhere('conpherencePHID IN (%Ls)', array_keys($conpherences)); $map = mgroup($participants, 'getConpherencePHID'); foreach ($conpherences as $current_conpherence) { $conpherence_phid = $current_conpherence->getPHID(); $conpherence_participants = idx( $map, $conpherence_phid, array()); $conpherence_participants = mpull( $conpherence_participants, null, 'getParticipantPHID'); $current_conpherence->attachParticipants($conpherence_participants); $current_conpherence->attachHandles(array()); } return $this; } private function loadCoreHandles( array $conpherences, $method) { $handle_phids = array(); foreach ($conpherences as $conpherence) { $handle_phids[$conpherence->getPHID()] = $conpherence->$method(); } $flat_phids = array_mergev($handle_phids); $handles = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($flat_phids) ->execute(); foreach ($handle_phids as $conpherence_phid => $phids) { $conpherence = $conpherences[$conpherence_phid]; $conpherence->attachHandles( $conpherence->getHandles() + array_select_keys($handles, $phids)); } return $this; } private function loadTransactionsAndHandles(array $conpherences) { $query = id(new ConpherenceTransactionQuery()) ->setViewer($this->getViewer()) ->withObjectPHIDs(array_keys($conpherences)) ->needHandles(true); // We have to flip these for the underyling query class. The semantics of // paging are tricky business. if ($this->afterTransactionID) { $query->setBeforeID($this->afterTransactionID); } else if ($this->beforeTransactionID) { $query->setAfterID($this->beforeTransactionID); } if ($this->getTransactionLimit()) { // fetch an extra for "show older" scenarios $query->setLimit($this->getTransactionLimit() + 1); } $transactions = $query->execute(); $transactions = mgroup($transactions, 'getObjectPHID'); foreach ($conpherences as $phid => $conpherence) { $current_transactions = idx($transactions, $phid, array()); $handles = array(); foreach ($current_transactions as $transaction) { $handles += $transaction->getHandles(); } $conpherence->attachHandles($conpherence->getHandles() + $handles); $conpherence->attachTransactions($current_transactions); } return $this; } private function loadFilePHIDs(array $conpherences) { $edge_type = PhabricatorObjectHasFileEdgeType::EDGECONST; $file_edges = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(array_keys($conpherences)) ->withEdgeTypes(array($edge_type)) ->execute(); foreach ($file_edges as $conpherence_phid => $data) { $conpherence = $conpherences[$conpherence_phid]; $conpherence->attachFilePHIDs(array_keys($data[$edge_type])); } return $this; } private function loadWidgetData(array $conpherences) { $participant_phids = array(); $file_phids = array(); foreach ($conpherences as $conpherence) { $participant_phids[] = array_keys($conpherence->getParticipants()); $file_phids[] = $conpherence->getFilePHIDs(); } $participant_phids = array_mergev($participant_phids); $file_phids = array_mergev($file_phids); $epochs = CalendarTimeUtil::getCalendarEventEpochs( $this->getViewer()); $start_epoch = $epochs['start_epoch']; $end_epoch = $epochs['end_epoch']; $statuses = id(new PhabricatorCalendarEventQuery()) ->setViewer($this->getViewer()) ->withInvitedPHIDs($participant_phids) ->withDateRange($start_epoch, $end_epoch) ->execute(); $statuses = mgroup($statuses, 'getUserPHID'); // attached files $files = array(); $file_author_phids = array(); $authors = array(); if ($file_phids) { $files = id(new PhabricatorFileQuery()) ->setViewer($this->getViewer()) ->withPHIDs($file_phids) ->execute(); $files = mpull($files, null, 'getPHID'); $file_author_phids = mpull($files, 'getAuthorPHID', 'getPHID'); $authors = id(new PhabricatorHandleQuery()) ->setViewer($this->getViewer()) ->withPHIDs($file_author_phids) ->execute(); $authors = mpull($authors, null, 'getPHID'); } foreach ($conpherences as $phid => $conpherence) { $participant_phids = array_keys($conpherence->getParticipants()); $statuses = array_select_keys($statuses, $participant_phids); $statuses = array_mergev($statuses); $statuses = msort($statuses, 'getDateFrom'); $conpherence_files = array(); $files_authors = array(); foreach ($conpherence->getFilePHIDs() as $curr_phid) { $curr_file = idx($files, $curr_phid); if (!$curr_file) { // this file was deleted or user doesn't have permission to see it // this is generally weird continue; } $conpherence_files[$curr_phid] = $curr_file; // some files don't have authors so be careful $current_author = null; $current_author_phid = idx($file_author_phids, $curr_phid); if ($current_author_phid) { $current_author = $authors[$current_author_phid]; } $files_authors[$curr_phid] = $current_author; } $widget_data = array( 'statuses' => $statuses, 'files' => $conpherence_files, 'files_authors' => $files_authors, ); $conpherence->attachWidgetData($widget_data); } return $this; } public function getQueryApplicationClass() { return 'PhabricatorConpherenceApplication'; } } diff --git a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php index 636504639e..e57e73c1d2 100644 --- a/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php +++ b/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php @@ -1,185 +1,198 @@ setParameter( 'participantPHIDs', $this->readUsersFromRequest($request, 'participants')); + $saved->setParameter('fulltext', $request->getStr('fulltext')); + $saved->setParameter( 'threadType', $request->getStr('threadType')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new ConpherenceThreadQuery()) ->needParticipantCache(true); $participant_phids = $saved->getParameter('participantPHIDs', array()); if ($participant_phids && is_array($participant_phids)) { $query->withParticipantPHIDs($participant_phids); } + $fulltext = $saved->getParameter('fulltext'); + if (strlen($fulltext)) { + $query->withFulltext($fulltext); + } + $thread_type = $saved->getParameter('threadType'); if (idx($this->getTypeOptions(), $thread_type)) { switch ($thread_type) { case 'rooms': $query->withIsRoom(true); break; case 'messages': $query->withIsRoom(false); break; case 'both': $query->withIsRoom(null); break; } } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved) { $participant_phids = $saved->getParameter('participantPHIDs', array()); + $fulltext = $saved->getParameter('fulltext'); $form ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorPeopleDatasource()) ->setName('participants') ->setLabel(pht('Participants')) ->setValue($participant_phids)) + ->appendControl( + id(new AphrontFormTextControl()) + ->setName('fulltext') + ->setLabel(pht('Contains Words')) + ->setValue($fulltext)) ->appendControl( id(new AphrontFormSelectControl()) ->setLabel(pht('Type')) ->setName('threadType') ->setOptions($this->getTypeOptions()) ->setValue($saved->getParameter('threadType'))); } protected function getURI($path) { return '/conpherence/search/'.$path; } protected function getBuiltinQueryNames() { $names = array(); $names = array( 'all' => pht('All Rooms'), ); if ($this->requireViewer()->isLoggedIn()) { $names['participant'] = pht('Joined Rooms'); $names['messages'] = pht('All Messages'); } return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'all': $query->setParameter('threadType', 'rooms'); return $query; case 'participant': $query->setParameter('threadType', 'rooms'); return $query->setParameter( 'participantPHIDs', array($this->requireViewer()->getPHID())); case 'messages': $query->setParameter('threadType', 'messages'); return $query->setParameter( 'participantPHIDs', array($this->requireViewer()->getPHID())); } return parent::buildSavedQueryFromBuiltin($query_key); } protected function getRequiredHandlePHIDsForResultList( array $conpherences, PhabricatorSavedQuery $query) { $recent = mpull($conpherences, 'getRecentParticipantPHIDs'); return array_unique(array_mergev($recent)); } protected function renderResultList( array $conpherences, PhabricatorSavedQuery $query, array $handles) { assert_instances_of($conpherences, 'ConpherenceThread'); $viewer = $this->requireViewer(); $policy_objects = ConpherenceThread::loadPolicyObjects( $viewer, $conpherences); $list = new PHUIObjectItemListView(); $list->setUser($viewer); foreach ($conpherences as $conpherence) { $created = phabricator_date($conpherence->getDateCreated(), $viewer); $data = $conpherence->getDisplayData($viewer); $title = $data['title']; if ($conpherence->getIsRoom()) { $icon_name = $conpherence->getPolicyIconName($policy_objects); } else { $icon_name = 'fa-envelope-o'; } $icon = id(new PHUIIconView()) ->setIconFont($icon_name); $item = id(new PHUIObjectItemView()) ->setObjectName($conpherence->getMonogram()) ->setHeader($title) ->setHref('/conpherence/'.$conpherence->getID().'/') ->setObject($conpherence) ->addIcon('none', $created) ->addIcon( 'none', pht('Messages: %d', $conpherence->getMessageCount())) ->addAttribute( array( $icon, ' ', pht( 'Last updated %s', phabricator_datetime($conpherence->getDateModified(), $viewer)), )); $list->addItem($item); } return $list; } private function getTypeOptions() { return array( 'rooms' => pht('Rooms'), 'messages' => pht('Messages'), 'both' => pht('Both'), ); } }