Passed
Push — hypernext ( 1bd761...99b617 )
by Nico
14:39
created

FieldValidatorVisitor::visitMetadataField()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
1
<?php declare(strict_types=1);
2
/**
3
 * @author Nicolas CARPi <[email protected]>
4
 * @author Marcel Bolten <[email protected]>
5
 * @copyright 2022 Nicolas CARPi
6
 * @see https://www.elabftw.net Official website
7
 * @license AGPL-3.0
8
 * @package elabftw
9
 */
10
11
namespace Elabftw\Services\AdvancedSearchQuery\Visitors;
12
13
use Elabftw\Services\AdvancedSearchQuery\Collectors\InvalidFieldCollector;
14
use Elabftw\Services\AdvancedSearchQuery\Enums\Fields;
15
use Elabftw\Services\AdvancedSearchQuery\Enums\TimestampFields;
16
use Elabftw\Services\AdvancedSearchQuery\Grammar\AndExpression;
17
use Elabftw\Services\AdvancedSearchQuery\Grammar\AndOperand;
18
use Elabftw\Services\AdvancedSearchQuery\Grammar\DateField;
19
use Elabftw\Services\AdvancedSearchQuery\Grammar\Field;
20
use Elabftw\Services\AdvancedSearchQuery\Grammar\MetadataField;
21
use Elabftw\Services\AdvancedSearchQuery\Grammar\NotExpression;
22
use Elabftw\Services\AdvancedSearchQuery\Grammar\OrExpression;
23
use Elabftw\Services\AdvancedSearchQuery\Grammar\OrOperand;
24
use Elabftw\Services\AdvancedSearchQuery\Grammar\SimpleValueWrapper;
25
use Elabftw\Services\AdvancedSearchQuery\Grammar\TimestampField;
26
use Elabftw\Services\AdvancedSearchQuery\Interfaces\Visitable;
27
use Elabftw\Services\AdvancedSearchQuery\Interfaces\Visitor;
28
use function sprintf;
29
use function ucfirst;
30
31
/** @psalm-suppress UnusedParam */
32
class FieldValidatorVisitor implements Visitor
33
{
34 3
    public function check(Visitable $parsedQuery, VisitorParameters $parameters): array
35
    {
36 3
        return $parsedQuery->accept($this, $parameters)->getfieldErrors();
37
    }
38
39 1
    public function visitSimpleValueWrapper(SimpleValueWrapper $simpleValueWrapper, VisitorParameters $parameters): InvalidFieldCollector
40
    {
41 1
        return new InvalidFieldCollector();
42
    }
43
44 2
    public function visitMetadataField(MetadataField $metadataField, VisitorParameters $parameters): InvalidFieldCollector
45
    {
46 2
        return new InvalidFieldCollector();
47
    }
48
49 2
    public function visitDateField(DateField $dateField, VisitorParameters $parameters): InvalidFieldCollector
50
    {
51 2
        if ($dateField->getDateType() === 'range' && $dateField->getValue() > $dateField->getDateTo()) {
52 1
            $message = sprintf(
53 1
                'date:%s..%s. Second date needs to be equal or greater than first date.',
54 1
                $dateField->getValue(),
55 1
                $dateField->getDateTo(),
56 1
            );
57 1
            return new InvalidFieldCollector(array($message));
58
        }
59
60 1
        return new InvalidFieldCollector();
61
    }
62
63 2
    public function visitTimestampField(TimestampField $timestampField, VisitorParameters $parameters): InvalidFieldCollector
64
    {
65 2
        $messages = array();
66
67 2
        if ($parameters->getEntityType() !== 'experiments'
68 2
            && $timestampField->getFieldType() === TimestampFields::TimestampedAt
69
        ) {
70 1
            $messages[] = sprintf(
71 1
                '%s: is only allowed when searching in experiments.',
72 1
                TimestampFields::TimestampedAt->value,
73 1
            );
74
        }
75
76
        // MySQL range for TIMESTAMP values is '1970-01-01 00:00:01.000000' to '2038-01-19 03:14:07.999999'
77
        // We use 1970-01-02 and 2038-01-18 because time 00:00:00 and/or 23:59:59 will be added
78 2
        $DateMin = 19700102;
79 2
        $DateMax = 20380118;
80
81 2
        if ((intval($timestampField->getValue(), 10) < $DateMin) || (intval($timestampField->getValue(), 10) > $DateMax)
82 2
            || ($timestampField->getDateType() === 'range' && ((intval($timestampField->getDateTo(), 10) < $DateMin) || (intval($timestampField->getDateTo(), 10) > $DateMax)))
83
        ) {
84 1
            $messages[] = sprintf(
85 1
                '%s: Date needs to be between 1970-01-02 and 2038-01-18.',
86 1
                $timestampField->getFieldType()->value,
87 1
            );
88
        }
89
90 2
        if ($timestampField->getDateType() === 'range' && $timestampField->getValue() > $timestampField->getDateTo()) {
91 1
            $messages[] = sprintf(
92 1
                '%s:%s..%s. Second date needs to be equal or greater than first date.',
93 1
                $timestampField->getFieldType()->value,
94 1
                $timestampField->getValue(),
95 1
                $timestampField->getDateTo(),
96 1
            );
97
        }
98
99 2
        return new InvalidFieldCollector($messages);
100
    }
101
102 2
    public function visitField(Field $field, VisitorParameters $parameters): InvalidFieldCollector
103
    {
104
        // only add the class methods that are actually used
105 2
        if ($field->getFieldType() === Fields::Visibility
106 2
            || $field->getFieldType() === Fields::Timestamped
107 2
            || $field->getFieldType() === Fields::Group
108
        ) {
109
            // Call class methods dynamically to avoid many if statements.
110
            // This works because the parser and the Fields enum define the list of fields.
111 2
            $method = 'visitField' . ucfirst($field->getFieldType()->value);
112 2
            return $this->$method($field->getValue(), $field->getAffix(), $parameters);
113
        }
114
115 2
        return new InvalidFieldCollector();
116
    }
117
118 1
    public function visitNotExpression(NotExpression $notExpression, VisitorParameters $parameters): InvalidFieldCollector
119
    {
120 1
        $invalidFieldCollectorExpression = $notExpression->getExpression()->accept($this, $parameters);
121
122 1
        return new InvalidFieldCollector($invalidFieldCollectorExpression->getfieldErrors());
123
    }
124
125 3
    public function visitAndExpression(AndExpression $andExpression, VisitorParameters $parameters): InvalidFieldCollector
126
    {
127 3
        return $this->visitExpression($andExpression, $parameters);
128
    }
129
130 3
    public function visitOrExpression(OrExpression $orExpression, VisitorParameters $parameters): InvalidFieldCollector
131
    {
132 3
        return $this->visitExpression($orExpression, $parameters);
133
    }
134
135 1
    public function visitOrOperand(OrOperand $orOperand, VisitorParameters $parameters): InvalidFieldCollector
136
    {
137 1
        return $this->visitOperand($orOperand, $parameters);
138
    }
139
140 2
    public function visitAndOperand(AndOperand $andOperand, VisitorParameters $parameters): InvalidFieldCollector
141
    {
142 2
        return $this->visitOperand($andOperand, $parameters);
143
    }
144
145 3
    private function visitTail(null | OrExpression | AndExpression | OrOperand | AndOperand $tail, VisitorParameters $parameters): InvalidFieldCollector
146
    {
147 3
        if ($tail) {
148 2
            return $tail->accept($this, $parameters);
149
        }
150 3
        return new InvalidFieldCollector();
151
    }
152
153 2
    private function visitOperand(OrOperand | AndOperand $operand, VisitorParameters $parameters): InvalidFieldCollector
154
    {
155 2
        $head = $operand->getOperand()->accept($this, $parameters);
156 2
        $tail = $this->visitTail($operand->getTail(), $parameters);
157
158 2
        return $this->mergeHeadTail($head, $tail);
159
    }
160
161 3
    private function visitExpression(OrExpression | AndExpression $expression, VisitorParameters $parameters): InvalidFieldCollector
162
    {
163 3
        $head = $expression->getExpression()->accept($this, $parameters);
164 3
        $tail = $this->visitTail($expression->getTail(), $parameters);
165
166 3
        return $this->mergeHeadTail($head, $tail);
167
    }
168
169 3
    private function mergeHeadTail(InvalidFieldCollector $head, InvalidFieldCollector $tail): InvalidFieldCollector
170
    {
171 3
        return new InvalidFieldCollector(array_merge(
172 3
            $head->getfieldErrors(),
173 3
            $tail->getfieldErrors(),
174 3
        ));
175
    }
176
177 2
    private function visitFieldGroup(string $searchTerm, string $affix, VisitorParameters $parameters): InvalidFieldCollector
178
    {
179 2
        $teamGroups = $parameters->getTeamGroups();
180 2
        $groupNames = array_column($teamGroups, 'name');
181 2
        if (!in_array($searchTerm, $groupNames, true)) {
182 1
            $message = sprintf(
183 1
                'group:%s. Valid values are %s.',
184 1
                $searchTerm,
185 1
                implode(', ', $groupNames),
186 1
            );
187 1
            return new InvalidFieldCollector(array($message));
188
        }
189 1
        return new InvalidFieldCollector();
190
    }
191
192 2
    private function visitFieldTimestamped(string $searchTerm, string $affix, VisitorParameters $parameters): InvalidFieldCollector
193
    {
194 2
        if ($parameters->getEntityType() !== 'experiments') {
195 1
            return new InvalidFieldCollector(array('timestamped: is only allowed when searching in experiments.'));
196
        }
197 1
        return new InvalidFieldCollector();
198
    }
199
200 2
    private function visitFieldVisibility(string $searchTerm, string $affix, VisitorParameters $parameters): InvalidFieldCollector
201
    {
202 2
        $visibilityFieldHelper = new VisibilityFieldHelper($searchTerm);
203 2
        if (!$visibilityFieldHelper->getArr()) {
204 1
            $message = sprintf(
205 1
                'visibility:%s. Valid values are %s.',
206 1
                $searchTerm,
207 1
                $visibilityFieldHelper->possibleInput,
208 1
            );
209 1
            return new InvalidFieldCollector(array($message));
210
        }
211 1
        return new InvalidFieldCollector();
212
    }
213
}
214