Completed
Push — master ( 58be28...a7fd72 )
by KwangSeob
02:13
created

JiraClient::getConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
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
     * Constructor.
59
     *
60
     * @param ConfigurationInterface $configuration
61
     * @param Logger                 $logger
62
     * @param string                 $path
63
     *
64
     * @throws JiraException
65
     * @throws \Exception
66
     */
67
    public function __construct(ConfigurationInterface $configuration = null, Logger $logger = null, $path = './')
68
    {
69
        if ($configuration === null) {
70
            if (!file_exists($path.'.env')) {
71
                // If calling the getcwd() on laravel it will returning the 'public' directory.
72
                $path = '../';
73
            }
74
            $this->configuration = new DotEnvConfiguration($path);
75
        } else {
76
            $this->configuration = $configuration;
77
        }
78
79
        $this->json_mapper = new \JsonMapper();
80
81
        // Fix "\JiraRestApi\JsonMapperHelper::class" syntax error, unexpected 'class' (T_CLASS), expecting identifier (T_STRING) or variable (T_VARIABLE) or '{' or '$'
82
        $this->json_mapper->undefinedPropertyHandler = [new \JiraRestApi\JsonMapperHelper(), 'setUndefinedProperty'];
83
84
        // Properties that are annotated with `@var \DateTimeInterface` should result in \DateTime objects being created.
85
        $this->json_mapper->classMap['\\'.\DateTimeInterface::class] = \DateTime::class;
86
87
        // create logger
88
        if ($logger) {
89
            $this->log = $logger;
90
        } else {
91
            $this->log = new Logger('JiraClient');
92
            $this->log->pushHandler(new StreamHandler(
93
                $this->configuration->getJiraLogFile(),
94
                $this->convertLogLevel($this->configuration->getJiraLogLevel())
95
            ));
96
        }
97
98
        $this->http_response = 200;
99
    }
100
101
    /**
102
     * Convert log level.
103
     *
104
     * @param $log_level
105
     *
106
     * @return int
107
     */
108
    private function convertLogLevel($log_level)
109
    {
110
        $log_level = strtoupper($log_level);
111
112
        switch ($log_level) {
113
            case 'EMERGENCY':
114
                return Logger::EMERGENCY;
115
            case 'ALERT':
116
                return Logger::ALERT;
117
            case 'CRITICAL':
118
                return Logger::CRITICAL;
119
            case 'ERROR':
120
                return Logger::ERROR;
121
            case 'WARNING':
122
                return Logger::WARNING;
123
            case 'NOTICE':
124
                return Logger::NOTICE;
125
            case 'DEBUG':
126
                return Logger::DEBUG;
127
            case 'INFO':
128
                return Logger::INFO;
129
            default:
130
                return Logger::WARNING;
131
        }
132
    }
133
134
    /**
135
     * Serilize only not null field.
136
     *
137
     * @param array $haystack
138
     *
139
     * @return array
140
     */
141
    protected function filterNullVariable($haystack)
142
    {
143
        foreach ($haystack as $key => $value) {
144
            if (is_array($value)) {
145
                $haystack[$key] = $this->filterNullVariable($haystack[$key]);
146
            } elseif (is_object($value)) {
147
                $haystack[$key] = $this->filterNullVariable(get_class_vars(get_class($value)));
148
            }
149
150
            if (is_null($haystack[$key]) || empty($haystack[$key])) {
151
                unset($haystack[$key]);
152
            }
153
        }
154
155
        return $haystack;
156
    }
157
158
    /**
159
     * Execute REST request.
160
     *
161
     * @param string $context        Rest API context (ex.:issue, search, etc..)
162
     * @param string $post_data
163
     * @param string $custom_request [PUT|DELETE]
164
     * @param string $cookieFile     cookie file
165
     *
166
     * @throws JiraException
167
     *
168
     * @return string
169
     */
170
    public function exec($context, $post_data = null, $custom_request = null, $cookieFile = null)
