1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
4
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
5
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
6
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
7
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
8
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
9
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
10
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
11
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
12
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
13
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
14
|
|
|
* |
15
|
|
|
* This software consists of voluntary contributions made by many individuals |
16
|
|
|
* and is licensed under the LGPL. For more information please see |
17
|
|
|
* <http://phing.info>. |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace Phing\Task\Optional; |
21
|
|
|
|
22
|
|
|
use Phing\Exception\BuildException; |
23
|
|
|
use Phing\Io\FileUtils; |
24
|
|
|
use Phing\Task; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* Publish Wiki document using Wiki API. |
28
|
|
|
* |
29
|
|
|
* @author Piotr Lewandowski <[email protected]> |
30
|
|
|
* @package phing.tasks.ext |
31
|
|
|
*/ |
32
|
|
|
class WikiPublishTask extends Task |
33
|
|
|
{ |
34
|
|
|
/** |
35
|
|
|
* Wiki API url |
36
|
|
|
* |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
private $apiUrl; |
40
|
|
|
/** |
41
|
|
|
* Wiki API user name |
42
|
|
|
* |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
private $apiUser; |
46
|
|
|
/** |
47
|
|
|
* Wiki API password |
48
|
|
|
* |
49
|
|
|
* @var string |
50
|
|
|
*/ |
51
|
|
|
private $apiPassword; |
52
|
|
|
/** |
53
|
|
|
* Wiki document Id. Document can be identified by title instead. |
54
|
|
|
* |
55
|
|
|
* @var int |
56
|
|
|
*/ |
57
|
|
|
private $id; |
58
|
|
|
/** |
59
|
|
|
* Wiki document title |
60
|
|
|
* |
61
|
|
|
* @var string |
62
|
|
|
*/ |
63
|
|
|
private $title; |
64
|
|
|
/** |
65
|
|
|
* Wiki document content |
66
|
|
|
* |
67
|
|
|
* @var string |
68
|
|
|
*/ |
69
|
|
|
private $content; |
70
|
|
|
/** |
71
|
|
|
* Publishing mode (append, prepend, overwrite). |
72
|
|
|
* |
73
|
|
|
* @var string |
74
|
|
|
*/ |
75
|
|
|
private $mode = 'append'; |
76
|
|
|
/** |
77
|
|
|
* Publish modes map |
78
|
|
|
* |
79
|
|
|
* @var array |
80
|
|
|
*/ |
81
|
|
|
private $modeMap = [ |
82
|
|
|
'overwrite' => 'text', |
83
|
|
|
'append' => 'appendtext', |
84
|
|
|
'prepend' => 'prependtext', |
85
|
|
|
]; |
86
|
|
|
/** |
87
|
|
|
* Curl handler |
88
|
|
|
* |
89
|
|
|
* @var resource |
90
|
|
|
*/ |
91
|
|
|
private $curl; |
92
|
|
|
/** |
93
|
|
|
* Wiki api edit token |
94
|
|
|
* |
95
|
|
|
* @var string |
96
|
|
|
*/ |
97
|
|
|
private $apiEditToken; |
98
|
|
|
/** |
99
|
|
|
* Temporary cookies file |
100
|
|
|
* |
101
|
|
|
* @var string |
102
|
|
|
*/ |
103
|
|
|
private $cookiesFile; |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @param string $apiPassword |
107
|
|
|
*/ |
108
|
1 |
|
public function setApiPassword($apiPassword) |
109
|
|
|
{ |
110
|
1 |
|
$this->apiPassword = $apiPassword; |
111
|
1 |
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @return string |
115
|
|
|
*/ |
116
|
|
|
public function getApiPassword() |
117
|
|
|
{ |
118
|
|
|
return $this->apiPassword; |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* @param string $apiUrl |
123
|
|
|
*/ |
124
|
2 |
|
public function setApiUrl($apiUrl) |
125
|
|
|
{ |
126
|
2 |
|
$this->apiUrl = $apiUrl; |
127
|
2 |
|
} |
128
|
|
|
|
129
|
|
|
/** |
130
|
|
|
* @return string |
131
|
|
|
*/ |
132
|
|
|
public function getApiUrl() |
133
|
|
|
{ |
134
|
|
|
return $this->apiUrl; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
/** |
138
|
|
|
* @param string $apiUser |
139
|
|
|
*/ |
140
|
1 |
|
public function setApiUser($apiUser) |
141
|
|
|
{ |
142
|
1 |
|
$this->apiUser = $apiUser; |
143
|
1 |
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @return string |
147
|
|
|
*/ |
148
|
|
|
public function getApiUser() |
149
|
|
|
{ |
150
|
|
|
return $this->apiUser; |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* @param int $id |
155
|
|
|
*/ |
156
|
|
|
public function setId($id) |
157
|
|
|
{ |
158
|
|
|
$this->id = $id; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
/** |
162
|
|
|
* @return int |
163
|
|
|
*/ |
164
|
|
|
public function getId() |
165
|
|
|
{ |
166
|
|
|
return $this->id; |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
/** |
170
|
|
|
* @param string $mode |
171
|
|
|
* @throws BuildException |
172
|
|
|
*/ |
173
|
1 |
|
public function setMode($mode) |
174
|
|
|
{ |
175
|
1 |
|
if (false === isset($this->modeMap[$mode])) { |
176
|
|
|
throw new BuildException( |
177
|
|
|
'Mode is invalid (' . $mode . ', should be one of ' . implode( |
178
|
|
|
',', |
179
|
|
|
array_keys($this->modeMap) |
180
|
|
|
) . ')' |
181
|
|
|
); |
182
|
|
|
} |
183
|
1 |
|
$this->mode = $mode; |
184
|
1 |
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @return string |
188
|
|
|
*/ |
189
|
|
|
public function getMode() |
190
|
|
|
{ |
191
|
|
|
return $this->mode; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* @param string $title |
196
|
|
|
*/ |
197
|
1 |
|
public function setTitle($title) |
198
|
|
|
{ |
199
|
1 |
|
$this->title = $title; |
200
|
1 |
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @return string |
204
|
|
|
*/ |
205
|
|
|
public function getTitle() |
206
|
|
|
{ |
207
|
|
|
return $this->title; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* @param string $content |
212
|
|
|
*/ |
213
|
1 |
|
public function setContent($content) |
214
|
|
|
{ |
215
|
1 |
|
$this->content = $content; |
216
|
1 |
|
} |
217
|
|
|
|
218
|
|
|
/** |
219
|
|
|
* @return string |
220
|
|
|
*/ |
221
|
|
|
public function getContent() |
222
|
|
|
{ |
223
|
|
|
return $this->content; |
224
|
|
|
} |
225
|
|
|
|
226
|
|
|
/** |
227
|
|
|
* Prepare CURL object |
228
|
|
|
* |
229
|
|
|
* @throws BuildException |
230
|
|
|
*/ |
231
|
|
|
public function init() |
232
|
|
|
{ |
233
|
|
|
$this->cookiesFile = tempnam(FileUtils::getTempDir(), 'WikiPublish.' . uniqid('', true) . '.cookies'); |
234
|
|
|
|
235
|
|
|
$this->curl = curl_init(); |
|
|
|
|
236
|
|
|
if (false === is_resource($this->curl)) { |
237
|
|
|
throw new BuildException('Curl init failed (' . $this->apiUrl . ')'); |
238
|
|
|
} |
239
|
|
|
|
240
|
|
|
curl_setopt($this->curl, CURLOPT_RETURNTRANSFER, true); |
241
|
|
|
curl_setopt($this->curl, CURLOPT_COOKIEJAR, $this->cookiesFile); |
242
|
|
|
curl_setopt($this->curl, CURLOPT_COOKIEFILE, $this->cookiesFile); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* The main entry point method |
247
|
|
|
*/ |
248
|
2 |
|
public function main() |
249
|
|
|
{ |
250
|
2 |
|
$this->validateAttributes(); |
251
|
1 |
|
$this->callApiLogin(); |
252
|
1 |
|
$this->callApiEdit(); |
253
|
1 |
|
} |
254
|
|
|
|
255
|
|
|
/** |
256
|
|
|
* Close curl connection and clean up |
257
|
|
|
*/ |
258
|
2 |
|
public function __destruct() |
259
|
|
|
{ |
260
|
2 |
|
if (null !== $this->curl && is_resource($this->curl)) { |
261
|
|
|
curl_close($this->curl); |
262
|
|
|
} |
263
|
2 |
|
if (null !== $this->cookiesFile && file_exists($this->cookiesFile)) { |
264
|
|
|
unlink($this->cookiesFile); |
265
|
|
|
} |
266
|
2 |
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Validates attributes coming in from XML |
270
|
|
|
* |
271
|
|
|
* @throws BuildException |
272
|
|
|
*/ |
273
|
2 |
|
private function validateAttributes() |
274
|
|
|
{ |
275
|
2 |
|
if (null === $this->apiUrl) { |
276
|
1 |
|
throw new BuildException('Wiki apiUrl is required'); |
277
|
|
|
} |
278
|
|
|
|
279
|
2 |
|
if (null === $this->id && null === $this->title) { |
280
|
1 |
|
throw new BuildException('Wiki page id or title is required'); |
281
|
|
|
} |
282
|
1 |
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Call Wiki webapi login action |
286
|
|
|
* |
287
|
|
|
* @param string|null $token |
288
|
|
|
* |
289
|
|
|
* @throws BuildException |
290
|
|
|
*/ |
291
|
1 |
|
private function callApiLogin($token = null) |
292
|
|
|
{ |
293
|
1 |
|
$postData = ['lgname' => $this->apiUser, 'lgpassword' => $this->apiPassword]; |
294
|
1 |
|
if (null !== $token) { |
295
|
1 |
|
$postData['lgtoken'] = $token; |
296
|
|
|
} |
297
|
|
|
|
298
|
1 |
|
$result = $this->callApi('action=login', $postData); |
299
|
|
|
try { |
300
|
1 |
|
$this->checkApiResponseResult('login', $result); |
301
|
1 |
|
} catch (BuildException $e) { |
302
|
1 |
|
if (null !== $token) { |
303
|
|
|
throw $e; |
304
|
|
|
} |
305
|
|
|
// if token is required then call login again with token |
306
|
1 |
|
$this->checkApiResponseResult('login', $result, 'NeedToken'); |
307
|
1 |
|
if (isset($result['login']) && isset($result['login']['token'])) { |
308
|
1 |
|
$this->callApiLogin($result['login']['token']); |
309
|
|
|
} else { |
310
|
|
|
throw $e; |
311
|
|
|
} |
312
|
|
|
} |
313
|
1 |
|
} |
314
|
|
|
|
315
|
|
|
/** |
316
|
|
|
* Call Wiki webapi edit action |
317
|
|
|
*/ |
318
|
1 |
|
private function callApiEdit() |
319
|
|
|
{ |
320
|
1 |
|
$this->callApiTokens(); |
321
|
1 |
|
$result = $this->callApi('action=edit&token=' . urlencode($this->apiEditToken), $this->getApiEditData()); |
322
|
1 |
|
$this->checkApiResponseResult('edit', $result); |
323
|
1 |
|
} |
324
|
|
|
|
325
|
|
|
/** |
326
|
|
|
* Return prepared data for Wiki webapi edit action |
327
|
|
|
* |
328
|
|
|
* @return array |
329
|
|
|
*/ |
330
|
1 |
|
private function getApiEditData() |
331
|
|
|
{ |
332
|
|
|
$result = [ |
333
|
1 |
|
'minor' => '', |
334
|
|
|
]; |
335
|
1 |
|
if (null !== $this->title) { |
336
|
1 |
|
$result['title'] = $this->title; |
337
|
|
|
} |
338
|
1 |
|
if (null !== $this->id) { |
339
|
|
|
$result['pageid'] = $this->id; |
340
|
|
|
} |
341
|
1 |
|
$result[$this->modeMap[$this->mode]] = $this->content; |
342
|
|
|
|
343
|
1 |
|
return $result; |
344
|
|
|
} |
345
|
|
|
|
346
|
|
|
/** |
347
|
|
|
* Call Wiki webapi tokens action |
348
|
|
|
* |
349
|
|
|
* @throws BuildException |
350
|
|
|
*/ |
351
|
1 |
|
private function callApiTokens() |
352
|
|
|
{ |
353
|
1 |
|
$result = $this->callApi('action=tokens&type=edit'); |
354
|
1 |
|
if (false == isset($result['tokens']) || false == isset($result['tokens']['edittoken'])) { |
|
|
|
|
355
|
|
|
throw new BuildException('Wiki token not found'); |
356
|
|
|
} |
357
|
|
|
|
358
|
1 |
|
$this->apiEditToken = $result['tokens']['edittoken']; |
359
|
1 |
|
} |
360
|
|
|
|
361
|
|
|
/** |
362
|
|
|
* Call Wiki webapi |
363
|
|
|
* |
364
|
|
|
* @param string $queryString |
365
|
|
|
* @param array|null $postData |
366
|
|
|
* |
367
|
|
|
* @return array |
368
|
|
|
* @throws BuildException |
369
|
|
|
*/ |
370
|
|
|
protected function callApi($queryString, $postData = null) |
371
|
|
|
{ |
372
|
|
|
$this->setPostData($postData); |
373
|
|
|
|
374
|
|
|
$url = $this->apiUrl . '?' . $queryString . '&format=php'; |
375
|
|
|
|
376
|
|
|
curl_setopt($this->curl, CURLOPT_URL, $url); |
377
|
|
|
|
378
|
|
|
$response = curl_exec($this->curl); |
379
|
|
|
$responseCode = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
380
|
|
|
|
381
|
|
|
if (200 !== $responseCode) { |
382
|
|
|
throw new BuildException('Wiki webapi call failed (http response ' . $responseCode . ')'); |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
$result = @unserialize($response); |
|
|
|
|
386
|
|
|
if (false === $result) { |
387
|
|
|
throw new BuildException('Couldn\'t unserialize Wiki webapi response'); |
388
|
|
|
} |
389
|
|
|
|
390
|
|
|
return $result; |
391
|
|
|
} |
392
|
|
|
|
393
|
|
|
/** |
394
|
|
|
* Set POST data for curl call |
395
|
|
|
* |
396
|
|
|
* @param array|null $data |
397
|
|
|
*/ |
398
|
|
|
private function setPostData($data = null) |
399
|
|
|
{ |
400
|
|
|
if (null === $data) { |
401
|
|
|
curl_setopt($this->curl, CURLOPT_POST, false); |
402
|
|
|
|
403
|
|
|
return; |
404
|
|
|
} |
405
|
|
|
$postData = ''; |
406
|
|
|
foreach ($data as $key => $value) { |
407
|
|
|
$postData .= urlencode($key) . '=' . urlencode($value) . '&'; |
408
|
|
|
} |
409
|
|
|
if ($postData != '') { |
410
|
|
|
curl_setopt($this->curl, CURLOPT_POST, true); |
411
|
|
|
curl_setopt($this->curl, CURLOPT_POSTFIELDS, substr($postData, 0, -1)); |
412
|
|
|
} |
413
|
|
|
} |
414
|
|
|
|
415
|
|
|
/** |
416
|
|
|
* Validate Wiki webapi response |
417
|
|
|
* |
418
|
|
|
* @param string $action |
419
|
|
|
* @param array $response |
420
|
|
|
* @param string $expect |
421
|
|
|
* |
422
|
|
|
* @throws BuildException |
423
|
|
|
*/ |
424
|
1 |
|
private function checkApiResponseResult($action, $response, $expect = 'Success') |
425
|
|
|
{ |
426
|
1 |
|
if (isset($response['error'])) { |
427
|
|
|
throw new BuildException( |
428
|
|
|
'Wiki response error (action: ' . $action . ', error code: ' . $response['error']['code'] . ')' |
429
|
|
|
); |
430
|
|
|
} |
431
|
1 |
|
if (false == isset($response[$action]) || false == isset($response[$action]['result'])) { |
|
|
|
|
432
|
|
|
throw new BuildException('Wiki response result not found (action: ' . $action . ')'); |
433
|
|
|
} |
434
|
1 |
|
if ($response[$action]['result'] !== $expect) { |
435
|
1 |
|
throw new BuildException( |
436
|
1 |
|
'Unexpected Wiki response result ' . $response[$action]['result'] . ' (expected: ' . $expect . ')' |
437
|
|
|
); |
438
|
|
|
} |
439
|
1 |
|
} |
440
|
|
|
} |
441
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.