Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
63.41% covered (warning)
63.41%
52 / 82
53.57% covered (warning)
53.57%
15 / 28
CRAP
0.00% covered (danger)
0.00%
0 / 1
TaskLogFilter
63.41% covered (warning)
63.41%
52 / 82
53.57% covered (warning)
53.57%
15 / 28
123.32
0.00% covered (danger)
0.00%
0 / 1
 getColumns
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 deselect
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getSortBy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSortBy
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getSortOrder
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setSortOrder
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLimit
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getOffset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setOffset
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getFilters
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 addFilter
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 addAvailableFilters
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
5.03
 availableForArchived
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 availableForCancelled
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 applyFilters
20.00% covered (danger)
20.00%
2 / 10
0.00% covered (danger)
0.00%
0 / 1
24.43
 eq
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 neq
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 lte
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 gt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 gte
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 like
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 notLike
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 in
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 notIn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 withIgnoredTasks
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 assertValidOperator
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2017-2021 (original work) Open Assessment Technologies SA (under the project TAO-PRODUCT);
19 *
20 */
21
22namespace oat\tao\model\taskQueue\TaskLog;
23
24use Doctrine\DBAL\Connection;
25use Doctrine\DBAL\Query\QueryBuilder;
26use InvalidArgumentException;
27use oat\tao\model\taskQueue\TaskLog\Broker\TaskLogBrokerInterface;
28use oat\tao\model\taskQueue\TaskLogInterface;
29
30class TaskLogFilter
31{
32    private const OP_EQ  = '=';
33    private const OP_NEQ = '!=';
34    private const OP_LT  = '<';
35    private const OP_LTE = '<=';
36    private const OP_GT  = '>';
37    private const OP_GTE = '>=';
38    private const OP_LIKE = 'LIKE';
39    private const OP_NOT_LIKE = 'NOT LIKE';
40    private const OP_IN = 'IN';
41    private const OP_NOT_IN = 'NOT IN';
42
43    private $filters = [];
44    private $limit;
45    private $offset;
46    private $sortBy;
47    private $sortOrder;
48
49    private $baseColumns = [
50        TaskLogBrokerInterface::COLUMN_ID,
51        TaskLogBrokerInterface::COLUMN_PARENT_ID,
52        TaskLogBrokerInterface::COLUMN_TASK_NAME,
53        TaskLogBrokerInterface::COLUMN_STATUS,
54        TaskLogBrokerInterface::COLUMN_MASTER_STATUS,
55        TaskLogBrokerInterface::COLUMN_REPORT
56    ];
57
58    private $optionalColumns = [
59        TaskLogBrokerInterface::COLUMN_PARAMETERS,
60        TaskLogBrokerInterface::COLUMN_LABEL,
61        TaskLogBrokerInterface::COLUMN_OWNER,
62        TaskLogBrokerInterface::COLUMN_CREATED_AT,
63        TaskLogBrokerInterface::COLUMN_UPDATED_AT
64    ];
65
66    private $deselectedColumns = [];
67
68    private $ignoredTasks = [];
69
70    public function getColumns(): array
71    {
72        return array_merge($this->baseColumns, array_diff($this->optionalColumns, $this->deselectedColumns));
73    }
74
75    public function deselect(string $column): self
76    {
77        if (!in_array($column, $this->optionalColumns)) {
78            throw new InvalidArgumentException('Column "' . $column . '"" is not valid column or not unselectable.');
79        }
80
81        $this->deselectedColumns[] = $column;
82
83        return $this;
84    }
85
86    public function getSortBy()
87    {
88        return $this->sortBy;
89    }
90
91    public function setSortBy($sortBy): self
92    {
93        $this->sortBy = $sortBy;
94
95        return $this;
96    }
97
98    public function getSortOrder()
99    {
100        return $this->sortOrder;
101    }
102
103    public function setSortOrder($sortOrder): self
104    {
105        $this->sortOrder = $sortOrder;
106
107        return $this;
108    }
109
110    public function getLimit(): ?int
111    {
112        return $this->limit;
113    }
114
115    public function setLimit(int $limit): self
116    {
117        $this->limit = max(0, $limit);
118
119        return $this;
120    }
121
122    public function getOffset(): ?int
123    {
124        return $this->offset;
125    }
126
127    public function setOffset(int $offset): self
128    {
129        $this->offset = max(0, $offset);
130
131        return $this;
132    }
133
134    public function getFilters(): array
135    {
136        return $this->filters;
137    }
138
139    public function addFilter(string $field, string $operator, $value): self
140    {
141        $this->assertValidOperator($operator);
142
143        $this->filters[] =  [
144            'column' => $field,
145            'columnSqlTranslate' => ':' . $field . uniqid(), // we need a unique placeholder
146            'operator' => $operator,
147            'value' => $value
148        ];
149
150        return $this;
151    }
152
153    /**
154     * Add a basic filter to query only rows belonging to a given user and not having status ARCHIVED or CANCELLED.
155     */
156    public function addAvailableFilters(
157        string $userId,
158        bool $archivedAllowed = false,
159        bool $cancelledAvailable = false
160    ): self {
161        if (!$archivedAllowed) {
162            $this->neq(TaskLogBrokerInterface::COLUMN_STATUS, TaskLogInterface::STATUS_ARCHIVED);
163        }
164
165        if (!$cancelledAvailable) {
166            $this->neq(TaskLogBrokerInterface::COLUMN_STATUS, TaskLogInterface::STATUS_CANCELLED);
167        }
168
169        if ($userId !== TaskLogInterface::SUPER_USER) {
170            $this->eq(TaskLogBrokerInterface::COLUMN_OWNER, $userId);
171        }
172
173        if ($this->ignoredTasks) {
174            $this->notIn(TaskLogBrokerInterface::COLUMN_TASK_NAME, $this->ignoredTasks);
175        }
176
177        return $this;
178    }
179
180    public function availableForArchived(string $userId): self
181    {
182        $this->in(
183            TaskLogBrokerInterface::COLUMN_STATUS,
184            [TaskLogInterface::STATUS_FAILED, TaskLogInterface::STATUS_COMPLETED]
185        );
186
187        if ($userId !== TaskLogInterface::SUPER_USER) {
188            $this->eq(TaskLogBrokerInterface::COLUMN_OWNER, $userId);
189        }
190
191        return $this;
192    }
193
194    public function availableForCancelled(string $userId): self
195    {
196        $this->eq(TaskLogBrokerInterface::COLUMN_STATUS, TaskLogInterface::STATUS_ENQUEUED);
197
198        if ($userId !== TaskLogInterface::SUPER_USER) {
199            $this->eq(TaskLogBrokerInterface::COLUMN_OWNER, $userId);
200        }
201
202        return $this;
203    }
204
205    public function applyFilters(QueryBuilder $qb): QueryBuilder
206    {
207        foreach ($this->getFilters() as $filter) {
208            $withParentheses = is_array($filter['value']) ? true : false;
209            $type = is_array($filter['value']) ? Connection::PARAM_STR_ARRAY : null;
210
211            $qb
212                ->andWhere(
213                    $filter['column'] . ' ' . $filter['operator'] . ' ' . ($withParentheses ? '(' : '')
214                        . $filter['columnSqlTranslate'] . ($withParentheses ? ')' : '')
215                )
216                ->setParameter($filter['columnSqlTranslate'], $filter['value'], $type);
217        }
218
219        return $qb;
220    }
221
222    public function eq(string $field, $value): self
223    {
224        return $this->addFilter($field, self::OP_EQ, $value);
225    }
226
227    public function neq(string $field, $value): self
228    {
229        return $this->addFilter($field, self::OP_NEQ, $value);
230    }
231
232    public function lt(string $field, $value): self
233    {
234        return $this->addFilter($field, self::OP_LT, $value);
235    }
236
237    public function lte(string $field, $value): self
238    {
239        return $this->addFilter($field, self::OP_LTE, $value);
240    }
241
242    public function gt(string $field, $value): self
243    {
244        return $this->addFilter($field, self::OP_GT, $value);
245    }
246
247    public function gte(string $field, $value): self
248    {
249        return $this->addFilter($field, self::OP_GTE, $value);
250    }
251
252    public function like(string $field, $value): self
253    {
254        return $this->addFilter($field, self::OP_LIKE, $value);
255    }
256
257    public function notLike(string $field, string $value): self
258    {
259        return $this->addFilter($field, self::OP_NOT_LIKE, $value);
260    }
261
262    public function in(string $field, array $value): self
263    {
264        return $this->addFilter($field, self::OP_IN, $value);
265    }
266
267    public function notIn(string $field, array $value): self
268    {
269        return $this->addFilter($field, self::OP_NOT_IN, $value);
270    }
271
272    public function withIgnoredTasks(array $ignoredTasks): self
273    {
274        $this->ignoredTasks = $ignoredTasks;
275        return $this;
276    }
277
278    /**
279     * @throws InvalidArgumentException
280     */
281    private function assertValidOperator($op): void
282    {
283        $operators = [
284            self::OP_EQ,
285            self::OP_NEQ,
286            self::OP_LT,
287            self::OP_LTE,
288            self::OP_GT,
289            self::OP_GTE,
290            self::OP_LIKE,
291            self::OP_NOT_LIKE,
292            self::OP_IN,
293            self::OP_NOT_IN,
294        ];
295
296        if (!in_array($op, $operators)) {
297            throw new InvalidArgumentException('Operator "' . $op . '"" is not a valid operator.');
298        }
299    }
300}