public function execute($par)
 {
     global $wgLanguageCode;
     $this->setHeaders();
     $this->checkPermissions();
     $server = TTMServer::primary();
     if (!$server instanceof SearchableTTMServer) {
         throw new ErrorPageError('tux-sst-nosolr-title', 'tux-sst-nosolr-body');
     }
     $out = $this->getOutput();
     $out->addModuleStyles('jquery.uls.grid');
     $out->addModuleStyles('ext.translate.special.searchtranslations.styles');
     $out->addModuleStyles('ext.translate.special.translate.styles');
     $out->addModules('ext.translate.special.searchtranslations');
     $out->addModules('ext.translate.special.searchtranslations.operatorsuggest');
     TranslateUtils::addSpecialHelpLink($out, 'Help:Extension:Translate#searching');
     $this->opts = $opts = new FormOptions();
     $opts->add('query', '');
     $opts->add('sourcelanguage', $wgLanguageCode);
     $opts->add('language', '');
     $opts->add('group', '');
     $opts->add('grouppath', '');
     $opts->add('filter', '');
     $opts->add('match', '');
     $opts->add('case', '');
     $opts->add('limit', $this->limit);
     $opts->add('offset', 0);
     $opts->fetchValuesFromRequest($this->getRequest());
     $queryString = $opts->getValue('query');
     if ($queryString === '') {
         $this->showEmptySearch();
         return;
     }
     $params = $opts->getAllValues();
     $filter = $opts->getValue('filter');
     try {
         $translationSearch = new CrossLanguageTranslationSearchQuery($params, $server);
         if (in_array($filter, $translationSearch->getAvailableFilters())) {
             if ($opts->getValue('language') === '') {
                 $params['language'] = $this->getLanguage()->getCode();
                 $opts->setValue('language', $params['language']);
             }
             $documents = $translationSearch->getDocuments();
             $total = $translationSearch->getTotalHits();
             $resultset = $translationSearch->getResultSet();
         } else {
             $resultset = $server->search($queryString, $params, $this->hl);
             $documents = $server->getDocuments($resultset);
             $total = $server->getTotalHits($resultset);
         }
     } catch (TTMServerException $e) {
         error_log('Translation search server unavailable:' . $e->getMessage());
         throw new ErrorPageError('tux-sst-solr-offline-title', 'tux-sst-solr-offline-body');
     }
     // Part 1: facets
     $facets = $server->getFacets($resultset);
     $facetHtml = '';
     if (count($facets['language']) > 0) {
         if ($filter !== '') {
             $facets['language'] = array_merge($facets['language'], array($opts->getValue('language') => $total));
         }
         $facetHtml = Html::element('div', array('class' => 'row facet languages', 'data-facets' => FormatJson::encode($this->getLanguages($facets['language'])), 'data-language' => $opts->getValue('language')), $this->msg('tux-sst-facet-language'));
     }
     if (count($facets['group']) > 0) {
         $facetHtml .= Html::element('div', array('class' => 'row facet groups', 'data-facets' => FormatJson::encode($this->getGroups($facets['group'])), 'data-group' => $opts->getValue('group')), $this->msg('tux-sst-facet-group'));
     }
     // Part 2: results
     $resultsHtml = '';
     $title = Title::newFromText($queryString);
     if ($title && !in_array($filter, $translationSearch->getAvailableFilters())) {
         $handle = new MessageHandle($title);
         $code = $handle->getCode();
         $language = $opts->getValue('language');
         if ($handle->isValid() && $code !== '' && $code !== $language) {
             $groupId = $handle->getGroup()->getId();
             $helpers = new TranslationHelpers($title, $groupId);
             $document['wiki'] = wfWikiId();
             $document['localid'] = $handle->getTitleForBase()->getPrefixedText();
             $document['content'] = $helpers->getTranslation();
             $document['language'] = $handle->getCode();
             array_unshift($documents, $document);
             $total++;
         }
     }
     foreach ($documents as $document) {
         $text = $document['content'];
         $text = TranslateUtils::convertWhiteSpaceToHTML($text);
         list($pre, $post) = $this->hl;
         $text = str_replace($pre, '<strong class="tux-search-highlight">', $text);
         $text = str_replace($post, '</strong>', $text);
         $title = Title::newFromText($document['localid'] . '/' . $document['language']);
         if (!$title) {
             // Should not ever happen but who knows...
             continue;
         }
         $resultAttribs = array('class' => 'row tux-message', 'data-title' => $title->getPrefixedText(), 'data-language' => $document['language']);
         $handle = new MessageHandle($title);
         if ($handle->isValid()) {
             $groupId = $handle->getGroup()->getId();
             $helpers = new TranslationHelpers($title, $groupId);
             $resultAttribs['data-definition'] = $helpers->getDefinition();
             $resultAttribs['data-translation'] = $helpers->getTranslation();
             $resultAttribs['data-group'] = $groupId;
             $uri = $title->getLocalUrl(array('action' => 'edit'));
             $link = Html::element('a', array('href' => $uri), $this->msg('tux-sst-edit')->text());
         } else {
             $url = wfParseUrl($document['uri']);
             $domain = $url['host'];
             $link = Html::element('a', array('href' => $document['uri']), $this->msg('tux-sst-view-foreign', $domain)->text());
         }
         $access = Html::rawElement('div', array('class' => 'row tux-edit tux-message-item'), $link);
         $titleText = $title->getPrefixedText();
         $titleAttribs = array('class' => 'row tux-title', 'dir' => 'ltr');
         $textAttribs = array('class' => 'row tux-text', 'lang' => wfBCP47($document['language']), 'dir' => Language::factory($document['language'])->getDir());
         $resultsHtml = $resultsHtml . Html::openElement('div', $resultAttribs) . Html::rawElement('div', $textAttribs, $text) . Html::element('div', $titleAttribs, $titleText) . $access . Html::closeElement('div');
     }
     $resultsHtml .= Html::rawElement('hr', array('class' => 'tux-pagination-line'));
     $prev = $next = '';
     $offset = $this->opts->getValue('offset');
     $params = $this->opts->getChangedValues();
     if ($total - $offset > $this->limit) {
         $newParams = array('offset' => $offset + $this->limit) + $params;
         $attribs = array('class' => 'mw-ui-button pager-next', 'href' => $this->getPageTitle()->getLocalUrl($newParams));
         $next = Html::element('a', $attribs, $this->msg('tux-sst-next')->text());
     }
     if ($offset) {
         $newParams = array('offset' => max(0, $offset - $this->limit)) + $params;
         $attribs = array('class' => 'mw-ui-button pager-prev', 'href' => $this->getPageTitle()->getLocalUrl($newParams));
         $prev = Html::element('a', $attribs, $this->msg('tux-sst-prev')->text());
     }
     $resultsHtml .= Html::rawElement('div', array('class' => 'tux-pagination-links'), "{$prev} {$next}");
     $search = $this->getSearchInput($queryString);
     $count = $this->msg('tux-sst-count')->numParams($total);
     $this->showSearch($search, $count, $facetHtml, $resultsHtml, $total);
 }
 protected function extractMessages($resultset, $offset, $limit)
 {
     $messages = $documents = $ret = array();
     $language = $this->params['language'];
     foreach ($resultset->getResults() as $document) {
         $data = $document->getData();
         if (!$this->server->isLocalSuggestion($data)) {
             continue;
         }
         $title = Title::newFromText($data['localid']);
         if (!$title) {
             continue;
         }
         $handle = new MessageHandle($title);
         if (!$handle->isValid()) {
             continue;
         }
         $key = $title->getNamespace() . ':' . $title->getDBKey();
         $messages[$key] = $data['content'];
     }
     $definitions = new MessageDefinitions($messages);
     $collection = MessageCollection::newFromDefinitions($definitions, $language);
     $filter = $this->params['filter'];
     if ($filter === 'untranslated') {
         $collection->filter('hastranslation', true);
     } elseif (in_array($filter, $this->getAvailableFilters())) {
         $collection->filter($filter, false);
     }
     $total = count($collection);
     $offset = $collection->slice($offset, $limit);
     $left = count($collection);
     $offsets = array('start' => $offset[2], 'left' => $left, 'total' => $total);
     if ($filter === 'translated' || $filter === 'fuzzy') {
         $collection->loadTranslations();
     }
     foreach ($collection->keys() as $mkey => $title) {
         $documents[$mkey]['content'] = $messages[$mkey];
         if ($filter === 'translated' || $filter === 'fuzzy') {
             $documents[$mkey]['content'] = $collection[$mkey]->translation();
         }
         $handle = new MessageHandle($title);
         $documents[$mkey]['localid'] = $handle->getTitleForBase()->getPrefixedText();
         $documents[$mkey]['language'] = $language;
         $ret[] = $documents[$mkey];
     }
     return array($ret, $offsets);
 }
 protected function parseQueryString($queryString, array $opts)
 {
     $fields = $highlights = array();
     $terms = preg_split('/\\s+/', $queryString);
     $match = $opts['match'];
     $case = $opts['case'];
     // Map each word in the query string with its corresponding field
     foreach ($terms as $term) {
         $prefix = strstr($term, '*', true);
         if ($prefix) {
             // For wildcard search
             $fields['content.prefix_complete'][] = $prefix;
         } elseif ($case === '1') {
             // For case sensitive search
             $fields['content.case_sensitive'][] = $term;
         } else {
             $fields['content'][] = $term;
         }
     }
     // Allow searching either by message content or message id (page name
     // without language subpage) with exact match only.
     $searchQuery = new \Elastica\Query\Bool();
     foreach ($fields as $analyzer => $words) {
         foreach ($words as $word) {
             $boolQuery = new \Elastica\Query\Bool();
             $contentQuery = new \Elastica\Query\Match();
             $contentQuery->setFieldQuery($analyzer, $word);
             $boolQuery->addShould($contentQuery);
             $messageQuery = new \Elastica\Query\Term();
             $messageQuery->setTerm('localid', $word);
             $boolQuery->addShould($messageQuery);
             if ($match === 'all') {
                 $searchQuery->addMust($boolQuery);
             } else {
                 $searchQuery->addShould($boolQuery);
             }
             // Fields for highlighting
             $highlights[$analyzer] = array('number_of_fragments' => 0);
             // Allow searching by exact message title (page name with
             // language subpage).
             $title = Title::newFromText($word);
             if (!$title) {
                 continue;
             }
             $handle = new MessageHandle($title);
             if ($handle->isValid() && $handle->getCode() !== '') {
                 $localid = $handle->getTitleForBase()->getPrefixedText();
                 $boolQuery = new \Elastica\Query\Bool();
                 $messageId = new \Elastica\Query\Term();
                 $messageId->setTerm('localid', $localid);
                 $boolQuery->addMust($messageId);
                 $searchQuery->addShould($boolQuery);
             }
         }
     }
     return array($searchQuery, $highlights);
 }