Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 180
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialListGroupRights
0.00% covered (danger)
0.00%
0 / 179
0.00% covered (danger)
0.00%
0 / 5
870
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 1
90
 outputNamespaceProtectionInfo
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 1
56
 formatPermissions
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 1
132
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\Specials;
8
9use MediaWiki\Html\Html;
10use MediaWiki\Language\ILanguageConverter;
11use MediaWiki\Languages\LanguageConverterFactory;
12use MediaWiki\MainConfigNames;
13use MediaWiki\Parser\Sanitizer;
14use MediaWiki\Permissions\GroupPermissionsLookup;
15use MediaWiki\SpecialPage\SpecialPage;
16use MediaWiki\Title\NamespaceInfo;
17use MediaWiki\Title\Title;
18use MediaWiki\User\User;
19use MediaWiki\User\UserGroupManager;
20use MediaWiki\User\UserGroupMembership;
21
22/**
23 * List all defined user groups and the associated rights.
24 *
25 * See also @ref $wgGroupPermissions.
26 *
27 * @ingroup SpecialPage
28 * @author Petr Kadlec <mormegil@centrum.cz>
29 */
30class SpecialListGroupRights extends SpecialPage {
31
32    private NamespaceInfo $nsInfo;
33    private UserGroupManager $userGroupManager;
34    private ILanguageConverter $languageConverter;
35    private GroupPermissionsLookup $groupPermissionsLookup;
36
37    public function __construct(
38        NamespaceInfo $nsInfo,
39        UserGroupManager $userGroupManager,
40        LanguageConverterFactory $languageConverterFactory,
41        GroupPermissionsLookup $groupPermissionsLookup
42    ) {
43        parent::__construct( 'Listgrouprights' );
44        $this->nsInfo = $nsInfo;
45        $this->userGroupManager = $userGroupManager;
46        $this->languageConverter = $languageConverterFactory->getLanguageConverter( $this->getContentLanguage() );
47        $this->groupPermissionsLookup = $groupPermissionsLookup;
48    }
49
50    /**
51     * Show the special page
52     * @param string|null $par
53     */
54    public function execute( $par ) {
55        $this->setHeaders();
56        $this->outputHeader();
57
58        $out = $this->getOutput();
59        $out->addModuleStyles( 'mediawiki.special' );
60        $this->addHelpLink( 'Help:User_rights_and_groups' );
61
62        $out->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
63
64        $out->addHTML(
65            Html::openElement( 'table', [ 'class' => [ 'wikitable', 'mw-listgrouprights-table' ] ] ) .
66                '<tr>' .
67                Html::element( 'th', [], $this->msg( 'listgrouprights-group' )->text() ) .
68                Html::element( 'th', [], $this->msg( 'listgrouprights-rights' )->text() ) .
69                '</tr>'
70        );
71
72        $config = $this->getConfig();
73        $addGroups = $config->get( MainConfigNames::AddGroups );
74        $removeGroups = $config->get( MainConfigNames::RemoveGroups );
75        $groupsAddToSelf = $config->get( MainConfigNames::GroupsAddToSelf );
76        $groupsRemoveFromSelf = $config->get( MainConfigNames::GroupsRemoveFromSelf );
77        $allGroups = array_merge(
78            $this->userGroupManager->listAllGroups(),
79            $this->userGroupManager->listAllImplicitGroups()
80        );
81        asort( $allGroups );
82
83        $linkRenderer = $this->getLinkRenderer();
84        $lang = $this->getLanguage();
85
86        foreach ( $allGroups as $group ) {
87            $permissions = $this->groupPermissionsLookup->getGrantedPermissions( $group );
88            $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname
89                ? 'all'
90                : $group;
91
92            $groupnameLocalized = $lang->getGroupName( $groupname );
93
94            $grouppageLocalizedTitle = UserGroupMembership::getGroupPage( $groupname )
95                ?: Title::makeTitleSafe( NS_PROJECT, $groupname );
96
97            if ( $group == '*' || !$grouppageLocalizedTitle ) {
98                // Do not make a link for the generic * group or group with invalid group page
99                $grouppage = htmlspecialchars( $groupnameLocalized );
100            } else {
101                $grouppage = $linkRenderer->makeLink(
102                    $grouppageLocalizedTitle,
103                    $groupnameLocalized
104                );
105            }
106
107            $groupWithParentheses = $this->msg( 'parentheses' )->rawParams( $group )->escaped();
108            $groupname = "<br /><code>$groupWithParentheses</code>";
109
110            if ( $group === 'user' ) {
111                // Link to Special:listusers for implicit group 'user'
112                $grouplink = '<br />' . $linkRenderer->makeKnownLink(
113                    SpecialPage::getTitleFor( 'Listusers' ),
114                    $this->msg( 'listgrouprights-members' )->text()
115                );
116            } elseif ( !in_array( $group, $config->get( MainConfigNames::ImplicitGroups ) ) ) {
117                $grouplink = '<br />' . $linkRenderer->makeKnownLink(
118                    SpecialPage::getTitleFor( 'Listusers' ),
119                    $this->msg( 'listgrouprights-members' )->text(),
120                    [],
121                    [ 'group' => $group ]
122                );
123            } else {
124                // No link to Special:listusers for other implicit groups as they are unlistable
125                $grouplink = '';
126            }
127
128            $revoke = $this->groupPermissionsLookup->getRevokedPermissions( $group );
129            $addgroups = $addGroups[$group] ?? [];
130            $removegroups = $removeGroups[$group] ?? [];
131            $addgroupsSelf = $groupsAddToSelf[$group] ?? [];
132            $removegroupsSelf = $groupsRemoveFromSelf[$group] ?? [];
133
134            $id = $group == '*' ? false : Sanitizer::escapeIdForAttribute( $group );
135            $out->addHTML( Html::rawElement( 'tr', [ 'id' => $id ], "
136                <td>$grouppage$groupname$grouplink</td>
137                    <td>" .
138                    $this->formatPermissions( $permissions, $revoke, $addgroups, $removegroups,
139                        $addgroupsSelf, $removegroupsSelf ) .
140                    '</td>
141                '
142            ) );
143        }
144        $out->addHTML( Html::closeElement( 'table' ) );
145        $this->outputNamespaceProtectionInfo();
146    }
147
148    private function outputNamespaceProtectionInfo() {
149        $out = $this->getOutput();
150        $namespaceProtection = $this->getConfig()->get( MainConfigNames::NamespaceProtection );
151
152        if ( count( $namespaceProtection ) == 0 ) {
153            return;
154        }
155
156        $header = $this->msg( 'listgrouprights-namespaceprotection-header' )->text();
157        $out->addHTML(
158            Html::element( 'h2', [
159                'id' => Sanitizer::escapeIdForAttribute( $header )
160            ], $header ) .
161            Html::openElement( 'table', [ 'class' => 'wikitable' ] ) .
162            Html::element(
163                'th',
164                [],
165                $this->msg( 'listgrouprights-namespaceprotection-namespace' )->text()
166            ) .
167            Html::element(
168                'th',
169                [],
170                $this->msg( 'listgrouprights-namespaceprotection-restrictedto' )->text()
171            )
172        );
173        $linkRenderer = $this->getLinkRenderer();
174        ksort( $namespaceProtection );
175        $validNamespaces = $this->nsInfo->getValidNamespaces();
176        foreach ( $namespaceProtection as $namespace => $rights ) {
177            if ( !in_array( $namespace, $validNamespaces ) ) {
178                continue;
179            }
180
181            if ( $namespace == NS_MAIN ) {
182                $namespaceText = $this->msg( 'blanknamespace' )->text();
183            } else {
184                $namespaceText = $this->languageConverter->convertNamespace( $namespace );
185            }
186
187            $out->addHTML(
188                Html::openElement( 'tr' ) .
189                Html::rawElement(
190                    'td',
191                    [],
192                    $linkRenderer->makeLink(
193                        SpecialPage::getTitleFor( 'Allpages' ),
194                        $namespaceText,
195                        [],
196                        [ 'namespace' => $namespace ]
197                    )
198                ) .
199                Html::openElement( 'td' ) . Html::openElement( 'ul' )
200            );
201
202            if ( !is_array( $rights ) ) {
203                $rights = [ $rights ];
204            }
205
206            foreach ( $rights as $right ) {
207                $out->addHTML( Html::rawElement( 'li', [],
208                    $this->msg( 'listgrouprights-right-display' )
209                        ->params( User::getRightDescription( $right ) )
210                        ->rawParams( Html::element(
211                            'span',
212                            [ 'class' => 'mw-listgrouprights-right-name' ],
213                            $right
214                        ) )->parse()
215                ) );
216            }
217
218            $out->addHTML(
219                Html::closeElement( 'ul' ) .
220                Html::closeElement( 'td' ) .
221                Html::closeElement( 'tr' )
222            );
223        }
224        $out->addHTML( Html::closeElement( 'table' ) );
225    }
226
227    /**
228     * Create a user-readable list of permissions from the given array.
229     *
230     * @param string[] $permissions Array of granted permissions
231     * @param string[] $revoke Array of revoked permissions
232     * @param array $add Array of groups this group is allowed to add or true
233     * @param array $remove Array of groups this group is allowed to remove or true
234     * @param array $addSelf Array of groups this group is allowed to add to self or true
235     * @param array $removeSelf Array of group this group is allowed to remove from self or true
236     * @return string HTML list of all granted permissions
237     */
238    private function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
239        $r = [];
240        foreach ( $permissions as $permission ) {
241            // show as granted only if it isn't revoked to prevent duplicate display of permissions
242            if ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) {
243                $r[] = $this->msg( 'listgrouprights-right-display' )
244                    ->params( User::getRightDescription( $permission ) )
245                    ->rawParams( Html::element(
246                        'span',
247                        [ 'class' => 'mw-listgrouprights-right-name' ],
248                        $permission
249                    ) )->parse();
250            }
251        }
252        foreach ( $revoke as $permission ) {
253            $r[] = $this->msg( 'listgrouprights-right-revoked' )
254                ->params( User::getRightDescription( $permission ) )
255                ->rawParams( Html::element(
256                    'span',
257                    [ 'class' => 'mw-listgrouprights-right-name' ],
258                    $permission
259                ) )->parse();
260        }
261
262        sort( $r );
263
264        $lang = $this->getLanguage();
265        $allGroups = $this->userGroupManager->listAllGroups();
266
267        $changeGroups = [
268            'addgroup' => $add,
269            'removegroup' => $remove,
270            'addgroup-self' => $addSelf,
271            'removegroup-self' => $removeSelf
272        ];
273
274        foreach ( $changeGroups as $messageKey => $changeGroup ) {
275            // @phan-suppress-next-line PhanTypeComparisonFromArray
276            if ( $changeGroup === true ) {
277                // For grep: listgrouprights-addgroup-all, listgrouprights-removegroup-all,
278                // listgrouprights-addgroup-self-all, listgrouprights-removegroup-self-all
279                $r[] = $this->msg( 'listgrouprights-' . $messageKey . '-all' )->escaped();
280            } elseif ( is_array( $changeGroup ) ) {
281                $changeGroup = array_intersect( array_values( array_unique( $changeGroup ) ), $allGroups );
282                if ( count( $changeGroup ) ) {
283                    $groupLinks = [];
284                    foreach ( $changeGroup as $group ) {
285                        $groupLinks[] = UserGroupMembership::getLinkWiki( $group, $this->getContext() );
286                    }
287                    // For grep: listgrouprights-addgroup, listgrouprights-removegroup,
288                    // listgrouprights-addgroup-self, listgrouprights-removegroup-self
289                    $r[] = $this->msg( 'listgrouprights-' . $messageKey,
290                        $lang->listToText( $groupLinks ), count( $changeGroup ) )->parse();
291                }
292            }
293        }
294
295        if ( !$r ) {
296            return '';
297        } else {
298            return '<ul><li>' . implode( "</li>\n<li>", $r ) . '</li></ul>';
299        }
300    }
301
302    /** @inheritDoc */
303    protected function getGroupName() {
304        return 'users';
305    }
306}
307
308/** @deprecated class alias since 1.41 */
309class_alias( SpecialListGroupRights::class, 'SpecialListGroupRights' );