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