Passed
Push — master ( 855f4c...e5357b )
by KwangSeob
02:12
created

IssueService   F

Complexity

Total Complexity 79

Size/Duplication

Total Lines 1238
Duplicated Lines 0 %

Importance

Changes 44
Bugs 14 Features 8
Metric Value
eloc 361
dl 0
loc 1238
rs 2.08
c 44
b 14
f 8
wmc 79

44 Methods

Rating   Name   Duplication   Size   Complexity  
A getIssueFromJSON() 0 8 1
A addWorklog() 0 16 1
A getCreateMeta() 0 10 2
A getWatchers() 0 15 1
A changeAssignee() 0 13 1
A getAllPriorities() 0 11 1
A getTransition() 0 17 1
A timeTracking() 0 18 1
A removeWatcher() 0 9 2
A getIssueSecuritySchemes() 0 12 1
A notify() 0 20 4
A update() 0 18 1
A deleteIssue() 0 11 1
A createMultiple() 0 18 3
A getCustomFields() 0 12 1
A bulkInsert() 0 13 2
A findTransitonId() 0 18 3
A get() 0 16 4
A createOrUpdateRemoteIssueLink() 0 16 1
A search() 0 28 2
A removeWatcherByAccountId() 0 9 2
A addWatcher() 0 11 2
A removeRemoteIssueLink() 0 18 1
A getPriority() 0 12 1
A getEditMeta() 0 21 1
A updateComment() 0 19 3
A getAllIssueSecuritySchemes() 0 18 1
A getWorklog() 0 10 1
A getRemoteIssueLink() 0 13 1
A getComments() 0 13 1
A deleteComment() 0 9 1
A getComment() 0 13 1
A create() 0 14 1
A editWorklog() 0 16 1
A getWorklogById() 0 10 1
A getTimeTracking() 0 11 1
A findTransitonIdByUntranslatedName() 0 21 4
B addAttachments() 0 35 6
A addComment() 0 19 3
A transition() 0 23 4
A updateFixVersions() 0 24 3
A updateLabels() 0 24 3
A changeAssigneeByAccountId() 0 13 1
A deleteWorklog() 0 10 1

How to fix   Complexity   

Complex Class

