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