1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Base class for all skins. |
4
|
|
|
* |
5
|
|
|
* This program is free software; you can redistribute it and/or modify |
6
|
|
|
* it under the terms of the GNU General Public License as published by |
7
|
|
|
* the Free Software Foundation; either version 2 of the License, or |
8
|
|
|
* (at your option) any later version. |
9
|
|
|
* |
10
|
|
|
* This program is distributed in the hope that it will be useful, |
11
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
12
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13
|
|
|
* GNU General Public License for more details. |
14
|
|
|
* |
15
|
|
|
* You should have received a copy of the GNU General Public License along |
16
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc., |
17
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18
|
|
|
* http://www.gnu.org/copyleft/gpl.html |
19
|
|
|
* |
20
|
|
|
* @file |
21
|
|
|
*/ |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* @defgroup Skins Skins |
25
|
|
|
*/ |
26
|
|
|
|
27
|
|
|
/** |
28
|
|
|
* The main skin class which provides methods and properties for all other skins. |
29
|
|
|
* |
30
|
|
|
* See docs/skin.txt for more information. |
31
|
|
|
* |
32
|
|
|
* @ingroup Skins |
33
|
|
|
*/ |
34
|
|
|
abstract class Skin extends ContextSource { |
35
|
|
|
protected $skinname = null; |
36
|
|
|
protected $mRelevantTitle = null; |
37
|
|
|
protected $mRelevantUser = null; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @var string Stylesheets set to use. Subdirectory in skins/ where various stylesheets are |
41
|
|
|
* located. Only needs to be set if you intend to use the getSkinStylePath() method. |
42
|
|
|
*/ |
43
|
|
|
public $stylename = null; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* Fetch the set of available skins. |
47
|
|
|
* @return array Associative array of strings |
48
|
|
|
*/ |
49
|
|
|
static function getSkinNames() { |
50
|
|
|
return SkinFactory::getDefaultInstance()->getSkinNames(); |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Fetch the skinname messages for available skins. |
55
|
|
|
* @return string[] |
56
|
|
|
*/ |
57
|
|
|
static function getSkinNameMessages() { |
58
|
|
|
$messages = []; |
59
|
|
|
foreach ( self::getSkinNames() as $skinKey => $skinName ) { |
60
|
|
|
$messages[] = "skinname-$skinKey"; |
61
|
|
|
} |
62
|
|
|
return $messages; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* Fetch the list of user-selectable skins in regards to $wgSkipSkins. |
67
|
|
|
* Useful for Special:Preferences and other places where you |
68
|
|
|
* only want to show skins users _can_ use. |
69
|
|
|
* @return string[] |
70
|
|
|
* @since 1.23 |
71
|
|
|
*/ |
72
|
|
|
public static function getAllowedSkins() { |
73
|
|
|
global $wgSkipSkins; |
74
|
|
|
|
75
|
|
|
$allowedSkins = self::getSkinNames(); |
76
|
|
|
|
77
|
|
|
foreach ( $wgSkipSkins as $skip ) { |
78
|
|
|
unset( $allowedSkins[$skip] ); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
return $allowedSkins; |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* Normalize a skin preference value to a form that can be loaded. |
86
|
|
|
* |
87
|
|
|
* If a skin can't be found, it will fall back to the configured default ($wgDefaultSkin), or the |
88
|
|
|
* hardcoded default ($wgFallbackSkin) if the default skin is unavailable too. |
89
|
|
|
* |
90
|
|
|
* @param string $key 'monobook', 'vector', etc. |
91
|
|
|
* @return string |
92
|
|
|
*/ |
93
|
|
|
static function normalizeKey( $key ) { |
94
|
|
|
global $wgDefaultSkin, $wgFallbackSkin; |
95
|
|
|
|
96
|
|
|
$skinNames = Skin::getSkinNames(); |
97
|
|
|
|
98
|
|
|
// Make keys lowercase for case-insensitive matching. |
99
|
|
|
$skinNames = array_change_key_case( $skinNames, CASE_LOWER ); |
100
|
|
|
$key = strtolower( $key ); |
101
|
|
|
$defaultSkin = strtolower( $wgDefaultSkin ); |
102
|
|
|
$fallbackSkin = strtolower( $wgFallbackSkin ); |
103
|
|
|
|
104
|
|
|
if ( $key == '' || $key == 'default' ) { |
105
|
|
|
// Don't return the default immediately; |
106
|
|
|
// in a misconfiguration we need to fall back. |
107
|
|
|
$key = $defaultSkin; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
if ( isset( $skinNames[$key] ) ) { |
111
|
|
|
return $key; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
// Older versions of the software used a numeric setting |
115
|
|
|
// in the user preferences. |
116
|
|
|
$fallback = [ |
117
|
|
|
0 => $defaultSkin, |
118
|
|
|
2 => 'cologneblue' |
119
|
|
|
]; |
120
|
|
|
|
121
|
|
|
if ( isset( $fallback[$key] ) ) { |
122
|
|
|
$key = $fallback[$key]; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
if ( isset( $skinNames[$key] ) ) { |
126
|
|
|
return $key; |
127
|
|
|
} elseif ( isset( $skinNames[$defaultSkin] ) ) { |
128
|
|
|
return $defaultSkin; |
129
|
|
|
} else { |
130
|
|
|
return $fallbackSkin; |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @return string Skin name |
136
|
|
|
*/ |
137
|
|
|
public function getSkinName() { |
138
|
|
|
return $this->skinname; |
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
/** |
142
|
|
|
* @param OutputPage $out |
143
|
|
|
*/ |
144
|
|
|
public function initPage( OutputPage $out ) { |
145
|
|
|
$this->preloadExistence(); |
146
|
|
|
} |
147
|
|
|
|
148
|
|
|
/** |
149
|
|
|
* Defines the ResourceLoader modules that should be added to the skin |
150
|
|
|
* It is recommended that skins wishing to override call parent::getDefaultModules() |
151
|
|
|
* and substitute out any modules they wish to change by using a key to look them up |
152
|
|
|
* @return array Array of modules with helper keys for easy overriding |
153
|
|
|
*/ |
154
|
|
|
public function getDefaultModules() { |
155
|
|
|
global $wgUseAjax, $wgEnableAPI, $wgEnableWriteAPI; |
156
|
|
|
|
157
|
|
|
$out = $this->getOutput(); |
158
|
|
|
$user = $out->getUser(); |
159
|
|
|
$modules = [ |
160
|
|
|
// modules that enhance the page content in some way |
161
|
|
|
'content' => [ |
162
|
|
|
'mediawiki.page.ready', |
163
|
|
|
], |
164
|
|
|
// modules that exist for legacy reasons |
165
|
|
|
'legacy' => ResourceLoaderStartUpModule::getLegacyModules(), |
166
|
|
|
// modules relating to search functionality |
167
|
|
|
'search' => [], |
168
|
|
|
// modules relating to functionality relating to watching an article |
169
|
|
|
'watch' => [], |
170
|
|
|
// modules which relate to the current users preferences |
171
|
|
|
'user' => [], |
172
|
|
|
]; |
173
|
|
|
|
174
|
|
|
// Add various resources if required |
175
|
|
|
if ( $wgUseAjax && $wgEnableAPI ) { |
176
|
|
|
if ( $wgEnableWriteAPI && $user->isLoggedIn() |
177
|
|
|
&& $user->isAllowedAll( 'writeapi', 'viewmywatchlist', 'editmywatchlist' ) |
178
|
|
|
&& $this->getRelevantTitle()->canExist() |
179
|
|
|
) { |
180
|
|
|
$modules['watch'][] = 'mediawiki.page.watch.ajax'; |
181
|
|
|
} |
182
|
|
|
|
183
|
|
|
$modules['search'][] = 'mediawiki.searchSuggest'; |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
if ( $user->getBoolOption( 'editsectiononrightclick' ) ) { |
187
|
|
|
$modules['user'][] = 'mediawiki.action.view.rightClickEdit'; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
// Crazy edit-on-double-click stuff |
191
|
|
|
if ( $out->isArticle() && $user->getOption( 'editondblclick' ) ) { |
192
|
|
|
$modules['user'][] = 'mediawiki.action.view.dblClickEdit'; |
193
|
|
|
} |
194
|
|
|
return $modules; |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
/** |
198
|
|
|
* Preload the existence of three commonly-requested pages in a single query |
199
|
|
|
*/ |
200
|
|
|
protected function preloadExistence() { |
201
|
|
|
$titles = []; |
202
|
|
|
|
203
|
|
|
// User/talk link |
204
|
|
|
$user = $this->getUser(); |
205
|
|
|
if ( $user->isLoggedIn() ) { |
206
|
|
|
$titles[] = $user->getUserPage(); |
207
|
|
|
$titles[] = $user->getTalkPage(); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
// Check, if the page can hold some kind of content, otherwise do nothing |
211
|
|
|
$title = $this->getRelevantTitle(); |
212
|
|
|
if ( $title->canExist() ) { |
213
|
|
|
if ( $title->isTalkPage() ) { |
214
|
|
|
$titles[] = $title->getSubjectPage(); |
215
|
|
|
} else { |
216
|
|
|
$titles[] = $title->getTalkPage(); |
217
|
|
|
} |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
Hooks::run( 'SkinPreloadExistence', [ &$titles, $this ] ); |
221
|
|
|
|
222
|
|
|
if ( $titles ) { |
223
|
|
|
$lb = new LinkBatch( $titles ); |
224
|
|
|
$lb->setCaller( __METHOD__ ); |
225
|
|
|
$lb->execute(); |
226
|
|
|
} |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Get the current revision ID |
231
|
|
|
* |
232
|
|
|
* @return int |
233
|
|
|
*/ |
234
|
|
|
public function getRevisionId() { |
235
|
|
|
return $this->getOutput()->getRevisionId(); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Whether the revision displayed is the latest revision of the page |
240
|
|
|
* |
241
|
|
|
* @return bool |
242
|
|
|
*/ |
243
|
|
|
public function isRevisionCurrent() { |
244
|
|
|
$revID = $this->getRevisionId(); |
245
|
|
|
return $revID == 0 || $revID == $this->getTitle()->getLatestRevID(); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
/** |
249
|
|
|
* Set the "relevant" title |
250
|
|
|
* @see self::getRelevantTitle() |
251
|
|
|
* @param Title $t |
252
|
|
|
*/ |
253
|
|
|
public function setRelevantTitle( $t ) { |
254
|
|
|
$this->mRelevantTitle = $t; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Return the "relevant" title. |
259
|
|
|
* A "relevant" title is not necessarily the actual title of the page. |
260
|
|
|
* Special pages like Special:MovePage use set the page they are acting on |
261
|
|
|
* as their "relevant" title, this allows the skin system to display things |
262
|
|
|
* such as content tabs which belong to to that page instead of displaying |
263
|
|
|
* a basic special page tab which has almost no meaning. |
264
|
|
|
* |
265
|
|
|
* @return Title |
266
|
|
|
*/ |
267
|
|
|
public function getRelevantTitle() { |
268
|
|
|
if ( isset( $this->mRelevantTitle ) ) { |
269
|
|
|
return $this->mRelevantTitle; |
270
|
|
|
} |
271
|
|
|
return $this->getTitle(); |
272
|
|
|
} |
273
|
|
|
|
274
|
|
|
/** |
275
|
|
|
* Set the "relevant" user |
276
|
|
|
* @see self::getRelevantUser() |
277
|
|
|
* @param User $u |
278
|
|
|
*/ |
279
|
|
|
public function setRelevantUser( $u ) { |
280
|
|
|
$this->mRelevantUser = $u; |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
/** |
284
|
|
|
* Return the "relevant" user. |
285
|
|
|
* A "relevant" user is similar to a relevant title. Special pages like |
286
|
|
|
* Special:Contributions mark the user which they are relevant to so that |
287
|
|
|
* things like the toolbox can display the information they usually are only |
288
|
|
|
* able to display on a user's userpage and talkpage. |
289
|
|
|
* @return User |
290
|
|
|
*/ |
291
|
|
|
public function getRelevantUser() { |
292
|
|
|
if ( isset( $this->mRelevantUser ) ) { |
293
|
|
|
return $this->mRelevantUser; |
294
|
|
|
} |
295
|
|
|
$title = $this->getRelevantTitle(); |
296
|
|
|
if ( $title->hasSubjectNamespace( NS_USER ) ) { |
297
|
|
|
$rootUser = $title->getRootText(); |
298
|
|
|
if ( User::isIP( $rootUser ) ) { |
299
|
|
|
$this->mRelevantUser = User::newFromName( $rootUser, false ); |
300
|
|
|
} else { |
301
|
|
|
$user = User::newFromName( $rootUser, false ); |
302
|
|
|
|
303
|
|
|
if ( $user ) { |
304
|
|
|
$user->load( User::READ_NORMAL ); |
305
|
|
|
|
306
|
|
|
if ( $user->isLoggedIn() ) { |
307
|
|
|
$this->mRelevantUser = $user; |
308
|
|
|
} |
309
|
|
|
} |
310
|
|
|
} |
311
|
|
|
return $this->mRelevantUser; |
312
|
|
|
} |
313
|
|
|
return null; |
314
|
|
|
} |
315
|
|
|
|
316
|
|
|
/** |
317
|
|
|
* Outputs the HTML generated by other functions. |
318
|
|
|
* @param OutputPage $out |
319
|
|
|
*/ |
320
|
|
|
abstract function outputPage( OutputPage $out = null ); |
321
|
|
|
|
322
|
|
|
/** |
323
|
|
|
* @param array $data |
324
|
|
|
* @return string |
325
|
|
|
*/ |
326
|
|
|
static function makeVariablesScript( $data ) { |
327
|
|
|
if ( $data ) { |
328
|
|
|
return ResourceLoader::makeInlineScript( |
329
|
|
|
ResourceLoader::makeConfigSetScript( $data ) |
|
|
|
|
330
|
|
|
); |
331
|
|
|
} else { |
332
|
|
|
return ''; |
333
|
|
|
} |
334
|
|
|
} |
335
|
|
|
|
336
|
|
|
/** |
337
|
|
|
* Get the query to generate a dynamic stylesheet |
338
|
|
|
* |
339
|
|
|
* @return array |
340
|
|
|
*/ |
341
|
|
|
public static function getDynamicStylesheetQuery() { |
342
|
|
|
global $wgSquidMaxage; |
343
|
|
|
|
344
|
|
|
return [ |
345
|
|
|
'action' => 'raw', |
346
|
|
|
'maxage' => $wgSquidMaxage, |
347
|
|
|
'usemsgcache' => 'yes', |
348
|
|
|
'ctype' => 'text/css', |
349
|
|
|
'smaxage' => $wgSquidMaxage, |
350
|
|
|
]; |
351
|
|
|
} |
352
|
|
|
|
353
|
|
|
/** |
354
|
|
|
* Add skin specific stylesheets |
355
|
|
|
* Calling this method with an $out of anything but the same OutputPage |
356
|
|
|
* inside ->getOutput() is deprecated. The $out arg is kept |
357
|
|
|
* for compatibility purposes with skins. |
358
|
|
|
* @param OutputPage $out |
359
|
|
|
* @todo delete |
360
|
|
|
*/ |
361
|
|
|
abstract function setupSkinUserCss( OutputPage $out ); |
362
|
|
|
|
363
|
|
|
/** |
364
|
|
|
* TODO: document |
365
|
|
|
* @param Title $title |
366
|
|
|
* @return string |
367
|
|
|
*/ |
368
|
|
|
function getPageClasses( $title ) { |
369
|
|
|
$numeric = 'ns-' . $title->getNamespace(); |
370
|
|
|
|
371
|
|
|
if ( $title->isSpecialPage() ) { |
372
|
|
|
$type = 'ns-special'; |
373
|
|
|
// bug 23315: provide a class based on the canonical special page name without subpages |
374
|
|
|
list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); |
375
|
|
|
if ( $canonicalName ) { |
376
|
|
|
$type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" ); |
377
|
|
|
} else { |
378
|
|
|
$type .= ' mw-invalidspecialpage'; |
379
|
|
|
} |
380
|
|
|
} elseif ( $title->isTalkPage() ) { |
381
|
|
|
$type = 'ns-talk'; |
382
|
|
|
} else { |
383
|
|
|
$type = 'ns-subject'; |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
$name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() ); |
387
|
|
|
$root = Sanitizer::escapeClass( 'rootpage-' . $title->getRootTitle()->getPrefixedText() ); |
388
|
|
|
|
389
|
|
|
return "$numeric $type $name $root"; |
390
|
|
|
} |
391
|
|
|
|
392
|
|
|
/** |
393
|
|
|
* Return values for <html> element |
394
|
|
|
* @return array Array of associative name-to-value elements for <html> element |
395
|
|
|
*/ |
396
|
|
|
public function getHtmlElementAttributes() { |
397
|
|
|
$lang = $this->getLanguage(); |
398
|
|
|
return [ |
399
|
|
|
'lang' => $lang->getHtmlCode(), |
400
|
|
|
'dir' => $lang->getDir(), |
401
|
|
|
'class' => 'client-nojs', |
402
|
|
|
]; |
403
|
|
|
} |
404
|
|
|
|
405
|
|
|
/** |
406
|
|
|
* This will be called by OutputPage::headElement when it is creating the |
407
|
|
|
* "<body>" tag, skins can override it if they have a need to add in any |
408
|
|
|
* body attributes or classes of their own. |
409
|
|
|
* @param OutputPage $out |
410
|
|
|
* @param array $bodyAttrs |
411
|
|
|
*/ |
412
|
|
|
function addToBodyAttributes( $out, &$bodyAttrs ) { |
413
|
|
|
// does nothing by default |
414
|
|
|
} |
415
|
|
|
|
416
|
|
|
/** |
417
|
|
|
* URL to the logo |
418
|
|
|
* @return string |
419
|
|
|
*/ |
420
|
|
|
function getLogo() { |
421
|
|
|
global $wgLogo; |
422
|
|
|
return $wgLogo; |
423
|
|
|
} |
424
|
|
|
|
425
|
|
|
/** |
426
|
|
|
* @return string HTML |
427
|
|
|
*/ |
428
|
|
|
function getCategoryLinks() { |
429
|
|
|
global $wgUseCategoryBrowser; |
430
|
|
|
|
431
|
|
|
$out = $this->getOutput(); |
432
|
|
|
$allCats = $out->getCategoryLinks(); |
433
|
|
|
|
434
|
|
|
if ( !count( $allCats ) ) { |
435
|
|
|
return ''; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
$embed = "<li>"; |
439
|
|
|
$pop = "</li>"; |
440
|
|
|
|
441
|
|
|
$s = ''; |
442
|
|
|
$colon = $this->msg( 'colon-separator' )->escaped(); |
443
|
|
|
|
444
|
|
|
if ( !empty( $allCats['normal'] ) ) { |
445
|
|
|
$t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop; |
446
|
|
|
|
447
|
|
|
$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped(); |
448
|
|
|
$linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text(); |
449
|
|
|
$title = Title::newFromText( $linkPage ); |
450
|
|
|
$link = $title ? Linker::link( $title, $msg ) : $msg; |
451
|
|
|
$s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' . |
452
|
|
|
$link . $colon . '<ul>' . $t . '</ul>' . '</div>'; |
453
|
|
|
} |
454
|
|
|
|
455
|
|
|
# Hidden categories |
456
|
|
|
if ( isset( $allCats['hidden'] ) ) { |
457
|
|
|
if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) { |
458
|
|
|
$class = ' mw-hidden-cats-user-shown'; |
459
|
|
|
} elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) { |
460
|
|
|
$class = ' mw-hidden-cats-ns-shown'; |
461
|
|
|
} else { |
462
|
|
|
$class = ' mw-hidden-cats-hidden'; |
463
|
|
|
} |
464
|
|
|
|
465
|
|
|
$s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" . |
466
|
|
|
$this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() . |
467
|
|
|
$colon . '<ul>' . $embed . implode( "{$pop}{$embed}", $allCats['hidden'] ) . $pop . '</ul>' . |
468
|
|
|
'</div>'; |
469
|
|
|
} |
470
|
|
|
|
471
|
|
|
# optional 'dmoz-like' category browser. Will be shown under the list |
472
|
|
|
# of categories an article belong to |
473
|
|
|
if ( $wgUseCategoryBrowser ) { |
474
|
|
|
$s .= '<br /><hr />'; |
475
|
|
|
|
476
|
|
|
# get a big array of the parents tree |
477
|
|
|
$parenttree = $this->getTitle()->getParentCategoryTree(); |
478
|
|
|
# Skin object passed by reference cause it can not be |
479
|
|
|
# accessed under the method subfunction drawCategoryBrowser |
480
|
|
|
$tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) ); |
481
|
|
|
# Clean out bogus first entry and sort them |
482
|
|
|
unset( $tempout[0] ); |
483
|
|
|
asort( $tempout ); |
484
|
|
|
# Output one per line |
485
|
|
|
$s .= implode( "<br />\n", $tempout ); |
486
|
|
|
} |
487
|
|
|
|
488
|
|
|
return $s; |
489
|
|
|
} |
490
|
|
|
|
491
|
|
|
/** |
492
|
|
|
* Render the array as a series of links. |
493
|
|
|
* @param array $tree Categories tree returned by Title::getParentCategoryTree |
494
|
|
|
* @return string Separated by >, terminate with "\n" |
495
|
|
|
*/ |
496
|
|
|
function drawCategoryBrowser( $tree ) { |
497
|
|
|
$return = ''; |
498
|
|
|
|
499
|
|
|
foreach ( $tree as $element => $parent ) { |
500
|
|
|
if ( empty( $parent ) ) { |
501
|
|
|
# element start a new list |
502
|
|
|
$return .= "\n"; |
503
|
|
|
} else { |
504
|
|
|
# grab the others elements |
505
|
|
|
$return .= $this->drawCategoryBrowser( $parent ) . ' > '; |
506
|
|
|
} |
507
|
|
|
|
508
|
|
|
# add our current element to the list |
509
|
|
|
$eltitle = Title::newFromText( $element ); |
510
|
|
|
$return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) ); |
|
|
|
|
511
|
|
|
} |
512
|
|
|
|
513
|
|
|
return $return; |
514
|
|
|
} |
515
|
|
|
|
516
|
|
|
/** |
517
|
|
|
* @return string HTML |
518
|
|
|
*/ |
519
|
|
|
function getCategories() { |
520
|
|
|
$out = $this->getOutput(); |
521
|
|
|
$catlinks = $this->getCategoryLinks(); |
522
|
|
|
|
523
|
|
|
// Check what we're showing |
524
|
|
|
$allCats = $out->getCategoryLinks(); |
525
|
|
|
$showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) || |
526
|
|
|
$this->getTitle()->getNamespace() == NS_CATEGORY; |
527
|
|
|
|
528
|
|
|
$classes = [ 'catlinks' ]; |
529
|
|
|
if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) { |
530
|
|
|
$classes[] = 'catlinks-allhidden'; |
531
|
|
|
} |
532
|
|
|
|
533
|
|
|
return Html::rawElement( |
534
|
|
|
'div', |
535
|
|
|
[ 'id' => 'catlinks', 'class' => $classes, 'data-mw' => 'interface' ], |
536
|
|
|
$catlinks |
537
|
|
|
); |
538
|
|
|
} |
539
|
|
|
|
540
|
|
|
/** |
541
|
|
|
* This runs a hook to allow extensions placing their stuff after content |
542
|
|
|
* and article metadata (e.g. categories). |
543
|
|
|
* Note: This function has nothing to do with afterContent(). |
544
|
|
|
* |
545
|
|
|
* This hook is placed here in order to allow using the same hook for all |
546
|
|
|
* skins, both the SkinTemplate based ones and the older ones, which directly |
547
|
|
|
* use this class to get their data. |
548
|
|
|
* |
549
|
|
|
* The output of this function gets processed in SkinTemplate::outputPage() for |
550
|
|
|
* the SkinTemplate based skins, all other skins should directly echo it. |
551
|
|
|
* |
552
|
|
|
* @return string Empty by default, if not changed by any hook function. |
553
|
|
|
*/ |
554
|
|
|
protected function afterContentHook() { |
555
|
|
|
$data = ''; |
556
|
|
|
|
557
|
|
|
if ( Hooks::run( 'SkinAfterContent', [ &$data, $this ] ) ) { |
558
|
|
|
// adding just some spaces shouldn't toggle the output |
559
|
|
|
// of the whole <div/>, so we use trim() here |
560
|
|
|
if ( trim( $data ) != '' ) { |
561
|
|
|
// Doing this here instead of in the skins to |
562
|
|
|
// ensure that the div has the same ID in all |
563
|
|
|
// skins |
564
|
|
|
$data = "<div id='mw-data-after-content'>\n" . |
565
|
|
|
"\t$data\n" . |
566
|
|
|
"</div>\n"; |
567
|
|
|
} |
568
|
|
|
} else { |
569
|
|
|
wfDebug( "Hook SkinAfterContent changed output processing.\n" ); |
570
|
|
|
} |
571
|
|
|
|
572
|
|
|
return $data; |
573
|
|
|
} |
574
|
|
|
|
575
|
|
|
/** |
576
|
|
|
* Generate debug data HTML for displaying at the bottom of the main content |
577
|
|
|
* area. |
578
|
|
|
* @return string HTML containing debug data, if enabled (otherwise empty). |
579
|
|
|
*/ |
580
|
|
|
protected function generateDebugHTML() { |
581
|
|
|
return MWDebug::getHTMLDebugLog(); |
582
|
|
|
} |
583
|
|
|
|
584
|
|
|
/** |
585
|
|
|
* This gets called shortly before the "</body>" tag. |
586
|
|
|
* |
587
|
|
|
* @return string HTML-wrapped JS code to be put before "</body>" |
588
|
|
|
*/ |
589
|
|
|
function bottomScripts() { |
590
|
|
|
// TODO and the suckage continues. This function is really just a wrapper around |
591
|
|
|
// OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned |
592
|
|
|
// up at some point |
593
|
|
|
$bottomScriptText = $this->getOutput()->getBottomScripts(); |
594
|
|
|
Hooks::run( 'SkinAfterBottomScripts', [ $this, &$bottomScriptText ] ); |
595
|
|
|
|
596
|
|
|
return $bottomScriptText; |
597
|
|
|
} |
598
|
|
|
|
599
|
|
|
/** |
600
|
|
|
* Text with the permalink to the source page, |
601
|
|
|
* usually shown on the footer of a printed page |
602
|
|
|
* |
603
|
|
|
* @return string HTML text with an URL |
604
|
|
|
*/ |
605
|
|
|
function printSource() { |
606
|
|
|
$oldid = $this->getRevisionId(); |
607
|
|
|
if ( $oldid ) { |
608
|
|
|
$canonicalUrl = $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid ); |
609
|
|
|
$url = htmlspecialchars( wfExpandIRI( $canonicalUrl ) ); |
610
|
|
|
} else { |
611
|
|
|
// oldid not available for non existing pages |
612
|
|
|
$url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) ); |
613
|
|
|
} |
614
|
|
|
|
615
|
|
|
return $this->msg( 'retrievedfrom' ) |
616
|
|
|
->rawParams( '<a dir="ltr" href="' . $url . '">' . $url . '</a>' ) |
617
|
|
|
->parse(); |
618
|
|
|
} |
619
|
|
|
|
620
|
|
|
/** |
621
|
|
|
* @return string HTML |
622
|
|
|
*/ |
623
|
|
|
function getUndeleteLink() { |
624
|
|
|
$action = $this->getRequest()->getVal( 'action', 'view' ); |
625
|
|
|
|
626
|
|
|
if ( $this->getTitle()->userCan( 'deletedhistory', $this->getUser() ) && |
627
|
|
|
( !$this->getTitle()->exists() || $action == 'history' ) ) { |
628
|
|
|
$n = $this->getTitle()->isDeleted(); |
629
|
|
|
|
630
|
|
|
if ( $n ) { |
631
|
|
|
if ( $this->getTitle()->quickUserCan( 'undelete', $this->getUser() ) ) { |
632
|
|
|
$msg = 'thisisdeleted'; |
633
|
|
|
} else { |
634
|
|
|
$msg = 'viewdeleted'; |
635
|
|
|
} |
636
|
|
|
|
637
|
|
|
return $this->msg( $msg )->rawParams( |
638
|
|
|
Linker::linkKnown( |
639
|
|
|
SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ), |
640
|
|
|
$this->msg( 'restorelink' )->numParams( $n )->escaped() ) |
641
|
|
|
)->escaped(); |
642
|
|
|
} |
643
|
|
|
} |
644
|
|
|
|
645
|
|
|
return ''; |
646
|
|
|
} |
647
|
|
|
|
648
|
|
|
/** |
649
|
|
|
* @param OutputPage $out Defaults to $this->getOutput() if left as null |
650
|
|
|
* @return string |
651
|
|
|
*/ |
652
|
|
|
function subPageSubtitle( $out = null ) { |
653
|
|
|
if ( $out === null ) { |
654
|
|
|
$out = $this->getOutput(); |
655
|
|
|
} |
656
|
|
|
$title = $out->getTitle(); |
657
|
|
|
$subpages = ''; |
658
|
|
|
|
659
|
|
|
if ( !Hooks::run( 'SkinSubPageSubtitle', [ &$subpages, $this, $out ] ) ) { |
660
|
|
|
return $subpages; |
661
|
|
|
} |
662
|
|
|
|
663
|
|
|
if ( $out->isArticle() && MWNamespace::hasSubpages( $title->getNamespace() ) ) { |
664
|
|
|
$ptext = $title->getPrefixedText(); |
665
|
|
|
if ( strpos( $ptext, '/' ) !== false ) { |
666
|
|
|
$links = explode( '/', $ptext ); |
667
|
|
|
array_pop( $links ); |
668
|
|
|
$c = 0; |
669
|
|
|
$growinglink = ''; |
670
|
|
|
$display = ''; |
671
|
|
|
$lang = $this->getLanguage(); |
672
|
|
|
|
673
|
|
|
foreach ( $links as $link ) { |
674
|
|
|
$growinglink .= $link; |
675
|
|
|
$display .= $link; |
676
|
|
|
$linkObj = Title::newFromText( $growinglink ); |
677
|
|
|
|
678
|
|
|
if ( is_object( $linkObj ) && $linkObj->isKnown() ) { |
679
|
|
|
$getlink = Linker::linkKnown( |
680
|
|
|
$linkObj, |
681
|
|
|
htmlspecialchars( $display ) |
682
|
|
|
); |
683
|
|
|
|
684
|
|
|
$c++; |
685
|
|
|
|
686
|
|
|
if ( $c > 1 ) { |
687
|
|
|
$subpages .= $lang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped(); |
688
|
|
|
} else { |
689
|
|
|
$subpages .= '< '; |
690
|
|
|
} |
691
|
|
|
|
692
|
|
|
$subpages .= $getlink; |
693
|
|
|
$display = ''; |
694
|
|
|
} else { |
695
|
|
|
$display .= '/'; |
696
|
|
|
} |
697
|
|
|
$growinglink .= '/'; |
698
|
|
|
} |
699
|
|
|
} |
700
|
|
|
} |
701
|
|
|
|
702
|
|
|
return $subpages; |
703
|
|
|
} |
704
|
|
|
|
705
|
|
|
/** |
706
|
|
|
* @deprecated since 1.27, feature removed |
707
|
|
|
* @return bool Always false |
708
|
|
|
*/ |
709
|
|
|
function showIPinHeader() { |
710
|
|
|
wfDeprecated( __METHOD__, '1.27' ); |
711
|
|
|
return false; |
712
|
|
|
} |
713
|
|
|
|
714
|
|
|
/** |
715
|
|
|
* @return string |
716
|
|
|
*/ |
717
|
|
|
function getSearchLink() { |
718
|
|
|
$searchPage = SpecialPage::getTitleFor( 'Search' ); |
719
|
|
|
return $searchPage->getLocalURL(); |
720
|
|
|
} |
721
|
|
|
|
722
|
|
|
/** |
723
|
|
|
* @return string |
724
|
|
|
*/ |
725
|
|
|
function escapeSearchLink() { |
726
|
|
|
return htmlspecialchars( $this->getSearchLink() ); |
727
|
|
|
} |
728
|
|
|
|
729
|
|
|
/** |
730
|
|
|
* @param string $type |
731
|
|
|
* @return string |
732
|
|
|
*/ |
733
|
|
|
function getCopyright( $type = 'detect' ) { |
734
|
|
|
global $wgRightsPage, $wgRightsUrl, $wgRightsText; |
735
|
|
|
|
736
|
|
|
if ( $type == 'detect' ) { |
737
|
|
|
if ( !$this->isRevisionCurrent() |
738
|
|
|
&& !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled() |
739
|
|
|
) { |
740
|
|
|
$type = 'history'; |
741
|
|
|
} else { |
742
|
|
|
$type = 'normal'; |
743
|
|
|
} |
744
|
|
|
} |
745
|
|
|
|
746
|
|
|
if ( $type == 'history' ) { |
747
|
|
|
$msg = 'history_copyright'; |
748
|
|
|
} else { |
749
|
|
|
$msg = 'copyright'; |
750
|
|
|
} |
751
|
|
|
|
752
|
|
|
if ( $wgRightsPage ) { |
753
|
|
|
$title = Title::newFromText( $wgRightsPage ); |
754
|
|
|
$link = Linker::linkKnown( $title, $wgRightsText ); |
755
|
|
|
} elseif ( $wgRightsUrl ) { |
756
|
|
|
$link = Linker::makeExternalLink( $wgRightsUrl, $wgRightsText ); |
757
|
|
|
} elseif ( $wgRightsText ) { |
758
|
|
|
$link = $wgRightsText; |
759
|
|
|
} else { |
760
|
|
|
# Give up now |
761
|
|
|
return ''; |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
// Allow for site and per-namespace customization of copyright notice. |
765
|
|
|
// @todo Remove deprecated $forContent param from hook handlers and then remove here. |
766
|
|
|
$forContent = true; |
767
|
|
|
|
768
|
|
|
Hooks::run( |
769
|
|
|
'SkinCopyrightFooter', |
770
|
|
|
[ $this->getTitle(), $type, &$msg, &$link, &$forContent ] |
771
|
|
|
); |
772
|
|
|
|
773
|
|
|
return $this->msg( $msg )->rawParams( $link )->text(); |
774
|
|
|
} |
775
|
|
|
|
776
|
|
|
/** |
777
|
|
|
* @return null|string |
778
|
|
|
*/ |
779
|
|
|
function getCopyrightIcon() { |
780
|
|
|
global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgFooterIcons; |
781
|
|
|
|
782
|
|
|
$out = ''; |
783
|
|
|
|
784
|
|
|
if ( $wgFooterIcons['copyright']['copyright'] ) { |
785
|
|
|
$out = $wgFooterIcons['copyright']['copyright']; |
786
|
|
|
} elseif ( $wgRightsIcon ) { |
787
|
|
|
$icon = htmlspecialchars( $wgRightsIcon ); |
788
|
|
|
|
789
|
|
|
if ( $wgRightsUrl ) { |
790
|
|
|
$url = htmlspecialchars( $wgRightsUrl ); |
791
|
|
|
$out .= '<a href="' . $url . '">'; |
792
|
|
|
} |
793
|
|
|
|
794
|
|
|
$text = htmlspecialchars( $wgRightsText ); |
795
|
|
|
$out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />"; |
796
|
|
|
|
797
|
|
|
if ( $wgRightsUrl ) { |
798
|
|
|
$out .= '</a>'; |
799
|
|
|
} |
800
|
|
|
} |
801
|
|
|
|
802
|
|
|
return $out; |
803
|
|
|
} |
804
|
|
|
|
805
|
|
|
/** |
806
|
|
|
* Gets the powered by MediaWiki icon. |
807
|
|
|
* @return string |
808
|
|
|
*/ |
809
|
|
|
function getPoweredBy() { |
810
|
|
|
global $wgResourceBasePath; |
811
|
|
|
|
812
|
|
|
$url1 = htmlspecialchars( |
813
|
|
|
"$wgResourceBasePath/resources/assets/poweredby_mediawiki_88x31.png" |
814
|
|
|
); |
815
|
|
|
$url1_5 = htmlspecialchars( |
816
|
|
|
"$wgResourceBasePath/resources/assets/poweredby_mediawiki_132x47.png" |
817
|
|
|
); |
818
|
|
|
$url2 = htmlspecialchars( |
819
|
|
|
"$wgResourceBasePath/resources/assets/poweredby_mediawiki_176x62.png" |
820
|
|
|
); |
821
|
|
|
$text = '<a href="//www.mediawiki.org/"><img src="' . $url1 |
822
|
|
|
. '" srcset="' . $url1_5 . ' 1.5x, ' . $url2 . ' 2x" ' |
823
|
|
|
. 'height="31" width="88" alt="Powered by MediaWiki" /></a>'; |
824
|
|
|
Hooks::run( 'SkinGetPoweredBy', [ &$text, $this ] ); |
825
|
|
|
return $text; |
826
|
|
|
} |
827
|
|
|
|
828
|
|
|
/** |
829
|
|
|
* Get the timestamp of the latest revision, formatted in user language |
830
|
|
|
* |
831
|
|
|
* @return string |
832
|
|
|
*/ |
833
|
|
|
protected function lastModified() { |
834
|
|
|
$timestamp = $this->getOutput()->getRevisionTimestamp(); |
835
|
|
|
|
836
|
|
|
# No cached timestamp, load it from the database |
837
|
|
|
if ( $timestamp === null ) { |
838
|
|
|
$timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() ); |
|
|
|
|
839
|
|
|
} |
840
|
|
|
|
841
|
|
|
if ( $timestamp ) { |
|
|
|
|
842
|
|
|
$d = $this->getLanguage()->userDate( $timestamp, $this->getUser() ); |
843
|
|
|
$t = $this->getLanguage()->userTime( $timestamp, $this->getUser() ); |
844
|
|
|
$s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->parse(); |
845
|
|
|
} else { |
846
|
|
|
$s = ''; |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
if ( wfGetLB()->getLaggedReplicaMode() ) { |
|
|
|
|
850
|
|
|
$s .= ' <strong>' . $this->msg( 'laggedslavemode' )->parse() . '</strong>'; |
851
|
|
|
} |
852
|
|
|
|
853
|
|
|
return $s; |
854
|
|
|
} |
855
|
|
|
|
856
|
|
|
/** |
857
|
|
|
* @param string $align |
858
|
|
|
* @return string |
859
|
|
|
*/ |
860
|
|
|
function logoText( $align = '' ) { |
861
|
|
|
if ( $align != '' ) { |
862
|
|
|
$a = " style='float: {$align};'"; |
863
|
|
|
} else { |
864
|
|
|
$a = ''; |
865
|
|
|
} |
866
|
|
|
|
867
|
|
|
$mp = $this->msg( 'mainpage' )->escaped(); |
868
|
|
|
$mptitle = Title::newMainPage(); |
869
|
|
|
$url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' ); |
870
|
|
|
|
871
|
|
|
$logourl = $this->getLogo(); |
872
|
|
|
$s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>"; |
873
|
|
|
|
874
|
|
|
return $s; |
875
|
|
|
} |
876
|
|
|
|
877
|
|
|
/** |
878
|
|
|
* Renders a $wgFooterIcons icon according to the method's arguments |
879
|
|
|
* @param array $icon The icon to build the html for, see $wgFooterIcons |
880
|
|
|
* for the format of this array. |
881
|
|
|
* @param bool|string $withImage Whether to use the icon's image or output |
882
|
|
|
* a text-only footericon. |
883
|
|
|
* @return string HTML |
884
|
|
|
*/ |
885
|
|
|
function makeFooterIcon( $icon, $withImage = 'withImage' ) { |
886
|
|
|
if ( is_string( $icon ) ) { |
887
|
|
|
$html = $icon; |
888
|
|
|
} else { // Assuming array |
889
|
|
|
$url = isset( $icon["url"] ) ? $icon["url"] : null; |
890
|
|
|
unset( $icon["url"] ); |
891
|
|
|
if ( isset( $icon["src"] ) && $withImage === 'withImage' ) { |
892
|
|
|
// do this the lazy way, just pass icon data as an attribute array |
893
|
|
|
$html = Html::element( 'img', $icon ); |
894
|
|
|
} else { |
895
|
|
|
$html = htmlspecialchars( $icon["alt"] ); |
896
|
|
|
} |
897
|
|
|
if ( $url ) { |
898
|
|
|
$html = Html::rawElement( 'a', [ "href" => $url ], $html ); |
899
|
|
|
} |
900
|
|
|
} |
901
|
|
|
return $html; |
902
|
|
|
} |
903
|
|
|
|
904
|
|
|
/** |
905
|
|
|
* Gets the link to the wiki's main page. |
906
|
|
|
* @return string |
907
|
|
|
*/ |
908
|
|
|
function mainPageLink() { |
909
|
|
|
$s = Linker::linkKnown( |
910
|
|
|
Title::newMainPage(), |
911
|
|
|
$this->msg( 'mainpage' )->escaped() |
912
|
|
|
); |
913
|
|
|
|
914
|
|
|
return $s; |
915
|
|
|
} |
916
|
|
|
|
917
|
|
|
/** |
918
|
|
|
* Returns an HTML link for use in the footer |
919
|
|
|
* @param string $desc The i18n message key for the link text |
920
|
|
|
* @param string $page The i18n message key for the page to link to |
921
|
|
|
* @return string HTML anchor |
922
|
|
|
*/ |
923
|
|
|
public function footerLink( $desc, $page ) { |
924
|
|
|
// if the link description has been set to "-" in the default language, |
925
|
|
|
if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) { |
926
|
|
|
// then it is disabled, for all languages. |
927
|
|
|
return ''; |
928
|
|
|
} else { |
929
|
|
|
// Otherwise, we display the link for the user, described in their |
930
|
|
|
// language (which may or may not be the same as the default language), |
931
|
|
|
// but we make the link target be the one site-wide page. |
932
|
|
|
$title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() ); |
933
|
|
|
|
934
|
|
|
if ( !$title ) { |
935
|
|
|
return ''; |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
return Linker::linkKnown( |
939
|
|
|
$title, |
940
|
|
|
$this->msg( $desc )->escaped() |
941
|
|
|
); |
942
|
|
|
} |
943
|
|
|
} |
944
|
|
|
|
945
|
|
|
/** |
946
|
|
|
* Gets the link to the wiki's privacy policy page. |
947
|
|
|
* @return string HTML |
948
|
|
|
*/ |
949
|
|
|
function privacyLink() { |
950
|
|
|
return $this->footerLink( 'privacy', 'privacypage' ); |
951
|
|
|
} |
952
|
|
|
|
953
|
|
|
/** |
954
|
|
|
* Gets the link to the wiki's about page. |
955
|
|
|
* @return string HTML |
956
|
|
|
*/ |
957
|
|
|
function aboutLink() { |
958
|
|
|
return $this->footerLink( 'aboutsite', 'aboutpage' ); |
959
|
|
|
} |
960
|
|
|
|
961
|
|
|
/** |
962
|
|
|
* Gets the link to the wiki's general disclaimers page. |
963
|
|
|
* @return string HTML |
964
|
|
|
*/ |
965
|
|
|
function disclaimerLink() { |
966
|
|
|
return $this->footerLink( 'disclaimers', 'disclaimerpage' ); |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
/** |
970
|
|
|
* Return URL options for the 'edit page' link. |
971
|
|
|
* This may include an 'oldid' specifier, if the current page view is such. |
972
|
|
|
* |
973
|
|
|
* @return array |
974
|
|
|
* @private |
975
|
|
|
*/ |
976
|
|
|
function editUrlOptions() { |
977
|
|
|
$options = [ 'action' => 'edit' ]; |
978
|
|
|
|
979
|
|
|
if ( !$this->isRevisionCurrent() ) { |
980
|
|
|
$options['oldid'] = intval( $this->getRevisionId() ); |
981
|
|
|
} |
982
|
|
|
|
983
|
|
|
return $options; |
984
|
|
|
} |
985
|
|
|
|
986
|
|
|
/** |
987
|
|
|
* @param User|int $id |
988
|
|
|
* @return bool |
989
|
|
|
*/ |
990
|
|
|
function showEmailUser( $id ) { |
991
|
|
|
if ( $id instanceof User ) { |
992
|
|
|
$targetUser = $id; |
993
|
|
|
} else { |
994
|
|
|
$targetUser = User::newFromId( $id ); |
995
|
|
|
} |
996
|
|
|
|
997
|
|
|
# The sending user must have a confirmed email address and the target |
998
|
|
|
# user must have a confirmed email address and allow emails from users. |
999
|
|
|
return $this->getUser()->canSendEmail() && |
1000
|
|
|
$targetUser->canReceiveEmail(); |
1001
|
|
|
} |
1002
|
|
|
|
1003
|
|
|
/** |
1004
|
|
|
* Return a fully resolved style path url to images or styles stored in the current skins's folder. |
1005
|
|
|
* This method returns a url resolved using the configured skin style path |
1006
|
|
|
* and includes the style version inside of the url. |
1007
|
|
|
* |
1008
|
|
|
* Requires $stylename to be set, otherwise throws MWException. |
1009
|
|
|
* |
1010
|
|
|
* @param string $name The name or path of a skin resource file |
1011
|
|
|
* @return string The fully resolved style path url including styleversion |
1012
|
|
|
* @throws MWException |
1013
|
|
|
*/ |
1014
|
|
|
function getSkinStylePath( $name ) { |
1015
|
|
|
global $wgStylePath, $wgStyleVersion; |
1016
|
|
|
|
1017
|
|
|
if ( $this->stylename === null ) { |
1018
|
|
|
$class = get_class( $this ); |
1019
|
|
|
throw new MWException( "$class::\$stylename must be set to use getSkinStylePath()" ); |
1020
|
|
|
} |
1021
|
|
|
|
1022
|
|
|
return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion"; |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
|
|
/* these are used extensively in SkinTemplate, but also some other places */ |
1026
|
|
|
|
1027
|
|
|
/** |
1028
|
|
|
* @param string $urlaction |
1029
|
|
|
* @return string |
1030
|
|
|
*/ |
1031
|
|
|
static function makeMainPageUrl( $urlaction = '' ) { |
1032
|
|
|
$title = Title::newMainPage(); |
1033
|
|
|
self::checkTitle( $title, '' ); |
|
|
|
|
1034
|
|
|
|
1035
|
|
|
return $title->getLocalURL( $urlaction ); |
1036
|
|
|
} |
1037
|
|
|
|
1038
|
|
|
/** |
1039
|
|
|
* Make a URL for a Special Page using the given query and protocol. |
1040
|
|
|
* |
1041
|
|
|
* If $proto is set to null, make a local URL. Otherwise, make a full |
1042
|
|
|
* URL with the protocol specified. |
1043
|
|
|
* |
1044
|
|
|
* @param string $name Name of the Special page |
1045
|
|
|
* @param string $urlaction Query to append |
1046
|
|
|
* @param string|null $proto Protocol to use or null for a local URL |
1047
|
|
|
* @return string |
1048
|
|
|
*/ |
1049
|
|
|
static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) { |
1050
|
|
|
$title = SpecialPage::getSafeTitleFor( $name ); |
1051
|
|
|
if ( is_null( $proto ) ) { |
1052
|
|
|
return $title->getLocalURL( $urlaction ); |
1053
|
|
|
} else { |
1054
|
|
|
return $title->getFullURL( $urlaction, false, $proto ); |
1055
|
|
|
} |
1056
|
|
|
} |
1057
|
|
|
|
1058
|
|
|
/** |
1059
|
|
|
* @param string $name |
1060
|
|
|
* @param string $subpage |
1061
|
|
|
* @param string $urlaction |
1062
|
|
|
* @return string |
1063
|
|
|
*/ |
1064
|
|
|
static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) { |
1065
|
|
|
$title = SpecialPage::getSafeTitleFor( $name, $subpage ); |
1066
|
|
|
return $title->getLocalURL( $urlaction ); |
1067
|
|
|
} |
1068
|
|
|
|
1069
|
|
|
/** |
1070
|
|
|
* @param string $name |
1071
|
|
|
* @param string $urlaction |
1072
|
|
|
* @return string |
1073
|
|
|
*/ |
1074
|
|
|
static function makeI18nUrl( $name, $urlaction = '' ) { |
1075
|
|
|
$title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() ); |
1076
|
|
|
self::checkTitle( $title, $name ); |
|
|
|
|
1077
|
|
|
return $title->getLocalURL( $urlaction ); |
1078
|
|
|
} |
1079
|
|
|
|
1080
|
|
|
/** |
1081
|
|
|
* @param string $name |
1082
|
|
|
* @param string $urlaction |
1083
|
|
|
* @return string |
1084
|
|
|
*/ |
1085
|
|
|
static function makeUrl( $name, $urlaction = '' ) { |
1086
|
|
|
$title = Title::newFromText( $name ); |
1087
|
|
|
self::checkTitle( $title, $name ); |
|
|
|
|
1088
|
|
|
|
1089
|
|
|
return $title->getLocalURL( $urlaction ); |
1090
|
|
|
} |
1091
|
|
|
|
1092
|
|
|
/** |
1093
|
|
|
* If url string starts with http, consider as external URL, else |
1094
|
|
|
* internal |
1095
|
|
|
* @param string $name |
1096
|
|
|
* @return string URL |
1097
|
|
|
*/ |
1098
|
|
|
static function makeInternalOrExternalUrl( $name ) { |
1099
|
|
|
if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) { |
1100
|
|
|
return $name; |
1101
|
|
|
} else { |
1102
|
|
|
return self::makeUrl( $name ); |
1103
|
|
|
} |
1104
|
|
|
} |
1105
|
|
|
|
1106
|
|
|
/** |
1107
|
|
|
* this can be passed the NS number as defined in Language.php |
1108
|
|
|
* @param string $name |
1109
|
|
|
* @param string $urlaction |
1110
|
|
|
* @param int $namespace |
1111
|
|
|
* @return string |
1112
|
|
|
*/ |
1113
|
|
|
static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) { |
1114
|
|
|
$title = Title::makeTitleSafe( $namespace, $name ); |
1115
|
|
|
self::checkTitle( $title, $name ); |
|
|
|
|
1116
|
|
|
|
1117
|
|
|
return $title->getLocalURL( $urlaction ); |
1118
|
|
|
} |
1119
|
|
|
|
1120
|
|
|
/** |
1121
|
|
|
* these return an array with the 'href' and boolean 'exists' |
1122
|
|
|
* @param string $name |
1123
|
|
|
* @param string $urlaction |
1124
|
|
|
* @return array |
1125
|
|
|
*/ |
1126
|
|
View Code Duplication |
static function makeUrlDetails( $name, $urlaction = '' ) { |
1127
|
|
|
$title = Title::newFromText( $name ); |
1128
|
|
|
self::checkTitle( $title, $name ); |
|
|
|
|
1129
|
|
|
|
1130
|
|
|
return [ |
1131
|
|
|
'href' => $title->getLocalURL( $urlaction ), |
1132
|
|
|
'exists' => $title->isKnown(), |
1133
|
|
|
]; |
1134
|
|
|
} |
1135
|
|
|
|
1136
|
|
|
/** |
1137
|
|
|
* Make URL details where the article exists (or at least it's convenient to think so) |
1138
|
|
|
* @param string $name Article name |
1139
|
|
|
* @param string $urlaction |
1140
|
|
|
* @return array |
1141
|
|
|
*/ |
1142
|
|
View Code Duplication |
static function makeKnownUrlDetails( $name, $urlaction = '' ) { |
1143
|
|
|
$title = Title::newFromText( $name ); |
1144
|
|
|
self::checkTitle( $title, $name ); |
|
|
|
|
1145
|
|
|
|
1146
|
|
|
return [ |
1147
|
|
|
'href' => $title->getLocalURL( $urlaction ), |
1148
|
|
|
'exists' => true |
1149
|
|
|
]; |
1150
|
|
|
} |
1151
|
|
|
|
1152
|
|
|
/** |
1153
|
|
|
* make sure we have some title to operate on |
1154
|
|
|
* |
1155
|
|
|
* @param Title $title |
1156
|
|
|
* @param string $name |
1157
|
|
|
*/ |
1158
|
|
|
static function checkTitle( &$title, $name ) { |
1159
|
|
|
if ( !is_object( $title ) ) { |
1160
|
|
|
$title = Title::newFromText( $name ); |
1161
|
|
|
if ( !is_object( $title ) ) { |
1162
|
|
|
$title = Title::newFromText( '--error: link target missing--' ); |
1163
|
|
|
} |
1164
|
|
|
} |
1165
|
|
|
} |
1166
|
|
|
|
1167
|
|
|
/** |
1168
|
|
|
* Build an array that represents the sidebar(s), the navigation bar among them. |
1169
|
|
|
* |
1170
|
|
|
* BaseTemplate::getSidebar can be used to simplify the format and id generation in new skins. |
1171
|
|
|
* |
1172
|
|
|
* The format of the returned array is [ heading => content, ... ], where: |
1173
|
|
|
* - heading is the heading of a navigation portlet. It is either: |
1174
|
|
|
* - magic string to be handled by the skins ('SEARCH' / 'LANGUAGES' / 'TOOLBOX' / ...) |
1175
|
|
|
* - a message name (e.g. 'navigation'), the message should be HTML-escaped by the skin |
1176
|
|
|
* - plain text, which should be HTML-escaped by the skin |
1177
|
|
|
* - content is the contents of the portlet. It is either: |
1178
|
|
|
* - HTML text (<ul><li>...</li>...</ul>) |
1179
|
|
|
* - array of link data in a format accepted by BaseTemplate::makeListItem() |
1180
|
|
|
* - (for a magic string as a key, any value) |
1181
|
|
|
* |
1182
|
|
|
* Note that extensions can control the sidebar contents using the SkinBuildSidebar hook |
1183
|
|
|
* and can technically insert anything in here; skin creators are expected to handle |
1184
|
|
|
* values described above. |
1185
|
|
|
* |
1186
|
|
|
* @return array |
1187
|
|
|
*/ |
1188
|
|
|
function buildSidebar() { |
1189
|
|
|
global $wgEnableSidebarCache, $wgSidebarCacheExpiry; |
1190
|
|
|
|
1191
|
|
|
$that = $this; |
1192
|
|
|
$callback = function () use ( $that ) { |
1193
|
|
|
$bar = []; |
1194
|
|
|
$that->addToSidebar( $bar, 'sidebar' ); |
1195
|
|
|
Hooks::run( 'SkinBuildSidebar', [ $that, &$bar ] ); |
1196
|
|
|
|
1197
|
|
|
return $bar; |
1198
|
|
|
}; |
1199
|
|
|
|
1200
|
|
|
if ( $wgEnableSidebarCache ) { |
1201
|
|
|
$cache = ObjectCache::getMainWANInstance(); |
1202
|
|
|
$sidebar = $cache->getWithSetCallback( |
1203
|
|
|
$cache->makeKey( 'sidebar', $this->getLanguage()->getCode() ), |
1204
|
|
|
MessageCache::singleton()->isDisabled() |
1205
|
|
|
? $cache::TTL_UNCACHEABLE // bug T133069 |
1206
|
|
|
: $wgSidebarCacheExpiry, |
1207
|
|
|
$callback, |
1208
|
|
|
[ 'lockTSE' => 30 ] |
1209
|
|
|
); |
1210
|
|
|
} else { |
1211
|
|
|
$sidebar = $callback(); |
1212
|
|
|
} |
1213
|
|
|
|
1214
|
|
|
// Apply post-processing to the cached value |
1215
|
|
|
Hooks::run( 'SidebarBeforeOutput', [ $this, &$sidebar ] ); |
1216
|
|
|
|
1217
|
|
|
return $sidebar; |
1218
|
|
|
} |
1219
|
|
|
|
1220
|
|
|
/** |
1221
|
|
|
* Add content from a sidebar system message |
1222
|
|
|
* Currently only used for MediaWiki:Sidebar (but may be used by Extensions) |
1223
|
|
|
* |
1224
|
|
|
* This is just a wrapper around addToSidebarPlain() for backwards compatibility |
1225
|
|
|
* |
1226
|
|
|
* @param array $bar |
1227
|
|
|
* @param string $message |
1228
|
|
|
*/ |
1229
|
|
|
public function addToSidebar( &$bar, $message ) { |
1230
|
|
|
$this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() ); |
1231
|
|
|
} |
1232
|
|
|
|
1233
|
|
|
/** |
1234
|
|
|
* Add content from plain text |
1235
|
|
|
* @since 1.17 |
1236
|
|
|
* @param array $bar |
1237
|
|
|
* @param string $text |
1238
|
|
|
* @return array |
1239
|
|
|
*/ |
1240
|
|
|
function addToSidebarPlain( &$bar, $text ) { |
1241
|
|
|
$lines = explode( "\n", $text ); |
1242
|
|
|
|
1243
|
|
|
$heading = ''; |
1244
|
|
|
$messageTitle = $this->getConfig()->get( 'EnableSidebarCache' ) |
1245
|
|
|
? Title::newMainPage() : $this->getTitle(); |
1246
|
|
|
|
1247
|
|
|
foreach ( $lines as $line ) { |
1248
|
|
|
if ( strpos( $line, '*' ) !== 0 ) { |
1249
|
|
|
continue; |
1250
|
|
|
} |
1251
|
|
|
$line = rtrim( $line, "\r" ); // for Windows compat |
1252
|
|
|
|
1253
|
|
|
if ( strpos( $line, '**' ) !== 0 ) { |
1254
|
|
|
$heading = trim( $line, '* ' ); |
1255
|
|
|
if ( !array_key_exists( $heading, $bar ) ) { |
1256
|
|
|
$bar[$heading] = []; |
1257
|
|
|
} |
1258
|
|
|
} else { |
1259
|
|
|
$line = trim( $line, '* ' ); |
1260
|
|
|
|
1261
|
|
|
if ( strpos( $line, '|' ) !== false ) { // sanity check |
1262
|
|
|
$line = MessageCache::singleton()->transform( $line, false, null, $messageTitle ); |
1263
|
|
|
$line = array_map( 'trim', explode( '|', $line, 2 ) ); |
1264
|
|
|
if ( count( $line ) !== 2 ) { |
1265
|
|
|
// Second sanity check, could be hit by people doing |
1266
|
|
|
// funky stuff with parserfuncs... (bug 33321) |
1267
|
|
|
continue; |
1268
|
|
|
} |
1269
|
|
|
|
1270
|
|
|
$extraAttribs = []; |
1271
|
|
|
|
1272
|
|
|
$msgLink = $this->msg( $line[0] )->title( $messageTitle )->inContentLanguage(); |
|
|
|
|
1273
|
|
|
if ( $msgLink->exists() ) { |
1274
|
|
|
$link = $msgLink->text(); |
1275
|
|
|
if ( $link == '-' ) { |
1276
|
|
|
continue; |
1277
|
|
|
} |
1278
|
|
|
} else { |
1279
|
|
|
$link = $line[0]; |
1280
|
|
|
} |
1281
|
|
|
$msgText = $this->msg( $line[1] )->title( $messageTitle ); |
|
|
|
|
1282
|
|
|
if ( $msgText->exists() ) { |
1283
|
|
|
$text = $msgText->text(); |
1284
|
|
|
} else { |
1285
|
|
|
$text = $line[1]; |
1286
|
|
|
} |
1287
|
|
|
|
1288
|
|
|
if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) { |
1289
|
|
|
$href = $link; |
1290
|
|
|
|
1291
|
|
|
// Parser::getExternalLinkAttribs won't work here because of the Namespace things |
1292
|
|
|
global $wgNoFollowLinks, $wgNoFollowDomainExceptions; |
1293
|
|
|
if ( $wgNoFollowLinks && !wfMatchesDomainList( $href, $wgNoFollowDomainExceptions ) ) { |
1294
|
|
|
$extraAttribs['rel'] = 'nofollow'; |
1295
|
|
|
} |
1296
|
|
|
|
1297
|
|
|
global $wgExternalLinkTarget; |
1298
|
|
|
if ( $wgExternalLinkTarget ) { |
1299
|
|
|
$extraAttribs['target'] = $wgExternalLinkTarget; |
1300
|
|
|
} |
1301
|
|
|
} else { |
1302
|
|
|
$title = Title::newFromText( $link ); |
1303
|
|
|
|
1304
|
|
|
if ( $title ) { |
1305
|
|
|
$title = $title->fixSpecialName(); |
1306
|
|
|
$href = $title->getLinkURL(); |
1307
|
|
|
} else { |
1308
|
|
|
$href = 'INVALID-TITLE'; |
1309
|
|
|
} |
1310
|
|
|
} |
1311
|
|
|
|
1312
|
|
|
$bar[$heading][] = array_merge( [ |
1313
|
|
|
'text' => $text, |
1314
|
|
|
'href' => $href, |
1315
|
|
|
'id' => 'n-' . Sanitizer::escapeId( strtr( $line[1], ' ', '-' ), 'noninitial' ), |
1316
|
|
|
'active' => false |
1317
|
|
|
], $extraAttribs ); |
1318
|
|
|
} else { |
1319
|
|
|
continue; |
1320
|
|
|
} |
1321
|
|
|
} |
1322
|
|
|
} |
1323
|
|
|
|
1324
|
|
|
return $bar; |
1325
|
|
|
} |
1326
|
|
|
|
1327
|
|
|
/** |
1328
|
|
|
* Gets new talk page messages for the current user and returns an |
1329
|
|
|
* appropriate alert message (or an empty string if there are no messages) |
1330
|
|
|
* @return string |
1331
|
|
|
*/ |
1332
|
|
|
function getNewtalks() { |
1333
|
|
|
|
1334
|
|
|
$newMessagesAlert = ''; |
1335
|
|
|
$user = $this->getUser(); |
1336
|
|
|
$newtalks = $user->getNewMessageLinks(); |
1337
|
|
|
$out = $this->getOutput(); |
1338
|
|
|
|
1339
|
|
|
// Allow extensions to disable or modify the new messages alert |
1340
|
|
|
if ( !Hooks::run( 'GetNewMessagesAlert', [ &$newMessagesAlert, $newtalks, $user, $out ] ) ) { |
1341
|
|
|
return ''; |
1342
|
|
|
} |
1343
|
|
|
if ( $newMessagesAlert ) { |
1344
|
|
|
return $newMessagesAlert; |
1345
|
|
|
} |
1346
|
|
|
|
1347
|
|
|
if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) { |
1348
|
|
|
$uTalkTitle = $user->getTalkPage(); |
1349
|
|
|
$lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null; |
1350
|
|
|
$nofAuthors = 0; |
1351
|
|
|
if ( $lastSeenRev !== null ) { |
1352
|
|
|
$plural = true; // Default if we have a last seen revision: if unknown, use plural |
1353
|
|
|
$latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL ); |
1354
|
|
|
if ( $latestRev !== null ) { |
1355
|
|
|
// Singular if only 1 unseen revision, plural if several unseen revisions. |
1356
|
|
|
$plural = $latestRev->getParentId() !== $lastSeenRev->getId(); |
1357
|
|
|
$nofAuthors = $uTalkTitle->countAuthorsBetween( |
1358
|
|
|
$lastSeenRev, $latestRev, 10, 'include_new' ); |
1359
|
|
|
} |
1360
|
|
|
} else { |
1361
|
|
|
// Singular if no revision -> diff link will show latest change only in any case |
1362
|
|
|
$plural = false; |
1363
|
|
|
} |
1364
|
|
|
$plural = $plural ? 999 : 1; |
1365
|
|
|
// 999 signifies "more than one revision". We don't know how many, and even if we did, |
1366
|
|
|
// the number of revisions or authors is not necessarily the same as the number of |
1367
|
|
|
// "messages". |
1368
|
|
|
$newMessagesLink = Linker::linkKnown( |
1369
|
|
|
$uTalkTitle, |
1370
|
|
|
$this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(), |
1371
|
|
|
[], |
1372
|
|
|
[ 'redirect' => 'no' ] |
1373
|
|
|
); |
1374
|
|
|
|
1375
|
|
|
$newMessagesDiffLink = Linker::linkKnown( |
1376
|
|
|
$uTalkTitle, |
1377
|
|
|
$this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(), |
1378
|
|
|
[], |
1379
|
|
|
$lastSeenRev !== null |
1380
|
|
|
? [ 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' ] |
1381
|
|
|
: [ 'diff' => 'cur' ] |
1382
|
|
|
); |
1383
|
|
|
|
1384
|
|
|
if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) { |
1385
|
|
|
$newMessagesAlert = $this->msg( |
1386
|
|
|
'youhavenewmessagesfromusers', |
1387
|
|
|
$newMessagesLink, |
1388
|
|
|
$newMessagesDiffLink |
1389
|
|
|
)->numParams( $nofAuthors, $plural ); |
1390
|
|
|
} else { |
1391
|
|
|
// $nofAuthors === 11 signifies "11 or more" ("more than 10") |
1392
|
|
|
$newMessagesAlert = $this->msg( |
1393
|
|
|
$nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages', |
1394
|
|
|
$newMessagesLink, |
1395
|
|
|
$newMessagesDiffLink |
1396
|
|
|
)->numParams( $plural ); |
1397
|
|
|
} |
1398
|
|
|
$newMessagesAlert = $newMessagesAlert->text(); |
1399
|
|
|
# Disable CDN cache |
1400
|
|
|
$out->setCdnMaxage( 0 ); |
1401
|
|
|
} elseif ( count( $newtalks ) ) { |
1402
|
|
|
$sep = $this->msg( 'newtalkseparator' )->escaped(); |
1403
|
|
|
$msgs = []; |
1404
|
|
|
|
1405
|
|
|
foreach ( $newtalks as $newtalk ) { |
1406
|
|
|
$msgs[] = Xml::element( |
1407
|
|
|
'a', |
1408
|
|
|
[ 'href' => $newtalk['link'] ], $newtalk['wiki'] |
1409
|
|
|
); |
1410
|
|
|
} |
1411
|
|
|
$parts = implode( $sep, $msgs ); |
1412
|
|
|
$newMessagesAlert = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped(); |
1413
|
|
|
$out->setCdnMaxage( 0 ); |
1414
|
|
|
} |
1415
|
|
|
|
1416
|
|
|
return $newMessagesAlert; |
1417
|
|
|
} |
1418
|
|
|
|
1419
|
|
|
/** |
1420
|
|
|
* Get a cached notice |
1421
|
|
|
* |
1422
|
|
|
* @param string $name Message name, or 'default' for $wgSiteNotice |
1423
|
|
|
* @return string|bool HTML fragment, or false to indicate that the caller |
1424
|
|
|
* should fall back to the next notice in its sequence |
1425
|
|
|
*/ |
1426
|
|
|
private function getCachedNotice( $name ) { |
1427
|
|
|
global $wgRenderHashAppend, $parserMemc, $wgContLang; |
1428
|
|
|
|
1429
|
|
|
$needParse = false; |
1430
|
|
|
|
1431
|
|
|
if ( $name === 'default' ) { |
1432
|
|
|
// special case |
1433
|
|
|
global $wgSiteNotice; |
1434
|
|
|
$notice = $wgSiteNotice; |
1435
|
|
|
if ( empty( $notice ) ) { |
1436
|
|
|
return false; |
1437
|
|
|
} |
1438
|
|
|
} else { |
1439
|
|
|
$msg = $this->msg( $name )->inContentLanguage(); |
1440
|
|
|
if ( $msg->isBlank() ) { |
1441
|
|
|
return ''; |
1442
|
|
|
} elseif ( $msg->isDisabled() ) { |
1443
|
|
|
return false; |
1444
|
|
|
} |
1445
|
|
|
$notice = $msg->plain(); |
1446
|
|
|
} |
1447
|
|
|
|
1448
|
|
|
// Use the extra hash appender to let eg SSL variants separately cache. |
1449
|
|
|
$key = wfMemcKey( $name . $wgRenderHashAppend ); |
1450
|
|
|
$cachedNotice = $parserMemc->get( $key ); |
1451
|
|
|
if ( is_array( $cachedNotice ) ) { |
1452
|
|
|
if ( md5( $notice ) == $cachedNotice['hash'] ) { |
1453
|
|
|
$notice = $cachedNotice['html']; |
1454
|
|
|
} else { |
1455
|
|
|
$needParse = true; |
1456
|
|
|
} |
1457
|
|
|
} else { |
1458
|
|
|
$needParse = true; |
1459
|
|
|
} |
1460
|
|
|
|
1461
|
|
|
if ( $needParse ) { |
1462
|
|
|
$parsed = $this->getOutput()->parse( $notice ); |
1463
|
|
|
$parserMemc->set( $key, [ 'html' => $parsed, 'hash' => md5( $notice ) ], 600 ); |
1464
|
|
|
$notice = $parsed; |
1465
|
|
|
} |
1466
|
|
|
|
1467
|
|
|
$notice = Html::rawElement( 'div', [ 'id' => 'localNotice', |
1468
|
|
|
'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ], $notice ); |
1469
|
|
|
return $notice; |
1470
|
|
|
} |
1471
|
|
|
|
1472
|
|
|
/** |
1473
|
|
|
* Get the site notice |
1474
|
|
|
* |
1475
|
|
|
* @return string HTML fragment |
1476
|
|
|
*/ |
1477
|
|
|
function getSiteNotice() { |
1478
|
|
|
$siteNotice = ''; |
1479
|
|
|
|
1480
|
|
|
if ( Hooks::run( 'SiteNoticeBefore', [ &$siteNotice, $this ] ) ) { |
1481
|
|
|
if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) { |
1482
|
|
|
$siteNotice = $this->getCachedNotice( 'sitenotice' ); |
1483
|
|
|
} else { |
1484
|
|
|
$anonNotice = $this->getCachedNotice( 'anonnotice' ); |
1485
|
|
|
if ( $anonNotice === false ) { |
1486
|
|
|
$siteNotice = $this->getCachedNotice( 'sitenotice' ); |
1487
|
|
|
} else { |
1488
|
|
|
$siteNotice = $anonNotice; |
1489
|
|
|
} |
1490
|
|
|
} |
1491
|
|
|
if ( $siteNotice === false ) { |
1492
|
|
|
$siteNotice = $this->getCachedNotice( 'default' ); |
1493
|
|
|
} |
1494
|
|
|
} |
1495
|
|
|
|
1496
|
|
|
Hooks::run( 'SiteNoticeAfter', [ &$siteNotice, $this ] ); |
1497
|
|
|
return $siteNotice; |
1498
|
|
|
} |
1499
|
|
|
|
1500
|
|
|
/** |
1501
|
|
|
* Create a section edit link. This supersedes editSectionLink() and |
1502
|
|
|
* editSectionLinkForOther(). |
1503
|
|
|
* |
1504
|
|
|
* @param Title $nt The title being linked to (may not be the same as |
1505
|
|
|
* the current page, if the section is included from a template) |
1506
|
|
|
* @param string $section The designation of the section being pointed to, |
1507
|
|
|
* to be included in the link, like "§ion=$section" |
1508
|
|
|
* @param string $tooltip The tooltip to use for the link: will be escaped |
1509
|
|
|
* and wrapped in the 'editsectionhint' message |
1510
|
|
|
* @param string $lang Language code |
1511
|
|
|
* @return string HTML to use for edit link |
1512
|
|
|
*/ |
1513
|
|
|
public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) { |
1514
|
|
|
// HTML generated here should probably have userlangattributes |
1515
|
|
|
// added to it for LTR text on RTL pages |
1516
|
|
|
|
1517
|
|
|
$lang = wfGetLangObj( $lang ); |
1518
|
|
|
|
1519
|
|
|
$attribs = []; |
1520
|
|
|
if ( !is_null( $tooltip ) ) { |
1521
|
|
|
# Bug 25462: undo double-escaping. |
1522
|
|
|
$tooltip = Sanitizer::decodeCharReferences( $tooltip ); |
1523
|
|
|
$attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip ) |
1524
|
|
|
->inLanguage( $lang )->text(); |
1525
|
|
|
} |
1526
|
|
|
|
1527
|
|
|
$links = [ |
1528
|
|
|
'editsection' => [ |
1529
|
|
|
'text' => wfMessage( 'editsection' )->inLanguage( $lang )->escaped(), |
1530
|
|
|
'targetTitle' => $nt, |
1531
|
|
|
'attribs' => $attribs, |
1532
|
|
|
'query' => [ 'action' => 'edit', 'section' => $section ], |
1533
|
|
|
'options' => [ 'noclasses', 'known' ] |
1534
|
|
|
] |
1535
|
|
|
]; |
1536
|
|
|
|
1537
|
|
|
Hooks::run( 'SkinEditSectionLinks', [ $this, $nt, $section, $tooltip, &$links, $lang ] ); |
1538
|
|
|
|
1539
|
|
|
$result = '<span class="mw-editsection"><span class="mw-editsection-bracket">[</span>'; |
1540
|
|
|
|
1541
|
|
|
$linksHtml = []; |
1542
|
|
|
foreach ( $links as $k => $linkDetails ) { |
1543
|
|
|
$linksHtml[] = Linker::link( |
1544
|
|
|
$linkDetails['targetTitle'], |
1545
|
|
|
$linkDetails['text'], |
1546
|
|
|
$linkDetails['attribs'], |
1547
|
|
|
$linkDetails['query'], |
1548
|
|
|
$linkDetails['options'] |
1549
|
|
|
); |
1550
|
|
|
} |
1551
|
|
|
|
1552
|
|
|
$result .= implode( |
1553
|
|
|
'<span class="mw-editsection-divider">' |
1554
|
|
|
. wfMessage( 'pipe-separator' )->inLanguage( $lang )->text() |
1555
|
|
|
. '</span>', |
1556
|
|
|
$linksHtml |
1557
|
|
|
); |
1558
|
|
|
|
1559
|
|
|
$result .= '<span class="mw-editsection-bracket">]</span></span>'; |
1560
|
|
|
// Deprecated, use SkinEditSectionLinks hook instead |
1561
|
|
|
Hooks::run( |
1562
|
|
|
'DoEditSectionLink', |
1563
|
|
|
[ $this, $nt, $section, $tooltip, &$result, $lang ], |
1564
|
|
|
'1.25' |
1565
|
|
|
); |
1566
|
|
|
return $result; |
1567
|
|
|
} |
1568
|
|
|
|
1569
|
|
|
} |
1570
|
|
|
|