Passed
Pull Request — master (#225)
by
unknown
06:11
created

QueryBasedMatcher::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 2.0054

Importance

Changes 0
Metric Value
cc 2
eloc 9
nc 2
nop 6
dl 0
loc 14
ccs 8
cts 9
cp 0.8889
crap 2.0054
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
namespace Kaliop\eZMigrationBundle\Core\Matcher;
4
5
use eZ\Publish\API\Repository\Values\Content\Query;
6
use eZ\Publish\API\Repository\Values\Content\Query\SortClause;
7
use eZ\Publish\API\Repository\Values\Content\Query\Criterion\Operator;
8
use eZ\Publish\API\Repository\Repository;
9
use Kaliop\eZMigrationBundle\API\KeyMatcherInterface;
10
use Kaliop\eZMigrationBundle\API\Exception\InvalidSortConditionsException;
11
use Kaliop\eZMigrationBundle\API\Exception\InvalidMatchConditionsException;
12
13
/**
14
 * @todo extend to allow matching by modifier, language code, content_type_group_id
15
 */
16
abstract class QueryBasedMatcher extends RepositoryMatcher
17
{
18
    const MATCH_CONTENT_ID = 'content_id';
19
    const MATCH_LOCATION_ID = 'location_id';
20
    const MATCH_CONTENT_REMOTE_ID = 'content_remote_id';
21
    const MATCH_LOCATION_REMOTE_ID = 'location_remote_id';
22
    const MATCH_ATTRIBUTE = 'attribute';
23
    const MATCH_CONTENT_TYPE_ID = 'contenttype_id';
24
    const MATCH_CONTENT_TYPE_IDENTIFIER = 'contenttype_identifier';
25
    const MATCH_CREATION_DATE = 'creation_date';
26
    const MATCH_GROUP = 'group';
27
    const MATCH_LANGUAGE_CODE = 'lang';
28
    const MATCH_MODIFICATION_DATE = 'modification_date';
29
    const MATCH_OBJECT_STATE = 'object_state';
30
    const MATCH_OWNER = 'owner';
31
    const MATCH_PARENT_LOCATION_ID = 'parent_location_id';
32
    const MATCH_PARENT_LOCATION_REMOTE_ID = 'parent_location_remote_id';
33
    const MATCH_SECTION = 'section';
34
    const MATCH_SUBTREE = 'subtree';
35
    const MATCH_VISIBILITY = 'visibility';
36
37
    const SORT_CONTENT_ID = 'content_id';
38
    const SORT_CONTENT_NAME = 'name';
39
    const SORT_DATE_MODIFIED = 'modified';
40
    const SORT_DATE_PUBLISHED = 'published';
41
    const SORT_LOCATION_DEPTH = 'depth';
42
    const SORT_LOCATION_ID = 'node_id';
43
    const SORT_LOCATION_ISMAIN = 'is_main';
44
    const SORT_LOCATION_PATH = 'path';
45
    const SORT_LOCATION_PRIORITY = 'priority';
46
    const SORT_LOCATION_VISIBILITY = 'visibility';
47
    const SORT_SECTION_IDENTIFIER = 'section_identifier';
48
    const SORT_SECTION_NAME = 'section_name';
49
50
    // useful f.e. when talking to Solr, which defaults to java integers for max nr of items for queries
51
    const INT_MAX_16BIT = 2147483647;
52
53
    static protected $operatorsMap = array(
54
        'eq' => Operator::EQ,
55
        'gt' => Operator::GT,
56
        'gte' => Operator::GTE,
57
        'lt' => Operator::LT,
58
        'lte' => Operator::LTE,
59
        'in' => Operator::IN,
60
        'between' => Operator::BETWEEN,
61
        'like' => Operator::LIKE,
62
        'contains' => Operator::CONTAINS,
63
        Operator::EQ => Operator::EQ,
64
        Operator::GT => Operator::GT,
65
        Operator::GTE => Operator::GTE,
66
        Operator::LT => Operator::LT,
67
        Operator::LTE => Operator::LTE,
68
    );
69
70
    /** @var  KeyMatcherInterface $groupMatcher */
71
    protected $groupMatcher;
72
    /** @var  KeyMatcherInterface $sectionMatcher */
73
    protected $sectionMatcher;
74
    /** @var  KeyMatcherInterface $stateMatcher */
75
    protected $stateMatcher;
76
    /** @var  KeyMatcherInterface $userMatcher */
77
    protected $userMatcher;
78
    /** @var int $queryLimit */
79
    protected $queryLimit;
80
81
    /**
82
     * @param Repository $repository
83
     * @param KeyMatcherInterface $groupMatcher
84
     * @param KeyMatcherInterface $sectionMatcher
85
     * @param KeyMatcherInterface $stateMatcher
86
     * @param KeyMatcherInterface $userMatcher
87
     * @param int $queryLimit passed to the repo as max. number of results to fetch. Important to avoid SOLR errors
88
     * @todo inject the services needed, not the whole repository
89
     */
90 106
    public function __construct(Repository $repository, KeyMatcherInterface $groupMatcher = null,
91
        KeyMatcherInterface $sectionMatcher = null, KeyMatcherInterface $stateMatcher = null,
92
        KeyMatcherInterface $userMatcher = null, $queryLimit = null)
93
    {
94 106
        parent::__construct($repository);
95 106
        $this->groupMatcher = $groupMatcher;
96 106
        $this->sectionMatcher = $sectionMatcher;
97 106
        $this->stateMatcher = $stateMatcher;
98 106
        $this->userMatcher = $userMatcher;
99
100 106
        if ($queryLimit !== null) {
101 106
            $this->queryLimit = (int)$queryLimit;
102
        } else {
103
            $this->queryLimit = self::INT_MAX_16BIT;
104
        }
105 106
    }
106
107
    /**
108
     * @param $key
109
     * @param $values
110
     * @return mixed should it be \eZ\Publish\API\Repository\Values\Content\Query\CriterionInterface ?
111
     * @throws InvalidMatchConditionsException for unsupported keys
112
     */
113
    protected function getQueryCriterion($key, $values)
114
    {
115
        if (!is_array($values)) {
116
            $values = array($values);
117
        }
118
119
        switch ($key) {
120
            case self::MATCH_CONTENT_ID:
121
                return new Query\Criterion\ContentId($values);
122
123
            case self::MATCH_LOCATION_ID:
124
                // NB: seems to cause problems with EZP 2014.3
125
                return new Query\Criterion\LocationId(reset($values));
126
127
            case self::MATCH_CONTENT_REMOTE_ID:
128
                return new Query\Criterion\RemoteId($values);
129
130
            case self::MATCH_LOCATION_REMOTE_ID:
131
                return new Query\Criterion\LocationRemoteId($values);
132
133
            case self::MATCH_ATTRIBUTE:
134
                $spec = reset($values);
135
                $attribute = key($values);
136
                $match = reset($spec);
137
                $operator = key($spec);
138
                if (!isset(self::$operatorsMap[$operator])) {
139
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for attributes");
140
                }
141
                return new Query\Criterion\Field($attribute, self::$operatorsMap[$operator], $match);
142
143
            case 'content_type_id':
144
            case self::MATCH_CONTENT_TYPE_ID:
145
                return new Query\Criterion\ContentTypeId($values);
146
147
            case 'content_type_identifier':
148
            case self::MATCH_CONTENT_TYPE_IDENTIFIER:
149
                return new Query\Criterion\ContentTypeIdentifier($values);
150
151
            case self::MATCH_CREATION_DATE:
152
                $match = reset($values);
153
                $operator = key($values);
154
                if (!isset(self::$operatorsMap[$operator])) {
155
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for dates");
156
                }
157
                return new Query\Criterion\DateMetadata(Query\Criterion\DateMetadata::CREATED, self::$operatorsMap[$operator], $match);
158
159
            case self::MATCH_GROUP:
160
                foreach($values as &$value) {
161
                    if (!ctype_digit($value)) {
162
                        $value = $this->groupMatcher->matchOneByKey($value)->id;
163
                    }
164
                }
165
                return new Query\Criterion\UserMetadata(Query\Criterion\UserMetadata::GROUP, Operator::IN, $values);
166
167
            case self::MATCH_LANGUAGE_CODE:
168
                return new Query\Criterion\LanguageCode($values);
169
170
            case self::MATCH_MODIFICATION_DATE:
171
                $match = reset($values);
172
                $operator = key($values);
173
                if (!isset(self::$operatorsMap[$operator])) {
174
                    throw new InvalidMatchConditionsException("Can not use '$operator' as comparison operator for dates");
175
                }
176
                return new Query\Criterion\DateMetadata(Query\Criterion\DateMetadata::MODIFIED, self::$operatorsMap[$operator], $match);
177
178
            case self::MATCH_OBJECT_STATE:
179
                foreach($values as &$value) {
180
                    if (!ctype_digit($value)) {
181
                        $value = $this->stateMatcher->matchOneByKey($value)->id;
182
                    }
183
                }
184
                return new Query\Criterion\ObjectStateId($values);
185
186
            case self::MATCH_OWNER:
187
                foreach($values as &$value) {
188
                    if (!ctype_digit($value)) {
189
                        $value = $this->userMatcher->matchOneByKey($value)->id;
190
                    }
191
                }
192
                return new Query\Criterion\UserMetadata(Query\Criterion\UserMetadata::OWNER, Operator::IN, $values);
193
194
            case self::MATCH_PARENT_LOCATION_ID:
195
                return new Query\Criterion\ParentLocationId($values);
196
197
            case self::MATCH_PARENT_LOCATION_REMOTE_ID:
198
                $locationIds = [];
199
                foreach ($values as $remoteParentLocationId) {
200
                    $location = $this->repository->getLocationService()->loadLocationByRemoteId($remoteParentLocationId);
201
                    // unique locations
202
                    $locationIds[$location->id] = $location->id;
203
                }
204
                return new Query\Criterion\ParentLocationId($locationIds);
205
206
            case self::MATCH_SECTION:
207
                foreach($values as &$value) {
208
                    if (!ctype_digit($value)) {
209
                        $value = $this->sectionMatcher->matchOneByKey($value)->id;
210
                    }
211
                }
212
                return new Query\Criterion\SectionId($values);
213
214
            case self::MATCH_SUBTREE:
215
                return new Query\Criterion\Subtree($values);
216
217
            case self::MATCH_VISIBILITY:
218
                /// @todo error/warning if there is more than 1 value...
219
                $value = reset($values);
220
                if ($value) {
221
                    return new Query\Criterion\Visibility(Query\Criterion\Visibility::VISIBLE);
222
                } else {
223
                    return new Query\Criterion\Visibility(Query\Criterion\Visibility::HIDDEN);
224
                }
225
226
            case self::MATCH_AND:
227
                $subCriteria = array();
228
                foreach($values as $subCriterion) {
229
                    $value = reset($subCriterion);
230
                    $subCriteria[] = $this->getQueryCriterion(key($subCriterion), $value);
231
                }
232
                return new Query\Criterion\LogicalAnd($subCriteria);
233
234
            case self::MATCH_OR:
235
                $subCriteria = array();
236
                foreach($values as $subCriterion) {
237
                    $value = reset($subCriterion);
238
                    $subCriteria[] = $this->getQueryCriterion(key($subCriterion), $value);
239
                }
240
                return new Query\Criterion\LogicalOr($subCriteria);
241
242
            case self::MATCH_NOT:
243
                /// @todo throw if more than one sub-criteria found
244
                $value = reset($values);
245
                $subCriterion = $this->getQueryCriterion(key($values), $value);
246
                return new Query\Criterion\LogicalNot($subCriterion);
247
248
            default:
249
                throw new InvalidMatchConditionsException($this->returns . " can not be matched because matching condition '$key' is not supported. Supported conditions are: " .
250
                    implode(', ', $this->allowedConditions));
251
        }
252
    }
253
254
    /**
255
     * @param array $sortDefinition
256
     * @return array
257
     * @throws InvalidSortConditionsException
258
     */
259
    protected function getSortClauses(array $sortDefinition)
260
    {
261
        $out = array();
262
263
        foreach ($sortDefinition as $sortItem) {
264
265
            if (is_string($sortItem)) {
266
                $sortItem = array('sort_field' => $sortItem);
267
            }
268
            if (!is_array($sortItem) || !isset($sortItem['sort_field'])) {
269
                throw new InvalidSortConditionsException("Missing sort_field element in sorting definition");
270
            }
271
            if (!isset($sortItem['sort_order'])) {
272
                // we have to pick a default ;-)
273
                $sortItem['sort_order'] = 'ASC';
274
            }
275
276
            $direction = $this->hash2SortOrder($sortItem['sort_order']);
277
278
            switch($sortItem['sort_field']) {
279
                case self::SORT_CONTENT_ID:
280
                    $out[] = new SortClause\ContentId($direction);
281
                    break;
282
                case self::SORT_CONTENT_NAME:
283
                    $out[] = new SortClause\ContentName($direction);
284
                    break;
285
                case self::SORT_DATE_MODIFIED:
286
                    $out[] = new SortClause\DateModified($direction);
287
                    break;
288
                case self::SORT_DATE_PUBLISHED:
289
                    $out[] = new SortClause\DatePublished($direction);
290
                    break;
291
                /// @todo
292
                //case self::SORT_FIELD:
293
                //    $out[] = new SortClause\Field($direction);
294
                //    break;
295
                case self::SORT_LOCATION_DEPTH:
296
                    $out[] = new SortClause\Location\Depth($direction);
297
                    break;
298
                case self::SORT_LOCATION_ID:
299
                    $out[] = new SortClause\Location\Id($direction);
300
                    break;
301
                case self::SORT_LOCATION_ISMAIN:
302
                    $out[] = new SortClause\Location\IsMainLocation($direction);
303
                    break;
304
                case self::SORT_LOCATION_PATH:
305
                    $out[] = new SortClause\Location\Path($direction);
306
                    break;
307
                case self::SORT_LOCATION_PRIORITY:
308
                    $out[] = new SortClause\Location\Priority($direction);
309
                    break;
310
                case self::SORT_LOCATION_VISIBILITY:
311
                    $out[] = new SortClause\Location\Visibility($direction);
312
                    break;
313
                case self::SORT_SECTION_IDENTIFIER:
314
                    $out[] = new SortClause\SectionIdentifier($direction);
315
                    break;
316
                case self::SORT_SECTION_NAME:
317
                    $out[] = new SortClause\SectionName($direction);
318
                    break;
319
                default:
320
                    throw new InvalidSortConditionsException("Sort field '{$sortItem['sort_field']}' not implemented");
321
            }
322
        }
323
324
        return $out;
325
    }
326
327
    protected function hash2SortOrder($value)
328
    {
329
        $sortOrder = null;
330
331
        if ($value !== null) {
332
            if (strtoupper($value) === 'ASC') {
333
                $sortOrder = Query::SORT_ASC;
334
            } else {
335
                $sortOrder = Query::SORT_DESC;
336
            }
337
        }
338
339
        return $sortOrder;
340
    }
341
342
    /**
343
     * @param int $value
344
     * @return string
345
     */
346
    protected function sortOrder2Hash($value)
347
    {
348
        if ($value === Query::SORT_ASC) {
0 ignored issues
show
introduced by
The condition $value === eZ\Publish\AP...Content\Query::SORT_ASC is always false.
Loading history...
349
            return 'ASC';
350
        } else {
351
            return 'DESC';
352
        }
353
    }
354
}
355