Passed
Push — master ( e9dfe5...fa6a28 )
by MusikAnimal
05:21
created

UserRights::getRightsNames()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 0
dl 0
loc 17
ccs 0
cts 11
cp 0
crap 6
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file contains only the UserRights class.
4
 */
5
6
namespace Xtools;
7
8
use AppBundle\Helper\I18nHelper;
9
use DateInterval;
10
use DatePeriod;
11
use DateTime;
12
use Exception;
13
14
/**
15
 * An UserRights provides methods around parsing changes to a user's rights.
16
 */
17
class UserRights extends Model
18
{
19
    /** @var string[] Rights changes, keyed by timestamp then 'added' and 'removed'. */
20
    protected $rightsChanges;
21
22
    /** @var string[] Localized names of the rights. */
23
    protected $rightsNames;
24
25
    /** @var string[] Global rights changes, keyed by timestamp then 'added' and 'removed'. */
26
    protected $globalRightsChanges;
27
28
    /**
29
     * Get user rights changes of the given user.
30
     * @param Project $project
31
     * @param User $user
32
     * @return string[] Keyed by timestamp then 'added' and 'removed'.
33
     */
34 1
    public function getRightsChanges()
35
    {
36 1
        if (isset($this->rightsChanges)) {
37 1
            return $this->rightsChanges;
38
        }
39
40 1
        $logData = $this->getRepository()
41 1
            ->getRightsChanges($this->project, $this->user);
42
43 1
        $this->rightsChanges = $this->processRightsChanges($logData);
44
45 1
        return $this->rightsChanges;
46
    }
47
48
    /**
49
     * Checks the user rights log to see whether the user is an admin
50
     * or used to be one.
51
     * @return string|false One of false (never an admin), 'current' or 'former'.
52
     */
53 1
    public function getAdminStatus()
54
    {
55 1
        $rightsStates = $this->getRightsStates();
56
57 1
        if (in_array('sysop', $rightsStates['current'])) {
58 1
            return 'current';
59
        } elseif (in_array('sysop', $rightsStates['former'])) {
60
            return 'former';
61
        } else {
62
            return false;
63
        }
64
    }
65
66
    /**
67
     * Get a list of the current and former rights of the user.
68
     * @return array With keys 'current' and 'former'.
69
     */
70 1
    public function getRightsStates()
71
    {
72 1
        static $rightsStates = null;
73 1
        if ($rightsStates !== null) {
74 1
            return $rightsStates;
75
        }
76
77 1
        $former = [];
78
79 1
        foreach (array_reverse($this->getRightsChanges()) as $change) {
80 1
            $former = array_diff(
81 1
                array_merge($former, $change['removed']),
82 1
                $change['added']
83
            );
84
        }
85
86
        // Current rights are not fetched from the log because really old
87
        // log entries contained little or no metadata, and the rights
88
        // changes may be undetectable.
89
        $rightsStates = [
90 1
            'current' => $this->user->getUserRights($this->project),
91 1
            'former' => array_unique($former),
92
        ];
93
94 1
        return $rightsStates;
95
    }
96
97
    /**
98
     * Get a list of the current and former global rights of the user.
99
     * @return array With keys 'current' and 'former'.
100
     */
101
    public function getGlobalRightsStates()
102
    {
103
        $current = [];
104
        $former = [];
105
106
        foreach (array_reverse($this->getGlobalRightsChanges()) as $change) {
107
            $current = array_diff(
108
                array_unique(array_merge($current, $change['added'])),
109
                $change['removed']
110
            );
111
            $former = array_diff(
112
                array_unique(array_merge($former, $change['removed'])),
113
                $change['added']
114
            );
115
        }
116
117
        return [
118
            'current' => $current,
119
            'former' => $former,
120
        ];
121
    }
122
123
    /**
124
     * Get the localized names for the user groups, fetched from on-wiki system messages.
125
     * @return string[] Localized names keyed by database value.
126
     */
127
    public function getRightsNames()
128
    {
129
        if (isset($this->rightsNames)) {
130
            return $this->rightsNames;
131
        }
132
133
        $rightsStates = $this->getRightsStates();
134
        $globalRightsStates = $this->getGlobalRightsStates();
135
        $rightsToCheck = array_merge(
136
            array_merge($rightsStates['current'], $globalRightsStates['current']),
137
            array_merge($rightsStates['former'], $globalRightsStates['former'])
138
        );
139
140
        $this->rightsNames = $this->getRepository()
141
            ->getRightsNames($this->project, $rightsToCheck, $this->i18n->getLang());
142
143
        return $this->rightsNames;
144
    }
145
146
    /**
147
     * Get global user rights changes of the given user.
148
     * @param Project $project
149
     * @param User $user
150
     * @return string[] Keyed by timestamp then 'added' and 'removed'.
151
     */
152 1
    public function getGlobalRightsChanges()
153
    {
154 1
        if (isset($this->globalRightsChanges)) {
155
            return $this->globalRightsChanges;
156
        }
157
158 1
        $logData = $this->getRepository()
159 1
            ->getGlobalRightsChanges($this->project, $this->user);
160
161 1
        $this->globalRightsChanges = $this->processRightsChanges($logData);
162
163 1
        return $this->globalRightsChanges;
164
    }
165
166
    /**
167
     * Process the given rights changes, sorting an putting in a human-readable format.
168
     * @param  array $logData As fetched with EditCounterRepository::getRightsChanges.
169
     * @return array
170
     */
171 1
    private function processRightsChanges($logData)
172
    {
173 1
        $rightsChanges = [];
174
175 1
        foreach ($logData as $row) {
176 1
            $unserialized = @unserialize($row['log_params']);
177 1
            if ($unserialized !== false) {
178 1
                $old = $unserialized['4::oldgroups'];
179 1
                $new = $unserialized['5::newgroups'];
180 1
                $added = array_diff($new, $old);
181 1
                $removed = array_diff($old, $new);
182
183 1
                $rightsChanges = $this->setAutoRemovals($rightsChanges, $row, $unserialized, $added);
184
            } else {
185
                // This is the old school format the most likely contains
186
                // the list of rights additions as a comma-separated list.
187
                try {
188 1
                    list($old, $new) = explode("\n", $row['log_params']);
189 1
                    $old = array_filter(array_map('trim', explode(',', $old)));
190 1
                    $new = array_filter(array_map('trim', explode(',', $new)));
191 1
                    $added = array_diff($new, $old);
192 1
                    $removed = array_diff($old, $new);
193
                } catch (Exception $e) {
194
                    // Really, really old school format that may be missing metadata
195
                    // altogether. Here we'll just leave $added and $removed empty.
196
                    $added = [];
197
                    $removed = [];
198
                }
199
            }
200
201
            // Remove '(none)'.
202 1
            if (in_array('(none)', $added)) {
203
                array_splice($added, array_search('(none)', $added), 1);
0 ignored issues
show
Bug introduced by
It seems like array_search('(none)', $added) can also be of type string and false; however, parameter $offset of array_splice() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

203
                array_splice($added, /** @scrutinizer ignore-type */ array_search('(none)', $added), 1);
Loading history...
204
            }
205 1
            if (in_array('(none)', $removed)) {
206
                array_splice($removed, array_search('(none)', $removed), 1);
207
            }
208
209 1
            $rightsChanges[$row['log_timestamp']] = [
210 1
                'logId' => $row['log_id'],
211 1
                'admin' => $row['log_user_text'],
212 1
                'comment' => $row['log_comment'],
213 1
                'added' => array_values($added),
214 1
                'removed' => array_values($removed),
215 1
                'automatic' => $row['log_action'] === 'autopromote',
216 1
                'type' => $row['type'],
217
            ];
218
        }
219
220 1
        krsort($rightsChanges);
221
222 1
        return $rightsChanges;
223
    }
224
225
    /**
226
     * Check the given log entry for rights changes that are set to automatically expire,
227
     * and add entries to $rightsChanges accordingly.
228
     * @param array $rightsChanges
229
     * @param array $row Log entry row from database.
230
     * @param array $params Unserialized log params.
231
     * @param string[] $added List of added user rights.
232
     * @return array Modified $rightsChanges.
233
     */
234 1
    private function setAutoRemovals($rightsChanges, $row, $params, $added)
235
    {
236 1
        foreach ($added as $index => $entry) {
237 1
            if (!isset($params['newmetadata'][$index]) ||
238 1
                !array_key_exists('expiry', $params['newmetadata'][$index]) ||
239 1
                empty($params['newmetadata'][$index]['expiry'])
240
            ) {
241 1
                continue;
242
            }
243
244 1
            $expiry = $params['newmetadata'][$index]['expiry'];
245
246 1
            if (isset($rightsChanges[$expiry]) && !in_array($entry, $rightsChanges[$expiry]['removed'])) {
247 1
                $rightsChanges[$expiry]['removed'][] = $entry;
248
            } else {
249 1
                $rightsChanges[$expiry] = [
250 1
                    'logId' => $row['log_id'],
251 1
                    'admin' => $row['log_user_text'],
252
                    'comment' => null,
253
                    'added' => [],
254 1
                    'removed' => [$entry],
255
                    'automatic' => true,
256 1
                    'type' => $row['type'],
257
                ];
258
            }
259
        }
260
261 1
        return $rightsChanges;
262
    }
263
}
264