MediaWiki REL1_35
PermissionManager.php
Go to the documentation of this file.
1<?php
20namespace MediaWiki\Permissions;
21
22use Action;
23use Article;
24use Exception;
38use SpecialPage;
39use Title;
40use User;
41use Wikimedia\ScopedCallback;
42
50
52 public const RIGOR_QUICK = 'quick';
53
55 public const RIGOR_FULL = 'full';
56
58 public const RIGOR_SECURE = 'secure';
59
64 public const CONSTRUCTOR_OPTIONS = [
65 'WhitelistRead',
66 'WhitelistReadRegexp',
67 'EmailConfirmToEdit',
68 'BlockDisablesLogin',
69 'GroupPermissions',
70 'RevokePermissions',
71 'AvailableRights',
72 'NamespaceProtection',
73 'RestrictionLevels'
74 ];
75
77 private $options;
78
81
84
86 private $nsInfo;
87
89 private $allRights;
90
93
95 private $hookRunner;
96
98 private $usersRights = null;
99
105
107 private $cachedRights = [];
108
115 private $coreRights = [
116 'apihighlimits',
117 'applychangetags',
118 'autoconfirmed',
119 'autocreateaccount',
120 'autopatrol',
121 'bigdelete',
122 'block',
123 'blockemail',
124 'bot',
125 'browsearchive',
126 'changetags',
127 'createaccount',
128 'createpage',
129 'createtalk',
130 'delete',
131 'deletechangetags',
132 'deletedhistory',
133 'deletedtext',
134 'deletelogentry',
135 'deleterevision',
136 'edit',
137 'editcontentmodel',
138 'editinterface',
139 'editprotected',
140 'editmyoptions',
141 'editmyprivateinfo',
142 'editmyusercss',
143 'editmyuserjson',
144 'editmyuserjs',
145 'editmyuserjsredirect',
146 'editmywatchlist',
147 'editsemiprotected',
148 'editsitecss',
149 'editsitejson',
150 'editsitejs',
151 'editusercss',
152 'edituserjson',
153 'edituserjs',
154 'hideuser',
155 'import',
156 'importupload',
157 'ipblock-exempt',
158 'managechangetags',
159 'markbotedits',
160 'mergehistory',
161 'minoredit',
162 'move',
163 'movefile',
164 'move-categorypages',
165 'move-rootuserpages',
166 'move-subpages',
167 'nominornewtalk',
168 'noratelimit',
169 'override-export-depth',
170 'pagelang',
171 'patrol',
172 'patrolmarks',
173 'protect',
174 'purge',
175 'read',
176 'reupload',
177 'reupload-own',
178 'reupload-shared',
179 'rollback',
180 'sendemail',
181 'siteadmin',
182 'suppressionlog',
183 'suppressredirect',
184 'suppressrevision',
185 'unblockself',
186 'undelete',
187 'unwatchedpages',
188 'upload',
189 'upload_by_url',
190 'userrights',
191 'userrights-interwiki',
192 'viewmyprivateinfo',
193 'viewmywatchlist',
194 'viewsuppressed',
195 'writeapi',
196 ];
197
206 public function __construct(
212 HookContainer $hookContainer
213 ) {
214 $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
215 $this->options = $options;
216 $this->specialPageFactory = $specialPageFactory;
217 $this->revisionLookup = $revisionLookup;
218 $this->nsInfo = $nsInfo;
219 $this->blockErrorFormatter = $blockErrorFormatter;
220 $this->hookRunner = new HookRunner( $hookContainer );
221 }
222
242 public function userCan( $action, User $user, LinkTarget $page, $rigor = self::RIGOR_SECURE ) {
243 return !count( $this->getPermissionErrorsInternal( $action, $user, $page, $rigor, true ) );
244 }
245
261 public function quickUserCan( $action, User $user, LinkTarget $page ) {
262 return $this->userCan( $action, $user, $page, self::RIGOR_QUICK );
263 }
264
282 public function getPermissionErrors(
283 $action,
284 User $user,
285 LinkTarget $page,
286 $rigor = self::RIGOR_SECURE,
287 $ignoreErrors = []
288 ) {
289 $errors = $this->getPermissionErrorsInternal( $action, $user, $page, $rigor );
290
291 // Remove the errors being ignored.
292 foreach ( $errors as $index => $error ) {
293 $errKey = is_array( $error ) ? $error[0] : $error;
294
295 if ( in_array( $errKey, $ignoreErrors ) ) {
296 unset( $errors[$index] );
297 }
298 if ( $errKey instanceof MessageSpecifier && in_array( $errKey->getKey(), $ignoreErrors ) ) {
299 unset( $errors[$index] );
300 }
301 }
302
303 return $errors;
304 }
305
316 public function isBlockedFrom( User $user, LinkTarget $page, $fromReplica = false ) {
317 $block = $user->getBlock( $fromReplica );
318 if ( !$block ) {
319 return false;
320 }
321
322 // TODO: remove upon further migration to LinkTarget
323 $title = Title::newFromLinkTarget( $page );
324
325 $blocked = $user->isHidden();
326 if ( !$blocked ) {
327 // Special handling for a user's own talk page. The block is not aware
328 // of the user, so this must be done here.
329 if ( $title->equals( $user->getTalkPage() ) ) {
330 $blocked = $block->appliesToUsertalk( $title );
331 } else {
332 $blocked = $block->appliesToTitle( $title );
333 }
334 }
335
336 // only for the purpose of the hook. We really don't need this here.
337 $allowUsertalk = $user->isAllowUsertalk();
338
339 // Allow extensions to let a blocked user access a particular page
340 $this->hookRunner->onUserIsBlockedFrom( $user, $title, $blocked, $allowUsertalk );
341
342 return $blocked;
343 }
344
363 $action,
364 User $user,
365 LinkTarget $page,
366 $rigor = self::RIGOR_SECURE,
367 $short = false
368 ) {
369 if ( !in_array( $rigor, [ self::RIGOR_QUICK, self::RIGOR_FULL, self::RIGOR_SECURE ] ) ) {
370 throw new Exception( "Invalid rigor parameter '$rigor'." );
371 }
372
373 # Read has special handling
374 if ( $action == 'read' ) {
375 $checks = [
376 'checkPermissionHooks',
377 'checkReadPermissions',
378 'checkUserBlock', // for wgBlockDisablesLogin
379 ];
380 # Don't call checkSpecialsAndNSPermissions, checkSiteConfigPermissions
381 # or checkUserConfigPermissions here as it will lead to duplicate
382 # error messages. This is okay to do since anywhere that checks for
383 # create will also check for edit, and those checks are called for edit.
384 } elseif ( $action == 'create' ) {
385 $checks = [
386 'checkQuickPermissions',
387 'checkPermissionHooks',
388 'checkPageRestrictions',
389 'checkCascadingSourcesRestrictions',
390 'checkActionPermissions',
391 'checkUserBlock'
392 ];
393 } else {
394 $checks = [
395 'checkQuickPermissions',
396 'checkPermissionHooks',
397 'checkSpecialsAndNSPermissions',
398 'checkSiteConfigPermissions',
399 'checkUserConfigPermissions',
400 'checkPageRestrictions',
401 'checkCascadingSourcesRestrictions',
402 'checkActionPermissions',
403 'checkUserBlock'
404 ];
405 }
406
407 $errors = [];
408 foreach ( $checks as $method ) {
409 $errors = $this->$method( $action, $user, $errors, $rigor, $short, $page );
410
411 if ( $short && $errors !== [] ) {
412 break;
413 }
414 }
415
416 return $errors;
417 }
418
435 private function checkPermissionHooks(
436 $action,
437 User $user,
438 $errors,
439 $rigor,
440 $short,
441 LinkTarget $page
442 ) {
443 // TODO: remove when LinkTarget usage will expand further
444 $title = Title::newFromLinkTarget( $page );
445 // Use getUserPermissionsErrors instead
446 $result = '';
447 if ( !$this->hookRunner->onUserCan( $title, $user, $action, $result ) ) {
448 return $result ? [] : [ [ 'badaccess-group0' ] ];
449 }
450 // Check getUserPermissionsErrors hook
451 if ( !$this->hookRunner->onGetUserPermissionsErrors( $title, $user, $action, $result ) ) {
452 $errors = $this->resultToError( $errors, $result );
453 }
454 // Check getUserPermissionsErrorsExpensive hook
455 if (
456 $rigor !== self::RIGOR_QUICK
457 && !( $short && count( $errors ) > 0 )
458 && !$this->hookRunner->onGetUserPermissionsErrorsExpensive(
459 $title, $user, $action, $result )
460 ) {
461 $errors = $this->resultToError( $errors, $result );
462 }
463
464 return $errors;
465 }
466
475 private function resultToError( $errors, $result ) {
476 if ( is_array( $result ) && count( $result ) && !is_array( $result[0] ) ) {
477 // A single array representing an error
478 $errors[] = $result;
479 } elseif ( is_array( $result ) && is_array( $result[0] ) ) {
480 // A nested array representing multiple errors
481 $errors = array_merge( $errors, $result );
482 } elseif ( $result !== '' && is_string( $result ) ) {
483 // A string representing a message-id
484 $errors[] = [ $result ];
485 } elseif ( $result instanceof MessageSpecifier ) {
486 // A message specifier representing an error
487 $errors[] = [ $result ];
488 } elseif ( $result === false ) {
489 // a generic "We don't want them to do that"
490 $errors[] = [ 'badaccess-group0' ];
491 }
492 return $errors;
493 }
494
511 private function checkReadPermissions(
512 $action,
513 User $user,
514 $errors,
515 $rigor,
516 $short,
517 LinkTarget $page
518 ) {
519 // TODO: remove when LinkTarget usage will expand further
520 $title = Title::newFromLinkTarget( $page );
521
522 $whiteListRead = $this->options->get( 'WhitelistRead' );
523 $whitelisted = false;
524 if ( $this->isEveryoneAllowed( 'read' ) ) {
525 # Shortcut for public wikis, allows skipping quite a bit of code
526 $whitelisted = true;
527 } elseif ( $this->userHasRight( $user, 'read' ) ) {
528 # If the user is allowed to read pages, he is allowed to read all pages
529 $whitelisted = true;
530 } elseif ( $this->isSameSpecialPage( 'Userlogin', $title )
531 || $this->isSameSpecialPage( 'PasswordReset', $title )
532 || $this->isSameSpecialPage( 'Userlogout', $title )
533 ) {
534 # Always grant access to the login page.
535 # Even anons need to be able to log in.
536 $whitelisted = true;
537 } elseif ( is_array( $whiteListRead ) && count( $whiteListRead ) ) {
538 # Time to check the whitelist
539 # Only do these checks is there's something to check against
540 $name = $title->getPrefixedText();
541 $dbName = $title->getPrefixedDBkey();
542
543 // Check for explicit whitelisting with and without underscores
544 if ( in_array( $name, $whiteListRead, true )
545 || in_array( $dbName, $whiteListRead, true ) ) {
546 $whitelisted = true;
547 } elseif ( $title->getNamespace() == NS_MAIN ) {
548 # Old settings might have the title prefixed with
549 # a colon for main-namespace pages
550 if ( in_array( ':' . $name, $whiteListRead ) ) {
551 $whitelisted = true;
552 }
553 } elseif ( $title->isSpecialPage() ) {
554 # If it's a special page, ditch the subpage bit and check again
555 $name = $title->getDBkey();
556 list( $name, /* $subpage */ ) =
557 $this->specialPageFactory->resolveAlias( $name );
558 if ( $name ) {
559 $pure = SpecialPage::getTitleFor( $name )->getPrefixedText();
560 if ( in_array( $pure, $whiteListRead, true ) ) {
561 $whitelisted = true;
562 }
563 }
564 }
565 }
566
567 $whitelistReadRegexp = $this->options->get( 'WhitelistReadRegexp' );
568 if ( !$whitelisted && is_array( $whitelistReadRegexp )
569 && !empty( $whitelistReadRegexp ) ) {
570 $name = $title->getPrefixedText();
571 // Check for regex whitelisting
572 foreach ( $whitelistReadRegexp as $listItem ) {
573 if ( preg_match( $listItem, $name ) ) {
574 $whitelisted = true;
575 break;
576 }
577 }
578 }
579
580 if ( !$whitelisted ) {
581 # If the title is not whitelisted, give extensions a chance to do so...
582 $this->hookRunner->onTitleReadWhitelist( $title, $user, $whitelisted );
583 if ( !$whitelisted ) {
584 $errors[] = $this->missingPermissionError( $action, $short );
585 }
586 }
587
588 return $errors;
589 }
590
599 private function missingPermissionError( $action, $short ) {
600 // We avoid expensive display logic for quickUserCan's and such
601 if ( $short ) {
602 return [ 'badaccess-group0' ];
603 }
604
605 // TODO: it would be a good idea to replace the method below with something else like
606 // maybe callback injection
607 return User::newFatalPermissionDeniedStatus( $action )->getErrorsArray()[0];
608 }
609
618 private function isSameSpecialPage( $name, LinkTarget $page ) {
619 if ( $page->getNamespace() == NS_SPECIAL ) {
620 list( $thisName, /* $subpage */ ) =
621 $this->specialPageFactory->resolveAlias( $page->getDBkey() );
622 if ( $name == $thisName ) {
623 return true;
624 }
625 }
626 return false;
627 }
628
645 private function checkUserBlock(
646 $action,
647 User $user,
648 $errors,
649 $rigor,
650 $short,
651 LinkTarget $page
652 ) {
653 // Account creation blocks handled at userlogin.
654 // Unblocking handled in SpecialUnblock
655 if ( $rigor === self::RIGOR_QUICK || in_array( $action, [ 'createaccount', 'unblock' ] ) ) {
656 return $errors;
657 }
658
659 // Optimize for a very common case
660 if ( $action === 'read' && !$this->options->get( 'BlockDisablesLogin' ) ) {
661 return $errors;
662 }
663
664 if ( $this->options->get( 'EmailConfirmToEdit' )
665 && !$user->isEmailConfirmed()
666 && $action === 'edit'
667 ) {
668 $errors[] = [ 'confirmedittext' ];
669 }
670
671 $useReplica = ( $rigor !== self::RIGOR_SECURE );
672 $block = $user->getBlock( $useReplica );
673
674 // If the user does not have a block, or the block they do have explicitly
675 // allows the action (like "read" or "upload").
676 if ( !$block || $block->appliesToRight( $action ) === false ) {
677 return $errors;
678 }
679
680 // Determine if the user is blocked from this action on this page.
681 // What gets passed into this method is a user right, not an action name.
682 // There is no way to instantiate an action by restriction. However, this
683 // will get the action where the restriction is the same. This may result
684 // in actions being blocked that shouldn't be.
685 $actionObj = null;
686 if ( Action::exists( $action ) ) {
687 // TODO: this drags a ton of dependencies in, would be good to avoid Article
688 // instantiation and decouple it creating an ActionPermissionChecker interface
689 // Creating an action will perform several database queries to ensure that
690 // the action has not been overridden by the content type.
691 // FIXME: avoid use of RequestContext since it drags in User and Title dependencies
692 // probably we may use fake context object since it's unlikely that Action uses it
693 // anyway. It would be nice if we could avoid instantiating the Action at all.
694 $title = Title::newFromLinkTarget( $page, 'clone' );
695 $context = RequestContext::getMain();
696 $actionObj = Action::factory(
697 $action,
698 Article::newFromTitle( $title, $context ),
699 $context
700 );
701 // Ensure that the retrieved action matches the restriction.
702 if ( $actionObj && $actionObj->getRestriction() !== $action ) {
703 $actionObj = null;
704 }
705 }
706
707 // If no action object is returned, assume that the action requires unblock
708 // which is the default.
709 if ( !$actionObj || $actionObj->requiresUnblock() ) {
710 if ( $this->isBlockedFrom( $user, $page, $useReplica ) ) {
711 // @todo FIXME: Pass the relevant context into this function.
712 $context = RequestContext::getMain();
713 $message = $this->blockErrorFormatter->getMessage(
714 $block,
715 $context->getUser(),
716 $context->getLanguage(),
717 $context->getRequest()->getIP()
718 );
719 $errors[] = array_merge( [ $message->getKey() ], $message->getParams() );
720 }
721 }
722
723 return $errors;
724 }
725
742 private function checkQuickPermissions(
743 $action,
744 User $user,
745 $errors,
746 $rigor,
747 $short,
748 LinkTarget $page
749 ) {
750 // TODO: remove when LinkTarget usage will expand further
751 $title = Title::newFromLinkTarget( $page );
752
753 if ( !$this->hookRunner->onTitleQuickPermissions( $title, $user, $action,
754 $errors, $rigor !== self::RIGOR_QUICK, $short )
755 ) {
756 return $errors;
757 }
758
759 $isSubPage = $this->nsInfo->hasSubpages( $title->getNamespace() ) ?
760 strpos( $title->getText(), '/' ) !== false : false;
761
762 if ( $action == 'create' ) {
763 if (
764 ( $this->nsInfo->isTalk( $title->getNamespace() ) &&
765 !$this->userHasRight( $user, 'createtalk' ) ) ||
766 ( !$this->nsInfo->isTalk( $title->getNamespace() ) &&
767 !$this->userHasRight( $user, 'createpage' ) )
768 ) {
769 $errors[] = $user->isAnon() ? [ 'nocreatetext' ] : [ 'nocreate-loggedin' ];
770 }
771 } elseif ( $action == 'move' ) {
772 if ( !$this->userHasRight( $user, 'move-rootuserpages' )
773 && $title->getNamespace() == NS_USER && !$isSubPage ) {
774 // Show user page-specific message only if the user can move other pages
775 $errors[] = [ 'cant-move-user-page' ];
776 }
777
778 // Check if user is allowed to move files if it's a file
779 if ( $title->getNamespace() == NS_FILE &&
780 !$this->userHasRight( $user, 'movefile' ) ) {
781 $errors[] = [ 'movenotallowedfile' ];
782 }
783
784 // Check if user is allowed to move category pages if it's a category page
785 if ( $title->getNamespace() == NS_CATEGORY &&
786 !$this->userHasRight( $user, 'move-categorypages' ) ) {
787 $errors[] = [ 'cant-move-category-page' ];
788 }
789
790 if ( !$this->userHasRight( $user, 'move' ) ) {
791 // User can't move anything
792 $userCanMove = $this->groupHasPermission( 'user', 'move' );
793 $autoconfirmedCanMove = $this->groupHasPermission( 'autoconfirmed', 'move' );
794 if ( $user->isAnon() && ( $userCanMove || $autoconfirmedCanMove ) ) {
795 // custom message if logged-in users without any special rights can move
796 $errors[] = [ 'movenologintext' ];
797 } else {
798 $errors[] = [ 'movenotallowed' ];
799 }
800 }
801 } elseif ( $action == 'move-target' ) {
802 if ( !$this->userHasRight( $user, 'move' ) ) {
803 // User can't move anything
804 $errors[] = [ 'movenotallowed' ];
805 } elseif ( !$this->userHasRight( $user, 'move-rootuserpages' )
806 && $title->getNamespace() == NS_USER
807 && !$isSubPage
808 ) {
809 // Show user page-specific message only if the user can move other pages
810 $errors[] = [ 'cant-move-to-user-page' ];
811 } elseif ( !$this->userHasRight( $user, 'move-categorypages' )
812 && $title->getNamespace() == NS_CATEGORY
813 ) {
814 // Show category page-specific message only if the user can move other pages
815 $errors[] = [ 'cant-move-to-category-page' ];
816 }
817 } elseif ( !$this->userHasRight( $user, $action ) ) {
818 $errors[] = $this->missingPermissionError( $action, $short );
819 }
820
821 return $errors;
822 }
823
842 private function checkPageRestrictions(
843 $action,
844 User $user,
845 $errors,
846 $rigor,
847 $short,
848 LinkTarget $page
849 ) {
850 // TODO: remove & rework upon further use of LinkTarget
851 $title = Title::newFromLinkTarget( $page );
852 foreach ( $title->getRestrictions( $action ) as $right ) {
853 // Backwards compatibility, rewrite sysop -> editprotected
854 if ( $right == 'sysop' ) {
855 $right = 'editprotected';
856 }
857 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
858 if ( $right == 'autoconfirmed' ) {
859 $right = 'editsemiprotected';
860 }
861 if ( $right == '' ) {
862 continue;
863 }
864 if ( !$this->userHasRight( $user, $right ) ) {
865 $errors[] = [ 'protectedpagetext', $right, $action ];
866 } elseif ( $title->areRestrictionsCascading() &&
867 !$this->userHasRight( $user, 'protect' )
868 ) {
869 $errors[] = [ 'protectedpagetext', 'protect', $action ];
870 }
871 }
872
873 return $errors;
874 }
875
893 $action,
894 UserIdentity $user,
895 $errors,
896 $rigor,
897 $short,
898 LinkTarget $page
899 ) {
900 // TODO: remove & rework upon further use of LinkTarget
901 $title = Title::newFromLinkTarget( $page );
902 if ( $rigor !== self::RIGOR_QUICK && !$title->isUserConfigPage() ) {
903 list( $cascadingSources, $restrictions ) = $title->getCascadeProtectionSources();
904 # Cascading protection depends on more than this page...
905 # Several cascading protected pages may include this page...
906 # Check each cascading level
907 # This is only for protection restrictions, not for all actions
908 if ( isset( $restrictions[$action] ) ) {
909 foreach ( $restrictions[$action] as $right ) {
910 // Backwards compatibility, rewrite sysop -> editprotected
911 if ( $right == 'sysop' ) {
912 $right = 'editprotected';
913 }
914 // Backwards compatibility, rewrite autoconfirmed -> editsemiprotected
915 if ( $right == 'autoconfirmed' ) {
916 $right = 'editsemiprotected';
917 }
918 if ( $right != '' && !$this->userHasAllRights( $user, 'protect', $right ) ) {
919 $wikiPages = '';
921 foreach ( $cascadingSources as $wikiPage ) {
922 $wikiPages .= '* [[:' . $wikiPage->getPrefixedText() . "]]\n";
923 }
924 $errors[] = [ 'cascadeprotected', count( $cascadingSources ), $wikiPages, $action ];
925 }
926 }
927 }
928 }
929
930 return $errors;
931 }
932
949 private function checkActionPermissions(
950 $action,
951 User $user,
952 $errors,
953 $rigor,
954 $short,
955 LinkTarget $page
956 ) {
958
959 // TODO: remove & rework upon further use of LinkTarget
960 $title = Title::newFromLinkTarget( $page );
961
962 if ( $action == 'protect' ) {
963 if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
964 // If they can't edit, they shouldn't protect.
965 $errors[] = [ 'protect-cantedit' ];
966 }
967 } elseif ( $action == 'create' ) {
968 $title_protection = $title->getTitleProtection();
969 if ( $title_protection ) {
970 if ( $title_protection['permission'] == ''
971 || !$this->userHasRight( $user, $title_protection['permission'] )
972 ) {
973 $errors[] = [
974 'titleprotected',
975 // TODO: get rid of the User dependency
976 User::whoIs( $title_protection['user'] ),
977 $title_protection['reason']
978 ];
979 }
980 }
981 } elseif ( $action == 'move' ) {
982 // Check for immobile pages
983 if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
984 // Specific message for this case
985 $nsText = $title->getNsText();
986 if ( $nsText === '' ) {
987 $nsText = wfMessage( 'blanknamespace' )->text();
988 }
989 $errors[] = [ 'immobile-source-namespace', $nsText ];
990 } elseif ( !$title->isMovable() ) {
991 // Less specific message for rarer cases
992 $errors[] = [ 'immobile-source-page' ];
993 }
994 } elseif ( $action == 'move-target' ) {
995 if ( !$this->nsInfo->isMovable( $title->getNamespace() ) ) {
996 $nsText = $title->getNsText();
997 if ( $nsText === '' ) {
998 $nsText = wfMessage( 'blanknamespace' )->text();
999 }
1000 $errors[] = [ 'immobile-target-namespace', $nsText ];
1001 } elseif ( !$title->isMovable() ) {
1002 $errors[] = [ 'immobile-target-page' ];
1003 }
1004 } elseif ( $action == 'delete' ) {
1005 $tempErrors = $this->checkPageRestrictions( 'edit', $user, [], $rigor, true, $title );
1006 if ( !$tempErrors ) {
1007 $tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
1008 $user, $tempErrors, $rigor, true, $title );
1009 }
1010 if ( $tempErrors ) {
1011 // If protection keeps them from editing, they shouldn't be able to delete.
1012 $errors[] = [ 'deleteprotected' ];
1013 }
1014 if ( $rigor !== self::RIGOR_QUICK && $wgDeleteRevisionsLimit
1015 && !$this->userCan( 'bigdelete', $user, $title ) && $title->isBigDeletion()
1016 ) {
1017 $errors[] = [ 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ];
1018 }
1019 } elseif ( $action === 'undelete' ) {
1020 if ( count( $this->getPermissionErrorsInternal( 'edit', $user, $title, $rigor, true ) ) ) {
1021 // Undeleting implies editing
1022 $errors[] = [ 'undelete-cantedit' ];
1023 }
1024 if ( !$title->exists()
1025 && count( $this->getPermissionErrorsInternal( 'create', $user, $title, $rigor, true ) )
1026 ) {
1027 // Undeleting where nothing currently exists implies creating
1028 $errors[] = [ 'undelete-cantcreate' ];
1029 }
1030 }
1031 return $errors;
1032 }
1033
1051 $action,
1052 UserIdentity $user,
1053 $errors,
1054 $rigor,
1055 $short,
1056 LinkTarget $page
1057 ) {
1058 // TODO: remove & rework upon further use of LinkTarget
1059 $title = Title::newFromLinkTarget( $page );
1060
1061 # Only 'createaccount' can be performed on special pages,
1062 # which don't actually exist in the DB.
1063 if ( $title->getNamespace() == NS_SPECIAL && $action !== 'createaccount' ) {
1064 $errors[] = [ 'ns-specialprotected' ];
1065 }
1066
1067 # Check $wgNamespaceProtection for restricted namespaces
1068 if ( $this->isNamespaceProtected( $title->getNamespace(), $user ) ) {
1069 $ns = $title->getNamespace() == NS_MAIN ?
1070 wfMessage( 'nstab-main' )->text() : $title->getNsText();
1071 $errors[] = $title->getNamespace() == NS_MEDIAWIKI ?
1072 [ 'protectedinterface', $action ] : [ 'namespaceprotected', $ns, $action ];
1073 }
1074
1075 return $errors;
1076 }
1077
1095 $action,
1096 User $user,
1097 $errors,
1098 $rigor,
1099 $short,
1100 LinkTarget $page
1101 ) {
1102 // TODO: remove & rework upon further use of LinkTarget
1103 $title = Title::newFromLinkTarget( $page );
1104
1105 if ( $action != 'patrol' ) {
1106 $error = null;
1107 // Sitewide CSS/JSON/JS/RawHTML changes, like all NS_MEDIAWIKI changes, also require the
1108 // editinterface right. That's implemented as a restriction so no check needed here.
1109 if ( $title->isSiteCssConfigPage() && !$this->userHasRight( $user, 'editsitecss' ) ) {
1110 $error = [ 'sitecssprotected', $action ];
1111 } elseif ( $title->isSiteJsonConfigPage() && !$this->userHasRight( $user, 'editsitejson' ) ) {
1112 $error = [ 'sitejsonprotected', $action ];
1113 } elseif ( $title->isSiteJsConfigPage() && !$this->userHasRight( $user, 'editsitejs' ) ) {
1114 $error = [ 'sitejsprotected', $action ];
1115 }
1116 if ( $title->isRawHtmlMessage() && !$this->userCanEditRawHtmlPage( $user ) ) {
1117 $error = [ 'siterawhtmlprotected', $action ];
1118 }
1119
1120 if ( $error ) {
1121 if ( $this->userHasRight( $user, 'editinterface' ) ) {
1122 // Most users / site admins will probably find out about the new, more restrictive
1123 // permissions by failing to edit something. Give them more info.
1124 // TODO remove this a few release cycles after 1.32
1125 $error = [ 'interfaceadmin-info', wfMessage( $error[0], $error[1] ) ];
1126 }
1127 $errors[] = $error;
1128 }
1129 }
1130
1131 return $errors;
1132 }
1133
1151 $action,
1152 UserIdentity $user,
1153 $errors,
1154 $rigor,
1155 $short,
1156 LinkTarget $page
1157 ) {
1158 // TODO: remove & rework upon further use of LinkTarget
1159 $title = Title::newFromLinkTarget( $page );
1160
1161 # Protect css/json/js subpages of user pages
1162 # XXX: this might be better using restrictions
1163
1164 if ( $action === 'patrol' ) {
1165 return $errors;
1166 }
1167
1168 if ( preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $title->getText() ) ) {
1169 // Users need editmyuser* to edit their own CSS/JSON/JS subpages.
1170 if (
1171 $title->isUserCssConfigPage()
1172 && !$this->userHasAnyRight( $user, 'editmyusercss', 'editusercss' )
1173 ) {
1174 $errors[] = [ 'mycustomcssprotected', $action ];
1175 } elseif (
1176 $title->isUserJsonConfigPage()
1177 && !$this->userHasAnyRight( $user, 'editmyuserjson', 'edituserjson' )
1178 ) {
1179 $errors[] = [ 'mycustomjsonprotected', $action ];
1180 } elseif (
1181 $title->isUserJsConfigPage()
1182 && !$this->userHasAnyRight( $user, 'editmyuserjs', 'edituserjs' )
1183 ) {
1184 $errors[] = [ 'mycustomjsprotected', $action ];
1185 } elseif (
1186 $title->isUserJsConfigPage()
1187 && !$this->userHasAnyRight( $user, 'edituserjs', 'editmyuserjsredirect' )
1188 ) {
1189 // T207750 - do not allow users to edit a redirect if they couldn't edit the target
1190 $rev = $this->revisionLookup->getRevisionByTitle( $title );
1191 $content = $rev ? $rev->getContent( 'main', RevisionRecord::RAW ) : null;
1192 $target = $content ? $content->getUltimateRedirectTarget() : null;
1193 if ( $target && (
1194 !$target->inNamespace( NS_USER )
1195 || !preg_match( '/^' . preg_quote( $user->getName(), '/' ) . '\//', $target->getText() )
1196 ) ) {
1197 $errors[] = [ 'mycustomjsredirectprotected', $action ];
1198 }
1199 }
1200 } else {
1201 // Users need edituser* to edit others' CSS/JSON/JS subpages, except for
1202 // deletion/suppression which cannot be used for attacks and we want to avoid the
1203 // situation where an unprivileged user can post abusive content on their subpages
1204 // and only very highly privileged users could remove it.
1205 if ( !in_array( $action, [ 'delete', 'deleterevision', 'suppressrevision' ], true ) ) {
1206 if (
1207 $title->isUserCssConfigPage()
1208 && !$this->userHasRight( $user, 'editusercss' )
1209 ) {
1210 $errors[] = [ 'customcssprotected', $action ];
1211 } elseif (
1212 $title->isUserJsonConfigPage()
1213 && !$this->userHasRight( $user, 'edituserjson' )
1214 ) {
1215 $errors[] = [ 'customjsonprotected', $action ];
1216 } elseif (
1217 $title->isUserJsConfigPage()
1218 && !$this->userHasRight( $user, 'edituserjs' )
1219 ) {
1220 $errors[] = [ 'customjsprotected', $action ];
1221 }
1222 }
1223 }
1224
1225 return $errors;
1226 }
1227
1238 public function userHasRight( UserIdentity $user, $action = '' ) {
1239 if ( $action === '' ) {
1240 return true; // In the spirit of DWIM
1241 }
1242 // Use strict parameter to avoid matching numeric 0 accidentally inserted
1243 // by misconfiguration: 0 == 'foo'
1244 return in_array( $action, $this->getUserPermissions( $user ), true );
1245 }
1246
1255 public function userHasAnyRight( UserIdentity $user, ...$actions ) {
1256 foreach ( $actions as $action ) {
1257 if ( $this->userHasRight( $user, $action ) ) {
1258 return true;
1259 }
1260 }
1261 return false;
1262 }
1263
1272 public function userHasAllRights( UserIdentity $user, ...$actions ) {
1273 foreach ( $actions as $action ) {
1274 if ( !$this->userHasRight( $user, $action ) ) {
1275 return false;
1276 }
1277 }
1278 return true;
1279 }
1280
1290 public function getUserPermissions( UserIdentity $user ) {
1291 $user = User::newFromIdentity( $user );
1292 $rightsCacheKey = $this->getRightsCacheKey( $user );
1293 if ( !isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1294 $this->usersRights[ $rightsCacheKey ] = $this->getGroupPermissions(
1295 $user->getEffectiveGroups()
1296 );
1297 $this->hookRunner->onUserGetRights( $user, $this->usersRights[ $rightsCacheKey ] );
1298
1299 // Deny any rights denied by the user's session, unless this
1300 // endpoint has no sessions.
1301 if ( !defined( 'MW_NO_SESSION' ) ) {
1302 // FIXME: $user->getRequest().. need to be replaced with something else
1303 $allowedRights = $user->getRequest()->getSession()->getAllowedUserRights();
1304 if ( $allowedRights !== null ) {
1305 $this->usersRights[ $rightsCacheKey ] = array_intersect(
1306 $this->usersRights[ $rightsCacheKey ],
1307 $allowedRights
1308 );
1309 }
1310 }
1311
1312 $this->hookRunner->onUserGetRightsRemove(
1313 $user, $this->usersRights[ $rightsCacheKey ] );
1314 // Force reindexation of rights when a hook has unset one of them
1315 $this->usersRights[ $rightsCacheKey ] = array_values(
1316 array_unique( $this->usersRights[ $rightsCacheKey ] )
1317 );
1318
1319 if (
1320 $user->isLoggedIn() &&
1321 $this->options->get( 'BlockDisablesLogin' ) &&
1322 $user->getBlock()
1323 ) {
1324 $anon = new User;
1325 $this->usersRights[ $rightsCacheKey ] = array_intersect(
1326 $this->usersRights[ $rightsCacheKey ],
1327 $this->getUserPermissions( $anon )
1328 );
1329 }
1330 }
1331 $rights = $this->usersRights[ $rightsCacheKey ];
1332 foreach ( $this->temporaryUserRights[ $user->getId() ] ?? [] as $overrides ) {
1333 $rights = array_values( array_unique( array_merge( $rights, $overrides ) ) );
1334 }
1335 return $rights;
1336 }
1337
1346 public function invalidateUsersRightsCache( $user = null ) {
1347 if ( $user !== null ) {
1348 $rightsCacheKey = $this->getRightsCacheKey( $user );
1349 if ( isset( $this->usersRights[ $rightsCacheKey ] ) ) {
1350 unset( $this->usersRights[ $rightsCacheKey ] );
1351 }
1352 } else {
1353 $this->usersRights = null;
1354 }
1355 }
1356
1362 private function getRightsCacheKey( UserIdentity $user ) {
1363 return $user->isRegistered() ? "u:{$user->getId()}" : "anon:{$user->getName()}";
1364 }
1365
1380 public function groupHasPermission( $group, $role ) {
1381 $groupPermissions = $this->options->get( 'GroupPermissions' );
1382 $revokePermissions = $this->options->get( 'RevokePermissions' );
1383 return isset( $groupPermissions[$group][$role] ) && $groupPermissions[$group][$role] &&
1384 !( isset( $revokePermissions[$group][$role] ) && $revokePermissions[$group][$role] );
1385 }
1386
1395 public function getGroupPermissions( $groups ) {
1396 $rights = [];
1397 // grant every granted permission first
1398 foreach ( $groups as $group ) {
1399 if ( isset( $this->options->get( 'GroupPermissions' )[$group] ) ) {
1400 $rights = array_merge( $rights,
1401 // array_filter removes empty items
1402 array_keys( array_filter( $this->options->get( 'GroupPermissions' )[$group] ) ) );
1403 }
1404 }
1405 // now revoke the revoked permissions
1406 foreach ( $groups as $group ) {
1407 if ( isset( $this->options->get( 'RevokePermissions' )[$group] ) ) {
1408 $rights = array_diff( $rights,
1409 array_keys( array_filter( $this->options->get( 'RevokePermissions' )[$group] ) ) );
1410 }
1411 }
1412 return array_unique( $rights );
1413 }
1414
1423 public function getGroupsWithPermission( $role ) {
1424 $allowedGroups = [];
1425 foreach ( array_keys( $this->options->get( 'GroupPermissions' ) ) as $group ) {
1426 if ( $this->groupHasPermission( $group, $role ) ) {
1427 $allowedGroups[] = $group;
1428 }
1429 }
1430 return $allowedGroups;
1431 }
1432
1448 public function isEveryoneAllowed( $right ) {
1449 // Use the cached results, except in unit tests which rely on
1450 // being able change the permission mid-request
1451 if ( isset( $this->cachedRights[$right] ) ) {
1452 return $this->cachedRights[$right];
1453 }
1454
1455 if ( !isset( $this->options->get( 'GroupPermissions' )['*'][$right] )
1456 || !$this->options->get( 'GroupPermissions' )['*'][$right] ) {
1457 $this->cachedRights[$right] = false;
1458 return false;
1459 }
1460
1461 // If it's revoked anywhere, then everyone doesn't have it
1462 foreach ( $this->options->get( 'RevokePermissions' ) as $rights ) {
1463 if ( isset( $rights[$right] ) && $rights[$right] ) {
1464 $this->cachedRights[$right] = false;
1465 return false;
1466 }
1467 }
1468
1469 // Remove any rights that aren't allowed to the global-session user,
1470 // unless there are no sessions for this endpoint.
1471 if ( !defined( 'MW_NO_SESSION' ) ) {
1472
1473 // XXX: think what could be done with the below
1474 $allowedRights = SessionManager::getGlobalSession()->getAllowedUserRights();
1475 if ( $allowedRights !== null && !in_array( $right, $allowedRights, true ) ) {
1476 $this->cachedRights[$right] = false;
1477 return false;
1478 }
1479 }
1480
1481 // Allow extensions to say false
1482 if ( !$this->hookRunner->onUserIsEveryoneAllowed( $right ) ) {
1483 $this->cachedRights[$right] = false;
1484 return false;
1485 }
1486
1487 $this->cachedRights[$right] = true;
1488 return true;
1489 }
1490
1498 public function getAllPermissions() {
1499 if ( $this->allRights === null ) {
1500 if ( count( $this->options->get( 'AvailableRights' ) ) ) {
1501 $this->allRights = array_unique( array_merge(
1502 $this->coreRights,
1503 $this->options->get( 'AvailableRights' )
1504 ) );
1505 } else {
1506 $this->allRights = $this->coreRights;
1507 }
1508 $this->hookRunner->onUserGetAllRights( $this->allRights );
1509 }
1510 return $this->allRights;
1511 }
1512
1519 private function isNamespaceProtected( $index, UserIdentity $user ) {
1520 $namespaceProtection = $this->options->get( 'NamespaceProtection' );
1521 if ( isset( $namespaceProtection[$index] ) ) {
1522 return !$this->userHasAllRights( $user, ...(array)$namespaceProtection[$index] );
1523 }
1524 return false;
1525 }
1526
1535 public function getNamespaceRestrictionLevels( $index, UserIdentity $user = null ) {
1536 if ( !isset( $this->options->get( 'NamespaceProtection' )[$index] ) ) {
1537 // All levels are valid if there's no namespace restriction.
1538 // But still filter by user, if necessary
1539 $levels = $this->options->get( 'RestrictionLevels' );
1540 if ( $user ) {
1541 $levels = array_values( array_filter( $levels, function ( $level ) use ( $user ) {
1542 $right = $level;
1543 if ( $right == 'sysop' ) {
1544 $right = 'editprotected'; // BC
1545 }
1546 if ( $right == 'autoconfirmed' ) {
1547 $right = 'editsemiprotected'; // BC
1548 }
1549 return $this->userHasRight( $user, $right );
1550 } ) );
1551 }
1552 return $levels;
1553 }
1554
1555 // $wgNamespaceProtection can require one or more rights to edit the namespace, which
1556 // may be satisfied by membership in multiple groups each giving a subset of those rights.
1557 // A restriction level is redundant if, for any one of the namespace rights, all groups
1558 // giving that right also give the restriction level's right. Or, conversely, a
1559 // restriction level is not redundant if, for every namespace right, there's at least one
1560 // group giving that right without the restriction level's right.
1561 //
1562 // First, for each right, get a list of groups with that right.
1563 $namespaceRightGroups = [];
1564 foreach ( (array)$this->options->get( 'NamespaceProtection' )[$index] as $right ) {
1565 if ( $right == 'sysop' ) {
1566 $right = 'editprotected'; // BC
1567 }
1568 if ( $right == 'autoconfirmed' ) {
1569 $right = 'editsemiprotected'; // BC
1570 }
1571 if ( $right != '' ) {
1572 $namespaceRightGroups[$right] = $this->getGroupsWithPermission( $right );
1573 }
1574 }
1575
1576 // Now, go through the protection levels one by one.
1577 $usableLevels = [ '' ];
1578 foreach ( $this->options->get( 'RestrictionLevels' ) as $level ) {
1579 $right = $level;
1580 if ( $right == 'sysop' ) {
1581 $right = 'editprotected'; // BC
1582 }
1583 if ( $right == 'autoconfirmed' ) {
1584 $right = 'editsemiprotected'; // BC
1585 }
1586
1587 if ( $right != '' &&
1588 !isset( $namespaceRightGroups[$right] ) &&
1589 ( !$user || $this->userHasRight( $user, $right ) )
1590 ) {
1591 // Do any of the namespace rights imply the restriction right? (see explanation above)
1592 foreach ( $namespaceRightGroups as $groups ) {
1593 if ( !array_diff( $groups, $this->getGroupsWithPermission( $right ) ) ) {
1594 // Yes, this one does.
1595 continue 2;
1596 }
1597 }
1598 // No, keep the restriction level
1599 $usableLevels[] = $level;
1600 }
1601 }
1602
1603 return $usableLevels;
1604 }
1605
1614 private function userCanEditRawHtmlPage( UserIdentity $user ) {
1615 return $this->userHasAllRights( $user, 'editsitecss', 'editsitejs' );
1616 }
1617
1632 public function addTemporaryUserRights( UserIdentity $user, $rights ) {
1633 $userId = $user->getId();
1634 $nextKey = count( $this->temporaryUserRights[$userId] ?? [] );
1635 $this->temporaryUserRights[$userId][$nextKey] = (array)$rights;
1636 return new ScopedCallback( function () use ( $userId, $nextKey ) {
1637 unset( $this->temporaryUserRights[$userId][$nextKey] );
1638 } );
1639 }
1640
1651 public function overrideUserRightsForTesting( $user, $rights = [] ) {
1652 if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
1653 throw new Exception( __METHOD__ . ' can not be called outside of tests' );
1654 }
1655 $this->usersRights[ $this->getRightsCacheKey( $user ) ] =
1656 is_array( $rights ) ? $rights : [ $rights ];
1657 }
1658
1659}
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
$wgLang
Definition Setup.php:781
Actions are things which can be done to pages (edit, delete, rollback, etc).
Definition Action.php:43
static factory(?string $action, Page $article, IContextSource $context=null)
Get an appropriate Action subclass for the given action.
Definition Action.php:115
static exists(string $name)
Check if a given action is recognised, even if it's disabled.
Definition Action.php:206
Class for viewing MediaWiki article and history.
Definition Article.php:46
A service class for getting formatted information about a block.
A class for passing options to services.
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
getRightsCacheKey(UserIdentity $user)
Gets a unique key for user rights cache.
string[][] $usersRights
Cached user rights.
bool[] $cachedRights
Cached rights for isEveryoneAllowed, [ right => allowed ].
checkPermissionHooks( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check various permission hooks.
missingPermissionError( $action, $short)
Get a description array when the user doesn't have the right to perform $action (i....
checkUserConfigPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check CSS/JSON/JS sub-page permissions.
quickUserCan( $action, User $user, LinkTarget $page)
A convenience method for calling PermissionManager::userCan with PermissionManager::RIGOR_QUICK.
checkReadPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user is allowed to read this page.
isEveryoneAllowed( $right)
Check if all users may be assumed to have the given permission.
getPermissionErrors( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on a page?
checkUserBlock( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check that the user isn't blocked from editing.
invalidateUsersRightsCache( $user=null)
Clears users permissions cache, if specific user is provided it tries to clear permissions cache only...
getGroupPermissions( $groups)
Get the permissions associated with a given list of groups.
overrideUserRightsForTesting( $user, $rights=[])
Overrides user permissions cache.
groupHasPermission( $group, $role)
Check, if the given group has the given permission.
checkSpecialsAndNSPermissions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check permissions on special pages & namespaces.
addTemporaryUserRights(UserIdentity $user, $rights)
Add temporary user rights, only valid for the current scope.
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...
getGroupsWithPermission( $role)
Get all the groups who have a given permission.
userCan( $action, User $user, LinkTarget $page, $rigor=self::RIGOR_SECURE)
Can $user perform $action on a page?
checkActionPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check action permissions not already checked in checkQuickPermissions.
getUserPermissions(UserIdentity $user)
Get the permissions this user has.
checkSiteConfigPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check sitewide CSS/JSON/JS permissions.
checkCascadingSourcesRestrictions( $action, UserIdentity $user, $errors, $rigor, $short, LinkTarget $page)
Check restrictions on cascading pages.
userHasAnyRight(UserIdentity $user,... $actions)
Check if user is allowed to make any action.
getAllPermissions()
Get a list of all available permissions.
userCanEditRawHtmlPage(UserIdentity $user)
Check if user is allowed to edit sitewide pages that contain raw HTML.
isBlockedFrom(User $user, LinkTarget $page, $fromReplica=false)
Check if user is blocked from editing a particular article.
isNamespaceProtected( $index, UserIdentity $user)
Determines if $user is unable to edit pages in namespace because it has been protected.
userHasRight(UserIdentity $user, $action='')
Testing a permission.
$coreRights
Array of Strings Core rights.
getNamespaceRestrictionLevels( $index, UserIdentity $user=null)
Determine which restriction levels it makes sense to use in a namespace, optionally filtered by a use...
isSameSpecialPage( $name, LinkTarget $page)
Returns true if this title resolves to the named special page.
__construct(ServiceOptions $options, SpecialPageFactory $specialPageFactory, RevisionLookup $revisionLookup, NamespaceInfo $nsInfo, BlockErrorFormatter $blockErrorFormatter, HookContainer $hookContainer)
checkQuickPermissions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Permissions checks that fail most often, and which are easiest to test.
resultToError( $errors, $result)
Add the resulting error code to the errors array.
string[][][] $temporaryUserRights
Temporary user rights, valid for the current request only.
string[] null $allRights
Cached results of getAllRights()
checkPageRestrictions( $action, User $user, $errors, $rigor, $short, LinkTarget $page)
Check against page_restrictions table requirements on this page.
userHasAllRights(UserIdentity $user,... $actions)
Check if user is allowed to make all actions.
Page revision base class.
This serves as the entry point to the MediaWiki session handling system.
Factory for handling the special page list and generating SpecialPage objects.
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
Group all the pieces relevant to the context of a request into one instance @newable.
Parent class for all special pages.
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,...
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
isAllowUsertalk()
Checks if usertalk is allowed.
Definition User.php:4593
isEmailConfirmed()
Is this user's e-mail address valid-looking and confirmed within limits of the current site configura...
Definition User.php:4113
isHidden()
Check if user account is hidden.
Definition User.php:2109
static whoIs( $id)
Get the username corresponding to a given user ID.
Definition User.php:867
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:597
getTalkPage()
Get this user's talk page title.
Definition User.php:3811
static newFatalPermissionDeniedStatus( $permission)
Factory function for fatal permission-denied errors.
Definition User.php:4537
getBlock( $fromReplica=true, $disableIpBlockExemptChecking=false)
Get the block affecting the user, or null if the user is not blocked.
Definition User.php:1991
isAnon()
Get whether the user is anonymous.
Definition User.php:3087
const NS_USER
Definition Defines.php:72
const NS_FILE
Definition Defines.php:76
const NS_MAIN
Definition Defines.php:70
const NS_MEDIAWIKI
Definition Defines.php:78
const NS_SPECIAL
Definition Defines.php:59
const NS_CATEGORY
Definition Defines.php:84
getNamespace()
Get the namespace index.
getDBkey()
Get the main part with underscores.
Service for looking up page revisions.
Interface for objects representing user identity.
Stable for implementing.
$content
Definition router.php:76