Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
48.61% covered (danger)
48.61%
70 / 144
25.00% covered (danger)
25.00%
2 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
PlayerHooks
48.61% covered (danger)
48.61%
70 / 144
25.00% covered (danger)
25.00%
2 / 8
109.82
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 onBeforePageDisplay
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
4.01
 shouldWikispeechRun
97.06% covered (success)
97.06%
33 / 34
0.00% covered (danger)
0.00%
0 / 1
10
 onResourceLoaderGetConfigVars
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 onGetPreferences
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
2
 addVoicePreferences
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 addSpeechRatePreferences
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 onSkinTemplateNavigation__Universal
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Wikispeech\Hooks;
4
5use Action;
6use Config;
7use ConfigFactory;
8use Exception;
9use MediaWiki\Hook\BeforePageDisplayHook;
10use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook;
11use MediaWiki\Http\HttpRequestFactory;
12use MediaWiki\Languages\LanguageFactory;
13use MediaWiki\Logger\LoggerFactory;
14use MediaWiki\Permissions\PermissionManager;
15use MediaWiki\Preferences\Hook\GetPreferencesHook;
16use MediaWiki\User\UserOptionsLookup;
17use MediaWiki\Wikispeech\ConfigurationValidator;
18use MediaWiki\Wikispeech\Utterance\UtteranceStore;
19use MediaWiki\Wikispeech\VoiceHandler;
20use OutputPage;
21use Psr\Log\LoggerInterface;
22use Skin;
23use SkinTemplate;
24use User;
25use Wikimedia\ObjectCache\WANObjectCache;
26
27/**
28 * @license GPL-2.0-or-later
29 * @since 0.1.11
30 */
31class PlayerHooks implements
32    BeforePageDisplayHook,
33    GetPreferencesHook,
34    SkinTemplateNavigation__UniversalHook
35{
36    /** @var Config */
37    private $config;
38
39    /** @var ConfigurationValidator */
40    private $configValidator;
41
42    /** @var UserOptionsLookup */
43    private $userOptionsLookup;
44
45    /** @var LoggerInterface */
46    private $logger;
47
48    /** @var WANObjectCache */
49    private $mainWANObjectCache;
50
51    /** @var LanguageFactory */
52    private $languageFactory;
53
54    /** @var PermissionManager */
55    private $permissionManager;
56
57    /** @var HttpRequestFactory */
58    private $requestFactory;
59
60    /** @var VoiceHandler */
61    private $voiceHandler;
62
63    /**
64     * @since 0.1.13
65     * @param ConfigFactory $configFactory
66     * @param UserOptionsLookup $userOptionsLookup
67     * @param WANObjectCache $mainWANObjectCache
68     * @param LanguageFactory $languageFactory
69     * @param PermissionManager $permissionManager
70     * @param HttpRequestFactory $requestFactory
71     * @param VoiceHandler $voiceHandler
72     */
73    public function __construct(
74        ConfigFactory $configFactory,
75        UserOptionsLookup $userOptionsLookup,
76        WANObjectCache $mainWANObjectCache,
77        LanguageFactory $languageFactory,
78        PermissionManager $permissionManager,
79        HttpRequestFactory $requestFactory,
80        VoiceHandler $voiceHandler
81    ) {
82        $this->logger = LoggerFactory::getInstance( 'Wikispeech' );
83        $this->config = $configFactory->makeConfig( 'wikispeech' );
84        $this->configValidator = new ConfigurationValidator( $this->config, $this->logger );
85        $this->userOptionsLookup = $userOptionsLookup;
86        $this->mainWANObjectCache = $mainWANObjectCache;
87        $this->languageFactory = $languageFactory;
88        $this->permissionManager = $permissionManager;
89        $this->requestFactory = $requestFactory;
90        $this->voiceHandler = $voiceHandler;
91    }
92
93    /**
94     * Hook for BeforePageDisplay.
95     *
96     * Enables JavaScript.
97     *
98     * @since 0.1.11
99     * @param OutputPage $out The OutputPage object.
100     * @param Skin $skin Skin object that will be used to generate the page,
101     *  added in MediaWiki 1.13.
102     */
103    public function onBeforePageDisplay( $out, $skin ): void {
104        if ( !$this->shouldWikispeechRun( $out ) ) {
105            return;
106        }
107        $flushUtterances = $this->config->get( 'WikispeechFlushUtterances' );
108        $pageId = $out->getTitle()->getArticleID();
109        if ( $flushUtterances ) {
110            $utteranceStore = new UtteranceStore();
111            $utteranceStore->flushUtterancesByPage( null, $pageId );
112        }
113        $showPlayer = $this->userOptionsLookup->getOption(
114            $out->getUser(), 'wikispeechShowPlayer'
115        );
116        if ( $showPlayer ) {
117            $this->logger->info( __METHOD__ . ': Loading player.' );
118            $out->addModules( [ 'ext.wikispeech' ] );
119        } else {
120            $this->logger->info( __METHOD__ . ': Adding option to load player.' );
121            $out->addModules( [ 'ext.wikispeech.loader' ] );
122        }
123        $out->addJsConfigVars( [
124            'wgWikispeechKeyboardShortcuts' => $this->config->get( 'WikispeechKeyboardShortcuts' ),
125            'wgWikispeechContentSelector' => $this->config->get( 'WikispeechContentSelector' ),
126            'wgWikispeechSkipBackRewindsThreshold' =>
127                $this->config->get( 'WikispeechSkipBackRewindsThreshold' ),
128            'wgWikispeechHelpPage' => $this->config->get( 'WikispeechHelpPage' ),
129            'wgWikispeechFeedbackPage' => $this->config->get( 'WikispeechFeedbackPage' )
130        ] );
131    }
132
133    /**
134     * Checks if Wikispeech should run.
135     *
136     * Returns true if all of the following are true:
137     * * User has enabled Wikispeech in the settings
138     * * User is allowed to listen to pages
139     * * Wikispeech configuration is valid
140     * * Wikispeech is enabled for the page's namespace
141     * * Revision is current
142     * * Page's language is enabled for Wikispeech
143     * * The action is "view"
144     *
145     * @since 0.1.11
146     * @param OutputPage $out
147     * @return bool
148     */
149    private function shouldWikispeechRun( OutputPage $out ) {
150        $wikispeechEnabled = $this->userOptionsLookup
151            ->getOption( $out->getUser(), 'wikispeechEnable' );
152        if ( !$wikispeechEnabled ) {
153            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: disabled by user.' );
154            return false;
155        }
156
157        $userIsAllowed = $this->permissionManager
158            ->userHasRight( $out->getUser(), 'wikispeech-listen' );
159        if ( !$userIsAllowed ) {
160            $this->logger->info( __METHOD__ .
161                ': Not loading Wikispeech: user lacks right "wikispeech-listen".' );
162            return false;
163        }
164
165        if ( !$this->configValidator->validateConfiguration() ) {
166            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: config invalid.' );
167            return false;
168        }
169
170        $namespace = $out->getTitle()->getNamespace();
171        $validNamespaces = $this->config->get( 'WikispeechNamespaces' );
172        if ( !in_array( $namespace, $validNamespaces ) ) {
173            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: unsupported namespace.' );
174            return false;
175        }
176
177        if ( !$out->isRevisionCurrent() ) {
178            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: non-current revision.' );
179            return false;
180        }
181
182        if ( $namespace == NS_MEDIA || $namespace < 0 ) {
183            // cannot get pageContentLanguage of e.g. a Special page or a
184            // virtual page. These should all use the interface language.
185            $pageContentLanguage = $out->getLanguage();
186        } else {
187            $pageContentLanguage = $out->getTitle()->getPageLanguage();
188        }
189        $validLanguages = array_keys( $this->config->get( 'WikispeechVoices' ) );
190        if ( !in_array( $pageContentLanguage->getCode(), $validLanguages ) ) {
191            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: unsupported language.' );
192            return false;
193        }
194
195        $actionName = Action::getActionName( $out );
196        if ( $actionName !== 'view' ) {
197            $this->logger->info( __METHOD__ . ': Not loading Wikispeech: unsupported action.' );
198            return false;
199        }
200
201        return true;
202    }
203
204    /**
205     *  Conditionally register static configuration variables for the
206     * ext.wikispeech module only if that module is loaded.
207     *
208     * @since 0.1.11
209     * @param array &$vars The array of static configuration variables.
210     * @param string $skin
211     * @param Config $config
212     */
213    public function onResourceLoaderGetConfigVars( array &$vars, $skin, Config $config ): void {
214        $vars['wgWikispeechSpeechoidUrl'] = $config->get( 'WikispeechSpeechoidUrl' );
215        $vars['wgWikispeechNamespaces'] = $config->get( 'WikispeechNamespaces' );
216        $vars['wgWikispeechVoices'] = $config->get( 'WikispeechVoices' );
217        $vars['wgWikispeechSpeechRates'] = $config->get( 'WikispeechSpeechRates' );
218    }
219
220    /**
221     * Add Wikispeech options to Special:Preferences.
222     *
223     * @since 0.1.11
224     * @param User $user current User object.
225     * @param array &$preferences Preferences array.
226     */
227    public function onGetPreferences( $user, &$preferences ) {
228        $preferences['wikispeechEnable'] = [
229            'type' => 'toggle',
230            'label-message' => 'prefs-wikispeech-enable',
231            'section' => 'wikispeech'
232        ];
233        $preferences['wikispeechShowPlayer'] = [
234            'type' => 'toggle',
235            'label-message' => 'prefs-wikispeech-show-player',
236            'section' => 'wikispeech'
237        ];
238        $preferences['wikispeechPartOfContent'] = [
239            'section' => 'wikispeech',
240            'type' => 'toggle',
241            'label-message' => 'prefs-wikispeech-part-of-content',
242            'help-message' => 'prefs-help-wikispeech-part-of-content'
243        ];
244        $preferences['wikispeechPageToolbar'] = [
245            'section' => 'wikispeech',
246            'type' => 'toggle',
247            'label-message' => 'prefs-wikispeech-page-toolbar',
248            'help-message' => 'prefs-help-wikispeech-page-toolbar'
249        ];
250        $preferences['wikispeechHighlightingEnabled'] = [
251            'section' => 'wikispeech',
252            'type' => 'toggle',
253            'label-message' => 'prefs-wikispeech-highlighting-enabled'
254        ];
255        $preferences['wikispeechHighlightingColor'] = [
256            'section' => 'wikispeech',
257            'type' => 'select',
258            'label-message' => 'prefs-wikispeech-highlighting-options',
259            'options-messages' => [
260                'prefs-wikispeech-highlighting-default-option' => 1,
261                'prefs-wikispeech-highlighting-green-blue' => 2
262            ],
263            'disable-if' => [ '!==', 'wikispeechHighlightingEnabled', '1' ],
264        ];
265        $this->addVoicePreferences( $preferences, $this->voiceHandler );
266        $this->addSpeechRatePreferences( $preferences );
267    }
268
269    /**
270     * Add preferences for selecting voices per language.
271     *
272     * @since 0.1.11
273     * @param array &$preferences Preferences array.
274     * @param VoiceHandler $voiceHandler
275     */
276    private function addVoicePreferences( &$preferences, $voiceHandler ) {
277        $wikispeechVoices = $this->config->get( 'WikispeechVoices' );
278        foreach ( $wikispeechVoices as $language => $voices ) {
279            $languageKey = 'wikispeechVoice' . ucfirst( $language );
280            $mwLanguage = $this->languageFactory->getLanguage( 'en' );
281            $languageName = $mwLanguage->getVariantname( $language );
282            $options = [];
283            try {
284                $defaultVoice = $voiceHandler->getDefaultVoice( $language );
285                $options["Default ($defaultVoice)"] = '';
286            } catch ( Exception ) {
287                $options["Default"] = '';
288            }
289            foreach ( $voices as $voice ) {
290                $options[$voice] = $voice;
291            }
292            $preferences[$languageKey] = [
293                'type' => 'select',
294                'label' => $languageName,
295                'section' => 'wikispeech/wikispeech-voice',
296                'options' => $options
297            ];
298        }
299    }
300
301    /**
302     * Add preferences for selecting speech rate.
303     *
304     * @since 0.1.11
305     * @param array &$preferences Preferences array.
306     */
307    private function addSpeechRatePreferences( &$preferences ) {
308        $values = $this->config->get( 'WikispeechSpeechRates' );
309        $options = [];
310        foreach ( $values as $value ) {
311            $options[ wfMessage( 'percent', $value * 100 )->text() ] = $value;
312        }
313        $preferences['wikispeechSpeechRate'] = [
314            'type' => 'select',
315            'label-message' => 'prefs-wikispeech-speech-rate',
316            'section' => 'wikispeech/wikispeech-voice',
317            'options' => $options
318        ];
319    }
320
321    /**
322     * Add tab for activating Wikispeech player.
323     *
324     * @since 0.1.11
325     * @param SkinTemplate $skinTemplate The skin template on which
326     *  the UI is built.
327     * @param array &$links Navigation links.
328     */
329    public function onSkinTemplateNavigation__Universal( $skinTemplate, &$links ): void { // phpcs:ignore MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName, Generic.Files.LineLength.TooLong
330        $out = $skinTemplate->getOutput();
331        if ( $this->shouldWikispeechRun( $out ) ) {
332            $links['actions']['listen'] = [
333                'class' => 'ext-wikispeech-listen',
334                'text' => $skinTemplate->msg( 'wikispeech-listen' )->text(),
335                'href' => 'javascript:void(0)'
336            ];
337        }
338    }
339}