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