Complex classes like IssueService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use IssueService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace JiraRestApi\Issue;
4
5
use JiraRestApi\JiraException;
6
use JiraRestApi\Project\ProjectService;
7
8
class IssueService extends \JiraRestApi\JiraClient
9
{
10
    private $uri = '/issue';
11
12
    /**
13
     * @param $json
14
     *
15
     * @throws \JsonMapper_Exception
16
     *
17
     * @return Issue|object
18
     */
19
    public function getIssueFromJSON($json)
20
    {
21
        $issue = $this->json_mapper->map(
22
            $json,
23
            new Issue()
24
        );
25
26
        return $issue;
27
    }
28
29
    /**
30
     *  get all project list.
31
     *
32
     * @param string|int $issueIdOrKey
33
     * @param array      $paramArray   Query Parameter key-value Array.
34
     * @param Issue      $issueObject
35
     *
36
     * @throws JiraException
37
     * @throws \JsonMapper_Exception
38
     *
39
     * @return Issue|object class
40
     */
41
    public function get($issueIdOrKey, $paramArray = [], $issueObject = null)
42
    {
43
        // for REST API V3
44
        if ($this->isRestApiV3()) {
45
            $issueObject = ($issueObject) ? $issueObject : new IssueV3();
46
        } else {
47
            $issueObject = ($issueObject) ? $issueObject : new Issue();
48
        }
49
50
        $ret = $this->exec($this->uri.'/'.$issueIdOrKey.$this->toHttpQueryParameter($paramArray), null);
51
52
        $this->log->info("Result=\n".$ret);
53
54
        return $issue = $this->json_mapper->map(
0 ignored issues
show
Unused Code introduced by
The assignment to $issue is dead and can be removed.
Loading history...
55
            json_decode($ret),
56
            $issueObject
57
        );
58
    }
59
60
    /**
61
     * create new issue.
62
     *
63
     * @param IssueField $issueField
64
     *
65
     * @throws JiraException
66
     * @throws \JsonMapper_Exception
67
     *
68
     * @return Issue|object created issue key
69
     */
70
    public function create($issueField)
71
    {
72
        $issue = new Issue();
73
74
        // serilize only not null field.
75
        $issue->fields = $issueField;
76
77
        $data = json_encode($issue);
78
79
        $this->log->info("Create Issue=\n".$data);
80
81
        $ret = $this->exec($this->uri, $data, 'POST');
82
83
        return $this->getIssueFromJSON(json_decode($ret));
84
    }
85
86
    /**
87
     * Create multiple issues using bulk insert.
88
     *
89
     * @param IssueField[] $issueFields Array of IssueField objects
90
     * @param int          $batchSize   Maximum number of issues to send in each request
91
     *
92
     * @throws JiraException
93
     * @throws \JsonMapper_Exception
94
     *
95
     * @return array Array of results, where each result represents one batch of insertions
96
     */
97
    public function createMultiple($issueFields, $batchSize = 50)
98
    {
99
        $issues = [];
100
101
        foreach ($issueFields as $issueField) {
102
            $issue = new Issue();
103
            $issue->fields = $issueField;
104
            $issues[] = $issue;
105
        }
106
107
        $batches = array_chunk($issues, $batchSize);
108
109
        $results = [];
110
        foreach ($batches as $batch) {
111
            $results = array_merge($results, $this->bulkInsert($batch));
112
        }
113
114
        return $results;
115
    }
116
117
    /**
118
     * Makes API call to bulk insert issues.
119
     *
120
     * @param Issue[] $issues Array of issue arrays that are sent to Jira one by one in single create
121
     *
122
     * @throws JiraException
123
     * @throws \JsonMapper_Exception
124
     *
125
     * @return Issue[] Result of API call to insert many issues
126
     */
127
    private function bulkInsert($issues)
128
    {
129
        $data = json_encode(['issueUpdates' => $issues]);
130
131
        $this->log->info("Create Issues=\n".$data);
132
        $results = $this->exec($this->uri.'/bulk', $data, 'POST');
133
134
        $issues = [];
135
        foreach (json_decode($results)->issues as $result) {
136
            $issues[] = $this->getIssueFromJSON($result);
137
        }
138
139
        return $issues;
140
    }
141
142
    /**
143
     * Add one or more file to an issue.
144
     *
145
     * @param string|int   $issueIdOrKey  Issue id or key
146
     * @param array|string $filePathArray attachment file path.
147
     *
148
     * @throws JiraException
149
     * @throws \JsonMapper_Exception
150
     *
151
     * @return Attachment[]
152
     */
153
    public function addAttachments($issueIdOrKey, $filePathArray)
154
    {
155
        if (is_array($filePathArray) == 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...
156
            $filePathArray = [$filePathArray];
157
        }
158
159
        $results = $this->upload($this->uri."/$issueIdOrKey/attachments", $filePathArray);
0 ignored issues
show
Bug introduced by
It seems like $filePathArray can also be of type string; however, parameter $filePathArray of JiraRestApi\JiraClient::upload() does only seem to accept array, 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

159
        $results = $this->upload($this->uri."/$issueIdOrKey/attachments", /** @scrutinizer ignore-type */ $filePathArray);
Loading history...
160
161
        $this->log->info('addAttachments result='.var_export($results, true));
162
163
        $attachArr = [];
164
        foreach ($results as $ret) {
165
            $ret = json_decode($ret);
166
            if (is_array($ret)) {
167
                $tmpArr = $this->json_mapper->mapArray(
168
                    $ret,
169
                    new \ArrayObject(),
170
                    '\JiraRestApi\Issue\Attachment'
171
                );
172
173
                foreach ($tmpArr as $t) {
174
                    array_push($attachArr, $t);
175
                }
176
            } elseif (is_object($ret)) {
177
                array_push(
178
                    $attachArr,
179
                    $this->json_mapper->map(
180
                        $ret,
181
                        new Attachment()
182
                    )
183
                );
184
            }
185
        }
186
187
        return $attachArr;
188
    }
189
190
    /**
191
     * update issue.
192
     *
193
     * @param string|int $issueIdOrKey Issue Key
194
     * @param IssueField $issueField   object of Issue class
195
     * @param array      $paramArray   Query Parameter key-value Array.
196
     *
197
     * @throws JiraException
198
     *
199
     * @return string created issue key
200
     */
201
    public function update($issueIdOrKey, $issueField, $paramArray = [])
202
    {
203
        $issue = new Issue();
204
205
        // serilize only not null field.
206
        $issue->fields = $issueField;
207
208
        //$issue = $this->filterNullVariable((array)$issue);
209
210
        $data = json_encode($issue);
211
212
        $this->log->info("Update Issue=\n".$data);
213
214
        $queryParam = '?'.http_build_query($paramArray);
215
216
        $ret = $this->exec($this->uri."/$issueIdOrKey".$queryParam, $data, 'PUT');
217
218
        return $ret;
219
    }
220
221
    /**
222
     * Adds a new comment to an issue.
223
     *
224
     * @param string|int $issueIdOrKey Issue id or key
225
     * @param Comment    $comment
226
     *
227
     * @throws JiraException
228
     * @throws \JsonMapper_Exception
229
     *
230
     * @return Comment|object Comment class
231
     */
232
    public function addComment($issueIdOrKey, $comment)
233
    {
234
        $this->log->info("addComment=\n");
235
236
        if (!($comment instanceof Comment) || empty($comment->body)) {
0 ignored issues
show
introduced by
$comment is always a sub-type of JiraRestApi\Issue\Comment.
Loading history...
237
            throw new JiraException('comment param must instance of Comment and have to body text.!');
238
        }
239
240
        $data = json_encode($comment);
241
242
        $ret = $this->exec($this->uri."/$issueIdOrKey/comment", $data);
243
244
        $this->log->debug('add comment result='.var_export($ret, true));
245
        $comment = $this->json_mapper->map(
246
            json_decode($ret),
247
            new Comment()
248
        );
249
250
        return $comment;
251
    }
252
253
    /**
254
     * Update a comment in issue.
255
     *
256
     * @param string|int $issueIdOrKey Issue id or key
257
     * @param string|int $id           Comment id
258
     * @param Comment    $comment
259
     *
260
     * @throws JiraException
261
     * @throws \JsonMapper_Exception
262
     *
263
     * @return Comment|object Comment class
264
     */
265
    public function updateComment($issueIdOrKey, $id, $comment)
266
    {
267
        $this->log->info("updateComment=\n");
268
269
        if (!($comment instanceof Comment) || empty($comment->body)) {
0 ignored issues
show
introduced by
$comment is always a sub-type of JiraRestApi\Issue\Comment.
Loading history...
270
            throw new JiraException('comment param must instance of Comment and have to body text.!');
271
        }
272
273
        $data = json_encode($comment);
274
275
        $ret = $this->exec($this->uri."/$issueIdOrKey/comment/$id", $data, 'PUT');
276
277
        $this->log->debug('update comment result='.var_export($ret, true));
278
        $comment = $this->json_mapper->map(
279
            json_decode($ret),
280
            new Comment()
281
        );
282
283
        return $comment;
284
    }
285
286
    /**
287
     * Get a comment on an issue.
288
     *
289
     * @param string|int $issueIdOrKey Issue id or key
290
     * @param string|int $id           Comment id
291
     *
292
     * @throws JiraException
293
     * @throws \JsonMapper_Exception
294
     *
295
     * @return Comment|object Comment class
296
     */
297
    public function getComment($issueIdOrKey, $id)
298
    {
299
        $this->log->info("getComment=\n");
300
301
        $ret = $this->exec($this->uri."/$issueIdOrKey/comment/$id");
302
303
        $this->log->debug('get comment result='.var_export($ret, true));
304
        $comment = $this->json_mapper->map(
305
            json_decode($ret),
306
            new Comment()
307
        );
308
309
        return $comment;
310
    }
311
312
    /**
313
     * Get all comments on an issue.
314
     *
315
     * @param string|int $issueIdOrKey Issue id or key
316
     * @param array      $paramArray   Query Parameter key-value Array.
317
     *
318
     * @throws JiraException
319
     * @throws \JsonMapper_Exception
320
     *
321
     * @return Comment|object Comment class
322
     */
323
    public function getComments($issueIdOrKey, $paramArray = [])
324
    {
325
        $this->log->info("getComments=\n");
326
327
        $ret = $this->exec($this->uri.'/'.$issueIdOrKey.'/comment'.$this->toHttpQueryParameter($paramArray), null);
328
329
        $this->log->debug('get comments result='.var_export($ret, true));
330
        $comment = $this->json_mapper->map(
331
            json_decode($ret),
332
            new Comment()
333
        );
334
335
        return $comment;
336
    }
337
338
    /**
339
     * Delete a comment on an issue.
340
     *
341
     * @param string|int $issueIdOrKey Issue id or key
342
     * @param string|int $id           Comment id
343
     *
344
     * @throws JiraException
345
     *
346
     * @return string|bool
347
     */
348
    public function deleteComment($issueIdOrKey, $id)
349
    {
350
        $this->log->info("deleteComment=\n");
351
352
        $ret = $this->exec($this->uri."/$issueIdOrKey/comment/$id", '', 'DELETE');
353
354
        $this->log->info('delete comment '.$issueIdOrKey.' '.$id.' result='.var_export($ret, true));
355
356
        return $ret;
357
    }
358
359
    /**
360
     * Change a issue assignee.
361
     *
362
     * @param string|int  $issueIdOrKey
363
     * @param string|null $assigneeName Assigns an issue to a user.
364
     *                                  If the assigneeName is "-1" automatic assignee is used.
365
     *                                  A null name will remove the assignee.
366
     *
367
     * @throws JiraException
368
     *
369
     * @return string|bool
370
     */
371
    public function changeAssignee($issueIdOrKey, $assigneeName)
372
    {
373
        $this->log->info("changeAssignee=\n");
374
375
        $ar = ['name' => $assigneeName];
376
377
        $data = json_encode($ar);
378
379
        $ret = $this->exec($this->uri."/$issueIdOrKey/assignee", $data, 'PUT');
380
381
        $this->log->info('change assignee of '.$issueIdOrKey.' to '.$assigneeName.' result='.var_export($ret, true));
382
383
        return $ret;
384
    }
385
386
    /**
387
     * Change a issue assignee for REST API V3.
388
     *
389
     * @param string|int  $issueIdOrKey
390
     * @param string|null $accountId    Assigns an issue to a user.
391
     *
392
     * @throws JiraException
393
     *
394
     * @return string
395
     */
396
    public function changeAssigneeByAccountId($issueIdOrKey, $accountId)
397
    {
398
        $this->log->info("changeAssigneeByAccountId=\n");
399
400
        $ar = ['accountId' => $accountId];
401
402
        $data = json_encode($ar);
403
404
        $ret = $this->exec($this->uri."/$issueIdOrKey/assignee", $data, 'PUT');
405
406
        $this->log->info('change assignee of '.$issueIdOrKey.' to '.$accountId.' result='.var_export($ret, true));
407
408
        return $ret;
409
    }
410
411
    /**
412
     * Delete a issue.
413
     *
414
     * @param string|int $issueIdOrKey Issue id or key
415
     * @param array      $paramArray   Query Parameter key-value Array.
416
     *
417
     * @throws JiraException
418
     *
419
     * @return string|bool
420
     */
421
    public function deleteIssue($issueIdOrKey, $paramArray = [])
422
    {
423
        $this->log->info("deleteIssue=\n");
424
425
        $queryParam = '?'.http_build_query($paramArray);
426
427
        $ret = $this->exec($this->uri."/$issueIdOrKey".$queryParam, '', 'DELETE');
428
429
        $this->log->info('delete issue '.$issueIdOrKey.' result='.var_export($ret, true));
430
431
        return $ret;
432
    }
433
434
    /**
435
     * Get a list of the transitions possible for this issue by the current user, along with fields that are required and their types.
436
     *
437
     * @param string|int $issueIdOrKey Issue id or key
438
     * @param array      $paramArray   Query Parameter key-value Array.
439
     *
440
     * @throws JiraException
441
     *
442
     * @return Transition[] array of Transition class
443
     */
444
    public function getTransition($issueIdOrKey, $paramArray = [])
445
    {
446
        $queryParam = '?'.http_build_query($paramArray);
447
448
        $ret = $this->exec($this->uri."/$issueIdOrKey/transitions".$queryParam);
449
450
        $this->log->debug('getTransitions result='.var_export($ret, true));
451
452
        $data = json_encode(json_decode($ret)->transitions);
453
454
        $transitions = $this->json_mapper->mapArray(
455
            json_decode($data),
456
            new \ArrayObject(),
457
            '\JiraRestApi\Issue\Transition'
458
        );
459
460
        return $transitions;
461
    }
462
463
    /**
464
     * find transition id by transition's to field name(aka 'Resolved').
465
     *
466
     * @param string|int $issueIdOrKey
467
     * @param string     $transitionToName
468
     *
469
     * @throws JiraException
470
     *
471
     * @return string
472
     */
473
    public function findTransitonId($issueIdOrKey, $transitionToName)
474
    {
475
        $this->log->debug('findTransitonId=');
476
477
        $ret = $this->getTransition($issueIdOrKey);
478
479
        foreach ($ret as $trans) {
480
            $toName = $trans->to->name;
481
482
            $this->log->debug('getTransitions result='.var_export($ret, true));
483
484
            if (strcasecmp($toName, $transitionToName) === 0) {
485
                return $trans->id;
486
            }
487
        }
488
489
        // transition keyword not found
490
        throw new JiraException("Transition name '$transitionToName' not found on JIRA Server.");
491
    }
492
493
    /**
494
     * Perform a transition on an issue.
495
     *
496
     * @param string|int $issueIdOrKey Issue id or key
497
     * @param Transition $transition
498
     *
499
     * @throws JiraException
500
     *
501
     * @return string|null nothing - if transition was successful return http 204(no contents)
502
     */
503
    public function transition($issueIdOrKey, $transition)
504
    {
505
        $this->log->debug('transition='.var_export($transition, true));
506
507
        if (!isset($transition->transition['id'])) {
508
            if (isset($transition->transition['untranslatedName'])) {
509
                $transition->transition['id'] = $this->findTransitonIdByUntranslatedName($issueIdOrKey, $transition->transition['untranslatedName']);
510
            } elseif (isset($transition->transition['untranslatedName'])) {
511
                $transition->transition['id'] = $this->findTransitonId($issueIdOrKey, $transition->transition['name']);
512
            } else {
513
                throw new JiraException("you must set either name or untranslatedName for performing transition.");
514
            }
515
        }
516
517
        $data = json_encode($transition);
518
519
        $this->log->debug("transition req=$data\n");
520
521
        $ret = $this->exec($this->uri."/$issueIdOrKey/transitions", $data, 'POST');
522
523
        $this->log->debug('getTransitions result='.var_export($ret, true));
524
525
        return $ret;
526
    }
527
528
    /**
529
     * Search issues.
530
     *
531
     * @param string $jql
532
     * @param int    $startAt
533
     * @param int    $maxResults
534
     * @param array  $fields
535
     * @param array  $expand
536
     * @param bool   $validateQuery
537
     *
538
     * @throws JiraException
539
     * @throws \JsonMapper_Exception
540
     *
541
     * @return IssueSearchResult|object
542
     */
543
    public function search($jql, $startAt = 0, $maxResults = 15, $fields = [], $expand = [], $validateQuery = true)
544
    {
545
        $data = json_encode([
546
            'jql'           => $jql,
547
            'startAt'       => $startAt,
548
            'maxResults'    => $maxResults,
549
            'fields'        => $fields,
550
            'expand'        => $expand,
551
            'validateQuery' => $validateQuery,
552
        ]);
553
554
        $ret = $this->exec('search', $data, 'POST');
555
        $json = json_decode($ret);
556
557
        $result = null;
558
        if ($this->isRestApiV3()) {
559
            $result = $this->json_mapper->map(
560
                $json,
561
                new IssueSearchResultV3()
562
            );
563
        } else {
564
            $result = $this->json_mapper->map(
565
                $json,
566
                new IssueSearchResult()
567
            );
568
        }
569
570
        return $result;
571
    }
572
573
    /**
574
     * get TimeTracking info.
575
     *
576
     * @param string|int $issueIdOrKey
577
     *
578
     * @throws JiraException
579
     * @throws \JsonMapper_Exception
580
     *
581
     * @return TimeTracking
582
     */
583
    public function getTimeTracking($issueIdOrKey)
584
    {
585
        $ret = $this->exec($this->uri."/$issueIdOrKey", null);
586
        $this->log->debug("getTimeTracking res=$ret\n");
587
588
        $issue = $this->json_mapper->map(
589
            json_decode($ret),
590
            new Issue()
591
        );
592
593
        return $issue->fields->timeTracking;
594
    }
595
596
    /**
597
     * TimeTracking issues.
598
     *
599
     * @param string|int   $issueIdOrKey Issue id or key
600
     * @param TimeTracking $timeTracking
601
     *
602
     * @throws JiraException
603
     *
604
     * @return string
605
     */
606
    public function timeTracking($issueIdOrKey, $timeTracking)
607
    {
608
        $array = [
609
            'update' => [
610
                'timetracking' => [
611
                    ['edit' => $timeTracking],
612
                ],
613
            ],
614
        ];
615
616
        $data = json_encode($array);
617
618
        $this->log->debug("TimeTracking req=$data\n");
619
620
        // if success, just return HTTP 201.
621
        $ret = $this->exec($this->uri."/$issueIdOrKey", $data, 'PUT');
622
623
        return $ret;
624
    }
625
626
    /**
627
     * get getWorklog.
628
     *
629
     * @param string|int $issueIdOrKey
630
     *
631
     * @throws JiraException
632
     * @throws \JsonMapper_Exception
633
     *
634
     * @return PaginatedWorklog|object
635
     */
636
    public function getWorklog($issueIdOrKey)
637
    {
638
        $ret = $this->exec($this->uri."/$issueIdOrKey/worklog");
639
        $this->log->debug("getWorklog res=$ret\n");
640
        $worklog = $this->json_mapper->map(
641
            json_decode($ret),
642
            new PaginatedWorklog()
643
        );
644
645
        return $worklog;
646
    }
647
648
    /**
649
     * get getWorklog by Id.
650
     *
651
     * @param string|int $issueIdOrKey
652
     * @param int        $workLogId
653
     *
654
     * @throws JiraException
655
     * @throws \JsonMapper_Exception
656
     *
657
     * @return Worklog|object PaginatedWorklog object
658
     */
659
    public function getWorklogById($issueIdOrKey, $workLogId)
660
    {
661
        $ret = $this->exec($this->uri."/$issueIdOrKey/worklog/$workLogId");
662
        $this->log->debug("getWorklogById res=$ret\n");
663
        $worklog = $this->json_mapper->map(
664
            json_decode($ret),
665
            new Worklog()
666
        );
667
668
        return $worklog;
669
    }
670
671
    /**
672
     * add work log to issue.
673
     *
674
     * @param string|int     $issueIdOrKey
675
     * @param Worklog|object $worklog
676
     *
677
     * @throws JiraException
678
     * @throws \JsonMapper_Exception
679
     *
680
     * @return Worklog|object Worklog Object
681
     */
682
    public function addWorklog($issueIdOrKey, $worklog)
683
    {
684
        $this->log->info("addWorklog=\n");
685
686
        $data = json_encode($worklog);
687
        $url = $this->uri."/$issueIdOrKey/worklog";
688
        $type = 'POST';
689
690
        $ret = $this->exec($url, $data, $type);
691
692
        $ret_worklog = $this->json_mapper->map(
693
            json_decode($ret),
694
            new Worklog()
695
        );
696
697
        return $ret_worklog;
698
    }
699
700
    /**
701
     * edit the worklog.
702
     *
703
     * @param string|int     $issueIdOrKey
704
     * @param Worklog|object $worklog
705
     * @param string|int     $worklogId
706
     *
707
     * @throws JiraException
708
     * @throws \JsonMapper_Exception
709
     *
710
     * @return Worklog|object
711
     */
712
    public function editWorklog($issueIdOrKey, $worklog, $worklogId)
713
    {
714
        $this->log->info("editWorklog=\n");
715
716
        $data = json_encode($worklog);
717
        $url = $this->uri."/$issueIdOrKey/worklog/$worklogId";
718
        $type = 'PUT';
719
720
        $ret = $this->exec($url, $data, $type);
721
722
        $ret_worklog = $this->json_mapper->map(
723
            json_decode($ret),
724
            new Worklog()
725
        );
726
727
        return $ret_worklog;
728
    }
729
730
    /**
731
     * delete worklog.
732
     *
733
     * @param string|int $issueIdOrKey
734
     * @param string|int $worklogId
735
     *
736
     * @throws JiraException
737
     *
738
     * @return bool
739
     */
740
    public function deleteWorklog($issueIdOrKey, $worklogId)
741
    {
742
        $this->log->info("deleteWorklog=\n");
743
744
        $url = $this->uri."/$issueIdOrKey/worklog/$worklogId";
745
        $type = 'DELETE';
746
747
        $ret = $this->exec($url, null, $type);
748
749
        return (bool) $ret;
750
    }
751
752
    /**
753
     * Get all priorities.
754
     *
755
     * @throws JiraException
756
     *
757
     * @return Priority[] array of priority class
758
     */
759
    public function getAllPriorities()
760
    {
761
        $ret = $this->exec('priority', null);
762
763
        $priorities = $this->json_mapper->mapArray(
764
            json_decode($ret, false),
765
            new \ArrayObject(),
766
            '\JiraRestApi\Issue\Priority'
767
        );
768
769
        return $priorities;
770
    }
771
772
    /**
773
     * Get priority by id.
774
     * throws  HTTPException if the priority is not found, or the calling user does not have permission or view it.
775
     *
776
     * @param string|int $priorityId Id of priority.
777
     *
778
     * @throws JiraException
779
     * @throws \JsonMapper_Exception
780
     *
781
     * @return Priority|object priority
782
     */
783
    public function getPriority($priorityId)
784
    {
785
        $ret = $this->exec("priority/$priorityId", null);
786
787
        $this->log->info('Result='.$ret);
788
789
        $prio = $this->json_mapper->map(
790
            json_decode($ret),
791
            new Priority()
792
        );
793
794
        return $prio;
795
    }
796
797
    /**
798
     * Get priority by id.
799
     * throws HTTPException if the priority is not found, or the calling user does not have permission or view it.
800
     *
801
     * @param string|int $priorityId Id of priority.
802
     *
803
     * @throws JiraException
804
     * @throws \JsonMapper_Exception
805
     *
806
     * @return Priority|object priority
807
     */
808
    public function getCustomFields($priorityId)
809
    {
810
        $ret = $this->exec("priority/$priorityId", null);
811
812
        $this->log->info('Result='.$ret);
813
814
        $prio = $this->json_mapper->map(
815
            json_decode($ret),
816
            new Priority()
817
        );
818
819
        return $prio;
820
    }
821
822
    /**
823
     * get watchers.
824
     *
825
     * @param $issueIdOrKey
826
     *
827
     * @throws JiraException
828
     *
829
     * @return Reporter[]
830
     */
831
    public function getWatchers($issueIdOrKey)
832
    {
833
        $this->log->info("getWatchers=\n");
834
835
        $url = $this->uri."/$issueIdOrKey/watchers";
836
837
        $ret = $this->exec($url, null);
838
839
        $watchers = $this->json_mapper->mapArray(
840
            json_decode($ret, false)->watchers,
841
            new \ArrayObject(),
842
            '\JiraRestApi\Issue\Reporter'
843
        );
844
845
        return $watchers;
846
    }
847
848
    /**
849
     * add watcher to issue.
850
     *
851
     * @param string|int $issueIdOrKey
852
     * @param string     $watcher      watcher id
853
     *
854
     * @throws JiraException
855
     *
856
     * @return bool
857
     */
858
    public function addWatcher($issueIdOrKey, $watcher)
859
    {
860
        $this->log->info("addWatcher=\n");
861
862
        $data = json_encode($watcher);
863
        $url = $this->uri."/$issueIdOrKey/watchers";
864
        $type = 'POST';
865
866
        $this->exec($url, $data, $type);
867
868
        return $this->http_response == 204 ? true : false;
869
    }
870
871
    /**
872
     * remove watcher from issue.
873
     *
874
     * @param string|int $issueIdOrKey
875
     * @param string     $watcher      watcher id
876
     *
877
     * @throws JiraException
878
     *
879
     * @return bool
880
     */
881
    public function removeWatcher($issueIdOrKey, $watcher)
882
    {
883
        $this->log->addInfo("removeWatcher=\n");
884
885
        $ret = $this->exec($this->uri."/$issueIdOrKey/watchers/?username=$watcher", '', 'DELETE');
886
887
        $this->log->addInfo('remove watcher '.$issueIdOrKey.' result='.var_export($ret, true));
888
889
        return $this->http_response == 204 ? true : false;
890
    }
891
892
    /**
893
     * remove watcher from issue by watcher account id.
894
     *
895
     * @param string|int $issueIdOrKey
896
     * @param string     $accountId    Watcher account id.
897
     *
898
     * @throws JiraException
899
     *
900
     * @return bool
901
     */
902
    public function removeWatcherByAccountId($issueIdOrKey, $accountId)
903
    {
904
        $this->log->info("removeWatcher=\n");
905
906
        $ret = $this->exec($this->uri."/$issueIdOrKey/watchers/?accountId=$accountId", '', 'DELETE');
907
908
        $this->log->info('remove watcher '.$issueIdOrKey.' result='.var_export($ret, true));
909
910
        return $this->http_response == 204 ? true : false;
911
    }
912
913
    /**
914
     * Get the meta data for creating issues.
915
     *
916
     * @param array $paramArray Possible keys for $paramArray: 'projectIds', 'projectKeys', 'issuetypeIds', 'issuetypeNames'.
917
     * @param bool  $expand     Retrieve all issue fields and values
918
     *
919
     * @throws JiraException
920
     *
921
     * @return object array of meta data for creating issues.
922
     */
923
    public function getCreateMeta($paramArray = [], $expand = true)
924
    {
925
        $paramArray['expand'] = ($expand) ? 'projects.issuetypes.fields' : null;
926
        $paramArray = array_filter($paramArray);
927
928
        $queryParam = '?'.http_build_query($paramArray);
929
930
        $ret = $this->exec($this->uri.'/createmeta'.$queryParam, null);
931
932
        return json_decode($ret);
933
    }
934
935
    /**
936
     * returns the metadata(include custom field) for an issue.
937
     *
938
     * @param string $idOrKey                issue id or key
939
     * @param bool   $overrideEditableFlag   Allows retrieving edit metadata for fields in non-editable status
940
     * @param bool   $overrideScreenSecurity Allows retrieving edit metadata for the fields hidden on Edit screen.
941
     *
942
     * @throws JiraException
943
     *
944
     * @return array of custom fields
945
     *
946
     * @see https://confluence.atlassian.com/jirakb/how-to-retrieve-available-options-for-a-multi-select-customfield-via-jira-rest-api-815566715.html How to retrieve available options for a multi-select customfield via JIRA REST API
947
     * @see https://developer.atlassian.com/cloud/jira/platform/rest/#api-api-2-issue-issueIdOrKey-editmeta-get
948
     */
949
    public function getEditMeta($idOrKey, $overrideEditableFlag = false, $overrideScreenSecurity = false)
950
    {
951
        $queryParam = '?'.http_build_query([
952
            'overrideEditableFlag'   => $overrideEditableFlag,
953
            'overrideScreenSecurity' => $overrideScreenSecurity,
954
        ]);
955
956
        $uri = sprintf('%s/%s/editmeta', $this->uri, $idOrKey).$queryParam;
957
958
        $ret = $this->exec($uri, null);
959
960
        $metas = json_decode($ret, true);
961
962
        // extract only custom field(startWith customefield_XXXXX)
963
        $cfs = array_filter($metas['fields'], function ($key) {
964
            $pos = strpos($key, 'customfield');
965
966
            return $pos !== false;
967
        }, ARRAY_FILTER_USE_KEY);
968
969
        return $cfs;
970
    }
971
972
    /**
973
     * Sends a notification (email) to the list or recipients defined in the request.
974
     *
975
     * @param string|int $issueIdOrKey Issue id Or Key
976
     * @param Notify     $notify
977
     *
978
     * @throws JiraException
979
     *
980
     * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-notify
981
     */
982
    public function notify($issueIdOrKey, $notify)
983
    {
984
        $full_uri = $this->uri."/$issueIdOrKey/notify";
985
986
        // set self value
987
        foreach ($notify->to['groups'] as &$g) {
988
            $g['self'] = $this->getConfiguration()->getJiraHost().'/rest/api/2/group?groupname='.$g['name'];
989
        }
990
        foreach ($notify->restrict['groups'] as &$g) {
991
            $g['self'] = $this->getConfiguration()->getJiraHost().'/rest/api/2/group?groupname='.$g['name'];
992
        }
993
994
        $data = json_encode($notify, JSON_UNESCAPED_SLASHES);
995
996
        $this->log->debug("notify=$data\n");
997
998
        $ret = $this->exec($full_uri, $data, 'POST');
999
1000
        if ($ret !== true) {
0 ignored issues
show
introduced by
The condition $ret !== true is always true.
Loading history...
1001
            throw new JiraException('notify failed: response code='.$ret);
1002
        }
1003
    }
1004
1005
    /**
1006
     * Get a remote issue links on the issue.
1007
     *
1008
     * @param string|int $issueIdOrKey Issue id Or Key
1009
     *
1010
     * @throws JiraException
1011
     *
1012
     * @return array array os RemoteIssueLink class
1013
     *
1014
     * @see https://developer.atlassian.com/server/jira/platform/jira-rest-api-for-remote-issue-links/
1015
     * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/issue-getRemoteIssueLinks
1016
     */
1017
    public function getRemoteIssueLink($issueIdOrKey)
1018
    {
1019
        $full_uri = $this->uri."/$issueIdOrKey/remotelink";
1020
1021
        $ret = $this->exec($full_uri, null);
1022
1023
        $rils = $this->json_mapper->mapArray(
1024
            json_decode($ret, false),
1025
            new \ArrayObject(),
1026
            RemoteIssueLink::class
1027
        );
1028
1029
        return $rils;
1030
    }
1031
1032
    /**
1033
     * @param string|int      $issueIdOrKey
1034
     * @param RemoteIssueLink $ril
1035
     *
1036
     * @throws JiraException
1037
     * @throws \JsonMapper_Exception
1038
     *
1039
     * @return object
1040
     */
1041
    public function createOrUpdateRemoteIssueLink($issueIdOrKey, RemoteIssueLink $ril)
1042
    {
1043
        $full_uri = $this->uri."/$issueIdOrKey/remotelink";
1044
1045
        $data = json_encode($ril, JSON_UNESCAPED_SLASHES);
1046
1047
        $this->log->debug("create remoteIssueLink=$data\n");
1048
1049
        $ret = $this->exec($full_uri, $data, 'POST');
1050
1051
        $res = $this->json_mapper->map(
1052
            json_decode($ret),
1053
            new RemoteIssueLink()
1054
        );
1055
1056
        return $res;
1057
    }
1058
1059
    /**
1060
     * @param string|int $issueIdOrKey
1061
     * @param string|int $globalId
1062
     *
1063
     * @throws JiraException
1064
     *
1065
     * @return string|bool
1066
     */
1067
    public function removeRemoteIssueLink($issueIdOrKey, $globalId)
1068
    {
1069
        $query = http_build_query(['globalId' => $globalId]);
1070
1071
        $full_uri = sprintf('%s/%s/remotelink?%s', $this->uri, $issueIdOrKey, $query);
1072
1073
        $ret = $this->exec($full_uri, '', 'DELETE');
1074
1075
        $this->log->info(
1076
            sprintf(
1077
                'delete remote issue link for issue "%s" with globalId "%s". Result=%s',
1078
                $issueIdOrKey,
1079
                $globalId,
1080
                var_export($ret, true)
1081
            )
1082
        );
1083
1084
        return $ret;
1085
    }
1086
1087
    /**
1088
     * get all issue security schemes.
1089
     *
1090
     * @throws JiraException
1091
     * @throws \JsonMapper_Exception
1092
     *
1093
     * @return SecurityScheme[] array of SecurityScheme class
1094
     */
1095
    public function getAllIssueSecuritySchemes()
1096
    {
1097
        $url = '/issuesecurityschemes';
1098
1099
        $ret = $this->exec($url);
1100
1101
        $data = json_decode($ret, true);
1102
1103
        // extract schem field
1104
        $schemes = json_decode(json_encode($data['issueSecuritySchemes']), false);
1105
1106
        $res = $this->json_mapper->mapArray(
1107
            $schemes,
1108
            new \ArrayObject(),
1109
            '\JiraRestApi\Issue\SecurityScheme'
1110
        );
1111
1112
        return $res;
1113
    }
1114
1115
    /**
1116
     *  get issue security scheme.
1117
     *
1118
     * @param int $securityId security scheme id
1119
     *
1120
     * @throws JiraException
1121
     * @throws \JsonMapper_Exception
1122
     *
1123
     * @return SecurityScheme SecurityScheme
1124
     */
1125
    public function getIssueSecuritySchemes($securityId)
1126
    {
1127
        $url = '/issuesecurityschemes/'.$securityId;
1128
1129
        $ret = $this->exec($url);
1130
1131
        $res = $this->json_mapper->map(
1132
            json_decode($ret),
1133
            new SecurityScheme()
1134
        );
1135
1136
        return $res;
1137
    }
1138
1139
    /**
1140
     * convenient wrapper function for add or remove labels.
1141
     *
1142
     * @param string|int $issueIdOrKey
1143
     * @param array|null $addLablesParam
1144
     * @param array|null $removeLabelsParam
1145
     * @param bool       $notifyUsers
1146
     *
1147
     * @throws JiraException
1148
     *
1149
     * @return Issue|object class
1150
     */
1151
    public function updateLabels($issueIdOrKey, $addLablesParam, $removeLabelsParam, $notifyUsers = true)
1152
    {
1153
        $labels = [];
1154
        foreach ($addLablesParam as $a) {
1155
            array_push($labels, ['add' => $a]);
1156
        }
1157
1158
        foreach ($removeLabelsParam as $r) {
1159
            array_push($labels, ['remove' => $r]);
1160
        }
1161
1162
        $postData = json_encode([
1163
            'update' => [
1164
                'labels' => $labels,
1165
            ],
1166
        ], JSON_UNESCAPED_UNICODE);
1167
1168
        $this->log->info("Update labels=\n".$postData);
1169
1170
        $queryParam = '?'.http_build_query(['notifyUsers' => $notifyUsers]);
1171
1172
        $ret = $this->exec($this->uri."/$issueIdOrKey".$queryParam, $postData, 'PUT');
1173
1174
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret returns the type string which is incompatible with the documented return type JiraRestApi\Issue\Issue|object.
Loading history...
1175
    }
1176
1177
    /**
1178
     * convenient wrapper function for add or remove fix versions.
1179
     *
1180
     * @param string|int $issueIdOrKey
1181
     * @param array|null $addFixVersionsParam
1182
     * @param array|null $removeFixVersionsParam
1183
     * @param bool       $notifyUsers
1184
     *
1185
     * @throws JiraException
1186
     *
1187
     * @return Issue|object class
1188
     */
1189
    public function updateFixVersions($issueIdOrKey, $addFixVersionsParam, $removeFixVersionsParam, $notifyUsers = true)
1190
    {
1191
        $fixVersions = [];
1192
        foreach ($addFixVersionsParam as $a) {
1193
            array_push($fixVersions, ['add' => ['name' => $a]]);
1194
        }
1195
1196
        foreach ($removeFixVersionsParam as $r) {
1197
            array_push($fixVersions, ['remove' => ['name' => $r]]);
1198
        }
1199
1200
        $postData = json_encode([
1201
            'update' => [
1202
                'fixVersions' => $fixVersions,
1203
            ],
1204
        ], JSON_UNESCAPED_UNICODE);
1205
1206
        $this->log->info("Update fixVersions=\n".$postData);
1207
1208
        $queryParam = '?'.http_build_query(['notifyUsers' => $notifyUsers]);
1209
1210
        $ret = $this->exec($this->uri."/$issueIdOrKey".$queryParam, $postData, 'PUT');
1211
1212
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret returns the type string which is incompatible with the documented return type JiraRestApi\Issue\Issue|object.
Loading history...
1213
    }
1214
1215
    /**
1216
     * find transition id by transition's untranslatedName.
1217
     *
1218
     * @param string|int $issueIdOrKey
1219
     * @param string     $untranslatedName
1220
     *
1221
     * @throws JiraException
1222
     *
1223
     * @return string
1224
     */
1225
    public function findTransitonIdByUntranslatedName($issueIdOrKey, $untranslatedName)
1226
    {
1227
        $this->log->debug('findTransitonIdByUntranslatedName=');
1228
1229
        $prj = new ProjectService();
1230
        $pkey = explode('-', $issueIdOrKey);
1231
        $transitionArray = $prj->getProjectTransitionsToArray($pkey[0]);
1232
1233
        $this->log->debug('getTransitions result='.var_export($transitionArray, true));
1234
1235
        foreach ($transitionArray as $trans) {
1236
1237
            if (strcasecmp($trans['name'], $untranslatedName) === 0 ||
1238
                strcasecmp($trans['untranslatedName'] ?? '', $untranslatedName) === 0) {
1239
1240
                return $trans['id'];
1241
            }
1242
        }
1243
1244
        // transition keyword not found
1245
        throw new JiraException("Transition name '$untranslatedName' not found on JIRA Server.");
1246
    }
1247
}
1248