r86714 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86713‎ | r86714 | r86715 >
Date:16:13, 22 April 2011
Author:nikerabbit
Status:resolved (Comments)
Tags:
Comment:
Allow extensions to customize the search forms. This required some cleanup and refactoring to special:search and search engine.
Should be fully backwards compatible. Lightly tested, but only with MySQL search backend.
Introduces concept of search profiles, which replace long list of namespaces in the url.
Modified paths:
  • /trunk/extensions/Translate/Translate.i18n.php (modified) (history)
  • /trunk/extensions/Translate/Translate.php (modified) (history)
  • /trunk/extensions/Translate/TranslateEditAddons.php (modified) (history)
  • /trunk/phase3/docs/hooks.txt (modified) (history)
  • /trunk/phase3/includes/search/SearchEngine.php (modified) (history)
  • /trunk/phase3/includes/search/SearchMySQL.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialSearch.php (modified) (history)

Diff [purge]

Index: trunk/phase3/docs/hooks.txt
@@ -1665,6 +1665,18 @@
16661666 'SpecialSearchProfiles': allows modification of search profiles
16671667 &$profiles: profiles, which can be modified.
16681668
 1669+'SpecialSearchProfileForm': allows modification of search profile forms
 1670+$search: special page object
 1671+&$form: String: form html
 1672+$profile: String: current search profile
 1673+$term: String: search term
 1674+$opts: Array: key => value of hidden options for inclusion in custom forms
 1675+
 1676+'SpecialSearchSetupEngine': allows passing custom data to search engine
 1677+$search: special page object
 1678+$profile: String: current search profile
 1679+$engine: the search engine
 1680+
16691681 'SpecialSearchResults': called before search result display when there
16701682 are matches
16711683 $term: string of search term
Index: trunk/phase3/includes/search/SearchEngine.php
@@ -22,6 +22,9 @@
2323 var $namespaces = array( NS_MAIN );
2424 var $showRedirects = false;
2525
 26+ /// Feature values
 27+ protected $features = array();
 28+
2629 /**
2730 * @var DatabaseBase
2831 */
@@ -59,12 +62,41 @@
6063 return null;
6164 }
6265
63 - /** If this search backend can list/unlist redirects */
 66+ /**
 67+ * If this search backend can list/unlist redirects
 68+ * @deprecated Call supports( 'list-redirects' );
 69+ */
6470 function acceptListRedirects() {
65 - return true;
 71+ return $this->supports( 'list-redirects' );
6672 }
6773
6874 /**
 75+ * @since 1.18
 76+ * @param $feature String
 77+ * @return Boolean
 78+ */
 79+ public function supports( $feature ) {
 80+ switch( $feature ) {
 81+ case 'list-redirects':
 82+ return true;
 83+ case 'title-suffix-filter':
 84+ default:
 85+ return false;
 86+ }
 87+ }
 88+
 89+ /**
 90+ * Way to pass custom data for engines
 91+ * @since 1.18
 92+ * @param $feature String
 93+ * @param $data Mixed
 94+ * @return Noolean
 95+ */
 96+ public function setFeatureData( $feature, $data ) {
 97+ $this->features[$feature] = $data;
 98+ }
 99+
 100+ /**
69101 * When overridden in derived class, performs database-specific conversions
70102 * on text to be used for searching or updating search index.
71103 * Default implementation does nothing (simply returns $string).
Index: trunk/phase3/includes/search/SearchMySQL.php
@@ -199,15 +199,28 @@
200200 return new MySQLSearchResultSet( $resultSet, $this->searchTerms, $total );
201201 }
202202
 203+ public function supports( $feature ) {
 204+ switch( $feature ) {
 205+ case 'list-redirects':
 206+ case 'title-suffix-filter':
 207+ return true;
 208+ default:
 209+ return false;
 210+ }
 211+ }
203212
204213 /**
205 - * Add redirect conditions
 214+ * Add special conditions
206215 * @param $query Array
207 - * @since 1.18 (changed)
 216+ * @since 1.18
208217 */
209 - function queryRedirect( &$query ) {
210 - if( !$this->showRedirects ) {
211 - $query['conds']['page_is_redirect'] = 0;
 218+ protected function queryFeatures( &$query ) {
 219+ foreach ( $this->features as $feature => $value ) {
 220+ if ( $feature === 'list-redirects' && !$value ) {
 221+ $query['conds']['page_is_redirect'] = 0;
 222+ } elseif( $feature === 'title-suffix-filter' && $value ) {
 223+ $query['conds'][] = 'page_title' . $this->db->buildLike( $this->db->anyString(), $value );
 224+ }
212225 }
213226 }
214227
@@ -253,7 +266,7 @@
254267 );
255268
256269 $this->queryMain( $query, $filteredTerm, $fulltext );
257 - $this->queryRedirect( $query );
 270+ $this->queryFeatures( $query );
258271 $this->queryNamespaces( $query );
259272 $this->limitResult( $query );
260273
@@ -301,7 +314,7 @@
302315 'joins' => array(),
303316 );
304317
305 - $this->queryRedirect( $query );
 318+ $this->queryFeatures( $query );
