Path: blob/master/src/applications/conpherence/query/ConpherenceThreadSearchEngine.php
12256 views
<?php12final class ConpherenceThreadSearchEngine3extends PhabricatorApplicationSearchEngine {45public function getResultTypeDescription() {6return pht('Conpherence Rooms');7}89public function getApplicationClassName() {10return 'PhabricatorConpherenceApplication';11}1213public function newQuery() {14return id(new ConpherenceThreadQuery())15->needProfileImage(true);16}1718protected function buildCustomSearchFields() {19return array(20id(new PhabricatorUsersSearchField())21->setLabel(pht('Participants'))22->setKey('participants')23->setAliases(array('participant')),24id(new PhabricatorSearchDatasourceField())25->setLabel(pht('Rooms'))26->setKey('phids')27->setDescription(pht('Search by room titles.'))28->setDatasource(id(new ConpherenceThreadDatasource())),29id(new PhabricatorSearchTextField())30->setLabel(pht('Room Contains Words'))31->setKey('fulltext'),32);33}3435protected function getDefaultFieldOrder() {36return array(37'participants',38'...',39);40}4142protected function shouldShowOrderField() {43return false;44}4546protected function buildQueryFromParameters(array $map) {47$query = $this->newQuery();48if ($map['participants']) {49$query->withParticipantPHIDs($map['participants']);50}51if ($map['fulltext']) {52$query->withFulltext($map['fulltext']);53}54if ($map['phids']) {55$query->withPHIDs($map['phids']);56}57return $query;58}5960protected function getURI($path) {61return '/conpherence/search/'.$path;62}6364protected function getBuiltinQueryNames() {65$names = array();6667$names['all'] = pht('All Rooms');6869if ($this->requireViewer()->isLoggedIn()) {70$names['participant'] = pht('Joined Rooms');71}7273return $names;74}7576public function buildSavedQueryFromBuiltin($query_key) {7778$query = $this->newSavedQuery();79$query->setQueryKey($query_key);8081switch ($query_key) {82case 'all':83return $query;84case 'participant':85return $query->setParameter(86'participants',87array($this->requireViewer()->getPHID()));88}8990return parent::buildSavedQueryFromBuiltin($query_key);91}9293protected function renderResultList(94array $conpherences,95PhabricatorSavedQuery $query,96array $handles) {97assert_instances_of($conpherences, 'ConpherenceThread');9899$viewer = $this->requireViewer();100101$policy_objects = ConpherenceThread::loadViewPolicyObjects(102$viewer,103$conpherences);104105$engines = array();106107$fulltext = $query->getParameter('fulltext');108if ($fulltext !== null && strlen($fulltext) && $conpherences) {109$context = $this->loadContextMessages($conpherences, $fulltext);110111$author_phids = array();112foreach ($context as $phid => $messages) {113$conpherence = $conpherences[$phid];114115$engine = id(new PhabricatorMarkupEngine())116->setViewer($viewer)117->setContextObject($conpherence);118119foreach ($messages as $group) {120foreach ($group as $message) {121$xaction = $message['xaction'];122if ($xaction) {123$author_phids[] = $xaction->getAuthorPHID();124$engine->addObject(125$xaction->getComment(),126PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);127}128}129}130$engine->process();131132$engines[$phid] = $engine;133}134135$handles = $viewer->loadHandles($author_phids);136$handles = iterator_to_array($handles);137} else {138$context = array();139}140141$content = array();142$list = new PHUIObjectItemListView();143$list->setUser($viewer);144foreach ($conpherences as $conpherence_phid => $conpherence) {145$created = phabricator_date($conpherence->getDateCreated(), $viewer);146$title = $conpherence->getTitle();147$monogram = $conpherence->getMonogram();148149$icon_name = $conpherence->getPolicyIconName($policy_objects);150$icon = id(new PHUIIconView())151->setIcon($icon_name);152153if ($fulltext === null || !strlen($fulltext)) {154$item = id(new PHUIObjectItemView())155->setObjectName($conpherence->getMonogram())156->setHeader($title)157->setHref('/'.$conpherence->getMonogram())158->setObject($conpherence)159->setImageURI($conpherence->getProfileImageURI())160->addIcon('none', $created)161->addIcon(162'none',163pht('Messages: %d', $conpherence->getMessageCount()))164->addAttribute(165array(166$icon,167' ',168pht(169'Last updated %s',170phabricator_datetime($conpherence->getDateModified(), $viewer)),171));172$list->addItem($item);173} else {174$messages = idx($context, $conpherence_phid);175$box = array();176$list = null;177if ($messages) {178foreach ($messages as $group) {179$rows = array();180foreach ($group as $message) {181$xaction = $message['xaction'];182if (!$xaction) {183continue;184}185186$view = id(new ConpherenceTransactionView())187->setUser($viewer)188->setHandles($handles)189->setMarkupEngine($engines[$conpherence_phid])190->setConpherenceThread($conpherence)191->setConpherenceTransaction($xaction)192->setSearchResult(true)193->addClass('conpherence-fulltext-result');194195if ($message['match']) {196$view->addClass('conpherence-fulltext-match');197}198199$rows[] = $view;200}201$box[] = id(new PHUIBoxView())202->appendChild($rows)203->addClass('conpherence-fulltext-results');204}205}206$header = id(new PHUIHeaderView())207->setHeader($title)208->setHeaderIcon($icon_name)209->setHref('/'.$monogram);210211$content[] = id(new PHUIObjectBoxView())212->setHeader($header)213->appendChild($box);214}215}216217if ($list) {218$content = $list;219} else {220$content = id(new PHUIBoxView())221->addClass('conpherence-search-room-results')222->appendChild($content);223}224225$result = new PhabricatorApplicationSearchResultView();226$result->setContent($content);227$result->setNoDataString(pht('No results found.'));228229return $result;230}231232private function loadContextMessages(array $threads, $fulltext) {233$phids = mpull($threads, 'getPHID');234235// We want to load a few messages for each thread in the result list, to236// show some of the actual content hits to help the user find what they237// are looking for.238239// This method is trying to batch this lookup in most cases, so we do240// between one and "a handful" of queries instead of one per thread in241// most cases. To do this:242//243// - Load a big block of results for all of the threads.244// - If we didn't get a full block back, we have everything that matches245// the query. Sort it out and exit.246// - Otherwise, some threads had a ton of hits, so we might not be247// getting everything we want (we could be getting back 1,000 hits for248// the first thread). Remove any threads which we have enough results249// for and try again.250// - Repeat until we have everything or every thread has enough results.251//252// In the worst case, we could end up degrading to one query per thread,253// but this is incredibly unlikely on real data.254255// Size of the result blocks we're going to load.256$limit = 1000;257258// Number of messages we want for each thread.259$want = 3;260261$need = $phids;262$hits = array();263while ($need) {264$rows = id(new ConpherenceFulltextQuery())265->withThreadPHIDs($need)266->withFulltext($fulltext)267->setLimit($limit)268->execute();269270foreach ($rows as $row) {271$hits[$row['threadPHID']][] = $row;272}273274if (count($rows) < $limit) {275break;276}277278foreach ($need as $key => $phid) {279if (count($hits[$phid]) >= $want) {280unset($need[$key]);281}282}283}284285// Now that we have all the fulltext matches, throw away any extras that we286// aren't going to render so we don't need to do lookups on them.287foreach ($hits as $phid => $rows) {288if (count($rows) > $want) {289$hits[$phid] = array_slice($rows, 0, $want);290}291}292293// For each fulltext match, we want to render a message before and after294// the match to give it some context. We already know the transactions295// before each match because the rows have a "previousTransactionPHID",296// but we need to do one more query to figure out the transactions after297// each match.298299// Collect the transactions we want to find the next transactions for.300$after = array();301foreach ($hits as $phid => $rows) {302foreach ($rows as $row) {303$after[] = $row['transactionPHID'];304}305}306307// Look up the next transactions.308if ($after) {309$after_rows = id(new ConpherenceFulltextQuery())310->withPreviousTransactionPHIDs($after)311->execute();312} else {313$after_rows = array();314}315316// Build maps from PHIDs to the previous and next PHIDs.317$prev_map = array();318$next_map = array();319foreach ($after_rows as $row) {320$next_map[$row['previousTransactionPHID']] = $row['transactionPHID'];321}322323foreach ($hits as $phid => $rows) {324foreach ($rows as $row) {325$prev = $row['previousTransactionPHID'];326if ($prev) {327$prev_map[$row['transactionPHID']] = $prev;328$next_map[$prev] = $row['transactionPHID'];329}330}331}332333// Now we're going to collect the actual transaction PHIDs, in order, that334// we want to show for each thread.335$groups = array();336foreach ($hits as $thread_phid => $rows) {337$rows = ipull($rows, null, 'transactionPHID');338$done = array();339foreach ($rows as $phid => $row) {340if (isset($done[$phid])) {341continue;342}343$done[$phid] = true;344345$group = array();346347// Walk backward, finding all the previous results. We can just keep348// going until we run out of results because we've only loaded things349// that we want to show.350$prev = $phid;351while (true) {352if (!isset($prev_map[$prev])) {353// No previous transaction, so we're done.354break;355}356357$prev = $prev_map[$prev];358359if (isset($rows[$prev])) {360$match = true;361$done[$prev] = true;362} else {363$match = false;364}365366$group[] = array(367'phid' => $prev,368'match' => $match,369);370}371372if (count($group) > 1) {373$group = array_reverse($group);374}375376$group[] = array(377'phid' => $phid,378'match' => true,379);380381$next = $phid;382while (true) {383if (!isset($next_map[$next])) {384break;385}386387$next = $next_map[$next];388389if (isset($rows[$next])) {390$match = true;391$done[$next] = true;392} else {393$match = false;394}395396$group[] = array(397'phid' => $next,398'match' => $match,399);400}401402$groups[$thread_phid][] = $group;403}404}405406// Load all the actual transactions we need.407$xaction_phids = array();408foreach ($groups as $thread_phid => $group) {409foreach ($group as $list) {410foreach ($list as $item) {411$xaction_phids[] = $item['phid'];412}413}414}415416if ($xaction_phids) {417$xactions = id(new ConpherenceTransactionQuery())418->setViewer($this->requireViewer())419->withPHIDs($xaction_phids)420->needComments(true)421->execute();422$xactions = mpull($xactions, null, 'getPHID');423} else {424$xactions = array();425}426427foreach ($groups as $thread_phid => $group) {428foreach ($group as $key => $list) {429foreach ($list as $lkey => $item) {430$xaction = idx($xactions, $item['phid']);431if ($xaction->shouldHide()) {432continue;433}434$groups[$thread_phid][$key][$lkey]['xaction'] = $xaction;435}436}437}438439// TODO: Sort the groups chronologically?440441return $groups;442}443444}445446447