1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* DokuWiki Plugin issuelinks (Helper Component) |
4
|
|
|
* |
5
|
|
|
* @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html |
6
|
|
|
* @author Andreas Gohr <[email protected]> |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
// must be run within Dokuwiki |
10
|
|
|
use dokuwiki\plugin\issuelinks\classes\Issue; |
11
|
|
|
|
12
|
|
|
if(!defined('DOKU_INC')) die(); |
13
|
|
|
|
14
|
|
|
class helper_plugin_issuelinks_db extends DokuWiki_Plugin |
|
|
|
|
15
|
|
|
{ |
16
|
|
|
private $db = null; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Gives access to the sqlite DB. |
20
|
|
|
* |
21
|
|
|
* Returns null on error |
22
|
|
|
* |
23
|
|
|
* @return helper_plugin_sqlite|null |
|
|
|
|
24
|
|
|
* @throws Exception Only thrown in unittests |
25
|
|
|
*/ |
26
|
|
|
public function getDB() { |
27
|
|
|
if(null === $this->db) { |
28
|
|
|
/** @var helper_plugin_sqlite $sqlite */ |
29
|
|
|
$sqlite = plugin_load('helper', 'sqlite'); |
|
|
|
|
30
|
|
|
if(!$sqlite) { |
|
|
|
|
31
|
|
|
msg('This plugin requires the sqlite plugin. Please install it', -1); |
|
|
|
|
32
|
|
|
return null; |
33
|
|
|
} |
34
|
|
|
|
35
|
|
|
if($sqlite->getAdapter()->getName() !== DOKU_EXT_PDO) { |
|
|
|
|
36
|
|
|
if(defined('DOKU_UNITTEST')) throw new \Exception('Couldn\'t load PDO sqlite.'); |
37
|
|
|
return null; |
38
|
|
|
} |
39
|
|
|
$sqlite->getAdapter()->setUseNativeAlter(true); |
40
|
|
|
|
41
|
|
|
// initialize the database connection |
42
|
|
|
if(!$sqlite->init('issuelinks', DOKU_PLUGIN . 'issuelinks/db/')) { |
|
|
|
|
43
|
|
|
return null; |
44
|
|
|
} |
45
|
|
|
|
46
|
|
|
$this->db = $sqlite; |
47
|
|
|
} |
48
|
|
|
return $this->db; |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
|
52
|
|
|
|
53
|
|
|
/** |
54
|
|
|
* Save a key value pair to the database |
55
|
|
|
* |
56
|
|
|
* @param $key |
57
|
|
|
* @param $value |
58
|
|
|
* @return bool|null Returns false on error, nothing otherwise |
59
|
|
|
*/ |
60
|
|
|
public function saveKeyValuePair($key, $value) { |
61
|
|
|
$db = $this->getDB(); |
62
|
|
|
if(!$db) return false; |
63
|
|
|
$sql = 'REPLACE INTO opts VALUES (?, ?)'; |
64
|
|
|
$db->query($sql, array($key, $value)); |
65
|
|
|
} |
66
|
|
|
|
67
|
|
|
/** |
68
|
|
|
* Get a value to a stored key from the database |
69
|
|
|
* |
70
|
|
|
* @param $key |
71
|
|
|
* @return bool|string |
72
|
|
|
*/ |
73
|
|
|
public function getKeyValue($key) { |
74
|
|
|
$db = $this->getDB(); |
75
|
|
|
if(!$db) return false; |
76
|
|
|
$sql = 'SELECT val FROM opts WHERE opt = ?'; |
77
|
|
|
$res = $db->query($sql, array($key)); |
78
|
|
|
$value = $db->res2single($res); |
79
|
|
|
$db->res_close($res); |
80
|
|
|
return $value; |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
|
84
|
|
|
/** |
85
|
|
|
* @param string $service The name of the repository management service |
86
|
|
|
* @param string $repo The repository |
87
|
|
|
* @param string $id The id of the webhook |
88
|
|
|
* @param string $secret The secret to use when authenicationg incoming webhooks |
89
|
|
|
*/ |
90
|
|
|
public function saveWebhook($service, $repo, $id, $secret) { |
91
|
|
|
$entity = array( |
92
|
|
|
'service' => $service, |
93
|
|
|
'repository_id' => $repo, |
94
|
|
|
'id' => $id, |
95
|
|
|
'secret' => $secret |
96
|
|
|
); |
97
|
|
|
$this->saveEntity('webhooks', $entity); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
/** |
101
|
|
|
* Get the stored secret used to authenticate an incoming webhook |
102
|
|
|
* |
103
|
|
|
* @param string $rmservice |
104
|
|
|
* @param string $repo |
105
|
|
|
* @return array |
106
|
|
|
*/ |
107
|
|
|
public function getWebhookSecrets($service, $repo) { |
108
|
|
|
$sql = "SELECT secret FROM webhooks WHERE service = ? AND repository_id = ?"; |
109
|
|
|
$secrets = $this->sqlArrayQuery($sql, array($service, $repo)); |
110
|
|
|
return $secrets; |
|
|
|
|
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* @param string $service |
115
|
|
|
* @param string $repo |
116
|
|
|
* @param string $id |
117
|
|
|
*/ |
118
|
|
|
public function deleteWebhook($service, $repo, $id) { |
119
|
|
|
$entity = array( |
120
|
|
|
'service' => $service, |
121
|
|
|
'repository_id' => $repo, |
122
|
|
|
'id' => $id |
123
|
|
|
); |
124
|
|
|
$this->deleteEntity('webhooks', $entity); |
125
|
|
|
} |
126
|
|
|
|
127
|
|
|
public function getWebhooks($service, $repo = null, $id = null) |
128
|
|
|
{ |
129
|
|
|
$sql = 'SELECT * FROM webhooks WHERE service = ?'; |
130
|
|
|
$params = [$service]; |
131
|
|
|
if ($repo) { |
132
|
|
|
$sql .= ' AND repository_id = ?'; |
133
|
|
|
$params[] = $repo; |
134
|
|
|
} |
135
|
|
|
if ($id) { |
136
|
|
|
$sql .= ' AND id = ?'; |
137
|
|
|
$params[] = $id; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
$webhooks = $this->sqlArrayQuery($sql, $params); |
141
|
|
|
return $webhooks; |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* Save an issue into the database |
147
|
|
|
* |
148
|
|
|
* @param Issue $issue |
149
|
|
|
* @return bool |
150
|
|
|
*/ |
151
|
|
|
public function saveIssue(Issue $issue) { |
152
|
|
|
$ok = $this->saveEntity('issues', array ( |
153
|
|
|
'service' => $issue->getServiceName(), |
154
|
|
|
'project' => $issue->getProject(), |
155
|
|
|
'id' => $issue->getKey(), |
156
|
|
|
'is_mergerequest' => $issue->isMergeRequest() ? '1' : '0', |
157
|
|
|
'summary' => $issue->getSummary(), |
158
|
|
|
'description' => $issue->getDescription(), |
159
|
|
|
'type' => $issue->getType(), |
160
|
|
|
'status' => $issue->getStatus(), |
161
|
|
|
'parent' => $issue->getParent(), |
162
|
|
|
'components' => implode(',',$issue->getComponents()), |
163
|
|
|
'labels' => implode(',', $issue->getLabels()), |
164
|
|
|
'priority' => $issue->getPriority(), |
165
|
|
|
'duedate' => $issue->getDuedate(), |
|
|
|
|
166
|
|
|
'versions' => implode(',', $issue->getVersions()), |
167
|
|
|
'updated' => $issue->getUpdated() |
|
|
|
|
168
|
|
|
)); |
169
|
|
|
return (bool)$ok; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Query the database for the issue corresponding to the given project and issueId |
175
|
|
|
* |
176
|
|
|
* @param string $serviceName The name of the project management service |
177
|
|
|
* @param string $projectKey The short-key of a project, e.g. SPR |
178
|
|
|
* @param int $issueId The id of an issue e.g. 42 |
179
|
|
|
* |
180
|
|
|
* @return bool|array |
181
|
|
|
*/ |
182
|
|
|
public function loadIssue($serviceName, $projectKey, $issueId, $isMergeRequest) { |
183
|
|
|
$sql = 'SELECT * FROM issues WHERE service = ? AND project = ? AND id = ? AND is_mergerequest = ?'; |
184
|
|
|
$issues = $this->sqlArrayQuery($sql, array($serviceName, $projectKey, $issueId, $isMergeRequest ? 1 : 0)); |
185
|
|
|
return blank($issues[0]) ? false : $issues[0]; |
|
|
|
|
186
|
|
|
} |
187
|
|
|
|
188
|
|
|
public function saveIssueIssues(Issue $issue, array $issues) { |
189
|
|
|
$this->deleteEntity('issue_issues', array( |
190
|
|
|
'service' => $issue->getServiceName(), |
191
|
|
|
'project' => $issue->getProject(), |
192
|
|
|
'id' => $issue->getKey(), |
193
|
|
|
'is_mergerequest' => $issue->isMergeRequest() ? 1 : 0, |
194
|
|
|
)); |
195
|
|
|
foreach ($issues as $issueData) { |
196
|
|
|
$this->saveEntity('issue_issues', array( |
197
|
|
|
'service' => $issue->getServiceName(), |
198
|
|
|
'project' => $issue->getProject(), |
199
|
|
|
'id' => $issue->getKey(), |
200
|
|
|
'is_mergerequest' => $issue->isMergeRequest() ? 1 : 0, |
201
|
|
|
'referenced_service' => $issueData['service'], |
202
|
|
|
'referenced_project' => $issueData['project'], |
203
|
|
|
'referenced_id' => $issueData['issueId'], |
204
|
|
|
'referenced_is_mergerequest' => 0, |
205
|
|
|
)); |
206
|
|
|
} |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
public function getMergeRequestsReferencingIssue($serviceName, $project, $issueId, $isMergeRequest) { |
210
|
|
|
$sql = ' |
211
|
|
|
SELECT service, project as project_id, id as issue_id, is_mergerequest |
212
|
|
|
FROM issue_issues |
213
|
|
|
WHERE referenced_service = ? |
214
|
|
|
AND referenced_project = ? |
215
|
|
|
AND referenced_id = ? |
216
|
|
|
AND referenced_is_mergerequest = ? |
217
|
|
|
AND is_mergerequest = 1 |
218
|
|
|
'; |
219
|
|
|
return $this->sqlArrayQuery($sql, array($serviceName, $project, $issueId, $isMergeRequest ? 1 : 0)); |
220
|
|
|
} |
221
|
|
|
|
222
|
|
|
/** |
223
|
|
|
* Query the database for pages with link-syntax to the given issue |
224
|
|
|
* |
225
|
|
|
* @param string $serviceName The name of the project management service |
226
|
|
|
* @param string $projectKey The project short-key |
227
|
|
|
* @param int $issue_id The ID of the issue, e.g. 42 |
228
|
|
|
* |
229
|
|
|
* @return array |
230
|
|
|
*/ |
231
|
|
|
public function getAllPageLinkingToIssue($serviceName, $projectKey, $issue_id, $isMergeRequest) { |
232
|
|
|
$sql = "SELECT page, rev |
233
|
|
|
FROM pagerev_issues |
234
|
|
|
WHERE service = ? |
235
|
|
|
AND project_id = ? |
236
|
|
|
AND issue_id = ? |
237
|
|
|
AND is_mergerequest = ? |
238
|
|
|
AND type = 'link' |
239
|
|
|
ORDER BY rev DESC "; |
240
|
|
|
return $this->sqlArrayQuery($sql, array($serviceName, $projectKey, $issue_id, $isMergeRequest ? 1 : 0)); |
|
|
|
|
241
|
|
|
} |
242
|
|
|
|
243
|
|
|
/** |
244
|
|
|
* Delete "Link"-references to old revisions from database |
245
|
|
|
* |
246
|
|
|
* @param string $serviceName The name of the project management service |
247
|
|
|
* @param string $projectKey The short-key for the project, e.g. SPR |
248
|
|
|
* @param int $issue_id The id of the issue, e.g. 42 |
249
|
|
|
* @param array $pages |
250
|
|
|
* |
251
|
|
|
* @return array |
252
|
|
|
*/ |
253
|
|
|
public function removeOldLinks($serviceName, $projectKey, $issue_id, $isMergeRequest, $pages) { |
254
|
|
|
$activeLinks = array(); |
255
|
|
|
|
256
|
|
|
foreach($pages as $linkingPage) { |
257
|
|
|
$changelog = new PageChangelog($linkingPage['page']); |
|
|
|
|
258
|
|
|
$currentRev = $changelog->getRelativeRevision(time(), -1); |
259
|
|
|
if($linkingPage['rev'] < $currentRev) { |
260
|
|
|
$entity = array( |
261
|
|
|
'page' => $linkingPage['page'], |
262
|
|
|
'issue_id' => $issue_id, |
263
|
|
|
'project_id' => $projectKey, |
264
|
|
|
'service' => $serviceName, |
265
|
|
|
'is_mergerequest' => $isMergeRequest ? '1' : '0', |
266
|
|
|
'type' => 'link', |
267
|
|
|
); |
268
|
|
|
$this->deleteEntity('pagerev_issues', $entity); |
269
|
|
|
} else { |
270
|
|
|
$activeLinks[] = $linkingPage; |
271
|
|
|
} |
272
|
|
|
} |
273
|
|
|
return $activeLinks; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* Save the connection between a Jira issue and a revision of a page. |
278
|
|
|
* |
279
|
|
|
* @param string $page |
280
|
|
|
* @param int $rev |
281
|
|
|
* @param string $serviceName The name of the project management service |
282
|
|
|
* @param string $project |
283
|
|
|
* @param int $issue_id |
284
|
|
|
* @param string $type |
285
|
|
|
* |
286
|
|
|
* @return bool |
287
|
|
|
* |
288
|
|
|
* @throws \InvalidArgumentException |
289
|
|
|
*/ |
290
|
|
|
public function savePageRevIssues($page, $rev, $serviceName, $project, $issue_id, $isMergeRequest, $type) { |
291
|
|
|
/** @var helper_plugin_issuelinks_util $util */ |
292
|
|
|
$util = plugin_load('helper', 'issuelinks_util'); |
|
|
|
|
293
|
|
|
if (!$util->isValidTimeStamp($rev)) { |
294
|
|
|
throw new InvalidArgumentException("Second parameter must be a valid timestamp!"); |
295
|
|
|
} |
296
|
|
|
if ((int)$rev === 0) { |
297
|
|
|
$rev = filemtime(wikiFN($page)); |
|
|
|
|
298
|
|
|
$changelog = new PageChangelog($page); |
299
|
|
|
$rev_info = $changelog->getRevisionInfo($rev); |
300
|
|
|
$user = $rev_info['user'] ? $rev_info['user'] : $rev_info['ip']; |
301
|
|
|
$this->savePageRev($page, $rev, $rev_info['sum'], $user); |
302
|
|
|
} |
303
|
|
|
/** @noinspection TypeUnsafeComparisonInspection this is done to ensure $issue_id is a natural number */ |
304
|
|
|
if (!is_numeric($issue_id) || (int)$issue_id != $issue_id) { |
|
|
|
|
305
|
|
|
throw new InvalidArgumentException("IssueId must be an integer!"); |
306
|
|
|
} |
307
|
|
|
$ok = $this->saveEntity('pagerev_issues', array ( |
308
|
|
|
'page' => $page, |
309
|
|
|
'rev' => $rev, |
310
|
|
|
'service' => $serviceName, |
311
|
|
|
'project_id' => $project, |
312
|
|
|
'issue_id' => $issue_id, |
313
|
|
|
'is_mergerequest' => $isMergeRequest ? '1' : '0', |
314
|
|
|
'type' => $type |
315
|
|
|
)); |
316
|
|
|
|
317
|
|
|
return (bool)$ok; |
318
|
|
|
} |
319
|
|
|
|
320
|
|
|
/** |
321
|
|
|
* Save the data about a pagerevision |
322
|
|
|
* |
323
|
|
|
* @param string $page |
324
|
|
|
* @param int $rev |
325
|
|
|
* @param string $summary |
326
|
|
|
* @param string $user |
327
|
|
|
* @return bool |
328
|
|
|
*/ |
329
|
|
|
public function savePageRev($page, $rev, $summary, $user) { |
330
|
|
|
if (blank($page) || blank($rev) || blank($user)) { |
|
|
|
|
331
|
|
|
throw new InvalidArgumentException("No empty values allowed!"); |
332
|
|
|
} |
333
|
|
|
/** @var helper_plugin_issuelinks_util $util */ |
334
|
|
|
$util = plugin_load('helper', 'issuelinks_util'); |
|
|
|
|
335
|
|
|
if (!$util->isValidTimeStamp($rev)) { |
336
|
|
|
throw new InvalidArgumentException("Second parameter must be a valid timestamp!"); |
337
|
|
|
} |
338
|
|
|
$ok = $this->saveEntity('pagerevs', array ( |
339
|
|
|
'page' => $page, |
340
|
|
|
'rev' => $rev, |
341
|
|
|
'summary' => $summary, |
342
|
|
|
'user' => $user, |
343
|
|
|
)); |
344
|
|
|
return (bool)$ok; |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
/** |
348
|
|
|
* Delete ALL entries from the database that correspond to the given page, issue and type. |
349
|
|
|
* |
350
|
|
|
* @param string $page the wikipage |
351
|
|
|
* @param string $serviceName The name of the project management service |
352
|
|
|
* @param string $projectKey the key of the project, e.g. SPR |
353
|
|
|
* @param int $issueId the id of the issue, e.g. 42 |
354
|
|
|
* @param bool $isMergeRequest |
355
|
|
|
* @param string $type either 'context' or 'link' |
356
|
|
|
*/ |
357
|
|
|
public function deleteAllIssuePageRevisions($page, $serviceName, $projectKey, $issueId, $isMergeRequest, $type) { |
358
|
|
|
// todo: validation |
359
|
|
|
$this->deleteEntity('pagerev_issues', array( |
360
|
|
|
'page' => $page, |
361
|
|
|
'service' => $serviceName, |
362
|
|
|
'project_id' => $projectKey, |
363
|
|
|
'issue_id' => $issueId, |
364
|
|
|
'is_mergerequest' => $isMergeRequest ? 1 : 0, |
365
|
|
|
'type' => $type |
366
|
|
|
)); |
367
|
|
|
} |
368
|
|
|
|
369
|
|
|
/** |
370
|
|
|
* Deletes the given key-value array to the given table |
371
|
|
|
* |
372
|
|
|
* @param string $table |
373
|
|
|
* @param array $entity associative array holding the key/value pairs for the where clause |
374
|
|
|
*/ |
375
|
|
|
private function deleteEntity($table, $entity) { |
376
|
|
|
$db = $this->getDB(); |
377
|
|
|
if(!$db) return; |
378
|
|
|
|
379
|
|
|
$where = implode(' = ? AND ', array_keys($entity)) . ' = ?'; |
380
|
|
|
$vals = array_values($entity); |
381
|
|
|
|
382
|
|
|
$sql = "DELETE FROM $table WHERE $where"; |
383
|
|
|
$db->query($sql, $vals); |
384
|
|
|
} |
385
|
|
|
|
386
|
|
|
/** |
387
|
|
|
* Saves the given key-value array to the given table |
388
|
|
|
* |
389
|
|
|
* @param string $table |
390
|
|
|
* @param array $entity associative array holding the key/value pairs |
391
|
|
|
* @return bool|\SQLiteResult |
392
|
|
|
*/ |
393
|
|
|
private function saveEntity($table, $entity) { |
394
|
|
|
$db = $this->getDB(); |
395
|
|
|
if(!$db) return false; |
396
|
|
|
|
397
|
|
|
$keys = implode(', ', array_keys($entity)); |
398
|
|
|
$vals = array_values($entity); |
399
|
|
|
$wlds = implode(', ', array_fill(0, count($vals), '?')); |
400
|
|
|
|
401
|
|
|
$sql = "REPLACE INTO $table ($keys) VALUES ($wlds)"; |
402
|
|
|
$ok = $db->query($sql, $vals); |
403
|
|
|
if (empty($ok)) { |
404
|
|
|
global $conf; |
405
|
|
|
msg("Saving into table $table failed!", -1); |
|
|
|
|
406
|
|
|
msg(print_r($entity, true), -1); |
407
|
|
|
if ($conf['debug']) { |
408
|
|
|
msg(dbg_backtrace(), -1); |
|
|
|
|
409
|
|
|
} |
410
|
|
|
} |
411
|
|
|
return $ok; |
412
|
|
|
} |
413
|
|
|
|
414
|
|
|
/** |
415
|
|
|
* make a provided sql query and return the resulting lines as an array of associative arrays |
416
|
|
|
* |
417
|
|
|
* @param string $sql the query |
418
|
|
|
* @param string|array $conditional the parameters of the query |
419
|
|
|
* |
420
|
|
|
* @return array|bool |
421
|
|
|
*/ |
422
|
|
|
private function sqlArrayQuery($sql, $conditional) { |
423
|
|
|
if (substr(trim($sql),0,strlen('SELECT')) !== 'SELECT') { |
424
|
|
|
throw new InvalidArgumentException("SQL-Statement must be a SELECT statement! \n" . $sql); |
425
|
|
|
} |
426
|
|
|
if (strpos(trim($sql,';'), ';') !== false) { |
427
|
|
|
throw new InvalidArgumentException("SQL-Statement must be one single statement! \n" . $sql); |
428
|
|
|
} |
429
|
|
|
$db = $this->getDB(); |
430
|
|
|
if(!$db) return false; |
431
|
|
|
|
432
|
|
|
$res = $db->query($sql,$conditional); |
433
|
|
|
$result = $db->res2arr($res); |
434
|
|
|
$db->res_close($res); |
435
|
|
|
return $result; |
436
|
|
|
} |
437
|
|
|
|
438
|
|
|
} |
439
|
|
|
|
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:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths