MediaWiki  master
PermissionManager.php
Go to the documentation of this file.
1 <?php
21 
22 use Action;
23 use Exception;
24 use Hooks;
34 use NamespaceInfo;
35 use RequestContext;
36 use SpecialPage;
37 use Title;
38 use User;
40 use WikiPage;
41 
49 
51  const RIGOR_QUICK = 'quick';
52 
54  const RIGOR_FULL = 'full';
55 
57  const RIGOR_SECURE = 'secure';
58 
63  public const CONSTRUCTOR_OPTIONS = [
64  'WhitelistRead',
65  'WhitelistReadRegexp',
66  'EmailConfirmToEdit',
67  'BlockDisablesLogin',
68  'GroupPermissions',
69  'RevokePermissions',
70  'AvailableRights',
71  'NamespaceProtection',
72  'RestrictionLevels'
73  ];
74 
76  private $options;
77 
80 
82  private $revisionLookup;
83 
85  private $nsInfo;
86 
88  private $allRights;
89 
92 
94  private $usersRights = null;
95 
100  private $temporaryUserRights = [];
101 
103  private $cachedRights = [];
104 
111  private $coreRights = [
112  'apihighlimits',
113  'applychangetags',
114  'autoconfirmed',
115  'autocreateaccount',
116  'autopatrol',
117  'bigdelete',
118  'block',
119  'blockemail',
120  'bot',
121  'browsearchive',
122  'changetags',
123  'createaccount',
124  'createpage',
125  'createtalk',
126  'delete',
127  'deletechangetags',
128  'deletedhistory',
129  'deletedtext',
130  'deletelogentry',
131  'deleterevision',
132  'edit',
133  'editcontentmodel',
134  'editinterface',
135  'editprotected',
136  'editmyoptions',
137  'editmyprivateinfo',
138  'editmyusercss',
139  'editmyuserjson',
140  'editmyuserjs',
141  'editmyuserjsredirect',
142  'editmywatchlist',
143  'editsemiprotected',
144  'editsitecss',
145  'editsitejson',
146  'editsitejs',
147  'editusercss',
148  'edituserjson',
149  'edituserjs',
150  'hideuser',
151  'import',
152  'importupload',
153  'ipblock-exempt',
154  'managechangetags',
155  'markbotedits',
156  'mergehistory',
157  'minoredit',
158  'move',
159  'movefile',
160  'move-categorypages',
161  'move-rootuserpages',
162  'move-subpages',
163  'nominornewtalk',
164  'noratelimit',
165  'override-export-depth',
166  'pagelang',
167  'patrol',
168  'patrolmarks',
169  'protect',
170  'purge',
171  'read',
172  'reupload',
173  'reupload-own',
174  'reupload-shared',
175  'rollback',
176  'sendemail',
177  'siteadmin',
178  'suppressionlog',
179  'suppressredirect',
180  'suppressrevision',
181  'unblockself',
182  'undelete',
183  'unwatchedpages',
184  'upload',
185  'upload_by_url',
186  'userrights',
187  'userrights-interwiki',
188  'viewmyprivateinfo',
189  'viewmywatchlist',
190  'viewsuppressed',
191  'writeapi',
192  ];
193 
201  public function __construct(
207  ) {
208  $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
209  $this->options = $options;
210  $this->specialPageFactory = $specialPageFactory;
211  $this->revisionLookup = $revisionLookup;
212  $this->nsInfo = $nsInfo;
213  $this->blockErrorFormatter = $blockErrorFormatter;
214  }
215 
235  public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
236  return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
237  }
238 
254  public function quickUserCan( $action, User $user, LinkTarget $page ) {
255  return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
256  }
257 
275  public function getPermissionErrors(
276  $action,
277  User $user,
278  LinkTarget $page,
279  $rigor = self::RIGOR_SECURE,
280  $ignoreErrors = []
281  ) {
282  $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
283 
284  // Remove the errors being ignored.
285  foreach ( $errors as $index => $error ) {
286  $errKey = is_array( $error ) ? $error[0] : $error;
287 
288  if ( in_array( $errKey, $ignoreErrors ) ) {
289  unset( $errors[$index] );
290  }
291  if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
292  unset( $errors[$index] );
293  }
294  }
295 
296  return $errors;
297  }
298 
309  public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
310  $block = $user->getBlock( $fromReplica );
311  if ( !$block ) {
312  return false;
313  }
314 
315  // TODO: remove upon further migration to LinkTarget
316  $title = Title::newFromLinkTarget( $page );
317 
318  $blocked = $user->isHidden();
319  if ( !$blocked ) {
320  // Special handling for a user's own talk page. The block is not aware
321  // of the user, so this must be done here.
322  if ( $title->equals( $user->getTalkPage() ) ) {
323  $blocked = $block->appliesToUsertalk( $title );
324  } else {
325  $blocked = $block->appliesToTitle( $title );
326  }
327  }
328 
329  // only for the purpose of the hook. We really don't need this here.
330  $allowUsertalk = $user->isAllowUsertalk();
331 
332  // Allow extensions to let a blocked user access a particular page
333  Hooks::run( 'UserIsBlockedFrom', [ $user, $title, &$blocked, &$allowUsertalk ] );
334 
335  return $blocked;
336  }
337 
355  private function getPermissionErrorsInternal(
356  $action,
357  User $user,
358  LinkTarget $page,
359  $rigor = self::RIGOR_SECURE,
360  $short = false
361  ) {
362  if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
363  throw new Exception( "Invalid rigor parameter '$rigor'." );
364  }
365 
366  # Read has special handling
367  if ( $action == 'read' ) {
368  $checks = [
369  'checkPermissionHooks',
370  'checkReadPermissions',
371  'checkUserBlock', // for wgBlockDisablesLogin
372  ];
373  # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
374  # or checkUserConfigPermissions here as it will lead to duplicate
375  # error messages. This is okay to do since anywhere that checks for
376  # create will also check for edit, and those checks are called for edit.
377  } elseif ( $action == 'create' ) {
378  $checks = [
379  'checkQuickPermissions',
380  'checkPermissionHooks',
381  'checkPageRestrictions',
382  'checkCascadingSourcesRestrictions',
383  'checkActionPermissions',
384  'checkUserBlock'
385  ];
386  } else {
387  $checks = [
388  'checkQuickPermissions',
389  'checkPermissionHooks',
390  'checkSpecialsAndNSPermissions',
391  'checkSiteConfigPermissions',
392  'checkUserConfigPermissions',
393  'checkPageRestrictions',
394  'checkCascadingSourcesRestrictions',
395  'checkActionPermissions',
396  'checkUserBlock'
397  ];
398  }
399 
400  $errors = [];
401  foreach ( $checks as $method ) {
402  $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
403 
404  if ( $short && $errors !== [] ) {
405  break;
406  }
407  }
408 
409  return $errors;
410  }
411 
428  private function checkPermissionHooks(
429  $action,
430  User $user,
431  $errors,
432  $rigor,
433  $short,
434  LinkTarget $page
435  ) {
436  // TODO: remove when LinkTarget usage will expand further
437  $title = Title::newFromLinkTarget( $page );
438  // Use getUserPermissionsErrors instead
439  $result = '';
440  if ( !Hooks::run( 'userCan', [ &$title, &$user, $action, &$result ] ) ) {
441  return $result ? [] : [ [ 'badaccess-group0' ] ];
442  }
443  // Check getUserPermissionsErrors hook
444  if ( !Hooks::run( 'getUserPermissionsErrors', [ &$title, &$user, $action, &$result ] ) ) {
445  $errors = $this->resultToError( $errors, $result );
446  }
447  // Check getUserPermissionsErrorsExpensive hook
448  if (
449  $rigor !== self::RIGOR_QUICK
450  && !( $short && count( $errors ) > 0 )
451  && !Hooks::run( 'getUserPermissionsErrorsExpensive', [ &$title, &$user, $action, &$result ] )
452  ) {
453  $errors = $this->resultToError( $errors, $result );
454  }
455 
456  return $errors;
457  }
458 
467  private function resultToError( $errors, $result ) {
468  if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
469  // A single array representing an error
470  $errors[] = $result;
471  } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
472  // A nested array representing multiple errors
473  $errors = array_merge( $errors, $result );
474  } elseif ( $result !== '' && is_string( $result ) ) {
475  // A string representing a message-id
476  $errors[] = [ $result ];
477  } elseif ( $result instanceof MessageSpecifier ) {
478  // A message specifier representing an error
479  $errors[] = [ $result ];
480  } elseif ( $result === false ) {
481  // a generic "We don't want them to do that"
482  $errors[] = [ 'badaccess-group0' ];
483  }
484  return $errors;
485  }
486 
503  private function checkReadPermissions(
504  $action,
505  User $user,
506  $errors,
507  $rigor,
508  $short,
509  LinkTarget $page
510  ) {
511  // TODO: remove when LinkTarget usage will expand further
512  $title = Title::newFromLinkTarget( $page );
513 
514  $whiteListRead = $this->options->get( 'WhitelistRead' );
515  $whitelisted = false;
516  if ( $this->isEveryoneAllowed( 'read' ) ) {
517  # Shortcut for public wikis, allows skipping quite a bit of code
518  $whitelisted = true;
519  } elseif ( $this->userHasRight( $user, 'read' ) ) {
520  # If the user is allowed to read pages, he is allowed to read all pages
521  $whitelisted = true;
522  } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
523  || $this->isSameSpecialPage( 'PasswordReset', $title )
524  || $this->isSameSpecialPage( 'Userlogout', $title )
525  ) {
526  # Always grant access to the login page.
527  # Even anons need to be able to log in.
528  $whitelisted = true;
529  } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
530  # Time to check the whitelist
531  # Only do these checks is there's something to check against
532  $name = $title->getPrefixedText();
533  $dbName = $title->getPrefixedDBkey();
534 
535  // Check for explicit whitelisting with and without underscores
536  if ( in_array( $name, $whiteListRead, true )
537  || in_array( $dbName, $whiteListRead, true ) ) {
538  $whitelisted = true;
539  } elseif ( $title->getNamespace() == NS_MAIN ) {
540  # Old settings might have the title prefixed with
541  # a colon for main-namespace pages
542  if ( in_array( ':' . $name, $whiteListRead ) ) {
543  $whitelisted = true;
544  }
545  } elseif ( $title->isSpecialPage() ) {
546  # If it's a special page, ditch the subpage bit and check again
547  $name = $title->getDBkey();
548  list( $name, /* $subpage */ ) =
549  $this->specialPageFactory->resolveAlias( $name );
550  if ( $name ) {
551  $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
552  if ( in_array( $pure, $whiteListRead, true ) ) {
553  $whitelisted = true;
554  }
555  }
556  }
557  }
558 
559  $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
560  if ( !$whitelisted && is_array( $whitelistReadRegexp )
561  && !empty( $whitelistReadRegexp ) ) {
562  $name = $title->getPrefixedText();
563  // Check for regex whitelisting
564  foreach ( $whitelistReadRegexp as $listItem ) {
565  if ( preg_match( $listItem, $name ) ) {
566  $whitelisted = true;
567  break;
568  }
569  }
570  }
571 
572  if ( !$whitelisted ) {
573  # If the title is not whitelisted, give extensions a chance to do so...
574  Hooks::run( 'TitleReadWhitelist', [ $title, $user, &$whitelisted ] );
575  if ( !$whitelisted ) {
576  $errors[] = $this->missingPermissionError( $action, $short );
577  }
578  }
579 
580  return $errors;
581  }
582 
591  private function missingPermissionError( $action, $short ) {
592  // We avoid expensive display logic for quickUserCan's and such
593  if ( $short ) {
594  return [ 'badaccess-group0' ];
595  }
596 
597  // TODO: it would be a good idea to replace the method below with something else like
598  // maybe callback injection
599  return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
600  }
601 
610  private function isSameSpecialPage( $name, LinkTarget $page ) {
611  if ( $page->getNamespace() == NS_SPECIAL ) {
612  list( $thisName, /* $subpage */ ) =
613  $this->specialPageFactory->resolveAlias( $page->getDBkey() );
614  if ( $name == $thisName ) {
615  return true;
616  }
617  }
618  return false;
619  }
620 
637  private function checkUserBlock(
638  $action,
639  User $user,
640  $errors,
641  $rigor,
642  $short,
643  LinkTarget $page
644  ) {
645  // Account creation blocks handled at userlogin.
646  // Unblocking handled in SpecialUnblock
647  if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
648  return $errors;
649  }
650 
651  // Optimize for a very common case
652  if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
653  return $errors;
654  }
655 
656  if ( $this->options->get( 'EmailConfirmToEdit' )
657  && !$user->isEmailConfirmed()
658  && $action === 'edit'
659  ) {
660  $errors[] = [ 'confirmedittext' ];
661  }
662 
663  $useReplica = ( $rigor !== self::RIGOR_SECURE );
664  $block = $user->getBlock( $useReplica );
665 
666  // If the user does not have a block, or the block they do have explicitly
667  // allows the action (like "read" or "upload").
668  if ( !$block || $block->appliesToRight( $action ) === false ) {
669  return $errors;
670  }
671 
672  // Determine if the user is blocked from this action on this page.
673  // What gets passed into this method is a user right, not an action name.
674  // There is no way to instantiate an action by restriction. However, this
675  // will get the action where the restriction is the same. This may result
676  // in actions being blocked that shouldn't be.
677  $actionObj = null;
678  if ( Action::exists( $action ) ) {
679  // TODO: this drags a ton of dependencies in, would be good to avoid WikiPage
680  // instantiation and decouple it creating an ActionPermissionChecker interface
681  $wikiPage = WikiPage::factory( Title::newFromLinkTarget( $page, 'clone' ) );
682  // Creating an action will perform several database queries to ensure that
683  // the action has not been overridden by the content type.
684  // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
685  // probably we may use fake context object since it's unlikely that Action uses it
686  // anyway. It would be nice if we could avoid instantiating the Action at all.
687  $actionObj = Action::factory( $action, $wikiPage, RequestContext::getMain() );
688  // Ensure that the retrieved action matches the restriction.
689  if ( $actionObj && $actionObj->getRestriction() !== $action ) {
690  $actionObj = null;
691  }
692  }
693 
694  // If no action object is returned, assume that the action requires unblock
695  // which is the default.
696  if ( !$actionObj || $actionObj->requiresUnblock() ) {
697  if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
698  // @todo FIXME: Pass the relevant context into this function.
700  $message = $this->blockErrorFormatter->getMessage(
701  $block,
702  $context->getUser(),
704  $context->getRequest()->getIP()
705  );
706  $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
707  }
708  }
709 
710  return $errors;
711  }
712 
729  private function checkQuickPermissions(
730  $action,
731  User $user,
732  $errors,
733  $rigor,
734  $short,
735  LinkTarget $page
736  ) {
737  // TODO: remove when LinkTarget usage will expand further
738  $title = Title::newFromLinkTarget( $page );
739 
740  if ( !Hooks::run( 'TitleQuickPermissions',
741  [ $title, $user, $action, &$errors, ( $rigor !== self::RIGOR_QUICK ), $short ] )
742  ) {
743  return $errors;
744  }
745 
746  $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
747  strpos( $title->getText(), '/' ) !== false : false;
748 
749  if ( $action == 'create' ) {
750  if (
751  ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
752  !$this->userHasRight( $user, 'createtalk' ) ) ||
753  ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
754  !$this->userHasRight( $user, 'createpage' ) )
755  ) {
756  $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
757  }
758  } elseif ( $action == 'move' ) {
759  if ( !$this->userHasRight( $user, 'move-rootuserpages' )
760  && $title->getNamespace() == NS_USER && !$isSubPage ) {
761  // Show user page-specific message only if the user can move other pages
762  $errors[] = [ 'cant-move-user-page' ];
763  }
764 
765  // Check if user is allowed to move files if it's a file
766  if ( $title->getNamespace() == NS_FILE &&
767  !$this->userHasRight( $user, 'movefile' ) ) {
768  $errors[] = [ 'movenotallowedfile' ];
769  }
770 
771  // Check if user is allowed to move category pages if it's a category page
772  if ( $title->getNamespace() == NS_CATEGORY &&
773  !$this->userHasRight( $user, 'move-categorypages' ) ) {
774  $errors[] = [ 'cant-move-category-page' ];
775  }
776 
777  if ( !$this->userHasRight( $user, 'move' ) ) {
778  // User can't move anything
779  $userCanMove = $this->groupHasPermission( 'user', 'move' );
780  $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
781  if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
782  // custom message if logged-in users without any special rights can move
783  $errors[] = [ 'movenologintext' ];
784  } else {
785  $errors[] = [ 'movenotallowed' ];
786  }
787  }
788  } elseif ( $action == 'move-target' ) {
789  if ( !$this->userHasRight( $user, 'move' ) ) {
790  // User can't move anything
791  $errors[] = [ 'movenotallowed' ];
792  } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
793  && $title->getNamespace() == NS_USER && !$isSubPage ) {
794  // Show user page-specific message only if the user can move other pages
795  $errors[] = [ 'cant-move-to-user-page' ];
796  } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
797  && $title->getNamespace() == NS_CATEGORY ) {
798  // Show category page-specific message only if the user can move other pages
799  $errors[] = [ 'cant-move-to-category-page' ];
800  }
801  } elseif ( !$this->userHasRight( $user, $action ) ) {
802  $errors[] = $this->missingPermissionError( $action, $short );
803  }
804 
805  return $errors;
806  }
807 
826  private function checkPageRestrictions(
827  $action,
828  User $user,
829  $errors,
830  $rigor,
831  $short,
832  LinkTarget $page
833  ) {
834  // TODO: remove & rework upon further use of LinkTarget
835  $title = Title::newFromLinkTarget( $page );
836  foreach ( $title->getRestrictions( $action ) as $right ) {
837  // Backwards compatibility, rewrite sysop -> editprotected
838  if ( $right == 'sysop' ) {
839  $right = 'editprotected';
840  }
841  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
842  if ( $right == 'autoconfirmed' ) {
843  $right = 'editsemiprotected';
844  }
845  if ( $right == '' ) {
846  continue;
847  }
848  if ( !$this->userHasRight( $user, $right ) ) {
849  $errors[] = [ 'protectedpagetext', $right, $action ];
850  } elseif ( $title->areRestrictionsCascading() &&
851  !$this->userHasRight( $user, 'protect' ) ) {
852  $errors[] = [ 'protectedpagetext', 'protect', $action ];
853  }
854  }
855 
856  return $errors;
857  }
858 
876  $action,
877  UserIdentity $user,
878  $errors,
879  $rigor,
880  $short,
881  LinkTarget $page
882  ) {
883  // TODO: remove & rework upon further use of LinkTarget
884  $title = Title::newFromLinkTarget( $page );
885  if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
886  # We /could/ use the protection level on the source page, but it's
887  # fairly ugly as we have to establish a precedence hierarchy for pages
888  # included by multiple cascade-protected pages. So just restrict
889  # it to people with 'protect' permission, as they could remove the
890  # protection anyway.
891  list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
892  # Cascading protection depends on more than this page...
893  # Several cascading protected pages may include this page...
894  # Check each cascading level
895  # This is only for protection restrictions, not for all actions
896  if ( isset( $restrictions[$action] ) ) {
897  foreach ( $restrictions[$action] as $right ) {
898  // Backwards compatibility, rewrite sysop -> editprotected
899  if ( $right == 'sysop' ) {
900  $right = 'editprotected';
901  }
902  // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
903  if ( $right == 'autoconfirmed' ) {
904  $right = 'editsemiprotected';
905  }
906  if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
907  $wikiPages = '';
909  foreach ( $cascadingSources as $wikiPage ) {
910  $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
911  }
912  $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
913  }
914  }
915  }
916  }
917 
918  return $errors;
919  }
920 
937  private function checkActionPermissions(
938  $action,
939  User $user,
940  $errors,
941  $rigor,
942  $short,
943  LinkTarget $page
944  ) {
946 
947  // TODO: remove & rework upon further use of LinkTarget
948  $title = Title::newFromLinkTarget( $page );
949 
950  if ( $action == 'protect' ) {
951  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
952  // If they can't edit, they shouldn't protect.
953  $errors[] = [ 'protect-cantedit' ];
954  }
955  } elseif ( $action == 'create' ) {
956  $title_protection = $title->getTitleProtection();
957  if ( $title_protection ) {
958  if ( $title_protection['permission'] == ''
959  || !$this->userHasRight( $user, $title_protection['permission'] )
960  ) {
961  $errors[] = [
962  'titleprotected',
963  // TODO: get rid of the User dependency
964  User::whoIs( $title_protection['user'] ),
965  $title_protection['reason']
966  ];
967  }
968  }
969  } elseif ( $action == 'move' ) {
970  // Check for immobile pages
971  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
972  // Specific message for this case
973  $errors[] = [ 'immobile-source-namespace', $title->getNsText() ];
974  } elseif ( !$title->isMovable() ) {
975  // Less specific message for rarer cases
976  $errors[] = [ 'immobile-source-page' ];
977  }
978  } elseif ( $action == 'move-target' ) {
979  if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
980  $errors[] = [ 'immobile-target-namespace', $title->getNsText() ];
981  } elseif ( !$title->isMovable() ) {
982  $errors[] = [ 'immobile-target-page' ];
983  }
984  } elseif ( $action == 'delete' ) {
985  $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
986  if ( !$tempErrors ) {
987  $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
988  $user, $tempErrors, $rigor, true, $title );
989  }
990  if ( $tempErrors ) {
991  // If protection keeps them from editing, they shouldn't be able to delete.
992  $errors[] = [ 'deleteprotected' ];
993  }
994  if ( $rigor !== self::RIGOR_QUICK && $wgDeleteRevisionsLimit
995  && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
996  ) {
997  $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
998  }
999  } elseif ( $action === 'undelete' ) {
1000  if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1001  // Undeleting implies editing
1002  $errors[] = [ 'undelete-cantedit' ];
1003  }
1004  if ( !$title->exists()
1005  && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1006  ) {
1007  // Undeleting where nothing currently exists implies creating
1008  $errors[] = [ 'undelete-cantcreate' ];
1009  }
1010  }
1011  return $errors;
1012  }
1013 
1031  $action,
1032  UserIdentity $user,
1033  $errors,
1034  $rigor,
1035  $short,
1036  LinkTarget $page
1037  ) {
1038  // TODO: remove & rework upon further use of LinkTarget
1039  $title = Title::newFromLinkTarget( $page );
1040 
1041  # Only 'createaccount' can be performed on special pages,
1042  # which don't actually exist in the DB.
1043  if ( $title->getNamespace() == NS_SPECIAL && $action !== 'createaccount' ) {
1044  $errors[] = [ 'ns-specialprotected' ];
1045  }
1046 
1047  # Check $wgNamespaceProtection for restricted namespaces
1048  if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1049  $ns = $title->getNamespace() == NS_MAIN ?
1050  wfMessage( 'nstab-main' )->text() : $title->getNsText();
1051  $errors[] = $title->getNamespace() == NS_MEDIAWIKI ?
1052  [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1053  }
1054 
1055  return $errors;
1056  }
1057 
1074  private function checkSiteConfigPermissions(
1075  $action,
1076  User $user,
1077  $errors,
1078  $rigor,
1079  $short,
1080  LinkTarget $page
1081  ) {
1082  // TODO: remove & rework upon further use of LinkTarget
1083  $title = Title::newFromLinkTarget( $page );
1084 
1085  if ( $action != 'patrol' ) {
1086  $error = null;
1087  // Sitewide CSS/JSON/JS changes, like all NS_MEDIAWIKI changes, also require the
1088  // editinterface right. That's implemented as a restriction so no check needed here.
1089  if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1090  $error = [ 'sitecssprotected', $action ];
1091  } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1092  $error = [ 'sitejsonprotected', $action ];
1093  } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1094  $error = [ 'sitejsprotected', $action ];
1095  } elseif ( $title->isRawHtmlMessage() ) {
1096  // Raw HTML can be used to deploy CSS or JS so require rights for both.
1097  if ( !$this->userHasRight( $user, 'editsitejs' ) ) {
1098  $error = [ 'sitejsprotected', $action ];
1099  } elseif ( !$this->userHasRight( $user, 'editsitecss' ) ) {
1100  $error = [ 'sitecssprotected', $action ];
1101  }
1102  }
1103 
1104  if ( $error ) {
1105  if ( $this->userHasRight( $user, 'editinterface' ) ) {
1106  // Most users / site admins will probably find out about the new, more restrictive
1107  // permissions by failing to edit something. Give them more info.
1108  // TODO remove this a few release cycles after 1.32
1109  $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
1110  }
1111  $errors[] = $error;
1112  }
1113  }
1114 
1115  return $errors;
1116  }
1117 
1134  private function checkUserConfigPermissions(
1135  $action,
1136  UserIdentity $user,
1137  $errors,
1138  $rigor,
1139  $short,
1140  LinkTarget $page
1141  ) {
1142  // TODO: remove & rework upon further use of LinkTarget
1143  $title = Title::newFromLinkTarget( $page );
1144 
1145  # Protect css/json/js subpages of user pages
1146  # XXX: this might be better using restrictions
1147 
1148  if ( $action === 'patrol' ) {
1149  return $errors;
1150  }
1151 
1152  if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1153  // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1154  if (
1155  $title->isUserCssConfigPage()
1156  && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1157  ) {
1158  $errors[] = [ 'mycustomcssprotected', $action ];
1159  } elseif (
1160  $title->isUserJsonConfigPage()
1161  && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1162  ) {
1163  $errors[] = [ 'mycustomjsonprotected', $action ];
1164  } elseif (
1165  $title->isUserJsConfigPage()
1166  && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1167  ) {
1168  $errors[] = [ 'mycustomjsprotected', $action ];
1169  } elseif (
1170  $title->isUserJsConfigPage()
1171  && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1172  ) {
1173  // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1174  $rev = $this->revisionLookup->getRevisionByTitle( $title );
1175  $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1176  $target = $content ? $content->getUltimateRedirectTarget() : null;
1177  if ( $target && (
1178  !$target->inNamespace( NS_USER )
1179  || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1180  ) ) {
1181  $errors[] = [ 'mycustomjsredirectprotected', $action ];
1182  }
1183  }
1184  } else {
1185  // Users need edituser* to edit others' CSS/JSON/JS subpages, except for
1186  // deletion/suppression which cannot be used for attacks and we want to avoid the
1187  // situation where an unprivileged user can post abusive content on their subpages
1188  // and only very highly privileged users could remove it.
1189  if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
1190  if (
1191  $title->isUserCssConfigPage()
1192  && !$this->userHasRight( $user, 'editusercss' )
1193  ) {
1194  $errors[] = [ 'customcssprotected', $action ];
1195  } elseif (
1196  $title->isUserJsonConfigPage()
1197  && !$this->userHasRight( $user, 'edituserjson' )
1198  ) {
1199  $errors[] = [ 'customjsonprotected', $action ];
1200  } elseif (
1201  $title->isUserJsConfigPage()
1202  && !$this->userHasRight( $user, 'edituserjs' )
1203  ) {
1204  $errors[] = [ 'customjsprotected', $action ];
1205  }
1206  }
1207  }
1208 
1209  return $errors;
1210  }
1211 
1222  public function userHasRight( UserIdentity $user, $action = '' ) {
1223  if ( $action === '' ) {
1224  return true; // In the spirit of DWIM
1225  }
1226  // Use strict parameter to avoid matching numeric 0 accidentally inserted
1227  // by misconfiguration: 0 == 'foo'
1228  return in_array( $action, $this->getUserPermissions( $user ), true );
1229  }
1230 
1239  public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1240  foreach ( $actions as $action ) {
1241  if ( $this->userHasRight( $user, $action ) ) {
1242  return true;
1243  }
1244  }
1245  return false;
1246  }
1247 
1256  public function userHasAllRights( UserIdentity $user, ...$actions ) {
1257  foreach ( $actions as $action ) {
1258  if ( !$this->userHasRight( $user, $action ) ) {
1259  return false;
1260  }
1261  }
1262  return true;
1263  }
1264 
1274  public function getUserPermissions( UserIdentity $user ) {
1275  $user = User::newFromIdentity( $user );
1276  $rightsCacheKey = $this->getRightsCacheKey( $user );
1277  if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1278  $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1279  $user->getEffectiveGroups()
1280  );
1281  Hooks::run( 'UserGetRights', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1282 
1283  // Deny any rights denied by the user's session, unless this
1284  // endpoint has no sessions.
1285  if ( !defined( 'MW_NO_SESSION' ) ) {
1286  // FIXME: $user->getRequest().. need to be replaced with something else
1287  $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1288  if ( $allowedRights !== null ) {
1289  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1290  $this->usersRights[ $rightsCacheKey ],
1291  $allowedRights
1292  );
1293  }
1294  }
1295 
1296  Hooks::run( 'UserGetRightsRemove', [ $user, &$this->usersRights[ $rightsCacheKey ] ] );
1297  // Force reindexation of rights when a hook has unset one of them
1298  $this->usersRights[ $rightsCacheKey ] = array_values(
1299  array_unique( $this->usersRights[ $rightsCacheKey ] )
1300  );
1301 
1302  if (
1303  $user->isLoggedIn() &&
1304  $this->options->get( 'BlockDisablesLogin' ) &&
1305  $user->getBlock()
1306  ) {
1307  $anon = new User;
1308  $this->usersRights[ $rightsCacheKey ] = array_intersect(
1309  $this->usersRights[ $rightsCacheKey ],
1310  $this->getUserPermissions( $anon )
1311  );
1312  }
1313  }
1314  $rights = $this->usersRights[ $rightsCacheKey ];
1315  foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1316  $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1317  }
1318  return $rights;
1319  }
1320 
1329  public function invalidateUsersRightsCache( $user = null ) {
1330  if ( $user !== null ) {
1331  $rightsCacheKey = $this->getRightsCacheKey( $user );
1332  if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1333  unset( $this->usersRights[ $rightsCacheKey ] );
1334  }
1335  } else {
1336  $this->usersRights = null;
1337  }
1338  }
1339 
1345  private function getRightsCacheKey( UserIdentity $user ) {
1346  return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1347  }
1348 
1363  public function groupHasPermission( $group, $role ) {
1364  $groupPermissions = $this->options->get( 'GroupPermissions' );
1365  $revokePermissions = $this->options->get( 'RevokePermissions' );
1366  return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
1367  !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
1368  }
1369 
1378  public function getGroupPermissions( $groups ) {
1379  $rights = [];
1380  // grant every granted permission first
1381  foreach ( $groups as $group ) {
1382  if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
1383  $rights = array_merge( $rights,
1384  // array_filter removes empty items
1385  array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
1386  }
1387  }
1388  // now revoke the revoked permissions
1389  foreach ( $groups as $group ) {
1390  if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
1391  $rights = array_diff( $rights,
1392  array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
1393  }
1394  }
1395  return array_unique( $rights );
1396  }
1397 
1406  public function getGroupsWithPermission( $role ) {
1407  $allowedGroups = [];
1408  foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
1409  if ( $this->groupHasPermission( $group, $role ) ) {
1410  $allowedGroups[] = $group;
1411  }
1412  }
1413  return $allowedGroups;
1414  }
1415 
1431  public function isEveryoneAllowed( $right ) {
1432  // Use the cached results, except in unit tests which rely on
1433  // being able change the permission mid-request
1434  if ( isset( $this->cachedRights[$right] ) ) {
1435  return $this->cachedRights[$right];
1436  }
1437 
1438  if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1439  || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1440  $this->cachedRights[$right] = false;
1441  return false;
1442  }
1443 
1444  // If it's revoked anywhere, then everyone doesn't have it
1445  foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1446  if ( isset( $rights[$right] ) && $rights[$right] ) {
1447  $this->cachedRights[$right] = false;
1448  return false;
1449  }
1450  }
1451 
1452  // Remove any rights that aren't allowed to the global-session user,
1453  // unless there are no sessions for this endpoint.
1454  if ( !defined( 'MW_NO_SESSION' ) ) {
1455 
1456  // XXX: think what could be done with the below
1457  $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1458  if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1459  $this->cachedRights[$right] = false;
1460  return false;
1461  }
1462  }
1463 
1464  // Allow extensions to say false
1465  if ( !Hooks::run( 'UserIsEveryoneAllowed', [ $right ] ) ) {
1466  $this->cachedRights[$right] = false;
1467  return false;
1468  }
1469 
1470  $this->cachedRights[$right] = true;
1471  return true;
1472  }
1473 
1481  public function getAllPermissions() {
1482  if ( $this->allRights === null ) {
1483  if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1484  $this->allRights = array_unique( array_merge(
1485  $this->coreRights,
1486  $this->options->get( 'AvailableRights' )
1487  ) );
1488  } else {
1489  $this->allRights = $this->coreRights;
1490  }
1491  Hooks::run( 'UserGetAllRights', [ &$this->allRights ] );
1492  }
1493  return $this->allRights;
1494  }
1495 
1502  private function isNamespaceProtected( $index, UserIdentity $user ) {
1503  $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1504  if ( isset( $namespaceProtection[$index] ) ) {
1505  return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1506  }
1507  return false;
1508  }
1509 
1518  public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1519  if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1520  // All levels are valid if there's no namespace restriction.
1521  // But still filter by user, if necessary
1522  $levels = $this->options->get( 'RestrictionLevels' );
1523  if ( $user ) {
1524  $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1525  $right = $level;
1526  if ( $right == 'sysop' ) {
1527  $right = 'editprotected'; // BC
1528  }
1529  if ( $right == 'autoconfirmed' ) {
1530  $right = 'editsemiprotected'; // BC
1531  }
1532  return $this->userHasRight( $user, $right );
1533  } ) );
1534  }
1535  return $levels;
1536  }
1537 
1538  // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1539  // may be satisfied by membership in multiple groups each giving a subset of those rights.
1540  // A restriction level is redundant if, for any one of the namespace rights, all groups
1541  // giving that right also give the restriction level's right. Or, conversely, a
1542  // restriction level is not redundant if, for every namespace right, there's at least one
1543  // group giving that right without the restriction level's right.
1544  //
1545  // First, for each right, get a list of groups with that right.
1546  $namespaceRightGroups = [];
1547  foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1548  if ( $right == 'sysop' ) {
1549  $right = 'editprotected'; // BC
1550  }
1551  if ( $right == 'autoconfirmed' ) {
1552  $right = 'editsemiprotected'; // BC
1553  }
1554  if ( $right != '' ) {
1555  $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1556  }
1557  }
1558 
1559  // Now, go through the protection levels one by one.
1560  $usableLevels = [ '' ];
1561  foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1562  $right = $level;
1563  if ( $right == 'sysop' ) {
1564  $right = 'editprotected'; // BC
1565  }
1566  if ( $right == 'autoconfirmed' ) {
1567  $right = 'editsemiprotected'; // BC
1568  }
1569 
1570  if ( $right != '' &&
1571  !isset( $namespaceRightGroups[$right] ) &&
1572  ( !$user || $this->userHasRight( $user, $right ) )
1573  ) {
1574  // Do any of the namespace rights imply the restriction right? (see explanation above)
1575  foreach ( $namespaceRightGroups as $groups ) {
1576  if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1577  // Yes, this one does.
1578  continue 2;
1579  }
1580  }
1581  // No, keep the restriction level
1582  $usableLevels[] = $level;
1583  }
1584  }
1585 
1586  return $usableLevels;
1587  }
1588 
1603  public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1604  $userId = $user->getId();
1605  $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1606  $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1607  return new ScopedCallback( function () use ( $userId, $nextKey ) {
1608  unset( $this->temporaryUserRights[$userId][$nextKey] );
1609  } );
1610  }
1611 
1622  public function overrideUserRightsForTesting( $user, $rights = [] ) {
1623  if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1624  throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1625  }
1626  $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1627  is_array( $rights ) ? $rights : [ $rights ];
1628  }
1629 
1630 }
getPermissionErrors( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on a page?
checkPageRestrictions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check against page_restrictions table requirements on this page.
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
isHidden()
Check if user account is hidden.
Definition: User.php:2184
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
getPermissionErrorsInternal( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE, $short=false)
Can $user perform $action on a page? This is an internal function, with multiple levels of checks dep...
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition: User.php:813
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
checkReadPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user is allowed to read this page.
const NS_MAIN
Definition: Defines.php:60
string [][] $usersRights
Cached user rights.
static factory( $action, Page $page, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition: Action.php:97
isNamespaceProtected( $index, UserIdentity $user)
Determines if $user is unable to edit pages in namespace because it has been protected.
getBlock( $fromReplica=true)
Get the block affecting the user, or null if the user is not blocked.
Definition: User.php:2064
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
const NS_SPECIAL
Definition: Defines.php:49
string $action
Cache what action this request is.
Definition: MediaWiki.php:42
invalidateUsersRightsCache( $user=null)
Clears users permissions cache, if specific user is provided it tries to clear permissions cache only...
static exists( $name)
Check if a given action is recognised, even if it&#39;s disabled.
Definition: Action.php:170
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
getNamespace()
Get the namespace index.
$coreRights
Array of Strings Core rights.
getAllPermissions()
Get a list of all available permissions.
missingPermissionError( $action, $short)
Get a description array when the user doesn&#39;t have the right to perform $action (i.e.
A class for passing options to services.
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
isEmailConfirmed()
Is this user&#39;s e-mail address valid-looking and confirmed within limits of the current site configura...
Definition: User.php:4633
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
$wgLang
Definition: Setup.php:856
Factory for handling the special page list and generating SpecialPage objects.
static getMain()
Get the RequestContext object associated with the main request.
IContextSource $context
Definition: MediaWiki.php:37
isAnon()
Get whether the user is anonymous.
Definition: User.php:3531
isAllowUsertalk()
Checks if usertalk is allowed.
Definition: User.php:5339
isBlockedFrom(User $user, LinkTarget $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
resultToError( $errors, $result)
Add the resulting error code to the errors array.
bool [] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
getDBkey()
Get the main part with underscores.
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
const NS_CATEGORY
Definition: Defines.php:74
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
string [] null $allRights
Cached results of getAllRights()
Service for looking up page revisions.
const NS_FILE
Definition: Defines.php:66
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
getNamespaceRestrictionLevels( $index, UserIdentity $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:575
const NS_MEDIAWIKI
Definition: Defines.php:68
checkUserBlock( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user isn&#39;t blocked from editing.
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys, without regard for order.
static newFromLinkTarget(LinkTarget $linkTarget, $forceClone='')
Returns a Title given a LinkTarget.
Definition: Title.php:271
A service class for getting formatted information about a block.
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
static getGlobalSession()
Get the "global" session.
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
userHasRight(UserIdentity $user, $action='')
Testing a permission.
checkUserConfigPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check CSS/JSON/JS sub-page permissions.
addTemporaryUserRights(UserIdentity $user, $rights)
Add temporary user rights, only valid for the current scope.
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition: User.php:5283
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the &#39;bigdelete&#39; perm...
string [][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
checkQuickPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Permissions checks that fail most often, and which are easiest to test.
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, BlockErrorFormatter $blockErrorFormatter)
$content
Definition: router.php:78
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
getTalkPage()
Get this user&#39;s talk page title.
Definition: User.php:4283
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200