MediaWiki REL1_34
SpecialPage.php
Go to the documentation of this file.
1<?php
28
37class SpecialPage implements MessageLocalizer {
38 // The canonical name of this special page
39 // Also used for the default <h1> heading, @see getDescription()
40 protected $mName;
41
42 // The local name of this special page
43 private $mLocalName;
44
45 // Minimum user level required to access this page, or "" for anyone.
46 // Also used to categorise the pages in Special:Specialpages
47 protected $mRestriction;
48
49 // Listed in Special:Specialpages?
50 private $mListed;
51
52 // Whether or not this special page is being included from an article
53 protected $mIncluding;
54
55 // Whether the special page can be included in an article
56 protected $mIncludable;
57
62 protected $mContext;
63
68
83 public static function getTitleFor( $name, $subpage = false, $fragment = '' ) {
84 return Title::newFromTitleValue(
85 self::getTitleValueFor( $name, $subpage, $fragment )
86 );
87 }
88
98 public static function getTitleValueFor( $name, $subpage = false, $fragment = '' ) {
99 $name = MediaWikiServices::getInstance()->getSpecialPageFactory()->
100 getLocalNameFor( $name, $subpage );
101
102 return new TitleValue( NS_SPECIAL, $name, $fragment );
103 }
104
112 public static function getSafeTitleFor( $name, $subpage = false ) {
113 $name = MediaWikiServices::getInstance()->getSpecialPageFactory()->
114 getLocalNameFor( $name, $subpage );
115 if ( $name ) {
116 return Title::makeTitleSafe( NS_SPECIAL, $name );
117 } else {
118 return null;
119 }
120 }
121
139 public function __construct(
140 $name = '', $restriction = '', $listed = true,
141 $function = false, $file = '', $includable = false
142 ) {
143 $this->mName = $name;
144 $this->mRestriction = $restriction;
145 $this->mListed = $listed;
146 $this->mIncludable = $includable;
147 }
148
153 function getName() {
154 return $this->mName;
155 }
156
161 function getRestriction() {
162 return $this->mRestriction;
163 }
164
165 // @todo FIXME: Decide which syntax to use for this, and stick to it
166
172 function isListed() {
173 return $this->mListed;
174 }
175
182 function setListed( $listed ) {
183 return wfSetVar( $this->mListed, $listed );
184 }
185
192 function listed( $x = null ) {
193 return wfSetVar( $this->mListed, $x );
194 }
195
200 public function isIncludable() {
201 return $this->mIncludable;
202 }
203
214 public function maxIncludeCacheTime() {
215 return $this->getConfig()->get( 'MiserMode' ) ? $this->getCacheTTL() : 0;
216 }
217
221 protected function getCacheTTL() {
222 return 60 * 60;
223 }
224
230 function including( $x = null ) {
231 return wfSetVar( $this->mIncluding, $x );
232 }
233
238 function getLocalName() {
239 if ( !isset( $this->mLocalName ) ) {
240 $this->mLocalName = MediaWikiServices::getInstance()->getSpecialPageFactory()->
241 getLocalNameFor( $this->mName );
242 }
243
244 return $this->mLocalName;
245 }
246
255 public function isExpensive() {
256 return false;
257 }
258
268 public function isCached() {
269 return false;
270 }
271
279 public function isRestricted() {
280 // DWIM: If anons can do something, then it is not restricted
281 return $this->mRestriction != '' && !MediaWikiServices::getInstance()
282 ->getPermissionManager()
283 ->groupHasPermission( '*', $this->mRestriction );
284 }
285
294 public function userCanExecute( User $user ) {
295 return MediaWikiServices::getInstance()
296 ->getPermissionManager()
297 ->userHasRight( $user, $this->mRestriction );
298 }
299
305 throw new PermissionsError( $this->mRestriction );
306 }
307
315 public function checkPermissions() {
316 if ( !$this->userCanExecute( $this->getUser() ) ) {
318 }
319 }
320
328 public function checkReadOnly() {
329 if ( wfReadOnly() ) {
330 throw new ReadOnlyError;
331 }
332 }
333
345 public function requireLogin(
346 $reasonMsg = 'exception-nologin-text', $titleMsg = 'exception-nologin'
347 ) {
348 if ( $this->getUser()->isAnon() ) {
349 throw new UserNotLoggedIn( $reasonMsg, $titleMsg );
350 }
351 }
352
360 protected function getLoginSecurityLevel() {
361 return false;
362 }
363
378 protected function setReauthPostData( array $data ) {
379 }
380
406 protected function checkLoginSecurityLevel( $level = null ) {
407 $level = $level ?: $this->getName();
408 $key = 'SpecialPage:reauth:' . $this->getName();
409 $request = $this->getRequest();
410
411 $securityStatus = AuthManager::singleton()->securitySensitiveOperationStatus( $level );
412 if ( $securityStatus === AuthManager::SEC_OK ) {
413 $uniqueId = $request->getVal( 'postUniqueId' );
414 if ( $uniqueId ) {
415 $key .= ':' . $uniqueId;
416 $session = $request->getSession();
417 $data = $session->getSecret( $key );
418 if ( $data ) {
419 $session->remove( $key );
420 $this->setReauthPostData( $data );
421 }
422 }
423 return true;
424 } elseif ( $securityStatus === AuthManager::SEC_REAUTH ) {
425 $title = self::getTitleFor( 'Userlogin' );
426 $queryParams = $request->getQueryValues();
427
428 if ( $request->wasPosted() ) {
429 $data = array_diff_assoc( $request->getValues(), $request->getQueryValues() );
430 if ( $data ) {
431 // unique ID in case the same special page is open in multiple browser tabs
432 $uniqueId = MWCryptRand::generateHex( 6 );
433 $key .= ':' . $uniqueId;
434 $queryParams['postUniqueId'] = $uniqueId;
435 $session = $request->getSession();
436 $session->persist(); // Just in case
437 $session->setSecret( $key, $data );
438 }
439 }
440
441 $query = [
442 'returnto' => $this->getFullTitle()->getPrefixedDBkey(),
443 'returntoquery' => wfArrayToCgi( array_diff_key( $queryParams, [ 'title' => true ] ) ),
444 'force' => $level,
445 ];
446 $url = $title->getFullURL( $query, false, PROTO_HTTPS );
447
448 $this->getOutput()->redirect( $url );
449 return false;
450 }
451
452 $titleMessage = wfMessage( 'specialpage-securitylevel-not-allowed-title' );
453 $errorMessage = wfMessage( 'specialpage-securitylevel-not-allowed' );
454 throw new ErrorPageError( $titleMessage, $errorMessage );
455 }
456
473 public function prefixSearchSubpages( $search, $limit, $offset ) {
474 $subpages = $this->getSubpagesForPrefixSearch();
475 if ( !$subpages ) {
476 return [];
477 }
478
479 return self::prefixSearchArray( $search, $limit, $subpages, $offset );
480 }
481
490 protected function getSubpagesForPrefixSearch() {
491 return [];
492 }
493
501 protected function prefixSearchString( $search, $limit, $offset ) {
502 $title = Title::newFromText( $search );
503 if ( !$title || !$title->canExist() ) {
504 // No prefix suggestion in special and media namespace
505 return [];
506 }
507
508 $searchEngine = MediaWikiServices::getInstance()->newSearchEngine();
509 $searchEngine->setLimitOffset( $limit, $offset );
510 $searchEngine->setNamespaces( [] );
511 $result = $searchEngine->defaultPrefixSearch( $search );
512 return array_map( function ( Title $t ) {
513 return $t->getPrefixedText();
514 }, $result );
515 }
516
528 protected static function prefixSearchArray( $search, $limit, array $subpages, $offset ) {
529 $escaped = preg_quote( $search, '/' );
530 return array_slice( preg_grep( "/^$escaped/i",
531 array_slice( $subpages, $offset ) ), 0, $limit );
532 }
533
537 function setHeaders() {
538 $out = $this->getOutput();
539 $out->setArticleRelated( false );
540 $out->setRobotPolicy( $this->getRobotPolicy() );
541 $out->setPageTitle( $this->getDescription() );
542 if ( $this->getConfig()->get( 'UseMediaWikiUIEverywhere' ) ) {
543 $out->addModuleStyles( [
544 'mediawiki.ui.input',
545 'mediawiki.ui.radio',
546 'mediawiki.ui.checkbox',
547 ] );
548 }
549 }
550
558 final public function run( $subPage ) {
568 if ( !Hooks::run( 'SpecialPageBeforeExecute', [ $this, $subPage ] ) ) {
569 return;
570 }
571
572 if ( $this->beforeExecute( $subPage ) === false ) {
573 return;
574 }
575 $this->execute( $subPage );
576 $this->afterExecute( $subPage );
577
586 Hooks::run( 'SpecialPageAfterExecute', [ $this, $subPage ] );
587 }
588
598 protected function beforeExecute( $subPage ) {
599 // No-op
600 }
601
609 protected function afterExecute( $subPage ) {
610 // No-op
611 }
612
621 public function execute( $subPage ) {
622 $this->setHeaders();
623 $this->checkPermissions();
624 $securityLevel = $this->getLoginSecurityLevel();
625 if ( $securityLevel !== false && !$this->checkLoginSecurityLevel( $securityLevel ) ) {
626 return;
627 }
628 $this->outputHeader();
629 }
630
639 function outputHeader( $summaryMessageKey = '' ) {
640 if ( $summaryMessageKey == '' ) {
641 $msg = MediaWikiServices::getInstance()->getContentLanguage()->lc( $this->getName() ) .
642 '-summary';
643 } else {
644 $msg = $summaryMessageKey;
645 }
646 if ( !$this->msg( $msg )->isDisabled() && !$this->including() ) {
647 $this->getOutput()->wrapWikiMsg(
648 "<div class='mw-specialpage-summary'>\n$1\n</div>", $msg );
649 }
650 }
651
661 function getDescription() {
662 return $this->msg( strtolower( $this->mName ) )->text();
663 }
664
672 function getPageTitle( $subpage = false ) {
673 return self::getTitleFor( $this->mName, $subpage );
674 }
675
682 public function setContext( $context ) {
683 $this->mContext = $context;
684 }
685
692 public function getContext() {
693 if ( $this->mContext instanceof IContextSource ) {
694 return $this->mContext;
695 } else {
696 wfDebug( __METHOD__ . " called and \$mContext is null. " .
697 "Return RequestContext::getMain(); for sanity\n" );
698
699 return RequestContext::getMain();
700 }
701 }
702
709 public function getRequest() {
710 return $this->getContext()->getRequest();
711 }
712
719 public function getOutput() {
720 return $this->getContext()->getOutput();
721 }
722
729 public function getUser() {
730 return $this->getContext()->getUser();
731 }
732
739 public function getSkin() {
740 return $this->getContext()->getSkin();
741 }
742
749 public function getLanguage() {
750 return $this->getContext()->getLanguage();
751 }
752
758 public function getConfig() {
759 return $this->getContext()->getConfig();
760 }
761
768 public function getFullTitle() {
769 return $this->getContext()->getTitle();
770 }
771
779 protected function getRobotPolicy() {
780 return 'noindex,nofollow';
781 }
782
792 public function msg( $key, ...$params ) {
793 $message = $this->getContext()->msg( $key, ...$params );
794 // RequestContext passes context to wfMessage, and the language is set from
795 // the context, but setting the language for Message class removes the
796 // interface message status, which breaks for example usernameless gender
797 // invocations. Restore the flag when not including special page in content.
798 if ( $this->including() ) {
799 $message->setInterfaceMessageFlag( false );
800 }
801
802 return $message;
803 }
804
810 protected function addFeedLinks( $params ) {
811 $feedTemplate = wfScript( 'api' );
812
813 foreach ( $this->getConfig()->get( 'FeedClasses' ) as $format => $class ) {
814 $theseParams = $params + [ 'feedformat' => $format ];
815 $url = wfAppendQuery( $feedTemplate, $theseParams );
816 $this->getOutput()->addFeedLink( $format, $url );
817 }
818 }
819
828 public function addHelpLink( $to, $overrideBaseUrl = false ) {
829 if ( $this->including() ) {
830 return;
831 }
832
833 $msg = $this->msg(
834 MediaWikiServices::getInstance()->getContentLanguage()->lc( $this->getName() ) .
835 '-helppage' );
836
837 if ( !$msg->isDisabled() ) {
838 $helpUrl = Skin::makeUrl( $msg->plain() );
839 $this->getOutput()->addHelpLink( $helpUrl, true );
840 } else {
841 $this->getOutput()->addHelpLink( $to, $overrideBaseUrl );
842 }
843 }
844
853 public function getFinalGroupName() {
854 $name = $this->getName();
855
856 // Allow overriding the group from the wiki side
857 $msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
858 if ( !$msg->isBlank() ) {
859 $group = $msg->text();
860 } else {
861 // Than use the group from this object
862 $group = $this->getGroupName();
863 }
864
865 return $group;
866 }
867
874 public function doesWrites() {
875 return false;
876 }
877
886 protected function getGroupName() {
887 return 'other';
888 }
889
894 protected function useTransactionalTimeLimit() {
895 if ( $this->getRequest()->wasPosted() ) {
897 }
898 }
899
904 public function getLinkRenderer() {
905 if ( $this->linkRenderer ) {
906 return $this->linkRenderer;
907 } else {
908 return MediaWikiServices::getInstance()->getLinkRenderer();
909 }
910 }
911
916 public function setLinkRenderer( LinkRenderer $linkRenderer ) {
917 $this->linkRenderer = $linkRenderer;
918 }
919
930 protected function buildPrevNextNavigation( $offset, $limit,
931 array $query = [], $atend = false, $subpage = false
932 ) {
933 $title = $this->getPageTitle( $subpage );
934 $prevNext = new PrevNextNavigationRenderer( $this );
935
936 return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query, $atend );
937 }
938}
getUser()
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfReadOnly()
Check whether the wiki is in read-only mode.
wfSetVar(&$dest, $source, $force=false)
Sets dest to source and returns the original value of dest If source is NULL, it just returns the val...
wfTransactionalTimeLimit()
Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e....
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getContext()
An error page which can definitely be safely rendered using the OutputPage.
Some internal bits split of from Skin.php.
Definition Linker.php:35
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format.
This serves as the entry point to the authentication system.
Class that generates HTML links for pages.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Helper class for generating prev/next links for paging.
Show an error when a user tries to do something they do not have the necessary permissions for.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Parent class for all special pages.
__construct( $name='', $restriction='', $listed=true, $function=false, $file='', $includable=false)
Default constructor for special pages Derivative classes should call this from their constructor Note...
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
setContext( $context)
Sets the context this SpecialPage is executed in.
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages 'specialpages-gro...
getName()
Get the name of this Special Page.
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes!
static getSafeTitleFor( $name, $subpage=false)
Get a localised Title object for a page name with a possibly unvalidated subpage.
getLocalName()
Get the localised name of the special page.
afterExecute( $subPage)
Gets called after.
getRestriction()
Get the permission that a user must have to execute this page.
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
run( $subPage)
Entry point.
getOutput()
Get the OutputPage being used for this instance.
requireLogin( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in, throws UserNotLoggedIn error.
beforeExecute( $subPage)
Gets called before.
checkLoginSecurityLevel( $level=null)
Verifies that the user meets the security level, possibly reauthenticating them in the process.
getUser()
Shortcut to get the User executing this instance.
static prefixSearchArray( $search, $limit, array $subpages, $offset)
Helper function for implementations of prefixSearchSubpages() that filter the values in memory (as op...
setListed( $listed)
Set whether this page is listed in Special:Specialpages, at run-time.
buildPrevNextNavigation( $offset, $limit, array $query=[], $atend=false, $subpage=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
isListed()
Whether this special page is listed in Special:SpecialPages.
getSkin()
Shortcut to get the skin being used for this instance.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
execute( $subPage)
Default execute method Checks user permissions.
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
isCached()
Is this page cached? Expensive pages are cached or disabled in miser mode.
addFeedLinks( $params)
Adds RSS/atom links.
setReauthPostData(array $data)
Record preserved POST data after a reauthentication.
getContext()
Gets the context this SpecialPage is executed in.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
getConfig()
Shortcut to get main config object.
listed( $x=null)
Get or set whether this special page is listed in Special:SpecialPages.
doesWrites()
Indicates whether this special page may perform database writes.
getRequest()
Get the WebRequest being used for this instance.
getFinalGroupName()
Get the group that the special page belongs in on Special:SpecialPage Use this method,...
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
displayRestrictionError()
Output an error message telling the user what access level they have to have.
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept for prefix searches.
getPageTitle( $subpage=false)
Get a self-referential title object.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
getLanguage()
Shortcut to get user's language.
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
getLoginSecurityLevel()
Tells if the special page does something security-sensitive and needs extra defense against a stolen ...
setLinkRenderer(LinkRenderer $linkRenderer)
IContextSource $mContext
Current request context.
including( $x=null)
Whether the special page is being evaluated via transclusion.
maxIncludeCacheTime()
How long to cache page when it is being included.
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
isRestricted()
Can be overridden by subclasses with more complicated permissions schemes.
MediaWiki Linker LinkRenderer null $linkRenderer
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
getFullTitle()
Return the full title, including $par.
getRobotPolicy()
Return the robot policy.
isExpensive()
Is this page expensive (for some definition of expensive)? Expensive pages are disabled or cached in ...
isIncludable()
Whether it's allowed to transclude the special page via {{Special:Foo/params}}.
Represents a page (or page fragment) title within MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:42
Redirect a user to the login page.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:51
const PROTO_HTTPS
Definition Defines.php:209
const NS_SPECIAL
Definition Defines.php:58
Interface for objects which can provide a MediaWiki context on request.
Interface for localizing messages in MediaWiki.
$context
Definition load.php:45
This class serves as a utility class for this extension.
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition router.php:42