hl = [ TranslateUtils::getPlaceholder(), TranslateUtils::getPlaceholder(), ]; } 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->addModuleStyles( [ 'mediawiki.ui.button', 'mediawiki.ui.input', 'mediawiki.ui.checkbox' ] ); $out->addModules( 'ext.translate.special.searchtranslations' ); $out->addModules( 'ext.translate.special.searchtranslations.operatorsuggest' ); $out->addHelpLink( 'Help:Extension:Translate#searching' ); $out->addJsConfigVars( 'wgTranslateLanguages', TranslateUtils::getLanguageNames( null ) ); $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; } $search = $this->getSearchInput( $queryString ); $options = $params = $opts->getAllValues(); $filter = $opts->getValue( 'filter' ); try { if ( $opts->getValue( 'language' ) === '' ) { $options['language'] = $this->getLanguage()->getCode(); } $translationSearch = new CrossLanguageTranslationSearchQuery( $options, $server ); if ( in_array( $filter, $translationSearch->getAvailableFilters() ) ) { if ( $options['language'] === $options['sourcelanguage'] ) { $this->showSearchError( $search, $this->msg( 'tux-sst-error-language' ) ); return; } $opts->setValue( 'language', $options['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 ) { $message = $e->getMessage(); // Known exceptions if ( preg_match( '/^Result window is too large/', $message ) ) { $this->showSearchError( $search, $this->msg( 'tux-sst-error-offset' ) ); return; } // Other exceptions 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 ( $facets['language'] !== [] ) { if ( $filter !== '' ) { $facets['language'] = array_merge( $facets['language'], [ $opts->getValue( 'language' ) => $total ] ); } $facetHtml = Html::element( 'div', [ 'class' => 'row facet languages', 'data-facets' => FormatJson::encode( $this->getLanguages( $facets['language'] ) ), 'data-language' => $opts->getValue( 'language' ), ], $this->msg( 'tux-sst-facet-language' )->text() ); } if ( $facets['group'] !== [] ) { $facetHtml .= Html::element( 'div', [ 'class' => 'row facet groups', 'data-facets' => FormatJson::encode( $this->getGroups( $facets['group'] ) ), 'data-group' => $opts->getValue( 'group' ) ], $this->msg( 'tux-sst-facet-group' )->text() ); } // 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 ( $code !== '' && $code !== $language && $handle->isValid() ) { $dataProvider = new TranslationAidDataProvider( $handle ); $aid = new CurrentTranslationAid( $handle->getGroup(), $handle, $this->getContext(), $dataProvider ); $document = [ 'wiki' => wfWikiID(), 'localid' => $handle->getTitleForBase()->getPrefixedText(), 'content' => $aid->getData()['value'], '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, '', $text ); $text = str_replace( $post, '', $text ); $title = Title::newFromText( $document['localid'] . '/' . $document['language'] ); if ( !$title ) { // Should not ever happen but who knows... continue; } $resultAttribs = [ 'class' => 'row tux-message', 'data-title' => $title->getPrefixedText(), 'data-language' => $document['language'], ]; $handle = new MessageHandle( $title ); if ( $handle->isValid() ) { $uri = TranslateUtils::getEditorUrl( $handle ); $link = Html::element( 'a', [ 'href' => $uri ], $this->msg( 'tux-sst-edit' )->text() ); } else { $url = wfParseUrl( $document['uri'] ); $domain = $url['host']; $link = Html::element( 'a', [ 'href' => $document['uri'] ], $this->msg( 'tux-sst-view-foreign', $domain )->text() ); } $access = Html::rawElement( 'div', [ 'class' => 'row tux-edit tux-message-item' ], $link ); $titleText = $title->getPrefixedText(); $titleAttribs = [ 'class' => 'row tux-title', 'dir' => 'ltr', ]; $language = Language::factory( $document['language'] ); $textAttribs = [ 'class' => 'row tux-text', 'lang' => $language->getHtmlCode(), 'dir' => $language->getDir(), ]; $resultsHtml .= Html::openElement( 'div', $resultAttribs ) . Html::rawElement( 'div', $textAttribs, $text ) . Html::element( 'div', $titleAttribs, $titleText ) . $access . Html::closeElement( 'div' ); } $resultsHtml .= Html::rawElement( 'hr', [ 'class' => 'tux-pagination-line' ] ); $prev = $next = ''; $offset = $this->opts->getValue( 'offset' ); $params = $this->opts->getChangedValues(); if ( $total - $offset > $this->limit ) { $newParams = [ 'offset' => $offset + $this->limit ] + $params; $attribs = [ '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 = [ 'offset' => max( 0, $offset - $this->limit ) ] + $params; $attribs = [ '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', [ 'class' => 'tux-pagination-links' ], "$prev $next" ); $count = $this->msg( 'tux-sst-count' )->numParams( $total )->escaped(); $this->showSearch( $search, $count, $facetHtml, $resultsHtml, $total ); } protected function getLanguages( array $facet ) { $output = []; $nondefaults = $this->opts->getChangedValues(); $selected = $this->opts->getValue( 'language' ); $filter = $this->opts->getValue( 'filter' ); foreach ( $facet as $key => $value ) { if ( $filter !== '' && $key === $selected ) { unset( $nondefaults['language'] ); unset( $nondefaults['filter'] ); } elseif ( $filter !== '' ) { $nondefaults['language'] = $key; $nondefaults['filter'] = $filter; } elseif ( $key === $selected ) { unset( $nondefaults['language'] ); } else { $nondefaults['language'] = $key; } $url = $this->getPageTitle()->getLocalURL( $nondefaults ); $value = $this->getLanguage()->formatNum( $value ); $output[$key] = [ 'count' => $value, 'url' => $url ]; } return $output; } protected function getGroups( array $facet ) { $structure = MessageGroups::getGroupStructure(); return $this->makeGroupFacetRows( $structure, $facet ); } protected function makeGroupFacetRows( array $groups, $counts, $level = 0, $pathString = '' ) { $output = []; $nondefaults = $this->opts->getChangedValues(); $selected = $this->opts->getValue( 'group' ); $path = explode( '|', $this->opts->getValue( 'grouppath' ) ); foreach ( $groups as $mixed ) { $subgroups = $group = $mixed; if ( is_array( $mixed ) ) { $group = array_shift( $subgroups ); } else { $subgroups = []; } '@phan-var MessageGroup $group'; $id = $group->getId(); if ( $id !== $selected && !isset( $counts[$id] ) ) { continue; } if ( $id === $selected ) { unset( $nondefaults['group'] ); $nondefaults['grouppath'] = $pathString; } else { $nondefaults['group'] = $id; $nondefaults['grouppath'] = $pathString . $id; } $value = $counts[$id] ?? 0; $output[$id] = [ 'id' => $id, 'count' => $value, 'label' => $group->getLabel(), ]; if ( isset( $path[$level] ) && $path[$level] === $id ) { $output[$id]['groups'] = $this->makeGroupFacetRows( $subgroups, $counts, $level + 1, "$pathString$id|" ); } } return $output; } protected function showSearch( $search, $count, $facets, $results, $total ) { $messageSelector = $this->messageSelector(); $this->getOutput()->addHTML( <<