Completed
Push — master ( 653267...7d11aa )
by KwangSeob
03:10
created

JiraClient::download()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 66
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 34
nc 8
nop 3
dl 0
loc 66
rs 6.7081
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace JiraRestApi;
4
5
use JiraRestApi\Configuration\ConfigurationInterface;
6
use JiraRestApi\Configuration\DotEnvConfiguration;
7
use Monolog\Handler\StreamHandler;
8
use Monolog\Logger as Logger;
9
10
/**
11
 * Interact jira server with REST API.
12
 */
13
class JiraClient
14
{
15
    /**
16
     * Json Mapper.
17
     *
18
     * @var \JsonMapper
19
     */
20
    protected $json_mapper;
21
22
    /**
23
     * HTTP response code.
24
     *
25
     * @var string
26
     */
27
    protected $http_response;
28
29
    /**
30
     * JIRA REST API URI.
31
     *
32
     * @var string
33
     */
34
    private $api_uri = '/rest/api/2';
35
36
    /**
37
     * CURL instance.
38
     *
39
     * @var resource
40
     */
41
    protected $curl;
42
43
    /**
44
     * Monolog instance.
45
     *
46
     * @var \Monolog\Logger
47
     */
48
    protected $log;
49
50
    /**
51
     * Jira Rest API Configuration.
52
     *
53
     * @var ConfigurationInterface
54
     */
55
    protected $configuration;
56
57
    /**
58
     * cookie file name.
59
     *
60
     * @var string
61
     */
62
    protected $cookie = 'jira-cookies.txt';
63
64
    /**
65
     * Constructor.
66
     *
67
     * @param ConfigurationInterface $configuration
68
     * @param Logger                 $logger
69
     * @param string                 $path
70
     *
71
     * @throws JiraException
72
     * @throws \Exception
73
     */
74
    public function __construct(ConfigurationInterface $configuration = null, Logger $logger = null, $path = './')
75
    {
76
        if ($configuration === null) {
77
            if (!file_exists($path.'.env')) {
78
                // If calling the getcwd() on laravel it will returning the 'public' directory.
79
                $path = '../';
80
            }
81
            $configuration = new DotEnvConfiguration($path);
82
        }
83
84
        $this->configuration = $configuration;
85
        $this->json_mapper = new \JsonMapper();
86
87
        // Fix "\JiraRestApi\JsonMapperHelper::class" syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$'
88
        $this->json_mapper->undefinedPropertyHandler = [new \JiraRestApi\JsonMapperHelper(), 'setUndefinedProperty'];
0 ignored issues
show
Documentation Bug introduced by
It seems like array(new JiraRestApi\Js...'setUndefinedProperty') of type array<integer,string|Jir...stApi\JsonMapperHelper> is incompatible with the declared type callable of property $undefinedPropertyHandler.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
89
90
        // create logger
91
        if ($logger) {
92
            $this->log = $logger;
93
        } else {
94
            $this->log = new Logger('JiraClient');
95
            $this->log->pushHandler(new StreamHandler(
96
                $configuration->getJiraLogFile(),
97
                $this->convertLogLevel($configuration->getJiraLogLevel())
98
            ));
99
        }
100
101
        $this->http_response = 200;
102
    }
103
104
    /**
105
     * Convert log level.
106
     *
107
     * @param $log_level
108
     *
109
     * @return int
110
     */
111
    private function convertLogLevel($log_level)
112
    {
113
        $log_level = strtoupper($log_level);
114
115
        switch ($log_level) {
116
            case 'EMERGENCY':
117
                return Logger::EMERGENCY;
118
            case 'ALERT':
119
                return Logger::ALERT;
120
            case 'CRITICAL':
121
                return Logger::CRITICAL;
122
            case 'ERROR':
123
                return Logger::ERROR;
124
            case 'WARNING':
125
                return Logger::WARNING;
126
            case 'NOTICE':
127
                return Logger::NOTICE;
128
            case 'DEBUG':
129
                return Logger::DEBUG;
130
            case 'INFO':
131
                return Logger::INFO;
132
            default:
133
                return Logger::WARNING;
134
        }
135
    }
136
137
    /**
138
     * Serilize only not null field.
139
     *
140
     * @param array $haystack
141
     *
142
     * @return array
143
     */
144
    protected function filterNullVariable($haystack)
145
    {
146
        foreach ($haystack as $key => $value) {
147
            if (is_array($value)) {
148
                $haystack[$key] = $this->filterNullVariable($haystack[$key]);
149
            } elseif (is_object($value)) {
150
                $haystack[$key] = $this->filterNullVariable(get_class_vars(get_class($value)));
151
            }
152
153
            if (is_null($haystack[$key]) || empty($haystack[$key])) {
154
                unset($haystack[$key]);
155
            }
156
        }
157
158
        return $haystack;
159
    }
160
161
    /**
162
     * Execute REST request.
163
     *
164
     * @param string $context        Rest API context (ex.:issue, search, etc..)
165
     * @param string $post_data
166
     * @param string $custom_request [PUT|DELETE]
167
     *
168
     * @throws JiraException
169
     *
170
     * @return string
171
     */
172
    public function exec($context, $post_data = null, $custom_request = null)
173
    {
174
        $url = $this->createUrlByContext($context);
175
176
        $this->log->addInfo("Curl $custom_request: $url JsonData=".$post_data);
177
178
        $ch = curl_init();
179
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
180
        curl_setopt($ch, CURLOPT_URL, $url);
181
182
        // post_data
183
        if (!is_null($post_data)) {
184
            // PUT REQUEST
185
            if (!is_null($custom_request) && $custom_request == 'PUT') {
186
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
187
                curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
188
            }
189
            if (!is_null($custom_request) && $custom_request == 'DELETE') {
190
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
191
            } else {
192
                curl_setopt($ch, CURLOPT_POST, true);
193
                curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
194
            }
195
        } else {
196
            if (!is_null($custom_request) && $custom_request == 'DELETE') {
197
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
198
            }
199
        }
200
201
        $this->authorization($ch);
202
203
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
204
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
205
206
        // curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set
207
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
208
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
209
        }
210
211
        curl_setopt($ch, CURLOPT_HTTPHEADER,
212
            ['Accept: */*', 'Content-Type: application/json', 'X-Atlassian-Token: no-check']);
213
214
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
215
216
        $this->log->addDebug('Curl exec='.$url);
217
        $response = curl_exec($ch);
218
219
        // if request failed.
220
        if (!$response) {
221
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
222
            $body = curl_error($ch);
223
            curl_close($ch);
224
225
            /*
226
             * 201: The request has been fulfilled, resulting in the creation of a new resource.
227
             * 204: The server successfully processed the request, but is not returning any content.
228
             */
229
            if ($this->http_response === 204 || $this->http_response === 201) {
230
                return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type string.
Loading history...
231
            }
232
233
            // HostNotFound, No route to Host, etc Network error
234
            $msg = sprintf('CURL Error: http response=%d, %s', $this->http_response, $body);
235
236
            $this->log->addError($msg);
237
238
            throw new JiraException($msg);
239
        } else {
240
            // if request was ok, parsing http response code.
241
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
242
243
            curl_close($ch);
244
245
            // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
246
            if ($this->http_response != 200 && $this->http_response != 201) {
247
                throw new JiraException('CURL HTTP Request Failed: Status Code : '
248
                    .$this->http_response.', URL:'.$url
249
                    ."\nError Message : ".$response, $this->http_response);
250
            }
251
        }
252
253
        return $response;
254
    }
