MediaWiki  master
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 
00115         static function setGroup( $page, $group ) {
00116                 wfDeprecated( __METHOD__, '1.18' );
00117                 SpecialPageFactory::setGroup( $page, $group );
00118         }
00119 
00127         static function getGroup( &$page ) {
00128                 wfDeprecated( __METHOD__, '1.18' );
00129                 return SpecialPageFactory::getGroup( $page );
00130         }
00131 
00140         static function removePage( $name ) {
00141                 wfDeprecated( __METHOD__, '1.18' );
00142                 unset( SpecialPageFactory::getList()->$name );
00143         }
00144 
00152         static function exists( $name ) {
00153                 wfDeprecated( __METHOD__, '1.18' );
00154                 return SpecialPageFactory::exists( $name );
00155         }
00156 
00164         static function getPage( $name ) {
00165                 wfDeprecated( __METHOD__, '1.18' );
00166                 return SpecialPageFactory::getPage( $name );
00167         }
00168 
00177         static function getPageByAlias( $alias ) {
00178                 wfDeprecated( __METHOD__, '1.18' );
00179                 return SpecialPageFactory::getPage( $alias );
00180         }
00181 
00191         static function getUsablePages( User $user = null ) {
00192                 wfDeprecated( __METHOD__, '1.18' );
00193                 return SpecialPageFactory::getUsablePages( $user );
00194         }
00195 
00202         static function getRegularPages() {
00203                 wfDeprecated( __METHOD__, '1.18' );
00204                 return SpecialPageFactory::getRegularPages();
00205         }
00206 
00214         static function getRestrictedPages() {
00215                 wfDeprecated( __METHOD__, '1.18' );
00216                 return SpecialPageFactory::getRestrictedPages();
00217         }
00218 
00233         public static function executePath( &$title, IContextSource &$context, $including = false ) {
00234                 wfDeprecated( __METHOD__, '1.18' );
00235                 return SpecialPageFactory::executePath( $title, $context, $including );
00236         }
00237 
00247         static function getLocalNameFor( $name, $subpage = false ) {
00248                 wfDeprecated( __METHOD__, '1.18' );
00249                 return SpecialPageFactory::getLocalNameFor( $name, $subpage );
00250         }
00251 
00260         public static function getTitleFor( $name, $subpage = false ) {
00261                 $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
00262                 if ( $name ) {
00263                         return Title::makeTitle( NS_SPECIAL, $name );
00264                 } else {
00265                         throw new MWException( "Invalid special page name \"$name\"" );
00266                 }
00267         }
00268 
00276         public static function getSafeTitleFor( $name, $subpage = false ) {
00277                 $name = SpecialPageFactory::getLocalNameFor( $name, $subpage );
00278                 if ( $name ) {
00279                         return Title::makeTitleSafe( NS_SPECIAL, $name );
00280                 } else {
00281                         return null;
00282                 }
00283         }
00284 
00292         static function getTitleForAlias( $alias ) {
00293                 wfDeprecated( __METHOD__, '1.18' );
00294                 return SpecialPageFactory::getTitleForAlias( $alias );
00295         }
00296 
00314         public function __construct(
00315                 $name = '', $restriction = '', $listed = true,
00316                 $function = false, $file = 'default', $includable = false
00317         ) {
00318                 $this->init( $name, $restriction, $listed, $function, $file, $includable );
00319         }
00320 
00331         private function init( $name, $restriction, $listed, $function, $file, $includable ) {
00332                 $this->mName = $name;
00333                 $this->mRestriction = $restriction;
00334                 $this->mListed = $listed;
00335                 $this->mIncludable = $includable;
00336                 if ( !$function ) {
00337                         $this->mFunction = 'wfSpecial' . $name;
00338                 } else {
00339                         $this->mFunction = $function;
00340                 }
00341                 if ( $file === 'default' ) {
00342                         $this->mFile = __DIR__ . "/specials/Special$name.php";
00343                 } else {
00344                         $this->mFile = $file;
00345                 }
00346         }
00347 
00357         public function __call( $fName, $a ) {
00358                 // Deprecated messages now, remove in 1.19 or 1.20?
00359                 wfDeprecated( __METHOD__, '1.17' );
00360 
00361                 // Sometimes $fName is SpecialPage, sometimes it's specialpage. <3 PHP
00362                 if ( strtolower( $fName ) == 'specialpage' ) {
00363                         $name = isset( $a[0] ) ? $a[0] : '';
00364                         $restriction = isset( $a[1] ) ? $a[1] : '';
00365                         $listed = isset( $a[2] ) ? $a[2] : true;
00366                         $function = isset( $a[3] ) ? $a[3] : false;
00367                         $file = isset( $a[4] ) ? $a[4] : 'default';
00368                         $includable = isset( $a[5] ) ? $a[5] : false;
00369                         $this->init( $name, $restriction, $listed, $function, $file, $includable );
00370                 } else {
00371                         $className = get_class( $this );
00372                         throw new MWException( "Call to undefined method $className::$fName" );
00373                 }
00374         }
00375 
00380         function getName() {
00381                 return $this->mName;
00382         }
00383 
00388         function getRestriction() {
00389                 return $this->mRestriction;
00390         }
00391 
00399         function getFile() {
00400                 wfDeprecated( __METHOD__, '1.18' );
00401                 return $this->mFile;
00402         }
00403 
00404         // @todo FIXME: Decide which syntax to use for this, and stick to it
00410         function isListed() {
00411                 return $this->mListed;
00412         }
00419         function setListed( $listed ) {
00420                 return wfSetVar( $this->mListed, $listed );
00421         }
00428         function listed( $x = null ) {
00429                 return wfSetVar( $this->mListed, $x );
00430         }
00431 
00436         public function isIncludable() {
00437                 return $this->mIncludable;
00438         }
00439 
00447         function name( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mName, $x ); }
00448 
00456         function restriction( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mRestriction, $x ); }
00457 
00465         function func( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFunction, $x ); }
00466 
00474         function file( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mFile, $x ); }
00475 
00483         function includable( $x = null ) { wfDeprecated( __METHOD__, '1.18' ); return wfSetVar( $this->mIncludable, $x ); }
00484 
00490         function including( $x = null ) {
00491                 return wfSetVar( $this->mIncluding, $x );
00492         }
00493 
00497         function getLocalName() {
00498                 if ( !isset( $this->mLocalName ) ) {
00499                         $this->mLocalName = SpecialPageFactory::getLocalNameFor( $this->mName );
00500                 }
00501                 return $this->mLocalName;
00502         }
00503 
00512         public function isExpensive() {
00513                 return false;
00514         }
00515 
00523         public function isRestricted() {
00524                 // DWIM: If all anons can do something, then it is not restricted
00525                 return $this->mRestriction != '' && !User::groupHasPermission( '*', $this->mRestriction );
00526         }
00527 
00536         public function userCanExecute( User $user ) {
00537                 return $user->isAllowed( $this->mRestriction );
00538         }
00539 
00543         function displayRestrictionError() {
00544                 throw new PermissionsError( $this->mRestriction );
00545         }
00546 
00552         public function checkPermissions() {
00553                 if ( !$this->userCanExecute( $this->getUser() ) ) {
00554                         $this->displayRestrictionError();
00555                 }
00556         }
00557 
00564         public function checkReadOnly() {
00565                 if ( wfReadOnly() ) {
00566                         throw new ReadOnlyError;
00567                 }
00568         }
00569 
00573         function setHeaders() {
00574                 $out = $this->getOutput();
00575                 $out->setArticleRelated( false );
00576                 $out->setRobotPolicy( "noindex,nofollow" );
00577                 $out->setPageTitle( $this->getDescription() );
00578         }
00579 
00587         public final function run( $subPage ) {
00596                 wfRunHooks( 'SpecialPageBeforeExecute', array( $this, $subPage ) );
00597 
00598                 $this->beforeExecute( $subPage );
00599                 $this->execute( $subPage );
00600                 $this->afterExecute( $subPage );
00601 
00610                 wfRunHooks( 'SpecialPageAfterExecute', array( $this, $subPage ) );
00611         }
00612 
00620         protected function beforeExecute( $subPage ) {
00621                 // No-op
00622         }
00623 
00631         protected function afterExecute( $subPage ) {
00632                 // No-op
00633         }
00634 
00643         public function execute( $subPage ) {
00644                 $this->setHeaders();
00645                 $this->checkPermissions();
00646 
00647                 $func = $this->mFunction;
00648                 // only load file if the function does not exist
00649                 if ( !is_callable( $func ) && $this->mFile ) {
00650                         require_once( $this->mFile );
00651                 }
00652                 $this->outputHeader();
00653                 call_user_func( $func, $subPage, $this );
00654         }
00655 
00664         function outputHeader( $summaryMessageKey = '' ) {
00665                 global $wgContLang;
00666 
00667                 if ( $summaryMessageKey == '' ) {
00668                         $msg = $wgContLang->lc( $this->getName() ) . '-summary';
00669                 } else {
00670                         $msg = $summaryMessageKey;
00671                 }
00672                 if ( !$this->msg( $msg )->isDisabled() && !$this->including() ) {
00673                         $this->getOutput()->wrapWikiMsg(
00674                                 "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
00675                 }
00676 
00677         }
00678 
00689         function getDescription() {
00690                 return $this->msg( strtolower( $this->mName ) )->text();
00691         }
00692 
00699         function getTitle( $subpage = false ) {
00700                 return self::getTitleFor( $this->mName, $subpage );
00701         }
00702 
00709         public function setContext( $context ) {
00710                 $this->mContext = $context;
00711         }
00712 
00719         public function getContext() {
00720                 if ( $this->mContext instanceof IContextSource ) {
00721                         return $this->mContext;
00722                 } else {
00723                         wfDebug( __METHOD__ . " called and \$mContext is null. Return RequestContext::getMain(); for sanity\n" );
00724                         return RequestContext::getMain();
00725                 }
00726         }
00727 
00734         public function getRequest() {
00735                 return $this->getContext()->getRequest();
00736         }
00737 
00744         public function getOutput() {
00745                 return $this->getContext()->getOutput();
00746         }
00747 
00754         public function getUser() {
00755                 return $this->getContext()->getUser();
00756         }
00757 
00764         public function getSkin() {
00765                 return $this->getContext()->getSkin();
00766         }
00767 
00775         public function getLang() {
00776                 wfDeprecated( __METHOD__, '1.19' );
00777                 return $this->getLanguage();
00778         }
00779 
00786         public function getLanguage() {
00787                 return $this->getContext()->getLanguage();
00788         }
00789 
00796         public function getFullTitle() {
00797                 return $this->getContext()->getTitle();
00798         }
00799 
00806         public function msg( /* $args */ ) {
00807                 // Note: can't use func_get_args() directly as second or later item in
00808                 // a parameter list until PHP 5.3 or you get a fatal error.
00809                 // Works fine as the first parameter, which appears elsewhere in the
00810                 // code base. Sighhhh.
00811                 $args = func_get_args();
00812                 $message = call_user_func_array( array( $this->getContext(), 'msg' ), $args );
00813                 // RequestContext passes context to wfMessage, and the language is set from
00814                 // the context, but setting the language for Message class removes the
00815                 // interface message status, which breaks for example usernameless gender
00816                 // invokations. Restore the flag when not including special page in content.
00817                 if ( $this->including() ) {
00818                         $message->setInterfaceMessageFlag( false );
00819                 }
00820                 return $message;
00821         }
00822 
00828         protected function addFeedLinks( $params ) {
00829                 global $wgFeedClasses;
00830 
00831                 $feedTemplate = wfScript( 'api' ) . '?';
00832 
00833                 foreach ( $wgFeedClasses as $format => $class ) {
00834                         $theseParams = $params + array( 'feedformat' => $format );
00835                         $url = $feedTemplate . wfArrayToCGI( $theseParams );
00836                         $this->getOutput()->addFeedLink( $format, $url );
00837                 }
00838         }
00839 }
00840 
00846 abstract class FormSpecialPage extends SpecialPage {
00847 
00852         protected abstract function getFormFields();
00853 
00858         protected function preText() { return ''; }
00859         protected function postText() { return ''; }
00860 
00865         protected function alterForm( HTMLForm $form ) {}
00866 
00871         protected function getForm() {
00872                 $this->fields = $this->getFormFields();
00873 
00874                 $form = new HTMLForm( $this->fields, $this->getContext() );
00875                 $form->setSubmitCallback( array( $this, 'onSubmit' ) );
00876                 $form->setWrapperLegend( $this->msg( strtolower( $this->getName() ) . '-legend' ) );
00877                 $form->addHeaderText(
00878                         $this->msg( strtolower( $this->getName() ) . '-text' )->parseAsBlock() );
00879 
00880                 // Retain query parameters (uselang etc)
00881                 $params = array_diff_key(
00882                         $this->getRequest()->getQueryValues(), array( 'title' => null ) );
00883                 $form->addHiddenField( 'redirectparams', wfArrayToCGI( $params ) );
00884 
00885                 $form->addPreText( $this->preText() );
00886                 $form->addPostText( $this->postText() );
00887                 $this->alterForm( $form );
00888 
00889                 // Give hooks a chance to alter the form, adding extra fields or text etc
00890                 wfRunHooks( "Special{$this->getName()}BeforeFormDisplay", array( &$form ) );
00891 
00892                 return $form;
00893         }
00894 
00900         public abstract function onSubmit( array $data );
00901 
00906         public abstract function onSuccess();
00907 
00913         public function execute( $par ) {
00914                 $this->setParameter( $par );
00915                 $this->setHeaders();
00916 
00917                 // This will throw exceptions if there's a problem
00918                 $this->checkExecutePermissions( $this->getUser() );
00919 
00920                 $form = $this->getForm();
00921                 if ( $form->show() ) {
00922                         $this->onSuccess();
00923                 }
00924         }
00925 
00930         protected function setParameter( $par ) {}
00931 
00939         protected function checkExecutePermissions( User $user ) {
00940                 $this->checkPermissions();
00941 
00942                 if ( $this->requiresUnblock() && $user->isBlocked() ) {
00943                         $block = $user->getBlock();
00944                         throw new UserBlockedError( $block );
00945                 }
00946 
00947                 if ( $this->requiresWrite() ) {
00948                         $this->checkReadOnly();
00949                 }
00950 
00951                 return true;
00952         }
00953 
00958         public function requiresWrite() {
00959                 return true;
00960         }
00961 
00966         public function requiresUnblock() {
00967                 return true;
00968         }
00969 }
00970 
00975 class UnlistedSpecialPage extends SpecialPage {
00976         function __construct( $name, $restriction = '', $function = false, $file = 'default' ) {
00977                 parent::__construct( $name, $restriction, false, $function, $file );
00978         }
00979 
00980         public function isListed() {
00981                 return false;
00982         }
00983 }
00984 
00989 class IncludableSpecialPage extends SpecialPage {
00990         function __construct(
00991                 $name, $restriction = '', $listed = true, $function = false, $file = 'default'
00992         ) {
00993                 parent::__construct( $name, $restriction, $listed, $function, $file, true );
00994         }
00995 
00996         public function isIncludable() {
00997                 return true;
00998         }
00999 }
01000 
01005 abstract class RedirectSpecialPage extends UnlistedSpecialPage {
01006 
01007         // Query parameters that can be passed through redirects
01008         protected $mAllowedRedirectParams = array();
01009 
01010         // Query parameteres added by redirects
01011         protected $mAddedRedirectParams = array();
01012 
01013         public function execute( $par ) {
01014                 $redirect = $this->getRedirect( $par );
01015                 $query = $this->getRedirectQuery();
01016                 // Redirect to a page title with possible query parameters
01017                 if ( $redirect instanceof Title ) {
01018                         $url = $redirect->getFullUrl( $query );
01019                         $this->getOutput()->redirect( $url );
01020                         return $redirect;
01021                 // Redirect to index.php with query parameters
01022                 } elseif ( $redirect === true ) {
01023                         global $wgScript;
01024                         $url = $wgScript . '?' . wfArrayToCGI( $query );
01025                         $this->getOutput()->redirect( $url );
01026                         return $redirect;
01027                 } else {
01028                         $class = __CLASS__;
01029                         throw new MWException( "RedirectSpecialPage $class doesn't redirect!" );
01030                 }
01031         }
01032 
01040         abstract public function getRedirect( $par );
01041 
01048         public function getRedirectQuery() {
01049                 $params = array();
01050 
01051                 foreach ( $this->mAllowedRedirectParams as $arg ) {
01052                         if ( $this->getRequest()->getVal( $arg, null ) !== null ) {
01053                                 $params[$arg] = $this->getRequest()->getVal( $arg );
01054                         }
01055                 }
01056 
01057                 foreach ( $this->mAddedRedirectParams as $arg => $val ) {
01058                         $params[$arg] = $val;
01059                 }
01060 
01061                 return count( $params )
01062                         ? $params
01063                         : false;
01064         }
01065 }
01066 
01067 abstract class SpecialRedirectToSpecial extends RedirectSpecialPage {
01068         var $redirName, $redirSubpage;
01069 
01070         function __construct(
01071                 $name, $redirName, $redirSubpage = false,
01072                 $allowedRedirectParams = array(), $addedRedirectParams = array()
01073         ) {
01074                 parent::__construct( $name );
01075                 $this->redirName = $redirName;
01076                 $this->redirSubpage = $redirSubpage;
01077                 $this->mAllowedRedirectParams = $allowedRedirectParams;
01078                 $this->mAddedRedirectParams = $addedRedirectParams;
01079         }
01080 
01081         public function getRedirect( $subpage ) {
01082                 if ( $this->redirSubpage === false ) {
01083                         return SpecialPage::getTitleFor( $this->redirName, $subpage );
01084                 } else {
01085                         return SpecialPage::getTitleFor( $this->redirName, $this->redirSubpage );
01086                 }
01087         }
01088 }
01089 
01093 class SpecialListAdmins extends SpecialRedirectToSpecial {
01094         function __construct() {
01095                 parent::__construct( 'Listadmins', 'Listusers', 'sysop' );
01096         }
01097 }
01098 
01102 class SpecialListBots extends SpecialRedirectToSpecial {
01103         function __construct() {
01104                 parent::__construct( 'Listbots', 'Listusers', 'bot' );
01105         }
01106 }
01107 
01112 class SpecialCreateAccount extends SpecialRedirectToSpecial {
01113         function __construct() {
01114                 parent::__construct( 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) );
01115         }
01116 }
01191 abstract class RedirectSpecialArticle extends RedirectSpecialPage {
01192         function __construct( $name ) {
01193                 parent::__construct( $name );
01194                 $redirectParams = array(
01195                         'action',
01196                         'redirect', 'rdfrom',
01197                         # Options for preloaded edits
01198                         'preload', 'editintro', 'preloadtitle', 'summary',
01199                         # Options for overriding user settings
01200                         'preview', 'internaledit', 'externaledit', 'mode',
01201                         # Options for history/diffs
01202                         'section', 'oldid', 'diff', 'dir',
01203                         'limit', 'offset', 'feed',
01204                         # Misc options
01205                         'redlink', 'debug',
01206                         # Options for action=raw; missing ctype can break JS or CSS in some browsers
01207                         'ctype', 'maxage', 'smaxage',
01208                 );
01209 
01210                 wfRunHooks( "RedirectSpecialArticleRedirectParams", array( &$redirectParams ) );
01211                 $this->mAllowedRedirectParams = $redirectParams;
01212         }
01213 }
01214 
01219 class SpecialMypage extends RedirectSpecialArticle {
01220         function __construct() {
01221                 parent::__construct( 'Mypage' );
01222         }
01223 
01224         function getRedirect( $subpage ) {
01225                 if ( strval( $subpage ) !== '' ) {
01226                         return Title::makeTitle( NS_USER, $this->getUser()->getName() . '/' . $subpage );
01227                 } else {
01228                         return Title::makeTitle( NS_USER, $this->getUser()->getName() );
01229                 }
01230         }
01231 }
01232 
01237 class SpecialMytalk extends RedirectSpecialArticle {
01238         function __construct() {
01239                 parent::__construct( 'Mytalk' );
01240         }
01241 
01242         function getRedirect( $subpage ) {
01243                 if ( strval( $subpage ) !== '' ) {
01244                         return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() . '/' . $subpage );
01245                 } else {
01246                         return Title::makeTitle( NS_USER_TALK, $this->getUser()->getName() );
01247                 }
01248         }
01249 }
01250 
01255 class SpecialMycontributions extends RedirectSpecialPage {
01256         function __construct() {
01257                 parent::__construct(  'Mycontributions' );
01258                 $this->mAllowedRedirectParams = array( 'limit', 'namespace', 'tagfilter',
01259                         'offset', 'dir', 'year', 'month', 'feed' );
01260         }
01261 
01262         function getRedirect( $subpage ) {
01263                 return SpecialPage::getTitleFor( 'Contributions', $this->getUser()->getName() );
01264         }
01265 }
01266 
01270 class SpecialMyuploads extends RedirectSpecialPage {
01271         function __construct() {
01272                 parent::__construct( 'Myuploads' );
01273                 $this->mAllowedRedirectParams = array( 'limit' );
01274         }
01275 
01276         function getRedirect( $subpage ) {
01277                 return SpecialPage::getTitleFor( 'Listfiles', $this->getUser()->getName() );
01278         }
01279 }
01280 
01284 class SpecialPermanentLink extends RedirectSpecialPage {
01285         function __construct() {
01286                 parent::__construct( 'PermanentLink' );
01287                 $this->mAllowedRedirectParams = array();
01288         }
01289 
01290         function getRedirect( $subpage ) {
01291                 $subpage = intval( $subpage );
01292                 if ( $subpage === 0 ) {
01293                         # throw an error page when no subpage was given
01294                         throw new ErrorPageError( 'nopagetitle', 'nopagetext' );
01295                 }
01296                 $this->mAddedRedirectParams['oldid'] = $subpage;
01297                 return true;
01298         }
01299 }