Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
12.99% covered (danger)
12.99%
10 / 77
50.00% covered (danger)
50.00%
6 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
UserGroupMembership
13.16% covered (danger)
13.16%
10 / 76
50.00% covered (danger)
50.00%
6 / 12
468.73
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
3
 getUserId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGroup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getExpiry
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isExpired
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLink
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 getLinkHTML
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
12
 getLinkWiki
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
12
 getLinkInfo
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
12
 getLinkExpiryParams
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupPage
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 equals
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\User;
8
9use InvalidArgumentException;
10use MediaWiki\Context\IContextSource;
11use MediaWiki\MediaWikiServices;
12use MediaWiki\Message\Message;
13use MediaWiki\Title\Title;
14
15/**
16 * Represents the membership of one user in one user group.
17 *
18 * For example, if user "Mary" belongs to "sysop" and "bureaucrat" groups,
19 * those memberships can be represented by two UserGroupMembership objects.
20 *
21 * The class is a value object. Use UserGroupManager to modify user group memberships.
22 *
23 * @since 1.29
24 * @ingroup User
25 */
26class UserGroupMembership {
27
28    /** @var string|null Timestamp of expiry in TS::MW format, or null if no expiry */
29    private readonly ?string $expiry;
30
31    /** @var bool Expiration flag */
32    private readonly bool $expired;
33
34    /**
35     * @param int $userId The ID of the user who belongs to the group
36     * @param string|null $group The internal group name
37     * @param string|null $expiry Timestamp of expiry in TS::MW format, or null if no expiry
38     */
39    public function __construct(
40        private readonly int $userId = 0,
41        private readonly ?string $group = null,
42        ?string $expiry = null,
43    ) {
44        $this->expiry = $expiry ?: null;
45        $this->expired = $expiry && wfTimestampNow() > $expiry;
46    }
47
48    /**
49     * @return int
50     */
51    public function getUserId() {
52        return $this->userId;
53    }
54
55    /**
56     * @return string
57     */
58    public function getGroup() {
59        return $this->group;
60    }
61
62    /**
63     * @return string|null Timestamp of expiry in TS::MW format, or null if no expiry
64     */
65    public function getExpiry() {
66        return $this->expiry;
67    }
68
69    /**
70     * Has the membership expired?
71     *
72     * @return bool
73     */
74    public function isExpired() {
75        return $this->expired;
76    }
77
78    /**
79     * Gets a link for a user group, possibly including the expiry date if relevant.
80     *
81     * @deprecated since 1.41 use getLinkWiki or getLinkHTML directly
82     *
83     * @param string|UserGroupMembership $ugm Either a group name as a string, or
84     *   a UserGroupMembership object
85     * @param IContextSource $context
86     * @param string $format Either 'wiki' or 'html'
87     * @param string|null $userName If you want to use the group member message
88     *   ("administrator"), pass the name of the user who belongs to the group; it
89     *   is used for GENDER of the group member message. If you instead want the
90     *   group name message ("Administrators"), omit this parameter.
91     * @return string
92     */
93    public static function getLink( $ugm, IContextSource $context, string $format, $userName = null ) {
94        switch ( $format ) {
95            case 'wiki':
96                return self::getLinkWiki( $ugm, $context, $userName );
97            case 'html':
98                return self::getLinkHTML( $ugm, $context, $userName );
99            default:
100                throw new InvalidArgumentException( 'UserGroupMembership::getLink() $format parameter should be ' .
101                    "'wiki' or 'html'" );
102        }
103    }
104
105    /**
106     * Gets a link for a user group, possibly including the expiry date if relevant.
107     * @since 1.41
108     *
109     * @param string|UserGroupMembership $ugm Either a group name as a string, or
110     *   a UserGroupMembership object
111     * @param IContextSource $context
112     * @param string|null $userName If you want to use the group member message
113     *   ("administrator"), pass the name of the user who belongs to the group; it
114     *   is used for GENDER of the group member message. If you instead want the
115     *   group name message ("Administrators"), omit this parameter.
116     * @return string
117     */
118    public static function getLinkHTML( $ugm, IContextSource $context, $userName = null ): string {
119        [
120            'expiry' => $expiry,
121            'linkTitle' => $linkTitle,
122            'groupName' => $groupName
123        ] = self::getLinkInfo( $ugm, $context, $userName );
124
125        // link to the group description page, if it exists
126        $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
127        if ( $linkTitle ) {
128            $groupLink = $linkRenderer->makeLink( $linkTitle, $groupName );
129        } else {
130            $groupLink = htmlspecialchars( $groupName );
131        }
132
133        if ( $expiry ) {
134            [
135                'expiryDT' => $expiryDT,
136                'expiryD' => $expiryD,
137                'expiryT' => $expiryT
138            ] = self::getLinkExpiryParams( $context, $expiry );
139            $groupLink = Message::rawParam( $groupLink );
140            return $context->msg( 'group-membership-link-with-expiry' )
141                ->params( $groupLink, $expiryDT, $expiryD, $expiryT )->escaped();
142        }
143        return $groupLink;
144    }
145
146    /**
147     * Gets a link for a user group, possibly including the expiry date if relevant.
148     * @since 1.41
149     *
150     * @param string|UserGroupMembership $ugm Either a group name as a string, or
151     *   a UserGroupMembership object
152     * @param IContextSource $context
153     * @param string|null $userName If you want to use the group member message
154     *   ("administrator"), pass the name of the user who belongs to the group; it
155     *   is used for GENDER of the group member message. If you instead want the
156     *   group name message ("Administrators"), omit this parameter.
157     * @return string
158     */
159    public static function getLinkWiki( $ugm, IContextSource $context, $userName = null ): string {
160        [
161            'expiry' => $expiry,
162            'linkTitle' => $linkTitle,
163            'groupName' => $groupName
164        ] = self::getLinkInfo( $ugm, $context, $userName );
165
166        // link to the group description page, if it exists
167        if ( $linkTitle ) {
168            $linkPage = $linkTitle->getFullText();
169            $groupLink = "[[$linkPage|$groupName]]";
170        } else {
171            $groupLink = $groupName;
172        }
173
174        if ( $expiry ) {
175            [
176                'expiryDT' => $expiryDT,
177                'expiryD' => $expiryD,
178                'expiryT' => $expiryT
179            ] = self::getLinkExpiryParams( $context, $expiry );
180            return $context->msg( 'group-membership-link-with-expiry' )
181                ->params( $groupLink, $expiryDT, $expiryD, $expiryT )->text();
182        }
183        return $groupLink;
184    }
185
186    /**
187     * @param self|string $ugm
188     * @param IContextSource $context
189     * @param string|null $userName
190     * @return array{expiry:?string,linkTitle:Title|false,groupName:string}
191     */
192    private static function getLinkInfo( $ugm, $context, $userName = null ): array {
193        if ( $ugm instanceof UserGroupMembership ) {
194            $expiry = $ugm->getExpiry();
195            $group = $ugm->getGroup();
196        } else {
197            $expiry = null;
198            $group = $ugm;
199        }
200
201        $uiLanguage = $context->getLanguage();
202        if ( $userName !== null ) {
203            $groupName = $uiLanguage->getGroupMemberName( $group, $userName );
204        } else {
205            $groupName = $uiLanguage->getGroupName( $group );
206        }
207        $linkTitle = self::getGroupPage( $group );
208        return [ 'expiry' => $expiry, 'linkTitle' => $linkTitle, 'groupName' => $groupName ];
209    }
210
211    /**
212     * @param IContextSource $context
213     * @param string $expiry
214     * @return array
215     */
216    private static function getLinkExpiryParams( IContextSource $context, string $expiry ): array {
217        // format the expiry to a nice string
218        $uiLanguage = $context->getLanguage();
219        $uiUser = $context->getUser();
220        $expiryDT = $uiLanguage->userTimeAndDate( $expiry, $uiUser );
221        $expiryD = $uiLanguage->userDate( $expiry, $uiUser );
222        $expiryT = $uiLanguage->userTime( $expiry, $uiUser );
223        return [ 'expiryDT' => $expiryDT, 'expiryD' => $expiryD, 'expiryT' => $expiryT ];
224    }
225
226    /**
227     * Gets the title of a page describing a particular user group. When the name
228     * of the group appears in the UI, it can link to this page.
229     *
230     * @param string $group Internal group name
231     * @return Title|false Title of the page if it exists, false otherwise
232     */
233    public static function getGroupPage( $group ) {
234        $msg = wfMessage( "grouppage-$group" )->inContentLanguage();
235        if ( $msg->exists() ) {
236            $title = Title::newFromText( $msg->text() );
237            if ( $title ) {
238                return $title;
239            }
240        }
241        return false;
242    }
243
244    /**
245     * Compares two pure value objects
246     *
247     * @param UserGroupMembership $ugm
248     * @return bool
249     *
250     * @since 1.35
251     */
252    public function equals( UserGroupMembership $ugm ) {
253        return (
254            $ugm->getUserId() === $this->userId
255            && $ugm->getGroup() === $this->group
256        );
257    }
258
259}
260
261/** @deprecated class alias since 1.41 */
262class_alias( UserGroupMembership::class, 'UserGroupMembership' );