Passed
Push — main ( b7649a...398f47 )
by Michiel
06:32
created

WikiPublishTask::setContent()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 1
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();
0 ignored issues
show
Documentation Bug introduced by
It seems like curl_init() can also be of type CurlHandle. However, the property $curl is declared as type resource. Maybe add an additional type check?

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 the id property of an instance of the Account 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.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
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'])) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
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);
0 ignored issues
show
Bug introduced by
It seems like $response can also be of type true; however, parameter $data of unserialize() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

385
        $result = @unserialize(/** @scrutinizer ignore-type */ $response);
Loading history...
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'])) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
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