Search: Provide new preference to control redirects on search matches
To avoid preference bloat, this preference is hidden unless the new sysadmin config $wgSearchMatchRedirectPreference is set. Bug: T235263 Change-Id: Ic16f53a4e6ddb6da071d63cd5da28d937d4692c8
This commit is contained in:
parent
d30dc86558
commit
c4eae0dad4
11 changed files with 148 additions and 4 deletions
|
|
@ -32,6 +32,12 @@ For notes on 1.34.x and older releases, see HISTORY.
|
|||
* $wgDiffEngine can be used to specify the difference engine to use, rather
|
||||
than choosing the first of $wgExternalDiffEngine, wikidiff2, or php that
|
||||
is usable.
|
||||
* $wgSearchMatchRedirectPreference – This configuration setting controls whether
|
||||
users can set a new preference, search-match-redirect, which decides if search
|
||||
should redirect them to exact matches is available. By default, this is set to
|
||||
false, which maintains the previous behaviour without preference bloat. Change
|
||||
your site's default by setting $wgDefaultUserOptions['search-match-redirect'].
|
||||
* …
|
||||
|
||||
==== Changed configuration ====
|
||||
* $wgResourceLoaderMaxage (T235314) - This configuration array controls the
|
||||
|
|
|
|||
|
|
@ -4862,6 +4862,7 @@ $wgDefaultUserOptions = [
|
|||
'rcenhancedfilters-disable' => 0,
|
||||
'rclimit' => 50,
|
||||
'rows' => 25, // @deprecated since 1.29 No longer used in core
|
||||
'search-match-redirect' => true,
|
||||
'showhiddencats' => 0,
|
||||
'shownumberswatching' => 1,
|
||||
'showrollbackconfirmation' => 0,
|
||||
|
|
@ -9079,6 +9080,17 @@ $wgFeaturePolicyReportOnly = [];
|
|||
*/
|
||||
$wgSpecialSearchFormOptions = [];
|
||||
|
||||
/**
|
||||
* Set true to allow logged-in users to set a preference whether or not matches in
|
||||
* search results should force redirection to that page. If false, the preference is
|
||||
* not exposed and cannot be altered from site default. To change your site's default
|
||||
* preference, set via $wgDefaultUserOptions['search-match-redirect'].
|
||||
*
|
||||
* @since 1.35
|
||||
* @var bool
|
||||
*/
|
||||
$wgSearchMatchRedirectPreference = false;
|
||||
|
||||
/**
|
||||
* Toggles native image lazy loading, via the "loading" attribute.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ class DefaultPreferencesFactory implements PreferencesFactory {
|
|||
'RCMaxAge',
|
||||
'RCShowWatchingUsers',
|
||||
'RCWatchCategoryMembership',
|
||||
'SearchMatchRedirectPreference',
|
||||
'SecureLogin',
|
||||
'ThumbLimits',
|
||||
];
|
||||
|
|
@ -1279,6 +1280,19 @@ class DefaultPreferencesFactory implements PreferencesFactory {
|
|||
'type' => 'api',
|
||||
];
|
||||
}
|
||||
|
||||
if ( $this->options->get( 'SearchMatchRedirectPreference' ) ) {
|
||||
$defaultPreferences['search-match-redirect'] = [
|
||||
'type' => 'toggle',
|
||||
'section' => 'searchoptions',
|
||||
'label-message' => 'search-match-redirect-label',
|
||||
'help-message' => 'search-match-redirect-help',
|
||||
];
|
||||
} else {
|
||||
$defaultPreferences['search-match-redirect'] = [
|
||||
'type' => 'api',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -277,6 +277,9 @@ class SpecialSearch extends SpecialPage {
|
|||
* @return string|null The url to redirect to, or null if no redirect.
|
||||
*/
|
||||
public function goResult( $term ) {
|
||||
if ( !$this->redirectOnExactMatch() ) {
|
||||
return null;
|
||||
}
|
||||
# If the string cannot be used to create a title
|
||||
if ( is_null( Title::newFromText( $term ) ) ) {
|
||||
return null;
|
||||
|
|
@ -295,6 +298,18 @@ class SpecialSearch extends SpecialPage {
|
|||
return $url ?? $title->getFullUrlForRedirect();
|
||||
}
|
||||
|
||||
private function redirectOnExactMatch() {
|
||||
global $wgSearchMatchRedirectPreference;
|
||||
if ( !$wgSearchMatchRedirectPreference ) {
|
||||
// If the preference for whether to redirect is disabled, use the default setting
|
||||
$defaultOptions = $this->getUser()->getDefaultOptions();
|
||||
return $defaultOptions['search-match-redirect'];
|
||||
} else {
|
||||
// Otherwise use the user's preference
|
||||
return $this->getUser()->getOption( 'search-match-redirect' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $term
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1705,7 +1705,9 @@ class User implements IDBAccessObject, UserIdentity {
|
|||
* @return array Array of String options
|
||||
*/
|
||||
public static function getDefaultOptions() {
|
||||
global $wgNamespacesToBeSearchedDefault, $wgDefaultUserOptions, $wgDefaultSkin;
|
||||
global $wgNamespacesToBeSearchedDefault,
|
||||
$wgDefaultUserOptions,
|
||||
$wgDefaultSkin;
|
||||
|
||||
$contLang = MediaWikiServices::getInstance()->getContentLanguage();
|
||||
if ( self::$defOpt !== null && self::$defOptLang === $contLang->getCode() ) {
|
||||
|
|
|
|||
|
|
@ -3922,6 +3922,8 @@
|
|||
"feedback-useragent": "User agent:",
|
||||
"searchsuggest-search": "Search {{SITENAME}}",
|
||||
"searchsuggest-containing": "containing...",
|
||||
"search-match-redirect-label": "Redirect to exact matches when searching",
|
||||
"search-match-redirect-help": "Select to get redirected to a page when that page title matches what you have searched for",
|
||||
"api-error-badtoken": "Internal error: Bad token.",
|
||||
"api-error-emptypage": "Creating new, empty pages is not allowed.",
|
||||
"api-error-publishfailed": "Internal error: Server failed to publish temporary file.",
|
||||
|
|
|
|||
|
|
@ -4134,6 +4134,8 @@
|
|||
"feedback-useragent": "A label denoting the user agent in the feedback that is posted to the feedback page.\n{{Identical|User agent}}",
|
||||
"searchsuggest-search": "Greyed out default text in the simple search box in the Vector skin. (It disappears and lets the user enter the requested search terms when the search box receives focus.)\n{{Identical|Search}}",
|
||||
"searchsuggest-containing": "Label used in the special item of the search suggestions list which gives the user an option to perform a full text search for the term.",
|
||||
"search-match-redirect-label": "Label for user preference to force redirect to a page during search if the page's title matches a search term",
|
||||
"search-match-redirect-help": "Help text for user preference to force redirect to a page during search if the page's title matches a search term",
|
||||
"api-error-badtoken": "API error message that can be used for client side localisation of API errors.",
|
||||
"api-error-emptypage": "API error message that can be used for client side localisation of API errors.",
|
||||
"api-error-publishfailed": "API error message that can be used for client side localisation of API errors.",
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@
|
|||
index: context.config.suggestions.indexOf( query )
|
||||
} );
|
||||
|
||||
if ( $el.children().length === 0 ) {
|
||||
if ( mw.user.options.get( 'search-match-redirect' ) && $el.children().length === 0 ) {
|
||||
$el
|
||||
.append(
|
||||
$( '<div>' )
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ class DefaultPreferencesFactoryTest extends \MediaWikiTestCase {
|
|||
|
||||
/**
|
||||
* @covers MediaWiki\Preferences\DefaultPreferencesFactory::getForm()
|
||||
* @covers MediaWiki\Preferences\DefaultPreferencesFactory::searchPreferences()
|
||||
*/
|
||||
public function testGetForm() {
|
||||
$this->setTemporaryHook( 'GetPreferences', null );
|
||||
|
|
|
|||
|
|
@ -238,13 +238,26 @@ class SpecialSearchTest extends MediaWikiTestCase {
|
|||
|
||||
protected function mockSearchEngine( $results ) {
|
||||
$mock = $this->getMockBuilder( SearchEngine::class )
|
||||
->setMethods( [ 'searchText', 'searchTitle' ] )
|
||||
->setMethods( [ 'searchText', 'searchTitle', 'getNearMatcher' ] )
|
||||
->getMock();
|
||||
|
||||
$mock->expects( $this->any() )
|
||||
->method( 'searchText' )
|
||||
->will( $this->returnValue( $results ) );
|
||||
|
||||
$nearMatcherMock = $this->getMockBuilder( SearchNearMatcher::class )
|
||||
->disableOriginalConstructor()
|
||||
->setMethods( [ 'getNearMatch' ] )
|
||||
->getMock();
|
||||
|
||||
$nearMatcherMock->expects( $this->any() )
|
||||
->method( 'getNearMatch' )
|
||||
->willReturn( $results->getFirstResult() );
|
||||
|
||||
$mock->expects( $this->any() )
|
||||
->method( 'getNearMatcher' )
|
||||
->willReturn( $nearMatcherMock );
|
||||
|
||||
return $mock;
|
||||
}
|
||||
|
||||
|
|
@ -267,6 +280,62 @@ class SpecialSearchTest extends MediaWikiTestCase {
|
|||
$this->assertEquals( 'Special:Search', $query['title'] );
|
||||
$this->assertEquals( 'foo bar', $query['search'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the 'search-match-redirect' user pref is false, then SpecialSearch::goResult() should
|
||||
* return null
|
||||
*
|
||||
* @covers SpecialSearch::goResult
|
||||
*/
|
||||
public function testGoResult_userPrefRedirectOn() {
|
||||
$context = new RequestContext;
|
||||
$context->setUser(
|
||||
$this->newUserWithSearchNS( [ 'search-match-redirect' => false ] )
|
||||
);
|
||||
$context->setRequest(
|
||||
new FauxRequest( [ 'search' => 'TEST_SEARCH_PARAM', 'fulltext' => 1 ] )
|
||||
);
|
||||
$search = new SpecialSearch();
|
||||
$search->setContext( $context );
|
||||
$search->load();
|
||||
|
||||
$this->assertNull( $search->goResult( 'TEST_SEARCH_PARAM' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the 'search-match-redirect' user pref is true, then SpecialSearch::goResult() should
|
||||
* NOT return null if there is a near match found for the search term
|
||||
*
|
||||
* @covers SpecialSearch::goResult
|
||||
*/
|
||||
public function testGoResult_userPrefRedirectOff() {
|
||||
// mock the search engine so it returns a near match for an arbitrary search term
|
||||
$searchResults = new SpecialSearchTestMockResultSet(
|
||||
'TEST_SEARCH_SUGGESTION',
|
||||
'',
|
||||
[ SearchResult::newFromTitle( Title::newMainPage() ) ]
|
||||
);
|
||||
$mockSearchEngine = $this->mockSearchEngine( $searchResults );
|
||||
$search = $this->getMockBuilder( SpecialSearch::class )
|
||||
->setMethods( [ 'getSearchEngine' ] )
|
||||
->getMock();
|
||||
$search->expects( $this->any() )
|
||||
->method( 'getSearchEngine' )
|
||||
->will( $this->returnValue( $mockSearchEngine ) );
|
||||
|
||||
// set up a mock user with 'search-match-redirect' set to true
|
||||
$context = new RequestContext;
|
||||
$context->setUser(
|
||||
$this->newUserWithSearchNS( [ 'search-match-redirect' => true ] )
|
||||
);
|
||||
$context->setRequest(
|
||||
new FauxRequest( [ 'search' => 'TEST_SEARCH_PARAM', 'fulltext' => 1 ] )
|
||||
);
|
||||
$search->setContext( $context );
|
||||
$search->load();
|
||||
|
||||
$this->assertNotNull( $search->goResult( 'TEST_SEARCH_PARAM' ) );
|
||||
}
|
||||
}
|
||||
|
||||
class SpecialSearchTestMockResultSet extends SearchResultSet {
|
||||
|
|
@ -316,4 +385,11 @@ class SpecialSearchTestMockResultSet extends SearchResultSet {
|
|||
public function getQueryAfterRewriteSnippet() {
|
||||
return htmlspecialchars( $this->rewrittenQuery );
|
||||
}
|
||||
|
||||
public function getFirstResult() {
|
||||
if ( count( $this->results ) === 0 ) {
|
||||
return null;
|
||||
}
|
||||
return $this->results[0]->getTitle();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1656,7 +1656,12 @@ class UserTest extends MediaWikiTestCase {
|
|||
return [
|
||||
'Basic creation' => [ 'missing', [], [], 'user' ],
|
||||
'No creation' => [ 'missing', [ 'create' => false ], [], 'null' ],
|
||||
'Validation fail' => [ 'missing', [ 'validate' => 'usable' ], [ 'reserved' => true ], 'null' ],
|
||||
'Validation fail' => [
|
||||
'missing',
|
||||
[ 'validate' => 'usable' ],
|
||||
[ 'reserved' => true ],
|
||||
'null'
|
||||
],
|
||||
'No stealing' => [ 'user', [], [], 'null' ],
|
||||
'Stealing allowed' => [ 'user', [ 'steal' => true ], [], 'user' ],
|
||||
'Stealing an already-system user' => [ 'system', [ 'steal' => true ], [], 'user' ],
|
||||
|
|
@ -1666,4 +1671,13 @@ class UserTest extends MediaWikiTestCase {
|
|||
'Anonymous actor but not reserved' => [ 'actor', [], [], 'exception' ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @covers User::getDefaultOptions
|
||||
*/
|
||||
public function testGetDefaultOptions() {
|
||||
User::resetGetDefaultOptionsForTestsOnly();
|
||||
$defaultOptions = User::getDefaultOptions();
|
||||
$this->assertArrayHasKey( 'search-match-redirect', $defaultOptions );
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue