Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 107 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 1 |
SpecialLandingCheck | |
0.00% |
0 / 107 |
|
0.00% |
0 / 9 |
1640 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
execute | |
0.00% |
0 / 25 |
|
0.00% |
0 / 1 |
72 | |||
determineLocalServerType | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
72 | |||
routeRedirect | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
56 | |||
externalRedirect | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
6 | |||
localRedirect | |
0.00% |
0 / 38 |
|
0.00% |
0 / 1 |
90 | |||
setLocalServerType | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getLocalServerType | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
getGroupName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | /** |
4 | * This checks to see if a version of a landing page exists for the user's language and country. |
5 | * If not, it looks for a version localized for the user's language. If that doesn't exist either, |
6 | * it looks for the English version. If any of those exist, it then redirects the user. |
7 | */ |
8 | |
9 | namespace MediaWiki\Extension\LandingCheck; |
10 | |
11 | use MediaWiki\Languages\LanguageFallback; |
12 | use MediaWiki\Languages\LanguageNameUtils; |
13 | use MediaWiki\SpecialPage\SpecialPage; |
14 | use MediaWiki\Title\Title; |
15 | use MediaWiki\Utils\UrlUtils; |
16 | |
17 | class SpecialLandingCheck extends SpecialPage { |
18 | /** @var LanguageNameUtils */ |
19 | private $languageNameUtils; |
20 | |
21 | /** @var LanguageFallback */ |
22 | private $languageFallback; |
23 | |
24 | /** @var UrlUtils */ |
25 | private $urlUtils; |
26 | |
27 | protected $localServerType = null; |
28 | /** |
29 | * If basic is set to true, do a local redirect, ignore priority, and don't pass tracking |
30 | * params. This is for non-fundraising links that just need localization. |
31 | * |
32 | * @var bool |
33 | */ |
34 | protected $basic = false; |
35 | |
36 | /** |
37 | * If anchor text is passed add that to the end of the created url so that it can be used to |
38 | * position the resulting page. This is currently used only for non-fundraising links that need |
39 | * localization and therefore is only checked if basic (above) is true. |
40 | * |
41 | * @var string|null |
42 | */ |
43 | protected $anchor = null; |
44 | |
45 | /** |
46 | * @param LanguageNameUtils $languageNameUtils |
47 | * @param LanguageFallback $languageFallback |
48 | * @param UrlUtils $urlUtils |
49 | */ |
50 | public function __construct( |
51 | LanguageNameUtils $languageNameUtils, |
52 | LanguageFallback $languageFallback, |
53 | UrlUtils $urlUtils |
54 | ) { |
55 | // Register special page |
56 | parent::__construct( 'LandingCheck' ); |
57 | $this->languageNameUtils = $languageNameUtils; |
58 | $this->languageFallback = $languageFallback; |
59 | $this->urlUtils = $urlUtils; |
60 | } |
61 | |
62 | /** |
63 | * @param string $sub |
64 | */ |
65 | public function execute( $sub ) { |
66 | global $wgPriorityCountries; |
67 | $request = $this->getRequest(); |
68 | |
69 | // If we have a subpage; assume it's a language like an internationalized page |
70 | |
71 | $language = 'en'; |
72 | $path = explode( '/', $sub ); |
73 | if ( $this->languageNameUtils->isValidCode( $path[count( $path ) - 1] ) ) { |
74 | $language = $sub; |
75 | } |
76 | |
77 | // Pull in query string parameters |
78 | $language = $request->getVal( 'language', $language ); |
79 | $this->basic = $request->getBool( 'basic' ); |
80 | $country = $request->getVal( 'country' ); |
81 | $this->anchor = $request->getVal( 'anchor' ); |
82 | |
83 | // if the language is false-ish, set to default |
84 | if ( !$language ) { |
85 | $language = 'en'; |
86 | } |
87 | |
88 | // if it's not a supported language, but the section before a |
89 | // dash or underscore is, use that |
90 | if ( !$this->languageNameUtils->isSupportedLanguage( $language ) ) { |
91 | $parts = preg_split( '/[-_]/', $language ); |
92 | if ( $this->languageNameUtils->isSupportedLanguage( $parts[0] ) ) { |
93 | $language = $parts[0]; |
94 | } |
95 | } |
96 | |
97 | // Use the GeoIP cookie if available. |
98 | if ( !$country ) { |
99 | $geoip = $request->getCookie( 'GeoIP', '' ); |
100 | if ( $geoip ) { |
101 | $components = explode( ':', $geoip ); |
102 | $country = $components[0]; |
103 | } |
104 | } |
105 | |
106 | if ( !$country ) { |
107 | $country = 'US'; // Default |
108 | } |
109 | |
110 | // determine if we are fulfilling a request for a priority country |
111 | $priority = in_array( $country, $wgPriorityCountries ); |
112 | |
113 | // handle the actual redirect |
114 | $this->routeRedirect( $country, $language, $priority ); |
115 | } |
116 | |
117 | /** |
118 | * Determine whether this server is configured as the priority or normal server |
119 | * |
120 | * If this is neither the priority nor normal server, assumes 'local' - meaning |
121 | * this server should be handling the request. |
122 | * @return string |
123 | */ |
124 | public function determineLocalServerType() { |
125 | global $wgServer, $wgLandingCheckPriorityURLBase, $wgLandingCheckNormalURLBase; |
126 | |
127 | $localServerDetails = $this->urlUtils->parse( $wgServer ); |
128 | |
129 | if ( $localServerDetails === null ) { |
130 | return 'local'; |
131 | } |
132 | |
133 | if ( $wgLandingCheckPriorityURLBase !== null ) { |
134 | $priorityServerDetails = $this->urlUtils->parse( $wgLandingCheckPriorityURLBase ); |
135 | if ( $priorityServerDetails !== null |
136 | && $localServerDetails[ 'host' ] === $priorityServerDetails[ 'host' ] |
137 | ) { |
138 | return 'priority'; |
139 | } |
140 | } |
141 | |
142 | if ( $wgLandingCheckNormalURLBase !== null ) { |
143 | $normalServerDetails = $this->urlUtils->parse( $wgLandingCheckNormalURLBase ); |
144 | if ( $normalServerDetails !== null |
145 | && $localServerDetails[ 'host' ] === $normalServerDetails[ 'host' ] |
146 | ) { |
147 | return 'normal'; |
148 | } |
149 | } |
150 | |
151 | return 'local'; |
152 | } |
153 | |
154 | /** |
155 | * Route the request to the appropriate redirect method |
156 | * @param string $country |
157 | * @param string $language |
158 | * @param bool $priority Whether or not we handle this request on behalf of a priority country |
159 | */ |
160 | public function routeRedirect( $country, $language, $priority ) { |
161 | $localServerType = $this->getLocalServerType(); |
162 | |
163 | if ( $this->basic ) { |
164 | $this->localRedirect( $country, $language, false ); |
165 | |
166 | } elseif ( $localServerType == 'local' ) { |
167 | $this->localRedirect( $country, $language, $priority ); |
168 | |
169 | } elseif ( $priority && $localServerType == 'priority' ) { |
170 | $this->localRedirect( $country, $language, $priority ); |
171 | |
172 | } elseif ( !$priority && $localServerType == 'normal' ) { |
173 | $this->localRedirect( $country, $language, $priority ); |
174 | |
175 | } else { |
176 | $this->externalRedirect( $priority ); |
177 | } |
178 | } |
179 | |
180 | /** |
181 | * Handle an external redirect |
182 | * |
183 | * The external redirect should point to another instance of LandingCheck |
184 | * which will ultimately handle the request. |
185 | * @param bool $priority |
186 | */ |
187 | public function externalRedirect( $priority ) { |
188 | global $wgLandingCheckPriorityURLBase, $wgLandingCheckNormalURLBase; |
189 | |
190 | if ( $priority ) { |
191 | $urlBase = $wgLandingCheckPriorityURLBase; |
192 | |
193 | } else { |
194 | $urlBase = $wgLandingCheckNormalURLBase; |
195 | } |
196 | |
197 | $query = $this->getRequest()->getValues(); |
198 | unset( $query[ 'title' ] ); |
199 | |
200 | // @phan-suppress-next-line PhanTypeMismatchArgument urlBase always not null |
201 | $url = wfAppendQuery( $urlBase, $query ); |
202 | $this->getOutput()->redirect( $url ); |
203 | } |
204 | |
205 | /** |
206 | * Handle local redirect |
207 | * @param string $country |
208 | * @param string $language |
209 | * @param bool $priority Whether or not we handle this request on behalf of a priority country |
210 | */ |
211 | public function localRedirect( $country, $language, $priority = false ) { |
212 | $out = $this->getOutput(); |
213 | $request = $this->getRequest(); |
214 | $landingPage = $request->getVal( 'landing_page', 'Donate' ); |
215 | |
216 | /** |
217 | * Construct new query string for tracking |
218 | * |
219 | * Note that both 'language' and 'uselang' get set to |
220 | * $request->getVal( 'language', 'en' ) |
221 | * This is wacky, yet by design! This is a unique oddity to fundraising |
222 | * stuff, but CentralNotice converts wgUserLanguage to 'language' rather than |
223 | * 'uselang'. Ultimately, this is something that should probably be rectified |
224 | * in CentralNotice. Until then, this is what we've got. |
225 | */ |
226 | $tracking = wfArrayToCgi( [ |
227 | 'utm_source' => $request->getVal( 'utm_source' ), |
228 | 'utm_medium' => $request->getVal( 'utm_medium' ), |
229 | 'utm_campaign' => $request->getVal( 'utm_campaign' ), |
230 | 'utm_key' => $request->getVal( 'utm_key' ), |
231 | 'language' => $language, |
232 | 'uselang' => $language, // for {{int:xxx}} rendering |
233 | 'country' => $country, |
234 | 'referrer' => $request->getHeader( 'referer' ) |
235 | ] ); |
236 | |
237 | if ( $priority ) { |
238 | // Build array of landing pages to check for |
239 | $targetTexts = [ |
240 | $landingPage . '/' . $country . '/' . $language, |
241 | $landingPage . '/' . $country, |
242 | $landingPage . '/' . $language |
243 | ]; |
244 | } else { |
245 | // Build array of landing pages to check for |
246 | $targetTexts = [ |
247 | $landingPage . '/' . $language . '/' . $country, |
248 | $landingPage . '/' . $language |
249 | ]; |
250 | // Add fallback languages |
251 | $fallbacks = $this->languageFallback->getAll( $language ); |
252 | foreach ( $fallbacks as $fallback ) { |
253 | $targetTexts[] = $landingPage . '/' . $fallback; |
254 | } |
255 | } |
256 | |
257 | // Go through the possible landing pages and redirect the user as soon as one is found to exist |
258 | foreach ( $targetTexts as $targetText ) { |
259 | $target = Title::newFromText( $targetText ); |
260 | if ( $target && $target->isKnown() && $target->getNamespace() == NS_MAIN ) { |
261 | if ( $this->basic ) { |
262 | if ( $this->anchor !== null ) { |
263 | $out->redirect( $target->getLocalURL() . '#' . $this->anchor ); |
264 | } else { |
265 | $out->redirect( $target->getLocalURL() ); |
266 | } |
267 | } else { |
268 | $out->redirect( $target->getLocalURL( $tracking ) ); |
269 | } |
270 | return; |
271 | } |
272 | } |
273 | |
274 | // Output a simple error message if no pages were found |
275 | $this->setHeaders(); |
276 | $this->outputHeader(); |
277 | $out->addWikiMsg( 'landingcheck-nopage' ); |
278 | } |
279 | |
280 | /** |
281 | * Setter for $this->localServerType |
282 | * @param string|null $type |
283 | */ |
284 | public function setLocalServerType( $type = null ) { |
285 | if ( !$type ) { |
286 | $this->localServerType = $this->determineLocalServerType(); |
287 | } else { |
288 | $this->localServerType = $type; |
289 | } |
290 | } |
291 | |
292 | /** |
293 | * Getter for $this->localServerType |
294 | * @return string |
295 | */ |
296 | public function getLocalServerType() { |
297 | if ( !$this->localServerType ) { |
298 | $this->setLocalServerType(); |
299 | } |
300 | return $this->localServerType; |
301 | } |
302 | |
303 | protected function getGroupName() { |
304 | return 'contribution'; |
305 | } |
306 | } |