PageRenderTime 51ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/Skin.php

https://bitbucket.org/ghostfreeman/freeside-wiki
PHP | 1551 lines | 865 code | 205 blank | 481 comment | 176 complexity | 02da87fe1eda9a2229a811dd0f82f371 MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, LGPL-3.0
  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. * @defgroup Skins Skins
  24. */
  25. /**
  26. * The main skin class that provide methods and properties for all other skins.
  27. * This base class is also the "Standard" skin.
  28. *
  29. * See docs/skin.txt for more information.
  30. *
  31. * @ingroup Skins
  32. */
  33. abstract class Skin extends ContextSource {
  34. protected $skinname = 'standard';
  35. protected $mRelevantTitle = null;
  36. protected $mRelevantUser = null;
  37. /**
  38. * Fetch the set of available skins.
  39. * @return array associative array of strings
  40. */
  41. static function getSkinNames() {
  42. global $wgValidSkinNames;
  43. static $skinsInitialised = false;
  44. if ( !$skinsInitialised || !count( $wgValidSkinNames ) ) {
  45. # Get a list of available skins
  46. # Build using the regular expression '^(.*).php$'
  47. # Array keys are all lower case, array value keep the case used by filename
  48. #
  49. wfProfileIn( __METHOD__ . '-init' );
  50. global $wgStyleDirectory;
  51. $skinDir = dir( $wgStyleDirectory );
  52. # while code from www.php.net
  53. while ( false !== ( $file = $skinDir->read() ) ) {
  54. // Skip non-PHP files, hidden files, and '.dep' includes
  55. $matches = array();
  56. if ( preg_match( '/^([^.]*)\.php$/', $file, $matches ) ) {
  57. $aSkin = $matches[1];
  58. $wgValidSkinNames[strtolower( $aSkin )] = $aSkin;
  59. }
  60. }
  61. $skinDir->close();
  62. $skinsInitialised = true;
  63. wfProfileOut( __METHOD__ . '-init' );
  64. }
  65. return $wgValidSkinNames;
  66. }
  67. /**
  68. * Fetch the skinname messages for available skins.
  69. * @return array of strings
  70. */
  71. static function getSkinNameMessages() {
  72. $messages = array();
  73. foreach( self::getSkinNames() as $skinKey => $skinName ) {
  74. $messages[] = "skinname-$skinKey";
  75. }
  76. return $messages;
  77. }
  78. /**
  79. * Fetch the list of usable skins in regards to $wgSkipSkins.
  80. * Useful for Special:Preferences and other places where you
  81. * only want to show skins users _can_ use.
  82. * @return array of strings
  83. */
  84. public static function getUsableSkins() {
  85. global $wgSkipSkins;
  86. $usableSkins = self::getSkinNames();
  87. foreach ( $wgSkipSkins as $skip ) {
  88. unset( $usableSkins[$skip] );
  89. }
  90. return $usableSkins;
  91. }
  92. /**
  93. * Normalize a skin preference value to a form that can be loaded.
  94. * If a skin can't be found, it will fall back to the configured
  95. * default (or the old 'Classic' skin if that's broken).
  96. * @param $key String: 'monobook', 'standard', etc.
  97. * @return string
  98. */
  99. static function normalizeKey( $key ) {
  100. global $wgDefaultSkin;
  101. $skinNames = Skin::getSkinNames();
  102. if ( $key == '' || $key == 'default' ) {
  103. // Don't return the default immediately;
  104. // in a misconfiguration we need to fall back.
  105. $key = $wgDefaultSkin;
  106. }
  107. if ( isset( $skinNames[$key] ) ) {
  108. return $key;
  109. }
  110. // Older versions of the software used a numeric setting
  111. // in the user preferences.
  112. $fallback = array(
  113. 0 => $wgDefaultSkin,
  114. 1 => 'nostalgia',
  115. 2 => 'cologneblue'
  116. );
  117. if ( isset( $fallback[$key] ) ) {
  118. $key = $fallback[$key];
  119. }
  120. if ( isset( $skinNames[$key] ) ) {
  121. return $key;
  122. } elseif ( isset( $skinNames[$wgDefaultSkin] ) ) {
  123. return $wgDefaultSkin;
  124. } else {
  125. return 'vector';
  126. }
  127. }
  128. /**
  129. * Factory method for loading a skin of a given type
  130. * @param $key String: 'monobook', 'standard', etc.
  131. * @return Skin
  132. */
  133. static function &newFromKey( $key ) {
  134. global $wgStyleDirectory;
  135. $key = Skin::normalizeKey( $key );
  136. $skinNames = Skin::getSkinNames();
  137. $skinName = $skinNames[$key];
  138. $className = "Skin{$skinName}";
  139. # Grab the skin class and initialise it.
  140. if ( !MWInit::classExists( $className ) ) {
  141. if ( !defined( 'MW_COMPILED' ) ) {
  142. require_once( "{$wgStyleDirectory}/{$skinName}.php" );
  143. }
  144. # Check if we got if not failback to default skin
  145. if ( !MWInit::classExists( $className ) ) {
  146. # DO NOT die if the class isn't found. This breaks maintenance
  147. # scripts and can cause a user account to be unrecoverable
  148. # except by SQL manipulation if a previously valid skin name
  149. # is no longer valid.
  150. wfDebug( "Skin class does not exist: $className\n" );
  151. $className = 'SkinVector';
  152. if ( !defined( 'MW_COMPILED' ) ) {
  153. require_once( "{$wgStyleDirectory}/Vector.php" );
  154. }
  155. }
  156. }
  157. $skin = new $className( $key );
  158. return $skin;
  159. }
  160. /** @return string skin name */
  161. public function getSkinName() {
  162. return $this->skinname;
  163. }
  164. /**
  165. * @param $out OutputPage
  166. */
  167. function initPage( OutputPage $out ) {
  168. wfProfileIn( __METHOD__ );
  169. $this->preloadExistence();
  170. wfProfileOut( __METHOD__ );
  171. }
  172. /**
  173. * Preload the existence of three commonly-requested pages in a single query
  174. */
  175. function preloadExistence() {
  176. $user = $this->getUser();
  177. // User/talk link
  178. $titles = array( $user->getUserPage(), $user->getTalkPage() );
  179. // Other tab link
  180. if ( $this->getTitle()->isSpecialPage() ) {
  181. // nothing
  182. } elseif ( $this->getTitle()->isTalkPage() ) {
  183. $titles[] = $this->getTitle()->getSubjectPage();
  184. } else {
  185. $titles[] = $this->getTitle()->getTalkPage();
  186. }
  187. $lb = new LinkBatch( $titles );
  188. $lb->setCaller( __METHOD__ );
  189. $lb->execute();
  190. }
  191. /**
  192. * Get the current revision ID
  193. *
  194. * @return Integer
  195. */
  196. public function getRevisionId() {
  197. return $this->getOutput()->getRevisionId();
  198. }
  199. /**
  200. * Whether the revision displayed is the latest revision of the page
  201. *
  202. * @return Boolean
  203. */
  204. public function isRevisionCurrent() {
  205. $revID = $this->getRevisionId();
  206. return $revID == 0 || $revID == $this->getTitle()->getLatestRevID();
  207. }
  208. /**
  209. * Set the "relevant" title
  210. * @see self::getRelevantTitle()
  211. * @param $t Title object to use
  212. */
  213. public function setRelevantTitle( $t ) {
  214. $this->mRelevantTitle = $t;
  215. }
  216. /**
  217. * Return the "relevant" title.
  218. * A "relevant" title is not necessarily the actual title of the page.
  219. * Special pages like Special:MovePage use set the page they are acting on
  220. * as their "relevant" title, this allows the skin system to display things
  221. * such as content tabs which belong to to that page instead of displaying
  222. * a basic special page tab which has almost no meaning.
  223. *
  224. * @return Title
  225. */
  226. public function getRelevantTitle() {
  227. if ( isset($this->mRelevantTitle) ) {
  228. return $this->mRelevantTitle;
  229. }
  230. return $this->getTitle();
  231. }
  232. /**
  233. * Set the "relevant" user
  234. * @see self::getRelevantUser()
  235. * @param $u User object to use
  236. */
  237. public function setRelevantUser( $u ) {
  238. $this->mRelevantUser = $u;
  239. }
  240. /**
  241. * Return the "relevant" user.
  242. * A "relevant" user is similar to a relevant title. Special pages like
  243. * Special:Contributions mark the user which they are relevant to so that
  244. * things like the toolbox can display the information they usually are only
  245. * able to display on a user's userpage and talkpage.
  246. * @return User
  247. */
  248. public function getRelevantUser() {
  249. if ( isset($this->mRelevantUser) ) {
  250. return $this->mRelevantUser;
  251. }
  252. $title = $this->getRelevantTitle();
  253. if( $title->getNamespace() == NS_USER || $title->getNamespace() == NS_USER_TALK ) {
  254. $rootUser = strtok( $title->getText(), '/' );
  255. if ( User::isIP( $rootUser ) ) {
  256. $this->mRelevantUser = User::newFromName( $rootUser, false );
  257. } else {
  258. $user = User::newFromName( $rootUser, false );
  259. if ( $user && $user->isLoggedIn() ) {
  260. $this->mRelevantUser = $user;
  261. }
  262. }
  263. return $this->mRelevantUser;
  264. }
  265. return null;
  266. }
  267. /**
  268. * Outputs the HTML generated by other functions.
  269. * @param $out OutputPage
  270. */
  271. abstract function outputPage( OutputPage $out = null );
  272. /**
  273. * @param $data array
  274. * @return string
  275. */
  276. static function makeVariablesScript( $data ) {
  277. if ( $data ) {
  278. return Html::inlineScript(
  279. ResourceLoader::makeLoaderConditionalScript( ResourceLoader::makeConfigSetScript( $data ) )
  280. );
  281. } else {
  282. return '';
  283. }
  284. }
  285. /**
  286. * Make a "<script>" tag containing global variables
  287. *
  288. * @deprecated in 1.19
  289. * @param $unused
  290. * @return string HTML fragment
  291. */
  292. public static function makeGlobalVariablesScript( $unused ) {
  293. global $wgOut;
  294. wfDeprecated( __METHOD__, '1.19' );
  295. return self::makeVariablesScript( $wgOut->getJSVars() );
  296. }
  297. /**
  298. * Get the query to generate a dynamic stylesheet
  299. *
  300. * @return array
  301. */
  302. public static function getDynamicStylesheetQuery() {
  303. global $wgSquidMaxage;
  304. return array(
  305. 'action' => 'raw',
  306. 'maxage' => $wgSquidMaxage,
  307. 'usemsgcache' => 'yes',
  308. 'ctype' => 'text/css',
  309. 'smaxage' => $wgSquidMaxage,
  310. );
  311. }
  312. /**
  313. * Add skin specific stylesheets
  314. * Calling this method with an $out of anything but the same OutputPage
  315. * inside ->getOutput() is deprecated. The $out arg is kept
  316. * for compatibility purposes with skins.
  317. * @param $out OutputPage
  318. * @todo delete
  319. */
  320. abstract function setupSkinUserCss( OutputPage $out );
  321. /**
  322. * TODO: document
  323. * @param $title Title
  324. * @return String
  325. */
  326. function getPageClasses( $title ) {
  327. $numeric = 'ns-' . $title->getNamespace();
  328. if ( $title->isSpecialPage() ) {
  329. $type = 'ns-special';
  330. // bug 23315: provide a class based on the canonical special page name without subpages
  331. list( $canonicalName ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
  332. if ( $canonicalName ) {
  333. $type .= ' ' . Sanitizer::escapeClass( "mw-special-$canonicalName" );
  334. } else {
  335. $type .= ' mw-invalidspecialpage';
  336. }
  337. } elseif ( $title->isTalkPage() ) {
  338. $type = 'ns-talk';
  339. } else {
  340. $type = 'ns-subject';
  341. }
  342. $name = Sanitizer::escapeClass( 'page-' . $title->getPrefixedText() );
  343. return "$numeric $type $name";
  344. }
  345. /**
  346. * This will be called by OutputPage::headElement when it is creating the
  347. * "<body>" tag, skins can override it if they have a need to add in any
  348. * body attributes or classes of their own.
  349. * @param $out OutputPage
  350. * @param $bodyAttrs Array
  351. */
  352. function addToBodyAttributes( $out, &$bodyAttrs ) {
  353. // does nothing by default
  354. }
  355. /**
  356. * URL to the logo
  357. * @return String
  358. */
  359. function getLogo() {
  360. global $wgLogo;
  361. return $wgLogo;
  362. }
  363. /**
  364. * @return string
  365. */
  366. function getCategoryLinks() {
  367. global $wgUseCategoryBrowser;
  368. $out = $this->getOutput();
  369. $allCats = $out->getCategoryLinks();
  370. if ( !count( $allCats ) ) {
  371. return '';
  372. }
  373. $embed = "<li>";
  374. $pop = "</li>";
  375. $s = '';
  376. $colon = $this->msg( 'colon-separator' )->escaped();
  377. if ( !empty( $allCats['normal'] ) ) {
  378. $t = $embed . implode( "{$pop}{$embed}" , $allCats['normal'] ) . $pop;
  379. $msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
  380. $linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
  381. $s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
  382. Linker::link( Title::newFromText( $linkPage ), $msg )
  383. . $colon . '<ul>' . $t . '</ul>' . '</div>';
  384. }
  385. # Hidden categories
  386. if ( isset( $allCats['hidden'] ) ) {
  387. if ( $this->getUser()->getBoolOption( 'showhiddencats' ) ) {
  388. $class = ' mw-hidden-cats-user-shown';
  389. } elseif ( $this->getTitle()->getNamespace() == NS_CATEGORY ) {
  390. $class = ' mw-hidden-cats-ns-shown';
  391. } else {
  392. $class = ' mw-hidden-cats-hidden';
  393. }
  394. $s .= "<div id=\"mw-hidden-catlinks\" class=\"mw-hidden-catlinks$class\">" .
  395. $this->msg( 'hidden-categories' )->numParams( count( $allCats['hidden'] ) )->escaped() .
  396. $colon . '<ul>' . $embed . implode( "{$pop}{$embed}" , $allCats['hidden'] ) . $pop . '</ul>' .
  397. '</div>';
  398. }
  399. # optional 'dmoz-like' category browser. Will be shown under the list
  400. # of categories an article belong to
  401. if ( $wgUseCategoryBrowser ) {
  402. $s .= '<br /><hr />';
  403. # get a big array of the parents tree
  404. $parenttree = $this->getTitle()->getParentCategoryTree();
  405. # Skin object passed by reference cause it can not be
  406. # accessed under the method subfunction drawCategoryBrowser
  407. $tempout = explode( "\n", $this->drawCategoryBrowser( $parenttree ) );
  408. # Clean out bogus first entry and sort them
  409. unset( $tempout[0] );
  410. asort( $tempout );
  411. # Output one per line
  412. $s .= implode( "<br />\n", $tempout );
  413. }
  414. return $s;
  415. }
  416. /**
  417. * Render the array as a serie of links.
  418. * @param $tree Array: categories tree returned by Title::getParentCategoryTree
  419. * @return String separated by &gt;, terminate with "\n"
  420. */
  421. function drawCategoryBrowser( $tree ) {
  422. $return = '';
  423. foreach ( $tree as $element => $parent ) {
  424. if ( empty( $parent ) ) {
  425. # element start a new list
  426. $return .= "\n";
  427. } else {
  428. # grab the others elements
  429. $return .= $this->drawCategoryBrowser( $parent ) . ' &gt; ';
  430. }
  431. # add our current element to the list
  432. $eltitle = Title::newFromText( $element );
  433. $return .= Linker::link( $eltitle, htmlspecialchars( $eltitle->getText() ) );
  434. }
  435. return $return;
  436. }
  437. /**
  438. * @return string
  439. */
  440. function getCategories() {
  441. $out = $this->getOutput();
  442. $catlinks = $this->getCategoryLinks();
  443. $classes = 'catlinks';
  444. // Check what we're showing
  445. $allCats = $out->getCategoryLinks();
  446. $showHidden = $this->getUser()->getBoolOption( 'showhiddencats' ) ||
  447. $this->getTitle()->getNamespace() == NS_CATEGORY;
  448. if ( empty( $allCats['normal'] ) && !( !empty( $allCats['hidden'] ) && $showHidden ) ) {
  449. $classes .= ' catlinks-allhidden';
  450. }
  451. return "<div id='catlinks' class='$classes'>{$catlinks}</div>";
  452. }
  453. /**
  454. * This runs a hook to allow extensions placing their stuff after content
  455. * and article metadata (e.g. categories).
  456. * Note: This function has nothing to do with afterContent().
  457. *
  458. * This hook is placed here in order to allow using the same hook for all
  459. * skins, both the SkinTemplate based ones and the older ones, which directly
  460. * use this class to get their data.
  461. *
  462. * The output of this function gets processed in SkinTemplate::outputPage() for
  463. * the SkinTemplate based skins, all other skins should directly echo it.
  464. *
  465. * @return String, empty by default, if not changed by any hook function.
  466. */
  467. protected function afterContentHook() {
  468. $data = '';
  469. if ( wfRunHooks( 'SkinAfterContent', array( &$data, $this ) ) ) {
  470. // adding just some spaces shouldn't toggle the output
  471. // of the whole <div/>, so we use trim() here
  472. if ( trim( $data ) != '' ) {
  473. // Doing this here instead of in the skins to
  474. // ensure that the div has the same ID in all
  475. // skins
  476. $data = "<div id='mw-data-after-content'>\n" .
  477. "\t$data\n" .
  478. "</div>\n";
  479. }
  480. } else {
  481. wfDebug( "Hook SkinAfterContent changed output processing.\n" );
  482. }
  483. return $data;
  484. }
  485. /**
  486. * Generate debug data HTML for displaying at the bottom of the main content
  487. * area.
  488. * @return String HTML containing debug data, if enabled (otherwise empty).
  489. */
  490. protected function generateDebugHTML() {
  491. return MWDebug::getHTMLDebugLog();
  492. }
  493. /**
  494. * This gets called shortly before the "</body>" tag.
  495. *
  496. * @return String HTML-wrapped JS code to be put before "</body>"
  497. */
  498. function bottomScripts() {
  499. // TODO and the suckage continues. This function is really just a wrapper around
  500. // OutputPage::getBottomScripts() which takes a Skin param. This should be cleaned
  501. // up at some point
  502. $bottomScriptText = $this->getOutput()->getBottomScripts();
  503. wfRunHooks( 'SkinAfterBottomScripts', array( $this, &$bottomScriptText ) );
  504. return $bottomScriptText;
  505. }
  506. /**
  507. * Text with the permalink to the source page,
  508. * usually shown on the footer of a printed page
  509. *
  510. * @return string HTML text with an URL
  511. */
  512. function printSource() {
  513. $oldid = $this->getRevisionId();
  514. if ( $oldid ) {
  515. $url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL( 'oldid=' . $oldid ) ) );
  516. } else {
  517. // oldid not available for non existing pages
  518. $url = htmlspecialchars( wfExpandIRI( $this->getTitle()->getCanonicalURL() ) );
  519. }
  520. return $this->msg( 'retrievedfrom', '<a href="' . $url . '">' . $url . '</a>' )->text();
  521. }
  522. /**
  523. * @return String
  524. */
  525. function getUndeleteLink() {
  526. $action = $this->getRequest()->getVal( 'action', 'view' );
  527. if ( $this->getUser()->isAllowed( 'deletedhistory' ) &&
  528. ( $this->getTitle()->getArticleID() == 0 || $action == 'history' ) ) {
  529. $n = $this->getTitle()->isDeleted();
  530. if ( $n ) {
  531. if ( $this->getUser()->isAllowed( 'undelete' ) ) {
  532. $msg = 'thisisdeleted';
  533. } else {
  534. $msg = 'viewdeleted';
  535. }
  536. return $this->msg( $msg )->rawParams(
  537. Linker::linkKnown(
  538. SpecialPage::getTitleFor( 'Undelete', $this->getTitle()->getPrefixedDBkey() ),
  539. $this->msg( 'restorelink' )->numParams( $n )->escaped() )
  540. )->text();
  541. }
  542. }
  543. return '';
  544. }
  545. /**
  546. * @return string
  547. */
  548. function subPageSubtitle() {
  549. global $wgLang;
  550. $out = $this->getOutput();
  551. $subpages = '';
  552. if ( !wfRunHooks( 'SkinSubPageSubtitle', array( &$subpages, $this, $out ) ) ) {
  553. return $subpages;
  554. }
  555. if ( $out->isArticle() && MWNamespace::hasSubpages( $out->getTitle()->getNamespace() ) ) {
  556. $ptext = $this->getTitle()->getPrefixedText();
  557. if ( preg_match( '/\//', $ptext ) ) {
  558. $links = explode( '/', $ptext );
  559. array_pop( $links );
  560. $c = 0;
  561. $growinglink = '';
  562. $display = '';
  563. foreach ( $links as $link ) {
  564. $growinglink .= $link;
  565. $display .= $link;
  566. $linkObj = Title::newFromText( $growinglink );
  567. if ( is_object( $linkObj ) && $linkObj->isKnown() ) {
  568. $getlink = Linker::linkKnown(
  569. $linkObj,
  570. htmlspecialchars( $display )
  571. );
  572. $c++;
  573. if ( $c > 1 ) {
  574. $subpages .= $wgLang->getDirMarkEntity() . $this->msg( 'pipe-separator' )->escaped();
  575. } else {
  576. $subpages .= '&lt; ';
  577. }
  578. $subpages .= $getlink;
  579. $display = '';
  580. } else {
  581. $display .= '/';
  582. }
  583. $growinglink .= '/';
  584. }
  585. }
  586. }
  587. return $subpages;
  588. }
  589. /**
  590. * Returns true if the IP should be shown in the header
  591. * @return Bool
  592. */
  593. function showIPinHeader() {
  594. global $wgShowIPinHeader;
  595. return $wgShowIPinHeader && session_id() != '';
  596. }
  597. /**
  598. * @return String
  599. */
  600. function getSearchLink() {
  601. $searchPage = SpecialPage::getTitleFor( 'Search' );
  602. return $searchPage->getLocalURL();
  603. }
  604. /**
  605. * @return string
  606. */
  607. function escapeSearchLink() {
  608. return htmlspecialchars( $this->getSearchLink() );
  609. }
  610. /**
  611. * @param $type string
  612. * @return string
  613. */
  614. function getCopyright( $type = 'detect' ) {
  615. global $wgRightsPage, $wgRightsUrl, $wgRightsText, $wgContLang;
  616. if ( $type == 'detect' ) {
  617. if ( !$this->isRevisionCurrent() && !$this->msg( 'history_copyright' )->inContentLanguage()->isDisabled() ) {
  618. $type = 'history';
  619. } else {
  620. $type = 'normal';
  621. }
  622. }
  623. if ( $type == 'history' ) {
  624. $msg = 'history_copyright';
  625. } else {
  626. $msg = 'copyright';
  627. }
  628. if ( $wgRightsPage ) {
  629. $title = Title::newFromText( $wgRightsPage );
  630. $link = Linker::linkKnown( $title, $wgRightsText );
  631. } elseif ( $wgRightsUrl ) {
  632. $link = Linker::makeExternalLink( $wgRightsUrl, $wgRightsText );
  633. } elseif ( $wgRightsText ) {
  634. $link = $wgRightsText;
  635. } else {
  636. # Give up now
  637. return '';
  638. }
  639. // Allow for site and per-namespace customization of copyright notice.
  640. $forContent = true;
  641. wfRunHooks( 'SkinCopyrightFooter', array( $this->getTitle(), $type, &$msg, &$link, &$forContent ) );
  642. $msgObj = $this->msg( $msg )->rawParams( $link );
  643. if ( $forContent ) {
  644. $msg = $msgObj->inContentLanguage()->text();
  645. if ( $this->getLanguage()->getCode() !== $wgContLang->getCode() ) {
  646. $msg = Html::rawElement( 'span', array( 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $msg );
  647. }
  648. return $msg;
  649. } else {
  650. return $msgObj->text();
  651. }
  652. }
  653. /**
  654. * @return null|string
  655. */
  656. function getCopyrightIcon() {
  657. global $wgRightsUrl, $wgRightsText, $wgRightsIcon, $wgCopyrightIcon;
  658. $out = '';
  659. if ( isset( $wgCopyrightIcon ) && $wgCopyrightIcon ) {
  660. $out = $wgCopyrightIcon;
  661. } elseif ( $wgRightsIcon ) {
  662. $icon = htmlspecialchars( $wgRightsIcon );
  663. if ( $wgRightsUrl ) {
  664. $url = htmlspecialchars( $wgRightsUrl );
  665. $out .= '<a href="' . $url . '">';
  666. }
  667. $text = htmlspecialchars( $wgRightsText );
  668. $out .= "<img src=\"$icon\" alt=\"$text\" width=\"88\" height=\"31\" />";
  669. if ( $wgRightsUrl ) {
  670. $out .= '</a>';
  671. }
  672. }
  673. return $out;
  674. }
  675. /**
  676. * Gets the powered by MediaWiki icon.
  677. * @return string
  678. */
  679. function getPoweredBy() {
  680. global $wgStylePath;
  681. $url = htmlspecialchars( "$wgStylePath/common/images/poweredby_mediawiki_88x31.png" );
  682. $text = '<a href="//www.mediawiki.org/"><img src="' . $url . '" height="31" width="88" alt="Powered by MediaWiki" /></a>';
  683. wfRunHooks( 'SkinGetPoweredBy', array( &$text, $this ) );
  684. return $text;
  685. }
  686. /**
  687. * Get the timestamp of the latest revision, formatted in user language
  688. *
  689. * @return String
  690. */
  691. protected function lastModified() {
  692. $timestamp = $this->getOutput()->getRevisionTimestamp();
  693. # No cached timestamp, load it from the database
  694. if ( $timestamp === null ) {
  695. $timestamp = Revision::getTimestampFromId( $this->getTitle(), $this->getRevisionId() );
  696. }
  697. if ( $timestamp ) {
  698. $d = $this->getLanguage()->userDate( $timestamp, $this->getUser() );
  699. $t = $this->getLanguage()->userTime( $timestamp, $this->getUser() );
  700. $s = ' ' . $this->msg( 'lastmodifiedat', $d, $t )->text();
  701. } else {
  702. $s = '';
  703. }
  704. if ( wfGetLB()->getLaggedSlaveMode() ) {
  705. $s .= ' <strong>' . $this->msg( 'laggedslavemode' )->text() . '</strong>';
  706. }
  707. return $s;
  708. }
  709. /**
  710. * @param $align string
  711. * @return string
  712. */
  713. function logoText( $align = '' ) {
  714. if ( $align != '' ) {
  715. $a = " style='float: {$align};'";
  716. } else {
  717. $a = '';
  718. }
  719. $mp = $this->msg( 'mainpage' )->escaped();
  720. $mptitle = Title::newMainPage();
  721. $url = ( is_object( $mptitle ) ? htmlspecialchars( $mptitle->getLocalURL() ) : '' );
  722. $logourl = $this->getLogo();
  723. $s = "<a href='{$url}'><img{$a} src='{$logourl}' alt='[{$mp}]' /></a>";
  724. return $s;
  725. }
  726. /**
  727. * Renders a $wgFooterIcons icon acording to the method's arguments
  728. * @param $icon Array: The icon to build the html for, see $wgFooterIcons for the format of this array
  729. * @param $withImage Bool|String: Whether to use the icon's image or output a text-only footericon
  730. * @return String HTML
  731. */
  732. function makeFooterIcon( $icon, $withImage = 'withImage' ) {
  733. if ( is_string( $icon ) ) {
  734. $html = $icon;
  735. } else { // Assuming array
  736. $url = isset($icon["url"]) ? $icon["url"] : null;
  737. unset( $icon["url"] );
  738. if ( isset( $icon["src"] ) && $withImage === 'withImage' ) {
  739. $html = Html::element( 'img', $icon ); // do this the lazy way, just pass icon data as an attribute array
  740. } else {
  741. $html = htmlspecialchars( $icon["alt"] );
  742. }
  743. if ( $url ) {
  744. $html = Html::rawElement( 'a', array( "href" => $url ), $html );
  745. }
  746. }
  747. return $html;
  748. }
  749. /**
  750. * Gets the link to the wiki's main page.
  751. * @return string
  752. */
  753. function mainPageLink() {
  754. $s = Linker::linkKnown(
  755. Title::newMainPage(),
  756. $this->msg( 'mainpage' )->escaped()
  757. );
  758. return $s;
  759. }
  760. /**
  761. * @param $desc
  762. * @param $page
  763. * @return string
  764. */
  765. public function footerLink( $desc, $page ) {
  766. // if the link description has been set to "-" in the default language,
  767. if ( $this->msg( $desc )->inContentLanguage()->isDisabled() ) {
  768. // then it is disabled, for all languages.
  769. return '';
  770. } else {
  771. // Otherwise, we display the link for the user, described in their
  772. // language (which may or may not be the same as the default language),
  773. // but we make the link target be the one site-wide page.
  774. $title = Title::newFromText( $this->msg( $page )->inContentLanguage()->text() );
  775. return Linker::linkKnown(
  776. $title,
  777. $this->msg( $desc )->escaped()
  778. );
  779. }
  780. }
  781. /**
  782. * Gets the link to the wiki's privacy policy page.
  783. * @return String HTML
  784. */
  785. function privacyLink() {
  786. return $this->footerLink( 'privacy', 'privacypage' );
  787. }
  788. /**
  789. * Gets the link to the wiki's about page.
  790. * @return String HTML
  791. */
  792. function aboutLink() {
  793. return $this->footerLink( 'aboutsite', 'aboutpage' );
  794. }
  795. /**
  796. * Gets the link to the wiki's general disclaimers page.
  797. * @return String HTML
  798. */
  799. function disclaimerLink() {
  800. return $this->footerLink( 'disclaimers', 'disclaimerpage' );
  801. }
  802. /**
  803. * Return URL options for the 'edit page' link.
  804. * This may include an 'oldid' specifier, if the current page view is such.
  805. *
  806. * @return array
  807. * @private
  808. */
  809. function editUrlOptions() {
  810. $options = array( 'action' => 'edit' );
  811. if ( !$this->isRevisionCurrent() ) {
  812. $options['oldid'] = intval( $this->getRevisionId() );
  813. }
  814. return $options;
  815. }
  816. /**
  817. * @param $id User|int
  818. * @return bool
  819. */
  820. function showEmailUser( $id ) {
  821. if ( $id instanceof User ) {
  822. $targetUser = $id;
  823. } else {
  824. $targetUser = User::newFromId( $id );
  825. }
  826. return $this->getUser()->canSendEmail() && # the sending user must have a confirmed email address
  827. $targetUser->canReceiveEmail(); # the target user must have a confirmed email address and allow emails from users
  828. }
  829. /**
  830. * Return a fully resolved style path url to images or styles stored in the common folder.
  831. * This method returns a url resolved using the configured skin style path
  832. * and includes the style version inside of the url.
  833. * @param $name String: The name or path of a skin resource file
  834. * @return String The fully resolved style path url including styleversion
  835. */
  836. function getCommonStylePath( $name ) {
  837. global $wgStylePath, $wgStyleVersion;
  838. return "$wgStylePath/common/$name?$wgStyleVersion";
  839. }
  840. /**
  841. * Return a fully resolved style path url to images or styles stored in the curent skins's folder.
  842. * This method returns a url resolved using the configured skin style path
  843. * and includes the style version inside of the url.
  844. * @param $name String: The name or path of a skin resource file
  845. * @return String The fully resolved style path url including styleversion
  846. */
  847. function getSkinStylePath( $name ) {
  848. global $wgStylePath, $wgStyleVersion;
  849. return "$wgStylePath/{$this->stylename}/$name?$wgStyleVersion";
  850. }
  851. /* these are used extensively in SkinTemplate, but also some other places */
  852. /**
  853. * @param $urlaction string
  854. * @return String
  855. */
  856. static function makeMainPageUrl( $urlaction = '' ) {
  857. $title = Title::newMainPage();
  858. self::checkTitle( $title, '' );
  859. return $title->getLocalURL( $urlaction );
  860. }
  861. /**
  862. * Make a URL for a Special Page using the given query and protocol.
  863. *
  864. * If $proto is set to null, make a local URL. Otherwise, make a full
  865. * URL with the protocol specified.
  866. *
  867. * @param $name string Name of the Special page
  868. * @param $urlaction string Query to append
  869. * @param $proto Protocol to use or null for a local URL
  870. * @return String
  871. */
  872. static function makeSpecialUrl( $name, $urlaction = '', $proto = null ) {
  873. $title = SpecialPage::getSafeTitleFor( $name );
  874. if( is_null( $proto ) ) {
  875. return $title->getLocalURL( $urlaction );
  876. } else {
  877. return $title->getFullURL( $urlaction, false, $proto );
  878. }
  879. }
  880. /**
  881. * @param $name string
  882. * @param $subpage string
  883. * @param $urlaction string
  884. * @return String
  885. */
  886. static function makeSpecialUrlSubpage( $name, $subpage, $urlaction = '' ) {
  887. $title = SpecialPage::getSafeTitleFor( $name, $subpage );
  888. return $title->getLocalURL( $urlaction );
  889. }
  890. /**
  891. * @param $name string
  892. * @param $urlaction string
  893. * @return String
  894. */
  895. static function makeI18nUrl( $name, $urlaction = '' ) {
  896. $title = Title::newFromText( wfMessage( $name )->inContentLanguage()->text() );
  897. self::checkTitle( $title, $name );
  898. return $title->getLocalURL( $urlaction );
  899. }
  900. /**
  901. * @param $name string
  902. * @param $urlaction string
  903. * @return String
  904. */
  905. static function makeUrl( $name, $urlaction = '' ) {
  906. $title = Title::newFromText( $name );
  907. self::checkTitle( $title, $name );
  908. return $title->getLocalURL( $urlaction );
  909. }
  910. /**
  911. * If url string starts with http, consider as external URL, else
  912. * internal
  913. * @param $name String
  914. * @return String URL
  915. */
  916. static function makeInternalOrExternalUrl( $name ) {
  917. if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $name ) ) {
  918. return $name;
  919. } else {
  920. return self::makeUrl( $name );
  921. }
  922. }
  923. /**
  924. * this can be passed the NS number as defined in Language.php
  925. * @param $name
  926. * @param $urlaction string
  927. * @param $namespace int
  928. * @return String
  929. */
  930. static function makeNSUrl( $name, $urlaction = '', $namespace = NS_MAIN ) {
  931. $title = Title::makeTitleSafe( $namespace, $name );
  932. self::checkTitle( $title, $name );
  933. return $title->getLocalURL( $urlaction );
  934. }
  935. /**
  936. * these return an array with the 'href' and boolean 'exists'
  937. * @param $name
  938. * @param $urlaction string
  939. * @return array
  940. */
  941. static function makeUrlDetails( $name, $urlaction = '' ) {
  942. $title = Title::newFromText( $name );
  943. self::checkTitle( $title, $name );
  944. return array(
  945. 'href' => $title->getLocalURL( $urlaction ),
  946. 'exists' => $title->getArticleID() != 0,
  947. );
  948. }
  949. /**
  950. * Make URL details where the article exists (or at least it's convenient to think so)
  951. * @param $name String Article name
  952. * @param $urlaction String
  953. * @return Array
  954. */
  955. static function makeKnownUrlDetails( $name, $urlaction = '' ) {
  956. $title = Title::newFromText( $name );
  957. self::checkTitle( $title, $name );
  958. return array(
  959. 'href' => $title->getLocalURL( $urlaction ),
  960. 'exists' => true
  961. );
  962. }
  963. /**
  964. * make sure we have some title to operate on
  965. *
  966. * @param $title Title
  967. * @param $name string
  968. */
  969. static function checkTitle( &$title, $name ) {
  970. if ( !is_object( $title ) ) {
  971. $title = Title::newFromText( $name );
  972. if ( !is_object( $title ) ) {
  973. $title = Title::newFromText( '--error: link target missing--' );
  974. }
  975. }
  976. }
  977. /**
  978. * Build an array that represents the sidebar(s), the navigation bar among them
  979. *
  980. * @return array
  981. */
  982. function buildSidebar() {
  983. global $wgMemc, $wgEnableSidebarCache, $wgSidebarCacheExpiry;
  984. wfProfileIn( __METHOD__ );
  985. $key = wfMemcKey( 'sidebar', $this->getLanguage()->getCode() );
  986. if ( $wgEnableSidebarCache ) {
  987. $cachedsidebar = $wgMemc->get( $key );
  988. if ( $cachedsidebar ) {
  989. wfProfileOut( __METHOD__ );
  990. return $cachedsidebar;
  991. }
  992. }
  993. $bar = array();
  994. $this->addToSidebar( $bar, 'sidebar' );
  995. wfRunHooks( 'SkinBuildSidebar', array( $this, &$bar ) );
  996. if ( $wgEnableSidebarCache ) {
  997. $wgMemc->set( $key, $bar, $wgSidebarCacheExpiry );
  998. }
  999. wfProfileOut( __METHOD__ );
  1000. return $bar;
  1001. }
  1002. /**
  1003. * Add content from a sidebar system message
  1004. * Currently only used for MediaWiki:Sidebar (but may be used by Extensions)
  1005. *
  1006. * This is just a wrapper around addToSidebarPlain() for backwards compatibility
  1007. *
  1008. * @param $bar array
  1009. * @param $message String
  1010. */
  1011. function addToSidebar( &$bar, $message ) {
  1012. $this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
  1013. }
  1014. /**
  1015. * Add content from plain text
  1016. * @since 1.17
  1017. * @param $bar array
  1018. * @param $text string
  1019. * @return Array
  1020. */
  1021. function addToSidebarPlain( &$bar, $text ) {
  1022. $lines = explode( "\n", $text );
  1023. $heading = '';
  1024. foreach ( $lines as $line ) {
  1025. if ( strpos( $line, '*' ) !== 0 ) {
  1026. continue;
  1027. }
  1028. $line = rtrim( $line, "\r" ); // for Windows compat
  1029. if ( strpos( $line, '**' ) !== 0 ) {
  1030. $heading = trim( $line, '* ' );
  1031. if ( !array_key_exists( $heading, $bar ) ) {
  1032. $bar[$heading] = array();
  1033. }
  1034. } else {
  1035. $line = trim( $line, '* ' );
  1036. if ( strpos( $line, '|' ) !== false ) { // sanity check
  1037. $line = MessageCache::singleton()->transform( $line, false, null, $this->getTitle() );
  1038. $line = array_map( 'trim', explode( '|', $line, 2 ) );
  1039. if ( count( $line ) !== 2 ) {
  1040. // Second sanity check, could be hit by people doing
  1041. // funky stuff with parserfuncs... (bug 33321)
  1042. continue;
  1043. }
  1044. $extraAttribs = array();
  1045. $msgLink = $this->msg( $line[0] )->inContentLanguage();
  1046. if ( $msgLink->exists() ) {
  1047. $link = $msgLink->text();
  1048. if ( $link == '-' ) {
  1049. continue;
  1050. }
  1051. } else {
  1052. $link = $line[0];
  1053. }
  1054. $msgText = $this->msg( $line[1] );
  1055. if ( $msgText->exists() ) {
  1056. $text = $msgText->text();
  1057. } else {
  1058. $text = $line[1];
  1059. }
  1060. if ( preg_match( '/^(?i:' . wfUrlProtocols() . ')/', $link ) ) {
  1061. $href = $link;
  1062. // Parser::getExternalLinkAttribs won't work here because of the Namespace things
  1063. global $wgNoFollowLinks, $wgNoFollowDomainExceptions;
  1064. if ( $wgNoFollowLinks && !wfMatchesDomainList( $href, $wgNoFollowDomainExceptions ) ) {
  1065. $extraAttribs['rel'] = 'nofollow';
  1066. }
  1067. global $wgExternalLinkTarget;
  1068. if ( $wgExternalLinkTarget) {
  1069. $extraAttribs['target'] = $wgExternalLinkTarget;
  1070. }
  1071. } else {
  1072. $title = Title::newFromText( $link );
  1073. if ( $title ) {
  1074. $title = $title->fixSpecialName();
  1075. $href = $title->getLinkURL();
  1076. } else {
  1077. $href = 'INVALID-TITLE';
  1078. }
  1079. }
  1080. $bar[$heading][] = array_merge( array(
  1081. 'text' => $text,
  1082. 'href' => $href,
  1083. 'id' => 'n-' . Sanitizer::escapeId( strtr( $line[1], ' ', '-' ), 'noninitial' ),
  1084. 'active' => false
  1085. ), $extraAttribs );
  1086. } else {
  1087. continue;
  1088. }
  1089. }
  1090. }
  1091. return $bar;
  1092. }
  1093. /**
  1094. * Should we load mediawiki.legacy.wikiprintable? Skins that have their own
  1095. * print stylesheet should override this and return false. (This is an
  1096. * ugly hack to get Monobook to play nicely with OutputPage::headElement().)
  1097. *
  1098. * @return bool
  1099. */
  1100. public function commonPrintStylesheet() {
  1101. return true;
  1102. }
  1103. /**
  1104. * Gets new talk page messages for the current user.
  1105. * @return MediaWiki message or if no new talk page messages, nothing
  1106. */
  1107. function getNewtalks() {
  1108. $out = $this->getOutput();
  1109. $newtalks = $this->getUser()->getNewMessageLinks();
  1110. $ntl = '';
  1111. if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
  1112. $uTalkTitle = $this->getUser()->getTalkPage();
  1113. if ( !$uTalkTitle->equals( $out->getTitle() ) ) {
  1114. $lastSeenRev = isset( $newtalks[0]['rev'] ) ? $newtalks[0]['rev'] : null;
  1115. $nofAuthors = 0;
  1116. if ( $lastSeenRev !== null ) {
  1117. $plural = true; // Default if we have a last seen revision: if unknown, use plural
  1118. $latestRev = Revision::newFromTitle( $uTalkTitle, false, Revision::READ_NORMAL );
  1119. if ( $latestRev !== null ) {
  1120. // Singular if only 1 unseen revision, plural if several unseen revisions.
  1121. $plural = $latestRev->getParentId() !== $lastSeenRev->getId();
  1122. $nofAuthors = $uTalkTitle->countAuthorsBetween(
  1123. $lastSeenRev, $latestRev, 10, 'include_new' );
  1124. }
  1125. } else {
  1126. // Singular if no revision -> diff link will show latest change only in any case
  1127. $plural = false;
  1128. }
  1129. $plural = $plural ? 2 : 1;
  1130. // 2 signifies "more than one revision". We don't know how many, and even if we did,
  1131. // the number of revisions or authors is not necessarily the same as the number of
  1132. // "messages".
  1133. $newMessagesLink = Linker::linkKnown(
  1134. $uTalkTitle,
  1135. $this->msg( 'newmessageslinkplural' )->params( $plural )->escaped(),
  1136. array(),
  1137. array( 'redirect' => 'no' )
  1138. );
  1139. $newMessagesDiffLink = Linker::linkKnown(
  1140. $uTalkTitle,
  1141. $this->msg( 'newmessagesdifflinkplural' )->params( $plural )->escaped(),
  1142. array(),
  1143. $lastSeenRev !== null
  1144. ? array( 'oldid' => $lastSeenRev->getId(), 'diff' => 'cur' )
  1145. : array( 'diff' => 'cur' )
  1146. );
  1147. if ( $nofAuthors >= 1 && $nofAuthors <= 10 ) {
  1148. $ntl = $this->msg(
  1149. 'youhavenewmessagesfromusers',
  1150. $newMessagesLink,
  1151. $newMessagesDiffLink
  1152. )->numParams( $nofAuthors );
  1153. } else {
  1154. // $nofAuthors === 11 signifies "11 or more" ("more than 10")
  1155. $ntl = $this->msg(
  1156. $nofAuthors > 10 ? 'youhavenewmessagesmanyusers' : 'youhavenewmessages',
  1157. $newMessagesLink,
  1158. $newMessagesDiffLink
  1159. );
  1160. }
  1161. $ntl = $ntl->text();
  1162. # Disable Squid cache
  1163. $out->setSquidMaxage( 0 );
  1164. }
  1165. } elseif ( count( $newtalks ) ) {
  1166. // _>" " for BC <= 1.16
  1167. $sep = str_replace( '_', ' ', $this->msg( 'newtalkseparator' )->escaped() );
  1168. $msgs = array();
  1169. foreach ( $newtalks as $newtalk ) {
  1170. $msgs[] = Xml::element(
  1171. 'a',
  1172. array( 'href' => $newtalk['link'] ), $newtalk['wiki']
  1173. );
  1174. }
  1175. $parts = implode( $sep, $msgs );
  1176. $ntl = $this->msg( 'youhavenewmessagesmulti' )->rawParams( $parts )->escaped();
  1177. $out->setSquidMaxage( 0 );
  1178. }
  1179. return $ntl;
  1180. }
  1181. /**
  1182. * Get a cached notice
  1183. *
  1184. * @param $name String: message name, or 'default' for $wgSiteNotice
  1185. * @return String: HTML fragment
  1186. */
  1187. private function getCachedNotice( $name ) {
  1188. global $wgRenderHashAppend, $parserMemc, $wgContLang;
  1189. wfProfileIn( __METHOD__ );
  1190. $needParse = false;
  1191. if( $name === 'default' ) {
  1192. // special case
  1193. global $wgSiteNotice;
  1194. $notice = $wgSiteNotice;
  1195. if( empty( $notice ) ) {
  1196. wfProfileOut( __METHOD__ );
  1197. return false;
  1198. }
  1199. } else {
  1200. $msg = $this->msg( $name )->inContentLanguage();
  1201. if( $msg->isDisabled() ) {
  1202. wfProfileOut( __METHOD__ );
  1203. return false;
  1204. }
  1205. $notice = $msg->plain();
  1206. }
  1207. // Use the extra hash appender to let eg SSL variants separately cache.
  1208. $key = wfMemcKey( $name . $wgRenderHashAppend );
  1209. $cachedNotice = $parserMemc->get( $key );
  1210. if( is_array( $cachedNotice ) ) {
  1211. if( md5( $notice ) == $cachedNotice['hash'] ) {
  1212. $notice = $cachedNotice['html'];
  1213. } else {
  1214. $needParse = true;
  1215. }
  1216. } else {
  1217. $needParse = true;
  1218. }
  1219. if ( $needParse ) {
  1220. $parsed = $this->getOutput()->parse( $notice );
  1221. $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 );
  1222. $notice = $parsed;
  1223. }
  1224. $notice = Html::rawElement( 'div', array( 'id' => 'localNotice',
  1225. 'lang' => $wgContLang->getHtmlCode(), 'dir' => $wgContLang->getDir() ), $notice );
  1226. wfProfileOut( __METHOD__ );
  1227. return $notice;
  1228. }
  1229. /**
  1230. * Get a notice based on page's namespace
  1231. *
  1232. * @return String: HTML fragment
  1233. */
  1234. function getNamespaceNotice() {
  1235. wfProfileIn( __METHOD__ );
  1236. $key = 'namespacenotice-' . $this->getTitle()->getNsText();
  1237. $namespaceNotice = $this->getCachedNotice( $key );
  1238. if ( $namespaceNotice && substr( $namespaceNotice, 0, 7 ) != '<p>&lt;' ) {
  1239. $namespaceNotice = '<div id="namespacebanner">' . $namespaceNotice . '</div>';
  1240. } else {
  1241. $namespaceNotice = '';
  1242. }
  1243. wfProfileOut( __METHOD__ );
  1244. return $namespaceNotice;
  1245. }
  1246. /**
  1247. * Get the site notice
  1248. *
  1249. * @return String: HTML fragment
  1250. */
  1251. function getSiteNotice() {
  1252. wfProfileIn( __METHOD__ );
  1253. $siteNotice = '';
  1254. if ( wfRunHooks( 'SiteNoticeBefore', array( &$siteNotice, $this ) ) ) {
  1255. if ( is_object( $this->getUser() ) && $this->getUser()->isLoggedIn() ) {
  1256. $siteNotice = $this->getCachedNotice( 'sitenotice' );
  1257. } else {
  1258. $anonNotice = $this->getCachedNotice( 'anonnotice' );
  1259. if ( !$anonNotice ) {
  1260. $siteNotice = $this->getCachedNotice( 'sitenotice' );
  1261. } else {
  1262. $siteNotice = $anonNotice;
  1263. }
  1264. }
  1265. if ( !$siteNotice ) {
  1266. $siteNotice = $this->getCachedNotice( 'default' );
  1267. }
  1268. }
  1269. wfRunHooks( 'SiteNoticeAfter', array( &$siteNotice, $this ) );
  1270. wfProfileOut( __METHOD__ );
  1271. return $siteNotice;
  1272. }
  1273. /**
  1274. * Create a section edit link. This supersedes editSectionLink() and
  1275. * editSectionLinkForOther().
  1276. *
  1277. * @param $nt Title The title being linked to (may not be the same as
  1278. * $wgTitle, if the section is included from a template)
  1279. * @param $section string The designation of the section being pointed to,
  1280. * to be included in the link, like "&section=$section"
  1281. * @param $tooltip string The tooltip to use for the link: will be escaped
  1282. * and wrapped in the 'editsectionhint' message
  1283. * @param $lang string Language code
  1284. * @return string HTML to use for edit link
  1285. */
  1286. public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
  1287. // HTML generated here should probably have userlangattributes
  1288. // added to it for LTR text on RTL pages
  1289. $lang = wfGetLangObj( $lang );
  1290. $attribs = array();
  1291. if ( !is_null( $tooltip ) ) {
  1292. # Bug 25462: undo double-escaping.
  1293. $tooltip = Sanitizer::decodeCharReferences( $tooltip );
  1294. $attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
  1295. ->inLanguage( $lang )->text();
  1296. }
  1297. $link = Linker::link( $nt, wfMessage( 'editsection' )->inLanguage( $lang )->text(),
  1298. $attribs,
  1299. array( 'action' => 'edit', 'section' => $section ),
  1300. array( 'noclasses', 'known' )
  1301. );
  1302. # Run the old hook. This takes up half of the function . . . hopefully
  1303. # we can rid of it someday.
  1304. $attribs = '';
  1305. if ( $tooltip ) {
  1306. $attribs = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
  1307. ->inLanguage( $lang )->escaped();
  1308. $attribs = " title=\"$attribs\"";
  1309. }
  1310. $result = null;
  1311. wfRunHooks( 'EditSectionLink', array( &$this, $nt, $section, $attribs, $link, &$result, $lang ) );
  1312. if ( !is_null( $result ) ) {
  1313. # For reverse compatibility, add the brackets *after* the hook is
  1314. # run, and even add them to hook-provided text. (This is the main
  1315. # reason that the EditSectionLink hook is deprecated in favor of
  1316. # DoEditSectionLink: it can't change the brackets or the span.)
  1317. $result = wfMessage( 'editsection-brackets' )->rawParams( $result )
  1318. ->inLanguage( $lang )->escaped();
  1319. return "<span class=\"editsection\">$result</span>";
  1320. }
  1321. # Add the brackets and the span, and *then* run the nice new hook, with
  1322. # clean and non-redundant arguments.
  1323. $result = wfMessage( 'editsection-brackets' )->rawParams( $link )
  1324. ->inLanguage( $lang )->escaped();
  1325. $result = "<span class=\"editsection\">$result</span>";
  1326. wfRunHooks( 'DoEditSectionLink', array( $this, $nt, $section, $tooltip, &$result, $lang ) );
  1327. return $result;
  1328. }
  1329. /**
  1330. * Use PHP's magic __call handler to intercept legacy calls to the linker
  1331. * for backwards compatibility.
  1332. *
  1333. * @param $fname String Name of called method
  1334. * @param $args Array Arguments to the method
  1335. * @return mixed
  1336. */
  1337. function __call( $fname, $args ) {
  1338. $realFunction = array( 'Linker', $fname );
  1339. if ( is_callable( $realFunction ) ) {
  1340. return call_user_func_array( $realFunction, $args );
  1341. } else {
  1342. $className = get_class( $this );
  1343. throw new MWException( "Call to undefined method $className::$fname" );
  1344. }
  1345. }
  1346. }