Passed
Push — master ( 3cc4a0...9623d2 )
by Daimona
08:26
created

Wiki::getPageCreationTS()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

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