MediaWiki  REL1_20
SpecialPage.php
Go to the documentation of this file.
00001 <?php
00029 class SpecialPage {
00030 
00031         // The canonical name of this special page
00032         // Also used for the default <h1> heading, @see getDescription()
00033         protected $mName;
00034 
00035         // The local name of this special page
00036         private $mLocalName;
00037 
00038         // Minimum user level required to access this page, or "" for anyone.
00039         // Also used to categorise the pages in Special:Specialpages
00040         private $mRestriction;
00041 
00042         // Listed in Special:Specialpages?
00043         private $mListed;
00044 
00045         // Function name called by the default execute()
00046         private $mFunction;
00047 
00048         // File which needs to be included before the function above can be called
00049         private $mFile;
00050 
00051         // Whether or not this special page is being included from an article
00052         protected $mIncluding;
00053 
00054         // Whether the special page can be included in an article
00055         protected $mIncludable;
00056 
00061         protected $mContext;
00062 
00068         static function initList() {
00069                 wfDeprecated( __METHOD__, '1.18' );
00070                 // Noop
00071         }
00072 
00076         static function initAliasList() {
00077                 wfDeprecated( __METHOD__, '1.18' );
00078                 // Noop
00079         }
00080 
00089         static function resolveAlias( $alias ) {
00090                 wfDeprecated( __METHOD__, '1.18' );
00091                 list( $name, /*...*/ ) = SpecialPageFactory::resolveAlias( $alias );
00092                 return $name;
00093         }
00094 
00104         static function resolveAliasWithSubpage( $alias ) {
00105                 return SpecialPageFactory::resolveAlias( $alias );
00106         }
00107 
00116         static function addPage( &$page ) {
00117                 wfDeprecated( __METHOD__, '1.7' );
00118                 SpecialPageFactory::getList()->{$page->mName} = $page;
00119         }
00120 
00128         static function setGroup( $page, $group ) {
00129                 wfDeprecated( __METHOD__, '1.18' );
00130                 SpecialPageFactory::setGroup( $page, $group );
00131         }
00132 
00140         static function getGroup( &$page ) {
00141                 wfDeprecated( __METHOD__, '1.18' );
00142                 return SpecialPageFactory::getGroup( $page );
00143         }
00144 
00153         static function removePage( $name ) {
00154                 wfDeprecated( __METHOD__, '1.18' );
00155                 unset( SpecialPageFactory::getList()->$name );
00156         }
00157 
00165         static function exists( $name ) {
00166                 wfDeprecated( __METHOD__, '1.18' );
00167                 return SpecialPageFactory::exists( $name );
00168         }
00169 
00177         static function getPage( $name ) {
00178                 wfDeprecated( __METHOD__, '1.18' );
00179                 return SpecialPageFactory::getPage( $name );
00180         }
00181 
00190         static function getPageByAlias( $alias ) {
00191                 wfDeprecated( __METHOD__, '1.18' );
00192                 return SpecialPageFactory::getPage( $alias );
00193         }
00194 
00204         static function getUsablePages( User $user = null ) {
00205                 wfDeprecated( __METHOD__, '1.18' );
00206                 return SpecialPageFactory::getUsablePages( $user );
00207         }
00208 
00215         static function getRegularPages() {
00216                 wfDeprecated( __METHOD__, '1.18' );
00217                 return SpecialPageFactory::getRegularPages();
00218         }
00219 
00227         static function getRestrictedPages() {
00228                 wfDeprecated( __METHOD__, '1.18' );
00229                 return SpecialPageFactory::getRestrictedPages();
00230         }
00231 
00246         public static function executePath( &$title, IContextSource &$context, $including = false ) {
00247                 wfDeprecated( __METHOD__, '1.18' );
00248                 return SpecialPageFactory::executePath( $title, $context, $including );
00249         }
00250 
00260         static function getLocalNameFor( $name, $subpage = false ) {
00261                 wfDeprecated( __METHOD__, '1.18' );
00262                 return SpecialPageFactory::getLocalNameFor( $name, $subpage );
00263         }
00264 
00272         public static function getTitleFor( $name, $subpage = false ) {
00273                 $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
00274                 if ( $name ) {
00275                         return Title::makeTitle( NS_SPECIAL, $name );
00276                 } else {
00277                         throw new MWException( "Invalid special page name \"$name\"" );
00278                 }
00279         }
00280 
00288         public static function getSafeTitleFor( $name, $subpage = false ) {
00289                 $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
00290                 if ( $name ) {
00291                         return Title::makeTitleSafe( NS_SPECIAL, $name );
00292                 } else {
00293                         return null;
00294                 }
00295         }
00296 
00304         static function getTitleForAlias( $alias ) {
00305                 wfDeprecated( __METHOD__, '1.18' );
00306                 return SpecialPageFactory::getTitleForAlias( $alias );
00307         }
00308 
00326         public function __construct(
00327                 $name = '', $restriction = '', $listed = true,
00328                 $function = false, $file = 'default', $includable = false
00329         ) {
00330                 $this->init( $name, $restriction, $listed, $function, $file, $includable );
00331         }
00332 
00343         private function init( $name, $restriction, $listed, $function, $file, $includable ) {
00344                 $this->mName = $name;
00345                 $this->mRestriction = $restriction;
00346                 $this->mListed = $listed;
00347                 $this->mIncludable = $includable;
00348                 if ( !$function ) {
00349                         $this->mFunction = 'wfSpecial' . $name;
00350                 } else {
00351                         $this->mFunction = $function;
00352                 }
00353                 if ( $file === 'default' ) {
00354                         $this->mFile = __DIR__ . "/specials/Special$name.php";
00355                 } else {
00356                         $this->mFile = $file;
00357                 }
00358         }
00359 
00368         public function __call( $fName, $a ) {
00369                 // Deprecated messages now, remove in 1.19 or 1.20?
00370                 wfDeprecated( __METHOD__, '1.17' );
00371 
00372                 // Sometimes $fName is SpecialPage, sometimes it's specialpage. <3 PHP
00373                 if ( strtolower( $fName ) == 'specialpage' ) {
00374                         $name = isset( $a[0] ) ? $a[0] : '';
00375                         $restriction = isset( $a[1] ) ? $a[1] : '';
00376                         $listed = isset( $a[2] ) ? $a[2] : true;
00377                         $function = isset( $a[3] ) ? $a[3] : false;
00378                         $file = isset( $a[4] ) ? $a[4] : 'default';
00379                         $includable = isset( $a[5] ) ? $a[5] : false;
00380                         $this->init( $name, $restriction, $listed, $function, $file, $includable );
00381                 } else {
00382                         $className = get_class( $this );
00383                         throw new MWException( "Call to undefined method $className::$fName" );
00384                 }
00385         }
00386 
00391         function getName() {
00392                 return $this->mName;
00393         }
00394 
00399         function getRestriction() {
00400                 return $this->mRestriction;
00401         }
00402 
00410         function getFile() {
00411                 wfDeprecated( __METHOD__, '1.18' );
00412                 return $this->mFile;
00413         }
00414 
00415         // @todo FIXME: Decide which syntax to use for this, and stick to it
00421         function isListed() {
00422                 return $this->mListed;
00423         }
00430         function setListed( $listed ) {
00431                 return wfSetVar( $this->mListed, $listed );
00432         }
00439         function listed( $x = null ) {
00440                 return wfSetVar( $this->mListed, $x );
00441         }
00442 
00447         public function isIncludable() {
00448                 return $this->mIncludable;
00449         }
00450 
00458         function name( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mName, $x ); }
00459 
00467         function restriction( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mRestriction, $x ); }
00468 
00476         function func( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFunction, $x ); }
00477 
00485         function file( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFile, $x ); }
00486 
00494         function includable( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mIncludable, $x ); }
00495 
00501         function including( $x = null ) {
00502                 return wfSetVar( $this->mIncluding, $x );
00503         }
00504 
00508         function getLocalName() {
00509                 if ( !isset( $this->mLocalName ) ) {
00510                         $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName );
00511                 }
00512                 return $this->mLocalName;
00513         }
00514 
00523         public function isExpensive() {
00524                 return false;
00525         }
00526 
00534         public function isRestricted() {
00535                 global $wgGroupPermissions;
00536                 // DWIM: If all anons can do something, then it is not restricted
00537                 return $this->mRestriction != '' && empty( $wgGroupPermissions['*'][$this->mRestriction] );
00538         }
00539 
00548         public function userCanExecute( User $user ) {
00549                 return $user->isAllowed( $this->mRestriction );
00550         }
00551 
00555         function displayRestrictionError() {
00556                 throw new PermissionsError( $this->mRestriction );
00557         }
00558 
00564         public function checkPermissions() {
00565                 if ( !$this->userCanExecute( $this->getUser() ) ) {
00566                         $this->displayRestrictionError();
00567                 }
00568         }
00569 
00576         public function checkReadOnly() {
00577                 if ( wfReadOnly() ) {
00578                         throw new ReadOnlyError;
00579                 }
00580         }
00581 
00585         function setHeaders() {
00586                 $out = $this->getOutput();
00587                 $out->setArticleRelated( false );
00588                 $out->setRobotPolicy( "noindex,nofollow" );
00589                 $out->setPageTitle( $this->getDescription() );
00590         }
00591 
00599         public final function run( $subPage ) {
00608                 wfRunHooks( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
00609 
00610                 $this->beforeExecute( $subPage );
00611                 $this->execute( $subPage );
00612                 $this->afterExecute( $subPage );
00613 
00622                 wfRunHooks( 'SpecialPageAfterExecute', array( $this, $subPage ) );
00623         }
00624 
00632         protected function beforeExecute( $subPage ) {
00633                 // No-op
00634         }
00635 
00643         protected function afterExecute( $subPage ) {
00644                 // No-op
00645         }
00646 
00655         public function execute( $subPage ) {
00656                 $this->setHeaders();
00657                 $this->checkPermissions();
00658 
00659                 $func = $this->mFunction;
00660                 // only load file if the function does not exist
00661                 if ( !is_callable( $func ) && $this->mFile ) {
00662                         require_once( $this->mFile );
00663                 }
00664                 $this->outputHeader();
00665                 call_user_func( $func, $subPage, $this );
00666         }
00667 
00676         function outputHeader( $summaryMessageKey = '' ) {
00677                 global $wgContLang;
00678 
00679                 if ( $summaryMessageKey == '' ) {
00680                         $msg = $wgContLang->lc( $this->getName() ) . '-summary';
00681                 } else {
00682                         $msg = $summaryMessageKey;
00683                 }
00684                 if ( !$this->msg( $msg )->isDisabled() && !$this->including() ) {
00685                         $this->getOutput()->wrapWikiMsg(
00686                                 "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
00687                 }
00688 
00689         }
00690 
00701         function getDescription() {
00702                 return $this->msg( strtolower( $this->mName ) )->text();
00703         }
00704 
00711         function getTitle( $subpage = false ) {
00712                 return self::getTitleFor( $this->mName, $subpage );
00713         }
00714 
00721         public function setContext( $context ) {
00722                 $this->mContext = $context;
00723         }
00724 
00731         public function getContext() {
00732                 if ( $this->mContext instanceof IContextSource ) {
00733                         return $this->mContext;
00734                 } else {
00735                         wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
00736                         return RequestContext::getMain();
00737                 }
00738         }
00739 
00746         public function getRequest() {
00747                 return $this->getContext()->getRequest();
00748         }
00749 
00756         public function getOutput() {
00757                 return $this->getContext()->getOutput();
00758         }
00759 
00766         public function getUser() {
00767                 return $this->getContext()->getUser();
00768         }
00769 
00776         public function getSkin() {
00777                 return $this->getContext()->getSkin();
00778         }
00779 
00787         public function getLang() {
00788                 wfDeprecated( __METHOD__, '1.19' );
00789                 return $this->getLanguage();
00790         }
00791 
00798         public function getLanguage() {
00799                 return $this->getContext()->getLanguage();
00800         }
00801 
00808         public function getFullTitle() {
00809                 return $this->getContext()->getTitle();
00810         }
00811 
00818         public function msg( /* $args */ ) {
00819                 // Note: can't use func_get_args() directly as second or later item in
00820                 // a parameter list until PHP 5.3 or you get a fatal error.
00821                 // Works fine as the first parameter, which appears elsewhere in the
00822                 // code base. Sighhhh.
00823                 $args = func_get_args();
00824                 $message = call_user_func_array( array( $this->getContext(), 'msg' ), $args );
00825                 // RequestContext passes context to wfMessage, and the language is set from
00826                 // the context, but setting the language for Message class removes the
00827                 // interface message status, which breaks for example usernameless gender
00828                 // invokations. Restore the flag when not including special page in content.
00829                 if ( $this->including() ) {
00830                         $message->setInterfaceMessageFlag( false );
00831                 }
00832                 return $message;
00833         }
00834 
00840         protected function addFeedLinks( $params ) {
00841                 global $wgFeedClasses;
00842 
00843                 $feedTemplate = wfScript( 'api' ) . '?';
00844 
00845                 foreach ( $wgFeedClasses as $format => $class ) {
00846                         $theseParams = $params + array( 'feedformat' => $format );
00847                         $url = $feedTemplate . wfArrayToCGI( $theseParams );
00848                         $this->getOutput()->addFeedLink( $format, $url );
00849                 }
00850         }
00851 }
00852 
00858 abstract class FormSpecialPage extends SpecialPage {
00859 
00864         protected abstract function getFormFields();
00865 
00870         protected function preText() { return ''; }
00871         protected function postText() { return ''; }
00872 
00877         protected function alterForm( HTMLForm $form ) {}
00878 
00883         protected function getForm() {
00884                 $this->fields = $this->getFormFields();
00885 
00886                 $form = new HTMLForm( $this->fields, $this->getContext() );
00887                 $form->setSubmitCallback( array( $this, 'onSubmit' ) );
00888                 $form->setWrapperLegend( $this->msg( strtolower( $this->getName() ) . '-legend' ) );
00889                 $form->addHeaderText(
00890                         $this->msg( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
00891 
00892                 // Retain query parameters (uselang etc)
00893                 $params = array_diff_key(
00894                         $this->getRequest()->getQueryValues(), array( 'title' => null ) );
00895                 $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
00896 
00897                 $form->addPreText( $this->preText() );
00898                 $form->addPostText( $this->postText() );
00899                 $this->alterForm( $form );
00900 
00901                 // Give hooks a chance to alter the form, adding extra fields or text etc
00902                 wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) );
00903 
00904                 return $form;
00905         }
00906 
00912         public abstract function onSubmit( array $data );
00913 
00918         public abstract function onSuccess();
00919 
00925         public function execute( $par ) {
00926                 $this->setParameter( $par );
00927                 $this->setHeaders();
00928 
00929                 // This will throw exceptions if there's a problem
00930                 $this->checkExecutePermissions( $this->getUser() );
00931 
00932                 $form = $this->getForm();
00933                 if ( $form->show() ) {
00934                         $this->onSuccess();
00935                 }
00936         }
00937 
00942         protected function setParameter( $par ) {}
00943 
00951         protected function checkExecutePermissions( User $user ) {
00952                 $this->checkPermissions();
00953 
00954                 if ( $this->requiresUnblock() && $user->isBlocked() ) {
00955                         $block = $user->getBlock();
00956                         throw new UserBlockedError( $block );
00957                 }
00958 
00959                 if ( $this->requiresWrite() ) {
00960                         $this->checkReadOnly();
00961                 }
00962 
00963                 return true;
00964         }
00965 
00970         public function requiresWrite() {
00971                 return true;
00972         }
00973 
00978         public function requiresUnblock() {
00979                 return true;
00980         }
00981 }
00982 
00987 class UnlistedSpecialPage extends SpecialPage {
00988         function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
00989                 parent::__construct( $name, $restriction, false, $function, $file );
00990         }
00991 
00992         public function isListed() {
00993                 return false;
00994         }
00995 }
00996 
01001 class IncludableSpecialPage extends SpecialPage {
01002         function __construct(
01003                 $name, $restriction = '', $listed = true, $function = false, $file = 'default'
01004         ) {
01005                 parent::__construct( $name, $restriction, $listed, $function, $file, true );
01006         }
01007 
01008         public function isIncludable() {
01009                 return true;
01010         }
01011 }
01012 
01017 abstract class RedirectSpecialPage extends UnlistedSpecialPage {
01018 
01019         // Query parameters that can be passed through redirects
01020         protected $mAllowedRedirectParams = array();
01021 
01022         // Query parameteres added by redirects
01023         protected $mAddedRedirectParams = array();
01024 
01025         public function execute( $par ) {
01026                 $redirect = $this->getRedirect( $par );
01027                 $query = $this->getRedirectQuery();
01028                 // Redirect to a page title with possible query parameters
01029                 if ( $redirect instanceof Title ) {
01030                         $url = $redirect->getFullUrl( $query );
01031                         $this->getOutput()->redirect( $url );
01032                         wfProfileOut( __METHOD__ );
01033                         return $redirect;
01034                 // Redirect to index.php with query parameters
01035                 } elseif ( $redirect === true ) {
01036                         global $wgScript;
01037                         $url = $wgScript . '?' . wfArrayToCGI( $query );
01038                         $this->getOutput()->redirect( $url );
01039                         wfProfileOut( __METHOD__ );
01040                         return $redirect;
01041                 } else {
01042                         $class = __CLASS__;
01043                         throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
01044                 }
01045         }
01046 
01054         abstract public function getRedirect( $par );
01055 
01062         public function getRedirectQuery() {
01063                 $params = array();
01064 
01065                 foreach ( $this->mAllowedRedirectParams as $arg ) {
01066                         if ( $this->getRequest()->getVal( $arg, null ) !== null ) {
01067                                 $params[$arg] = $this->getRequest()->getVal( $arg );
01068                         }
01069                 }
01070 
01071                 foreach ( $this->mAddedRedirectParams as $arg => $val ) {
01072                         $params[$arg] = $val;
01073                 }
01074 
01075                 return count( $params )
01076                         ? $params
01077                         : false;
01078         }
01079 }
01080 
01081 abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
01082         var $redirName, $redirSubpage;
01083 
01084         function __construct(
01085                 $name, $redirName, $redirSubpage = false,
01086                 $allowedRedirectParams = array(), $addedRedirectParams = array()
01087         ) {
01088                 parent::__construct( $name );
01089                 $this->redirName = $redirName;
01090                 $this->redirSubpage = $redirSubpage;
01091                 $this->mAllowedRedirectParams = $allowedRedirectParams;
01092                 $this->mAddedRedirectParams = $addedRedirectParams;
01093         }
01094 
01095         public function getRedirect( $subpage ) {
01096                 if ( $this->redirSubpage === false ) {
01097                         return SpecialPage::getTitleFor( $this->redirName, $subpage );
01098                 } else {
01099                         return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
01100                 }
01101         }
01102 }
01103 
01107 class SpecialListAdmins extends SpecialRedirectToSpecial {
01108         function __construct() {
01109                 parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
01110         }
01111 }
01112 
01116 class SpecialListBots extends SpecialRedirectToSpecial {
01117         function __construct() {
01118                 parent::__construct( 'Listbots', 'Listusers', 'bot' );
01119         }
01120 }
01121 
01126 class SpecialCreateAccount extends SpecialRedirectToSpecial {
01127         function __construct() {
01128                 parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) );
01129         }
01130 }
01205 abstract class RedirectSpecialArticle extends RedirectSpecialPage {
01206         function __construct( $name ) {
01207                 parent::__construct( $name );
01208                 $redirectParams = array(
01209                         'action',
01210                         'redirect', 'rdfrom',
01211                         # Options for preloaded edits
01212                         'preload', 'editintro', 'preloadtitle', 'summary',
01213                         # Options for overriding user settings
01214                         'preview', 'internaledit', 'externaledit', 'mode',
01215                         # Options for history/diffs
01216                         'section', 'oldid', 'diff', 'dir',
01217                         'limit', 'offset', 'feed',
01218                         # Misc options
01219                         'redlink', 'debug',
01220                         # Options for action=raw; missing ctype can break JS or CSS in some browsers
01221                         'ctype', 'maxage', 'smaxage',
01222                 );
01223 
01224                 wfRunHooks( "RedirectSpecialArticleRedirectParams", array(&$redirectParams) );
01225                 $this->mAllowedRedirectParams = $redirectParams;
01226         }
01227 }
01228 
01233 class SpecialMypage extends RedirectSpecialArticle {
01234         function __construct() {
01235                 parent::__construct( 'Mypage' );
01236         }
01237 
01238         function getRedirect( $subpage ) {
01239                 if ( strval( $subpage ) !== '' ) {
01240                         return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
01241                 } else {
01242                         return Title::makeTitle( NS_USER, $this->getUser()->getName() );
01243                 }
01244         }
01245 }
01246 
01251 class SpecialMytalk extends RedirectSpecialArticle {
01252         function __construct() {
01253                 parent::__construct( 'Mytalk' );
01254         }
01255 
01256         function getRedirect( $subpage ) {
01257                 if ( strval( $subpage ) !== '' ) {
01258                         return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
01259                 } else {
01260                         return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
01261                 }
01262         }
01263 }
01264 
01269 class SpecialMycontributions extends RedirectSpecialPage {
01270         function __construct() {
01271                 parent::__construct(  'Mycontributions' );
01272                 $this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
01273                         'offset', 'dir', 'year', 'month', 'feed' );
01274         }
01275 
01276         function getRedirect( $subpage ) {
01277                 return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
01278         }
01279 }
01280 
01284 class SpecialMyuploads extends RedirectSpecialPage {
01285         function __construct() {
01286                 parent::__construct( 'Myuploads' );
01287                 $this->mAllowedRedirectParams = array( 'limit' );
01288         }
01289 
01290         function getRedirect( $subpage ) {
01291                 return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
01292         }
01293 }
01294 
01298 class SpecialPermanentLink extends RedirectSpecialPage {
01299         function __construct() {
01300                 parent::__construct( 'PermanentLink' );
01301                 $this->mAllowedRedirectParams = array();
01302         }
01303 
01304         function getRedirect( $subpage ) {
01305                 $subpage = intval( $subpage );
01306                 if ( $subpage === 0 ) {
01307                         # throw an error page when no subpage was given
01308                         throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
01309                 }
01310                 $this->mAddedRedirectParams['oldid'] = $subpage;
01311                 return true;
01312         }
01313 }