Passed
Branch master (a4e902)
by Daimona
01:39
created

UpdateList::runInternal()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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