Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
97.14% |
68 / 70 |
|
75.00% |
6 / 8 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
97.14% |
68 / 70 |
|
75.00% |
6 / 8 |
19 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
onSpecialPageBeforeExecute | |
95.24% |
20 / 21 |
|
0.00% |
0 / 1 |
5 | |||
getJsConfigVars | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
2 | |||
redirectToNamespacedRequest | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
3 | |||
getDefaultNamespaces | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
2 | |||
isNamespacedSearch | |
83.33% |
5 / 6 |
|
0.00% |
0 / 1 |
4.07 | |||
onSpecialSearchResultsPrepend | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
onGetPreferences | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace AdvancedSearch; |
4 | |
5 | use ExtensionRegistry; |
6 | use MediaWiki\Config\Config; |
7 | use MediaWiki\Hook\SpecialSearchResultsPrependHook; |
8 | use MediaWiki\Html\Html; |
9 | use MediaWiki\Languages\LanguageNameUtils; |
10 | use MediaWiki\Output\OutputPage; |
11 | use MediaWiki\Preferences\Hook\GetPreferencesHook; |
12 | use MediaWiki\Request\WebRequest; |
13 | use MediaWiki\SpecialPage\Hook\SpecialPageBeforeExecuteHook; |
14 | use MediaWiki\SpecialPage\SpecialPage; |
15 | use MediaWiki\Specials\SpecialSearch; |
16 | use MediaWiki\User\Options\UserOptionsLookup; |
17 | use MediaWiki\User\User; |
18 | use MediaWiki\User\UserIdentity; |
19 | use MessageLocalizer; |
20 | use MimeAnalyzer; |
21 | use SearchEngineConfig; |
22 | |
23 | /** |
24 | * @license GPL-2.0-or-later |
25 | */ |
26 | class Hooks implements |
27 | SpecialPageBeforeExecuteHook, |
28 | GetPreferencesHook, |
29 | SpecialSearchResultsPrependHook |
30 | { |
31 | |
32 | private UserOptionsLookup $userOptionsLookup; |
33 | private LanguageNameUtils $languageNameUtils; |
34 | private SearchEngineConfig $searchEngineConfig; |
35 | private MimeAnalyzer $mimeAnalyzer; |
36 | |
37 | public function __construct( |
38 | UserOptionsLookup $userOptionsLookup, |
39 | LanguageNameUtils $languageNameUtils, |
40 | SearchEngineConfig $searchEngineConfig, |
41 | MimeAnalyzer $mimeAnalyzer |
42 | ) { |
43 | $this->userOptionsLookup = $userOptionsLookup; |
44 | $this->languageNameUtils = $languageNameUtils; |
45 | $this->searchEngineConfig = $searchEngineConfig; |
46 | $this->mimeAnalyzer = $mimeAnalyzer; |
47 | } |
48 | |
49 | /** |
50 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/SpecialPageBeforeExecute |
51 | * |
52 | * @param SpecialPage $special |
53 | * @param string|null $subpage |
54 | * @return false|void false to abort the execution of the special page, "void" otherwise |
55 | */ |
56 | public function onSpecialPageBeforeExecute( $special, $subpage ) { |
57 | if ( $special->getName() !== 'Search' ) { |
58 | return; |
59 | } |
60 | |
61 | $user = $special->getUser(); |
62 | $outputPage = $special->getOutput(); |
63 | |
64 | /** |
65 | * If the user is logged in and has explicitly requested to disable the extension, don't load. |
66 | * Ensure namespaces are always part of search URLs |
67 | */ |
68 | if ( $user->isNamed() && |
69 | $this->userOptionsLookup->getBoolOption( $user, 'advancedsearch-disable' ) |
70 | ) { |
71 | return; |
72 | } |
73 | |
74 | /** |
75 | * Ensure the current URL is specifying the namespaces which are to be used |
76 | */ |
77 | $redirect = $this->redirectToNamespacedRequest( $special ); |
78 | if ( $redirect !== null ) { |
79 | $outputPage->redirect( $redirect ); |
80 | // Abort execution of the SpecialPage by returning false since we are redirecting |
81 | return false; |
82 | } |
83 | |
84 | $outputPage->addModules( [ |
85 | 'ext.advancedSearch.init', |
86 | 'ext.advancedSearch.searchtoken', |
87 | ] ); |
88 | |
89 | $outputPage->addModuleStyles( 'ext.advancedSearch.initialstyles' ); |
90 | |
91 | $outputPage->addJsConfigVars( $this->getJsConfigVars( |
92 | $special->getContext(), |
93 | $special->getConfig(), |
94 | ExtensionRegistry::getInstance() |
95 | ) ); |
96 | } |
97 | |
98 | /** |
99 | * @param MessageLocalizer $context |
100 | * @param Config $config |
101 | * @param ExtensionRegistry $extensionRegistry |
102 | * @return array |
103 | */ |
104 | private function getJsConfigVars( |
105 | MessageLocalizer $context, |
106 | Config $config, |
107 | ExtensionRegistry $extensionRegistry |
108 | ): array { |
109 | $vars = [ |
110 | 'advancedSearch.mimeTypes' => |
111 | ( new MimeTypeConfigurator( $this->mimeAnalyzer ) )->getMimeTypes( |
112 | $config->get( 'FileExtensions' ) |
113 | ), |
114 | 'advancedSearch.tooltips' => ( new TooltipGenerator( $context ) )->generateTooltips(), |
115 | 'advancedSearch.namespacePresets' => $config->get( 'AdvancedSearchNamespacePresets' ), |
116 | 'advancedSearch.deepcategoryEnabled' => $config->get( 'AdvancedSearchDeepcatEnabled' ), |
117 | 'advancedSearch.searchableNamespaces' => |
118 | SearchableNamespaceListBuilder::getCuratedNamespaces( |
119 | $this->searchEngineConfig->searchableNamespaces() |
120 | ), |
121 | ]; |
122 | |
123 | if ( $extensionRegistry->isLoaded( 'Translate' ) ) { |
124 | $vars += [ 'advancedSearch.languages' => |
125 | $this->languageNameUtils->getLanguageNames() |
126 | ]; |
127 | } |
128 | |
129 | return $vars; |
130 | } |
131 | |
132 | /** |
133 | * If the request does not contain any namespaces, redirect to URL with user default namespaces |
134 | * @param SpecialPage $special |
135 | * @return string|null the URL to redirect to or null if not needed |
136 | */ |
137 | private function redirectToNamespacedRequest( SpecialPage $special ): ?string { |
138 | if ( !self::isNamespacedSearch( $special->getRequest() ) ) { |
139 | $namespacedSearchUrl = $special->getRequest()->getFullRequestURL(); |
140 | $queryParts = []; |
141 | foreach ( $this->getDefaultNamespaces( $special->getUser() ) as $ns ) { |
142 | $queryParts['ns' . $ns] = '1'; |
143 | } |
144 | return wfAppendQuery( $namespacedSearchUrl, $queryParts ); |
145 | } |
146 | return null; |
147 | } |
148 | |
149 | /** |
150 | * Retrieves the default namespaces for the current user |
151 | * |
152 | * @param UserIdentity $user The user to lookup default namespaces for |
153 | * @return int[] List of namespaces to be searched by default |
154 | */ |
155 | private function getDefaultNamespaces( UserIdentity $user ): array { |
156 | return $this->searchEngineConfig->userNamespaces( $user ) ?: $this->searchEngineConfig->defaultNamespaces(); |
157 | } |
158 | |
159 | /** |
160 | * Checks if there is a search request, and it already specifies namespaces. |
161 | * @param WebRequest $request |
162 | * @return bool |
163 | */ |
164 | private static function isNamespacedSearch( WebRequest $request ): bool { |
165 | if ( $request->getRawVal( 'search', '' ) === '' ) { |
166 | return true; |
167 | } |
168 | |
169 | foreach ( $request->getValueNames() as $requestKey ) { |
170 | if ( preg_match( '/^ns\d+$/', $requestKey ) ) { |
171 | return true; |
172 | } |
173 | } |
174 | return false; |
175 | } |
176 | |
177 | /** |
178 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/SpecialSearchResultsPrepend |
179 | * |
180 | * @param SpecialSearch $specialSearch |
181 | * @param OutputPage $output |
182 | * @param string $term |
183 | */ |
184 | public function onSpecialSearchResultsPrepend( $specialSearch, $output, $term ) { |
185 | $output->addHTML( |
186 | Html::rawElement( |
187 | 'div', |
188 | [ 'class' => 'mw-search-spinner' ], |
189 | Html::element( 'div', [ 'class' => 'mw-search-spinner-bounce' ] ) |
190 | ) |
191 | ); |
192 | } |
193 | |
194 | /** |
195 | * @param User $user |
196 | * @param array[] &$preferences |
197 | */ |
198 | public function onGetPreferences( $user, &$preferences ) { |
199 | $preferences['advancedsearch-disable'] = [ |
200 | 'type' => 'toggle', |
201 | 'label-message' => 'advancedsearch-preference-disable', |
202 | 'section' => 'searchoptions/advancedsearch', |
203 | 'help-message' => 'advancedsearch-preference-help', |
204 | ]; |
205 | } |
206 | } |