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