|
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()->getLaggedSlaveMode() ) { |
|
|
|
|
|
|
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 array( 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
|
|
|
|