Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.42% covered (warning)
77.42%
96 / 124
70.00% covered (warning)
70.00%
7 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
GlobalRenameRequestStore
77.42% covered (warning)
77.42%
96 / 124
70.00% covered (warning)
70.00%
7 / 10
20.33
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
1
 save
93.62% covered (success)
93.62%
44 / 47
0.00% covered (danger)
0.00%
0 / 1
7.01
 newBlankRequest
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newForUser
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 newFromId
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 nameHasPendingRequest
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
1
 currentNameHasPendingRequest
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
2
 currentNameHasApprovedVanish
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 fetchRowFromDB
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
1
 newFromRow
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20
21namespace MediaWiki\Extension\CentralAuth\GlobalRename;
22
23use MediaWiki\Extension\CentralAuth\CentralAuthDatabaseManager;
24use MediaWiki\User\UserNameUtils;
25use stdClass;
26use Wikimedia\Rdbms\IDBAccessObject;
27
28/**
29 * Stores and loads GlobalRenameRequest objects in a database.
30 *
31 * @author Taavi "Majavah" Väänänen <hi@taavi.wtf>
32 */
33class GlobalRenameRequestStore {
34
35    private CentralAuthDatabaseManager $dbManager;
36    private UserNameUtils $userNameUtils;
37
38    public function __construct(
39        CentralAuthDatabaseManager $dbManager,
40        UserNameUtils $userNameUtils
41    ) {
42        $this->dbManager = $dbManager;
43        $this->userNameUtils = $userNameUtils;
44    }
45
46    /**
47     * Persists the given global rename request to the central database.
48     * @param GlobalRenameRequest $request
49     * @return bool
50     */
51    public function save( GlobalRenameRequest $request ): bool {
52        $dbw = $this->dbManager->getCentralPrimaryDB();
53        if ( $request->getId() === null ) {
54            $request->setRequested( wfTimestampNow() );
55
56            // Default to pending if unspecified, but otherwise use the
57            // provided status. This is needed for automatic account vanishing
58            // record keeping.
59            if ( $request->getStatus() === null ) {
60                $request->setStatus( GlobalRenameRequest::PENDING );
61            }
62
63            $row = [
64                'rq_name'         => $request->getName(),
65                'rq_wiki'         => $request->getWiki(),
66                'rq_newname'      => $request->getNewName(),
67                'rq_reason'       => $request->getReason(),
68                'rq_requested_ts' => $dbw->timestamp( $request->getRequested() ),
69                'rq_status'       => $request->getStatus(),
70                'rq_performer'    => $request->getPerformer(),
71                'rq_comments'     => $request->getComments(),
72                'rq_type'         => $request->getType() || GlobalRenameRequest::RENAME,
73            ];
74
75            // Ensure there's a completed timestamp if a pre-approved request
76            // is being saved to the database.
77            if ( $request->getStatus() === GlobalRenameRequest::APPROVED ) {
78                if ( $request->getCompleted() === null ) {
79                    $request->setCompleted( wfTimestampNow() );
80                }
81                $row['rq_completed_ts'] = $dbw->timestamp( $request->getCompleted() );
82            }
83
84            $dbw->newInsertQueryBuilder()
85                ->insertInto( 'renameuser_queue' )
86                ->row( $row )
87                ->caller( __METHOD__ )
88                ->execute();
89
90            $request->setId( $dbw->insertId() );
91        } else {
92            $dbw->newUpdateQueryBuilder()
93                ->update( 'renameuser_queue' )
94                ->set( [
95                    'rq_name'         => $request->getName(),
96                    'rq_wiki'         => $request->getWiki(),
97                    'rq_newname'      => $request->getNewName(),
98                    'rq_reason'       => $request->getReason(),
99                    'rq_requested_ts' => $dbw->timestamp( $request->getRequested() ),
100                    'rq_status'       => $request->getStatus(),
101                    'rq_completed_ts' => $dbw->timestamp( $request->getCompleted() ),
102                    'rq_deleted'      => $request->getDeleted(),
103                    'rq_performer'    => $request->getPerformer(),
104                    'rq_comments'     => $request->getComments(),
105                    'rq_type'         => $request->getType() || GlobalRenameRequest::RENAME,
106                ] )
107                ->where( [
108                    'rq_id' => $request->getId()
109                ] )
110                ->caller( __METHOD__ )
111                ->execute();
112        }
113
114        return $dbw->affectedRows() === 1;
115    }
116
117    /**
118     * Creates a new GlobalRenameRequest object without any filled data.
119     * @return GlobalRenameRequest
120     */
121    public function newBlankRequest(): GlobalRenameRequest {
122        return new GlobalRenameRequest( $this->userNameUtils );
123    }
124
125    /**
126     * Get the pending rename request for the given user and wiki.
127     *
128     * @param string $username
129     * @param string|null $wiki
130     * @param int $flags One of the IDBAccessObject::READ_* constants
131     * @return GlobalRenameRequest
132     */
133    public function newForUser( string $username, $wiki, int $flags = IDBAccessObject::READ_NORMAL ) {
134        return $this->newFromRow(
135            $this->fetchRowFromDB( [
136                'rq_name'   => $username,
137                'rq_wiki'   => $wiki,
138                'rq_status' => GlobalRenameRequest::PENDING,
139            ], $flags )
140        );
141    }
142
143    /**
144     * Get a request record.
145     *
146     * @param int $id Request id
147     * @param int $flags One of the IDBAccessObject::READ_* constants
148     * @return GlobalRenameRequest
149     */
150    public function newFromId( int $id, int $flags = IDBAccessObject::READ_NORMAL ) {
151        return $this->newFromRow(
152            $this->fetchRowFromDB( [
153                'rq_id' => $id,
154            ], $flags )
155        );
156    }
157
158    /**
159     * Check to see if there is a pending rename request to the given name.
160     *
161     * @param string $newname
162     * @param int $flags One of the IDBAccessObject::READ_* constants
163     * @return bool
164     */
165    public function nameHasPendingRequest( string $newname, int $flags = IDBAccessObject::READ_NORMAL ) {
166        $dbr = $this->dbManager->getCentralDBFromRecency( $flags );
167
168        $res = $dbr->newSelectQueryBuilder()
169            ->select( 'rq_id' )
170            ->from( 'renameuser_queue' )
171            ->where( [
172                'rq_newname' => $newname,
173                'rq_status'  => GlobalRenameRequest::PENDING,
174            ] )
175            ->recency( $flags )
176            ->caller( __METHOD__ )
177            ->fetchField();
178
179        return $res !== false;
180    }
181
182    /**
183     * Check to see if there is a pending rename request to the given (current) name.
184     *
185     * @param string $name
186     * @param int $flags One of the IDBAccessObject::READ_* constants
187     * @return bool
188     */
189    public function currentNameHasPendingRequest( string $name, int $flags = IDBAccessObject::READ_NORMAL ) {
190        $dbr = $this->dbManager->getCentralDBFromRecency( $flags );
191
192        $res = $dbr->newSelectQueryBuilder()
193            ->select( 'rq_id' )
194            ->from( 'renameuser_queue' )
195            ->where( [
196                'rq_name' => $name,
197                'rq_status'  => GlobalRenameRequest::PENDING,
198            ] )
199            ->recency( $flags )
200            ->caller( __METHOD__ )
201            ->fetchField();
202
203        return $res !== false;
204    }
205
206    /**
207     * Check to see if there is an approved vanish request for the given (previous) name.
208     *
209     * @param string $name
210     * @param int $flags One of the IDBAccessObject::READ_* constants
211     * @return bool
212     */
213    public function currentNameHasApprovedVanish( string $name, int $flags = IDBAccessObject::READ_NORMAL ) {
214        $dbr = $this->dbManager->getCentralDBFromRecency( $flags );
215
216        $res = $dbr->newSelectQueryBuilder()
217            ->select( 'rq_id' )
218            ->from( 'renameuser_queue' )
219            ->where( [
220                'rq_name' => $name,
221                'rq_status' => GlobalRenameRequest::APPROVED,
222                'rq_type' => GlobalRenameRequest::VANISH,
223            ] )
224            ->recency( $flags )
225            ->caller( __METHOD__ )
226            ->fetchField();
227
228        return $res !== false;
229    }
230
231    /**
232     * Fetch a single request from the database.
233     *
234     * @param array $where Where clause criteria
235     * @param int $flags One of the IDBAccessObject::READ_* constants
236     * @return stdClass|false Row as object or false if not found
237     */
238    protected function fetchRowFromDB( array $where, int $flags = IDBAccessObject::READ_NORMAL ) {
239        $dbr = $this->dbManager->getCentralDBFromRecency( $flags );
240
241        return $dbr->newSelectQueryBuilder()
242            ->select( [
243                'id'        => 'rq_id',
244                'name'      => 'rq_name',
245                'wiki'      => 'rq_wiki',
246                'newname'   => 'rq_newname',
247                'reason'    => 'rq_reason',
248                'requested' => 'rq_requested_ts',
249                'status'    => 'rq_status',
250                'completed' => 'rq_completed_ts',
251                'deleted'   => 'rq_deleted',
252                'performer' => 'rq_performer',
253                'comments'  => 'rq_comments',
254                'type'      => 'rq_type',
255            ] )
256            ->from( 'renameuser_queue' )
257            ->where( $where )
258            ->recency( $flags )
259            ->caller( __METHOD__ )
260            ->fetchRow();
261    }
262
263    /**
264     * Creates a new GlobalRenameRequest object from a database row.
265     *
266     * @param stdClass|false $row Database result
267     * @return GlobalRenameRequest
268     */
269    protected function newFromRow( $row ): GlobalRenameRequest {
270        $request = $this->newBlankRequest();
271
272        if ( $row ) {
273            $request->importRow( $row );
274        }
275
276        return $request;
277    }
278}