Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 455
0.00% covered (danger)
0.00%
0 / 33
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiController
0.00% covered (danger)
0.00%
0 / 455
0.00% covered (danger)
0.00%
0 / 33
10920
0.00% covered (danger)
0.00%
0 / 1
 apiTaskReminderCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 validateTaskReminderCreate
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 validateTaskCreate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 createTaskReminderFromRequest
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 apiTaskCreate
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 createTaskMedia
0.00% covered (danger)
0.00%
0 / 92
0.00% covered (danger)
0.00%
0 / 1
90
 createTaskDir
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 createTaskFromRequest
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
42
 apiTaskGet
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 apiTaskSet
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
6
 updateTaskFromRequest
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 validateTaskElementCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 apiTaskElementCreate
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 createTaskElementMedia
0.00% covered (danger)
0.00%
0 / 85
0.00% covered (danger)
0.00%
0 / 1
56
 createTaskElementFromRequest
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
30
 apiTaskElementGet
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 apiTaskElementSet
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
30
 updateTaskElementFromRequest
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
30
 apiTaskAttributeCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskAttributeFromRequest
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 validateTaskAttributeCreate
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 apiTaskAttributeTypeL11nCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskAttributeTypeL11nFromRequest
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 validateTaskAttributeTypeL11nCreate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 apiTaskAttributeTypeCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskAttributeTypeFromRequest
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 validateTaskAttributeTypeCreate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 apiTaskAttributeValueCreate
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
12
 createTaskAttributeValueFromRequest
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 validateTaskAttributeValueCreate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 apiTaskAttributeValueL11nCreate
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 createTaskAttributeValueL11nFromRequest
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 validateTaskAttributeValueL11nCreate
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   Modules\Tasks
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace Modules\Tasks\Controller;
16
17use Modules\Admin\Models\AccountMapper;
18use Modules\Admin\Models\NullAccount;
19use Modules\Media\Models\CollectionMapper;
20use Modules\Media\Models\MediaMapper;
21use Modules\Media\Models\NullMedia;
22use Modules\Media\Models\PathSettings;
23use Modules\Media\Models\Reference;
24use Modules\Media\Models\ReferenceMapper;
25use Modules\Tag\Models\NullTag;
26use Modules\Tasks\Models\NullTaskAttributeType;
27use Modules\Tasks\Models\NullTaskAttributeValue;
28use Modules\Tasks\Models\Task;
29use Modules\Tasks\Models\TaskAttribute;
30use Modules\Tasks\Models\TaskAttributeMapper;
31use Modules\Tasks\Models\TaskAttributeType;
32use Modules\Tasks\Models\TaskAttributeTypeL11nMapper;
33use Modules\Tasks\Models\TaskAttributeTypeMapper;
34use Modules\Tasks\Models\TaskAttributeValue;
35use Modules\Tasks\Models\TaskAttributeValueL11nMapper;
36use Modules\Tasks\Models\TaskAttributeValueMapper;
37use Modules\Tasks\Models\TaskElement;
38use Modules\Tasks\Models\TaskElementMapper;
39use Modules\Tasks\Models\TaskMapper;
40use Modules\Tasks\Models\TaskSeen;
41use Modules\Tasks\Models\TaskSeenMapper;
42use Modules\Tasks\Models\TaskStatus;
43use Modules\Tasks\Models\TaskType;
44use phpOMS\Localization\BaseStringL11n;
45use phpOMS\Localization\ISO639x1Enum;
46use phpOMS\Message\Http\HttpResponse;
47use phpOMS\Message\Http\RequestStatusCode;
48use phpOMS\Message\RequestAbstract;
49use phpOMS\Message\ResponseAbstract;
50use phpOMS\Utils\Parser\Markdown\Markdown;
51
52/**
53 * Api controller for the tasks module.
54 *
55 * @package Modules\Tasks
56 * @license OMS License 2.0
57 * @link    https://jingga.app
58 * @since   1.0.0
59 */
60final class ApiController extends Controller
61{
62    /**
63     * Api method to remind a task
64     *
65     * @param RequestAbstract  $request  Request
66     * @param ResponseAbstract $response Response
67     * @param array            $data     Generic data
68     *
69     * @return void
70     *
71     * @api
72     *
73     * @since 1.0.0
74     */
75    public function apiTaskReminderCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
76    {
77        if (!empty($val = $this->validateTaskReminderCreate($request))) {
78            $response->header->status = RequestStatusCode::R_400;
79            $this->createInvalidCreateResponse($request, $response, $val);
80
81            return;
82        }
83
84        /** @var TaskSeen[] $reminder */
85        $reminder = $this->createTaskReminderFromRequest($request);
86        $this->createModel($request->header->account, $reminder, TaskSeenMapper::class, 'reminder', $request->getOrigin());
87        $this->createStandardCreateResponse($request, $response, $reminder);
88    }
89
90    /**
91     * Validate reminder create request
92     *
93     * @param RequestAbstract $request Request
94     *
95     * @return array<string, bool> Returns the validation array of the request
96     *
97     * @since 1.0.0
98     */
99    private function validateTaskReminderCreate(RequestAbstract $request) : array
100    {
101        $val = [];
102        if (($val['id'] = !$request->hasData('id'))) {
103            return $val;
104        }
105
106        return [];
107    }
108
109    /**
110     * Validate task create request
111     *
112     * @param RequestAbstract $request Request
113     *
114     * @return array<string, bool> Returns the validation array of the request
115     *
116     * @since 1.0.0
117     */
118    private function validateTaskCreate(RequestAbstract $request) : array
119    {
120        $val = [];
121        if (($val['title'] = !$request->hasData('title'))
122            || ($val['plain'] = !$request->hasData('plain'))
123        ) {
124            return $val;
125        }
126
127        return [];
128    }
129
130    /**
131     * Method to create remeinder from request.
132     *
133     * @param RequestAbstract $request Request
134     *
135     * @return TaskSeen[] Returns the created reminders from the request
136     *
137     * @since 1.0.0
138     */
139    public function createTaskReminderFromRequest(RequestAbstract $request) : array
140    {
141        /** @var AccountRelation[] $responsible */
142        $responsible = TaskMapper::getResponsible((int) $request->getData('id'));
143
144        $reminder = [];
145        foreach ($responsible as $account) {
146            $unseen             = new TaskSeen();
147            $unseen->task       = (int) $request->getData('id');
148            $unseen->seenBy     = $account->relation->id;
149            $unseen->reminderBy = new NullAccount($request->header->account);
150            $unseen->reminderAt = new \DateTime('now');
151
152            $remidner[] = $unseen;
153        }
154
155        return $reminder;
156    }
157
158    /**
159     * Api method to create a task
160     *
161     * @param RequestAbstract  $request  Request
162     * @param ResponseAbstract $response Response
163     * @param array            $data     Generic data
164     *
165     * @return void
166     *
167     * @api
168     *
169     * @since 1.0.0
170     */
171    public function apiTaskCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
172    {
173        if (!empty($val = $this->validateTaskCreate($request))) {
174            $response->header->status = RequestStatusCode::R_400;
175            $this->createInvalidCreateResponse($request, $response, $val);
176
177            return;
178        }
179
180        /** @var Task $task */
181        $task = $this->createTaskFromRequest($request);
182        $this->createModel($request->header->account, $task, TaskMapper::class, 'task', $request->getOrigin());
183
184        if (!empty($request->files)
185            || !empty($request->getDataJson('media'))
186        ) {
187            $this->createTaskMedia($task, $request);
188        }
189
190        $this->createStandardCreateResponse($request, $response, $task);
191    }
192
193    /**
194     * Create media files for task
195     *
196     * @param Task            $task    Task
197     * @param RequestAbstract $request Request incl. media do upload
198     *
199     * @return void
200     *
201     * @since 1.0.0
202     */
203    private function createTaskMedia(Task $task, RequestAbstract $request) : void
204    {
205        $path = $this->createTaskDir($task);
206
207        /** @var \Modules\Admin\Models\Account $account */
208        $account = AccountMapper::get()->where('id', $request->header->account)->execute();
209
210        if (!empty($uploadedFiles = $request->files)) {
211            $uploaded = $this->app->moduleManager->get('Media')->uploadFiles(
212                names: [],
213                fileNames: [],
214                files: $uploadedFiles,
215                account: $request->header->account,
216                basePath: __DIR__ . '/../../../Modules/Media/Files' . $path,
217                virtualPath: $path,
218                pathSettings: PathSettings::FILE_PATH
219            );
220
221            $collection = null;
222            foreach ($uploaded as $media) {
223                $this->createModelRelation(
224                    $request->header->account,
225                    $task->id,
226                    $media->id,
227                    TaskMapper::class,
228                    'media',
229                    '',
230                    $request->getOrigin()
231                );
232
233                $accountPath = '/Accounts/'
234                    . $account->id . ' '
235                    . $account->login . '/Tasks/'
236                    . $task->createdAt->format('Y') . '/'
237                    . $task->createdAt->format('m') . '/'
238                    . $task->id;
239
240                $ref            = new Reference();
241                $ref->name      = $media->name;
242                $ref->source    = new NullMedia($media->id);
243                $ref->createdBy = new NullAccount($request->header->account);
244                $ref->setVirtualPath($accountPath);
245
246                $this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin());
247
248                if ($collection === null) {
249                    /** @var \Modules\Media\Models\Collection $collection */
250                    $collection = MediaMapper::getParentCollection($path)->limit(1)->execute();
251
252                    if ($collection->id === 0) {
253                        $collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
254                            $accountPath,
255                            $request->header->account,
256                            __DIR__ . '/../../../Modules/Media/Files/Accounts/'
257                                . $account->id . '/Tasks/'
258                                . $task->createdAt->format('Y') . '/'
259                                . $task->createdAt->format('m') . '/'
260                                . $task->id
261                        );
262                    }
263                }
264
265                $this->createModelRelation(
266                    $request->header->account,
267                    $collection->id,
268                    $ref->id,
269                    CollectionMapper::class,
270                    'sources',
271                    '',
272                    $request->getOrigin()
273                );
274            }
275        }
276
277        if (!empty($mediaFiles = $request->getDataJson('media'))) {
278            $collection = null;
279
280            foreach ($mediaFiles as $file) {
281                /** @var \Modules\Media\Models\Media $media */
282                $media = MediaMapper::get()->where('id', (int) $file)->limit(1)->execute();
283
284                $this->createModelRelation(
285                    $request->header->account,
286                    $task->id,
287                    $media->id,
288                    TaskMapper::class,
289                    'media',
290                    '',
291                    $request->getOrigin()
292                );
293
294                $ref            = new Reference();
295                $ref->name      = $media->name;
296                $ref->source    = new NullMedia($media->id);
297                $ref->createdBy = new NullAccount($request->header->account);
298                $ref->setVirtualPath($path);
299
300                $this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin());
301
302                if ($collection === null) {
303                    /** @var \Modules\Media\Models\Collection $collection */
304                    $collection = MediaMapper::getParentCollection($path)->limit(1)->execute();
305
306                    if ($collection->id === 0) {
307                        $collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
308                            $path,
309                            $request->header->account,
310                            __DIR__ . '/../../../Modules/Media/Files' . $path
311                        );
312                    }
313                }
314
315                $this->createModelRelation(
316                    $request->header->account,
317                    $collection->id,
318                    $ref->id,
319                    CollectionMapper::class,
320                    'sources',
321                    '',
322                    $request->getOrigin()
323                );
324            }
325        }
326    }
327
328    /**
329     * Create media directory path
330     *
331     * @param Task $task Task
332     *
333     * @return string
334     *
335     * @since 1.0.0
336     */
337    private function createTaskDir(Task $task) : string
338    {
339        return '/Modules/Tasks/'
340            . $task->createdAt->format('Y') . '/'
341            . $task->createdAt->format('m') . '/'
342            . $task->createdAt->format('d') . '/'
343            . $task->id;
344    }
345
346    /**
347     * Method to create task from request.
348     *
349     * @param RequestAbstract $request Request
350     *
351     * @return Task Returns the created task from the request
352     *
353     * @since 1.0.0
354     */
355    public function createTaskFromRequest(RequestAbstract $request) : Task
356    {
357        $task                 = new Task();
358        $task->title          = $request->getDataString('title') ?? '';
359        $task->description    = Markdown::parse($request->getDataString('plain') ?? '');
360        $task->descriptionRaw = $request->getDataString('plain') ?? '';
361        $task->setCreatedBy(new NullAccount($request->header->account));
362        $task->setStatus(TaskStatus::OPEN);
363        $task->setType(TaskType::SINGLE);
364        $task->redirect = $request->getDataString('redirect') ?? '';
365
366        if (!$request->hasData('priority')) {
367            $task->due = $request->getDataDateTime('due');
368        } else {
369            $task->setPriority((int) $request->getData('priority'));
370        }
371
372        if (!empty($tags = $request->getDataJson('tags'))) {
373            foreach ($tags as $tag) {
374                if (!isset($tag['id'])) {
375                    $request->setData('title', $tag['title'], true);
376                    $request->setData('color', $tag['color'], true);
377                    $request->setData('icon', $tag['icon'] ?? null, true);
378                    $request->setData('language', $tag['language'], true);
379
380                    $internalResponse = new HttpResponse();
381                    $this->app->moduleManager->get('Tag')->apiTagCreate($request, $internalResponse);
382
383                    if (!\is_array($data = $internalResponse->getDataArray($request->uri->__toString()))) {
384                        continue;
385                    }
386
387                    $task->addTag($data['response']);
388                } else {
389                    $task->addTag(new NullTag((int) $tag['id']));
390                }
391            }
392        }
393
394        $element = new TaskElement();
395        $element->addTo(new NullAccount($request->getDataInt('forward') ?? $request->header->account));
396        $element->createdBy = $task->getCreatedBy();
397        $element->due       = $task->due;
398        $element->setPriority($task->getPriority());
399        $element->setStatus(TaskStatus::OPEN);
400
401        $task->addElement($element);
402
403        return $task;
404    }
405
406    /**
407     * Api method to get a task
408     *
409     * @param RequestAbstract  $request  Request
410     * @param ResponseAbstract $response Response
411     * @param array            $data     Generic data
412     *
413     * @return void
414     *
415     * @api
416     *
417     * @since 1.0.0
418     */
419    public function apiTaskGet(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
420    {
421        /** @var Task $task */
422        $task = TaskMapper::get()->where('id', (int) $request->getData('id'))->execute();
423        $this->createStandardReturnResponse($request, $response, $task);
424    }
425
426    /**
427     * Api method to update a task
428     *
429     * @param RequestAbstract  $request  Request
430     * @param ResponseAbstract $response Response
431     * @param array            $data     Generic data
432     *
433     * @return void
434     *
435     * @api
436     *
437     * @since 1.0.0
438     */
439    public function apiTaskSet(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
440    {
441        /** @var Task $old */
442        $old = TaskMapper::get()->where('id', (int) $request->getData('id'))->execute();
443
444        /** @var Task $new */
445        $new = $this->updateTaskFromRequest($request, clone $old);
446        $this->updateModel($request->header->account, $old, $new, TaskMapper::class, 'task', $request->getOrigin());
447
448        if (!empty($new->trigger)) {
449            $this->app->eventManager->triggerSimilar($new->trigger, '', $new);
450        }
451
452        $this->createStandardUpdateResponse($request, $response, $new);
453    }
454
455    /**
456     * Method to update an task from a request
457     *
458     * @param RequestAbstract $request Request
459     *
460     * @return Task Returns the updated task from the request
461     *
462     * @since 1.0.0
463     */
464    private function updateTaskFromRequest(RequestAbstract $request, Task $task) : Task
465    {
466        $task->title          = $request->getDataString('title') ?? $task->title;
467        $task->description    = Markdown::parse($request->getDataString('plain') ?? $task->descriptionRaw);
468        $task->descriptionRaw = $request->getDataString('plain') ?? $task->descriptionRaw;
469        $task->due            = $request->hasData('due') ? new \DateTime($request->getDataString('due') ?? 'now') : $task->due;
470        $task->setStatus($request->getDataInt('status') ?? $task->getStatus());
471        $task->setType($request->getDataInt('type') ?? $task->getType());
472        $task->setPriority($request->getDataInt('priority') ?? $task->getPriority());
473        $task->completion = $request->getDataInt('completion') ?? $task->completion;
474
475        return $task;
476    }
477
478    /**
479     * Validate task element create request
480     *
481     * @param RequestAbstract $request Request
482     *
483     * @return array<string, bool> Returns the validation array of the request
484     *
485     * @since 1.0.0
486     */
487    private function validateTaskElementCreate(RequestAbstract $request) : array
488    {
489        $val = [];
490        if (($val['status'] = !TaskStatus::isValidValue((int) $request->getData('status')))
491            || ($val['due'] = !((bool) \strtotime((string) $request->getData('due'))))
492            || ($val['task'] = !(\is_numeric($request->getData('task'))))
493            || ($val['forward'] = !(\is_numeric($request->hasData('forward') ? $request->getData('forward') : $request->header->account)))
494        ) {
495            return $val;
496        }
497
498        return [];
499    }
500
501    /**
502     * Api method to create a task element
503     *
504     * @param RequestAbstract  $request  Request
505     * @param ResponseAbstract $response Response
506     * @param array            $data     Generic data
507     *
508     * @return void
509     *
510     * @api
511     *
512     * @since 1.0.0
513     */
514    public function apiTaskElementCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
515    {
516        if (!empty($val = $this->validateTaskElementCreate($request))) {
517            $response->header->status = RequestStatusCode::R_400;
518            $this->createInvalidCreateResponse($request, $response, $val);
519
520            return;
521        }
522
523        /** @var \Modules\Tasks\Models\Task $task */
524        $task    = TaskMapper::get()->where('id', (int) ($request->getData('task')))->execute();
525        $element = $this->createTaskElementFromRequest($request, $task);
526
527        $task->due        = $element->due;
528        $task->completion = $request->getDataInt('completion') ?? $task->completion;
529        $task->setPriority($element->getPriority());
530        $task->setStatus($element->getStatus());
531
532        if ($task->getStatus() === TaskStatus::DONE) {
533            $task->completion = 100;
534        }
535
536        $this->createModel($request->header->account, $element, TaskElementMapper::class, 'taskelement', $request->getOrigin());
537
538        if (!empty($request->files)
539            || !empty($request->getDataJson('media'))
540        ) {
541            $this->createTaskElementMedia($task, $element, $request);
542        }
543
544        $this->updateModel($request->header->account, $task, $task, TaskMapper::class, 'task', $request->getOrigin());
545
546        if (!empty($task->trigger)) {
547            $this->app->eventManager->triggerSimilar($task->trigger, '', $task);
548        }
549
550        $this->createStandardCreateResponse($request, $response, $element);
551    }
552
553    /**
554     * Create media files for task element
555     *
556     * @param Task            $task    Task
557     * @param TaskElement     $element Task element
558     * @param RequestAbstract $request Request incl. media do upload
559     *
560     * @return void
561     *
562     * @since 1.0.0
563     */
564    private function createTaskElementMedia(Task $task, TaskElement $element, RequestAbstract $request) : void
565    {
566        $path = $this->createTaskDir($task);
567
568        /** @var \Modules\Admin\Models\Account $account */
569        $account = AccountMapper::get()->where('id', $request->header->account)->execute();
570
571        if (!empty($uploadedFiles = $request->files)) {
572            $uploaded = $this->app->moduleManager->get('Media')->uploadFiles(
573                [],
574                [],
575                $uploadedFiles,
576                $request->header->account,
577                __DIR__ . '/../../../Modules/Media/Files' . $path,
578                $path,
579            );
580
581            $collection = null;
582            foreach ($uploaded as $media) {
583                $this->createModelRelation(
584                    $request->header->account,
585                    $element->id,
586                    $media->id,
587                    TaskElementMapper::class,
588                    'media',
589                    '',
590                    $request->getOrigin()
591                );
592
593                $accountPath = '/Accounts/' . $account->id . ' '
594                    . $account->login . '/Tasks/'
595                    . $task->createdAt->format('Y') . '/'
596                    . $task->createdAt->format('m') . '/'
597                    . $task->id;
598
599                $ref            = new Reference();
600                $ref->name      = $media->name;
601                $ref->source    = new NullMedia($media->id);
602                $ref->createdBy = new NullAccount($request->header->account);
603                $ref->setVirtualPath($accountPath);
604
605                $this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin());
606
607                if ($collection === null) {
608                    $collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
609                        $accountPath,
610                        $request->header->account,
611                        __DIR__ . '/../../../Modules/Media/Files/Accounts/' . $account->id
612                            . '/Tasks/' . $task->createdAt->format('Y') . '/'
613                            . $task->createdAt->format('m') . '/'
614                            . $task->id
615                    );
616                }
617
618                $this->createModelRelation(
619                    $request->header->account,
620                    $collection->id,
621                    $ref->id,
622                    CollectionMapper::class,
623                    'sources',
624                    '',
625                    $request->getOrigin()
626                );
627            }
628        }
629
630        if (!empty($mediaFiles = $request->getDataJson('media'))) {
631            $collection = null;
632
633            foreach ($mediaFiles as $file) {
634                /** @var \Modules\Media\Models\Media $media */
635                $media = MediaMapper::get()->where('id', (int) $file)->limit(1)->execute();
636
637                $this->createModelRelation(
638                    $request->header->account,
639                    $element->id,
640                    $media->id,
641                    TaskElementMapper::class,
642                    'media',
643                    '',
644                    $request->getOrigin()
645                );
646
647                $ref            = new Reference();
648                $ref->name      = $media->name;
649                $ref->source    = new NullMedia($media->id);
650                $ref->createdBy = new NullAccount($request->header->account);
651                $ref->setVirtualPath($path);
652
653                $this->createModel($request->header->account, $ref, ReferenceMapper::class, 'media_reference', $request->getOrigin());
654
655                if ($collection === null) {
656                    $collection = $this->app->moduleManager->get('Media')->createRecursiveMediaCollection(
657                        $path,
658                        $request->header->account,
659                        __DIR__ . '/../../../Modules/Media/Files' . $path
660                    );
661                }
662
663                $this->createModelRelation(
664                    $request->header->account,
665                    $collection->id,
666                    $ref->id,
667                    CollectionMapper::class,
668                    'sources',
669                    '',
670                    $request->getOrigin()
671                );
672            }
673        }
674    }
675
676    /**
677     * Method to create task element from request.
678     *
679     * @param RequestAbstract $request Request
680     * @param Task            $task    Task
681     *
682     * @return TaskElement Returns the task created from the request
683     *
684     * @since 1.0.0
685     */
686    public function createTaskElementFromRequest(RequestAbstract $request, Task $task) : TaskElement
687    {
688        $element            = new TaskElement();
689        $element->createdBy = new NullAccount($request->header->account);
690        $element->due       = $request->getDataDateTime('due') ?? $task->due;
691        $element->setPriority($request->getDataInt('priority') ?? $task->getPriority());
692        $element->setStatus((int) ($request->getData('status')));
693        $element->task           = $task->id;
694        $element->description    = Markdown::parse($request->getDataString('plain') ?? '');
695        $element->descriptionRaw = $request->getDataString('plain') ?? '';
696
697        $tos = $request->getData('to') ?? $request->header->account;
698        if (!\is_array($tos)) {
699            $tos = [$tos];
700        }
701
702        $ccs = $request->getData('cc') ?? [];
703        if (!\is_array($ccs)) {
704            $ccs = [$ccs];
705        }
706
707        foreach ($tos as $to) {
708            $element->addTo(new NullAccount((int) $to));
709        }
710
711        foreach ($ccs as $cc) {
712            $element->addCC(new NullAccount((int) $cc));
713        }
714
715        return $element;
716    }
717
718    /**
719     * Api method to get a task
720     *
721     * @param RequestAbstract  $request  Request
722     * @param ResponseAbstract $response Response
723     * @param array            $data     Generic data
724     *
725     * @return void
726     *
727     * @api
728     *
729     * @since 1.0.0
730     */
731    public function apiTaskElementGet(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
732    {
733        /** @var TaskElement $task */
734        $task = TaskElementMapper::get()->where('id', (int) $request->getData('id'))->execute();
735        $this->createStandardReturnResponse($request, $response, $task);
736    }
737
738    /**
739     * Api method to update a task element
740     *
741     * @param RequestAbstract  $request  Request
742     * @param ResponseAbstract $response Response
743     * @param array            $data     Generic data
744     *
745     * @return void
746     *
747     * @api
748     *
749     * @since 1.0.0
750     */
751    public function apiTaskElementSet(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
752    {
753        /** @var TaskElement $old */
754        $old = TaskElementMapper::get()->where('id', (int) $request->getData('id'))->execute();
755
756        /** @var TaskElement $new */
757        $new = $this->updateTaskElementFromRequest($request, clone $old);
758        $this->updateModel($request->header->account, $old, $new, TaskElementMapper::class, 'taskelement', $request->getOrigin());
759
760        if ($old->getStatus() !== $new->getStatus()
761            || $old->getPriority() !== $new->getPriority()
762            || $old->due !== $new->due
763        ) {
764            /** @var Task $task */
765            $task = TaskMapper::get()->where('id', $new->task)->execute();
766
767            $task->setStatus($new->getStatus());
768            $task->setPriority($new->getPriority());
769            $task->due = $new->due;
770
771            $this->updateModel($request->header->account, $task, $task, TaskMapper::class, 'task', $request->getOrigin());
772
773            if (!empty($task->trigger)) {
774                $this->app->eventManager->triggerSimilar($task->trigger, '', $task);
775            }
776        }
777
778        $this->createStandardUpdateResponse($request, $response, $new);
779    }
780
781    /**
782     * Method to update an task element from a request
783     *
784     * @param RequestAbstract $request Request
785     *
786     * @return TaskElement Returns the updated task element from the request
787     *
788     * @since 1.0.0
789     */
790    private function updateTaskElementFromRequest(RequestAbstract $request, TaskElement $element) : TaskElement
791    {
792        $element->due = $request->getDataDateTime('due') ?? $element->due;
793        $element->setStatus($request->getDataInt('status') ?? $element->getStatus());
794        $element->description    = Markdown::parse($request->getDataString('plain') ?? $element->descriptionRaw);
795        $element->descriptionRaw = $request->getDataString('plain') ?? $element->descriptionRaw;
796
797        $tos = $request->getData('to') ?? $request->header->account;
798        if (!\is_array($tos)) {
799            $tos = [$tos];
800        }
801
802        $ccs = $request->getData('cc') ?? [];
803        if (!\is_array($ccs)) {
804            $ccs = [$ccs];
805        }
806
807        foreach ($tos as $to) {
808            $element->addTo($to);
809        }
810
811        foreach ($ccs as $cc) {
812            $element->addCC($cc);
813        }
814
815        return $element;
816    }
817
818    /**
819     * Api method to create task attribute
820     *
821     * @param RequestAbstract  $request  Request
822     * @param ResponseAbstract $response Response
823     * @param array            $data     Generic data
824     *
825     * @return void
826     *
827     * @api
828     *
829     * @since 1.0.0
830     */
831    public function apiTaskAttributeCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
832    {
833        if (!empty($val = $this->validateTaskAttributeCreate($request))) {
834            $response->header->status = RequestStatusCode::R_400;
835            $this->createInvalidCreateResponse($request, $response, $val);
836
837            return;
838        }
839
840        $attribute = $this->createTaskAttributeFromRequest($request);
841        $this->createModel($request->header->account, $attribute, TaskAttributeMapper::class, 'attribute', $request->getOrigin());
842        $this->createStandardCreateResponse($request, $response, $attribute);
843    }
844
845    /**
846     * Method to create task attribute from request.
847     *
848     * @param RequestAbstract $request Request
849     *
850     * @return TaskAttribute
851     *
852     * @since 1.0.0
853     */
854    private function createTaskAttributeFromRequest(RequestAbstract $request) : TaskAttribute
855    {
856        $attribute       = new TaskAttribute();
857        $attribute->task = (int) $request->getData('task');
858        $attribute->type = new NullTaskAttributeType((int) $request->getData('type'));
859
860        if ($request->hasData('value_id')) {
861            $attribute->value = new NullTaskAttributeValue((int) $request->getData('value_id'));
862        } else {
863            $newRequest = clone $request;
864            $newRequest->setData('value', $request->getData('value'), true);
865
866            $value = $this->createTaskAttributeValueFromRequest($request);
867
868            $attribute->value = $value;
869        }
870
871        return $attribute;
872    }
873
874    /**
875     * Validate task attribute create request
876     *
877     * @param RequestAbstract $request Request
878     *
879     * @return array<string, bool>
880     *
881     * @since 1.0.0
882     */
883    private function validateTaskAttributeCreate(RequestAbstract $request) : array
884    {
885        $val = [];
886        if (($val['type'] = !$request->hasData('type'))
887            || ($val['value'] = (!$request->hasData('value') && !$request->hasData('custom')))
888            || ($val['task'] = !$request->hasData('task'))
889        ) {
890            return $val;
891        }
892
893        return [];
894    }
895
896    /**
897     * Api method to create task attribute l11n
898     *
899     * @param RequestAbstract  $request  Request
900     * @param ResponseAbstract $response Response
901     * @param array            $data     Generic data
902     *
903     * @return void
904     *
905     * @api
906     *
907     * @since 1.0.0
908     */
909    public function apiTaskAttributeTypeL11nCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
910    {
911        if (!empty($val = $this->validateTaskAttributeTypeL11nCreate($request))) {
912            $response->header->status = RequestStatusCode::R_400;
913            $this->createInvalidCreateResponse($request, $response, $val);
914
915            return;
916        }
917
918        $attrL11n = $this->createTaskAttributeTypeL11nFromRequest($request);
919        $this->createModel($request->header->account, $attrL11n, TaskAttributeTypeL11nMapper::class, 'attr_type_l11n', $request->getOrigin());
920        $this->createStandardCreateResponse($request, $response, $attrL11n);
921    }
922
923    /**
924     * Method to create task attribute l11n from request.
925     *
926     * @param RequestAbstract $request Request
927     *
928     * @return BaseStringL11n
929     *
930     * @since 1.0.0
931     */
932    private function createTaskAttributeTypeL11nFromRequest(RequestAbstract $request) : BaseStringL11n
933    {
934        $attrL11n      = new BaseStringL11n();
935        $attrL11n->ref = $request->getDataInt('type') ?? 0;
936        $attrL11n->setLanguage(
937            $request->getDataString('language') ?? $request->header->l11n->language
938        );
939        $attrL11n->content = $request->getDataString('title') ?? '';
940
941        return $attrL11n;
942    }
943
944    /**
945     * Validate task attribute l11n create request
946     *
947     * @param RequestAbstract $request Request
948     *
949     * @return array<string, bool>
950     *
951     * @since 1.0.0
952     */
953    private function validateTaskAttributeTypeL11nCreate(RequestAbstract $request) : array
954    {
955        $val = [];
956        if (($val['title'] = !$request->hasData('title'))
957            || ($val['type'] = !$request->hasData('type'))
958        ) {
959            return $val;
960        }
961
962        return [];
963    }
964
965    /**
966     * Api method to create task attribute type
967     *
968     * @param RequestAbstract  $request  Request
969     * @param ResponseAbstract $response Response
970     * @param array            $data     Generic data
971     *
972     * @return void
973     *
974     * @api
975     *
976     * @since 1.0.0
977     */
978    public function apiTaskAttributeTypeCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
979    {
980        if (!empty($val = $this->validateTaskAttributeTypeCreate($request))) {
981            $response->header->status = RequestStatusCode::R_400;
982            $this->createInvalidCreateResponse($request, $response, $val);
983
984            return;
985        }
986
987        $attrType = $this->createTaskAttributeTypeFromRequest($request);
988        $this->createModel($request->header->account, $attrType, TaskAttributeTypeMapper::class, 'attr_type', $request->getOrigin());
989        $this->createStandardCreateResponse($request, $response, $attrType);
990    }
991
992    /**
993     * Method to create task attribute from request.
994     *
995     * @param RequestAbstract $request Request
996     *
997     * @return TaskAttributeType
998     *
999     * @since 1.0.0
1000     */
1001    private function createTaskAttributeTypeFromRequest(RequestAbstract $request) : TaskAttributeType
1002    {
1003        $attrType = new TaskAttributeType($request->getDataString('name') ?? '');
1004        $attrType->setL11n($request->getDataString('title') ?? '', $request->getDataString('language') ?? ISO639x1Enum::_EN);
1005        $attrType->setFields($request->getDataInt('fields') ?? 0);
1006        $attrType->custom            = $request->getDataBool('custom') ?? false;
1007        $attrType->isRequired        = $request->getDataBool('is_required') ?? false;
1008        $attrType->validationPattern = $request->getDataString('validation_pattern') ?? '';
1009
1010        return $attrType;
1011    }
1012
1013    /**
1014     * Validate task attribute create request
1015     *
1016     * @param RequestAbstract $request Request
1017     *
1018     * @return array<string, bool>
1019     *
1020     * @since 1.0.0
1021     */
1022    private function validateTaskAttributeTypeCreate(RequestAbstract $request) : array
1023    {
1024        $val = [];
1025        if (($val['title'] = !$request->hasData('title'))
1026            || ($val['name'] = !$request->hasData('name'))
1027        ) {
1028            return $val;
1029        }
1030
1031        return [];
1032    }
1033
1034    /**
1035     * Api method to create task attribute value
1036     *
1037     * @param RequestAbstract  $request  Request
1038     * @param ResponseAbstract $response Response
1039     * @param array            $data     Generic data
1040     *
1041     * @return void
1042     *
1043     * @api
1044     *
1045     * @since 1.0.0
1046     */
1047    public function apiTaskAttributeValueCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
1048    {
1049        if (!empty($val = $this->validateTaskAttributeValueCreate($request))) {
1050            $response->header->status = RequestStatusCode::R_400;
1051            $this->createInvalidCreateResponse($request, $response, $val);
1052
1053            return;
1054        }
1055
1056        $attrValue = $this->createTaskAttributeValueFromRequest($request);
1057        $this->createModel($request->header->account, $attrValue, TaskAttributeValueMapper::class, 'attr_value', $request->getOrigin());
1058
1059        if ($attrValue->isDefault) {
1060            $this->createModelRelation(
1061                $request->header->account,
1062                (int) $request->getData('attributetype'),
1063                $attrValue->id,
1064                TaskAttributeTypeMapper::class, 'defaults', '', $request->getOrigin()
1065            );
1066        }
1067
1068        $this->createStandardCreateResponse($request, $response, $attrValue);
1069    }
1070
1071    /**
1072     * Method to create task attribute value from request.
1073     *
1074     * @param RequestAbstract $request Request
1075     *
1076     * @return TaskAttributeValue
1077     *
1078     * @since 1.0.0
1079     */
1080    private function createTaskAttributeValueFromRequest(RequestAbstract $request) : TaskAttributeValue
1081    {
1082        /** @var TaskAttributeType $type */
1083        $type = TaskAttributeTypeMapper::get()
1084            ->where('id', $request->getDataInt('type') ?? 0)
1085            ->execute();
1086
1087        $attrValue            = new TaskAttributeValue();
1088        $attrValue->isDefault = $request->getDataBool('default') ?? false;
1089        $attrValue->setValue($request->getDataString('value'), $type->datatype);
1090
1091        if ($request->hasData('title')) {
1092            $attrValue->setL11n(
1093                $request->getDataString('title') ?? '',
1094                $request->getDataString('language') ?? ISO639x1Enum::_EN
1095            );
1096        }
1097
1098        return $attrValue;
1099    }
1100
1101    /**
1102     * Validate task attribute value create request
1103     *
1104     * @param RequestAbstract $request Request
1105     *
1106     * @return array<string, bool>
1107     *
1108     * @since 1.0.0
1109     */
1110    private function validateTaskAttributeValueCreate(RequestAbstract $request) : array
1111    {
1112        $val = [];
1113        if (($val['attributetype'] = !$request->hasData('attributetype'))
1114            || ($val['value'] = !$request->hasData('value'))
1115        ) {
1116            return $val;
1117        }
1118
1119        return [];
1120    }
1121
1122    /**
1123     * Api method to create task attribute l11n
1124     *
1125     * @param RequestAbstract  $request  Request
1126     * @param ResponseAbstract $response Response
1127     * @param array            $data     Generic data
1128     *
1129     * @return void
1130     *
1131     * @api
1132     *
1133     * @since 1.0.0
1134     */
1135    public function apiTaskAttributeValueL11nCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : void
1136    {
1137        if (!empty($val = $this->validateTaskAttributeValueL11nCreate($request))) {
1138            $response->header->status = RequestStatusCode::R_400;
1139            $this->createInvalidCreateResponse($request, $response, $val);
1140
1141            return;
1142        }
1143
1144        $attrL11n = $this->createTaskAttributeValueL11nFromRequest($request);
1145        $this->createModel($request->header->account, $attrL11n, TaskAttributeValueL11nMapper::class, 'attr_value_l11n', $request->getOrigin());
1146        $this->createStandardCreateResponse($request, $response, $attrL11n);
1147    }
1148
1149    /**
1150     * Method to create task attribute l11n from request.
1151     *
1152     * @param RequestAbstract $request Request
1153     *
1154     * @return BaseStringL11n
1155     *
1156     * @since 1.0.0
1157     */
1158    private function createTaskAttributeValueL11nFromRequest(RequestAbstract $request) : BaseStringL11n
1159    {
1160        $attrL11n      = new BaseStringL11n();
1161        $attrL11n->ref = $request->getDataInt('value') ?? 0;
1162        $attrL11n->setLanguage(
1163            $request->getDataString('language') ?? $request->header->l11n->language
1164        );
1165        $attrL11n->content = $request->getDataString('title') ?? '';
1166
1167        return $attrL11n;
1168    }
1169
1170    /**
1171     * Validate task attribute l11n create request
1172     *
1173     * @param RequestAbstract $request Request
1174     *
1175     * @return array<string, bool>
1176     *
1177     * @since 1.0.0
1178     */
1179    private function validateTaskAttributeValueL11nCreate(RequestAbstract $request) : array
1180    {
1181        $val = [];
1182        if (($val['title'] = !$request->hasData('title'))
1183            || ($val['value'] = !$request->hasData('value'))
1184        ) {
1185            return $val;
1186        }
1187
1188        return [];
1189    }
1190}