[BUGFIX] Order extensions by integerVersion instead of version
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Classes / Domain / Repository / ExtensionRepository.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Domain\Repository;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Database\Connection;
18 use TYPO3\CMS\Core\Database\ConnectionPool;
19 use TYPO3\CMS\Core\Utility\GeneralUtility;
20
21 /**
22 * A repository for extensions
23 */
24 class ExtensionRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
25 {
26 /**
27 * @var string
28 */
29 const TABLE_NAME = 'tx_extensionmanager_domain_model_extension';
30
31 /**
32 * @var \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper
33 */
34 protected $dataMapper;
35
36 /**
37 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper
38 */
39 public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
40 {
41 $this->dataMapper = $dataMapper;
42 }
43
44 /**
45 * Do not include pid in queries
46 *
47 * @return void
48 */
49 public function initializeObject()
50 {
51 /** @var $defaultQuerySettings \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface */
52 $defaultQuerySettings = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface::class);
53 $defaultQuerySettings->setRespectStoragePage(false);
54 $this->setDefaultQuerySettings($defaultQuerySettings);
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|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
73 */
74 public function findAll()
75 {
76 $query = $this->createQuery();
77 $query = $this->addDefaultConstraints($query);
78 $query->setOrderings(
79 [
80 'lastUpdated' => \TYPO3\CMS\Extbase\Persistence\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 \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
91 */
92 public function findByExtensionKeyOrderedByVersion($extensionKey)
93 {
94 $query = $this->createQuery();
95 $query->matching($query->logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));
96 $query->setOrderings(['integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING]);
97 return $query->execute();
98 }
99
100 /**
101 * Find the current version by extension key
102 *
103 * @param string $extensionKey
104 * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
105 */
106 public function findOneByCurrentVersionByExtensionKey($extensionKey)
107 {
108 $query = $this->createQuery();
109 $query->matching(
110 $query->logicalAnd(
111 $query->equals('extensionKey', $extensionKey),
112 $query->greaterThanOrEqual('reviewState', 0),
113 $query->equals('currentVersion', 1)
114 )
115 );
116 $query->setLimit(1);
117 return $query->execute()->getFirst();
118 }
119
120 /**
121 * Find one extension by extension key and version
122 *
123 * @param string $extensionKey
124 * @param string $version (example: 4.3.10)
125 * @return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
126 */
127 public function findOneByExtensionKeyAndVersion($extensionKey, $version)
128 {
129 $query = $this->createQuery();
130 // Hint: This method must not filter out insecure extensions, if needed,
131 // it should be done on a different level, or with a helper method.
132 $query->matching($query->logicalAnd(
133 $query->equals('extensionKey', $extensionKey),
134 $query->equals('version', $version)
135 ));
136 return $query->setLimit(1)->execute()->getFirst();
137 }
138
139 /**
140 * Find an extension by title, author name or extension key
141 * This is the function used by the TER search. It is using a
142 * scoring for the matches to sort the extension with an
143 * exact key match on top
144 *
145 * @param string $searchString The string to search for extensions
146 * @return mixed
147 */
148 public function findByTitleOrAuthorNameOrExtensionKey($searchString)
149 {
150 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
151 ->getQueryBuilderForTable(self::TABLE_NAME);
152
153 $searchPlaceholder = $queryBuilder->createNamedParameter($searchString);
154 $searchPlaceholderForLike = '%' . $queryBuilder->escapeLikeWildcards($searchString) . '%';
155
156 $searchConstraints = [
157 'extension_key' => $queryBuilder->expr()->eq(
158 'extension_key',
159 $queryBuilder->createNamedParameter($searchPlaceholder, \PDO::PARAM_STR)
160 ),
161 'extension_key_like' => $queryBuilder->expr()->like(
162 'extension_key',
163 $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
164 ),
165 'title' => $queryBuilder->expr()->like(
166 'title',
167 $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
168 ),
169 'description' => $queryBuilder->expr()->like(
170 'description',
171 $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
172 ),
173 'author_name' => $queryBuilder->expr()->like(
174 'author_name',
175 $queryBuilder->createNamedParameter($searchPlaceholderForLike, \PDO::PARAM_STR)
176 ),
177 ];
178
179 $caseStatement = 'CASE ' .
180 'WHEN ' . $searchConstraints['extension_key'] . ' THEN 16 ' .
181 'WHEN ' . $searchConstraints['extension_key_like'] . ' THEN 8 ' .
182 'WHEN ' . $searchConstraints['title'] . ' THEN 4 ' .
183 'WHEN ' . $searchConstraints['description'] . ' THEN 2 ' .
184 'WHEN ' . $searchConstraints['author_name'] . ' THEN 1 ' .
185 'END AS ' . $queryBuilder->quoteIdentifier('position');
186
187 $result = $queryBuilder
188 ->select('*')
189 ->addSelectLiteral($caseStatement)
190 ->from(self::TABLE_NAME)
191 ->where(
192 $queryBuilder->expr()->orX(...array_values($searchConstraints)),
193 $queryBuilder->expr()->eq('current_version', $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)),
194 $queryBuilder->expr()->gte('review_state', $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT))
195 )
196 ->orderBy('position', 'DESC')
197 ->execute()
198 ->fetchAll();
199
200 return $this->dataMapper->map(\TYPO3\CMS\Extensionmanager\Domain\Model\Extension::class, $result);
201 }
202
203 /**
204 * Find an extension between a certain version range ordered by version number
205 *
206 * @param string $extensionKey
207 * @param int $lowestVersion
208 * @param int $highestVersion
209 * @param bool $includeCurrentVersion
210 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
211 */
212 public function findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion = 0, $highestVersion = 0, $includeCurrentVersion = true)
213 {
214 $query = $this->createQuery();
215 $constraint = null;
216 if ($lowestVersion !== 0 && $highestVersion !== 0) {
217 if ($includeCurrentVersion) {
218 $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
219 } else {
220 $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
221 }
222 } elseif ($lowestVersion === 0 && $highestVersion !== 0) {
223 if ($includeCurrentVersion) {
224 $constraint = $query->logicalAnd($query->lessThanOrEqual('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
225 } else {
226 $constraint = $query->logicalAnd($query->lessThan('integerVersion', $highestVersion), $query->equals('extensionKey', $extensionKey));
227 }
228 } elseif ($lowestVersion !== 0 && $highestVersion === 0) {
229 if ($includeCurrentVersion) {
230 $constraint = $query->logicalAnd($query->greaterThanOrEqual('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
231 } else {
232 $constraint = $query->logicalAnd($query->greaterThan('integerVersion', $lowestVersion), $query->equals('extensionKey', $extensionKey));
233 }
234 } elseif ($lowestVersion === 0 && $highestVersion === 0) {
235 $constraint = $query->equals('extensionKey', $extensionKey);
236 }
237 if ($constraint) {
238 $query->matching($query->logicalAnd($constraint, $query->greaterThanOrEqual('reviewState', 0)));
239 }
240 $query->setOrderings([
241 'integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
242 ]);
243 return $query->execute();
244 }
245
246 /**
247 * Finds all extensions with category "distribution" not published by the TYPO3 CMS Team
248 *
249 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
250 */
251 public function findAllCommunityDistributions()
252 {
253 $query = $this->createQuery();
254 $query->matching(
255 $query->logicalAnd(
256 $query->equals('category', \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::DISTRIBUTION_CATEGORY),
257 $query->equals('currentVersion', 1),
258 $query->logicalNot($query->equals('ownerusername', 'typo3v4'))
259 )
260 );
261
262 $query->setOrderings([
263 'alldownloadcounter' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
264 ]);
265
266 return $query->execute();
267 }
268
269 /**
270 * Finds all extensions with category "distribution" that are published by the TYPO3 CMS Team
271 *
272 * @return \TYPO3\CMS\Extbase\Persistence\QueryResultInterface
273 */
274 public function findAllOfficialDistributions()
275 {
276 $query = $this->createQuery();
277 $query->matching(
278 $query->logicalAnd(
279 $query->equals('category', \TYPO3\CMS\Extensionmanager\Domain\Model\Extension::DISTRIBUTION_CATEGORY),
280 $query->equals('currentVersion', 1),
281 $query->equals('ownerusername', 'typo3v4')
282 )
283 );
284
285 $query->setOrderings([
286 'alldownloadcounter' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
287 ]);
288
289 return $query->execute();
290 }
291
292 /**
293 * Count extensions with a certain key between a given version range
294 *
295 * @param string $extensionKey
296 * @param int $lowestVersion
297 * @param int $highestVersion
298 * @return int
299 */
300 public function countByVersionRangeAndExtensionKey($extensionKey, $lowestVersion = 0, $highestVersion = 0)
301 {
302 return $this->findByVersionRangeAndExtensionKeyOrderedByVersion($extensionKey, $lowestVersion, $highestVersion)->count();
303 }
304
305 /**
306 * Find highest version available of an extension
307 *
308 * @param string $extensionKey
309 * @return \TYPO3\CMS\Extensionmanager\Domain\Model\Extension
310 */
311 public function findHighestAvailableVersion($extensionKey)
312 {
313 $query = $this->createQuery();
314 $query->matching($query->logicalAnd($query->equals('extensionKey', $extensionKey), $query->greaterThanOrEqual('reviewState', 0)));
315 $query->setOrderings([
316 'integerVersion' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_DESCENDING
317 ]);
318 return $query->setLimit(1)->execute()->getFirst();
319 }
320
321 /**
322 * Updates the current_version field after update.
323 *
324 * @param int $repositoryUid
325 * @return int
326 */
327 public function insertLastVersion($repositoryUid = 1)
328 {
329 $this->markExtensionWithMaximumVersionAsCurrent($repositoryUid);
330
331 return $this->getNumberOfCurrentExtensions();
332 }
333
334 /**
335 * Sets current_version = 1 for all extensions where the extension version is maximal.
336 *
337 * For performance reasons, the "native" database connection is used here directly.
338 *
339 * @param int $repositoryUid
340 * @return void
341 */
342 protected function markExtensionWithMaximumVersionAsCurrent($repositoryUid)
343 {
344 $uidsOfCurrentVersion = $this->fetchMaximalVersionsForAllExtensions($repositoryUid);
345 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
346 ->getQueryBuilderForTable(self::TABLE_NAME);
347
348 $queryBuilder
349 ->update(self::TABLE_NAME)
350 ->where(
351 $queryBuilder->expr()->in(
352 'uid',
353 $queryBuilder->createNamedParameter($uidsOfCurrentVersion, Connection::PARAM_INT_ARRAY)
354 )
355 )
356 ->set('current_version', 1)
357 ->execute();
358 }
359
360 /**
361 * Fetches the UIDs of all maximal versions for all extensions.
362 * This is done by doing a LEFT JOIN to itself ("a" and "b") and comparing
363 * both integer_version fields.
364 *
365 * @param int $repositoryUid
366 * @return array
367 */
368 protected function fetchMaximalVersionsForAllExtensions($repositoryUid)
369 {
370 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
371 ->getQueryBuilderForTable(self::TABLE_NAME);
372
373 $queryResult = $queryBuilder
374 ->select('a.uid AS uid')
375 ->from(self::TABLE_NAME, 'a')
376 ->leftJoin(
377 'a',
378 self::TABLE_NAME,
379 'b',
380 $queryBuilder->expr()->andX(
381 $queryBuilder->expr()->eq('a.repository', $queryBuilder->quoteIdentifier('b.repository')),
382 $queryBuilder->expr()->eq('a.extension_key', $queryBuilder->quoteIdentifier('b.extension_key')),
383 $queryBuilder->expr()->lt('a.integer_version', $queryBuilder->quoteIdentifier('b.integer_version'))
384 )
385 )
386 ->where(
387 $queryBuilder->expr()->eq(
388 'a.repository',
389 $queryBuilder->createNamedParameter($repositoryUid, \PDO::PARAM_INT)
390 ),
391 $queryBuilder->expr()->isNull('b.extension_key')
392 )
393 ->orderBy('a.uid')
394 ->execute();
395
396 $extensionUids = [];
397 while ($row = $queryResult->fetch()) {
398 $extensionUids[] = $row['uid'];
399 }
400
401 return $extensionUids;
402 }
403
404 /**
405 * Returns the number of extensions that are current.
406 *
407 * @return int
408 */
409 protected function getNumberOfCurrentExtensions()
410 {
411 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
412 ->getQueryBuilderForTable(self::TABLE_NAME);
413
414 return (int)$queryBuilder
415 ->count('*')
416 ->from(self::TABLE_NAME)
417 ->where($queryBuilder->expr()->eq(
418 'current_version',
419 $queryBuilder->createNamedParameter(1, \PDO::PARAM_INT)
420 ))
421 ->execute()
422 ->fetchColumn(0);
423 }
424
425 /**
426 * Adds default constraints to the query - in this case it
427 * enables us to always just search for the latest version of an extension
428 *
429 * @param \TYPO3\CMS\Extbase\Persistence\Generic\Query $query the query to adjust
430 * @return \TYPO3\CMS\Extbase\Persistence\Generic\Query
431 */
432 protected function addDefaultConstraints(\TYPO3\CMS\Extbase\Persistence\Generic\Query $query)
433 {
434 if ($query->getConstraint()) {
435 $query->matching($query->logicalAnd(
436 $query->getConstraint(),
437 $query->equals('current_version', true),
438 $query->greaterThanOrEqual('reviewState', 0)
439 ));
440 } else {
441 $query->matching($query->logicalAnd(
442 $query->equals('current_version', true),
443 $query->greaterThanOrEqual('reviewState', 0)
444 ));
445 }
446 return $query;
447 }
448 }