Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
81.08% covered (warning)
81.08%
60 / 74
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
UnreadWikis
81.08% covered (warning)
81.08%
60 / 74
40.00% covered (danger)
40.00%
2 / 5
20.19
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
 newFromUser
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 getDB
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUnreadCounts
93.33% covered (success)
93.33%
28 / 30
0.00% covered (danger)
0.00%
0 / 1
7.01
 updateCount
82.86% covered (warning)
82.86%
29 / 35
0.00% covered (danger)
0.00%
0 / 1
7.25
1<?php
2
3namespace MediaWiki\Extension\Notifications;
4
5use MediaWiki\MediaWikiServices;
6use MediaWiki\User\CentralId\CentralIdLookup;
7use MediaWiki\User\UserIdentity;
8use MediaWiki\Utils\MWTimestamp;
9use Wikimedia\Rdbms\IDatabase;
10
11/**
12 * Manages what wikis a user has unread notifications on
13 */
14class UnreadWikis {
15
16    private const DEFAULT_TS = '00000000000000';
17    private const DEFAULT_TS_DB = '00010101010101';
18
19    /**
20     * @var int
21     */
22    private $id;
23
24    /**
25     * @var DbFactory
26     */
27    private $dbFactory;
28
29    /**
30     * @param int $id Central user id
31     */
32    public function __construct( $id ) {
33        $this->id = $id;
34        $this->dbFactory = DbFactory::newFromDefault();
35    }
36
37    /**
38     * Use the user id provided by the CentralIdLookup
39     *
40     * @param UserIdentity $user
41     * @return UnreadWikis|false
42     */
43    public static function newFromUser( UserIdentity $user ) {
44        $id = MediaWikiServices::getInstance()
45            ->getCentralIdLookup()
46            ->centralIdFromLocalUser( $user, CentralIdLookup::AUDIENCE_RAW );
47        if ( !$id ) {
48            return false;
49        }
50
51        return new self( $id );
52    }
53
54    /**
55     * @param int $index DB_* constant
56     *
57     * @return bool|IDatabase
58     */
59    private function getDB( $index ) {
60        return $this->dbFactory->getSharedDb( $index );
61    }
62
63    /**
64     * @return array[][]
65     */
66    public function getUnreadCounts() {
67        $dbr = $this->getDB( DB_REPLICA );
68        if ( $dbr === false ) {
69            return [];
70        }
71
72        $rows = $dbr->newSelectQueryBuilder()
73            ->select( [
74                'euw_wiki',
75                'euw_alerts', 'euw_alerts_ts',
76                'euw_messages', 'euw_messages_ts',
77            ] )
78            ->from( 'echo_unread_wikis' )
79            ->where( [ 'euw_user' => $this->id ] )
80            ->caller( __METHOD__ )
81            ->fetchResultSet();
82
83        $wikis = [];
84        foreach ( $rows as $row ) {
85            if ( !$row->euw_alerts && !$row->euw_messages ) {
86                // This shouldn't happen, but lets be safe...
87                continue;
88            }
89            $wikis[$row->euw_wiki] = [
90                AttributeManager::ALERT => [
91                    'count' => $row->euw_alerts,
92                    'ts' => wfTimestamp( TS_MW, $row->euw_alerts_ts ) === static::DEFAULT_TS_DB ?
93                        static::DEFAULT_TS : wfTimestamp( TS_MW, $row->euw_alerts_ts ),
94                ],
95                AttributeManager::MESSAGE => [
96                    'count' => $row->euw_messages,
97                    'ts' => wfTimestamp( TS_MW, $row->euw_messages_ts ) === static::DEFAULT_TS_DB ?
98                        static::DEFAULT_TS : wfTimestamp( TS_MW, $row->euw_messages_ts ),
99                ],
100            ];
101        }
102
103        return $wikis;
104    }
105
106    /**
107     * @param string $wiki Wiki code
108     * @param int $alertCount Number of alerts
109     * @param MWTimestamp|bool $alertTime Timestamp of most recent unread alert, or
110     *   false meaning no timestamp because there are no unread alerts.
111     * @param int $msgCount Number of messages
112     * @param MWTimestamp|bool $msgTime Timestamp of most recent message, or
113     *   false meaning no timestamp because there are no unread messages.
114     */
115    public function updateCount( $wiki, $alertCount, $alertTime, $msgCount, $msgTime ) {
116        $dbw = $this->getDB( DB_PRIMARY );
117        if ( $dbw === false || $dbw->isReadOnly() ) {
118            return;
119        }
120
121        $conditions = [
122            'euw_user' => $this->id,
123            'euw_wiki' => $wiki,
124        ];
125
126        if ( $alertCount || $msgCount ) {
127            $values = [
128                'euw_alerts' => $alertCount,
129                'euw_alerts_ts' => $dbw->timestamp(
130                    $alertTime
131                        ? $alertTime->getTimestamp( TS_MW )
132                        : static::DEFAULT_TS_DB
133                ),
134                'euw_messages' => $msgCount,
135                'euw_messages_ts' => $dbw->timestamp(
136                    $msgTime
137                        ? $msgTime->getTimestamp( TS_MW )
138                        : static::DEFAULT_TS_DB
139                ),
140            ];
141
142            // when there is unread alert(s) and/or message(s), upsert the row
143            $dbw->newInsertQueryBuilder()
144                ->insertInto( 'echo_unread_wikis' )
145                ->row( $conditions + $values )
146                ->onDuplicateKeyUpdate()
147                ->uniqueIndexFields( [ 'euw_user', 'euw_wiki' ] )
148                ->set( $values )
149                ->caller( __METHOD__ )
150                ->execute();
151        } else {
152            // No unread notifications, delete the row
153            $dbw->newDeleteQueryBuilder()
154                ->deleteFrom( 'echo_unread_wikis' )
155                ->where( $conditions )
156                ->caller( __METHOD__ )
157                ->execute();
158        }
159    }
160}