306319 $this->queryNamespaces( $query );
307320
308321 return $query;
Index: trunk/phase3/includes/specials/SpecialSearch.php
@@ -28,7 +28,14 @@
2929 * @ingroup SpecialPage
3030 */
3131 class SpecialSearch extends SpecialPage {
 32+ /// Current search profile
 33+ protected $profile;
3234
 35+ /// Search engine
 36+ protected $searchEngine;
 37+
 38+ const NAMESPACES_CURRENT = 'sense';
 39+
3340 public function __construct() {
3441 parent::__construct( 'Search' );
3542 }
@@ -75,14 +82,40 @@
7683 public function load( &$request, &$user ) {
7784 list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
7885 $this->mPrefix = $request->getVal('prefix', '');
79 - # Extract requested namespaces
80 - $this->namespaces = $this->powerSearch( $request );
81 - if( empty( $this->namespaces ) ) {
82 - $this->namespaces = SearchEngine::userNamespaces( $user );
 86+
 87+
 88+ # Extract manually requested namespaces
 89+ $nslist = $this->powerSearch( $request );
 90+ $this->profile = $profile = $request->getVal( 'profile', null );
 91+ $profiles = $this->getSearchProfiles();
 92+ if ( $profile === null) {
 93+ // BC with old request format
 94+ $this->profile = 'advanced';
 95+ if ( count( $nslist ) ) {
 96+ foreach( $profiles as $key => $data ) {
 97+ if ( $nslist === $data['namespaces'] && $key !== 'advanced') {
 98+ $this->profile = $key;
 99+ }
 100+ }
 101+ $this->namespaces = $nslist;
 102+ } else {
 103+ $this->namespaces = SearchEngine::userNamespaces( $user );
 104+ }
 105+ } elseif ( $profile === 'advanced' ) {
 106+ $this->namespaces = $nslist;
 107+ } else {
 108+ if ( isset( $profiles[$profile]['namespaces'] ) ) {
 109+ $this->namespaces = $profiles[$profile]['namespaces'];
 110+ } else {
 111+ // Unknown profile requested
 112+ $this->profile = 'default';
 113+ $this->namespaces = $profiles['default']['namespaces'];
 114+ }
83115 }
84 - $this->searchRedirects = $request->getCheck( 'redirs' );
85 - $this->searchAdvanced = $request->getVal( 'advanced' );
86 - $this->active = 'advanced';
 116+
 117+ // Redirects defaults to true, but we don't know whether it was ticked of or just missing
 118+ $default = $request->getBool( 'profile' ) ? 0 : 1;
 119+ $this->searchRedirects = $request->getBool( 'redirs', $default ) ? 1 : 0;
87120 $this->sk = $user->getSkin();
88121 $this->didYouMeanHtml = ''; # html of did you mean... link
89122 $this->fulltext = $request->getVal('fulltext');
@@ -139,14 +172,16 @@
140173
141174 $sk = $wgUser->getSkin();
142175
143 - $this->searchEngine = SearchEngine::create();
144 - $search =& $this->searchEngine;
 176+ $search = $this->getSearchEngine();
145177 $search->setLimitOffset( $this->limit, $this->offset );
146178 $search->setNamespaces( $this->namespaces );
147 - $search->showRedirects = $this->searchRedirects;
 179+ $search->showRedirects = $this->searchRedirects; // BC
 180+ $search->setFeatureData( 'list-redirects', $this->searchRedirects );
148181 $search->prefix = $this->mPrefix;
149182 $term = $search->transformSearchTerm($term);
150183
 184+ wfRunHooks( 'SpecialSearchSetupEngine', array( $this, $this->profile, $search ) );
 185+
151186 $this->setupPage( $term );
152187
153188 if( $wgDisableTextSearch ) {
@@ -216,7 +251,7 @@
217252 Xml::openElement(
218253 'form',
219254 array(
220 - 'id' => ( $this->searchAdvanced ? 'powersearch' : 'search' ),
 255+ 'id' => ( $this->profile === 'advanced' ? 'powersearch' : 'search' ),
221256 'method' => 'get',
222257 'action' => $wgScript
223258 )
@@ -225,7 +260,7 @@
226261 $wgOut->addHtml(
227262 Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
228263 Xml::openElement( 'tr' ) .
229 - Xml::openElement( 'td' ) . "\n" .
 264+ Xml::openElement( 'td' ) . "\n" .
230265 $this->shortDialog( $term ) .
231266 Xml::closeElement('td') .
232267 Xml::closeElement('tr') .
@@ -241,10 +276,8 @@
242277
243278 $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
244279 if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
245 - $wgOut->addHTML( $this->formHeader($term, 0, 0));
246 - if( $this->searchAdvanced ) {
247 - $wgOut->addHTML( $this->powerSearchBox( $term ) );
248 - }
 280+ $wgOut->addHTML( $this->formHeader( $term, 0, 0 ) );
 281+ $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
249282 $wgOut->addHTML( '</form>' );
250283 // Empty query -- straight view of search form
251284 wfProfileOut( __METHOD__ );
@@ -271,11 +304,10 @@
272305 $totalRes += $textMatches->getTotalHits();
273306
274307 // show number of results and current offset
275 - $wgOut->addHTML( $this->formHeader($term, $num, $totalRes));
276 - if( $this->searchAdvanced ) {
277 - $wgOut->addHTML( $this->powerSearchBox( $term ) );
278 - }
 308+ $wgOut->addHTML( $this->formHeader( $term, $num, $totalRes ) );
 309+ $wgOut->addHtml( $this->getProfileForm( $this->profile, $term ) );
279310
 311+
280312 $wgOut->addHtml( Xml::closeElement( 'form' ) );
281313 $wgOut->addHtml( "<div class='searchresults'>" );
282314
@@ -361,21 +393,10 @@
362394 */
363395 protected function setupPage( $term ) {
364396 global $wgOut;
365 - // Figure out the active search profile header
366 - if( $this->searchAdvanced ) {
367 - $this->active = 'advanced';
368 - } else {
369 - $profiles = $this->getSearchProfiles();
370397
371 - foreach( $profiles as $key => $data ) {
372 - if ( $this->namespaces == $data['namespaces'] && $key != 'advanced')
373 - $this->active = $key;
374 - }
375 -
376 - }
377398 # Should advanced UI be used?
378 - $this->searchAdvanced = ($this->active === 'advanced');
379 - if( !empty( $term ) ) {
 399+ $this->searchAdvanced = ($this->profile === 'advanced');
 400+ if( strval( $term ) !== '' ) {
380401 $wgOut->setPageTitle( wfMsg( 'searchresults') );
381402 $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term ) ) );
382403 }
@@ -398,6 +419,7 @@
399420 $arr[] = $ns;
400421 }
401422 }
 423+
402424 return $arr;
403425 }
404426
@@ -412,8 +434,8 @@
413435 $opt['ns' . $n] = 1;
414436 }
415437 $opt['redirs'] = $this->searchRedirects ? 1 : 0;
416 - if( $this->searchAdvanced ) {
417 - $opt['advanced'] = $this->searchAdvanced;
 438+ if( $this->profile ) {
 439+ $opt['profile'] = $this->profile;
418440 }
419441 return $opt;
420442 }
@@ -744,14 +766,28 @@
745767 return $out;
746768 }
747769
 770+ protected function getProfileForm( $profile, $term ) {
 771+ // Hidden stuff
 772+ $opts = array();
 773+ $opts['redirs'] = $this->searchRedirects;
 774+ $opts['profile'] = $this->profile;
748775
 776+ if ( $profile === 'advanced' ) {
 777+ return $this->powerSearchBox( $term, $opts );
 778+ } else {
 779+ $form = '';
 780+ wfRunHooks( 'SpecialSearchProfileForm', array( $this, &$form, $profile, $term, $opts ) );
 781+ return $form;
 782+ }
 783+ }
 784+
