MediaWiki  master
SpecialPage.php
Go to the documentation of this file.
1 <?php
28 
37 class 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 
67  private $linkRenderer;
68 
83  public static function getTitleFor( $name, $subpage = false, $fragment = '' ) {
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() ) ) {
317  $this->displayRestrictionError();
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 
790  public function msg( $key /* $args */ ) {
791  $message = $this->getContext()->msg( ...func_get_args() );
792  // RequestContext passes context to wfMessage, and the language is set from
793  // the context, but setting the language for Message class removes the
794  // interface message status, which breaks for example usernameless gender
795  // invocations. Restore the flag when not including special page in content.
796  if ( $this->including() ) {
797  $message->setInterfaceMessageFlag( false );
798  }
799 
800  return $message;
801  }
802 
808  protected function addFeedLinks( $params ) {
809  $feedTemplate = wfScript( 'api' );
810 
811  foreach ( $this->getConfig()->get( 'FeedClasses' ) as $format => $class ) {
812  $theseParams = $params + [ 'feedformat' => $format ];
813  $url = wfAppendQuery( $feedTemplate, $theseParams );
814  $this->getOutput()->addFeedLink( $format, $url );
815  }
816  }
817 
826  public function addHelpLink( $to, $overrideBaseUrl = false ) {
827  if ( $this->including() ) {
828  return;
829  }
830 
831  $msg = $this->msg(
832  MediaWikiServices::getInstance()->getContentLanguage()->lc( $this->getName() ) .
833  '-helppage' );
834 
835  if ( !$msg->isDisabled() ) {
836  $helpUrl = Skin::makeUrl( $msg->plain() );
837  $this->getOutput()->addHelpLink( $helpUrl, true );
838  } else {
839  $this->getOutput()->addHelpLink( $to, $overrideBaseUrl );
840  }
841  }
842 
851  public function getFinalGroupName() {
852  $name = $this->getName();
853 
854  // Allow overriding the group from the wiki side
855  $msg = $this->msg( 'specialpages-specialpagegroup-' . strtolower( $name ) )->inContentLanguage();
856  if ( !$msg->isBlank() ) {
857  $group = $msg->text();
858  } else {
859  // Than use the group from this object
860  $group = $this->getGroupName();
861  }
862 
863  return $group;
864  }
865 
872  public function doesWrites() {
873  return false;
874  }
875 
884  protected function getGroupName() {
885  return 'other';
886  }
887 
892  protected function useTransactionalTimeLimit() {
893  if ( $this->getRequest()->wasPosted() ) {
895  }
896  }
897 
902  public function getLinkRenderer() {
903  if ( $this->linkRenderer ) {
904  return $this->linkRenderer;
905  } else {
906  return MediaWikiServices::getInstance()->getLinkRenderer();
907  }
908  }
909 
915  $this->linkRenderer = $linkRenderer;
916  }
917 
928  protected function buildPrevNextNavigation( $offset, $limit,
929  array $query = [], $atend = false, $subpage = false
930  ) {
931  $title = $this->getPageTitle( $subpage );
932  $prevNext = new PrevNextNavigationRenderer( $this );
933 
934  return $prevNext->buildPrevNextNavigation( $title, $offset, $limit, $query, $atend );
935  }
936 }
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
Definition: SpecialPage.php:98
if(PHP_SAPI !='cli-server') if(!isset( $_SERVER['SCRIPT_FILENAME'])) $file
Item class for a filearchive table row.
Definition: router.php:42
run( $subPage)
Entry point.
getLoginSecurityLevel()
Tells if the special page does something security-sensitive and needs extra defense against a stolen ...
Helper class for generating prev/next links for paging.
getRestriction()
Get the permission that a user must have to execute this page.
doesWrites()
Indicates whether this special page may perform database writes.
$context
Definition: load.php:45
getContext()
Gets the context this SpecialPage is executed in.
static prefixSearchArray( $search, $limit, array $subpages, $offset)
Helper function for implementations of prefixSearchSubpages() that filter the values in memory (as op...
including( $x=null)
Whether the special page is being evaluated via transclusion.
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
isIncludable()
Whether it&#39;s allowed to transclude the special page via {{Special:Foo/params}}.
setReauthPostData(array $data)
Record preserved POST data after a reauthentication.
maxIncludeCacheTime()
How long to cache page when it is being included.
isExpensive()
Is this page expensive (for some definition of expensive)? Expensive pages are disabled or cached in ...
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:36
listed( $x=null)
Get or set whether this special page is listed in Special:SpecialPages.
getFinalGroupName()
Get the group that the special page belongs in on Special:SpecialPage Use this method, instead of getGroupName to allow customization of the group name from the wiki side.
const NS_SPECIAL
Definition: Defines.php:49
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
beforeExecute( $subPage)
Gets called before.
getOutput()
Get the OutputPage being used for this instance.
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1820
getRobotPolicy()
Return the robot policy.
Redirect a user to the login page.
isListed()
Whether this special page is listed in Special:SpecialPages.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
wfScript( $script='index')
Get the path to a specified script file, respecting file extensions; this is a wrapper around $wgScri...
wfArrayToCgi( $array1, $array2=null, $prefix='')
This function takes one or two arrays as input, and returns a CGI-style string, e.g.
wfAppendQuery( $url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
afterExecute( $subPage)
Gets called after.
buildPrevNextNavigation( $offset, $limit, array $query=[], $atend=false, $subpage=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
const PROTO_HTTPS
Definition: Defines.php:200
IContextSource $mContext
Current request context.
Definition: SpecialPage.php:62
prefixSearchString( $search, $limit, $offset)
Perform a regular substring search for prefixSearchSubpages.
wfReadOnly()
Check whether the wiki is in read-only mode.
setContext( $context)
Sets the context this SpecialPage is executed in.
Class that generates HTML links for pages.
static getMain()
Get the RequestContext object associated with the main request.
displayRestrictionError()
Output an error message telling the user what access level they have to have.
An error page which can definitely be safely rendered using the OutputPage.
getSubpagesForPrefixSearch()
Return an array of subpages that this special page will accept for prefix searches.
isRestricted()
Can be overridden by subclasses with more complicated permissions schemes.
setListed( $listed)
Set whether this page is listed in Special:Specialpages, at run-time.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getSkin()
Shortcut to get the skin being used for this instance.
getDescription()
Returns the name that goes in the <h1> in the special page itself, and also the name that will be l...
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
static generateHex( $chars)
Generate a run of cryptographically random data and return it in hexadecimal string format...
Definition: MWCryptRand.php:36
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don&#39;t need a full Title object...
Definition: SpecialPage.php:83
prefixSearchSubpages( $search, $limit, $offset)
Return an array of subpages beginning with $search that this special page will accept.
isCached()
Is this page cached? Expensive pages are cached or disabled in miser mode.
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1217
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:613
execute( $subPage)
Default execute method Checks user permissions.
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...
addFeedLinks( $params)
Adds RSS/atom links.
__construct( $name='', $restriction='', $listed=true, $function=false, $file='', $includable=false)
Default constructor for special pages Derivative classes should call this from their constructor Note...
getName()
Get the name of this Special Page.
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
requireLogin( $reasonMsg='exception-nologin-text', $titleMsg='exception-nologin')
If the user is not logged in, throws UserNotLoggedIn error.
getUser()
Shortcut to get the User executing this instance.
getConfig()
Shortcut to get main config object.
setLinkRenderer(LinkRenderer $linkRenderer)
Show an error when a user tries to do something they do not have the necessary permissions for...
getLanguage()
Shortcut to get user&#39;s language.
wfTransactionalTimeLimit()
Set PHP&#39;s time limit to the larger of php.ini or $wgTransactionalTimeLimit.
msg( $key)
Wrapper around wfMessage that sets the current context.
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
checkLoginSecurityLevel( $level=null)
Verifies that the user meets the security level, possibly reauthenticating them in the process...
getGroupName()
Under which header this special page is listed in Special:SpecialPages See messages &#39;specialpages-gro...
getFullTitle()
Return the full title, including $par.
checkPermissions()
Checks if userCanExecute, and if not throws a PermissionsError.
static getSafeTitleFor( $name, $subpage=false)
Get a localised Title object for a page name with a possibly unvalidated subpage. ...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getRequest()
Get the WebRequest being used for this instance.
getLocalName()
Get the localised name of the special page.
checkReadOnly()
If the wiki is currently in readonly mode, throws a ReadOnlyError.
static newFromTitleValue(TitleValue $titleValue, $forceClone='')
Returns a Title given a TitleValue.
Definition: Title.php:253
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
getPageTitle( $subpage=false)
Get a self-referential title object.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
MediaWiki Linker LinkRenderer null $linkRenderer
Definition: SpecialPage.php:67
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:316