Passed
Push — master ( aa10e3...d44ff3 )
by Daimona
02:11
created

Wiki::getPageContent()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 27
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
c 3
b 0
f 0
nc 6
nop 2
dl 0
loc 27
rs 9.3554
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme\Wiki;
4
5
use BotRiconferme\Exception\APIRequestException;
6
use BotRiconferme\Exception\CannotLoginException;
7
use BotRiconferme\Exception\EditException;
8
use BotRiconferme\Exception\LoginException;
9
use BotRiconferme\Exception\MissingPageException;
10
use BotRiconferme\Exception\MissingSectionException;
11
use BotRiconferme\Request\RequestBase;
12
use BotRiconferme\Request\RequestFactory;
13
use Psr\Log\LoggerInterface;
14
15
/**
16
 * Class for wiki interaction, contains some requests shorthands
17
 */
18
class Wiki {
19
	/** @var bool */
20
	private $loggedIn = false;
21
	/** @var LoggerInterface */
22
	private $logger;
23
	/** @var string[] */
24
	private $tokens;
25
	/** @var LoginInfo */
26
	private $loginInfo;
27
	/** @var RequestFactory */
28
	private $requestFactory;
29
	/** @var string */
30
	private $localUserIdentifier = '';
31
	/** @var string Used for logging */
32
	private $pagePrefix = '';
33
34
	/**
35
	 * @param LoginInfo $li
36
	 * @param LoggerInterface $logger
37
	 * @param RequestFactory $requestFactory
38
	 */
39
	public function __construct(
40
		LoginInfo $li,
41
		LoggerInterface $logger,
42
		RequestFactory $requestFactory
43
	) {
44
		$this->loginInfo = $li;
45
		$this->logger = $logger;
46
		$this->requestFactory = $requestFactory;
47
	}
48
49
	/**
50
	 * @return LoginInfo
51
	 */
52
	public function getLoginInfo() : LoginInfo {
53
		return $this->loginInfo;
54
	}
55
56
	/**
57
	 * @return RequestFactory
58
	 */
59
	public function getRequestFactory() : RequestFactory {
60
		return $this->requestFactory;
61
	}
62
63
	/**
64
	 * @param string $prefix
65
	 */
66
	public function setPagePrefix( string $prefix ) : void {
67
		$this->pagePrefix = $prefix;
68
	}
69
70
	/**
71
	 * @param string $ident
72
	 */
73
	public function setLocalUserIdentifier( string $ident ) : void {
74
		$this->localUserIdentifier = $ident;
75
	}
76
77
	/**
78
	 * @return string
79
	 */
80
	public function getLocalUserIdentifier() : string {
81
		return $this->localUserIdentifier;
82
	}
83
84
	/**
85
	 * @param string $title
86
	 * @param int|null $section
87
	 */
88
	private function logRead( string $title, int $section = null ) : void {
89
		$fullTitle = $this->pagePrefix . $title;
90
		$msg = "Retrieving content of $fullTitle" . ( $section !== null ? ", section $section" : '' );
91
		$this->logger->info( $msg );
92
	}
93
94
	/**
95
	 * Gets the content of a wiki page
96
	 *
97
	 * @param string $title
98
	 * @param int|null $section
99
	 * @return string
100
	 * @throws MissingPageException
101
	 * @throws MissingSectionException
102
	 */
103
	public function getPageContent( string $title, int $section = null ) : string {
104
		$this->logRead( $title, $section );
105
		$params = [
106
			'action' => 'query',
107
			'titles' => $title,
108
			'prop' => 'revisions',
109
			'rvslots' => 'main',
110
			'rvprop' => 'content'
111
		];
112
113
		if ( $section !== null ) {
114
			$params['rvsection'] = $section;
115
		}
116
117
		$req = $this->buildRequest( $params );
118
		$data = $req->execute();
119
		$page = reset( $data->query->pages );
120
		if ( isset( $page->missing ) ) {
121
			throw new MissingPageException( $title );
122
		}
123
124
		$mainSlot = $page->revisions[0]->slots->main;
125
126
		if ( $section !== null && isset( $mainSlot->nosuchsection ) ) {
127
			throw new MissingSectionException( $title, $section );
128
		}
129
		return $mainSlot->{ '*' };
130
	}
131
132
	/**
133
	 * Basically a wrapper for action=edit
134
	 *
135
	 * @param array $params
136
	 * @throws EditException
137
	 */
138
	public function editPage( array $params ) : void {
139
		$this->login();
140
141
		$params = [
142
			'action' => 'edit',
143
			'token' => $this->getToken( 'csrf' ),
144
		] + $params;
145
146
		if ( BOT_EDITS === true ) {
1 ignored issue
show
introduced by
The condition BotRiconferme\Wiki\BOT_EDITS === true is always false.
Loading history...
147
			$params['bot'] = 1;
148
		}
149
150
		$res = $this->buildRequest( $params )->setPost()->execute();
151
152
		$editData = $res->edit;
153
		if ( $editData->result !== 'Success' ) {
154
			if ( isset( $editData->captcha ) ) {
155
				throw new EditException( 'Got captcha!' );
156
			}
157
			throw new EditException( $editData->info ?? reset( $editData ) );
158
		}
159
	}
160
161
	/**
162
	 * Login wrapper. Checks if we're already logged in and clears tokens cache
163
	 * @throws LoginException
164
	 */
165
	public function login() : void {
166
		if ( $this->loginInfo === null ) {
167
			throw new CannotLoginException( 'Missing login data' );
168
		}
169
		if ( $this->loggedIn ) {
170
			return;
171
		}
172
173
		// Yes, this is an easter egg.
174
		$this->logger->info( 'Logging in. Username: BotRiconferme, password: correctHorseBatteryStaple' );
175
176
		$params = [
177
			'action' => 'login',
178
			'lgname' => $this->getLoginInfo()->getUsername(),
179
			'lgpassword' => $this->getLoginInfo()->getPassword(),
180
			'lgtoken' => $this->getToken( 'login' )
181
		];
182
183
		try {
184
			$res = $this->buildRequest( $params )->setPost()->execute();
185
		} catch ( APIRequestException $e ) {
186
			throw new LoginException( $e->getMessage() );
187
		}
188
189
		if ( !isset( $res->login->result ) || $res->login->result !== 'Success' ) {
190
			throw new LoginException( $res->login->reason ?? 'Unknown error' );
191
		}
192
193
		$this->loggedIn = true;
194
		// Clear tokens cache
195
		$this->tokens = [];
196
		$this->logger->info( 'Login succeeded' );
197
	}
198
199
	/**
200
	 * Get a token, cached.
201
	 *
202
	 * @param string $type
203
	 * @return string
204
	 */
205
	public function getToken( string $type ) : string {
206
		if ( !isset( $this->tokens[ $type ] ) ) {
207
			$params = [
208
				'action' => 'query',
209
				'meta'   => 'tokens',
210
				'type'   => $type
211
			];
212
213
			$req = $this->buildRequest( $params );
214
			$res = $req->execute();
215
216
			$this->tokens[ $type ] = $res->query->tokens->{ "{$type}token" };
217
		}
218
219
		return $this->tokens[ $type ];
220
	}
221
222
	/**
223
	 * Get the timestamp of the creation of the given page
224
	 *
225
	 * @param string $title
226
	 * @return int
227
	 */
228
	public function getPageCreationTS( string $title ) : int {
229
		$params = [
230
			'action' => 'query',
231
			'prop' => 'revisions',
232
			'titles' => $title,
233
			'rvprop' => 'timestamp',
234
			'rvslots' => 'main',
235
			'rvlimit' => 1,
236
			'rvdir' => 'newer'
237
		];
238
239
		$res = $this->buildRequest( $params )->execute();
240
		$data = $res->query->pages;
241
		return strtotime( reset( $data )->revisions[0]->timestamp );
242
	}
243
244
	/**
245
	 * Sysop-level inifinite protection for a given page
246
	 *
247
	 * @param string $title
248
	 * @param string $reason
249
	 */
250
	public function protectPage( string $title, string $reason ) : void {
251
		$fullTitle = $this->pagePrefix . $title;
252
		$this->logger->info( "Protecting page $fullTitle" );
253
		$this->login();
254
255
		$params = [
256
			'action' => 'protect',
257
			'title' => $title,
258
			'protections' => 'edit=sysop|move=sysop',
259
			'expiry' => 'infinite',
260
			'reason' => $reason,
261
			'token' => $this->getToken( 'csrf' )
262
		];
263
264
		$this->buildRequest( $params )->setPost()->execute();
265
	}
266
267
	/**
268
	 * Block a user, infinite expiry
269
	 *
270
	 * @param string $username
271
	 * @param string $reason
272
	 */
273
	public function blockUser( string $username, string $reason ) : void {
274
		$this->logger->info( "Blocking user $username" );
275
		$this->login();
276
277
		$params = [
278
			'action' => 'block',
279
			// Don't allow talk page edit 'allowusertalk' => 1,
280
			'autoblock' => 1,
281
			'nocreate' => 1,
282
			'expiry' => 'indefinite',
283
			// No anononly
284
			'noemail' => 1,
285
			// No reblock
286
			'reason' => $reason,
287
			'user' => $username,
288
			'token' => $this->getToken( 'csrf' )
289
		];
290
291
		$this->buildRequest( $params )->setPost()->execute();
292
	}
293
294
	/**
295
	 * Shorthand
296
	 * @param array $params
297
	 * @return RequestBase
298
	 */
299
	private function buildRequest( array $params ) : RequestBase {
300
		return $this->requestFactory->newFromParams( $params );
301
	}
302
}
303