749785 /**
750 - * Generates the power search box at bottom of [[Special:Search]]
 786+ * Generates the power search box at [[Special:Search]]
751787 *
752788 * @param $term String: search term
753789 * @return String: HTML form
754790 */
755 - protected function powerSearchBox( $term ) {
 791+ protected function powerSearchBox( $term, $opts ) {
756792 // Groups namespaces into rows according to subject
757793 $rows = array();
758794 foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
@@ -793,14 +829,16 @@
794830 }
795831 // Show redirects check only if backend supports it
796832 $redirects = '';
797 - if( $this->searchEngine->acceptListRedirects() ) {
 833+ if( $this->getSearchEngine()->supports( 'list-redirects' ) ) {
798834 $redirects =
799 - Xml::check(
800 - 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' )
801 - ) .
802 - ' ' .
803 - Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
 835+ Xml::checkLabel( wfMsg( 'powersearch-redir' ), 'redirs', 'redirs', $this->searchRedirects );
804836 }
 837+
 838+ $hidden = '';
 839+ unset( $opts['redirs'] );
 840+ foreach( $opts as $key => $value ) {
 841+ $hidden .= Html::hidden( $key, $value );
 842+ }
805843 // Return final output
806844 return
807845 Xml::openElement(
@@ -835,10 +873,7 @@
836874 Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
837875 $namespaceTables .
838876 Xml::element( 'div', array( 'class' => 'divider' ), '', false ) .
839 - $redirects .
840 - Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
841 - Html::hidden( 'advanced', $this->searchAdvanced ) .
842 - Html::hidden( 'fulltext', 'Advanced search' ) .
 877+ $redirects . $hidden .
843878 Xml::closeElement( 'fieldset' );
844879 }
845880
@@ -876,15 +911,15 @@
877912 'advanced' => array(
878913 'message' => 'searchprofile-advanced',
879914 'tooltip' => 'searchprofile-advanced-tooltip',
880 - 'namespaces' => $this->namespaces,
881 - 'parameters' => array( 'advanced' => 1 ),
 915+ 'namespaces' => self::NAMESPACES_CURRENT,
882916 )
883917 );
884918
885919 wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
886920
887921 foreach( $profiles as &$data ) {
888 - sort($data['namespaces']);
 922+ if ( !is_array( $data['namespaces'] ) ) continue;
 923+ sort( $data['namespaces'] );
889924 }
890925
891926 return $profiles;
@@ -907,19 +942,24 @@
908943 $out .= Xml::openElement( 'div', array( 'class' => 'search-types' ) );
909944 $out .= Xml::openElement( 'ul' );
910945 foreach ( $profiles as $id => $profile ) {
 946+ if ( !isset( $profile['parameters'] ) ) {
 947+ $profile['parameters'] = array();
 948+ }
 949+ $profile['parameters']['profile'] = $id;
 950+
911951 $tooltipParam = isset( $profile['namespace-messages'] ) ?
912952 $wgLang->commaList( $profile['namespace-messages'] ) : null;
913953 $out .= Xml::tags(
914954 'li',
915955 array(
916 - 'class' => $this->active == $id ? 'current' : 'normal'
 956+ 'class' => $this->profile === $id ? 'current' : 'normal'
917957 ),
918958 $this->makeSearchLink(
919959 $bareterm,
920 - $profile['namespaces'],
 960+ array(),
921961 wfMsg( $profile['message'] ),
922962 wfMsg( $profile['tooltip'], $tooltipParam ),
923 - isset( $profile['parameters'] ) ? $profile['parameters'] : array()
 963+ $profile['parameters']
924964 )
925965 );
926966 }
@@ -949,24 +989,14 @@
950990 $out .= Xml::element( 'div', array( 'style' => 'clear:both' ), '', false );
951991 $out .= Xml::closeElement('div');
952992
953 - // Adds hidden namespace fields
954 - if ( !$this->searchAdvanced ) {
955 - foreach( $this->namespaces as $ns ) {
956 - $out .= Html::hidden( "ns{$ns}", '1' );
957 - }
958 - }
959 -
960993 return $out;
961994 }
962995
963996 protected function shortDialog( $term ) {
964 - $searchTitle = SpecialPage::getTitleFor( 'Search' );
965 - $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
966 - // Keep redirect setting
967 - $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n";
 997+ $out = Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . "\n";
968998 // Term box
969999 $out .= Html::input( 'search', $term, 'search', array(
970 - 'id' => $this->searchAdvanced ? 'powerSearchText' : 'searchText',
 1000+ 'id' => $this->profile === 'advanced' ? 'powerSearchText' : 'searchText',
9711001 'size' => '50',
9721002 'autofocus'
9731003 ) ) . "\n";
@@ -979,20 +1009,19 @@
9801010 * Make a search link with some target namespaces
9811011 *
9821012 * @param $term String
983 - * @param $namespaces Array
 1013+ * @param $namespaces Array ignored
9841014 * @param $label String: link's text
9851015 * @param $tooltip String: link's tooltip
9861016 * @param $params Array: query string parameters
9871017 * @return String: HTML fragment
9881018 */
989 - protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) {
 1019+ protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params = array() ) {
9901020 $opt = $params;
9911021 foreach( $namespaces as $n ) {
9921022 $opt['ns' . $n] = 1;
9931023 }
994 - $opt['redirs'] = $this->searchRedirects ? 1 : 0;
 1024+ $opt['redirs'] = $this->searchRedirects;
9951025
996 - $st = SpecialPage::getTitleFor( 'Search' );
9971026 $stParams = array_merge(
9981027 array(
9991028 'search' => $term,
@@ -1004,7 +1033,7 @@
10051034 return Xml::element(
10061035 'a',
10071036 array(
1008 - 'href' => $st->getLocalURL( $stParams ),
 1037+ 'href' => $this->getTitle()->getLocalURL( $stParams ),
10091038 'title' => $tooltip,
10101039 'onmousedown' => 'mwSearchHeaderClick(this);',
10111040 'onkeydown' => 'mwSearchHeaderClick(this);'),
@@ -1044,4 +1073,14 @@
10451074 }
10461075 return false;
10471076 }
 1077+
 1078+ /**
 1079+ * @since 1.18
 1080+ */
 1081+ public function getSearchEngine() {
 1082+ if ( $this->searchEngine === null ) {
 1083+ $this->searchEngine = SearchEngine::create();
 1084+ }
 1085+ return $this->searchEngine;
 1086+ }
10481087 }
Index: trunk/extensions/Translate/TranslateEditAddons.php
@@ -498,4 +498,64 @@
499499 $profiles = wfArrayInsertAfter( $profiles, $insert, 'help' );
500500 return true;
501501 }
 502+
 503+ public static function searchProfileForm( $search, &$form, $profile, $term, $opts ) {
 504+ if ( $profile !== 'translation' ) {
 505+ return true;
 506+ }
 507+
 508+ if( !$search->getSearchEngine()->supports( 'title-suffix-filter' ) ) {
 509+ return false;
 510+ }
 511+
 512+ $hidden = '';
 513+ foreach( $opts as $key => $value ) {
 514+ $hidden .= Html::hidden( $key, $value );
 515+ }
 516+
 517+ $context = $search->getContext();
 518+ $code = $context->getLang()->getCode();
 519+ $selected = $context->getRequest()->getVal( 'languagefilter' );
 520+
 521+ if ( is_callable( array( 'LanguageNames', 'getNames' ) ) ) {
 522+ $languages = LanguageNames::getNames( $code,
 523+ LanguageNames::FALLBACK_NORMAL,
 524+ LanguageNames::LIST_MW
 525+ );
 526+ } else {
 527+ $languages = Language::getLanguageNames( false );
 528+ }
 529+
 530+ ksort( $languages );
 531+
 532+ $selector = new HTMLSelector( 'languagefilter', 'languagefilter', $selected );
 533+ $selector->addOption( wfMessage( 'translate-search-nofilter' ), '-' );
 534+ foreach ( $languages as $code => $name ) {
 535+ $selector->addOption( "$code - $name", $code );
 536+ }
 537+
 538+ $selector = $selector->getHTML();
 539+
 540+ $label = Xml::label( wfMessage( 'translate-search-languagefilter' ), 'languagefilter' ) . '&#160;';
 541+ $params = array( 'id' => 'mw-searchoptions' );
 542+
 543+ $form = Xml::fieldset( false, false, $params ) .
 544+ $hidden . $label . $selector .
 545+ Html::closeElement( 'fieldset' );
 546+ return false;
 547+ }
 548+
 549+ public static function searchProfileSetupEngine( $search, $profile, $engine ) {
 550+ if ( $profile !== 'translation' ) {
 551+ return true;
 552+ }
 553+
 554+ $context = $search->getContext();
 555+ $selected = $context->getRequest()->getVal( 'languagefilter' );
 556+ if ( $selected !== '-' && $selected ) {
 557+ $engine->setFeatureData( 'title-suffix-filter', "/$selected" );
 558+ }
 559+ return true;
 560+ }
 561+
