|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Tinyissue package. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Mohamed Alsharaf <[email protected]> |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Tinyissue\Repository\Project\Issue; |
|
13
|
|
|
|
|
14
|
|
|
use Tinyissue\Contracts\Repository\Project\Issue\UpdaterRepository as IssueUpdater; |
|
15
|
|
|
use Tinyissue\Model\Project; |
|
16
|
|
|
use Tinyissue\Model\User; |
|
17
|
|
|
use Tinyissue\Repository\RepositoryUpdater; |
|
18
|
|
|
|
|
19
|
|
|
class UpdaterRepository extends RepositoryUpdater implements IssueUpdater |
|
20
|
|
|
{ |
|
21
|
|
|
public function __construct(Project\Issue $model) |
|
22
|
|
|
{ |
|
23
|
|
|
$this->model = $model; |
|
24
|
|
|
} |
|
25
|
|
|
|
|
26
|
|
|
/** |
|
27
|
|
|
* Set the issue is updated by a user. |
|
28
|
|
|
* |
|
29
|
|
|
* @param int $userId |
|
30
|
|
|
* |
|
31
|
|
|
* @return Eloquent\Model |
|
32
|
|
|
*/ |
|
33
|
|
|
public function changeUpdatedBy($userId) |
|
34
|
|
|
{ |
|
35
|
|
|
$this->model->updated_by = $userId; |
|
36
|
|
|
$this->model->touch(); |
|
37
|
|
|
|
|
38
|
|
|
return $this->save(); |
|
|
|
|
|
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
|
/** |
|
42
|
|
|
* Reassign the issue to a new user. |
|
43
|
|
|
* |
|
44
|
|
|
* @param int|UserInterface $assignTo |
|
45
|
|
|
* @param UserInterface $user |
|
46
|
|
|
* |
|
47
|
|
|
* @return Eloquent\Model |
|
48
|
|
|
*/ |
|
49
|
|
|
public function reassign($assignTo, UserInterface $user) |
|
50
|
|
|
{ |
|
51
|
|
|
$assignToId = !$assignTo instanceof UserInterface ? $assignTo : $assignTo->id; |
|
|
|
|
|
|
52
|
|
|
$this->model->assigned_to = $assignToId; |
|
53
|
|
|
|
|
54
|
|
|
// Add event on successful save |
|
55
|
|
|
static::saved(function (Project\Issue $issue) use ($user) { |
|
|
|
|
|
|
56
|
|
|
$this->queueAssign($issue, $user); |
|
|
|
|
|
|
57
|
|
|
}); |
|
58
|
|
|
|
|
59
|
|
|
$this->save(); |
|
60
|
|
|
|
|
61
|
|
|
return $this->model->activities()->save(new User\Activity([ |
|
62
|
|
|
'type_id' => Activity::TYPE_REASSIGN_ISSUE, |
|
63
|
|
|
'parent_id' => $this->model->project->id, |
|
64
|
|
|
'user_id' => $user->id, |
|
65
|
|
|
'action_id' => $this->model->assigned_to, |
|
66
|
|
|
])); |
|
67
|
|
|
} |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Update the given issue. |
|
71
|
|
|
* |
|
72
|
|
|
* @param array $input |
|
73
|
|
|
* |
|
74
|
|
|
* @return Eloquent\Model |
|
75
|
|
|
*/ |
|
76
|
|
|
public function update(array $input) |
|
77
|
|
|
{ |
|
78
|
|
|
$fill = array_only($input, [ |
|
79
|
|
|
'title', 'body', 'assigned_to', |
|
80
|
|
|
]); |
|
81
|
|
|
$fill['updated_by'] = $this->model->updatedBy->id; |
|
82
|
|
|
|
|
83
|
|
View Code Duplication |
if (isset($input['time_quote']['lock'])) { |
|
|
|
|
|
|
84
|
|
|
$fill['lock_quote'] = $input['time_quote']['lock']; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
// Only save quote if not locked or locked & user allowed to modify it |
|
88
|
|
View Code Duplication |
if (array_key_exists('time_quote', $input) && |
|
|
|
|
|
|
89
|
|
|
(!$this->model->isQuoteLocked() || $this->model->user->permission(Model\Permission::PERM_ISSUE_LOCK_QUOTE)) |
|
90
|
|
|
) { |
|
91
|
|
|
$fill['time_quote'] = $input['time_quote']; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
/* Add to activity log for assignment if changed */ |
|
95
|
|
|
$assignToId = (int) array_get($input, 'assigned_to'); |
|
96
|
|
|
if ($assignToId > 0 && $assignToId !== (int) $this->assigned_to) { |
|
|
|
|
|
|
97
|
|
|
$this->model->activities()->save(new User\Activity([ |
|
98
|
|
|
'type_id' => Activity::TYPE_REASSIGN_ISSUE, |
|
99
|
|
|
'parent_id' => $this->model->project->id, |
|
100
|
|
|
'user_id' => $this->model->updatedBy->id, |
|
101
|
|
|
'action_id' => $this->model->assigned_to, |
|
102
|
|
|
])); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
$this->model->fill($fill); |
|
106
|
|
|
|
|
107
|
|
|
$this->syncTags($input, $this->model->tags()->with('parent')->get()); |
|
108
|
|
|
|
|
109
|
|
|
// Add event on successful save |
|
110
|
|
|
static::saved(function (Project\Issue $issue) { |
|
|
|
|
|
|
111
|
|
|
$this->queueUpdate($issue, $issue->updatedBy); |
|
|
|
|
|
|
112
|
|
|
}); |
|
113
|
|
|
|
|
114
|
|
|
return $this->save(); |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Create a new issue. |
|
119
|
|
|
* |
|
120
|
|
|
* @param array $input |
|
121
|
|
|
* |
|
122
|
|
|
* @return static |
|
123
|
|
|
*/ |
|
124
|
|
|
public function create(array $input) |
|
125
|
|
|
{ |
|
126
|
|
|
$input['project'] = Project::find((int) $input['project_id']); |
|
127
|
|
|
$this->model->setRelations($input); |
|
128
|
|
|
|
|
129
|
|
|
$fill = [ |
|
130
|
|
|
'created_by' => $this->model->user->id, |
|
131
|
|
|
'project_id' => $this->model->project->id, |
|
132
|
|
|
'title' => $input['title'], |
|
133
|
|
|
'body' => $input['body'], |
|
134
|
|
|
'assigned_to' => (int) $this->model->project->default_assignee, |
|
135
|
|
|
]; |
|
136
|
|
|
|
|
137
|
|
View Code Duplication |
if ($this->model->user->permission('issue-modify')) { |
|
|
|
|
|
|
138
|
|
|
$fill['assigned_to'] = $input['assigned_to']; |
|
139
|
|
|
$fill['time_quote'] = $input['time_quote']; |
|
140
|
|
|
} |
|
141
|
|
|
|
|
142
|
|
|
$this->model->fill($fill)->save(); |
|
143
|
|
|
|
|
144
|
|
|
// Add issue to messages queue |
|
145
|
|
|
$this->queueAdd($this, $this->user); |
|
|
|
|
|
|
146
|
|
|
|
|
147
|
|
|
/* Add to user's activity log */ |
|
148
|
|
|
$this->model->activities()->save(new User\Activity([ |
|
149
|
|
|
'type_id' => Activity::TYPE_CREATE_ISSUE, |
|
150
|
|
|
'parent_id' => $this->model->project->id, |
|
151
|
|
|
'user_id' => $this->model->user->id, |
|
152
|
|
|
])); |
|
153
|
|
|
|
|
154
|
|
|
/* Add attachments to issue */ |
|
155
|
|
|
Attachment::where('upload_token', '=', $input['upload_token']) |
|
156
|
|
|
->where('uploaded_by', '=', $this->model->user->id) |
|
157
|
|
|
->update(['issue_id' => $this->model->id]); |
|
158
|
|
|
|
|
159
|
|
|
// Add default tag to newly created issue |
|
160
|
|
|
$defaultTag = app('tinyissue.settings')->getFirstStatusTagId(); |
|
161
|
|
|
if ($defaultTag > 0 && empty($input['tag_status'])) { |
|
162
|
|
|
$input['tag_status'] = $defaultTag; |
|
163
|
|
|
} |
|
164
|
|
|
|
|
165
|
|
|
$this->syncTags($input); |
|
166
|
|
|
|
|
167
|
|
|
return $this; |
|
|
|
|
|
|
168
|
|
|
} |
|
169
|
|
|
|
|
170
|
|
|
/** |
|
171
|
|
|
* Move the issue (comments & activities) to another project. |
|
172
|
|
|
* |
|
173
|
|
|
* @param int $projectId |
|
174
|
|
|
* |
|
175
|
|
|
* @return $this |
|
176
|
|
|
*/ |
|
177
|
|
|
public function changeProject($projectId) |
|
178
|
|
|
{ |
|
179
|
|
|
$this->project_id = $projectId; |
|
|
|
|
|
|
180
|
|
|
$this->save(); |
|
181
|
|
|
$comments = $this->model->comments()->get(); |
|
182
|
|
|
foreach ($comments as $comment) { |
|
183
|
|
|
$comment->model->project_id = $projectId; |
|
184
|
|
|
$comment->save(); |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
$activities = $this->model->activities()->get(); |
|
188
|
|
|
foreach ($activities as $activity) { |
|
189
|
|
|
$activity->model->parent_id = $projectId; |
|
190
|
|
|
$activity->save(); |
|
191
|
|
|
} |
|
192
|
|
|
|
|
193
|
|
|
return $this; |
|
|
|
|
|
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* Delete an issue. |
|
198
|
|
|
* |
|
199
|
|
|
* @return bool |
|
200
|
|
|
* |
|
201
|
|
|
* @throws \Exception |
|
202
|
|
|
*/ |
|
203
|
|
|
public function delete() |
|
204
|
|
|
{ |
|
205
|
|
|
return $this->transaction('deleteIssue'); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
protected function deleteIssue() |
|
209
|
|
|
{ |
|
210
|
|
|
$id = $this->model->id; |
|
211
|
|
|
$projectId = $this->model->project_id; |
|
212
|
|
|
$comments = $this->model->comments; |
|
213
|
|
|
$attachments = $this->model->attachments; |
|
214
|
|
|
|
|
215
|
|
|
$status = $this->model->delete(); |
|
216
|
|
|
|
|
217
|
|
View Code Duplication |
if ($status) { |
|
|
|
|
|
|
218
|
|
|
$attachments->each(function (Attachment $attachment) use ($projectId) { |
|
219
|
|
|
$path = config('filesystems.disks.local.root') |
|
220
|
|
|
. '/' . config('tinyissue.uploads_dir') |
|
221
|
|
|
. '/' . $projectId |
|
222
|
|
|
. '/' . $attachment->upload_token; |
|
223
|
|
|
$attachment->deleteFile($path, $attachment->filename); |
|
224
|
|
|
$attachment->delete(); |
|
225
|
|
|
}); |
|
226
|
|
|
$comments->each(function (Project\Issue\Comment $comment) { |
|
227
|
|
|
$comment->deleteComment($this->getLoggedUser()->user()); |
|
|
|
|
|
|
228
|
|
|
}); |
|
229
|
|
|
User\Activity::where('parent_id', '=', $projectId)->where('item_id', '=', $id)->delete(); |
|
230
|
|
|
\DB::table('projects_issues_tags')->where('issue_id', '=', $id)->delete(); |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
return $status; |
|
234
|
|
|
} |
|
235
|
|
|
|
|
236
|
|
|
/** |
|
237
|
|
|
* Change the status of an issue. |
|
238
|
|
|
* |
|
239
|
|
|
* @param int $status |
|
240
|
|
|
* @param UserInterface $user |
|
241
|
|
|
* |
|
242
|
|
|
* @return Eloquent\Model |
|
243
|
|
|
*/ |
|
244
|
|
View Code Duplication |
public function changeStatus($status, UserInterface $user) |
|
|
|
|
|
|
245
|
|
|
{ |
|
246
|
|
|
if ($status == 0) { |
|
247
|
|
|
$this->closed_by = $user->id; |
|
|
|
|
|
|
248
|
|
|
$this->closed_at = (new \DateTime())->format('Y-m-d H:i:s'); |
|
|
|
|
|
|
249
|
|
|
$activityType = Activity::TYPE_CLOSE_ISSUE; |
|
250
|
|
|
} else { |
|
251
|
|
|
$this->closed_by = 0; |
|
252
|
|
|
$this->closed_at = null; |
|
253
|
|
|
$activityType = Activity::TYPE_REOPEN_ISSUE; |
|
254
|
|
|
} |
|
255
|
|
|
|
|
256
|
|
|
/* Add to activity log */ |
|
257
|
|
|
$this->activities()->save(new User\Activity([ |
|
|
|
|
|
|
258
|
|
|
'type_id' => $activityType, |
|
259
|
|
|
'parent_id' => $this->project->id, |
|
|
|
|
|
|
260
|
|
|
'user_id' => $user->id, |
|
261
|
|
|
])); |
|
262
|
|
|
|
|
263
|
|
|
$this->status = $status; |
|
|
|
|
|
|
264
|
|
|
|
|
265
|
|
|
// Add event on successful save |
|
266
|
|
|
static::saved(function (Project\Issue $issue) use ($user) { |
|
|
|
|
|
|
267
|
|
|
$this->queueUpdate($issue, $user); |
|
|
|
|
|
|
268
|
|
|
}); |
|
269
|
|
|
|
|
270
|
|
|
return $this->save(); |
|
|
|
|
|
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
/** |
|
274
|
|
|
* Sync the issue tags. |
|
275
|
|
|
* |
|
276
|
|
|
* @param array $input |
|
277
|
|
|
* @param Collection $currentTags |
|
278
|
|
|
* |
|
279
|
|
|
* @return bool |
|
280
|
|
|
*/ |
|
281
|
|
|
public function syncTags(array $input, Collection $currentTags = null) |
|
282
|
|
|
{ |
|
283
|
|
|
$tagIds = array_only($input, [ |
|
284
|
|
|
'tag_type', 'tag_status', 'tag_resolution', |
|
285
|
|
|
]); |
|
286
|
|
|
|
|
287
|
|
|
// User can edit their own role and can only change issue type |
|
288
|
|
|
if ($this->updatedBy instanceof UserInterface && $this->updatedBy->isUser()) { |
|
|
|
|
|
|
289
|
|
|
$currentTagIds = $currentTags->pluck('id', 'parent.name')->toArray(); |
|
290
|
|
|
$tagIds['tag_status'] = array_key_exists('status', $currentTagIds) ? $currentTagIds['status'] : 0; |
|
291
|
|
|
$tagIds['tag_resolution'] = array_key_exists('resolution', $currentTagIds) ? $currentTagIds['resolution'] : 0; |
|
292
|
|
|
} |
|
293
|
|
|
|
|
294
|
|
|
$tags = (new Tag())->whereIn('id', $tagIds)->get(); |
|
295
|
|
|
|
|
296
|
|
|
$removedTags = []; |
|
297
|
|
View Code Duplication |
if (null === $currentTags) { |
|
|
|
|
|
|
298
|
|
|
// Add the following tags except for open status |
|
299
|
|
|
$addedTags = $tags |
|
300
|
|
|
->map(function (Tag $tag) { |
|
301
|
|
|
return $tag->toShortArray(); |
|
302
|
|
|
}) |
|
303
|
|
|
->toArray(); |
|
304
|
|
|
} else { |
|
305
|
|
|
// Tags remove from the issue |
|
306
|
|
|
$removedTags = $currentTags |
|
307
|
|
|
->diff($tags) |
|
308
|
|
|
->map(function (Tag $tag) { |
|
309
|
|
|
return $tag->toShortArray(); |
|
310
|
|
|
}) |
|
311
|
|
|
->toArray(); |
|
312
|
|
|
|
|
313
|
|
|
// Check if we are adding new tags |
|
314
|
|
|
$addedTags = $tags |
|
315
|
|
|
->filter(function (Tag $tag) use ($currentTags) { |
|
316
|
|
|
return $currentTags->where('id', $tag->id)->count() === 0; |
|
317
|
|
|
}) |
|
318
|
|
|
->map(function (Tag $tag) { |
|
319
|
|
|
return $tag->toShortArray(); |
|
320
|
|
|
}) |
|
321
|
|
|
->toArray(); |
|
322
|
|
|
|
|
323
|
|
|
// No new tags to add or remove |
|
324
|
|
|
if (empty($removedTags) && empty($addedTags)) { |
|
325
|
|
|
return true; |
|
326
|
|
|
} |
|
327
|
|
|
} |
|
328
|
|
|
|
|
329
|
|
|
// Save relation |
|
330
|
|
|
$this->tags()->sync($tags->pluck('id')->all()); |
|
|
|
|
|
|
331
|
|
|
|
|
332
|
|
|
// Activity is added when new issue create with tags or updated with tags excluding the open status tag |
|
333
|
|
View Code Duplication |
if (!empty($removedTags) || !empty($addedTags)) { |
|
|
|
|
|
|
334
|
|
|
// Add this change to messages queue |
|
335
|
|
|
$this->queueChangeTags($this, $addedTags, $removedTags, $this->user); |
|
|
|
|
|
|
336
|
|
|
|
|
337
|
|
|
// Add to activity log for tags if changed |
|
338
|
|
|
$this->activities()->save(new User\Activity([ |
|
|
|
|
|
|
339
|
|
|
'type_id' => Activity::TYPE_ISSUE_TAG, |
|
340
|
|
|
'parent_id' => $this->project->id, |
|
|
|
|
|
|
341
|
|
|
'user_id' => $this->user->id, |
|
342
|
|
|
'data' => ['added_tags' => $addedTags, 'removed_tags' => $removedTags], |
|
343
|
|
|
])); |
|
344
|
|
|
} |
|
345
|
|
|
|
|
346
|
|
|
return true; |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
/** |
|
350
|
|
|
* Add tag to the issue & close issue if added tag is Closed. |
|
351
|
|
|
* |
|
352
|
|
|
* @param Tag $newTag |
|
353
|
|
|
* @param Tag $oldTag |
|
354
|
|
|
* |
|
355
|
|
|
* @return $this |
|
356
|
|
|
*/ |
|
357
|
|
View Code Duplication |
public function changeKanbanTag(Tag $newTag, Tag $oldTag) |
|
|
|
|
|
|
358
|
|
|
{ |
|
359
|
|
|
// skip if there is no change in status tags |
|
360
|
|
|
if ($oldTag->name === $newTag->name) { |
|
361
|
|
|
return $this; |
|
|
|
|
|
|
362
|
|
|
} |
|
363
|
|
|
|
|
364
|
|
|
// Open issue |
|
365
|
|
|
$data = ['added_tags' => [], 'removed_tags' => []]; |
|
366
|
|
|
|
|
367
|
|
|
// Remove previous status tag |
|
368
|
|
|
$this->tags()->detach($oldTag); |
|
|
|
|
|
|
369
|
|
|
$data['removed_tags'][] = $oldTag->toShortArray(); |
|
370
|
|
|
|
|
371
|
|
|
// Add new tag |
|
372
|
|
|
if (!$this->tags->contains($newTag)) { |
|
|
|
|
|
|
373
|
|
|
$this->tags()->attach($newTag); |
|
|
|
|
|
|
374
|
|
|
|
|
375
|
|
|
$data['added_tags'][] = $newTag->toShortArray(); |
|
376
|
|
|
} |
|
377
|
|
|
|
|
378
|
|
|
if (!empty($data)) { |
|
379
|
|
|
// Add this change to messages queue |
|
380
|
|
|
$this->queueChangeTags($this, $data['added_tags'], $data['removed_tags'], $this->user); |
|
|
|
|
|
|
381
|
|
|
|
|
382
|
|
|
// Add to activity log for tags if changed |
|
383
|
|
|
$this->activities()->save(new User\Activity([ |
|
|
|
|
|
|
384
|
|
|
'type_id' => Activity::TYPE_ISSUE_TAG, |
|
385
|
|
|
'parent_id' => $this->project->id, |
|
|
|
|
|
|
386
|
|
|
'user_id' => $this->user->id, |
|
387
|
|
|
'data' => $data, |
|
388
|
|
|
])); |
|
389
|
|
|
} |
|
390
|
|
|
|
|
391
|
|
|
return $this; |
|
|
|
|
|
|
392
|
|
|
} |
|
393
|
|
|
} |
|
394
|
|
|
|
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_functionexpects aPostobject, and outputs the author of the post. The base classPostreturns a simple string and outputting a simple string will work just fine. However, the child classBlogPostwhich is a sub-type ofPostinstead decided to return anobject, and is therefore violating the SOLID principles. If aBlogPostwere passed tomy_function, PHP would not complain, but ultimately fail when executing thestrtouppercall in its body.