|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* This program is free software; you can redistribute it and/or modify |
|
4
|
|
|
* it under the terms of the GNU General Public License as published by |
|
5
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
|
6
|
|
|
* (at your option) any later version. |
|
7
|
|
|
* |
|
8
|
|
|
* This program is distributed in the hope that it will be useful, |
|
9
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
10
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
11
|
|
|
* GNU General Public License for more details. |
|
12
|
|
|
* |
|
13
|
|
|
* You should have received a copy of the GNU General Public License along |
|
14
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc., |
|
15
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
16
|
|
|
* http://www.gnu.org/copyleft/gpl.html |
|
17
|
|
|
* |
|
18
|
|
|
* @since 1.18 |
|
19
|
|
|
* |
|
20
|
|
|
* @author Alexandre Emsenhuber |
|
21
|
|
|
* @author Daniel Friesen |
|
22
|
|
|
* @file |
|
23
|
|
|
*/ |
|
24
|
|
|
|
|
25
|
|
|
use Liuggio\StatsdClient\Factory\StatsdDataFactory; |
|
26
|
|
|
use MediaWiki\Logger\LoggerFactory; |
|
27
|
|
|
use MediaWiki\MediaWikiServices; |
|
28
|
|
|
use Wikimedia\ScopedCallback; |
|
|
|
|
|
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* Group all the pieces relevant to the context of a request into one instance |
|
32
|
|
|
*/ |
|
33
|
|
|
class RequestContext implements IContextSource, MutableContext { |
|
34
|
|
|
/** |
|
35
|
|
|
* @var WebRequest |
|
36
|
|
|
*/ |
|
37
|
|
|
private $request; |
|
38
|
|
|
|
|
39
|
|
|
/** |
|
40
|
|
|
* @var Title |
|
41
|
|
|
*/ |
|
42
|
|
|
private $title; |
|
43
|
|
|
|
|
44
|
|
|
/** |
|
45
|
|
|
* @var WikiPage |
|
46
|
|
|
*/ |
|
47
|
|
|
private $wikipage; |
|
48
|
|
|
|
|
49
|
|
|
/** |
|
50
|
|
|
* @var OutputPage |
|
51
|
|
|
*/ |
|
52
|
|
|
private $output; |
|
53
|
|
|
|
|
54
|
|
|
/** |
|
55
|
|
|
* @var User |
|
56
|
|
|
*/ |
|
57
|
|
|
private $user; |
|
58
|
|
|
|
|
59
|
|
|
/** |
|
60
|
|
|
* @var Language |
|
61
|
|
|
*/ |
|
62
|
|
|
private $lang; |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* @var Skin |
|
66
|
|
|
*/ |
|
67
|
|
|
private $skin; |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* @var Timing |
|
71
|
|
|
*/ |
|
72
|
|
|
private $timing; |
|
73
|
|
|
|
|
74
|
|
|
/** |
|
75
|
|
|
* @var Config |
|
76
|
|
|
*/ |
|
77
|
|
|
private $config; |
|
78
|
|
|
|
|
79
|
|
|
/** |
|
80
|
|
|
* @var RequestContext |
|
81
|
|
|
*/ |
|
82
|
|
|
private static $instance = null; |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Set the Config object |
|
86
|
|
|
* |
|
87
|
|
|
* @param Config $c |
|
88
|
|
|
*/ |
|
89
|
|
|
public function setConfig( Config $c ) { |
|
90
|
|
|
$this->config = $c; |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
/** |
|
94
|
|
|
* Get the Config object |
|
95
|
|
|
* |
|
96
|
|
|
* @return Config |
|
97
|
|
|
*/ |
|
98
|
|
|
public function getConfig() { |
|
99
|
|
|
if ( $this->config === null ) { |
|
100
|
|
|
// @todo In the future, we could move this to WebStart.php so |
|
101
|
|
|
// the Config object is ready for when initialization happens |
|
102
|
|
|
$this->config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
return $this->config; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Set the WebRequest object |
|
110
|
|
|
* |
|
111
|
|
|
* @param WebRequest $r |
|
112
|
|
|
*/ |
|
113
|
|
|
public function setRequest( WebRequest $r ) { |
|
114
|
|
|
$this->request = $r; |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Get the WebRequest object |
|
119
|
|
|
* |
|
120
|
|
|
* @return WebRequest |
|
121
|
|
|
*/ |
|
122
|
|
|
public function getRequest() { |
|
123
|
|
|
if ( $this->request === null ) { |
|
124
|
|
|
global $wgCommandLineMode; |
|
125
|
|
|
// create the WebRequest object on the fly |
|
126
|
|
|
if ( $wgCommandLineMode ) { |
|
127
|
|
|
$this->request = new FauxRequest( [] ); |
|
128
|
|
|
} else { |
|
129
|
|
|
$this->request = new WebRequest(); |
|
130
|
|
|
} |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
return $this->request; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
/** |
|
137
|
|
|
* Get the Stats object |
|
138
|
|
|
* |
|
139
|
|
|
* @deprecated since 1.27 use a StatsdDataFactory from MediaWikiServices (preferably injected) |
|
140
|
|
|
* |
|
141
|
|
|
* @return StatsdDataFactory |
|
142
|
|
|
*/ |
|
143
|
|
|
public function getStats() { |
|
144
|
|
|
return MediaWikiServices::getInstance()->getStatsdDataFactory(); |
|
145
|
|
|
} |
|
146
|
|
|
|
|
147
|
|
|
/** |
|
148
|
|
|
* Get the timing object |
|
149
|
|
|
* |
|
150
|
|
|
* @return Timing |
|
151
|
|
|
*/ |
|
152
|
|
|
public function getTiming() { |
|
153
|
|
|
if ( $this->timing === null ) { |
|
154
|
|
|
$this->timing = new Timing( [ |
|
155
|
|
|
'logger' => LoggerFactory::getInstance( 'Timing' ) |
|
156
|
|
|
] ); |
|
157
|
|
|
} |
|
158
|
|
|
return $this->timing; |
|
159
|
|
|
} |
|
160
|
|
|
|
|
161
|
|
|
/** |
|
162
|
|
|
* Set the Title object |
|
163
|
|
|
* |
|
164
|
|
|
* @param Title|null $title |
|
165
|
|
|
*/ |
|
166
|
|
|
public function setTitle( Title $title = null ) { |
|
167
|
|
|
$this->title = $title; |
|
168
|
|
|
// Erase the WikiPage so a new one with the new title gets created. |
|
169
|
|
|
$this->wikipage = null; |
|
170
|
|
|
} |
|
171
|
|
|
|
|
172
|
|
|
/** |
|
173
|
|
|
* Get the Title object |
|
174
|
|
|
* |
|
175
|
|
|
* @return Title|null |
|
176
|
|
|
*/ |
|
177
|
|
|
public function getTitle() { |
|
178
|
|
|
if ( $this->title === null ) { |
|
179
|
|
|
global $wgTitle; # fallback to $wg till we can improve this |
|
180
|
|
|
$this->title = $wgTitle; |
|
181
|
|
|
wfDebugLog( |
|
182
|
|
|
'GlobalTitleFail', |
|
183
|
|
|
__METHOD__ . ' called by ' . wfGetAllCallers( 5 ) . ' with no title set.' |
|
184
|
|
|
); |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
return $this->title; |
|
188
|
|
|
} |
|
189
|
|
|
|
|
190
|
|
|
/** |
|
191
|
|
|
* Check, if a Title object is set |
|
192
|
|
|
* |
|
193
|
|
|
* @since 1.25 |
|
194
|
|
|
* @return bool |
|
195
|
|
|
*/ |
|
196
|
|
|
public function hasTitle() { |
|
197
|
|
|
return $this->title !== null; |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
/** |
|
201
|
|
|
* Check whether a WikiPage object can be get with getWikiPage(). |
|
202
|
|
|
* Callers should expect that an exception is thrown from getWikiPage() |
|
203
|
|
|
* if this method returns false. |
|
204
|
|
|
* |
|
205
|
|
|
* @since 1.19 |
|
206
|
|
|
* @return bool |
|
207
|
|
|
*/ |
|
208
|
|
|
public function canUseWikiPage() { |
|
209
|
|
|
if ( $this->wikipage ) { |
|
210
|
|
|
// If there's a WikiPage object set, we can for sure get it |
|
211
|
|
|
return true; |
|
212
|
|
|
} |
|
213
|
|
|
// Only pages with legitimate titles can have WikiPages. |
|
214
|
|
|
// That usually means pages in non-virtual namespaces. |
|
215
|
|
|
$title = $this->getTitle(); |
|
216
|
|
|
return $title ? $title->canExist() : false; |
|
217
|
|
|
} |
|
218
|
|
|
|
|
219
|
|
|
/** |
|
220
|
|
|
* Set the WikiPage object |
|
221
|
|
|
* |
|
222
|
|
|
* @since 1.19 |
|
223
|
|
|
* @param WikiPage $p |
|
224
|
|
|
*/ |
|
225
|
|
|
public function setWikiPage( WikiPage $p ) { |
|
226
|
|
|
$pageTitle = $p->getTitle(); |
|
227
|
|
|
if ( !$this->hasTitle() || !$pageTitle->equals( $this->getTitle() ) ) { |
|
|
|
|
|
|
228
|
|
|
$this->setTitle( $pageTitle ); |
|
229
|
|
|
} |
|
230
|
|
|
// Defer this to the end since setTitle sets it to null. |
|
231
|
|
|
$this->wikipage = $p; |
|
232
|
|
|
} |
|
233
|
|
|
|
|
234
|
|
|
/** |
|
235
|
|
|
* Get the WikiPage object. |
|
236
|
|
|
* May throw an exception if there's no Title object set or the Title object |
|
237
|
|
|
* belongs to a special namespace that doesn't have WikiPage, so use first |
|
238
|
|
|
* canUseWikiPage() to check whether this method can be called safely. |
|
239
|
|
|
* |
|
240
|
|
|
* @since 1.19 |
|
241
|
|
|
* @throws MWException |
|
242
|
|
|
* @return WikiPage |
|
243
|
|
|
*/ |
|
244
|
|
|
public function getWikiPage() { |
|
245
|
|
|
if ( $this->wikipage === null ) { |
|
246
|
|
|
$title = $this->getTitle(); |
|
247
|
|
|
if ( $title === null ) { |
|
248
|
|
|
throw new MWException( __METHOD__ . ' called without Title object set' ); |
|
249
|
|
|
} |
|
250
|
|
|
$this->wikipage = WikiPage::factory( $title ); |
|
251
|
|
|
} |
|
252
|
|
|
|
|
253
|
|
|
return $this->wikipage; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/** |
|
257
|
|
|
* @param OutputPage $o |
|
258
|
|
|
*/ |
|
259
|
|
|
public function setOutput( OutputPage $o ) { |
|
260
|
|
|
$this->output = $o; |
|
261
|
|
|
} |
|
262
|
|
|
|
|
263
|
|
|
/** |
|
264
|
|
|
* Get the OutputPage object |
|
265
|
|
|
* |
|
266
|
|
|
* @return OutputPage |
|
267
|
|
|
*/ |
|
268
|
|
|
public function getOutput() { |
|
269
|
|
|
if ( $this->output === null ) { |
|
270
|
|
|
$this->output = new OutputPage( $this ); |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
return $this->output; |
|
274
|
|
|
} |
|
275
|
|
|
|
|
276
|
|
|
/** |
|
277
|
|
|
* Set the User object |
|
278
|
|
|
* |
|
279
|
|
|
* @param User $u |
|
280
|
|
|
*/ |
|
281
|
|
|
public function setUser( User $u ) { |
|
282
|
|
|
$this->user = $u; |
|
283
|
|
|
} |
|
284
|
|
|
|
|
285
|
|
|
/** |
|
286
|
|
|
* Get the User object |
|
287
|
|
|
* |
|
288
|
|
|
* @return User |
|
289
|
|
|
*/ |
|
290
|
|
|
public function getUser() { |
|
291
|
|
|
if ( $this->user === null ) { |
|
292
|
|
|
$this->user = User::newFromSession( $this->getRequest() ); |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
return $this->user; |
|
296
|
|
|
} |
|
297
|
|
|
|
|
298
|
|
|
/** |
|
299
|
|
|
* Accepts a language code and ensures it's sane. Outputs a cleaned up language |
|
300
|
|
|
* code and replaces with $wgLanguageCode if not sane. |
|
301
|
|
|
* @param string $code Language code |
|
302
|
|
|
* @return string |
|
303
|
|
|
*/ |
|
304
|
|
|
public static function sanitizeLangCode( $code ) { |
|
305
|
|
|
global $wgLanguageCode; |
|
306
|
|
|
|
|
307
|
|
|
// BCP 47 - letter case MUST NOT carry meaning |
|
308
|
|
|
$code = strtolower( $code ); |
|
309
|
|
|
|
|
310
|
|
|
# Validate $code |
|
311
|
|
|
if ( !$code || !Language::isValidCode( $code ) || $code === 'qqq' ) { |
|
312
|
|
|
wfDebug( "Invalid user language code\n" ); |
|
313
|
|
|
$code = $wgLanguageCode; |
|
314
|
|
|
} |
|
315
|
|
|
|
|
316
|
|
|
return $code; |
|
317
|
|
|
} |
|
318
|
|
|
|
|
319
|
|
|
/** |
|
320
|
|
|
* Set the Language object |
|
321
|
|
|
* |
|
322
|
|
|
* @param Language|string $l Language instance or language code |
|
323
|
|
|
* @throws MWException |
|
324
|
|
|
* @since 1.19 |
|
325
|
|
|
*/ |
|
326
|
|
View Code Duplication |
public function setLanguage( $l ) { |
|
327
|
|
|
if ( $l instanceof Language ) { |
|
328
|
|
|
$this->lang = $l; |
|
329
|
|
|
} elseif ( is_string( $l ) ) { |
|
330
|
|
|
$l = self::sanitizeLangCode( $l ); |
|
331
|
|
|
$obj = Language::factory( $l ); |
|
332
|
|
|
$this->lang = $obj; |
|
333
|
|
|
} else { |
|
334
|
|
|
throw new MWException( __METHOD__ . " was passed an invalid type of data." ); |
|
335
|
|
|
} |
|
336
|
|
|
} |
|
337
|
|
|
|
|
338
|
|
|
/** |
|
339
|
|
|
* Get the Language object. |
|
340
|
|
|
* Initialization of user or request objects can depend on this. |
|
341
|
|
|
* @return Language |
|
342
|
|
|
* @throws Exception |
|
343
|
|
|
* @since 1.19 |
|
344
|
|
|
*/ |
|
345
|
|
|
public function getLanguage() { |
|
346
|
|
|
if ( isset( $this->recursion ) ) { |
|
347
|
|
|
trigger_error( "Recursion detected in " . __METHOD__, E_USER_WARNING ); |
|
348
|
|
|
$e = new Exception; |
|
349
|
|
|
wfDebugLog( 'recursion-guard', "Recursion detected:\n" . $e->getTraceAsString() ); |
|
350
|
|
|
|
|
351
|
|
|
$code = $this->getConfig()->get( 'LanguageCode' ) ?: 'en'; |
|
352
|
|
|
$this->lang = Language::factory( $code ); |
|
353
|
|
|
} elseif ( $this->lang === null ) { |
|
354
|
|
|
$this->recursion = true; |
|
|
|
|
|
|
355
|
|
|
|
|
356
|
|
|
global $wgContLang; |
|
357
|
|
|
|
|
358
|
|
|
try { |
|
359
|
|
|
$request = $this->getRequest(); |
|
360
|
|
|
$user = $this->getUser(); |
|
361
|
|
|
|
|
362
|
|
|
$code = $request->getVal( 'uselang', 'user' ); |
|
363
|
|
|
if ( $code === 'user' ) { |
|
364
|
|
|
$code = $user->getOption( 'language' ); |
|
365
|
|
|
} |
|
366
|
|
|
$code = self::sanitizeLangCode( $code ); |
|
367
|
|
|
|
|
368
|
|
|
Hooks::run( 'UserGetLanguageObject', [ $user, &$code, $this ] ); |
|
369
|
|
|
|
|
370
|
|
|
if ( $code === $this->getConfig()->get( 'LanguageCode' ) ) { |
|
371
|
|
|
$this->lang = $wgContLang; |
|
372
|
|
|
} else { |
|
373
|
|
|
$obj = Language::factory( $code ); |
|
374
|
|
|
$this->lang = $obj; |
|
375
|
|
|
} |
|
376
|
|
|
|
|
377
|
|
|
unset( $this->recursion ); |
|
378
|
|
|
} |
|
379
|
|
|
catch ( Exception $ex ) { |
|
380
|
|
|
unset( $this->recursion ); |
|
381
|
|
|
throw $ex; |
|
382
|
|
|
} |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
return $this->lang; |
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
/** |
|
389
|
|
|
* Set the Skin object |
|
390
|
|
|
* |
|
391
|
|
|
* @param Skin $s |
|
392
|
|
|
*/ |
|
393
|
|
|
public function setSkin( Skin $s ) { |
|
394
|
|
|
$this->skin = clone $s; |
|
395
|
|
|
$this->skin->setContext( $this ); |
|
396
|
|
|
} |
|
397
|
|
|
|
|
398
|
|
|
/** |
|
399
|
|
|
* Get the Skin object |
|
400
|
|
|
* |
|
401
|
|
|
* @return Skin |
|
402
|
|
|
*/ |
|
403
|
|
|
public function getSkin() { |
|
404
|
|
|
if ( $this->skin === null ) { |
|
405
|
|
|
$skin = null; |
|
406
|
|
|
Hooks::run( 'RequestContextCreateSkin', [ $this, &$skin ] ); |
|
407
|
|
|
$factory = SkinFactory::getDefaultInstance(); |
|
408
|
|
|
|
|
409
|
|
|
// If the hook worked try to set a skin from it |
|
410
|
|
|
if ( $skin instanceof Skin ) { |
|
411
|
|
|
$this->skin = $skin; |
|
412
|
|
|
} elseif ( is_string( $skin ) ) { |
|
413
|
|
|
// Normalize the key, just in case the hook did something weird. |
|
414
|
|
|
$normalized = Skin::normalizeKey( $skin ); |
|
415
|
|
|
$this->skin = $factory->makeSkin( $normalized ); |
|
416
|
|
|
} |
|
417
|
|
|
|
|
418
|
|
|
// If this is still null (the hook didn't run or didn't work) |
|
419
|
|
|
// then go through the normal processing to load a skin |
|
420
|
|
|
if ( $this->skin === null ) { |
|
421
|
|
|
if ( !in_array( 'skin', $this->getConfig()->get( 'HiddenPrefs' ) ) ) { |
|
422
|
|
|
# get the user skin |
|
423
|
|
|
$userSkin = $this->getUser()->getOption( 'skin' ); |
|
424
|
|
|
$userSkin = $this->getRequest()->getVal( 'useskin', $userSkin ); |
|
425
|
|
|
} else { |
|
426
|
|
|
# if we're not allowing users to override, then use the default |
|
427
|
|
|
$userSkin = $this->getConfig()->get( 'DefaultSkin' ); |
|
428
|
|
|
} |
|
429
|
|
|
|
|
430
|
|
|
// Normalize the key in case the user is passing gibberish |
|
431
|
|
|
// or has old preferences (bug 69566). |
|
432
|
|
|
$normalized = Skin::normalizeKey( $userSkin ); |
|
433
|
|
|
|
|
434
|
|
|
// Skin::normalizeKey will also validate it, so |
|
435
|
|
|
// this won't throw an exception |
|
436
|
|
|
$this->skin = $factory->makeSkin( $normalized ); |
|
437
|
|
|
} |
|
438
|
|
|
|
|
439
|
|
|
// After all that set a context on whatever skin got created |
|
440
|
|
|
$this->skin->setContext( $this ); |
|
441
|
|
|
} |
|
442
|
|
|
|
|
443
|
|
|
return $this->skin; |
|
444
|
|
|
} |
|
445
|
|
|
|
|
446
|
|
|
/** Helpful methods **/ |
|
447
|
|
|
|
|
448
|
|
|
/** |
|
449
|
|
|
* Get a Message object with context set |
|
450
|
|
|
* Parameters are the same as wfMessage() |
|
451
|
|
|
* |
|
452
|
|
|
* @param mixed ... |
|
453
|
|
|
* @return Message |
|
454
|
|
|
*/ |
|
455
|
|
|
public function msg() { |
|
456
|
|
|
$args = func_get_args(); |
|
457
|
|
|
|
|
458
|
|
|
return call_user_func_array( 'wfMessage', $args )->setContext( $this ); |
|
459
|
|
|
} |
|
460
|
|
|
|
|
461
|
|
|
/** Static methods **/ |
|
462
|
|
|
|
|
463
|
|
|
/** |
|
464
|
|
|
* Get the RequestContext object associated with the main request |
|
465
|
|
|
* |
|
466
|
|
|
* @return RequestContext |
|
467
|
|
|
*/ |
|
468
|
|
|
public static function getMain() { |
|
469
|
|
|
if ( self::$instance === null ) { |
|
470
|
|
|
self::$instance = new self; |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
return self::$instance; |
|
474
|
|
|
} |
|
475
|
|
|
|
|
476
|
|
|
/** |
|
477
|
|
|
* Get the RequestContext object associated with the main request |
|
478
|
|
|
* and gives a warning to the log, to find places, where a context maybe is missing. |
|
479
|
|
|
* |
|
480
|
|
|
* @param string $func |
|
481
|
|
|
* @return RequestContext |
|
482
|
|
|
* @since 1.24 |
|
483
|
|
|
*/ |
|
484
|
|
|
public static function getMainAndWarn( $func = __METHOD__ ) { |
|
485
|
|
|
wfDebug( $func . ' called without context. ' . |
|
486
|
|
|
"Using RequestContext::getMain() for sanity\n" ); |
|
487
|
|
|
|
|
488
|
|
|
return self::getMain(); |
|
489
|
|
|
} |
|
490
|
|
|
|
|
491
|
|
|
/** |
|
492
|
|
|
* Resets singleton returned by getMain(). Should be called only from unit tests. |
|
493
|
|
|
*/ |
|
494
|
|
|
public static function resetMain() { |
|
495
|
|
|
if ( !( defined( 'MW_PHPUNIT_TEST' ) || defined( 'MW_PARSER_TEST' ) ) ) { |
|
496
|
|
|
throw new MWException( __METHOD__ . '() should be called only from unit tests!' ); |
|
497
|
|
|
} |
|
498
|
|
|
self::$instance = null; |
|
499
|
|
|
} |
|
500
|
|
|
|
|
501
|
|
|
/** |
|
502
|
|
|
* Export the resolved user IP, HTTP headers, user ID, and session ID. |
|
503
|
|
|
* The result will be reasonably sized to allow for serialization. |
|
504
|
|
|
* |
|
505
|
|
|
* @return array |
|
506
|
|
|
* @since 1.21 |
|
507
|
|
|
*/ |
|
508
|
|
|
public function exportSession() { |
|
509
|
|
|
$session = MediaWiki\Session\SessionManager::getGlobalSession(); |
|
510
|
|
|
return [ |
|
511
|
|
|
'ip' => $this->getRequest()->getIP(), |
|
512
|
|
|
'headers' => $this->getRequest()->getAllHeaders(), |
|
513
|
|
|
'sessionId' => $session->isPersistent() ? $session->getId() : '', |
|
514
|
|
|
'userId' => $this->getUser()->getId() |
|
515
|
|
|
]; |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
/** |
|
519
|
|
|
* Import an client IP address, HTTP headers, user ID, and session ID |
|
520
|
|
|
* |
|
521
|
|
|
* This sets the current session, $wgUser, and $wgRequest from $params. |
|
522
|
|
|
* Once the return value falls out of scope, the old context is restored. |
|
523
|
|
|
* This method should only be called in contexts where there is no session |
|
524
|
|
|
* ID or end user receiving the response (CLI or HTTP job runners). This |
|
525
|
|
|
* is partly enforced, and is done so to avoid leaking cookies if certain |
|
526
|
|
|
* error conditions arise. |
|
527
|
|
|
* |
|
528
|
|
|
* This is useful when background scripts inherit context when acting on |
|
529
|
|
|
* behalf of a user. In general the 'sessionId' parameter should be set |
|
530
|
|
|
* to an empty string unless session importing is *truly* needed. This |
|
531
|
|
|
* feature is somewhat deprecated. |
|
532
|
|
|
* |
|
533
|
|
|
* @note suhosin.session.encrypt may interfere with this method. |
|
534
|
|
|
* |
|
535
|
|
|
* @param array $params Result of RequestContext::exportSession() |
|
536
|
|
|
* @return ScopedCallback |
|
537
|
|
|
* @throws MWException |
|
538
|
|
|
* @since 1.21 |
|
539
|
|
|
*/ |
|
540
|
|
|
public static function importScopedSession( array $params ) { |
|
|
|
|
|
|
541
|
|
|
if ( strlen( $params['sessionId'] ) && |
|
542
|
|
|
MediaWiki\Session\SessionManager::getGlobalSession()->isPersistent() |
|
543
|
|
|
) { |
|
544
|
|
|
// Sanity check to avoid sending random cookies for the wrong users. |
|
545
|
|
|
// This method should only called by CLI scripts or by HTTP job runners. |
|
546
|
|
|
throw new MWException( "Sessions can only be imported when none is active." ); |
|
547
|
|
|
} elseif ( !IP::isValid( $params['ip'] ) ) { |
|
548
|
|
|
throw new MWException( "Invalid client IP address '{$params['ip']}'." ); |
|
549
|
|
|
} |
|
550
|
|
|
|
|
551
|
|
|
if ( $params['userId'] ) { // logged-in user |
|
552
|
|
|
$user = User::newFromId( $params['userId'] ); |
|
553
|
|
|
$user->load(); |
|
554
|
|
|
if ( !$user->getId() ) { |
|
555
|
|
|
throw new MWException( "No user with ID '{$params['userId']}'." ); |
|
556
|
|
|
} |
|
557
|
|
|
} else { // anon user |
|
558
|
|
|
$user = User::newFromName( $params['ip'], false ); |
|
559
|
|
|
} |
|
560
|
|
|
|
|
561
|
|
|
$importSessionFunc = function ( User $user, array $params ) { |
|
562
|
|
|
global $wgRequest, $wgUser; |
|
563
|
|
|
|
|
564
|
|
|
$context = RequestContext::getMain(); |
|
565
|
|
|
|
|
566
|
|
|
// Commit and close any current session |
|
567
|
|
|
if ( MediaWiki\Session\PHPSessionHandler::isEnabled() ) { |
|
568
|
|
|
session_write_close(); // persist |
|
569
|
|
|
session_id( '' ); // detach |
|
570
|
|
|
$_SESSION = []; // clear in-memory array |
|
571
|
|
|
} |
|
572
|
|
|
|
|
573
|
|
|
// Get new session, if applicable |
|
574
|
|
|
$session = null; |
|
575
|
|
|
if ( strlen( $params['sessionId'] ) ) { // don't make a new random ID |
|
576
|
|
|
$manager = MediaWiki\Session\SessionManager::singleton(); |
|
577
|
|
|
$session = $manager->getSessionById( $params['sessionId'], true ) |
|
578
|
|
|
?: $manager->getEmptySession(); |
|
579
|
|
|
} |
|
580
|
|
|
|
|
581
|
|
|
// Remove any user IP or agent information, and attach the request |
|
582
|
|
|
// with the new session. |
|
583
|
|
|
$context->setRequest( new FauxRequest( [], false, $session ) ); |
|
584
|
|
|
$wgRequest = $context->getRequest(); // b/c |
|
585
|
|
|
|
|
586
|
|
|
// Now that all private information is detached from the user, it should |
|
587
|
|
|
// be safe to load the new user. If errors occur or an exception is thrown |
|
588
|
|
|
// and caught (leaving the main context in a mixed state), there is no risk |
|
589
|
|
|
// of the User object being attached to the wrong IP, headers, or session. |
|
590
|
|
|
$context->setUser( $user ); |
|
591
|
|
|
$wgUser = $context->getUser(); // b/c |
|
592
|
|
|
if ( $session && MediaWiki\Session\PHPSessionHandler::isEnabled() ) { |
|
593
|
|
|
session_id( $session->getId() ); |
|
594
|
|
|
MediaWiki\quietCall( 'session_start' ); |
|
595
|
|
|
} |
|
596
|
|
|
$request = new FauxRequest( [], false, $session ); |
|
597
|
|
|
$request->setIP( $params['ip'] ); |
|
598
|
|
|
foreach ( $params['headers'] as $name => $value ) { |
|
599
|
|
|
$request->setHeader( $name, $value ); |
|
600
|
|
|
} |
|
601
|
|
|
// Set the current context to use the new WebRequest |
|
602
|
|
|
$context->setRequest( $request ); |
|
603
|
|
|
$wgRequest = $context->getRequest(); // b/c |
|
604
|
|
|
}; |
|
605
|
|
|
|
|
606
|
|
|
// Stash the old session and load in the new one |
|
607
|
|
|
$oUser = self::getMain()->getUser(); |
|
608
|
|
|
$oParams = self::getMain()->exportSession(); |
|
609
|
|
|
$oRequest = self::getMain()->getRequest(); |
|
610
|
|
|
$importSessionFunc( $user, $params ); |
|
611
|
|
|
|
|
612
|
|
|
// Set callback to save and close the new session and reload the old one |
|
613
|
|
|
return new ScopedCallback( |
|
614
|
|
|
function () use ( $importSessionFunc, $oUser, $oParams, $oRequest ) { |
|
615
|
|
|
global $wgRequest; |
|
616
|
|
|
$importSessionFunc( $oUser, $oParams ); |
|
617
|
|
|
// Restore the exact previous Request object (instead of leaving FauxRequest) |
|
618
|
|
|
RequestContext::getMain()->setRequest( $oRequest ); |
|
619
|
|
|
$wgRequest = RequestContext::getMain()->getRequest(); // b/c |
|
620
|
|
|
} |
|
621
|
|
|
); |
|
622
|
|
|
} |
|
623
|
|
|
|
|
624
|
|
|
/** |
|
625
|
|
|
* Create a new extraneous context. The context is filled with information |
|
626
|
|
|
* external to the current session. |
|
627
|
|
|
* - Title is specified by argument |
|
628
|
|
|
* - Request is a FauxRequest, or a FauxRequest can be specified by argument |
|
629
|
|
|
* - User is an anonymous user, for separation IPv4 localhost is used |
|
630
|
|
|
* - Language will be based on the anonymous user and request, may be content |
|
631
|
|
|
* language or a uselang param in the fauxrequest data may change the lang |
|
632
|
|
|
* - Skin will be based on the anonymous user, should be the wiki's default skin |
|
633
|
|
|
* |
|
634
|
|
|
* @param Title $title Title to use for the extraneous request |
|
635
|
|
|
* @param WebRequest|array $request A WebRequest or data to use for a FauxRequest |
|
636
|
|
|
* @return RequestContext |
|
637
|
|
|
*/ |
|
638
|
|
|
public static function newExtraneousContext( Title $title, $request = [] ) { |
|
639
|
|
|
$context = new self; |
|
640
|
|
|
$context->setTitle( $title ); |
|
641
|
|
|
if ( $request instanceof WebRequest ) { |
|
642
|
|
|
$context->setRequest( $request ); |
|
643
|
|
|
} else { |
|
644
|
|
|
$context->setRequest( new FauxRequest( $request ) ); |
|
645
|
|
|
} |
|
646
|
|
|
$context->user = User::newFromName( '127.0.0.1', false ); |
|
|
|
|
|
|
647
|
|
|
|
|
648
|
|
|
return $context; |
|
649
|
|
|
} |
|
650
|
|
|
} |
|
651
|
|
|
|
Let’s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let’s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: