Passed
Push — master ( 0986c5...31e6ff )
by Daimona
01:50
created

UpdateList::getExtraGroups()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
nc 4
nop 0
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme\Task;
4
5
use BotRiconferme\TaskResult;
6
use BotRiconferme\Request\RequestBase;
7
use BotRiconferme\Exception\TaskException;
8
9
/**
10
 * Updates the JSON list, adding and removing dates according to the API list of privileged people
11
 */
12
class UpdateList extends Task {
13
	/** @var array[] The JSON list */
14
	private $botList;
15
	/** @var array[] The list from the API request */
16
	private $actualList;
17
18
	/**
19
	 * @inheritDoc
20
	 */
21
	public function run() : TaskResult {
22
		$this->getLogger()->info( 'Starting task UpdateList' );
23
		$this->actualList = $this->getActualAdmins();
24
		$this->botList = $this->getDataProvider()->getUsersList();
25
26
		$missing = $this->getMissingGroups();
27
		$extra = $this->getExtraGroups();
28
29
		$newContent = $this->botList;
30
		if ( $missing || $extra ) {
31
			$newContent = $this->getNewContent( $missing, $extra );
32
		}
33
34
		if ( $newContent === $this->botList ) {
35
			$this->getLogger()->info( 'Admin list already up-to-date' );
36
		} else {
37
			$this->doUpdateList( $newContent );
38
		}
39
40
		if ( $this->errors ) {
41
			// We're fine with it, but don't run other tasks
42
			$msg = 'Task UpdateList completed with warnings.';
43
			$status = self::STATUS_ERROR;
44
		} else {
45
			$msg = 'Task UpdateList completed successfully';
46
			$status = self::STATUS_OK;
47
		}
48
49
		$this->getLogger()->info( $msg );
50
		return new TaskResult( $status, $this->errors );
51
	}
52
53
	/**
54
	 * @return array
55
	 */
56
	protected function getActualAdmins() : array {
57
		$this->getLogger()->debug( 'Retrieving admins - API' );
58
		$params = [
59
			'action' => 'query',
60
			'list' => 'allusers',
61
			'augroup' => 'sysop',
62
			'auprop' => 'groups',
63
			'aulimit' => 'max',
64
		];
65
66
		$req = RequestBase::newFromParams( $params );
67
		return $this->extractAdmins( $req->execute() );
68
	}
69
70
	/**
71
	 * @param \stdClass $data
72
	 * @return array
73
	 */
74
	protected function extractAdmins( \stdClass $data ) : array {
75
		$ret = [];
76
		$blacklist = $this->getConfig()->get( 'exclude-admins' );
77
		foreach ( $data->query->allusers as $u ) {
78
			if ( in_array( $u->name, $blacklist ) ) {
79
				continue;
80
			}
81
			$interestingGroups = array_intersect( $u->groups, [ 'sysop', 'bureaucrat', 'checkuser' ] );
82
			$ret[ $u->name ] = $interestingGroups;
83
		}
84
		return $ret;
85
	}
86
87
	/**
88
	 * Populate a list of new admins missing from the JSON list and their groups
89
	 *
90
	 * @return array[]
91
	 */
92
	protected function getMissingGroups() : array {
93
		$missing = [];
94
		foreach ( $this->actualList as $adm => $groups ) {
95
			$groupsList = [];
96
			if ( !isset( $this->botList[ $adm ] ) ) {
97
				$groupsList = $groups;
98
			} elseif ( count( $groups ) > count( $this->botList[$adm] ) ) {
99
				// Only some groups are missing
100
				$groupsList = array_diff_key( $groups, $this->botList[$adm] );
101
			}
102
103
			foreach ( $groupsList as $group ) {
104
				try {
105
					$missing[ $adm ][ $group ] = $this->getFlagDate( $adm, $group );
106
				} catch ( TaskException $e ) {
107
					$this->errors[] = $e->getMessage();
108
				}
109
			}
110
		}
111
		return $missing;
112
	}
113
114
	/**
115
	 * Get the flag date for the given admin and group.
116
	 *
117
	 * @param string $admin
118
	 * @param string $group
119
	 * @return string
120
	 * @throws TaskException
121
	 */
122
	protected function getFlagDate( string $admin, string $group ) : string {
123
		$this->getLogger()->info( "Retrieving $group flag date for $admin" );
124
125
		if ( $group === 'checkuser' ) {
126
			// Little hack
127
			$oldUrl = RequestBase::$url;
128
			RequestBase::$url = 'https://meta.wikimedia.org/w/api.php';
129
			$admin .= '@itwiki';
130
		}
131
132
		$params = [
133
			'action' => 'query',
134
			'list' => 'logevents',
135
			'leprop' => 'timestamp|details',
136
			'leaction' => 'rights/rights',
137
			'letitle' => "User:$admin",
138
			'lelimit' => 'max'
139
		];
140
141
		$data = RequestBase::newFromParams( $params )->execute();
142
		$ts = $this->extractTimestamp( $data, $group );
143
144
		if ( isset( $oldUrl ) ) {
145
			RequestBase::$url = $oldUrl;
146
		}
147
148
		if ( $ts === null ) {
149
			throw new TaskException( "$group flag date unavailable for $admin" );
150
		}
151
152
		return date( 'd/m/Y', strtotime( $ts ) );
153
	}
154
155
	/**
156
	 * Find the actual timestamp when the user was given the searched group
157
	 *
158
	 * @param \stdClass $data
159
	 * @param string $group
160
	 * @return string|null
161
	 */
162
	private function extractTimestamp( \stdClass $data, string $group ) : ?string {
163
		$ts = null;
164
		foreach ( $data->query->logevents as $entry ) {
165
			if ( !isset( $entry->params ) ) {
166
				// Old entries
167
				continue;
168
			}
169
170
			$addedGroups = array_diff( $entry->params->newgroups, $entry->params->oldgroups );
171
			if ( in_array( $group, $addedGroups ) ) {
172
				$ts = $entry->timestamp;
173
				break;
174
			}
175
		}
176
		return $ts;
177
	}
178
179
	/**
180
	 * Get a list of admins who are in the JSON page but don't have the listed privileges anymore
181
	 *
182
	 * @return array[]
183
	 */
184
	protected function getExtraGroups() : array {
185
		$extra = [];
186
		foreach ( $this->botList as $name => $groups ) {
187
			if ( !isset( $this->actualList[ $name ] ) ) {
188
				$extra[ $name ] = $groups;
189
			} elseif ( count( $groups ) > count( $this->actualList[ $name ] ) ) {
190
				$extra[ $name ] = array_diff_key( $groups, $this->actualList[ $name ] );
191
			}
192
		}
193
		return $extra;
194
	}
195
196
	/**
197
	 * Really edit the list with the new content, if it's not already up-to-date
198
	 *
199
	 * @param array $newContent
200
	 */
201
	protected function doUpdateList( array $newContent ) {
202
		$this->getLogger()->info( 'Updating admin list' );
203
204
		$params = [
205
			'title' => $this->getConfig()->get( 'list-title' ),
206
			'text' => json_encode( $newContent ),
207
			'summary' => $this->getConfig()->get( 'list-update-summary' )
208
		];
209
210
		$this->getController()->editPage( $params );
211
	}
212
213
	/**
214
	 * Get the new content for the list
215
	 *
216
	 * @param array[] $missing
217
	 * @param array[] $extra
218
	 * @return array[]
219
	 */
220
	protected function getNewContent( array $missing, array $extra ) : array {
221
		$newContent = $this->botList;
222
		foreach ( $newContent as $user => $groups ) {
223
			if ( isset( $missing[ $user ] ) ) {
224
				$newContent[ $user ] = array_merge( $groups, $missing[ $user ] );
225
				unset( $missing[ $user ] );
226
			} elseif ( isset( $extra[ $user ] ) ) {
227
				$newContent[ $user ] = array_diff_key( $groups, $extra[ $user ] );
228
			}
229
		}
230
		// Add users which don't have an entry at all, and remove empty users
231
		$newContent = array_filter( array_merge( $newContent, $missing ) );
232
		ksort( $newContent );
233
		return $newContent;
234
	}
235
}
236