This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * This program is free software; you can redistribute it and/or modify |
||
4 | * it under the terms of the GNU General Public License as published by |
||
5 | * the Free Software Foundation; either version 2 of the License, or |
||
6 | * (at your option) any later version. |
||
7 | * |
||
8 | * This program is distributed in the hope that it will be useful, |
||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
11 | * GNU General Public License for more details. |
||
12 | * |
||
13 | * You should have received a copy of the GNU General Public License along |
||
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
16 | * http://www.gnu.org/copyleft/gpl.html |
||
17 | * |
||
18 | * @file |
||
19 | */ |
||
20 | |||
21 | use MediaWiki\Auth\AuthManager; |
||
22 | use MediaWiki\MediaWikiServices; |
||
23 | |||
24 | /** |
||
25 | * Base class for template-based skins. |
||
26 | * |
||
27 | * Template-filler skin base class |
||
28 | * Formerly generic PHPTal (http://phptal.sourceforge.net/) skin |
||
29 | * Based on Brion's smarty skin |
||
30 | * @copyright Copyright © Gabriel Wicke -- http://www.aulinx.de/ |
||
31 | * |
||
32 | * @todo Needs some serious refactoring into functions that correspond |
||
33 | * to the computations individual esi snippets need. Most importantly no body |
||
34 | * parsing for most of those of course. |
||
35 | * |
||
36 | * @ingroup Skins |
||
37 | */ |
||
38 | class SkinTemplate extends Skin { |
||
39 | /** |
||
40 | * @var string Name of our skin, it probably needs to be all lower case. |
||
41 | * Child classes should override the default. |
||
42 | */ |
||
43 | public $skinname = 'monobook'; |
||
44 | |||
45 | /** |
||
46 | * @var string For QuickTemplate, the name of the subclass which will |
||
47 | * actually fill the template. Child classes should override the default. |
||
48 | */ |
||
49 | public $template = 'QuickTemplate'; |
||
50 | |||
51 | public $thispage; |
||
52 | public $titletxt; |
||
53 | public $userpage; |
||
54 | public $thisquery; |
||
55 | public $loggedin; |
||
56 | public $username; |
||
57 | public $userpageUrlDetails; |
||
58 | |||
59 | /** |
||
60 | * Add specific styles for this skin |
||
61 | * |
||
62 | * @param OutputPage $out |
||
63 | */ |
||
64 | function setupSkinUserCss( OutputPage $out ) { |
||
65 | $moduleStyles = [ |
||
66 | 'mediawiki.legacy.shared', |
||
67 | 'mediawiki.legacy.commonPrint', |
||
68 | 'mediawiki.sectionAnchor' |
||
69 | ]; |
||
70 | if ( $out->isSyndicated() ) { |
||
71 | $moduleStyles[] = 'mediawiki.feedlink'; |
||
72 | } |
||
73 | |||
74 | // Deprecated since 1.26: Unconditional loading of mediawiki.ui.button |
||
75 | // on every page is deprecated. Express a dependency instead. |
||
76 | if ( strpos( $out->getHTML(), 'mw-ui-button' ) !== false ) { |
||
77 | $moduleStyles[] = 'mediawiki.ui.button'; |
||
78 | } |
||
79 | |||
80 | $out->addModuleStyles( $moduleStyles ); |
||
81 | } |
||
82 | |||
83 | /** |
||
84 | * Create the template engine object; we feed it a bunch of data |
||
85 | * and eventually it spits out some HTML. Should have interface |
||
86 | * roughly equivalent to PHPTAL 0.7. |
||
87 | * |
||
88 | * @param string $classname |
||
89 | * @param bool|string $repository Subdirectory where we keep template files |
||
90 | * @param bool|string $cache_dir |
||
91 | * @return QuickTemplate |
||
92 | * @private |
||
93 | */ |
||
94 | function setupTemplate( $classname, $repository = false, $cache_dir = false ) { |
||
95 | return new $classname( $this->getConfig() ); |
||
96 | } |
||
97 | |||
98 | /** |
||
99 | * Generates array of language links for the current page |
||
100 | * |
||
101 | * @return array |
||
102 | */ |
||
103 | public function getLanguages() { |
||
104 | global $wgHideInterlanguageLinks; |
||
105 | if ( $wgHideInterlanguageLinks ) { |
||
106 | return []; |
||
107 | } |
||
108 | |||
109 | $userLang = $this->getLanguage(); |
||
110 | $languageLinks = []; |
||
111 | |||
112 | foreach ( $this->getOutput()->getLanguageLinks() as $languageLinkText ) { |
||
113 | $class = 'interlanguage-link interwiki-' . explode( ':', $languageLinkText, 2 )[0]; |
||
114 | |||
115 | $languageLinkTitle = Title::newFromText( $languageLinkText ); |
||
116 | if ( $languageLinkTitle ) { |
||
117 | $ilInterwikiCode = $languageLinkTitle->getInterwiki(); |
||
118 | $ilLangName = Language::fetchLanguageName( $ilInterwikiCode ); |
||
119 | |||
120 | if ( strval( $ilLangName ) === '' ) { |
||
121 | $ilDisplayTextMsg = wfMessage( "interlanguage-link-$ilInterwikiCode" ); |
||
122 | if ( !$ilDisplayTextMsg->isDisabled() ) { |
||
123 | // Use custom MW message for the display text |
||
124 | $ilLangName = $ilDisplayTextMsg->text(); |
||
125 | } else { |
||
126 | // Last resort: fallback to the language link target |
||
127 | $ilLangName = $languageLinkText; |
||
128 | } |
||
129 | } else { |
||
130 | // Use the language autonym as display text |
||
131 | $ilLangName = $this->formatLanguageName( $ilLangName ); |
||
132 | } |
||
133 | |||
134 | // CLDR extension or similar is required to localize the language name; |
||
135 | // otherwise we'll end up with the autonym again. |
||
136 | $ilLangLocalName = Language::fetchLanguageName( |
||
137 | $ilInterwikiCode, |
||
138 | $userLang->getCode() |
||
139 | ); |
||
140 | |||
141 | $languageLinkTitleText = $languageLinkTitle->getText(); |
||
142 | if ( $ilLangLocalName === '' ) { |
||
143 | $ilFriendlySiteName = wfMessage( "interlanguage-link-sitename-$ilInterwikiCode" ); |
||
144 | if ( !$ilFriendlySiteName->isDisabled() ) { |
||
145 | if ( $languageLinkTitleText === '' ) { |
||
146 | $ilTitle = wfMessage( |
||
147 | 'interlanguage-link-title-nonlangonly', |
||
148 | $ilFriendlySiteName->text() |
||
149 | )->text(); |
||
150 | } else { |
||
151 | $ilTitle = wfMessage( |
||
152 | 'interlanguage-link-title-nonlang', |
||
153 | $languageLinkTitleText, |
||
154 | $ilFriendlySiteName->text() |
||
155 | )->text(); |
||
156 | } |
||
157 | } else { |
||
158 | // we have nothing friendly to put in the title, so fall back to |
||
159 | // displaying the interlanguage link itself in the title text |
||
160 | // (similar to what is done in page content) |
||
161 | $ilTitle = $languageLinkTitle->getInterwiki() . |
||
162 | ":$languageLinkTitleText"; |
||
163 | } |
||
164 | } elseif ( $languageLinkTitleText === '' ) { |
||
165 | $ilTitle = wfMessage( |
||
166 | 'interlanguage-link-title-langonly', |
||
167 | $ilLangLocalName |
||
168 | )->text(); |
||
169 | } else { |
||
170 | $ilTitle = wfMessage( |
||
171 | 'interlanguage-link-title', |
||
172 | $languageLinkTitleText, |
||
173 | $ilLangLocalName |
||
174 | )->text(); |
||
175 | } |
||
176 | |||
177 | $ilInterwikiCodeBCP47 = wfBCP47( $ilInterwikiCode ); |
||
178 | $languageLink = [ |
||
179 | 'href' => $languageLinkTitle->getFullURL(), |
||
180 | 'text' => $ilLangName, |
||
181 | 'title' => $ilTitle, |
||
182 | 'class' => $class, |
||
183 | 'link-class' => 'interlanguage-link-target', |
||
184 | 'lang' => $ilInterwikiCodeBCP47, |
||
185 | 'hreflang' => $ilInterwikiCodeBCP47, |
||
186 | ]; |
||
187 | Hooks::run( |
||
188 | 'SkinTemplateGetLanguageLink', |
||
189 | [ &$languageLink, $languageLinkTitle, $this->getTitle(), $this->getOutput() ] |
||
190 | ); |
||
191 | $languageLinks[] = $languageLink; |
||
192 | } |
||
193 | } |
||
194 | |||
195 | return $languageLinks; |
||
196 | } |
||
197 | |||
198 | protected function setupTemplateForOutput() { |
||
199 | $request = $this->getRequest(); |
||
200 | $user = $this->getUser(); |
||
201 | $title = $this->getTitle(); |
||
202 | |||
203 | $tpl = $this->setupTemplate( $this->template, 'skins' ); |
||
204 | |||
205 | $this->thispage = $title->getPrefixedDBkey(); |
||
206 | $this->titletxt = $title->getPrefixedText(); |
||
207 | $this->userpage = $user->getUserPage()->getPrefixedText(); |
||
208 | $query = []; |
||
209 | View Code Duplication | if ( !$request->wasPosted() ) { |
|
210 | $query = $request->getValues(); |
||
211 | unset( $query['title'] ); |
||
212 | unset( $query['returnto'] ); |
||
213 | unset( $query['returntoquery'] ); |
||
214 | } |
||
215 | $this->thisquery = wfArrayToCgi( $query ); |
||
216 | $this->loggedin = $user->isLoggedIn(); |
||
217 | $this->username = $user->getName(); |
||
218 | |||
219 | if ( $this->loggedin ) { |
||
220 | $this->userpageUrlDetails = self::makeUrlDetails( $this->userpage ); |
||
221 | } else { |
||
222 | # This won't be used in the standard skins, but we define it to preserve the interface |
||
223 | # To save time, we check for existence |
||
224 | $this->userpageUrlDetails = self::makeKnownUrlDetails( $this->userpage ); |
||
225 | } |
||
226 | |||
227 | return $tpl; |
||
228 | } |
||
229 | |||
230 | /** |
||
231 | * initialize various variables and generate the template |
||
232 | * |
||
233 | * @param OutputPage $out |
||
234 | */ |
||
235 | function outputPage( OutputPage $out = null ) { |
||
236 | Profiler::instance()->setTemplated( true ); |
||
237 | |||
238 | $oldContext = null; |
||
239 | if ( $out !== null ) { |
||
240 | // Deprecated since 1.20, note added in 1.25 |
||
241 | wfDeprecated( __METHOD__, '1.25' ); |
||
242 | $oldContext = $this->getContext(); |
||
243 | $this->setContext( $out->getContext() ); |
||
244 | } |
||
245 | |||
246 | $out = $this->getOutput(); |
||
247 | |||
248 | $this->initPage( $out ); |
||
249 | $tpl = $this->prepareQuickTemplate(); |
||
250 | // execute template |
||
251 | $res = $tpl->execute(); |
||
252 | |||
253 | // result may be an error |
||
254 | $this->printOrError( $res ); |
||
255 | |||
256 | if ( $oldContext ) { |
||
257 | $this->setContext( $oldContext ); |
||
258 | } |
||
259 | } |
||
260 | |||
261 | /** |
||
262 | * Wrap the body text with language information and identifiable element |
||
263 | * |
||
264 | * @param Title $title |
||
265 | * @param string $html body text |
||
266 | * @return string html |
||
267 | */ |
||
268 | protected function wrapHTML( $title, $html ) { |
||
269 | # An ID that includes the actual body text; without categories, contentSub, ... |
||
270 | $realBodyAttribs = [ 'id' => 'mw-content-text' ]; |
||
271 | |||
272 | # Add a mw-content-ltr/rtl class to be able to style based on text direction |
||
273 | # when the content is different from the UI language, i.e.: |
||
274 | # not for special pages or file pages AND only when viewing |
||
275 | if ( !in_array( $title->getNamespace(), [ NS_SPECIAL, NS_FILE ] ) && |
||
276 | Action::getActionName( $this ) === 'view' ) { |
||
277 | $pageLang = $title->getPageViewLanguage(); |
||
278 | $realBodyAttribs['lang'] = $pageLang->getHtmlCode(); |
||
279 | $realBodyAttribs['dir'] = $pageLang->getDir(); |
||
280 | $realBodyAttribs['class'] = 'mw-content-' . $pageLang->getDir(); |
||
281 | } |
||
282 | |||
283 | return Html::rawElement( 'div', $realBodyAttribs, $html ); |
||
284 | } |
||
285 | |||
286 | /** |
||
287 | * initialize various variables and generate the template |
||
288 | * |
||
289 | * @since 1.23 |
||
290 | * @return QuickTemplate The template to be executed by outputPage |
||
291 | */ |
||
292 | protected function prepareQuickTemplate() { |
||
293 | global $wgContLang, $wgScript, $wgStylePath, $wgMimeType, $wgJsMimeType, |
||
294 | $wgSitename, $wgLogo, $wgMaxCredits, |
||
295 | $wgShowCreditsIfMax, $wgArticlePath, |
||
296 | $wgScriptPath, $wgServer; |
||
297 | |||
298 | $title = $this->getTitle(); |
||
299 | $request = $this->getRequest(); |
||
300 | $out = $this->getOutput(); |
||
301 | $tpl = $this->setupTemplateForOutput(); |
||
302 | |||
303 | $tpl->set( 'title', $out->getPageTitle() ); |
||
304 | $tpl->set( 'pagetitle', $out->getHTMLTitle() ); |
||
305 | $tpl->set( 'displaytitle', $out->mPageLinkTitle ); |
||
306 | |||
307 | $tpl->setRef( 'thispage', $this->thispage ); |
||
308 | $tpl->setRef( 'titleprefixeddbkey', $this->thispage ); |
||
309 | $tpl->set( 'titletext', $title->getText() ); |
||
310 | $tpl->set( 'articleid', $title->getArticleID() ); |
||
311 | |||
312 | $tpl->set( 'isarticle', $out->isArticle() ); |
||
313 | |||
314 | $subpagestr = $this->subPageSubtitle(); |
||
315 | if ( $subpagestr !== '' ) { |
||
316 | $subpagestr = '<span class="subpages">' . $subpagestr . '</span>'; |
||
317 | } |
||
318 | $tpl->set( 'subtitle', $subpagestr . $out->getSubtitle() ); |
||
319 | |||
320 | $undelete = $this->getUndeleteLink(); |
||
321 | if ( $undelete === '' ) { |
||
322 | $tpl->set( 'undelete', '' ); |
||
323 | } else { |
||
324 | $tpl->set( 'undelete', '<span class="subpages">' . $undelete . '</span>' ); |
||
325 | } |
||
326 | |||
327 | $tpl->set( 'catlinks', $this->getCategories() ); |
||
328 | if ( $out->isSyndicated() ) { |
||
329 | $feeds = []; |
||
330 | foreach ( $out->getSyndicationLinks() as $format => $link ) { |
||
331 | $feeds[$format] = [ |
||
332 | // Messages: feed-atom, feed-rss |
||
333 | 'text' => $this->msg( "feed-$format" )->text(), |
||
334 | 'href' => $link |
||
335 | ]; |
||
336 | } |
||
337 | $tpl->setRef( 'feeds', $feeds ); |
||
338 | } else { |
||
339 | $tpl->set( 'feeds', false ); |
||
340 | } |
||
341 | |||
342 | $tpl->setRef( 'mimetype', $wgMimeType ); |
||
343 | $tpl->setRef( 'jsmimetype', $wgJsMimeType ); |
||
344 | $tpl->set( 'charset', 'UTF-8' ); |
||
345 | $tpl->setRef( 'wgScript', $wgScript ); |
||
346 | $tpl->setRef( 'skinname', $this->skinname ); |
||
347 | $tpl->set( 'skinclass', get_class( $this ) ); |
||
348 | $tpl->setRef( 'skin', $this ); |
||
349 | $tpl->setRef( 'stylename', $this->stylename ); |
||
350 | $tpl->set( 'printable', $out->isPrintable() ); |
||
351 | $tpl->set( 'handheld', $request->getBool( 'handheld' ) ); |
||
352 | $tpl->setRef( 'loggedin', $this->loggedin ); |
||
353 | $tpl->set( 'notspecialpage', !$title->isSpecialPage() ); |
||
354 | $tpl->set( 'searchaction', $this->escapeSearchLink() ); |
||
355 | $tpl->set( 'searchtitle', SpecialPage::getTitleFor( 'Search' )->getPrefixedDBkey() ); |
||
356 | $tpl->set( 'search', trim( $request->getVal( 'search' ) ) ); |
||
357 | $tpl->setRef( 'stylepath', $wgStylePath ); |
||
358 | $tpl->setRef( 'articlepath', $wgArticlePath ); |
||
359 | $tpl->setRef( 'scriptpath', $wgScriptPath ); |
||
360 | $tpl->setRef( 'serverurl', $wgServer ); |
||
361 | $tpl->setRef( 'logopath', $wgLogo ); |
||
362 | $tpl->setRef( 'sitename', $wgSitename ); |
||
363 | |||
364 | $userLang = $this->getLanguage(); |
||
365 | $userLangCode = $userLang->getHtmlCode(); |
||
366 | $userLangDir = $userLang->getDir(); |
||
367 | |||
368 | $tpl->set( 'lang', $userLangCode ); |
||
369 | $tpl->set( 'dir', $userLangDir ); |
||
370 | $tpl->set( 'rtl', $userLang->isRTL() ); |
||
371 | |||
372 | $tpl->set( 'capitalizeallnouns', $userLang->capitalizeAllNouns() ? ' capitalize-all-nouns' : '' ); |
||
373 | $tpl->set( 'showjumplinks', true ); // showjumplinks preference has been removed |
||
374 | $tpl->set( 'username', $this->loggedin ? $this->username : null ); |
||
375 | $tpl->setRef( 'userpage', $this->userpage ); |
||
376 | $tpl->setRef( 'userpageurl', $this->userpageUrlDetails['href'] ); |
||
377 | $tpl->set( 'userlang', $userLangCode ); |
||
378 | |||
379 | // Users can have their language set differently than the |
||
380 | // content of the wiki. For these users, tell the web browser |
||
381 | // that interface elements are in a different language. |
||
382 | $tpl->set( 'userlangattributes', '' ); |
||
383 | $tpl->set( 'specialpageattributes', '' ); # obsolete |
||
384 | // Used by VectorBeta to insert HTML before content but after the |
||
385 | // heading for the page title. Defaults to empty string. |
||
386 | $tpl->set( 'prebodyhtml', '' ); |
||
387 | |||
388 | if ( $userLangCode !== $wgContLang->getHtmlCode() || $userLangDir !== $wgContLang->getDir() ) { |
||
389 | $escUserlang = htmlspecialchars( $userLangCode ); |
||
390 | $escUserdir = htmlspecialchars( $userLangDir ); |
||
391 | // Attributes must be in double quotes because htmlspecialchars() doesn't |
||
392 | // escape single quotes |
||
393 | $attrs = " lang=\"$escUserlang\" dir=\"$escUserdir\""; |
||
394 | $tpl->set( 'userlangattributes', $attrs ); |
||
395 | } |
||
396 | |||
397 | $tpl->set( 'newtalk', $this->getNewtalks() ); |
||
398 | $tpl->set( 'logo', $this->logoText() ); |
||
399 | |||
400 | $tpl->set( 'copyright', false ); |
||
401 | // No longer used |
||
402 | $tpl->set( 'viewcount', false ); |
||
403 | $tpl->set( 'lastmod', false ); |
||
404 | $tpl->set( 'credits', false ); |
||
405 | $tpl->set( 'numberofwatchingusers', false ); |
||
406 | if ( $out->isArticle() && $title->exists() ) { |
||
407 | if ( $this->isRevisionCurrent() ) { |
||
408 | if ( $wgMaxCredits != 0 ) { |
||
409 | $tpl->set( 'credits', Action::factory( 'credits', $this->getWikiPage(), |
||
0 ignored issues
–
show
|
|||
410 | $this->getContext() )->getCredits( $wgMaxCredits, $wgShowCreditsIfMax ) ); |
||
411 | } else { |
||
412 | $tpl->set( 'lastmod', $this->lastModified() ); |
||
413 | } |
||
414 | } |
||
415 | $tpl->set( 'copyright', $this->getCopyright() ); |
||
416 | } |
||
417 | |||
418 | $tpl->set( 'copyrightico', $this->getCopyrightIcon() ); |
||
419 | $tpl->set( 'poweredbyico', $this->getPoweredBy() ); |
||
420 | $tpl->set( 'disclaimer', $this->disclaimerLink() ); |
||
421 | $tpl->set( 'privacy', $this->privacyLink() ); |
||
422 | $tpl->set( 'about', $this->aboutLink() ); |
||
423 | |||
424 | $tpl->set( 'footerlinks', [ |
||
425 | 'info' => [ |
||
426 | 'lastmod', |
||
427 | 'numberofwatchingusers', |
||
428 | 'credits', |
||
429 | 'copyright', |
||
430 | ], |
||
431 | 'places' => [ |
||
432 | 'privacy', |
||
433 | 'about', |
||
434 | 'disclaimer', |
||
435 | ], |
||
436 | ] ); |
||
437 | |||
438 | global $wgFooterIcons; |
||
439 | $tpl->set( 'footericons', $wgFooterIcons ); |
||
440 | foreach ( $tpl->data['footericons'] as $footerIconsKey => &$footerIconsBlock ) { |
||
441 | if ( count( $footerIconsBlock ) > 0 ) { |
||
442 | foreach ( $footerIconsBlock as &$footerIcon ) { |
||
443 | if ( isset( $footerIcon['src'] ) ) { |
||
444 | if ( !isset( $footerIcon['width'] ) ) { |
||
445 | $footerIcon['width'] = 88; |
||
446 | } |
||
447 | if ( !isset( $footerIcon['height'] ) ) { |
||
448 | $footerIcon['height'] = 31; |
||
449 | } |
||
450 | } |
||
451 | } |
||
452 | } else { |
||
453 | unset( $tpl->data['footericons'][$footerIconsKey] ); |
||
454 | } |
||
455 | } |
||
456 | |||
457 | $tpl->set( 'indicators', $out->getIndicators() ); |
||
458 | |||
459 | $tpl->set( 'sitenotice', $this->getSiteNotice() ); |
||
460 | $tpl->set( 'printfooter', $this->printSource() ); |
||
461 | // Wrap the bodyText with #mw-content-text element |
||
462 | $out->mBodytext = $this->wrapHTML( $title, $out->mBodytext ); |
||
0 ignored issues
–
show
It seems like
$title defined by $this->getTitle() on line 298 can be null ; however, SkinTemplate::wrapHTML() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
463 | $tpl->setRef( 'bodytext', $out->mBodytext ); |
||
464 | |||
465 | $language_urls = $this->getLanguages(); |
||
466 | if ( count( $language_urls ) ) { |
||
467 | $tpl->setRef( 'language_urls', $language_urls ); |
||
468 | } else { |
||
469 | $tpl->set( 'language_urls', false ); |
||
470 | } |
||
471 | |||
472 | # Personal toolbar |
||
473 | $tpl->set( 'personal_urls', $this->buildPersonalUrls() ); |
||
474 | $content_navigation = $this->buildContentNavigationUrls(); |
||
475 | $content_actions = $this->buildContentActionUrls( $content_navigation ); |
||
476 | $tpl->setRef( 'content_navigation', $content_navigation ); |
||
477 | $tpl->setRef( 'content_actions', $content_actions ); |
||
478 | |||
479 | $tpl->set( 'sidebar', $this->buildSidebar() ); |
||
480 | $tpl->set( 'nav_urls', $this->buildNavUrls() ); |
||
481 | |||
482 | // Do this last in case hooks above add bottom scripts |
||
483 | $tpl->set( 'bottomscripts', $this->bottomScripts() ); |
||
484 | |||
485 | // Set the head scripts near the end, in case the above actions resulted in added scripts |
||
486 | $tpl->set( 'headelement', $out->headElement( $this ) ); |
||
487 | |||
488 | $tpl->set( 'debug', '' ); |
||
489 | $tpl->set( 'debughtml', $this->generateDebugHTML() ); |
||
490 | $tpl->set( 'reporttime', wfReportTime() ); |
||
491 | |||
492 | // original version by hansm |
||
493 | if ( !Hooks::run( 'SkinTemplateOutputPageBeforeExec', [ &$this, &$tpl ] ) ) { |
||
494 | wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" ); |
||
495 | } |
||
496 | |||
497 | // Set the bodytext to another key so that skins can just output it on its own |
||
498 | // and output printfooter and debughtml separately |
||
499 | $tpl->set( 'bodycontent', $tpl->data['bodytext'] ); |
||
500 | |||
501 | // Append printfooter and debughtml onto bodytext so that skins that |
||
502 | // were already using bodytext before they were split out don't suddenly |
||
503 | // start not outputting information. |
||
504 | $tpl->data['bodytext'] .= Html::rawElement( |
||
505 | 'div', |
||
506 | [ 'class' => 'printfooter' ], |
||
507 | "\n{$tpl->data['printfooter']}" |
||
508 | ) . "\n"; |
||
509 | $tpl->data['bodytext'] .= $tpl->data['debughtml']; |
||
510 | |||
511 | // allow extensions adding stuff after the page content. |
||
512 | // See Skin::afterContentHook() for further documentation. |
||
513 | $tpl->set( 'dataAfterContent', $this->afterContentHook() ); |
||
514 | |||
515 | return $tpl; |
||
516 | } |
||
517 | |||
518 | /** |
||
519 | * Get the HTML for the p-personal list |
||
520 | * @return string |
||
521 | */ |
||
522 | public function getPersonalToolsList() { |
||
523 | $tpl = $this->setupTemplateForOutput(); |
||
524 | $tpl->set( 'personal_urls', $this->buildPersonalUrls() ); |
||
525 | $html = ''; |
||
526 | foreach ( $tpl->getPersonalTools() as $key => $item ) { |
||
527 | $html .= $tpl->makeListItem( $key, $item ); |
||
528 | } |
||
529 | return $html; |
||
530 | } |
||
531 | |||
532 | /** |
||
533 | * Format language name for use in sidebar interlanguage links list. |
||
534 | * By default it is capitalized. |
||
535 | * |
||
536 | * @param string $name Language name, e.g. "English" or "español" |
||
537 | * @return string |
||
538 | * @private |
||
539 | */ |
||
540 | function formatLanguageName( $name ) { |
||
541 | return $this->getLanguage()->ucfirst( $name ); |
||
542 | } |
||
543 | |||
544 | /** |
||
545 | * Output the string, or print error message if it's |
||
546 | * an error object of the appropriate type. |
||
547 | * For the base class, assume strings all around. |
||
548 | * |
||
549 | * @param string $str |
||
550 | * @private |
||
551 | */ |
||
552 | function printOrError( $str ) { |
||
553 | echo $str; |
||
554 | } |
||
555 | |||
556 | /** |
||
557 | * Output a boolean indicating if buildPersonalUrls should output separate |
||
558 | * login and create account links or output a combined link |
||
559 | * By default we simply return a global config setting that affects most skins |
||
560 | * This is setup as a method so that like with $wgLogo and getLogo() a skin |
||
561 | * can override this setting and always output one or the other if it has |
||
562 | * a reason it can't output one of the two modes. |
||
563 | * @return bool |
||
564 | */ |
||
565 | function useCombinedLoginLink() { |
||
566 | global $wgUseCombinedLoginLink; |
||
567 | return $wgUseCombinedLoginLink; |
||
568 | } |
||
569 | |||
570 | /** |
||
571 | * build array of urls for personal toolbar |
||
572 | * @return array |
||
573 | */ |
||
574 | protected function buildPersonalUrls() { |
||
575 | $title = $this->getTitle(); |
||
576 | $request = $this->getRequest(); |
||
577 | $pageurl = $title->getLocalURL(); |
||
578 | $authManager = AuthManager::singleton(); |
||
579 | |||
580 | /* set up the default links for the personal toolbar */ |
||
581 | $personal_urls = []; |
||
582 | |||
583 | # Due to bug 32276, if a user does not have read permissions, |
||
584 | # $this->getTitle() will just give Special:Badtitle, which is |
||
585 | # not especially useful as a returnto parameter. Use the title |
||
586 | # from the request instead, if there was one. |
||
587 | if ( $this->getUser()->isAllowed( 'read' ) ) { |
||
588 | $page = $this->getTitle(); |
||
589 | } else { |
||
590 | $page = Title::newFromText( $request->getVal( 'title', '' ) ); |
||
591 | } |
||
592 | $page = $request->getVal( 'returnto', $page ); |
||
0 ignored issues
–
show
It seems like
$page can also be of type object<Title> ; however, WebRequest::getVal() does only seem to accept string|null , maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. ![]() |
|||
593 | $a = []; |
||
594 | if ( strval( $page ) !== '' ) { |
||
595 | $a['returnto'] = $page; |
||
596 | $query = $request->getVal( 'returntoquery', $this->thisquery ); |
||
597 | if ( $query != '' ) { |
||
598 | $a['returntoquery'] = $query; |
||
599 | } |
||
600 | } |
||
601 | |||
602 | $returnto = wfArrayToCgi( $a ); |
||
603 | if ( $this->loggedin ) { |
||
604 | $personal_urls['userpage'] = [ |
||
605 | 'text' => $this->username, |
||
606 | 'href' => &$this->userpageUrlDetails['href'], |
||
607 | 'class' => $this->userpageUrlDetails['exists'] ? false : 'new', |
||
608 | 'active' => ( $this->userpageUrlDetails['href'] == $pageurl ), |
||
609 | 'dir' => 'auto' |
||
610 | ]; |
||
611 | $usertalkUrlDetails = $this->makeTalkUrlDetails( $this->userpage ); |
||
612 | $personal_urls['mytalk'] = [ |
||
613 | 'text' => $this->msg( 'mytalk' )->text(), |
||
614 | 'href' => &$usertalkUrlDetails['href'], |
||
615 | 'class' => $usertalkUrlDetails['exists'] ? false : 'new', |
||
616 | 'active' => ( $usertalkUrlDetails['href'] == $pageurl ) |
||
617 | ]; |
||
618 | $href = self::makeSpecialUrl( 'Preferences' ); |
||
619 | $personal_urls['preferences'] = [ |
||
620 | 'text' => $this->msg( 'mypreferences' )->text(), |
||
621 | 'href' => $href, |
||
622 | 'active' => ( $href == $pageurl ) |
||
623 | ]; |
||
624 | |||
625 | if ( $this->getUser()->isAllowed( 'viewmywatchlist' ) ) { |
||
626 | $href = self::makeSpecialUrl( 'Watchlist' ); |
||
627 | $personal_urls['watchlist'] = [ |
||
628 | 'text' => $this->msg( 'mywatchlist' )->text(), |
||
629 | 'href' => $href, |
||
630 | 'active' => ( $href == $pageurl ) |
||
631 | ]; |
||
632 | } |
||
633 | |||
634 | # We need to do an explicit check for Special:Contributions, as we |
||
635 | # have to match both the title, and the target, which could come |
||
636 | # from request values (Special:Contributions?target=Jimbo_Wales) |
||
637 | # or be specified in "sub page" form |
||
638 | # (Special:Contributions/Jimbo_Wales). The plot |
||
639 | # thickens, because the Title object is altered for special pages, |
||
640 | # so it doesn't contain the original alias-with-subpage. |
||
641 | $origTitle = Title::newFromText( $request->getText( 'title' ) ); |
||
642 | if ( $origTitle instanceof Title && $origTitle->isSpecialPage() ) { |
||
643 | list( $spName, $spPar ) = SpecialPageFactory::resolveAlias( $origTitle->getText() ); |
||
644 | $active = $spName == 'Contributions' |
||
645 | && ( ( $spPar && $spPar == $this->username ) |
||
646 | || $request->getText( 'target' ) == $this->username ); |
||
647 | } else { |
||
648 | $active = false; |
||
649 | } |
||
650 | |||
651 | $href = self::makeSpecialUrlSubpage( 'Contributions', $this->username ); |
||
652 | $personal_urls['mycontris'] = [ |
||
653 | 'text' => $this->msg( 'mycontris' )->text(), |
||
654 | 'href' => $href, |
||
655 | 'active' => $active |
||
656 | ]; |
||
657 | |||
658 | // if we can't set the user, we can't unset it either |
||
659 | if ( $request->getSession()->canSetUser() ) { |
||
660 | $personal_urls['logout'] = [ |
||
661 | 'text' => $this->msg( 'pt-userlogout' )->text(), |
||
662 | 'href' => self::makeSpecialUrl( 'Userlogout', |
||
663 | // userlogout link must always contain an & character, otherwise we might not be able |
||
664 | // to detect a buggy precaching proxy (bug 17790) |
||
665 | $title->isSpecial( 'Preferences' ) ? 'noreturnto' : $returnto ), |
||
666 | 'active' => false |
||
667 | ]; |
||
668 | } |
||
669 | } else { |
||
670 | $useCombinedLoginLink = $this->useCombinedLoginLink(); |
||
671 | if ( !$authManager->canCreateAccounts() || !$authManager->canAuthenticateNow() ) { |
||
672 | // don't show combined login/signup link if one of those is actually not available |
||
673 | $useCombinedLoginLink = false; |
||
674 | } |
||
675 | |||
676 | $loginlink = $this->getUser()->isAllowed( 'createaccount' ) && $useCombinedLoginLink |
||
677 | ? 'nav-login-createaccount' |
||
678 | : 'pt-login'; |
||
679 | |||
680 | $login_url = [ |
||
681 | 'text' => $this->msg( $loginlink )->text(), |
||
682 | 'href' => self::makeSpecialUrl( 'Userlogin', $returnto ), |
||
683 | 'active' => $title->isSpecial( 'Userlogin' ) |
||
684 | || $title->isSpecial( 'CreateAccount' ) && $useCombinedLoginLink, |
||
685 | ]; |
||
686 | $createaccount_url = [ |
||
687 | 'text' => $this->msg( 'pt-createaccount' )->text(), |
||
688 | 'href' => self::makeSpecialUrl( 'CreateAccount', $returnto ), |
||
689 | 'active' => $title->isSpecial( 'CreateAccount' ), |
||
690 | ]; |
||
691 | |||
692 | // No need to show Talk and Contributions to anons if they can't contribute! |
||
693 | if ( User::groupHasPermission( '*', 'edit' ) ) { |
||
694 | // Because of caching, we can't link directly to the IP talk and |
||
695 | // contributions pages. Instead we use the special page shortcuts |
||
696 | // (which work correctly regardless of caching). This means we can't |
||
697 | // determine whether these links are active or not, but since major |
||
698 | // skins (MonoBook, Vector) don't use this information, it's not a |
||
699 | // huge loss. |
||
700 | $personal_urls['anontalk'] = [ |
||
701 | 'text' => $this->msg( 'anontalk' )->text(), |
||
702 | 'href' => self::makeSpecialUrlSubpage( 'Mytalk', false ), |
||
703 | 'active' => false |
||
704 | ]; |
||
705 | $personal_urls['anoncontribs'] = [ |
||
706 | 'text' => $this->msg( 'anoncontribs' )->text(), |
||
707 | 'href' => self::makeSpecialUrlSubpage( 'Mycontributions', false ), |
||
708 | 'active' => false |
||
709 | ]; |
||
710 | } |
||
711 | |||
712 | if ( |
||
713 | $authManager->canCreateAccounts() |
||
714 | && $this->getUser()->isAllowed( 'createaccount' ) |
||
715 | && !$useCombinedLoginLink |
||
716 | ) { |
||
717 | $personal_urls['createaccount'] = $createaccount_url; |
||
718 | } |
||
719 | |||
720 | if ( $authManager->canAuthenticateNow() ) { |
||
721 | $personal_urls['login'] = $login_url; |
||
722 | } |
||
723 | } |
||
724 | |||
725 | Hooks::run( 'PersonalUrls', [ &$personal_urls, &$title, $this ] ); |
||
726 | return $personal_urls; |
||
727 | } |
||
728 | |||
729 | /** |
||
730 | * Builds an array with tab definition |
||
731 | * |
||
732 | * @param Title $title Page Where the tab links to |
||
733 | * @param string|array $message Message key or an array of message keys (will fall back) |
||
734 | * @param bool $selected Display the tab as selected |
||
735 | * @param string $query Query string attached to tab URL |
||
736 | * @param bool $checkEdit Check if $title exists and mark with .new if one doesn't |
||
737 | * |
||
738 | * @return array |
||
739 | */ |
||
740 | function tabAction( $title, $message, $selected, $query = '', $checkEdit = false ) { |
||
741 | $classes = []; |
||
742 | if ( $selected ) { |
||
743 | $classes[] = 'selected'; |
||
744 | } |
||
745 | if ( $checkEdit && !$title->isKnown() ) { |
||
746 | $classes[] = 'new'; |
||
747 | if ( $query !== '' ) { |
||
748 | $query = 'action=edit&redlink=1&' . $query; |
||
749 | } else { |
||
750 | $query = 'action=edit&redlink=1'; |
||
751 | } |
||
752 | } |
||
753 | |||
754 | $linkClass = MediaWikiServices::getInstance()->getLinkRenderer()->getLinkClasses( $title ); |
||
755 | |||
756 | // wfMessageFallback will nicely accept $message as an array of fallbacks |
||
757 | // or just a single key |
||
758 | $msg = wfMessageFallback( $message )->setContext( $this->getContext() ); |
||
759 | if ( is_array( $message ) ) { |
||
760 | // for hook compatibility just keep the last message name |
||
761 | $message = end( $message ); |
||
762 | } |
||
763 | if ( $msg->exists() ) { |
||
764 | $text = $msg->text(); |
||
765 | } else { |
||
766 | global $wgContLang; |
||
767 | $text = $wgContLang->getConverter()->convertNamespace( |
||
768 | MWNamespace::getSubject( $title->getNamespace() ) ); |
||
769 | } |
||
770 | |||
771 | $result = []; |
||
772 | if ( !Hooks::run( 'SkinTemplateTabAction', [ &$this, |
||
773 | $title, $message, $selected, $checkEdit, |
||
774 | &$classes, &$query, &$text, &$result ] ) ) { |
||
775 | return $result; |
||
776 | } |
||
777 | |||
778 | $result = [ |
||
779 | 'class' => implode( ' ', $classes ), |
||
780 | 'text' => $text, |
||
781 | 'href' => $title->getLocalURL( $query ), |
||
782 | 'primary' => true ]; |
||
783 | if ( $linkClass !== '' ) { |
||
784 | $result['link-class'] = $linkClass; |
||
785 | } |
||
786 | |||
787 | return $result; |
||
788 | } |
||
789 | |||
790 | function makeTalkUrlDetails( $name, $urlaction = '' ) { |
||
791 | $title = Title::newFromText( $name ); |
||
792 | if ( !is_object( $title ) ) { |
||
793 | throw new MWException( __METHOD__ . " given invalid pagename $name" ); |
||
794 | } |
||
795 | $title = $title->getTalkPage(); |
||
796 | self::checkTitle( $title, $name ); |
||
797 | return [ |
||
798 | 'href' => $title->getLocalURL( $urlaction ), |
||
799 | 'exists' => $title->isKnown(), |
||
800 | ]; |
||
801 | } |
||
802 | |||
803 | /** |
||
804 | * @todo is this even used? |
||
805 | */ |
||
806 | View Code Duplication | function makeArticleUrlDetails( $name, $urlaction = '' ) { |
|
807 | $title = Title::newFromText( $name ); |
||
808 | $title = $title->getSubjectPage(); |
||
809 | self::checkTitle( $title, $name ); |
||
810 | return [ |
||
811 | 'href' => $title->getLocalURL( $urlaction ), |
||
812 | 'exists' => $title->exists(), |
||
813 | ]; |
||
814 | } |
||
815 | |||
816 | /** |
||
817 | * a structured array of links usually used for the tabs in a skin |
||
818 | * |
||
819 | * There are 4 standard sections |
||
820 | * namespaces: Used for namespace tabs like special, page, and talk namespaces |
||
821 | * views: Used for primary page views like read, edit, history |
||
822 | * actions: Used for most extra page actions like deletion, protection, etc... |
||
823 | * variants: Used to list the language variants for the page |
||
824 | * |
||
825 | * Each section's value is a key/value array of links for that section. |
||
826 | * The links themselves have these common keys: |
||
827 | * - class: The css classes to apply to the tab |
||
828 | * - text: The text to display on the tab |
||
829 | * - href: The href for the tab to point to |
||
830 | * - rel: An optional rel= for the tab's link |
||
831 | * - redundant: If true the tab will be dropped in skins using content_actions |
||
832 | * this is useful for tabs like "Read" which only have meaning in skins that |
||
833 | * take special meaning from the grouped structure of content_navigation |
||
834 | * |
||
835 | * Views also have an extra key which can be used: |
||
836 | * - primary: If this is not true skins like vector may try to hide the tab |
||
837 | * when the user has limited space in their browser window |
||
838 | * |
||
839 | * content_navigation using code also expects these ids to be present on the |
||
840 | * links, however these are usually automatically generated by SkinTemplate |
||
841 | * itself and are not necessary when using a hook. The only things these may |
||
842 | * matter to are people modifying content_navigation after it's initial creation: |
||
843 | * - id: A "preferred" id, most skins are best off outputting this preferred |
||
844 | * id for best compatibility. |
||
845 | * - tooltiponly: This is set to true for some tabs in cases where the system |
||
846 | * believes that the accesskey should not be added to the tab. |
||
847 | * |
||
848 | * @return array |
||
849 | */ |
||
850 | protected function buildContentNavigationUrls() { |
||
851 | global $wgDisableLangConversion; |
||
852 | |||
853 | // Display tabs for the relevant title rather than always the title itself |
||
854 | $title = $this->getRelevantTitle(); |
||
855 | $onPage = $title->equals( $this->getTitle() ); |
||
0 ignored issues
–
show
It seems like
$this->getTitle() can be null ; however, equals() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
856 | |||
857 | $out = $this->getOutput(); |
||
858 | $request = $this->getRequest(); |
||
859 | $user = $this->getUser(); |
||
860 | |||
861 | $content_navigation = [ |
||
862 | 'namespaces' => [], |
||
863 | 'views' => [], |
||
864 | 'actions' => [], |
||
865 | 'variants' => [] |
||
866 | ]; |
||
867 | |||
868 | // parameters |
||
869 | $action = $request->getVal( 'action', 'view' ); |
||
870 | |||
871 | $userCanRead = $title->quickUserCan( 'read', $user ); |
||
872 | |||
873 | $preventActiveTabs = false; |
||
874 | Hooks::run( 'SkinTemplatePreventOtherActiveTabs', [ &$this, &$preventActiveTabs ] ); |
||
875 | |||
876 | // Checks if page is some kind of content |
||
877 | if ( $title->canExist() ) { |
||
878 | // Gets page objects for the related namespaces |
||
879 | $subjectPage = $title->getSubjectPage(); |
||
880 | $talkPage = $title->getTalkPage(); |
||
881 | |||
882 | // Determines if this is a talk page |
||
883 | $isTalk = $title->isTalkPage(); |
||
884 | |||
885 | // Generates XML IDs from namespace names |
||
886 | $subjectId = $title->getNamespaceKey( '' ); |
||
887 | |||
888 | if ( $subjectId == 'main' ) { |
||
889 | $talkId = 'talk'; |
||
890 | } else { |
||
891 | $talkId = "{$subjectId}_talk"; |
||
892 | } |
||
893 | |||
894 | $skname = $this->skinname; |
||
895 | |||
896 | // Adds namespace links |
||
897 | $subjectMsg = [ "nstab-$subjectId" ]; |
||
898 | if ( $subjectPage->isMainPage() ) { |
||
899 | array_unshift( $subjectMsg, 'mainpage-nstab' ); |
||
900 | } |
||
901 | $content_navigation['namespaces'][$subjectId] = $this->tabAction( |
||
902 | $subjectPage, $subjectMsg, !$isTalk && !$preventActiveTabs, '', $userCanRead |
||
903 | ); |
||
904 | $content_navigation['namespaces'][$subjectId]['context'] = 'subject'; |
||
905 | $content_navigation['namespaces'][$talkId] = $this->tabAction( |
||
906 | $talkPage, [ "nstab-$talkId", 'talk' ], $isTalk && !$preventActiveTabs, '', $userCanRead |
||
907 | ); |
||
908 | $content_navigation['namespaces'][$talkId]['context'] = 'talk'; |
||
909 | |||
910 | if ( $userCanRead ) { |
||
911 | // Adds "view" view link |
||
912 | if ( $title->isKnown() ) { |
||
913 | $content_navigation['views']['view'] = $this->tabAction( |
||
914 | $isTalk ? $talkPage : $subjectPage, |
||
915 | [ "$skname-view-view", 'view' ], |
||
916 | ( $onPage && ( $action == 'view' || $action == 'purge' ) ), '', true |
||
917 | ); |
||
918 | // signal to hide this from simple content_actions |
||
919 | $content_navigation['views']['view']['redundant'] = true; |
||
920 | } |
||
921 | |||
922 | $isForeignFile = $title->inNamespace( NS_FILE ) && $this->canUseWikiPage() && |
||
923 | $this->getWikiPage() instanceof WikiFilePage && !$this->getWikiPage()->isLocal(); |
||
924 | |||
925 | // If it is a non-local file, show a link to the file in its own repository |
||
926 | // @todo abstract this for remote content that isn't a file |
||
927 | if ( $isForeignFile ) { |
||
928 | $file = $this->getWikiPage()->getFile(); |
||
929 | $content_navigation['views']['view-foreign'] = [ |
||
930 | 'class' => '', |
||
931 | 'text' => wfMessageFallback( "$skname-view-foreign", 'view-foreign' )-> |
||
932 | setContext( $this->getContext() )-> |
||
933 | params( $file->getRepo()->getDisplayName() )->text(), |
||
934 | 'href' => $file->getDescriptionUrl(), |
||
935 | 'primary' => false, |
||
936 | ]; |
||
937 | } |
||
938 | |||
939 | // Checks if user can edit the current page if it exists or create it otherwise |
||
940 | if ( $title->quickUserCan( 'edit', $user ) |
||
941 | && ( $title->exists() || $title->quickUserCan( 'create', $user ) ) |
||
942 | ) { |
||
943 | // Builds CSS class for talk page links |
||
944 | $isTalkClass = $isTalk ? ' istalk' : ''; |
||
945 | // Whether the user is editing the page |
||
946 | $isEditing = $onPage && ( $action == 'edit' || $action == 'submit' ); |
||
947 | // Whether to show the "Add a new section" tab |
||
948 | // Checks if this is a current rev of talk page and is not forced to be hidden |
||
949 | $showNewSection = !$out->forceHideNewSectionLink() |
||
950 | && ( ( $isTalk && $this->isRevisionCurrent() ) || $out->showNewSectionLink() ); |
||
951 | $section = $request->getVal( 'section' ); |
||
952 | |||
953 | if ( $title->exists() |
||
954 | || ( $title->getNamespace() == NS_MEDIAWIKI |
||
955 | && $title->getDefaultMessageText() !== false |
||
956 | ) |
||
957 | ) { |
||
958 | $msgKey = $isForeignFile ? 'edit-local' : 'edit'; |
||
959 | } else { |
||
960 | $msgKey = $isForeignFile ? 'create-local' : 'create'; |
||
961 | } |
||
962 | $content_navigation['views']['edit'] = [ |
||
963 | 'class' => ( $isEditing && ( $section !== 'new' || !$showNewSection ) |
||
964 | ? 'selected' |
||
965 | : '' |
||
966 | ) . $isTalkClass, |
||
967 | 'text' => wfMessageFallback( "$skname-view-$msgKey", $msgKey ) |
||
968 | ->setContext( $this->getContext() )->text(), |
||
969 | 'href' => $title->getLocalURL( $this->editUrlOptions() ), |
||
970 | 'primary' => !$isForeignFile, // don't collapse this in vector |
||
971 | ]; |
||
972 | |||
973 | // section link |
||
974 | if ( $showNewSection ) { |
||
975 | // Adds new section link |
||
976 | // $content_navigation['actions']['addsection'] |
||
977 | $content_navigation['views']['addsection'] = [ |
||
978 | 'class' => ( $isEditing && $section == 'new' ) ? 'selected' : false, |
||
979 | 'text' => wfMessageFallback( "$skname-action-addsection", 'addsection' ) |
||
980 | ->setContext( $this->getContext() )->text(), |
||
981 | 'href' => $title->getLocalURL( 'action=edit§ion=new' ) |
||
982 | ]; |
||
983 | } |
||
984 | // Checks if the page has some kind of viewable source content |
||
985 | View Code Duplication | } elseif ( $title->hasSourceText() ) { |
|
986 | // Adds view source view link |
||
987 | $content_navigation['views']['viewsource'] = [ |
||
988 | 'class' => ( $onPage && $action == 'edit' ) ? 'selected' : false, |
||
989 | 'text' => wfMessageFallback( "$skname-action-viewsource", 'viewsource' ) |
||
990 | ->setContext( $this->getContext() )->text(), |
||
991 | 'href' => $title->getLocalURL( $this->editUrlOptions() ), |
||
992 | 'primary' => true, // don't collapse this in vector |
||
993 | ]; |
||
994 | } |
||
995 | |||
996 | // Checks if the page exists |
||
997 | if ( $title->exists() ) { |
||
998 | // Adds history view link |
||
999 | $content_navigation['views']['history'] = [ |
||
1000 | 'class' => ( $onPage && $action == 'history' ) ? 'selected' : false, |
||
1001 | 'text' => wfMessageFallback( "$skname-view-history", 'history_short' ) |
||
1002 | ->setContext( $this->getContext() )->text(), |
||
1003 | 'href' => $title->getLocalURL( 'action=history' ), |
||
1004 | ]; |
||
1005 | |||
1006 | View Code Duplication | if ( $title->quickUserCan( 'delete', $user ) ) { |
|
1007 | $content_navigation['actions']['delete'] = [ |
||
1008 | 'class' => ( $onPage && $action == 'delete' ) ? 'selected' : false, |
||
1009 | 'text' => wfMessageFallback( "$skname-action-delete", 'delete' ) |
||
1010 | ->setContext( $this->getContext() )->text(), |
||
1011 | 'href' => $title->getLocalURL( 'action=delete' ) |
||
1012 | ]; |
||
1013 | } |
||
1014 | |||
1015 | if ( $title->quickUserCan( 'move', $user ) ) { |
||
1016 | $moveTitle = SpecialPage::getTitleFor( 'Movepage', $title->getPrefixedDBkey() ); |
||
1017 | $content_navigation['actions']['move'] = [ |
||
1018 | 'class' => $this->getTitle()->isSpecial( 'Movepage' ) ? 'selected' : false, |
||
1019 | 'text' => wfMessageFallback( "$skname-action-move", 'move' ) |
||
1020 | ->setContext( $this->getContext() )->text(), |
||
1021 | 'href' => $moveTitle->getLocalURL() |
||
1022 | ]; |
||
1023 | } |
||
1024 | } else { |
||
1025 | // article doesn't exist or is deleted |
||
1026 | if ( $user->isAllowed( 'deletedhistory' ) ) { |
||
1027 | $n = $title->isDeleted(); |
||
1028 | if ( $n ) { |
||
1029 | $undelTitle = SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedDBkey() ); |
||
1030 | // If the user can't undelete but can view deleted |
||
1031 | // history show them a "View .. deleted" tab instead. |
||
1032 | $msgKey = $user->isAllowed( 'undelete' ) ? 'undelete' : 'viewdeleted'; |
||
1033 | $content_navigation['actions']['undelete'] = [ |
||
1034 | 'class' => $this->getTitle()->isSpecial( 'Undelete' ) ? 'selected' : false, |
||
1035 | 'text' => wfMessageFallback( "$skname-action-$msgKey", "{$msgKey}_short" ) |
||
1036 | ->setContext( $this->getContext() )->numParams( $n )->text(), |
||
1037 | 'href' => $undelTitle->getLocalURL() |
||
1038 | ]; |
||
1039 | } |
||
1040 | } |
||
1041 | } |
||
1042 | |||
1043 | if ( $title->quickUserCan( 'protect', $user ) && $title->getRestrictionTypes() && |
||
1044 | MWNamespace::getRestrictionLevels( $title->getNamespace(), $user ) !== [ '' ] |
||
1045 | ) { |
||
1046 | $mode = $title->isProtected() ? 'unprotect' : 'protect'; |
||
1047 | $content_navigation['actions'][$mode] = [ |
||
1048 | 'class' => ( $onPage && $action == $mode ) ? 'selected' : false, |
||
1049 | 'text' => wfMessageFallback( "$skname-action-$mode", $mode ) |
||
1050 | ->setContext( $this->getContext() )->text(), |
||
1051 | 'href' => $title->getLocalURL( "action=$mode" ) |
||
1052 | ]; |
||
1053 | } |
||
1054 | |||
1055 | // Checks if the user is logged in |
||
1056 | if ( $this->loggedin && $user->isAllowedAll( 'viewmywatchlist', 'editmywatchlist' ) ) { |
||
1057 | /** |
||
1058 | * The following actions use messages which, if made particular to |
||
1059 | * the any specific skins, would break the Ajax code which makes this |
||
1060 | * action happen entirely inline. OutputPage::getJSVars |
||
1061 | * defines a set of messages in a javascript object - and these |
||
1062 | * messages are assumed to be global for all skins. Without making |
||
1063 | * a change to that procedure these messages will have to remain as |
||
1064 | * the global versions. |
||
1065 | */ |
||
1066 | $mode = $user->isWatched( $title ) ? 'unwatch' : 'watch'; |
||
0 ignored issues
–
show
It seems like
$title defined by $this->getRelevantTitle() on line 854 can be null ; however, User::isWatched() does not accept null , maybe add an additional type check?
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: /** @return stdClass|null */
function mayReturnNull() { }
function doesNotAcceptNull(stdClass $x) { }
// With potential error.
function withoutCheck() {
$x = mayReturnNull();
doesNotAcceptNull($x); // Potential error here.
}
// Safe - Alternative 1
function withCheck1() {
$x = mayReturnNull();
if ( ! $x instanceof stdClass) {
throw new \LogicException('$x must be defined.');
}
doesNotAcceptNull($x);
}
// Safe - Alternative 2
function withCheck2() {
$x = mayReturnNull();
if ($x instanceof stdClass) {
doesNotAcceptNull($x);
}
}
![]() |
|||
1067 | $content_navigation['actions'][$mode] = [ |
||
1068 | 'class' => 'mw-watchlink ' . ( |
||
1069 | $onPage && ( $action == 'watch' || $action == 'unwatch' ) ? 'selected' : '' |
||
1070 | ), |
||
1071 | // uses 'watch' or 'unwatch' message |
||
1072 | 'text' => $this->msg( $mode )->text(), |
||
1073 | 'href' => $title->getLocalURL( [ 'action' => $mode ] ) |
||
1074 | ]; |
||
1075 | } |
||
1076 | } |
||
1077 | |||
1078 | Hooks::run( 'SkinTemplateNavigation', [ &$this, &$content_navigation ] ); |
||
1079 | |||
1080 | if ( $userCanRead && !$wgDisableLangConversion ) { |
||
1081 | $pageLang = $title->getPageLanguage(); |
||
1082 | // Gets list of language variants |
||
1083 | $variants = $pageLang->getVariants(); |
||
1084 | // Checks that language conversion is enabled and variants exist |
||
1085 | // And if it is not in the special namespace |
||
1086 | if ( count( $variants ) > 1 ) { |
||
1087 | // Gets preferred variant (note that user preference is |
||
1088 | // only possible for wiki content language variant) |
||
1089 | $preferred = $pageLang->getPreferredVariant(); |
||
1090 | if ( Action::getActionName( $this ) === 'view' ) { |
||
1091 | $params = $request->getQueryValues(); |
||
1092 | unset( $params['title'] ); |
||
1093 | } else { |
||
1094 | $params = []; |
||
1095 | } |
||
1096 | // Loops over each variant |
||
1097 | foreach ( $variants as $code ) { |
||
1098 | // Gets variant name from language code |
||
1099 | $varname = $pageLang->getVariantname( $code ); |
||
1100 | // Appends variant link |
||
1101 | $content_navigation['variants'][] = [ |
||
1102 | 'class' => ( $code == $preferred ) ? 'selected' : false, |
||
1103 | 'text' => $varname, |
||
1104 | 'href' => $title->getLocalURL( [ 'variant' => $code ] + $params ), |
||
1105 | 'lang' => wfBCP47( $code ), |
||
1106 | 'hreflang' => wfBCP47( $code ), |
||
1107 | ]; |
||
1108 | } |
||
1109 | } |
||
1110 | } |
||
1111 | } else { |
||
1112 | // If it's not content, it's got to be a special page |
||
1113 | $content_navigation['namespaces']['special'] = [ |
||
1114 | 'class' => 'selected', |
||
1115 | 'text' => $this->msg( 'nstab-special' )->text(), |
||
1116 | 'href' => $request->getRequestURL(), // @see: bug 2457, bug 2510 |
||
1117 | 'context' => 'subject' |
||
1118 | ]; |
||
1119 | |||
1120 | Hooks::run( 'SkinTemplateNavigation::SpecialPage', |
||
1121 | [ &$this, &$content_navigation ] ); |
||
1122 | } |
||
1123 | |||
1124 | // Equiv to SkinTemplateContentActions |
||
1125 | Hooks::run( 'SkinTemplateNavigation::Universal', [ &$this, &$content_navigation ] ); |
||
1126 | |||
1127 | // Setup xml ids and tooltip info |
||
1128 | foreach ( $content_navigation as $section => &$links ) { |
||
1129 | foreach ( $links as $key => &$link ) { |
||
1130 | $xmlID = $key; |
||
1131 | if ( isset( $link['context'] ) && $link['context'] == 'subject' ) { |
||
1132 | $xmlID = 'ca-nstab-' . $xmlID; |
||
1133 | } elseif ( isset( $link['context'] ) && $link['context'] == 'talk' ) { |
||
1134 | $xmlID = 'ca-talk'; |
||
1135 | $link['rel'] = 'discussion'; |
||
1136 | } elseif ( $section == 'variants' ) { |
||
1137 | $xmlID = 'ca-varlang-' . $xmlID; |
||
1138 | } else { |
||
1139 | $xmlID = 'ca-' . $xmlID; |
||
1140 | } |
||
1141 | $link['id'] = $xmlID; |
||
1142 | } |
||
1143 | } |
||
1144 | |||
1145 | # We don't want to give the watch tab an accesskey if the |
||
1146 | # page is being edited, because that conflicts with the |
||
1147 | # accesskey on the watch checkbox. We also don't want to |
||
1148 | # give the edit tab an accesskey, because that's fairly |
||
1149 | # superfluous and conflicts with an accesskey (Ctrl-E) often |
||
1150 | # used for editing in Safari. |
||
1151 | if ( in_array( $action, [ 'edit', 'submit' ] ) ) { |
||
1152 | if ( isset( $content_navigation['views']['edit'] ) ) { |
||
1153 | $content_navigation['views']['edit']['tooltiponly'] = true; |
||
1154 | } |
||
1155 | View Code Duplication | if ( isset( $content_navigation['actions']['watch'] ) ) { |
|
1156 | $content_navigation['actions']['watch']['tooltiponly'] = true; |
||
1157 | } |
||
1158 | View Code Duplication | if ( isset( $content_navigation['actions']['unwatch'] ) ) { |
|
1159 | $content_navigation['actions']['unwatch']['tooltiponly'] = true; |
||
1160 | } |
||
1161 | } |
||
1162 | |||
1163 | return $content_navigation; |
||
1164 | } |
||
1165 | |||
1166 | /** |
||
1167 | * an array of edit links by default used for the tabs |
||
1168 | * @param array $content_navigation |
||
1169 | * @return array |
||
1170 | */ |
||
1171 | private function buildContentActionUrls( $content_navigation ) { |
||
1172 | // content_actions has been replaced with content_navigation for backwards |
||
1173 | // compatibility and also for skins that just want simple tabs content_actions |
||
1174 | // is now built by flattening the content_navigation arrays into one |
||
1175 | |||
1176 | $content_actions = []; |
||
1177 | |||
1178 | foreach ( $content_navigation as $links ) { |
||
1179 | foreach ( $links as $key => $value ) { |
||
1180 | if ( isset( $value['redundant'] ) && $value['redundant'] ) { |
||
1181 | // Redundant tabs are dropped from content_actions |
||
1182 | continue; |
||
1183 | } |
||
1184 | |||
1185 | // content_actions used to have ids built using the "ca-$key" pattern |
||
1186 | // so the xmlID based id is much closer to the actual $key that we want |
||
1187 | // for that reason we'll just strip out the ca- if present and use |
||
1188 | // the latter potion of the "id" as the $key |
||
1189 | if ( isset( $value['id'] ) && substr( $value['id'], 0, 3 ) == 'ca-' ) { |
||
1190 | $key = substr( $value['id'], 3 ); |
||
1191 | } |
||
1192 | |||
1193 | if ( isset( $content_actions[$key] ) ) { |
||
1194 | wfDebug( __METHOD__ . ": Found a duplicate key for $key while flattening " . |
||
1195 | "content_navigation into content_actions.\n" ); |
||
1196 | continue; |
||
1197 | } |
||
1198 | |||
1199 | $content_actions[$key] = $value; |
||
1200 | } |
||
1201 | } |
||
1202 | |||
1203 | return $content_actions; |
||
1204 | } |
||
1205 | |||
1206 | /** |
||
1207 | * build array of common navigation links |
||
1208 | * @return array |
||
1209 | */ |
||
1210 | protected function buildNavUrls() { |
||
1211 | global $wgUploadNavigationUrl; |
||
1212 | |||
1213 | $out = $this->getOutput(); |
||
1214 | $request = $this->getRequest(); |
||
1215 | |||
1216 | $nav_urls = []; |
||
1217 | $nav_urls['mainpage'] = [ 'href' => self::makeMainPageUrl() ]; |
||
1218 | if ( $wgUploadNavigationUrl ) { |
||
1219 | $nav_urls['upload'] = [ 'href' => $wgUploadNavigationUrl ]; |
||
1220 | } elseif ( UploadBase::isEnabled() && UploadBase::isAllowed( $this->getUser() ) === true ) { |
||
1221 | $nav_urls['upload'] = [ 'href' => self::makeSpecialUrl( 'Upload' ) ]; |
||
1222 | } else { |
||
1223 | $nav_urls['upload'] = false; |
||
1224 | } |
||
1225 | $nav_urls['specialpages'] = [ 'href' => self::makeSpecialUrl( 'Specialpages' ) ]; |
||
1226 | |||
1227 | $nav_urls['print'] = false; |
||
1228 | $nav_urls['permalink'] = false; |
||
1229 | $nav_urls['info'] = false; |
||
1230 | $nav_urls['whatlinkshere'] = false; |
||
1231 | $nav_urls['recentchangeslinked'] = false; |
||
1232 | $nav_urls['contributions'] = false; |
||
1233 | $nav_urls['log'] = false; |
||
1234 | $nav_urls['blockip'] = false; |
||
1235 | $nav_urls['emailuser'] = false; |
||
1236 | $nav_urls['userrights'] = false; |
||
1237 | |||
1238 | // A print stylesheet is attached to all pages, but nobody ever |
||
1239 | // figures that out. :) Add a link... |
||
1240 | if ( !$out->isPrintable() && ( $out->isArticle() || $this->getTitle()->isSpecialPage() ) ) { |
||
1241 | $nav_urls['print'] = [ |
||
1242 | 'text' => $this->msg( 'printableversion' )->text(), |
||
1243 | 'href' => $this->getTitle()->getLocalURL( |
||
1244 | $request->appendQueryValue( 'printable', 'yes' ) ) |
||
1245 | ]; |
||
1246 | } |
||
1247 | |||
1248 | if ( $out->isArticle() ) { |
||
1249 | // Also add a "permalink" while we're at it |
||
1250 | $revid = $this->getRevisionId(); |
||
1251 | if ( $revid ) { |
||
1252 | $nav_urls['permalink'] = [ |
||
1253 | 'text' => $this->msg( 'permalink' )->text(), |
||
1254 | 'href' => $this->getTitle()->getLocalURL( "oldid=$revid" ) |
||
1255 | ]; |
||
1256 | } |
||
1257 | |||
1258 | // Use the copy of revision ID in case this undocumented, shady hook tries to mess with internals |
||
1259 | Hooks::run( 'SkinTemplateBuildNavUrlsNav_urlsAfterPermalink', |
||
1260 | [ &$this, &$nav_urls, &$revid, &$revid ] ); |
||
1261 | } |
||
1262 | |||
1263 | if ( $out->isArticleRelated() ) { |
||
1264 | $nav_urls['whatlinkshere'] = [ |
||
1265 | 'href' => SpecialPage::getTitleFor( 'Whatlinkshere', $this->thispage )->getLocalURL() |
||
1266 | ]; |
||
1267 | |||
1268 | $nav_urls['info'] = [ |
||
1269 | 'text' => $this->msg( 'pageinfo-toolboxlink' )->text(), |
||
1270 | 'href' => $this->getTitle()->getLocalURL( "action=info" ) |
||
1271 | ]; |
||
1272 | |||
1273 | if ( $this->getTitle()->exists() ) { |
||
1274 | $nav_urls['recentchangeslinked'] = [ |
||
1275 | 'href' => SpecialPage::getTitleFor( 'Recentchangeslinked', $this->thispage )->getLocalURL() |
||
1276 | ]; |
||
1277 | } |
||
1278 | } |
||
1279 | |||
1280 | $user = $this->getRelevantUser(); |
||
1281 | if ( $user ) { |
||
1282 | $rootUser = $user->getName(); |
||
1283 | |||
1284 | $nav_urls['contributions'] = [ |
||
1285 | 'text' => $this->msg( 'contributions', $rootUser )->text(), |
||
1286 | 'href' => self::makeSpecialUrlSubpage( 'Contributions', $rootUser ), |
||
1287 | 'tooltip-params' => [ $rootUser ], |
||
1288 | ]; |
||
1289 | |||
1290 | $nav_urls['log'] = [ |
||
1291 | 'href' => self::makeSpecialUrlSubpage( 'Log', $rootUser ) |
||
1292 | ]; |
||
1293 | |||
1294 | if ( $this->getUser()->isAllowed( 'block' ) ) { |
||
1295 | $nav_urls['blockip'] = [ |
||
1296 | 'text' => $this->msg( 'blockip', $rootUser )->text(), |
||
1297 | 'href' => self::makeSpecialUrlSubpage( 'Block', $rootUser ) |
||
1298 | ]; |
||
1299 | } |
||
1300 | |||
1301 | if ( $this->showEmailUser( $user ) ) { |
||
1302 | $nav_urls['emailuser'] = [ |
||
1303 | 'text' => $this->msg( 'tool-link-emailuser', $rootUser )->text(), |
||
1304 | 'href' => self::makeSpecialUrlSubpage( 'Emailuser', $rootUser ), |
||
1305 | 'tooltip-params' => [ $rootUser ], |
||
1306 | ]; |
||
1307 | } |
||
1308 | |||
1309 | if ( !$user->isAnon() ) { |
||
1310 | $sur = new UserrightsPage; |
||
1311 | $sur->setContext( $this->getContext() ); |
||
1312 | if ( $sur->userCanExecute( $this->getUser() ) ) { |
||
1313 | $nav_urls['userrights'] = [ |
||
1314 | 'text' => $this->msg( 'tool-link-userrights', $this->getUser()->getName() )->text(), |
||
1315 | 'href' => self::makeSpecialUrlSubpage( 'Userrights', $rootUser ) |
||
1316 | ]; |
||
1317 | } |
||
1318 | } |
||
1319 | } |
||
1320 | |||
1321 | return $nav_urls; |
||
1322 | } |
||
1323 | |||
1324 | /** |
||
1325 | * Generate strings used for xml 'id' names |
||
1326 | * @return string |
||
1327 | */ |
||
1328 | protected function getNameSpaceKey() { |
||
1329 | return $this->getTitle()->getNamespaceKey(); |
||
1330 | } |
||
1331 | } |
||
1332 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the parent class: