00001 <?php
00016 class SpecialSearchTranslations extends SpecialPage {
00018 protected $opts;
00019
00027 protected $hl = array();
00028
00033 protected $limit = 25;
00034
00035 public function __construct() {
00036 parent::__construct( 'SearchTranslations' );
00037 $this->hl = array(
00038 TranslateUtils::getPlaceholder(),
00039 TranslateUtils::getPlaceholder(),
00040 );
00041 }
00042
00043 public function setHeaders() {
00044
00045
00046 $out = $this->getOutput();
00047 $out->setArticleRelated( false );
00048 $out->setRobotPolicy( 'noindex,nofollow' );
00049 $name = $this->msg( 'searchtranslations' );
00050 $name = Sanitizer::stripAllTags( $name );
00051 $out->setHTMLTitle( $this->msg( 'pagetitle' )->rawParams( $name ) );
00052 }
00053
00054 public function execute( $par ) {
00055 $this->setHeaders();
00056 $this->checkPermissions();
00057
00058 $server = TTMServer::primary();
00059 if ( !$server instanceof SolrTTMServer ) {
00060 throw new ErrorPageError( 'tux-sst-nosolr-title', 'tux-sst-nosolr-body' );
00061 }
00062
00063 $out = $this->getOutput();
00064 $out->addModules( 'ext.translate.special.searchtranslations' );
00065
00066 $this->opts = $opts = new FormOptions();
00067 $opts->add( 'query', '' );
00068 $opts->add( 'language', '' );
00069 $opts->add( 'group', '' );
00070 $opts->add( 'grouppath', '' );
00071
00072 $opts->fetchValuesFromRequest( $this->getRequest() );
00073
00074 $queryString = $opts->getValue( 'query' );
00075
00076 if ( $queryString === '' ) {
00077 $this->showEmptySearch();
00078
00079 return;
00080 }
00081
00082 try {
00083 $resultset = $this->doSearch( $server->getSolarium(), $queryString );
00084 } catch ( Solarium_Client_HttpException $e ) {
00085 error_log( 'Translate: Solr search server unavailable' );
00086 throw new ErrorPageError( 'tux-sst-solr-offline-title', 'tux-sst-solr-offline-body' );
00087 }
00088
00089
00090 $facets = '';
00091
00092 $facet = $resultset->getFacetSet()->getFacet( 'language' );
00093
00094 $facets .= Html::element( 'div',
00095 array( 'class' => 'row facet languages',
00096 'data-facets' => FormatJson::encode( $this->getLanguages( $facet ) ),
00097 'data-language' => $opts->getValue( 'language' ),
00098 ),
00099 $this->msg( 'tux-sst-facet-language' )
00100 );
00101
00102 $facet = $resultset->getFacetSet()->getFacet( 'group' );
00103 $facets .= Html::element( 'div',
00104 array( 'class' => 'row facet groups',
00105 'data-facets' => FormatJson::encode( $this->getGroups( $facet ) ),
00106 'data-group' => $opts->getValue( 'group' ), ),
00107 $this->msg( 'tux-sst-facet-group' )
00108 );
00109
00110
00111 $results = '';
00112
00113 $highlighting = $resultset->getHighlighting();
00114 foreach ( $resultset as $document ) {
00115
00116 $hdoc = $highlighting->getResult( $document->globalid );
00117 $text = $hdoc->getField( 'text' );
00118 if ( $text === array() ) {
00119 $text = $document->text;
00120 $text = htmlspecialchars( $text );
00121 } else {
00122 $text = $text[0];
00123 $text = htmlspecialchars( $text );
00124
00125 list( $pre, $post ) = $this->hl;
00126 $text = str_replace( $pre, '<strong class="tux-highlight">', $text );
00127 $text = str_replace( $post, '</strong>', $text );
00128 }
00129
00130 $title = Title::newFromText( $document->messageid . '/' . $document->language );
00131 if ( !$title ) {
00132
00133 continue;
00134 }
00135
00136 $resultAttribs = array(
00137 'class' => 'row tux-message',
00138 'data-title' => $title->getPrefixedText(),
00139 'data-language' => $document->language,
00140 );
00141
00142 $handle = new MessageHandle( $title );
00143 if ( $handle->isValid() ) {
00144 $groupId = $handle->getGroup()->getId();
00145 $helpers = new TranslationHelpers( $title, $groupId );
00146 $resultAttribs['data-definition'] = $helpers->getDefinition();
00147 $resultAttribs['data-translation'] = $helpers->getTranslation();
00148 $resultAttribs['data-group'] = $groupId;
00149 }
00150
00151 $result = Html::openElement( 'div', $resultAttribs );
00152 $result .= Html::rawElement( 'div', array( 'class' => 'row tux-text' ), $text );
00153 $result .= Html::element(
00154 'div',
00155 array( 'class' => 'row tux-title' ),
00156 $document->messageid
00157 );
00158
00159 if ( $handle->isValid() ) {
00160 $uri = wfAppendQuery( $document->uri, array( 'action' => 'edit' ) );
00161 $link = Html::element( 'a', array(
00162 'href' => $uri,
00163 ), $this->msg( 'tux-sst-edit' )->text() );
00164 $result .= Html::rawElement(
00165 'div',
00166 array( 'class' => 'row tux-edit tux-message-item' ),
00167 $link
00168 );
00169 }
00170
00171 $result .= Html::closeElement( 'div' );
00172 $results .= $result;
00173 }
00174
00175 $prev = $next = '';
00176 $total = $resultset->getNumFound();
00177 $offset = $this->getRequest()->getInt( 'offset' );
00178 $params = $this->getRequest()->getValues();
00179
00180 if ( $total - $offset > $this->limit ) {
00181 $newParams = array( 'offset' => $offset + $this->limit ) + $params;
00182 $attribs = array(
00183 'class' => 'pager-next',
00184 'href' => $this->getTitle()->getLocalUrl( $newParams ),
00185 );
00186 $next = Html::element( 'a', $attribs, $this->msg( 'tux-sst-next' )->text() );
00187 }
00188 if ( $offset ) {
00189 $newParams = array( 'offset' => max( 0, $offset - $this->limit ) ) + $params;
00190 $attribs = array(
00191 'class' => 'pager-prev',
00192 'href' => $this->getTitle()->getLocalUrl( $newParams ),
00193 );
00194 $prev = Html::element( 'a', $attribs, $this->msg( 'tux-sst-prev' )->text() );
00195 }
00196
00197 $results .= Html::rawElement( 'div', array(), "$prev $next" );
00198
00199 $search = $this->getSearchInput( $queryString );
00200 $count = $this->msg( 'tux-sst-count' )->numParams( $resultset->getNumFound() );
00201
00202 $this->showSearch( $search, $count, $facets, $results );
00203 }
00204
00205 protected function doSearch( Solarium_Client $client, $queryString ) {
00206 $query = $client->createSelect();
00207 $dismax = $query->getDisMax();
00208 $dismax->setQueryParser( 'edismax' );
00209 $query->setQuery( $queryString );
00210 $query->setRows( $this->limit );
00211 $query->setStart( $this->getRequest()->getInt( 'offset' ) );
00212
00213 list( $pre, $post ) = $this->hl;
00214 $hl = $query->getHighlighting();
00215 $hl->setFields( 'text' );
00216 $hl->setSimplePrefix( $pre );
00217 $hl->setSimplePostfix( $post );
00218 $hl->setMaxAnalyzedChars( '5000' );
00219 $hl->setFragSize( '5000' );
00220 $hl->setSnippets( 1 );
00221
00222 $languageFilter = $this->opts->getValue( 'language' );
00223 if ( $languageFilter !== '' ) {
00224 $query->createFilterQuery( 'languageFilter' )
00225 ->setQuery( 'language:%P1%', array( $languageFilter ) )
00226 ->addTag( 'filter' );
00227 }
00228
00229 $groupFilter = $this->opts->getValue( 'group' );
00230 if ( $groupFilter !== '' ) {
00231 $query->createFilterQuery( 'groupFilter' )
00232 ->setQuery( 'group:%P1%', array( $groupFilter ) )
00233 ->addTag( 'filter' );
00234 }
00235
00236 $facetSet = $query->getFacetSet();
00237
00238 $language = $facetSet->createFacetField( 'language' );
00239 $language->setField( 'language' );
00240 $language->setMincount( 1 );
00241 $language->addExclude( 'filter' );
00242
00243 $group = $facetSet->createFacetField( 'group' );
00244 $group->setField( 'group' );
00245 $group->setMincount( 1 );
00246 $group->setMissing( true );
00247 $group->addExclude( 'filter' );
00248
00249 return $client->select( $query );
00250 }
00251
00252 protected function getLanguages( Solarium_Result_Select_Facet_Field $facet ) {
00253 $output = array();
00254
00255 $nondefaults = $this->opts->getChangedValues();
00256 $selected = $this->opts->getValue( 'language' );
00257
00258 foreach ( $facet as $key => $value ) {
00259 if ( $key === $selected ) {
00260 unset( $nondefaults['language'] );
00261 } else {
00262 $nondefaults['language'] = $key;
00263 }
00264
00265 $url = $this->getTitle()->getLocalUrl( $nondefaults );
00266 $value = $this->getLanguage()->formatNum( $value );
00267
00268 $output[$key] = array(
00269 'count' => $value,
00270 'url' => $url
00271 );
00272 }
00273
00274 return $output;
00275 }
00276
00277 protected function getGroups( Solarium_Result_Select_Facet_Field $facet ) {
00278 $structure = MessageGroups::getGroupStructure();
00279 $counts = iterator_to_array( $facet );
00280
00281 return $this->makeGroupFacetRows( $structure, $counts );
00282 }
00283
00284 protected function makeGroupFacetRows( array $groups, $counts, $level = 0, $pathString = '' ) {
00285 $output = array();
00286
00287 $nondefaults = $this->opts->getChangedValues();
00288 $selected = $this->opts->getValue( 'group' );
00289 $path = explode( '|', $this->opts->getValue( 'grouppath' ) );
00290
00291 foreach ( $groups as $mixed ) {
00292 $subgroups = $group = $mixed;
00293
00294 if ( is_array( $mixed ) ) {
00295 $group = array_shift( $subgroups );
00296 } else {
00297 $subgroups = array();
00298 }
00299
00300 $id = $group->getId();
00301
00302 if ( $id !== $selected && !isset( $counts[$id] ) ) {
00303 continue;
00304 }
00305
00306 if ( $id === $selected ) {
00307 unset( $nondefaults['group'] );
00308 $nondefaults['grouppath'] = $pathString;
00309 } else {
00310 $nondefaults['group'] = $id;
00311 $nondefaults['grouppath'] = $pathString . $id;
00312 }
00313
00314 $url = $this->getTitle()->getLocalUrl( $nondefaults );
00315
00316 $value = isset( $counts[$id] ) ? $counts[$id] : 0;
00317 $count = $this->getLanguage()->formatNum( $value );
00318
00319 $output[$id] = array(
00320 'id' => $id,
00321 'count' => $count,
00322 'url' => $url,
00323 'label' => $group->getLabel(),
00324 'description' => $group->getDescription(),
00325 'icon' => TranslateUtils::getIcon( $group, 100 ),
00326 );
00327
00328 if ( isset( $path[$level] ) && $path[$level] === $id ) {
00329 $output[$id]['groups'] = $this->makeGroupFacetRows(
00330 $subgroups,
00331 $counts,
00332 $level + 1,
00333 "$pathString$id|"
00334 );
00335 }
00336 }
00337
00338 return $output;
00339 }
00340
00341 protected function showSearch( $search, $count, $facets, $results ) {
00342 $this->getOutput()->addHtml( <<<HTML
00343 <div class="grid tux-searchpage">
00344 <div class="row searchinput">
00345 <div class="nine columns offset-by-three">$search</div>
00346 </div>
00347 <div class="row count">
00348 <div class="nine columns offset-by-three">$count</div>
00349 </div>
00350 <div class="row searchcontent">
00351 <div class="three columns facets">$facets</div>
00352 <div class="nine columns results">$results</div>
00353 </div>
00354 </div>
00355 HTML
00356 );
00357 }
00358
00359 protected function showEmptySearch() {
00360 $search = $this->getSearchInput( '' );
00361 $this->getOutput()->addHtml( <<<HTML
00362 <div class="grid tux-searchpage">
00363 <div class="row searchinput">
00364 <div class="nine columns offset-by-three">$search</div>
00365 </div>
00366 </div>
00367 HTML
00368 );
00369 }
00370
00371 protected function getSearchInput( $query ) {
00372 $attribs = array(
00373 'placeholder' => $this->msg( 'tux-sst-search-ph' ),
00374 'class' => 'searchinputbox',
00375 );
00376
00377 $title = Html::hidden( 'title', $this->getTitle()->getPrefixedText() );
00378 $input = Xml::input( 'query', false, $query, $attribs );
00379 $submit = Xml::submitButton( $this->msg( 'tux-sst-search' ), array( 'class' => 'button' ) );
00380
00381 $form = Html::rawElement( 'form', array( 'action' => wfScript() ),
00382 $title . $input . $submit
00383 );
00384
00385 return $form;
00386 }
00387 }