171
    {
172
        $url = $this->createUrlByContext($context);
173
174
        if (is_string($post_data)){
175
            $this->log->addInfo("Curl $custom_request: $url JsonData=".$post_data);
176
        } elseif (is_array($post_data)) {
0 ignored issues
show
introduced by
The condition is_array($post_data) is always false.
Loading history...
177
            $this->log->addInfo("Curl $custom_request: $url JsonData=" . json_encode($post_data, JSON_UNESCAPED_UNICODE));
178
        }
179
180
        $ch = curl_init();
181
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
182
        curl_setopt($ch, CURLOPT_URL, $url);
183
184
        // post_data
185
        if (!is_null($post_data)) {
186
            // PUT REQUEST
187
            if (!is_null($custom_request) && $custom_request == 'PUT') {
188
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
189
                curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
190
            }
191
            if (!is_null($custom_request) && $custom_request == 'DELETE') {
192
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
193
            } else {
194
                curl_setopt($ch, CURLOPT_POST, true);
195
                curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
196
            }
197
        } else {
198
            if (!is_null($custom_request) && $custom_request == 'DELETE') {
199
                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
200
            }
201
        }
202
203
        $this->authorization($ch, $cookieFile);
204
205
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
206
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
207
        curl_setopt($ch, CURLOPT_USERAGENT, $this->getConfiguration()->getCurlOptUserAgent());
208
209
        // curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set
210
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
211
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
212
        }
213
214
        curl_setopt($ch, CURLOPT_ENCODING, '');
215
        curl_setopt($ch, CURLOPT_HTTPHEADER,
216
            ['Accept: */*', 'Content-Type: application/json', 'X-Atlassian-Token: no-check']);
217
218
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
219
220
        // Add proxy settings to the curl.
221
        if ($this->getConfiguration()->getProxyServer()) {
222
            curl_setopt($ch, CURLOPT_PROXY, $this->getConfiguration()->getProxyServer());
223
            curl_setopt($ch, CURLOPT_PROXYPORT, $this->getConfiguration()->getProxyPort());
224
225
            $username = $this->getConfiguration()->getProxyUser();
226
            $password = $this->getConfiguration()->getProxyPassword();
227
            curl_setopt($ch, CURLOPT_PROXYUSERPWD, "$username:$password");
228
        }
229
230
        $this->log->addDebug('Curl exec='.$url);
231
        $response = curl_exec($ch);
232
233
        // if request failed or have no result.
234
        if (!$response) {
235
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
236
            $body = curl_error($ch);
237
            curl_close($ch);
238
239
            /*
240
             * 201: The request has been fulfilled, resulting in the creation of a new resource.
241
             * 204: The server successfully processed the request, but is not returning any content.
242
             */
243
            if ($this->http_response === 204 || $this->http_response === 201 || $this->http_response === 200) {
244
                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...
245
            }
246
247
            // HostNotFound, No route to Host, etc Network error
248
            $msg = sprintf('CURL Error: http response=%d, %s', $this->http_response, $body);
249
250
            $this->log->addError($msg);
251
252
            throw new JiraException($msg);
253
        } else {
254
            // if request was ok, parsing http response code.
255
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
256
257
            curl_close($ch);
258
259
            // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
260
            if ($this->http_response != 200 && $this->http_response != 201) {
261
                throw new JiraException('CURL HTTP Request Failed: Status Code : '
262
                    .$this->http_response.', URL:'.$url
263
                    ."\nError Message : ".$response, $this->http_response);
264
            }
265
        }
266
267
        return $response;
268
    }
269
270
    /**
271
     * Create upload handle.
272
     *
273
     * @param string $url         Request URL
274
     * @param string $upload_file Filename
275
     *
276
     * @return resource
277
     */
278
    private function createUploadHandle($url, $upload_file)
279
    {
280
        $ch = curl_init();
281
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
282
        curl_setopt($ch, CURLOPT_URL, $url);
283
284
        // send file
285
        curl_setopt($ch, CURLOPT_POST, true);
286
287
        if (PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 5) {
288
            $attachments = realpath($upload_file);
289
            $filename = basename($upload_file);
290
291
            curl_setopt($ch, CURLOPT_POSTFIELDS,
292
                ['file' => '@'.$attachments.';filename='.$filename]);
293
294
            $this->log->addDebug('using legacy file upload');
295
        } else {
296
            // CURLFile require PHP > 5.5
297
            $attachments = new \CURLFile(realpath($upload_file));
298
            $attachments->setPostFilename(basename($upload_file));
299
300
            curl_setopt($ch, CURLOPT_POSTFIELDS,
301
                ['file' => $attachments]);
302
303
            $this->log->addDebug('using CURLFile='.var_export($attachments, true));
304
        }
305
306
        $this->authorization($ch);
307
308
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
309
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
310
311
        // curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); cannot be activated when an open_basedir is set
312
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
313
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
314
        }
315
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
316
            'Accept: */*',
317
            'Content-Type: multipart/form-data',