255
256
    /**
257
     * Create upload handle.
258
     *
259
     * @param string $url         Request URL
260
     * @param string $upload_file Filename
261
     *
262
     * @return resource
263
     */
264
    private function createUploadHandle($url, $upload_file)
265
    {
266
        $ch = curl_init();
267
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
268
        curl_setopt($ch, CURLOPT_URL, $url);
269
270
        // send file
271
        curl_setopt($ch, CURLOPT_POST, true);
272
273
        if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 5) {
274
            $attachments = realpath($upload_file);
275
            $filename = basename($upload_file);
276
277
            curl_setopt($ch, CURLOPT_POSTFIELDS,
278
                ['file' => '@'.$attachments.';filename='.$filename]);
279
280
            $this->log->addDebug('using legacy file upload');
281
        } else {
282
            // CURLFile require PHP > 5.5
283
            $attachments = new \CURLFile(realpath($upload_file));
284
            $attachments->setPostFilename(basename($upload_file));
285
286
            curl_setopt($ch, CURLOPT_POSTFIELDS,
287
                ['file' => $attachments]);
288
289
            $this->log->addDebug('using CURLFile='.var_export($attachments, true));
290
        }
291
292
        $this->authorization($ch);
293
294
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
295
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
296
297
        // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); cannot be activated when an open_basedir is set
