Passed
Pull Request — master (#1)
by Michael
02:26
created

Jira::hydrateConfigForm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 1
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by IntelliJ IDEA.
4
 * User: michael
5
 * Date: 4/16/18
6
 * Time: 7:57 AM
7
 */
8
9
namespace dokuwiki\plugin\issuelinks\services;
10
11
12
use dokuwiki\Form\Form;
1 ignored issue
show
Bug introduced by
The type dokuwiki\Form\Form 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...
13
use dokuwiki\plugin\issuelinks\classes\ExternalServerException;
14
use dokuwiki\plugin\issuelinks\classes\HTTPRequestException;
15
use dokuwiki\plugin\issuelinks\classes\Issue;
16
use dokuwiki\plugin\issuelinks\classes\Repository;
17
use dokuwiki\plugin\issuelinks\classes\RequestResult;
18
19
class Jira extends AbstractService
20
{
21
22
    const SYNTAX = 'jira';
23
    const DISPLAY_NAME = 'Jira';
24
    const ID = 'jira';
25
26
    protected $dokuHTTPClient;
27
    protected $jiraUrl;
28
    protected $token;
29
    protected $configError;
30
    protected $authUser;
31
    protected $total;
32
33
    // FIXME should this be rather protected?
34
    public function __construct()
35
    {
36
        $this->dokuHTTPClient = new \DokuHTTPClient();
1 ignored issue
show
Bug introduced by
The type DokuHTTPClient 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...
37
        /** @var \helper_plugin_issuelinks_db $db */
38
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

38
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
39
        $jiraUrl = $db->getKeyValue('jira_url');
40
        $this->jiraUrl = $jiraUrl ? trim($jiraUrl, '/') : null;
0 ignored issues
show
Bug introduced by
It seems like $jiraUrl can also be of type true; however, parameter $str of trim() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

40
        $this->jiraUrl = $jiraUrl ? trim(/** @scrutinizer ignore-type */ $jiraUrl, '/') : null;
Loading history...
41
        $authToken = $db->getKeyValue('jira_token');
42
        $this->token = $authToken;
43
        $jiraUser = $db->getKeyValue('jira_user');
44
        $this->authUser = $jiraUser;
45
    }
46
47
    /**
48
     * Get the url to the given issue at the given project
49
     *
50
     * @param      $projectId
51
     * @param      $issueId
52
     * @param bool $isMergeRequest ignored, GitHub routes the requests correctly by itself
53
     *
54
     * @return string
55
     */
56
    public function getIssueURL($projectId, $issueId, $isMergeRequest)
57
    {
58
        return $this->jiraUrl . '/browse/' . $projectId . '-' . $issueId;
59
    }
60
61
    /**
62
     * Decide whether the provided issue is valid
63
     *
64
     * @param Issue $issue
65
     *
66
     * @return bool
67
     */
68
    public static function isIssueValid(Issue $issue)
69
    {
70
        $summary = $issue->getSummary();
71
        $valid = !blank($summary);
1 ignored issue
show
Bug introduced by
The function blank was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

71
        $valid = !/** @scrutinizer ignore-call */ blank($summary);
Loading history...
72
        $status = $issue->getStatus();
73
        $valid &= !blank($status);
74
        $type = $issue->getType();
75
        $valid &= !blank($type);
76
        return $valid;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $valid returns the type integer which is incompatible with the documented return type boolean.
Loading history...
77
    }
78
79
    /**
80
     * Provide the character separation the project name from the issue number, may be different for merge requests
81
     *
82
     * @param bool $isMergeRequest
83
     *
84
     * @return string
85
     */
86
    public static function getProjectIssueSeparator($isMergeRequest)
87
    {
88
        return '-';
89
    }
90
91
    /**
92
     * @param string $issueSyntax
93
     *
94
     * @return Issue
95
     */
96
    public function parseIssueSyntax($issueSyntax)
97
    {
98
        if (preg_match('/^\w+\-[1-9]\d*$/', $issueSyntax) !== 1) {
99
            return null;
100
        }
101
102
        list($projectKey, $issueNumber) = explode('-', $issueSyntax);
103
104
        $issue = Issue::getInstance('jira', $projectKey, $issueNumber, false);
105
        $issue->getFromDB();
106
107
        return $issue;
108
    }
109
110
    /**
111
     * @return bool
112
     */
113
    public function isConfigured()
114
    {
115
        if (null === $this->jiraUrl) {
116
            $this->configError = 'Jira URL not set!';
117
            return false;
118
        }
119
120
        if (empty($this->token)) {
121
            $this->configError = 'Authentication token is missing!';
122
            return false;
123
        }
124
125
        if (empty($this->authUser)) {
126
            $this->configError = 'Authentication user is missing!';
127
            return false;
128
        }
129
130
        try {
131
            $this->makeJiraRequest('/rest/webhooks/1.0/webhook', [], 'GET');
132
//            $user = $this->makeJiraRequest('/rest/api/2/user', [], 'GET');
133
        } catch (\Exception $e) {
134
            $this->configError = 'Attempt to verify the Jira authentication failed with message: ' . hsc($e->getMessage());
1 ignored issue
show
Bug introduced by
The function hsc was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

134
            $this->configError = 'Attempt to verify the Jira authentication failed with message: ' . /** @scrutinizer ignore-call */ hsc($e->getMessage());
Loading history...
135
            return false;
136
        }
137
138
        return true;
139
    }
140
141
    /**
142
     * @param Form $configForm
143
     *
144
     * @return void
145
     */
146
    public function hydrateConfigForm(Form $configForm)
147
    {
148
        $url = 'https://id.atlassian.com/manage/api-tokens';
149
        $link = "<a href=\"$url\">$url</a>";
150
        $configForm->addHTML("<p>{$this->configError} Please go to $link and generate a new token for this plugin.</p>");
151
        $configForm->addTextInput('jira_url', 'Jira Url')->val($this->jiraUrl);
152
        $configForm->addTextInput('jira_user', 'Jira User')
153
            ->val($this->authUser)
154
            ->attr('placeholder', '[email protected]');
155
        $configForm->addPasswordInput('jira_token', 'Jira AccessToken')->useInput(false);
156
    }
157
158
    public function handleAuthorization()
159
    {
160
        global $INPUT;
161
162
        $token = $INPUT->str('jira_token');
163
        $url = $INPUT->str('jira_url');
164
        $user = $INPUT->str('jira_user');
165
166
        /** @var \helper_plugin_issuelinks_db $db */
167
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

167
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
168
        if (!empty($token)) {
169
            $db->saveKeyValuePair('jira_token', $token);
170
        }
171
        if (!empty($url)) {
172
            $db->saveKeyValuePair('jira_url', $url);
173
        }
174
        if (!empty($user)) {
175
            $db->saveKeyValuePair('jira_user', $user);
176
        }
177
178
    }
179
180
    public function getUserString()
181
    {
182
        return hsc($this->authUser);
1 ignored issue
show
Bug introduced by
The function hsc was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

182
        return /** @scrutinizer ignore-call */ hsc($this->authUser);
Loading history...
183
    }
184
185
    public function retrieveIssue(Issue $issue)
186
    {
187
        // FIXME: somehow validate that we are allowed to retrieve that issue
188
189
        $projectKey = $issue->getProject();
190
191
        /** @var \helper_plugin_issuelinks_db $db */
192
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

192
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
193
        $webhooks = $db->getWebhooks('jira');
194
        $allowedRepos = explode(',', $webhooks[0]['repository_id']);
195
196
        if (!in_array($projectKey, $allowedRepos, true)) {
197
//            Jira Projects must be enabled as Webhook for on-demand fetching
198
            return;
199
        }
200
201
202
        $issueNumber = $issue->getKey();
203
        $endpoint = "/rest/api/2/issue/$projectKey-$issueNumber";
204
205
        $issueData = $this->makeJiraRequest($endpoint, [], 'GET');
206
        $this->setIssueData($issue, $issueData);
207
    }
208
209
    public function retrieveAllIssues($projectKey, &$startat = 0)
210
    {
211
//        $jql = 'project=' . $projectKey
212
//            . '&startAt=' . $startat;
213
        $jqlQuery = "project=$projectKey";
214
//        $jqlQuery = urlencode("project=$projectKey");
215
//        $jqlQuery = urlencode("project=$projectKey ORDER BY updated DESC");
216
        $endpoint = '/rest/api/2/search?jql=' . $jqlQuery. '&startAt=' . $startat;
217
        $result = $this->makeJiraRequest($endpoint, [], 'GET');
218
219
        if (empty($result['issues'])) {
220
            return array();
221
        }
222
223
        $this->total = $result['total'];
224
225
        $startat += $result['maxResults'];
226
227
        $retrievedIssues = array();
228
        foreach ($result['issues'] as $issueData) {
229
            list(, $issueNumber) = explode('-', $issueData['key']);
230
            try {
231
                $issue = Issue::getInstance('jira', $projectKey, $issueNumber, false);
232
            } catch (\InvalidArgumentException $e) {
233
                continue;
234
            }
235
            $this->setIssueData($issue, $issueData);
236
            $issue->saveToDB();
237
            $retrievedIssues[] = $issue;
238
        }
239
        /** @var Issue $lastIssue */
240
        $lastIssue = end($retrievedIssues);
241
        dbglog($lastIssue->getProject() . ' ' . $lastIssue->getKey() . ': ' . $lastIssue->getSummary());
1 ignored issue
show
Bug introduced by
The function dbglog was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

241
        /** @scrutinizer ignore-call */ 
242
        dbglog($lastIssue->getProject() . ' ' . $lastIssue->getKey() . ': ' . $lastIssue->getSummary());
Loading history...
242
        dbglog($endpoint, __FILE__ . ': ' . __LINE__);
243
        dbglog($result['startAt'], __FILE__ . ': ' . __LINE__);
244
        return $retrievedIssues;
245
    }
246
247
    /**
248
     * Get the total of issues currently imported by retrieveAllIssues()
249
     *
250
     * This may be an estimated number
251
     *
252
     * @return int
253
     */
254
    public function getTotalIssuesBeingImported()
255
    {
256
        return $this->total;
257
    }
258
259
    /**
260
     * Get a list of all organisations a user is member of
261
     *
262
     * @return string[] the identifiers of the organisations
263
     */
264
    public function getListOfAllUserOrganisations()
265
    {
266
        return ['All projects'];
267
    }
268
269
    /**
270
     * @param $organisation
271
     *
272
     * @return Repository[]
273
     */
274
    public function getListOfAllReposAndHooks($organisation)
275
    {
276
        /** @var \helper_plugin_issuelinks_db $db */
277
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

277
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
278
        $webhooks = $db->getWebhooks('jira');
279
        $subscribedProjects = [];
280
        if (!empty($webhooks)) {
281
            $subscribedProjects = explode(',', $webhooks[0]['repository_id']);
282
        }
283
284
        $projects = $this->makeJiraRequest('/rest/api/2/project', [], 'GET');
285
286
        $repositories = [];
287
        foreach ($projects as $project) {
288
            $repo = new Repository();
289
            $repo->displayName = $project['name'];
290
            $repo->full_name = $project['key'];
291
            if (in_array($project['key'], $subscribedProjects)) {
292
                $repo->hookID = 1;
293
            }
294
            $repositories[] = $repo;
295
        }
296
        return $repositories;
297
    }
298
299
    public function createWebhook($project)
300
    {
301
302
        // get old webhook id
303
        /** @var \helper_plugin_issuelinks_db $db */
304
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

304
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
305
        $webhooks = $db->getWebhooks('jira');
306
        $projects = [];
307
        if (!empty($webhooks)) {
308
            $oldID = $webhooks[0]['id'];
309
            // get current webhook projects
310
            $projects = explode(',', $webhooks[0]['repository_id']);
311
            // remove old webhook
312
            $this->makeJiraRequest('/rest/webhooks/1.0/webhook/' . $oldID, [], 'DELETE');
313
            // delete old webhook from database
314
            $db->deleteWebhook('jira', $webhooks[0]['repository_id'], $oldID);
315
        }
316
317
        // add new project
318
        $projects[] = $project;
319
        $projects = array_filter(array_unique($projects));
320
        $projectsString = implode(',', $projects);
321
322
        // add new webhooks
323
        global $conf;
324
        $payload = [
325
            'name' => 'dokuwiki plugin issuelinks for Wiki: ' . $conf['title'],
326
            'url' => self::WEBHOOK_URL,
327
            'events' => [
328
                'jira:issue_created',
329
                'jira:issue_updated',
330
            ],
331
            'description' => 'dokuwiki plugin issuelinks for Wiki: ' . $conf['title'],
332
            'jqlFilter' => "project in ($projectsString)",
333
            'excludeIssueDetails' => 'false'
334
        ];
335
        $response = $this->makeJiraRequest('/rest/webhooks/1.0/webhook', $payload, 'POST');
336
        $selfLink = $response['self'];
337
        $newWebhookID = substr($selfLink, strrpos($selfLink, '/') + 1);
338
339
340
        // store new webhook to database
341
        $db->saveWebhook('jira', $projectsString, $newWebhookID, 'jira rest webhooks have no secrets :/');
342
        return ['status' => 200, 'data' => ['id' => $newWebhookID]];
343
344
    }
345
346
    /**
347
     * Delete our webhook in a source repository
348
     *
349
     * @param     $project
350
     * @param int $hookid the numerical id of the hook to be deleted
351
     *
352
     * @return array
353
     */
354
    public function deleteWebhook($project, $hookid)
355
    {
356
        // get old webhook id
357
        /** @var \helper_plugin_issuelinks_db $db */
358
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

358
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
359
        $webhooks = $db->getWebhooks('jira');
360
        $projects = [];
361
        if (!empty($webhooks)) {
362
            $oldID = $webhooks[0]['id'];
363
            // get current webhook projects
364
            $projects = explode(',', $webhooks[0]['repository_id']);
365
            // remove old webhook
366
            $this->makeJiraRequest('/rest/webhooks/1.0/webhook/' . $oldID, [], 'DELETE');
367
            // delete old webhook from database
368
            $db->deleteWebhook('jira', $webhooks[0]['repository_id'], $oldID);
369
        }
370
371
        // remove project
372
        $projects = array_filter(array_diff($projects, [$project]));
373
        if (empty($projects)) {
374
            return ['status' => 204, 'data' => ''];
375
        }
376
377
        $projectsString = implode(',', $projects);
378
379
        // add new webhooks
380
        global $conf;
381
        $payload = [
382
            'name' => 'dokuwiki plugin issuelinks for Wiki: ' . $conf['title'],
383
            'url' => self::WEBHOOK_URL,
384
            'events' => [
385
                'jira:issue_created',
386
                'jira:issue_updated',
387
            ],
388
            'description' => 'dokuwiki plugin issuelinks for Wiki: ' . $conf['title'],
389
            'jqlFilter' => "project in ($projectsString)",
390
            'excludeIssueDetails' => 'false'
391
        ];
392
        $response = $this->makeJiraRequest('/rest/webhooks/1.0/webhook', $payload, 'POST');
393
        $selfLink = $response['self'];
394
        $newWebhookID = substr($selfLink, strrpos($selfLink, '/') + 1);
395
396
        // store new webhook to database
397
        $db->saveWebhook('jira', $projectsString, $newWebhookID, 'jira rest webhooks have no secrets :/');
398
399
        return ['status' => 204, 'data' => ''];
400
    }
401
402
    public static function isOurWebhook()
403
    {
404
        global $INPUT;
405
        $userAgent = $INPUT->server->str('HTTP_USER_AGENT');
406
        return strpos($userAgent, 'Atlassian HttpClient') === 0;
407
    }
408
409
    /**
410
     * Do all checks to verify that the webhook is expected and actually ours
411
     *
412
     * @param $webhookBody
413
     *
414
     * @return true|RequestResult true if the the webhook is our and should be processed RequestResult with explanation otherwise
415
     */
416
    public function validateWebhook($webhookBody)
417
    {
418
        $data = json_decode($webhookBody);
419
        /** @var \helper_plugin_issuelinks_db $db */
420
        $db = plugin_load('helper', 'issuelinks_db');
1 ignored issue
show
Bug introduced by
The function plugin_load was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

420
        $db = /** @scrutinizer ignore-call */ plugin_load('helper', 'issuelinks_db');
Loading history...
421
        $webhooks = $db->getWebhooks('jira');
422
        $projects = [];
423
        if (!empty($webhooks)) {
424
            // get current webhook projects
425
            $projects = explode(',', $webhooks[0]['repository_id']);
426
        }
427
428
        if (!$data['webhookEvent'] || !in_array($data['webhookEvent'], ['jira:issue_updated', 'jira:issue_created'])) {
429
            return new RequestResult(400, 'unknown webhook event');
430
        }
431
432
        list($projectKey, $issueId) = explode('-', $data['issue']['key']);
433
434
        if (!in_array($projectKey, $projects)) {
435
            return new RequestResult(202,'Project ' . $projectKey . ' is not handled by this wiki.');
436
        }
437
438
        return true;
439
    }
440
441
    /**
442
     * Handle the contents of the webhooks body
443
     *
444
     * @param $webhookBody
445
     *
446
     * @return RequestResult
447
     */
448
    public function handleWebhook($webhookBody)
449
    {
450
        // TODO: Implement handleWebhook() method.
451
    }
452
453
    protected function setIssueData(Issue $issue, $issueData)
454
    {
455
        $issue->setSummary($issueData['fields']['summary']);
456
        $issue->setStatus($issueData['fields']['status']['name']);
457
        $issue->setDescription($issueData['fields']['description']);
458
        $issue->setType($issueData['fields']['issuetype']['name']);
459
460
        $issue->setUpdated($issueData['fields']['updated']);
461
        $versions = array_column($issueData['fields']['fixVersions'], 'name');
462
        $issue->setVersions($versions);
463
        $components = array_column($issueData['fields']['components'], 'name');
464
        $issue->setComponents($components);
465
        $issue->setLabels($issueData['fields']['labels']);
466
467
        if ($issueData['fields']['assignee']) {
468
            $assignee = $issueData['fields']['assignee'];
469
            $issue->setAssignee($assignee['displayName'], $assignee['avatarUrls']['48x48']);
470
        }
471
472
        // FIXME: check and handle these fields:
473
//        $issue->setPriority($info['fields']['priority']['name']);
474
//        $issue->setDuedate($info['fields']['duedate']);
475
//        $issue->setParent($info['fields']['parent']['key']);
476
477
    }
478
479
    protected function makeJiraRequest($endpoint, array $data, $method, array $headers = array()) {
480
        $url = $this->jiraUrl . strtolower($endpoint);
481
        $dataToBeSend = json_encode($data);
0 ignored issues
show
Unused Code introduced by
The assignment to $dataToBeSend is dead and can be removed.
Loading history...
482
        $defaultHeaders = [
483
            'Authorization' => 'Basic ' . base64_encode("$this->authUser:$this->token"),
484
            'Content-Type' => 'application/json',
485
        ];
486
487
        $this->dokuHTTPClient->headers = array_merge($this->dokuHTTPClient->headers, $defaultHeaders, $headers);
488
489
        try {
490
//            $responseSuccess = $this->dokuHTTPClient->sendRequest($url, $dataToBeSend, $method);
491
            $responseSuccess = $this->dokuHTTPClient->get($url);
492
        } catch (\HTTPClientException $e) {
1 ignored issue
show
Bug introduced by
The type HTTPClientException 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...
493
            throw new HTTPRequestException('request error', $this->dokuHTTPClient, $url, $method);
494
        }
495
496
        if (!$responseSuccess || $this->dokuHTTPClient->status < 200 || $this->dokuHTTPClient->status > 206) {
497
            if ($this->dokuHTTPClient->status >= 500) {
498
                throw new ExternalServerException('request error', $this->dokuHTTPClient, $url, $method);
499
            }
500
            throw new HTTPRequestException('request error', $this->dokuHTTPClient, $url, $method);
501
        }
502
        return json_decode($this->dokuHTTPClient->resp_body, true);
503
504
    }
505
506
}
507