Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaskMapper
0.00% covered (danger)
0.00%
0 / 97
0.00% covered (danger)
0.00%
0 / 11
182
0.00% covered (danger)
0.00%
0 / 1
 getOpenCreatedBy
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getResponsible
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getOpenTo
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getOpenAny
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getOpenCC
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getCreatedBy
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 getTo
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getCC
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
 getAnyRelatedToUser
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 hasReadingPermission
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
2
 countUnread
0.00% covered (danger)
0.00%
0 / 18
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\Models
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\Models;
16
17use Modules\Admin\Models\AccountMapper;
18use Modules\Calendar\Models\ScheduleMapper;
19use Modules\Media\Models\MediaMapper;
20use Modules\Tag\Models\TagMapper;
21use phpOMS\DataStorage\Database\Mapper\DataMapperFactory;
22use phpOMS\DataStorage\Database\Mapper\ReadMapper;
23use phpOMS\DataStorage\Database\Query\Builder;
24use phpOMS\DataStorage\Database\Query\Where;
25
26/**
27 * Mapper class.
28 *
29 * @package Modules\Tasks\Models
30 * @license OMS License 2.0
31 * @link    https://jingga.app
32 * @since   1.0.0
33 *
34 * @template T of Task
35 * @extends DataMapperFactory<T>
36 */
37final class TaskMapper extends DataMapperFactory
38{
39    /**
40     * Columns.
41     *
42     * @var array<string, array{name:string, type:string, internal:string, autocomplete?:bool, readonly?:bool, writeonly?:bool, annotations?:array}>
43     * @since 1.0.0
44     */
45    public const COLUMNS = [
46        'task_id'                => ['name' => 'task_id',         'type' => 'int',      'internal' => 'id'],
47        'task_title'             => ['name' => 'task_title',      'type' => 'string',   'internal' => 'title'],
48        'task_desc'              => ['name' => 'task_desc',       'type' => 'string',   'internal' => 'description'],
49        'task_desc_raw'          => ['name' => 'task_desc_raw',   'type' => 'string',   'internal' => 'descriptionRaw'],
50        'task_type'              => ['name' => 'task_type',       'type' => 'int',      'internal' => 'type'],
51        'task_status'            => ['name' => 'task_status',     'type' => 'int',      'internal' => 'status'],
52        'task_completion'        => ['name' => 'task_completion',     'type' => 'int',      'internal' => 'completion'],
53        'task_closable'          => ['name' => 'task_closable',   'type' => 'bool',     'internal' => 'isClosable'],
54        'task_editable'          => ['name' => 'task_editable',   'type' => 'bool',     'internal' => 'isEditable'],
55        'task_priority'          => ['name' => 'task_priority',   'type' => 'int',      'internal' => 'priority'],
56        'task_due'               => ['name' => 'task_due',        'type' => 'DateTime', 'internal' => 'due'],
57        'task_done'              => ['name' => 'task_done',       'type' => 'DateTime', 'internal' => 'done'],
58        'task_schedule'          => ['name' => 'task_schedule',   'type' => 'int',      'internal' => 'schedule'],
59        'task_start'             => ['name' => 'task_start',      'type' => 'DateTime', 'internal' => 'start'],
60        'task_redirect'          => ['name' => 'task_redirect',      'type' => 'string',   'internal' => 'redirect'],
61        'task_trigger'           => ['name' => 'task_trigger',      'type' => 'string',   'internal' => 'trigger'],
62        'task_created_by'        => ['name' => 'task_created_by', 'type' => 'int',      'internal' => 'createdBy', 'readonly' => true],
63        'task_created_at'        => ['name' => 'task_created_at', 'type' => 'DateTimeImmutable', 'internal' => 'createdAt', 'readonly' => true],
64    ];
65
66    /**
67     * Has many relation.
68     *
69     * @var array<string, array{mapper:class-string, table:string, self?:?string, external?:?string, column?:string}>
70     * @since 1.0.0
71     */
72    public const HAS_MANY = [
73        'taskElements' => [
74            'mapper'       => TaskElementMapper::class,
75            'table'        => 'task_element',
76            'self'         => 'task_element_task',
77            'external'     => null,
78        ],
79        'media'        => [
80            'mapper'   => MediaMapper::class,
81            'table'    => 'task_media',
82            'external' => 'task_media_dst',
83            'self'     => 'task_media_src',
84        ],
85        'tags'         => [
86            'mapper'   => TagMapper::class,
87            'table'    => 'task_tag',
88            'external' => 'task_tag_dst',
89            'self'     => 'task_tag_src',
90        ],
91        'attributes' => [
92            'mapper'   => TaskAttributeMapper::class,
93            'table'    => 'task_attr',
94            'self'     => 'task_attr_item',
95            'external' => null,
96        ],
97    ];
98
99    /**
100     * Belongs to.
101     *
102     * @var array<string, array{mapper:class-string, external:string, column?:string, by?:string}>
103     * @since 1.0.0
104     */
105    public const BELONGS_TO = [
106        'createdBy' => [
107            'mapper'     => AccountMapper::class,
108            'external'   => 'task_created_by',
109        ],
110    ];
111
112    /**
113     * Has one relation.
114     *
115     * @var array<string, array{mapper:class-string, external:string, by?:string, column?:string, conditional?:bool}>
116     * @since 1.0.0
117     */
118    public const OWNS_ONE = [
119        'schedule' => [
120            'mapper'     => ScheduleMapper::class,
121            'external'   => 'task_schedule',
122        ],
123    ];
124
125    /**
126     * Primary table.
127     *
128     * @var string
129     * @since 1.0.0
130     */
131    public const TABLE = 'task';
132
133    /**
134     * Created at.
135     *
136     * @var string
137     * @since 1.0.0
138     */
139    public const CREATED_AT = 'task_created_at';
140
141    /**
142     * Primary field name.
143     *
144     * @var string
145     * @since 1.0.0
146     */
147    public const PRIMARYFIELD = 'task_id';
148
149    /**
150     * Get open tasks by createdBy
151     *
152     * @param int $user User
153     *
154     * @return Task[]
155     *
156     * @since 1.0.0
157     */
158    public static function getOpenCreatedBy(int $user) : array
159    {
160        $query = self::getQuery();
161        $query->where(self::TABLE . '_d1.task_created_by', '=', $user)
162            ->where(self::TABLE . '_d1.task_status', '=', TaskStatus::OPEN);
163
164        return self::getAll()->execute($query);
165    }
166
167    /**
168     * Get responsible users for task
169     *
170     * @param int $task Task
171     *
172     * @return AccountRelation[]
173     *
174     * @since 1.0.0
175     */
176    public static function getResponsible(int $task) : array
177    {
178        $query = AccountRelationMapper::getQuery();
179        $query->innerJoin(TaskElementMapper::TABLE)
180                ->on(AccountRelationMapper::TABLE . '_d1.task_account_task_element', '=', TaskElementMapper::TABLE . '.task_element_task')
181            ->innerJoin(self::TABLE)
182                ->on(TaskElementMapper::TABLE . '_d1.task_element_task', '=', self::TABLE . '.task_id')
183            ->where(self::TABLE . '.task_id', '=', $task);
184
185        return AccountRelationMapper::getAll()
186            ->execute($query);
187    }
188
189    /**
190     * Get open tasks for user
191     *
192     * @param int $user User
193     *
194     * @return Task[]
195     *
196     * @since 1.0.0
197     */
198    public static function getOpenTo(int $user) : array
199    {
200        $query = self::getQuery();
201        $query->innerJoin(TaskElementMapper::TABLE)
202                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
203            ->innerJoin(AccountRelationMapper::TABLE)
204                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
205            ->where(self::TABLE . '_d1.task_status', '=', TaskStatus::OPEN)
206            ->andWhere(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
207            ->andWhere(AccountRelationMapper::TABLE . '.task_account_duty', '=', DutyType::TO);
208
209        return self::getAll()->execute($query);
210    }
211
212    /**
213     * Get open tasks for mentioned user
214     *
215     * @param int $user User
216     *
217     * @return Task[]
218     *
219     * @since 1.0.0
220     */
221    public static function getOpenAny(int $user) : array
222    {
223        $query = self::getQuery();
224        $query->innerJoin(TaskElementMapper::TABLE)
225                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
226            ->innerJoin(AccountRelationMapper::TABLE)
227                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
228            ->where(self::TABLE . '_d1.task_status', '=', TaskStatus::OPEN)
229            ->andWhere(AccountRelationMapper::TABLE . '.task_account_account', '=', $user);
230
231        return self::getAll()->execute($query);
232    }
233
234    /**
235     * Get open tasks by cc
236     *
237     * @param int $user User
238     *
239     * @return Task[]
240     *
241     * @since 1.0.0
242     */
243    public static function getOpenCC(int $user) : array
244    {
245        $query = self::getQuery();
246        $query->innerJoin(TaskElementMapper::TABLE)
247                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
248            ->innerJoin(AccountRelationMapper::TABLE)
249                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
250            ->where(self::TABLE . '_d1.task_status', '=', TaskStatus::OPEN)
251            ->andWhere(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
252            ->andWhere(AccountRelationMapper::TABLE . '.task_account_duty', '=', DutyType::CC);
253
254        return self::getAll()->execute($query);
255    }
256
257    /**
258     * Get tasks created by user
259     *
260     * @param int $user User
261     *
262     * @return Task[]
263     *
264     * @since 1.0.0
265     */
266    public static function getCreatedBy(int $user) : array
267    {
268        $query = self::getQuery();
269        $query->where(self::TABLE . '_d1.task_created_by', '=', $user);
270
271        return self::getAll()->execute($query);
272    }
273
274    /**
275     * Get tasks sent to user
276     *
277     * @param int $user User
278     *
279     * @return Task[]
280     *
281     * @since 1.0.0
282     */
283    public static function getTo(int $user) : array
284    {
285        $query = self::getQuery();
286        $query->innerJoin(TaskElementMapper::TABLE)
287                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
288            ->innerJoin(AccountRelationMapper::TABLE)
289                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
290            ->where(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
291            ->andWhere(AccountRelationMapper::TABLE . '.task_account_duty', '=', DutyType::TO);
292
293        return self::getAll()->execute($query);
294    }
295
296    /**
297     * Get tasks cc to user
298     *
299     * @param int $user User
300     *
301     * @return Task[]
302     *
303     * @since 1.0.0
304     */
305    public static function getCC(int $user) : array
306    {
307        $query = self::getQuery();
308        $query->innerJoin(TaskElementMapper::TABLE)
309                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
310            ->innerJoin(AccountRelationMapper::TABLE)
311                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
312            ->where(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
313            ->andWhere(AccountRelationMapper::TABLE . '.task_account_duty', '=', DutyType::CC);
314
315        return self::getAll()->execute($query);
316    }
317
318    /**
319     * Get tasks that have something to do with the user
320     *
321     * @param int $user User
322     *
323     * @return ReadMapper
324     *
325     * @since 1.0.0
326     */
327    public static function getAnyRelatedToUser(int $user) : ReadMapper
328    {
329        $query = new Builder(self::$db, true);
330        $query->innerJoin(TaskElementMapper::TABLE)
331                ->on(self::TABLE . '_d1.task_id', '=', TaskElementMapper::TABLE . '.task_element_task')
332            ->innerJoin(AccountRelationMapper::TABLE)
333                ->on(TaskElementMapper::TABLE . '.task_element_id', '=', AccountRelationMapper::TABLE . '.task_account_task_element')
334            ->where(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
335            ->orWhere(self::TABLE . '_d1.task_created_by', '=', $user)
336            ->groupBy(self::PRIMARYFIELD);
337
338        return self::getAll()->query($query);
339    }
340
341    /**
342     * Check if a user has reading permission for a task
343     *
344     * @param int $user User id
345     * @param int $task Task id
346     *
347     * @return bool
348     *
349     * @since 1.0.0
350     */
351    public static function hasReadingPermission(int $user, int $task) : bool
352    {
353        $userWhere = new Where(self::$db);
354        $userWhere->where(AccountRelationMapper::TABLE . '.task_account_account', '=', $user)
355            ->orWhere(self::TABLE . '_d1.task_created_by', '=', $user);
356
357        $query = new Builder(self::$db);
358        $query->selectAs(self::TABLE . '_d1.' . self::PRIMARYFIELD, self::PRIMARYFIELD . '_d1')
359            ->fromAs(self::TABLE, self::TABLE . '_d1')
360            ->innerJoin(TaskElementMapper::TABLE)
361                ->on(self::TABLE . '_d1.' . self::PRIMARYFIELD, '=', TaskElementMapper::TABLE . '.task_element_task')
362            ->innerJoin(AccountRelationMapper::TABLE)
363                ->on(TaskElementMapper::TABLE . '.' . TaskElementMapper::PRIMARYFIELD, '=', AccountRelationMapper::TABLE . '.task_account_task_element')
364            ->where($userWhere)
365            ->andWhere(self::TABLE . '_d1.' . self::PRIMARYFIELD, '=', $task);
366
367        return !empty($query->execute()?->fetchAll());
368    }
369
370    /**
371     * Count unread task
372     *
373     * @param int $user User
374     *
375     * @return int
376     *
377     * @since 1.0.0
378     */
379    public static function countUnread(int $user) : int
380    {
381        try {
382            $query = new Builder(self::$db);
383
384            $query->count('DISTINCT ' . self::TABLE . '.' . self::PRIMARYFIELD)
385                ->from(self::TABLE)
386                ->innerJoin(TaskElementMapper::TABLE)
387                    ->on(self::TABLE . '.' . self::PRIMARYFIELD, '=', TaskElementMapper::TABLE . '.task_element_task')
388                ->innerJoin(AccountRelationMapper::TABLE)
389                    ->on(TaskElementMapper::TABLE . '.' . TaskElementMapper::PRIMARYFIELD, '=', AccountRelationMapper::TABLE . '.task_account_task_element')
390                ->where(self::TABLE . '.task_status', '=', TaskStatus::OPEN)
391                ->andWhere(AccountRelationMapper::TABLE . '.task_account_account', '=', $user);
392
393            $sth = self::$db->con->prepare($query->toSql());
394            $sth->execute();
395
396            $fetched = $sth->fetchAll();
397
398            if ($fetched === false) {
399                return -1;
400            }
401
402            $count = $fetched[0][0] ?? 0;
403        } catch (\Exception $_) {
404            return -1;
405        }
406
407        return $count;
408    }
409}