298
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
299
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
300
        }
301
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
302
            'Accept: */*',
303
            'Content-Type: multipart/form-data',
304
            'X-Atlassian-Token: nocheck',
305
        ]);
306
307
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
308
309
        $this->log->addDebug('Curl exec='.$url);
310
311
        return $ch;
312
    }
313
314
    /**
315
     * File upload.
316
     *
317
     * @param string $context       url context
318
     * @param array  $filePathArray upload file path.
319
     *
320
     * @throws JiraException
321
     *
322
     * @return array
323
     */
324
    public function upload($context, $filePathArray)
325
    {
326
        $url = $this->createUrlByContext($context);
327
328
        // return value
329
        $result_code = 200;
330
331
        $chArr = [];
332
        $results = [];
333
        $mh = curl_multi_init();
334
335
        for ($idx = 0; $idx < count($filePathArray); $idx++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
336
            $file = $filePathArray[$idx];
337
            if (file_exists($file) == false) {
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...
338
                $body = "File $file not found";
339
                $result_code = -1;
340
                $this->closeCURLHandle($chArr, $mh, $body, $result_code);
341
342
                return $results;
343
            }
344
            $chArr[$idx] = $this->createUploadHandle($url, $filePathArray[$idx]);
345
346
            curl_multi_add_handle($mh, $chArr[$idx]);
347
        }
348
349
        $running = null;
350
        do {
351
            curl_multi_exec($mh, $running);
352
        } while ($running > 0);
353
354
        // Get content and remove handles.
355
        $body = '';
356
        for ($idx = 0; $idx < count($chArr); $idx++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
357
            $ch = $chArr[$idx];
358
359
            $results[$idx] = curl_multi_getcontent($ch);
360
361
            // if request failed.
362
            if (!$results[$idx]) {
363
                $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
364
                $body = curl_error($ch);
365
366
                //The server successfully processed the request, but is not returning any content.
367
                if ($this->http_response == 204) {
368
                    continue;
369
                }
370
371
                // HostNotFound, No route to Host, etc Network error
372
                $result_code = -1;
373
                $body = 'CURL Error: = '.$body;
374
                $this->log->addError($body);
375
            } else {
376
                // if request was ok, parsing http response code.
377
                $result_code = $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
378
379
                // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
380
                if ($this->http_response != 200 && $this->http_response != 201) {
381
                    $body = 'CURL HTTP Request Failed: Status Code : '
382
                        .$this->http_response.', URL:'.$url;
383
384
                    $this->log->addError($body);
385
                }
386
            }
387
        }
388
389
        $this->closeCURLHandle($chArr, $mh, $body, $result_code);
390
391
        return $results;
392
    }
393
394
    /**
395
     * @param array $chArr
396
     * @param $mh
397
     * @param $body
398
     * @param $result_code
399
     *
400
     * @throws \JiraRestApi\JiraException
401
     */
402
    protected function closeCURLHandle(array $chArr, $mh, $body, $result_code)
403
    {
404
        foreach ($chArr as $ch) {
405
            $this->log->addDebug('CURL Close handle..');
406
            curl_multi_remove_handle($mh, $ch);
407
            curl_close($ch);
408
        }
409
        $this->log->addDebug('CURL Multi Close handle..');
410
        curl_multi_close($mh);
411
        if ($result_code != 200) {
412
            // @TODO $body might have not been defined
413
            throw new JiraException('CURL Error: = '.$body, $result_code);
414
        }
415
    }
416
417
    /**
418
     * Get URL by context.
419
     *
420
     * @param string $context
421
     *
422
     * @return string
423
     */
424
    protected function createUrlByContext($context)
425
    {
426
        $host = $this->getConfiguration()->getJiraHost();
427
428
        return $host.$this->api_uri.'/'.preg_replace('/\//', '', $context, 1);
429
    }
430
431
    /**
432
     * Add authorize to curl request.
433
     *
434
     * @param resource $ch
435
     */
