Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
9.80% |
5 / 51 |
|
14.29% |
1 / 7 |
CRAP | |
0.00% |
0 / 1 |
Hooks | |
9.80% |
5 / 51 |
|
14.29% |
1 / 7 |
255.74 | |
0.00% |
0 / 1 |
getHelpGuiderUrl | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
12 | |||
getTourNames | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
addTour | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
30 | |||
onBeforePageDisplay | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
onResourceLoaderRegisterModules | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
6 | |||
onRedirectSpecialArticleRedirectParams | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onMakeGlobalVariablesScript | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\GuidedTour; |
4 | |
5 | use MediaWiki\Json\FormatJson; |
6 | use MediaWiki\Output\Hook\BeforePageDisplayHook; |
7 | use MediaWiki\Output\Hook\MakeGlobalVariablesScriptHook; |
8 | use MediaWiki\Output\OutputPage; |
9 | use MediaWiki\Registration\ExtensionRegistry; |
10 | use MediaWiki\ResourceLoader as RL; |
11 | use MediaWiki\ResourceLoader\Hook\ResourceLoaderRegisterModulesHook; |
12 | use MediaWiki\ResourceLoader\ResourceLoader; |
13 | use MediaWiki\Skin\Skin; |
14 | use MediaWiki\SpecialPage\Hook\RedirectSpecialArticleRedirectParamsHook; |
15 | use MediaWiki\Title\Title; |
16 | |
17 | /** |
18 | * Use a hook to include this extension's functionality in pages |
19 | * (if the page is called with a tour) |
20 | * |
21 | * @file |
22 | * @author Terry Chay tchay@wikimedia.org |
23 | * @author Matthew Flaschen mflaschen@wikimedia.org |
24 | * @author Luke Welling lwelling@wikimedia.org |
25 | */ |
26 | |
27 | class Hooks implements |
28 | BeforePageDisplayHook, |
29 | ResourceLoaderRegisterModulesHook, |
30 | RedirectSpecialArticleRedirectParamsHook, |
31 | MakeGlobalVariablesScriptHook |
32 | { |
33 | // Tour cookie name. It will be prefixed automatically. |
34 | public const COOKIE_NAME = '-mw-tour'; |
35 | |
36 | private const TOUR_PARAM = 'tour'; |
37 | |
38 | /** |
39 | * ResourceLoader callback that adds the page name of a GuidedTour local |
40 | * documentation page, to demonstrate showing tour content from pages. This is |
41 | * a hack pending forcontent messages: https://phabricator.wikimedia.org/T27349 |
42 | * |
43 | * If the page does not exist, it will not be set. |
44 | * |
45 | * @param RL\Context $context |
46 | * @return array |
47 | */ |
48 | public static function getHelpGuiderUrl( RL\Context $context ) { |
49 | $data = []; |
50 | |
51 | $pageName = $context->msg( 'guidedtour-help-guider-url' ) |
52 | ->inContentLanguage()->plain(); |
53 | $pageTitle = Title::newFromText( $pageName ); |
54 | if ( $pageTitle !== null && $pageTitle->exists() ) { |
55 | $data['pageName'] = $pageName; |
56 | } |
57 | |
58 | return $data; |
59 | } |
60 | |
61 | /** |
62 | * Parses tour cookie, returning an array of tour names (empty if there is no |
63 | * cookie or the format is invalid) |
64 | * |
65 | * Example cookie. This happens to have multiple tours, but cookies with any |
66 | * number of tours are accepted: |
67 | * |
68 | * { |
69 | * version: 1, |
70 | * tours: { |
71 | * firsttour: { |
72 | * step: 4 |
73 | * }, |
74 | * secondtour: { |
75 | * step: 2 |
76 | * }, |
77 | * thirdtour: { |
78 | * step: 3, |
79 | * firstArticleId: 38333 |
80 | * } |
81 | * } |
82 | * } |
83 | * |
84 | * This only supports new-style cookies. Old cookies will be converted on the |
85 | * client-side, then the tour module will be loaded. |
86 | * |
87 | * @param string $cookieValue cookie value |
88 | * |
89 | * @return array array of tour names, empty if no cookie or cookie is invalid |
90 | */ |
91 | public static function getTourNames( $cookieValue ) { |
92 | if ( $cookieValue !== null ) { |
93 | $parsed = FormatJson::decode( $cookieValue, true ); |
94 | if ( isset( $parsed['tours'] ) ) { |
95 | return array_keys( $parsed['tours'] ); |
96 | } |
97 | } |
98 | |
99 | return []; |
100 | } |
101 | |
102 | /** |
103 | * Adds a built-in or wiki tour. |
104 | * |
105 | * If user JS is disallowed on this page, it does nothing. |
106 | * |
107 | * If the built-in one exists as a module, it will add that. |
108 | * |
109 | * Otherwise, it will add the general guided tour module, which will take care of |
110 | * loading the on-wiki tour |
111 | * |
112 | * It will not attempt to add tours that violate the naming rules. |
113 | * |
114 | * @param OutputPage $out output page |
115 | * @param string $tourName the tour name, such as 'gettingstarted' |
116 | * |
117 | * @return bool true if a module was added, false otherwise |
118 | */ |
119 | public static function addTour( $out, $tourName ) { |
120 | $isUserJsAllowed = $out->getAllowedModules( RL\Module::TYPE_SCRIPTS ) |
121 | >= RL\Module::ORIGIN_USER_INDIVIDUAL; |
122 | |
123 | // Exclude '-' because MediaWiki message keys use it as a separator after the tourname. |
124 | // Exclude '.' because module names use it as a separator. |
125 | // "User JS" refers to on-wiki JavaScript. In theory we could still add |
126 | // extension-defined tours, but it's more conservative not to. |
127 | if ( $isUserJsAllowed && $tourName !== null && strpbrk( $tourName, '-.' ) === false ) { |
128 | $tourModuleName = "ext.guidedTour.tour.$tourName"; |
129 | if ( $out->getResourceLoader()->getModule( $tourModuleName ) ) { |
130 | // Add the tour itself for extension-defined tours. |
131 | $out->addModules( $tourModuleName ); |
132 | } else { |
133 | /* |
134 | Otherwise, add the main module, which attempts to import an |
135 | on-wiki tour. |
136 | */ |
137 | $out->addModules( 'ext.guidedTour' ); |
138 | } |
139 | return true; |
140 | } |
141 | |
142 | return false; |
143 | } |
144 | |
145 | /** |
146 | * Handler for BeforePageDisplay hook. |
147 | * |
148 | * Adds a tour-related module if possible. |
149 | * |
150 | * If the query parameter is set, it will use that. |
151 | * Otherwise, it will use the cookie if that exists. |
152 | * |
153 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/BeforePageDisplay |
154 | * |
155 | * @param OutputPage $out OutputPage object |
156 | * @param Skin $skin Skin being used. |
157 | */ |
158 | public function onBeforePageDisplay( $out, $skin ): void { |
159 | // test for tour enabled in url first |
160 | $request = $out->getRequest(); |
161 | $queryTourName = $request->getVal( self::TOUR_PARAM ); |
162 | if ( $queryTourName !== null ) { |
163 | self::addTour( $out, $queryTourName ); |
164 | } else { |
165 | $cookieValue = $request->getCookie( self::COOKIE_NAME ); |
166 | $tours = self::getTourNames( $cookieValue ); |
167 | foreach ( $tours as $tourName ) { |
168 | self::addTour( $out, $tourName ); |
169 | } |
170 | } |
171 | } |
172 | |
173 | /** |
174 | * Registers VisualEditor tour if VE is installed |
175 | * |
176 | * @param ResourceLoader $resourceLoader |
177 | */ |
178 | public function onResourceLoaderRegisterModules( ResourceLoader $resourceLoader ): void { |
179 | if ( ExtensionRegistry::getInstance()->isLoaded( 'VisualEditor' ) ) { |
180 | $resourceLoader->register( |
181 | 'ext.guidedTour.tour.firsteditve', |
182 | [ |
183 | 'scripts' => 'tours/firsteditve.js', |
184 | 'localBasePath' => __DIR__ . '/../modules', |
185 | 'remoteExtPath' => 'GuidedTour/modules', |
186 | 'dependencies' => 'ext.guidedTour', |
187 | 'messages' => [ |
188 | 'editsection', |
189 | 'publishchanges', |
190 | 'guidedtour-tour-firstedit-edit-page-title', |
191 | 'guidedtour-tour-firsteditve-edit-page-description', |
192 | 'guidedtour-tour-firstedit-edit-section-title', |
193 | 'guidedtour-tour-firsteditve-edit-section-description', |
194 | 'guidedtour-tour-firstedit-save-title', |
195 | 'guidedtour-tour-firsteditve-save-description' |
196 | ] |
197 | ] |
198 | ); |
199 | } |
200 | } |
201 | |
202 | /** |
203 | * @param array &$redirectParams |
204 | */ |
205 | public function onRedirectSpecialArticleRedirectParams( &$redirectParams ) { |
206 | array_push( $redirectParams, self::TOUR_PARAM, 'step' ); |
207 | } |
208 | |
209 | /** |
210 | * @param array &$vars |
211 | * @param OutputPage $out |
212 | */ |
213 | public function onMakeGlobalVariablesScript( &$vars, $out ): void { |
214 | GuidedTourLauncher::onMakeGlobalVariablesScript( $vars, $out ); |
215 | } |
216 | } |