Passed
Push — master ( 806b7a...235898 )
by
unknown
14:08
created

findOneByExtensionKeyAndVersion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 2
dl 0
loc 10
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the TYPO3 CMS project.
5
 *
6
 * It is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License, either version 2
8
 * of the License, or any later version.
9
 *
10
 * For the full copyright and license information, please read the
11
 * LICENSE.txt file that was distributed with this source code.
12
 *
13
 * The TYPO3 project - inspiring people to share!
14
 */
15
16
namespace TYPO3\CMS\Extensionmanager\Domain\Repository;
17
18
use TYPO3\CMS\Core\Database\ConnectionPool;
19
use TYPO3\CMS\Core\Utility\GeneralUtility;
20
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
21
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
22
use TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface;
23
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
24
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
25
use TYPO3\CMS\Extbase\Persistence\Repository;
26
use TYPO3\CMS\Extensionmanager\Domain\Model\Extension;
27
28
/**
29
 * A repository for extensions
30
 * @internal This class is a specific domain repository implementation and is not part of the Public TYPO3 API.
31
 */
32
class ExtensionRepository extends Repository
33
{
34
    /**
35
     * @var string
36
     */
37
    const TABLE_NAME = 'tx_extensionmanager_domain_model_extension';
38
39
    protected ?QuerySettingsInterface $querySettings = null;
40
41
    /**
42
     * @param QuerySettingsInterface $querySettings
43
     */
44
    public function injectQuerySettings(QuerySettingsInterface $querySettings)
45
    {
46
        $this->querySettings = $querySettings;
47
    }
48
49
    /**
50
     * Do not include pid in queries
51
     */
52
    public function initializeObject()
53
    {
54
        $this->setDefaultQuerySettings($this->querySettings->setRespectStoragePage(false));
0 ignored issues
show
Bug introduced by
The method setRespectStoragePage() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

54
        $this->setDefaultQuerySettings($this->querySettings->/** @scrutinizer ignore-call */ setRespectStoragePage(false));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
55
    }
56
57
    /**
58
     * Count all extensions
59
     *
60
     * @return int
61
     */
62
    public function countAll()
63
    {
64
        $query = $this->createQuery();
65
        $query = $this->addDefaultConstraints($query);
66
        return $query->execute()->count();
67
    }
68
69
    /**
70
     * Finds all extensions
71
     *
72
     * @return array|QueryResultInterface
73
     */
74
    public function findAll()
75
    {
76
        $query = $this->createQuery();
77
        $query = $this->addDefaultConstraints($query);
78
        $query->setOrderings(
79
            [
80
                'lastUpdated' => QueryInterface::ORDER_DESCENDING
81
            ]
82
        );
83
        return $query->execute();
84
    }
85
86
    /**
87
     * Find an extension by extension key ordered by version
88
     *
89
     * @param string $extensionKey
90
     * @return QueryResultInterface
91
     */
92
    public function findByExtensionKeyOrderedByVersion($extensionKey)
93
    {
94
        $query = $this->createQuery();
95
        $query->matching(
96
            $query->logicalAnd(
97
                $query->equals('extensionKey', $extensionKey),
98
                $query->greaterThanOrEqual('reviewState', 0)
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->greaterThanOrEqual('reviewState', 0). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

98
            $query->/** @scrutinizer ignore-call */ 
99
                    logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
99
            )
100
        );
101
        $query->setOrderings(['integerVersion' => QueryInterface::ORDER_DESCENDING]);
102
        return $query->execute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->execute() also could return the type array which is incompatible with the documented return type TYPO3\CMS\Extbase\Persistence\QueryResultInterface.
Loading history...
103
    }
104
105
    /**
106
     * Find the current version by extension key
107
     *
108
     * @param string $extensionKey
109
     * @return array|QueryResultInterface
110
     */
111
    public function findOneByCurrentVersionByExtensionKey($extensionKey)
112
    {
113
        $query = $this->createQuery();
114
        $query->matching(
115
            $query->logicalAnd(
116
                $query->equals('extensionKey', $extensionKey),
117
                $query->greaterThanOrEqual('reviewState', 0),
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->greaterThanOrEqual('reviewState', 0). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

117
            $query->/** @scrutinizer ignore-call */ 
118
                    logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
118
                $query->equals('currentVersion', 1)
119
            )
120
        );
121
        $query->setLimit(1);
122
        return $query->execute()->getFirst();
123
    }
124
125
    /**
126
     * Find one extension by extension key and version
127
     *
128
     * @param string $extensionKey
129
     * @param string $version (example: 4.3.10)
130
     * @return array|QueryResultInterface
131
     */
132
    public function findOneByExtensionKeyAndVersion($extensionKey, $version)
133
    {
134
        $query = $this->createQuery();
135
        // Hint: This method must not filter out insecure extensions, if needed,
136
        // it should be done on a different level, or with a helper method.
137
        $query->matching($query->logicalAnd(
138
            $query->equals('extensionKey', $extensionKey),
139
            $query->equals('version', $version)
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->equals('version', $version). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

139
        $query->matching($query->/** @scrutinizer ignore-call */ logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
140
        ));
141
        return $query->setLimit(1)->execute()->getFirst();
142
    }
143
144
    /**
145
     * Find an extension by title, author name or extension key
146
     * This is the function used by the TER search. It is using a
147
     * scoring for the matches to sort the extension with an
148
     * exact key match on top
149
     *
150
     * @param string $searchString The string to search for extensions
151
     * @return mixed
152
     */
153
    public function findByTitleOrAuthorNameOrExtensionKey($searchString)
154
    {
155
        $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
156
            ->getQueryBuilderForTable(self::TABLE_NAME);
157
158
        $searchPlaceholderForLike = '%' . $queryBuilder->escapeLikeWildcards($searchString) . '%';
159
160
        $searchConstraints = [
161
            'extension_key' => $queryBuilder->expr()->eq(
162
                'extension_key',
163
                $queryBuilder->createNamedParameter($searchString, \PDO::PARAM_STR)
164
            ),
165
            'extension_key_like' => $queryBuilder->expr()->like(
166
                'extension_key',
167
                $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
168
            ),
169
            'title' => $queryBuilder->expr()->like(
170
                'title',
171
                $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
172
            ),
173
            'description' => $queryBuilder->expr()->like(
174
                'description',
175
                $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
176
            ),
177
            'author_name' => $queryBuilder->expr()->like(
178
                'author_name',
179
                $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
180
            ),
181
        ];
182
183
        $caseStatement = 'CASE ' .
184
            'WHEN ' . $searchConstraints['extension_key'] . ' THEN 16 ' .
185
            'WHEN ' . $searchConstraints['extension_key_like'] . ' THEN 8 ' .
186
            'WHEN ' . $searchConstraints['title'] . ' THEN 4 ' .
187
            'WHEN ' . $searchConstraints['description'] . ' THEN 2 ' .
188
            'WHEN ' . $searchConstraints['author_name'] . ' THEN 1 ' .
189
            'END AS ' . $queryBuilder->quoteIdentifier('position');
190
191
        $result = $queryBuilder
192
            ->select('*')
193
            ->addSelectLiteral($caseStatement)
194
            ->from(self::TABLE_NAME)
195
            ->where(
196
                $queryBuilder->expr()->orX(...array_values($searchConstraints)),
197
                $queryBuilder->expr()->eq('current_version', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
198
                $queryBuilder->expr()->gte('review_state', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
199
            )
200
            ->orderBy('position', 'DESC')
201
            ->execute()
202
            ->fetchAll();
203
204
        $dataMapper = GeneralUtility::makeInstance(DataMapper::class);
205
        return $dataMapper->map(Extension::class, $result);
206
    }
207
208
    /**
209
     * Find an extension between a certain version range ordered by version number
210
     *
211
     * @param string $extensionKey
212
     * @param int $lowestVersion
213
     * @param int $highestVersion
214
     * @param bool $includeCurrentVersion
215
     * @return QueryResultInterface
216
     */
217
    public function findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion = 0, $highestVersion = 0, $includeCurrentVersion = true)
218
    {
219
        $query = $this->createQuery();
220
        $constraint = null;
221
        if ($lowestVersion !== 0 && $highestVersion !== 0) {
222
            if ($includeCurrentVersion) {
223
                $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->greaterThanOrEqu...rsion', $lowestVersion). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

223
                /** @scrutinizer ignore-call */ 
224
                $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
224
            } else {
225
                $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
226
            }
227
        } elseif ($lowestVersion === 0 && $highestVersion !== 0) {
228
            if ($includeCurrentVersion) {
229
                $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
230
            } else {
231
                $constraint = $query->logicalAnd($query->lessThan('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
232
            }
233
        } elseif ($lowestVersion !== 0 && $highestVersion === 0) {
234
            if ($includeCurrentVersion) {
235
                $constraint = $query->logicalAnd($query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
236
            } else {
237
                $constraint = $query->logicalAnd($query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
238
            }
239
        } elseif ($lowestVersion === 0 && $highestVersion === 0) {
240
            $constraint = $query->equals('extensionKey', $extensionKey);
241
        }
242
        if ($constraint) {
243
            $query->matching($query->logicalAnd($constraint, $query->greaterThanOrEqual('reviewState', 0)));
244
        }
245
        $query->setOrderings([
246
            'integerVersion' => QueryInterface::ORDER_DESCENDING
247
        ]);
248
        return $query->execute();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $query->execute() also could return the type array which is incompatible with the documented return type TYPO3\CMS\Extbase\Persistence\QueryResultInterface.
Loading history...
249
    }
250
251
    /**
252
     * Finds all extensions with category "distribution" not published by the TYPO3 CMS Team
253
     *
254
     * @param bool $showUnsuitableDistributions
255
     * @return Extension[]
256
     */
257
    public function findAllCommunityDistributions(bool $showUnsuitableDistributions = false): array
258
    {
259
        $query = $this->createQuery();
260
        $query->matching(
261
            $query->logicalAnd(
262
                $query->equals('category', Extension::DISTRIBUTION_CATEGORY),
263
                $query->logicalNot($query->equals('ownerusername', 'typo3v4'))
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->logicalNot($quer...rusername', 'typo3v4')). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

263
            $query->/** @scrutinizer ignore-call */ 
264
                    logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
264
            )
265
        );
266
267
        $query->setOrderings([
268
            'alldownloadcounter' => QueryInterface::ORDER_DESCENDING
269
        ]);
270
271
        return $this->filterYoungestVersionOfExtensionList($query->execute()->toArray(), $showUnsuitableDistributions);
272
    }
273
274
    /**
275
     * Finds all extensions with category "distribution" that are published by the TYPO3 CMS Team
276
     *
277
     * @param bool $showUnsuitableDistributions
278
     * @return Extension[]
279
     */
280
    public function findAllOfficialDistributions(bool $showUnsuitableDistributions = false): array
281
    {
282
        $query = $this->createQuery();
283
        $query->matching(
284
            $query->logicalAnd(
285
                $query->equals('category', Extension::DISTRIBUTION_CATEGORY),
286
                $query->equals('ownerusername', 'typo3v4')
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->equals('ownerusername', 'typo3v4'). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

286
            $query->/** @scrutinizer ignore-call */ 
287
                    logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
287
            )
288
        );
289
290
        $query->setOrderings([
291
            'alldownloadcounter' => QueryInterface::ORDER_DESCENDING
292
        ]);
293
294
        return $this->filterYoungestVersionOfExtensionList($query->execute()->toArray(), $showUnsuitableDistributions);
295
    }
296
297
    /**
298
     * Count extensions with a certain key between a given version range
299
     *
300
     * @param string $extensionKey
301
     * @param int $lowestVersion
302
     * @param int $highestVersion
303
     * @return int
304
     */
305
    public function countByVersionRangeAndExtensionKey($extensionKey, $lowestVersion = 0, $highestVersion = 0)
306
    {
307
        return $this->findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion, $highestVersion)->count();
308
    }
309
310
    /**
311
     * Find highest version available of an extension
312
     *
313
     * @param string $extensionKey
314
     * @return Extension
315
     */
316
    public function findHighestAvailableVersion($extensionKey)
317
    {
318
        $query = $this->createQuery();
319
        $query->matching($query->logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->greaterThanOrEqual('reviewState', 0). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

319
        $query->matching($query->/** @scrutinizer ignore-call */ logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
320
        $query->setOrderings([
321
            'integerVersion' => QueryInterface::ORDER_DESCENDING
322
        ]);
323
        return $query->setLimit(1)->execute()->getFirst();
324
    }
325
326
    /**
327
     * Adds default constraints to the query - in this case it
328
     * enables us to always just search for the latest version of an extension
329
     *
330
     * @param QueryInterface $query the query to adjust
331
     * @return QueryInterface
332
     */
333
    protected function addDefaultConstraints(QueryInterface $query): QueryInterface
334
    {
335
        if ($query->getConstraint()) {
336
            $query->matching($query->logicalAnd(
337
                $query->getConstraint(),
338
                $query->equals('current_version', true),
0 ignored issues
show
Unused Code introduced by
The call to TYPO3\CMS\Extbase\Persis...Interface::logicalAnd() has too many arguments starting with $query->equals('current_version', true). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

338
            $query->matching($query->/** @scrutinizer ignore-call */ logicalAnd(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
339
                $query->greaterThanOrEqual('reviewState', 0)
340
            ));
341
        } else {
342
            $query->matching($query->logicalAnd(
343
                $query->equals('current_version', true),
344
                $query->greaterThanOrEqual('reviewState', 0)
345
            ));
346
        }
347
        return $query;
348
    }
349
350
    /**
351
     * Get extensions (out of a given list) that are suitable for the current TYPO3 version
352
     *
353
     * @param Extension[] $extensions List of extensions to check
354
     * @return Extension[] List of extensions suitable for current TYPO3 version
355
     */
356
    protected function getExtensionsSuitableForTypo3Version(array $extensions): array
357
    {
358
        $suitableExtensions = [];
359
        foreach ($extensions as $extension) {
360
            $dependency = $extension->getTypo3Dependency();
361
            if ($dependency !== null && $dependency->isVersionCompatible(VersionNumberUtility::getNumericTypo3Version())) {
362
                $suitableExtensions[] = $extension;
363
            }
364
        }
365
        return $suitableExtensions;
366
    }
367
368
    /**
369
     * Get a list of various extensions in various versions and returns
370
     * a filtered list containing the extension-version combination with
371
     * the highest version number.
372
     *
373
     * @param Extension[] $extensions
374
     * @param bool $showUnsuitable
375
     * @return Extension[]
376
     */
377
    protected function filterYoungestVersionOfExtensionList(array $extensions, bool $showUnsuitable): array
378
    {
379
        if (!$showUnsuitable) {
380
            $extensions = $this->getExtensionsSuitableForTypo3Version($extensions);
381
        }
382
        $filteredExtensions = [];
383
        foreach ($extensions as $extension) {
384
            $extensionKey = $extension->getExtensionKey();
385
            if (!array_key_exists($extensionKey, $filteredExtensions)) {
386
                $filteredExtensions[$extensionKey] = $extension;
387
                continue;
388
            }
389
            $currentVersion = $filteredExtensions[$extensionKey]->getVersion();
390
            $newVersion = $extension->getVersion();
391
            if (version_compare($newVersion, $currentVersion, '>')) {
392
                $filteredExtensions[$extensionKey] = $extension;
393
            }
394
        }
395
        return $filteredExtensions;
396
    }
397
}
398