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