318
            'X-Atlassian-Token: nocheck',
319
        ]);
320
321
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
322
323
        $this->log->addDebug('Curl exec='.$url);
324
325
        return $ch;
326
    }
327
328
    /**
329
     * File upload.
330
     *
331
     * @param string $context       url context
332
     * @param array  $filePathArray upload file path.
333
     *
334
     * @throws JiraException
335
     *
336
     * @return array
337
     */
338
    public function upload($context, $filePathArray)
339
    {
340
        $url = $this->createUrlByContext($context);
341
342
        // return value
343
        $result_code = 200;
344
345
        $chArr = [];
346
        $results = [];
347
        $mh = curl_multi_init();
348
349
        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...
350
            $file = $filePathArray[$idx];
351
            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...
352
                $body = "File $file not found";
353
                $result_code = -1;
354
                $this->closeCURLHandle($chArr, $mh, $body, $result_code);
355
356
                return $results;
357
            }
358
            $chArr[$idx] = $this->createUploadHandle($url, $filePathArray[$idx]);
359
360
            curl_multi_add_handle($mh, $chArr[$idx]);
361
        }
362
363
        $running = null;
364
        do {
365
            curl_multi_exec($mh, $running);
366
        } while ($running > 0);
367
368
        // Get content and remove handles.
369
        $body = '';
370
        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...
371
            $ch = $chArr[$idx];
372
373
            $results[$idx] = curl_multi_getcontent($ch);
374
375
            // if request failed.
376
            if (!$results[$idx]) {
377
                $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
378
                $body = curl_error($ch);
379
380
                //The server successfully processed the request, but is not returning any content.
381
                if ($this->http_response == 204) {
382
                    continue;
383
                }
384
385
                // HostNotFound, No route to Host, etc Network error
386
                $result_code = -1;
387
                $body = 'CURL Error: = '.$body;
388
                $this->log->addError($body);
389
            } else {
390
                // if request was ok, parsing http response code.
391
                $result_code = $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
392
393
                // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
394
                if ($this->http_response != 200 && $this->http_response != 201) {
395
                    $body = 'CURL HTTP Request Failed: Status Code : '
396
                        .$this->http_response.', URL:'.$url;
397
398
                    $this->log->addError($body);
399
                }
400
            }
401
        }
402
403
        $this->closeCURLHandle($chArr, $mh, $body, $result_code);
404
405
        return $results;
406
    }
407
408
    /**
409
     * @param array $chArr
410
     * @param $mh
411
     * @param $body
412
     * @param $result_code
413
     *
414
     * @throws \JiraRestApi\JiraException
415
     */
416
    protected function closeCURLHandle(array $chArr, $mh, $body, $result_code)
417
    {
418
        foreach ($chArr as $ch) {
419
            $this->log->addDebug('CURL Close handle..');
420
            curl_multi_remove_handle($mh, $ch);
421
            curl_close($ch);
422
        }
423
        $this->log->addDebug('CURL Multi Close handle..');
424
        curl_multi_close($mh);
425
        if ($result_code != 200) {
426
            // @TODO $body might have not been defined
427
            throw new JiraException('CURL Error: = '.$body, $result_code);
428
        }
429
    }
430
431
    /**
432
     * Get URL by context.
433
     *
434
     * @param string $context
435
     *
436
     * @return string
437
     */
438
    protected function createUrlByContext($context)
439
    {
440
        $host = $this->getConfiguration()->getJiraHost();
441
442
        return $host.$this->api_uri.'/'.preg_replace('/\//', '', $context, 1);
443
    }
444
445
    /**
446
     * Add authorize to curl request.
447
     *
448
     * @param resource $ch
449
     */
450
    protected function authorization($ch, $cookieFile = null)
451
    {
452
        // use cookie
453
        if ($this->getConfiguration()->isCookieAuthorizationEnabled()) {
454
            if ($cookieFile === null) {
455
                $cookieFile = $this->getConfiguration()->getCookieFile();
456
            }
457
458
            curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile);
459
            curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile);
460
461
            $this->log->addDebug('Using cookie..');
462
        }
463
464
        // if cookie file not exist, using id/pwd login
465
        if (!file_exists($cookieFile)) {
466
            $username = $this->getConfiguration()->getJiraUser();
467
            $password = $this->getConfiguration()->getJiraPassword();
468
            curl_setopt($ch, CURLOPT_USERPWD, "$username:$password");
469
        }
470
    }
