Passed
Push — master ( ba2710...df8555 )
by Daimona
01:51
created

Controller::editPage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 15
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 12
nc 2
nop 1
dl 0
loc 15
rs 9.8666
c 0
b 0
f 0
1
<?php declare( strict_types=1 );
2
3
namespace BotRiconferme\Wiki;
4
5
use BotRiconferme\Config;
6
use BotRiconferme\Exception\EditException;
7
use BotRiconferme\Exception\LoginException;
8
use BotRiconferme\Exception\APIRequestException;
9
use BotRiconferme\Exception\MissingPageException;
10
use BotRiconferme\Exception\MissingSectionException;
11
use BotRiconferme\Logger;
12
use BotRiconferme\Request\RequestBase;
13
14
/**
15
 * Class for wiki interaction, contains some requests shorthands
16
 */
17
class Controller {
18
	/** @var bool */
19
	private static $loggedIn = false;
20
	/** @var Logger */
21
	private $logger;
22
	/** @var string */
23
	private $domain;
24
	/** @var string[] */
25
	private $tokens;
26
27
	/**
28
	 * @param string $domain The URL of the wiki, if different from default
29
	 */
30
	public function __construct( string $domain = DEFAULT_URL ) {
31
		$this->logger = new Logger;
32
		$this->domain = $domain;
33
	}
34
35
	/**
36
	 * Gets the content of a wiki page
37
	 *
38
	 * @param string $title
39
	 * @param int|null $section
40
	 * @return string
41
	 * @throws MissingPageException
42
	 */
43
	public function getPageContent( string $title, int $section = null ) : string {
44
		$this->logger->debug( "Retrieving content of page $title" );
45
		$params = [
46
			'action' => 'query',
47
			'titles' => $title,
48
			'prop' => 'revisions',
49
			'rvslots' => 'main',
50
			'rvprop' => 'content'
51
		];
52
53
		if ( $section !== null ) {
54
			$params['rvsection'] = $section;
55
		}
56
57
		$req = RequestBase::newFromParams( $params )->setUrl( $this->domain );
58
		$data = $req->execute();
59
		$page = reset( $data->query->pages );
60
		if ( isset( $page->missing ) ) {
61
			throw new MissingPageException( $title );
62
		}
63
64
		$mainSlot = $page->revisions[0]->slots->main;
65
66
		if ( $section !== null && isset( $mainSlot->nosuchsection ) ) {
67
			throw new MissingSectionException( $title, $section );
68
		}
69
		return $mainSlot->{ '*' };
70
	}
71
72
	/**
73
	 * Basically a wrapper for action=edit
74
	 *
75
	 * @param array $params
76
	 * @throws EditException
77
	 */
78
	public function editPage( array $params ) {
79
		$this->login();
80
81
		$params = [
82
			'action' => 'edit',
83
			'token' => $this->getToken( 'csrf' ),
84
			'bot' => Config::getInstance()->get( 'bot-edits' )
85
		] + $params;
86
87
		$res = RequestBase::newFromParams( $params )
88
			->setUrl( $this->domain )
89
			->setPost()
90
			->execute();
91
		if ( $res->edit->result !== 'Success' ) {
92
			throw new EditException( $res->edit->info );
93
		}
94
	}
95
96
	/**
97
	 * Login wrapper. Checks if we're already logged in and clears tokens cache
98
	 * @throws LoginException
99
	 */
100
	public function login() {
101
		if ( self::$loggedIn ) {
102
			$this->logger->debug( 'Already logged in' );
103
			return;
104
		}
105
106
		$this->logger->info( 'Logging in' );
107
108
		$params = [
109
			'action' => 'login',
110
			'lgname' => Config::getInstance()->get( 'username' ),
111
			'lgpassword' => Config::getInstance()->get( 'password' ),
112
			'lgtoken' => $this->getToken( 'login' )
113
		];
114
115
		try {
116
			$res = RequestBase::newFromParams( $params )->setUrl( $this->domain )->setPost()->execute();
117
		} catch ( APIRequestException $e ) {
118
			throw new LoginException( $e->getMessage() );
119
		}
120
121
		if ( !isset( $res->login->result ) || $res->login->result !== 'Success' ) {
122
			throw new LoginException( 'Unknown error' );
123
		}
124
125
		self::$loggedIn = true;
126
		// Clear tokens cache
127
		$this->tokens = [];
128
		$this->logger->info( 'Login succeeded' );
129
	}
130
131
	/**
132
	 * Get a token, cached.
133
	 *
134
	 * @param string $type
135
	 * @return string
136
	 */
137
	public function getToken( string $type ) : string {
138
		if ( !isset( $this->tokens[ $type ] ) ) {
139
			$params = [
140
				'action' => 'query',
141
				'meta'   => 'tokens',
142
				'type'   => $type
143
			];
144
145
			$req = RequestBase::newFromParams( $params )->setUrl( $this->domain );
146
			$res = $req->execute();
147
148
			$this->tokens[ $type ] = $res->query->tokens->{ "{$type}token" };
149
		}
150
151
		return $this->tokens[ $type ];
152
	}
153
154
	/**
155
	 * Get the timestamp of the creation of the given page
156
	 *
157
	 * @param string $title
158
	 * @return int
159
	 */
160
	public function getPageCreationTS( string $title ) : int {
161
		$params = [
162
			'action' => 'query',
163
			'prop' => 'revisions',
164
			'titles' => $title,
165
			'rvprop' => 'timestamp',
166
			'rvslots' => 'main',
167
			'rvlimit' => 1,
168
			'rvdir' => 'newer'
169
		];
170
171
		$res = RequestBase::newFromParams( $params )->setUrl( $this->domain )->execute();
172
		$data = $res->query->pages;
173
		return strtotime( reset( $data )->revisions[0]->timestamp );
174
	}
175
176
	/**
177
	 * Sysop-level inifinite protection for a given page
178
	 *
179
	 * @param string $title
180
	 * @param string $reason
181
	 */
182
	public function protectPage( string $title, string $reason ) {
183
		$this->logger->info( "Protecting page $title" );
184
		$this->login();
185
186
		$params = [
187
			'action' => 'protect',
188
			'title' => $title,
189
			'protections' => 'edit=sysop|move=sysop',
190
			'expiry' => 'infinite',
191
			'reason' => $reason,
192
			'token' => $this->getToken( 'csrf' )
193
		];
194
195
		RequestBase::newFromParams( $params )->setUrl( $this->domain )->setPost()->execute();
196
	}
197
}
198