Passed
Push — master ( 2f436c...0d0277 )
by Daimona
02:05
created

Wiki::setPagePrefix()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 1
b 0
f 0
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
	 * Gets the content of a wiki page
86
	 *
87
	 * @param string $title
88
	 * @param int|null $section
89
	 * @return string
90
	 * @throws MissingPageException
91
	 * @throws MissingSectionException
92
	 */
93
	public function getPageContent( string $title, int $section = null ) : string {
94
		$fullTitle = $this->pagePrefix . $title;
95
		$msg = "Retrieving content of $fullTitle" . ( $section !== null ? ", section $section" : '' );
96
		$this->logger->info( $msg );
97
		$params = [
98
			'action' => 'query',
99
			'titles' => $title,
100
			'prop' => 'revisions',
101
			'rvslots' => 'main',
102
			'rvprop' => 'content'
103
		];
104
105
		if ( $section !== null ) {
106
			$params['rvsection'] = $section;
107
		}
108
109
		$req = $this->buildRequest( $params );
110
		$data = $req->execute();
111
		$page = reset( $data->query->pages );
112
		if ( isset( $page->missing ) ) {
113
			throw new MissingPageException( $title );
114
		}
115
116
		$mainSlot = $page->revisions[0]->slots->main;
117
118
		if ( $section !== null && isset( $mainSlot->nosuchsection ) ) {
119
			throw new MissingSectionException( $title, $section );
120
		}
121
		return $mainSlot->{ '*' };
122
	}
123
124
	/**
125
	 * Basically a wrapper for action=edit
126
	 *
127
	 * @param array $params
128
	 * @throws EditException
129
	 */
130
	public function editPage( array $params ) : void {
131
		$this->login();
132
133
		$params = [
134
			'action' => 'edit',
135
			'token' => $this->getToken( 'csrf' ),
136
		] + $params;
137
138
		if ( BOT_EDITS === true ) {
1 ignored issue
show
introduced by
The condition BotRiconferme\Wiki\BOT_EDITS === true is always false.
Loading history...
139
			$params['bot'] = 1;
140
		}
141
142
		$res = $this->buildRequest( $params )->setPost()->execute();
143
144
		$editData = $res->edit;
145
		if ( $editData->result !== 'Success' ) {
146
			if ( isset( $editData->captcha ) ) {
147
				throw new EditException( 'Got captcha!' );
148
			}
149
			throw new EditException( $editData->info ?? reset( $editData ) );
150
		}
151
	}
152
153
	/**
154
	 * Login wrapper. Checks if we're already logged in and clears tokens cache
155
	 * @throws LoginException
156
	 */
157
	public function login() : void {
158
		if ( $this->loginInfo === null ) {
159
			throw new CannotLoginException( 'Missing login data' );
160
		}
161
		if ( $this->loggedIn ) {
162
			return;
163
		}
164
165
		// Yes, this is an easter egg.
166
		$this->logger->info( 'Logging in. Username: BotRiconferme, password: correctHorseBatteryStaple' );
167
168
		$params = [
169
			'action' => 'login',
170
			'lgname' => $this->getLoginInfo()->getUsername(),
171
			'lgpassword' => $this->getLoginInfo()->getPassword(),
172
			'lgtoken' => $this->getToken( 'login' )
173
		];
174
175
		try {
176
			$res = $this->buildRequest( $params )->setPost()->execute();
177
		} catch ( APIRequestException $e ) {
178
			throw new LoginException( $e->getMessage() );
179
		}
180
181
		if ( !isset( $res->login->result ) || $res->login->result !== 'Success' ) {
182
			throw new LoginException( $res->login->reason ?? 'Unknown error' );
183
		}
184
185
		$this->loggedIn = true;
186
		// Clear tokens cache
187
		$this->tokens = [];
188
		$this->logger->info( 'Login succeeded' );
189
	}
190
191
	/**
192
	 * Get a token, cached.
193
	 *
194
	 * @param string $type
195
	 * @return string
196
	 */
197
	public function getToken( string $type ) : string {
198
		if ( !isset( $this->tokens[ $type ] ) ) {
199
			$params = [
200
				'action' => 'query',
201
				'meta'   => 'tokens',
202
				'type'   => $type
203
			];
204
205
			$req = $this->buildRequest( $params );
206
			$res = $req->execute();
207
208
			$this->tokens[ $type ] = $res->query->tokens->{ "{$type}token" };
209
		}
210
211
		return $this->tokens[ $type ];
212
	}
213
214
	/**
215
	 * Get the timestamp of the creation of the given page
216
	 *
217
	 * @param string $title
218
	 * @return int
219
	 */
220
	public function getPageCreationTS( string $title ) : int {
221
		$params = [
222
			'action' => 'query',
223
			'prop' => 'revisions',
224
			'titles' => $title,
225
			'rvprop' => 'timestamp',
226
			'rvslots' => 'main',
227
			'rvlimit' => 1,
228
			'rvdir' => 'newer'
229
		];
230
231
		$res = $this->buildRequest( $params )->execute();
232
		$data = $res->query->pages;
233
		return strtotime( reset( $data )->revisions[0]->timestamp );
234
	}
235
236
	/**
237
	 * Sysop-level inifinite protection for a given page
238
	 *
239
	 * @param string $title
240
	 * @param string $reason
241
	 */
242
	public function protectPage( string $title, string $reason ) : void {
243
		$fullTitle = $this->pagePrefix . $title;
244
		$this->logger->info( "Protecting page $fullTitle" );
245
		$this->login();
246
247
		$params = [
248
			'action' => 'protect',
249
			'title' => $title,
250
			'protections' => 'edit=sysop|move=sysop',
251
			'expiry' => 'infinite',
252
			'reason' => $reason,
253
			'token' => $this->getToken( 'csrf' )
254
		];
255
256
		$this->buildRequest( $params )->setPost()->execute();
257
	}
258
259
	/**
260
	 * Shorthand
261
	 * @param array $params
262
	 * @return RequestBase
263
	 */
264
	private function buildRequest( array $params ) : RequestBase {
265
		return $this->requestFactory->newFromParams( $params );
266
	}
267
}
268