436
    protected function authorization($ch)
437
    {
438
        // use cookie
439
        if ($this->getConfiguration()->isCookieAuthorizationEnabled()) {
440
            curl_setopt($ch, CURLOPT_COOKIEJAR, $this->cookie);
441
            curl_setopt($ch, CURLOPT_COOKIEFILE, $this->cookie);
442
443
            $this->log->addDebug('Using cookie..');
444
        }
445
446
        // if cookie file not exist, using id/pwd login
447
        if (!file_exists($this->cookie)) {
448
            $username = $this->getConfiguration()->getJiraUser();
449
            $password = $this->getConfiguration()->getJiraPassword();
450
            curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
451
        }
452
    }
453
454
    /**
455
     * Jira Rest API Configuration.
456
     *
457
     * @return ConfigurationInterface
458
     */
459
    public function getConfiguration()
460
    {
461
        return $this->configuration;
462
    }
463
464
    /**
465
     * Set a custom Jira API URI for the request.
466
     *
467
     * @param string $api_uri
468
     */
469
    public function setAPIUri($api_uri)
470
    {
471
        $this->api_uri = $api_uri;
472
    }
473
474
    /**
475
     * convert to query array to http query parameter.
476
     *
477
     * @param $paramArray
478
     *
479
     * @return string
480
     */
481
    public function toHttpQueryParameter($paramArray)
482
    {
483
        $queryParam = '?';
484
485
        foreach ($paramArray as $key => $value) {
486
            $v = null;
487
488
            // some param field(Ex: expand) type is array.
489
            if (is_array($value)) {
490
                $v = implode(',', $value);
491
            } else {
492
                $v = $value;
493
            }
494
495
            $queryParam .= $key.'='.$v.'&';
496
        }
497
498
        return $queryParam;
499
    }
500
501
    /**
502
     * download and save into outDir
503
     *
504
     * @param $url full url
0 ignored issues
show
Bug introduced by
The type JiraRestApi\full was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
505
     * @param $outDir save dir
506
     * @param $file save filename
0 ignored issues
show
Bug introduced by
The type JiraRestApi\save was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
507
     * @return bool|mixed
508
     * @throws JiraException
509
     */
510
    public function download($url, $outDir, $file)
511
    {
512
        $file = fopen($outDir .DIRECTORY_SEPARATOR.$file, 'w');
513
514
        $ch = curl_init();
515
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
516
        curl_setopt($ch, CURLOPT_URL, $url);
517
518
        // output to file handle
519
        curl_setopt($ch, CURLOPT_FILE, $file);
520
521
        $this->authorization($ch);
522
523
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
524
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
525
526
        // curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set
527
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
528
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
529
        }
530
531
        curl_setopt($ch, CURLOPT_HTTPHEADER,
532
            ['Accept: */*', 'Content-Type: application/json', 'X-Atlassian-Token: no-check']);
533
534
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
535
536
        $this->log->addDebug('Curl exec='.$url);
537
        $response = curl_exec($ch);
538
539
        // if request failed.
540
        if (!$response) {
541
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
542
            $body = curl_error($ch);
543
            curl_close($ch);
544
            fclose($file);
0 ignored issues
show
Bug introduced by
It seems like $file can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

544
            fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
545
546
            /*
547
             * 201: The request has been fulfilled, resulting in the creation of a new resource.
548
             * 204: The server successfully processed the request, but is not returning any content.
549
             */
550
            if ($this->http_response === 204 || $this->http_response === 201) {
551
                return true;
552
            }
553
554
            // HostNotFound, No route to Host, etc Network error
555
            $msg = sprintf('CURL Error: http response=%d, %s', $this->http_response, $body);
556
557
            $this->log->addError($msg);
558
559
            throw new JiraException($msg);
560
        } else {
561
            // if request was ok, parsing http response code.
562
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
563
564
            curl_close($ch);
565
            fclose($file);
566
567
            // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
568
            if ($this->http_response != 200 && $this->http_response != 201) {
569
                throw new JiraException('CURL HTTP Request Failed: Status Code : '
570
                    .$this->http_response.', URL:'.$url
571
                    ."\nError Message : ".$response, $this->http_response);
572
            }
573
        }
574
575
        return $response;
576
    }
577
}
578