502562 }
Index: trunk/extensions/Translate/Translate.php
@@ -113,6 +113,8 @@
114114
115115 // Search profile
116116 $wgHooks['SpecialSearchProfiles'][] = 'TranslateEditAddons::searchProfile';
 117+$wgHooks['SpecialSearchProfileForm'][] = 'TranslateEditAddons::searchProfileForm';
 118+$wgHooks['SpecialSearchSetupEngine'][] = 'TranslateEditAddons::searchProfileSetupEngine';
117119
118120 // New rights
119121 $wgAvailableRights[] = 'translate';
Index: trunk/extensions/Translate/Translate.i18n.php
@@ -361,6 +361,8 @@
362362 // Search profile hook
363363 'translate-searchprofile' => 'Translations',
364364 'translate-searchprofile-tooltip' => 'Search from all translations',
 365+ 'translate-search-languagefilter' => 'Filter by language:',
 366+ 'translate-search-nofilter' => 'No filtering',
365367 );
366368
367369 /** Message documentation (Message documentation)

Follow-up revisions

RevisionCommit summaryAuthorDate
r94737Amend the documention of search profile, fu r86714nikerabbit09:50, 17 August 2011

Comments

#Comment by Brion VIBBER (talk | contribs)   00:22, 15 June 2011

What exactly are the 'profiles' introduced here? Can you describe & document the concept and how it is exposed to the UI and to the search engine implementations?

#Comment by Nikerabbit (talk | contribs)   20:55, 16 June 2011

What would be a good place to add that documentation?

#Comment by Siebrand (talk | contribs)   22:05, 28 July 2011

Brion, please reply

#Comment by Brion VIBBER (talk | contribs)   22:07, 28 July 2011

First -- here in this commit comment would be nice.

Second -- in doc comments on the code would be nice.

Third -- whoever the audience who has to know what they are should be able to find that information. If we don't know what 'search profiles' are, we can't tell whether that's info that only people working on tweaking Special:Search have to worry about, or whether this is a concept that's being introduced to end-users that they'll have to select from and manage.

#Comment by Nikerabbit (talk | contribs)   09:51, 17 August 2011

Is r94737 good enough?

#Comment by SPQRobin (talk | contribs)   01:28, 6 September 2011

Status & tagging log