471
472
    /**
473
     * Jira Rest API Configuration.
474
     *
475
     * @return ConfigurationInterface
476
     */
477
    public function getConfiguration()
478
    {
479
        return $this->configuration;
480
    }
481
482
    /**
483
     * Set a custom Jira API URI for the request.
484
     *
485
     * @param string $api_uri
486
     */
487
    public function setAPIUri($api_uri)
488
    {
489
        $this->api_uri = $api_uri;
490
    }
491
492
    /**
493
     * convert to query array to http query parameter.
494
     *
495
     * @param $paramArray
496
     *
497
     * @return string
498
     */
499
    public function toHttpQueryParameter($paramArray)
500
    {
501
        $queryParam = '?';
502
503
        foreach ($paramArray as $key => $value) {
504
            $v = null;
505
506
            // some param field(Ex: expand) type is array.
507
            if (is_array($value)) {
508
                $v = implode(',', $value);
509
            } else {
510
                $v = $value;
511
            }
512
513
            $queryParam .= $key.'='.$v.'&';
514
        }
515
516
        return $queryParam;
517
    }
518
519
    /**
520
     * download and save into outDir.
521
     *
522
     * @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...
523
     * @param $outDir save dir
524
     * @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...
525
     * @param $cookieFile cookie filename
0 ignored issues
show
Bug introduced by
The type JiraRestApi\cookie 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...
526
     *
527
     * @throws JiraException
528
     *
529
     * @return bool|mixed
530
     */
531
    public function download($url, $outDir, $file, $cookieFile = null)
532
    {
533
        $file = fopen($outDir.DIRECTORY_SEPARATOR.$file, 'w');
534
535
        $ch = curl_init();
536
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
537
        curl_setopt($ch, CURLOPT_URL, $url);
538
539
        // output to file handle
540
        curl_setopt($ch, CURLOPT_FILE, $file);
541
542
        $this->authorization($ch, $cookieFile);
543
544
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $this->getConfiguration()->isCurlOptSslVerifyHost());
545
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, $this->getConfiguration()->isCurlOptSslVerifyPeer());
546
547
        // curl_setopt(): CURLOPT_FOLLOWLOCATION cannot be activated when an open_basedir is set
548
        if (!function_exists('ini_get') || !ini_get('open_basedir')) {
549
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
550
        }
551
552
        curl_setopt($ch, CURLOPT_HTTPHEADER,
553
            ['Accept: */*', 'Content-Type: application/json', 'X-Atlassian-Token: no-check']);
554
555
        curl_setopt($ch, CURLOPT_VERBOSE, $this->getConfiguration()->isCurlOptVerbose());
556
557
        $this->log->addDebug('Curl exec='.$url);
558
        $response = curl_exec($ch);
559
560
        // if request failed.
561
        if (!$response) {
562
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
563
            $body = curl_error($ch);
564
            curl_close($ch);
565
            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

565
            fclose(/** @scrutinizer ignore-type */ $file);
Loading history...
566
567
            /*
568
             * 201: The request has been fulfilled, resulting in the creation of a new resource.
569
             * 204: The server successfully processed the request, but is not returning any content.
570
             */
571
            if ($this->http_response === 204 || $this->http_response === 201) {
572
                return true;
573
            }
574
575
            // HostNotFound, No route to Host, etc Network error
576
            $msg = sprintf('CURL Error: http response=%d, %s', $this->http_response, $body);
577
578
            $this->log->addError($msg);
579
580
            throw new JiraException($msg);
581
        } else {
582
            // if request was ok, parsing http response code.
583
            $this->http_response = curl_getinfo($ch, CURLINFO_HTTP_CODE);
584
585
            curl_close($ch);
586
            fclose($file);
587
588
            // don't check 301, 302 because setting CURLOPT_FOLLOWLOCATION
589
            if ($this->http_response != 200 && $this->http_response != 201) {
590
                throw new JiraException('CURL HTTP Request Failed: Status Code : '
591
                    .$this->http_response.', URL:'.$url
592
                    ."\nError Message : ".$response, $this->http_response);
593
            }
594
        }
595
596
        return $response;
597
    }
598
599
    /**
600
     * setting cookie file path.
601
     *
602
     * @param $cookieFile
603
     *
604
     * @return $this
605
     */
606
    public function setCookieFile($cookieFile)
607
    {
608
        $this->cookieFile = $cookieFile;
0 ignored issues
show
Bug Best Practice introduced by
The property cookieFile does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
609
610
        return $this;
611
    }
612
}
613