Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikiSet
0.00% covered (danger)
0.00%
0 / 155
0.00% covered (danger)
0.00%
0 / 24
1640
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
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 exists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWikisRaw
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setWikisRaw
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setType
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 newFromRow
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 newFromName
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 newFromID
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 1
12
 getDataForCache
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 loadFromCachedData
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 saveToDB
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 delete
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 purge
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getPerIdCacheKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getPerNameCacheKey
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWikis
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 inSet
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getRestrictedGroups
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getAllWikiSets
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
30
 getWikiSetForGroup
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 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;
22
23use MediaWiki\MediaWikiServices;
24use MediaWiki\WikiMap\WikiMap;
25use stdClass;
26use WANObjectCache;
27use Wikimedia\Rdbms\Database;
28
29class WikiSet {
30    public const OPTIN = 'optin';
31    public const OPTOUT = 'optout';
32
33    private const VERSION = 1;
34
35    /** @var int ID of the group */
36    private $mId;
37    /** @var string Display name of the group */
38    private $mName;
39    /** @var string Opt-in based or opt-out based */
40    private $mType;
41    /** @var string[] List of wikis */
42    private $mWikis;
43
44    /** @var string[] */
45    private static $mCacheVars = [
46        'mId',
47        'mName',
48        'mType',
49        'mWikis',
50    ];
51
52    /**
53     * @param string $name
54     * @param string $type
55     * @param string[] $wikis
56     * @param int $id
57     */
58    public function __construct( $name = '', $type = self::OPTIN, $wikis = [], $id = 0 ) {
59        $this->mId = $id;
60        $this->mName = $name;
61        $this->mType = $type;
62        $this->mWikis = $wikis;
63    }
64
65    /**
66     * @return int
67     */
68    public function getId() {
69        return $this->mId;
70    }
71
72    /**
73     * @return bool
74     */
75    public function exists() {
76        return (bool)$this->getID();
77    }
78
79    /**
80     * @return string
81     */
82    public function getName() {
83        return $this->mName;
84    }
85
86    /**
87     * @param string $name
88     */
89    public function setName( $name ) {
90        $this->mName = $name;
91    }
92
93    /**
94     * @return string[]
95     */
96    public function getWikisRaw() {
97        return $this->mWikis;
98    }
99
100    /**
101     * @param string[] $wikis
102     */
103    public function setWikisRaw( $wikis ) {
104        $this->mWikis = $wikis;
105    }
106
107    /**
108     * @return string
109     */
110    public function getType() {
111        return $this->mType;
112    }
113
114    /**
115     * @param string $type
116     */
117    public function setType( $type ) {
118        if ( !in_array( $type, [ self::OPTIN, self::OPTOUT ] ) ) {
119            return;
120        }
121        $this->mType = $type;
122    }
123
124    /**
125     * @param stdClass|bool $row
126     * @return null|WikiSet
127     */
128    public static function newFromRow( $row ) {
129        if ( !$row ) {
130            return null;
131        }
132        return new WikiSet(
133            $row->ws_name,
134            $row->ws_type,
135            explode( ',', $row->ws_wikis ),
136            $row->ws_id
137        );
138    }
139
140    /**
141     * @param string $name
142     * @return null|WikiSet
143     */
144    public static function newFromName( $name ) {
145        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
146        $fname = __METHOD__;
147
148        $data = $cache->getWithSetCallback(
149            self::getPerNameCacheKey( $cache, $name ),
150            $cache::TTL_INDEFINITE,
151            function ( $oldValue, &$ttl, &$setOpts ) use ( $name, $fname ) {
152                $dbr = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
153                $setOpts += Database::getCacheSetOptions( $dbr );
154
155                $row = $dbr->newSelectQueryBuilder()
156                    ->select( '*' )
157                    ->from( 'wikiset' )
158                    ->where( [ 'ws_name' => $name ] )
159                    ->caller( $fname )
160                    ->fetchRow();
161
162                $wikiSet = self::newFromRow( $row );
163                if ( $wikiSet ) {
164                    $value = $wikiSet->getDataForCache();
165                } else {
166                    // cache negatives
167                    $ttl = WANObjectCache::TTL_MINUTE;
168                    $value = null;
169                }
170
171                return $value;
172            },
173            [ 'version' => self::VERSION ]
174        );
175
176        if ( !$data ) {
177            return null;
178        }
179
180        $wikiSet = new WikiSet();
181        $wikiSet->loadFromCachedData( $data );
182
183        return $wikiSet;
184    }
185
186    /**
187     * @param string|int $id
188     * @return null|WikiSet
189     */
190    public static function newFromID( $id ) {
191        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
192        $fname = __METHOD__;
193
194        $data = $cache->getWithSetCallback(
195            self::getPerIdCacheKey( $cache, $id ),
196            $cache::TTL_INDEFINITE,
197            function ( $oldValue, &$ttl, &$setOpts ) use ( $id, $fname ) {
198                $dbr = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
199                $setOpts += Database::getCacheSetOptions( $dbr );
200
201                $row = $dbr->newSelectQueryBuilder()
202                    ->select( '*' )
203                    ->from( 'wikiset' )
204                    ->where( [ 'ws_id' => $id ] )
205                    ->caller( $fname )
206                    ->fetchRow();
207
208                $wikiSet = self::newFromRow( $row );
209                if ( $wikiSet ) {
210                    $value = $wikiSet->getDataForCache();
211                } else {
212                    // cache negatives
213                    $ttl = WANObjectCache::TTL_MINUTE;
214                    $value = null;
215                }
216
217                return $value;
218            },
219            [ 'version' => self::VERSION ]
220        );
221
222        if ( !$data ) {
223            return null;
224        }
225
226        $wikiSet = new WikiSet();
227        $wikiSet->loadFromCachedData( $data );
228
229        return $wikiSet;
230    }
231
232    /**
233     * @return array
234     */
235    private function getDataForCache() {
236        $data = [];
237        foreach ( self::$mCacheVars as $var ) {
238            if ( isset( $this->$var ) ) {
239                $data[$var] = $this->$var;
240            }
241        }
242
243        return $data;
244    }
245
246    /**
247     * @param array $data
248     */
249    private function loadFromCachedData( array $data ) {
250        foreach ( $data as $key => $val ) {
251            $this->$key = $val;
252        }
253    }
254
255    /**
256     * @return bool
257     */
258    public function saveToDB() {
259        $dbw = CentralAuthServices::getDatabaseManager()->getCentralPrimaryDB();
260        $dbw->startAtomic( __METHOD__ );
261        $dbw->newReplaceQueryBuilder()
262            ->replaceInto( 'wikiset' )
263            ->uniqueIndexFields( 'ws_id' )
264            ->row( [
265                'ws_id' => $this->mId,
266                'ws_name' => $this->mName,
267                'ws_type' => $this->mType,
268                'ws_wikis' => implode( ',', $this->mWikis ),
269            ] )
270            ->caller( __METHOD__ )
271            ->execute();
272        if ( !$this->mId ) {
273            $this->mId = $dbw->insertId();
274        }
275        $dbw->endAtomic( __METHOD__ );
276        $this->purge();
277        return (bool)$dbw->affectedRows();
278    }
279
280    /**
281     * @return bool
282     */
283    public function delete() {
284        $dbw = CentralAuthServices::getDatabaseManager()->getCentralPrimaryDB();
285        $dbw->newDeleteQueryBuilder()
286            ->deleteFrom( 'wikiset' )
287            ->where( [ 'ws_id' => $this->mId ] )
288            ->caller( __METHOD__ )
289            ->execute();
290        $this->purge();
291        return (bool)$dbw->affectedRows();
292    }
293
294    public function purge() {
295        $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
296        $cache->delete( self::getPerIdCacheKey( $cache, $this->mId ) );
297        $cache->delete( self::getPerNameCacheKey( $cache, $this->mName ) );
298    }
299
300    /**
301     * @param WANObjectCache $cache
302     * @param string|int $id
303     * @return string
304     */
305    private static function getPerIdCacheKey( WANObjectCache $cache, $id ) {
306        return $cache->makeGlobalKey( __CLASS__, 'id', $id );
307    }
308
309    /**
310     * @param WANObjectCache $cache
311     * @param string $name
312     * @return string
313     */
314    private static function getPerNameCacheKey( WANObjectCache $cache, $name ) {
315        return $cache->makeGlobalKey( __CLASS__, 'name', md5( $name ) );
316    }
317
318    /**
319     * @return string[]
320     */
321    public function getWikis() {
322        if ( $this->mType == self::OPTIN ) {
323            return $this->mWikis;
324        } else {
325            $wikiList = CentralAuthServices::getWikiListService()->getWikiList();
326            return array_diff( $wikiList, $this->mWikis );
327        }
328    }
329
330    /**
331     * @param string $wiki
332     * @return bool
333     */
334    public function inSet( $wiki = '' ) {
335        if ( !$wiki ) {
336            $wiki = WikiMap::getCurrentWikiId();
337        }
338        return in_array( $wiki, $this->getWikis() );
339    }
340
341    /**
342     * @return string[]
343     */
344    public function getRestrictedGroups() {
345        $dbr = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
346        return $dbr->newSelectQueryBuilder()
347            ->select( 'ggr_group' )
348            ->from( 'global_group_restrictions' )
349            ->where( [ 'ggr_set' => $this->mId ] )
350            ->caller( __METHOD__ )
351            ->fetchFieldValues();
352    }
353
354    /**
355     * @param string|null $from The wiki set name to start from (result is ordered by name)
356     * @param int|null $limit Limit for the selection (0 or null = no limit)
357     * @param bool $orderByName Order the result by name?
358     * @return self[]
359     */
360    public static function getAllWikiSets( $from = null, $limit = null, $orderByName = false ) {
361        $dbr = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
362
363        $qb = $dbr->newSelectQueryBuilder()
364            ->select( '*' )
365            ->from( 'wikiset' )
366            ->caller( __METHOD__ );
367
368        if ( $from != null ) {
369            $qb->where( $dbr->expr( 'ws_name', '>=', $from ) );
370            $orderByName = true;
371        }
372
373        if ( $limit ) {
374            $qb->limit( intval( $limit ) );
375        }
376
377        if ( $orderByName ) {
378            $qb->orderBy( 'ws_name' );
379        }
380
381        $res = $qb->fetchResultSet();
382
383        $result = [];
384        foreach ( $res as $row ) {
385            $result[] = self::newFromRow( $row );
386        }
387        return $result;
388    }
389
390    /**
391     * @param string $group
392     * @return int
393     */
394    public static function getWikiSetForGroup( $group ) {
395        $dbr = CentralAuthServices::getDatabaseManager()->getCentralReplicaDB();
396        return (int)$dbr->newSelectQueryBuilder()
397            ->select( 'ggr_set' )
398            ->from( 'global_group_restrictions' )
399            ->where( [ 'ggr_group' => $group ] )
400            ->caller( __METHOD__ )
401            ->fetchField();
402    }
403
404}