Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
BackendController
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
756
0.00% covered (danger)
0.00%
0 / 1
 viewTaskDashboard
n/a
0 / 0
n/a
0 / 0
10
 viewDashboard
n/a
0 / 0
n/a
0 / 0
1
 viewTaskView
n/a
0 / 0
n/a
0 / 0
13
 viewTaskCreate
n/a
0 / 0
n/a
0 / 0
1
 viewTaskAnalysis
n/a
0 / 0
n/a
0 / 0
1
 openNav
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
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\Dashboard\Models\DashboardElementInterface;
18use Modules\Media\Models\MediaMapper;
19use Modules\Profile\Models\SettingsEnum;
20use Modules\Tasks\Models\AccountRelationMapper;
21use Modules\Tasks\Models\PermissionCategory;
22use Modules\Tasks\Models\TaskElementMapper;
23use Modules\Tasks\Models\TaskMapper;
24use Modules\Tasks\Models\TaskSeen;
25use Modules\Tasks\Models\TaskSeenMapper;
26use Modules\Tasks\Models\TaskStatus;
27use Modules\Tasks\Views\TaskView;
28use phpOMS\Account\PermissionType;
29use phpOMS\Asset\AssetType;
30use phpOMS\Contract\RenderableInterface;
31use phpOMS\DataStorage\Database\Query\Builder;
32use phpOMS\DataStorage\Database\Query\OrderType;
33use phpOMS\Message\Http\RequestStatusCode;
34use phpOMS\Message\RequestAbstract;
35use phpOMS\Message\ResponseAbstract;
36use phpOMS\Views\View;
37
38/**
39 * Backend controller for the tasks module.
40 *
41 * @property \Web\WebApplication $app
42 *
43 * @package Modules\Tasks
44 * @license OMS License 2.0
45 * @link    https://jingga.app
46 * @since   1.0.0
47 */
48final class BackendController extends Controller implements DashboardElementInterface
49{
50    /**
51     * Routing end-point for application behaviour.
52     *
53     * @param RequestAbstract  $request  Request
54     * @param ResponseAbstract $response Response
55     * @param array            $data     Generic data
56     *
57     * @return RenderableInterface Returns a renderable object
58     *
59     * @since 1.0.0
60     * @codeCoverageIgnore
61     */
62    public function viewTaskDashboard(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
63    {
64        $view = new View($this->app->l11nManager, $request, $response);
65
66        /** @var \phpOMS\Model\Html\Head $head */
67        $head = $response->data['Content']->head;
68        $head->addAsset(AssetType::CSS, 'Modules/Tasks/Theme/Backend/css/styles.css?v=1.0.0');
69
70        $view->setTemplate('/Modules/Tasks/Theme/Backend/task-dashboard');
71        $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1001101001, $request, $response);
72
73        $mapperQuery = TaskMapper::getAnyRelatedToUser($request->header->account)
74            ->with('tags')
75            ->with('tags/title')
76            ->where('status', TaskStatus::OPEN, '!=')
77            ->where('tags/title/language', $response->header->l11n->language)
78            ->sort('createdAt', OrderType::DESC)
79            ->limit(25);
80
81        if ($request->getData('ptype') === 'p') {
82            $view->data['tasks'] = $mapperQuery->with('createdBy')
83                ->where('id', $request->getDataInt('id') ?? 0, '<')
84                ->execute();
85        } elseif ($request->getData('ptype') === 'n') {
86            $view->data['tasks'] = $mapperQuery->with('createdBy')
87                ->where('id', $request->getDataInt('id') ?? 0, '>')
88                ->execute();
89        } else {
90            $view->data['tasks'] = $mapperQuery->with('createdBy')
91                ->where('id', 0, '>')
92                ->execute();
93        }
94
95        $view->data['task_media'] = [];
96        foreach ($view->data['tasks'] as $task) {
97            $view->data['task_media'][$task->id] = TaskMapper::has()
98                ->with('media')
99                ->where('id', $task->id)
100                ->execute();
101        }
102
103        $openQuery = new Builder($this->app->dbPool->get(), true);
104        $openQuery->innerJoin(TaskElementMapper::TABLE)
105                ->on(TaskMapper::TABLE . '_d1.' . TaskMapper::PRIMARYFIELD, '=', TaskElementMapper::TABLE . '.task_element_task')
106            ->innerJoin(AccountRelationMapper::TABLE)
107                ->on(TaskElementMapper::TABLE . '.' . TaskElementMapper::PRIMARYFIELD, '=', AccountRelationMapper::TABLE . '.task_account_task_element')
108            ->andWhere(AccountRelationMapper::TABLE . '.task_account_account', '=', $request->header->account);
109
110        /** @var \Modules\Tasks\Models\Task[] $open */
111        $open = TaskMapper::getAll()
112            ->with('createdBy')
113            ->with('tags')
114            ->with('tags/title')
115            ->where('tags/title/language', $response->header->l11n->language)
116            ->where('status', TaskStatus::OPEN)
117            ->sort('createdAt', OrderType::DESC)
118            ->query($openQuery)
119            ->execute();
120
121        $view->data['open'] = $open;
122
123        foreach ($view->data['open'] as $task) {
124            $view->data['task_media'][$task->id] = TaskMapper::has()
125                ->with('media')
126                ->where('id', $task->id)
127                ->execute();
128        }
129
130        // given
131        // @todo: this should also include forwarded tasks
132        /** @var \Modules\Tasks\Models\Task[] $given */
133        $given = TaskMapper::getAll()
134            ->with('createdBy')
135            ->with('tags')
136            ->with('tags/title')
137            ->where('tags/title/language', $response->header->l11n->language)
138            ->where('status', TaskStatus::OPEN)
139            ->where('createdBy', $response->header->account, '=')
140            ->sort('createdAt', OrderType::DESC)
141            ->execute();
142
143        $view->data['given'] = $given;
144
145        foreach ($view->data['given'] as $task) {
146            $view->data['task_media'][$task->id] = TaskMapper::has()
147                ->with('media')
148                ->where('id', $task->id)
149                ->execute();
150        }
151
152        /** @var \Modules\Tasks\Models\TaskSeen[] $unread */
153        $unread = TaskSeenMapper::getAll()
154            ->where('task', \array_keys($open), 'in')
155            ->where('seenBy', $request->header->account)
156            ->execute();
157
158        $unseen = [];
159        foreach ($unread as $read) {
160            if ($read->isRemindered && ($read->reminderAt?->getTimestamp() ?? 0) > $request->header->getRequestTime()) {
161                continue;
162            }
163
164            $unseen[$read->task] = true;
165        }
166
167        $view->data['unread'] = $unseen;
168
169        foreach ($view->data['unread'] as $task) {
170            $view->data['task_media'][$task->id] = TaskMapper::has()
171                ->with('media')
172                ->where('id', $task->id)
173                ->execute();
174        }
175
176        return $view;
177    }
178
179    /**
180     * {@inheritdoc}
181     * @codeCoverageIgnore
182     */
183    public function viewDashboard(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
184    {
185        /** @var \phpOMS\Model\Html\Head $head */
186        $head = $response->data['Content']->head;
187        $head->addAsset(AssetType::CSS, 'Modules/Tasks/Theme/Backend/css/styles.css?v=1.0.0');
188
189        $view = new View($this->app->l11nManager, $request, $response);
190        $view->setTemplate('/Modules/Tasks/Theme/Backend/dashboard-task');
191        $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1001101001, $request, $response);
192
193        /** @var \Modules\Tasks\Models\Task[] $tasks */
194        $tasks = TaskMapper::getAnyRelatedToUser($request->header->account)
195            ->with('tags')
196            ->with('tags/title')
197            ->sort('taskElements/createdAt', OrderType::DESC)
198            ->limit(5)
199            ->where('id', 0, '>')
200            ->where('tags/title/language', $response->header->l11n->language)
201            ->execute();
202
203        $view->data['tasks'] = $tasks;
204
205        return $view;
206    }
207
208    /**
209     * Routing end-point for application behaviour.
210     *
211     * @param RequestAbstract  $request  Request
212     * @param ResponseAbstract $response Response
213     * @param array            $data     Generic data
214     *
215     * @return RenderableInterface Returns a renderable object
216     *
217     * @since 1.0.0
218     * @codeCoverageIgnore
219     */
220    public function viewTaskView(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
221    {
222        $view = new TaskView($this->app->l11nManager, $request, $response);
223
224        /** @var \Model\Setting $profileImage */
225        $profileImage = $this->app->appSettings->get(names: SettingsEnum::DEFAULT_PROFILE_IMAGE, module: 'Profile');
226
227        /** @var \Modules\Media\Models\Media $image */
228        $image                     = MediaMapper::get()->where('id', (int) $profileImage->content)->execute();
229        $view->defaultProfileImage = $image;
230
231        if (!TaskMapper::hasReadingPermission($request->header->account, (int) $request->getData('id'))) {
232            $response->header->status = RequestStatusCode::R_403;
233            $view->setTemplate('/Web/Backend/Error/403');
234
235            $this->app->loadLanguageFromPath(
236                $response->header->l11n->language,
237                __DIR__ . '/../../../Web/Backend/Error/lang/' . $response->header->l11n->language . '.lang.php'
238            );
239
240            return $view;
241        }
242
243        /** @var \phpOMS\Model\Html\Head $head */
244        $head = $response->data['Content']->head;
245        $head->addAsset(AssetType::CSS, 'Modules/Tasks/Theme/Backend/css/styles.css?v=1.0.0');
246
247        /** @var \Modules\Tasks\Models\Task $task */
248        $task = TaskMapper::get()
249            ->with('createdBy')
250            ->with('media')
251            ->with('tags')
252            ->with('tags/title')
253            ->with('taskElements')
254            ->with('taskElements/createdBy')
255            ->with('taskElements/media')
256            ->with('taskElements/accRelation')
257            ->with('taskElements/accRelation/relation')
258            ->where('id', (int) $request->getData('id'))
259            ->where('tags/title/language', $request->header->l11n->language)
260            ->execute();
261
262        $accountId = $request->header->account;
263
264        if (!($task->createdBy->id === $accountId
265            || $task->isCCAccount($accountId)
266            || $task->isToAccount($accountId))
267            && !$this->app->accountManager->get($accountId)->hasPermission(
268                PermissionType::READ, $this->app->unitId, $this->app->appId, self::NAME, PermissionCategory::TASK, $task->id)
269        ) {
270            $view->setTemplate('/Web/Backend/Error/403_inline');
271            $response->header->status = RequestStatusCode::R_403;
272            return $view;
273        }
274
275        $reminderStatus = [];
276
277        // Set task as seen
278        if ($task->id !== 0) {
279            /** @var \Modules\Tasks\Models\TaskSeen[] $taskSeen */
280            $taskSeen = TaskSeenMapper::getAll()
281                ->with('reminderBy')
282                ->where('task', $task->id)
283                ->where('seenBy', $request->header->account)
284                ->execute();
285
286            foreach ($taskSeen as $unseen) {
287                // Shows all reminders
288                if ($unseen->reminderBy !== null
289                    && ($unseen->reminderAt?->getTimestamp() ?? 0) < $request->header->getRequestTime()
290                    && ($unseen->reminderAt?->getTimestamp() ?? 0) > ($unseen->seenAt?->getTimestamp() ?? 0) - 300
291                ) {
292                    $reminderStatus[] = $unseen;
293
294                    if ($unseen->isRemindered) {
295                        $new               = clone $unseen;
296                        $new->seenAt       = new \DateTime('now');
297                        $new->isRemindered = false;
298
299                        $this->updateModel($request->header->account, $unseen, $new, TaskSeenMapper::class, 'reminder_seen', $request->getOrigin());
300                    }
301                }
302            }
303
304            if (empty($taskSeen)) {
305                $taskSeen         = new TaskSeen();
306                $taskSeen->seenBy = $request->header->account;
307                $taskSeen->task   = (int) $request->getData('id');
308
309                $this->createModel($request->header->account, $taskSeen, TaskSeenMapper::class, 'task_seen', $request->getOrigin());
310            }
311        }
312
313        $view->data['reminder'] = $reminderStatus;
314
315        $view->setTemplate('/Modules/Tasks/Theme/Backend/task-single');
316        $view->data['task'] = $task;
317        $view->data['nav']  = $this->app->moduleManager->get('Navigation')->createNavigationMid(1001101001, $request, $response);
318
319        $accGrpSelector               = new \Modules\Profile\Theme\Backend\Components\AccountGroupSelector\BaseView($this->app->l11nManager, $request, $response);
320        $view->data['accGrpSelector'] = $accGrpSelector;
321
322        $editor               = new \Modules\Editor\Theme\Backend\Components\Editor\BaseView($this->app->l11nManager, $request, $response);
323        $view->data['editor'] = $editor;
324
325        return $view;
326    }
327
328    /**
329     * Routing end-point for application behaviour.
330     *
331     * @param RequestAbstract  $request  Request
332     * @param ResponseAbstract $response Response
333     * @param array            $data     Generic data
334     *
335     * @return RenderableInterface Returns a renderable object
336     *
337     * @since 1.0.0
338     * @codeCoverageIgnore
339     */
340    public function viewTaskCreate(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
341    {
342        $view = new View($this->app->l11nManager, $request, $response);
343
344        $view->setTemplate('/Modules/Tasks/Theme/Backend/task-create');
345        $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1001101001, $request, $response);
346
347        $accGrpSelector               = new \Modules\Profile\Theme\Backend\Components\AccountGroupSelector\BaseView($this->app->l11nManager, $request, $response);
348        $view->data['accGrpSelector'] = $accGrpSelector;
349
350        $editor               = new \Modules\Editor\Theme\Backend\Components\Editor\BaseView($this->app->l11nManager, $request, $response);
351        $view->data['editor'] = $editor;
352
353        return $view;
354    }
355
356    /**
357     * Routing end-point for application behaviour.
358     *
359     * @param RequestAbstract  $request  Request
360     * @param ResponseAbstract $response Response
361     * @param array            $data     Generic data
362     *
363     * @return RenderableInterface Returns a renderable object
364     *
365     * @since 1.0.0
366     * @codeCoverageIgnore
367     */
368    public function viewTaskAnalysis(RequestAbstract $request, ResponseAbstract $response, array $data = []) : RenderableInterface
369    {
370        $view = new View($this->app->l11nManager, $request, $response);
371        $view->setTemplate('/Modules/Tasks/Theme/Backend/task-analysis');
372        $view->data['nav'] = $this->app->moduleManager->get('Navigation')->createNavigationMid(1001101001, $request, $response);
373
374        return $view;
375    }
376
377    /**
378     * Count unread messages
379     *
380     * @param int $account Account id
381     *
382     * @return int Returns the amount of unread tasks
383     *
384     * @since 1.0.0
385     */
386    public function openNav(int $account) : int
387    {
388        return TaskMapper::countUnread($account);
389    }
390}