Passed
Push — master ( dd1fbc...6aef8a )
by Daimona
01:29
created

UpdateList::getNewContent()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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