dba66f8c464186224e890ebf1296a9c28ed01e41
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Domain / Repository / Localization / LocalizationRepository.php
1 <?php
2 namespace TYPO3\CMS\Backend\Domain\Repository\Localization;
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\Backend\Utility\BackendUtility;
18 use TYPO3\CMS\Core\Database\Connection;
19 use TYPO3\CMS\Core\Database\ConnectionPool;
20 use TYPO3\CMS\Core\Database\Query\QueryBuilder;
21 use TYPO3\CMS\Core\Database\Query\QueryHelper;
22 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
23 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * Repository for record localizations
28 */
29 class LocalizationRepository
30 {
31 /**
32 * Fetch the language from which the records of a colPos in a certain language were initially localized
33 *
34 * @param int $pageId
35 * @param int $colPos
36 * @param int $localizedLanguage
37 * @return array|false
38 */
39 public function fetchOriginLanguage($pageId, $colPos, $localizedLanguage)
40 {
41 $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
42
43 $constraints = [
44 $queryBuilder->expr()->eq(
45 'tt_content.colPos',
46 $queryBuilder->createNamedParameter($colPos, \PDO::PARAM_INT)
47 ),
48 $queryBuilder->expr()->eq(
49 'tt_content.pid',
50 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
51 ),
52 $queryBuilder->expr()->eq(
53 'tt_content.sys_language_uid',
54 $queryBuilder->createNamedParameter($localizedLanguage, \PDO::PARAM_INT)
55 ),
56 ];
57 $constraints += $this->getAllowedLanguageConstraintsForBackendUser();
58
59 $queryBuilder->select('tt_content_orig.sys_language_uid')
60 ->from('tt_content')
61 ->join(
62 'tt_content',
63 'tt_content',
64 'tt_content_orig',
65 $queryBuilder->expr()->eq(
66 'tt_content.t3_origuid',
67 $queryBuilder->quoteIdentifier('tt_content_orig.uid')
68 )
69 )
70 ->join(
71 'tt_content_orig',
72 'sys_language',
73 'sys_language',
74 $queryBuilder->expr()->eq(
75 'tt_content_orig.sys_language_uid',
76 $queryBuilder->quoteIdentifier('sys_language.uid')
77 )
78 )
79 ->where(...$constraints)
80 ->groupBy('tt_content_orig.sys_language_uid');
81
82 return $queryBuilder->execute()->fetch();
83 }
84
85 /**
86 * @param int $pageId
87 * @param int $colPos
88 * @param int $languageId
89 * @return int
90 */
91 public function getLocalizedRecordCount($pageId, $colPos, $languageId)
92 {
93 $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
94
95 $rowCount = $queryBuilder->count('uid')
96 ->from('tt_content')
97 ->where(
98 $queryBuilder->expr()->eq(
99 'tt_content.sys_language_uid',
100 $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
101 ),
102 $queryBuilder->expr()->eq(
103 'tt_content.colPos',
104 $queryBuilder->createNamedParameter($colPos, \PDO::PARAM_INT)
105 ),
106 $queryBuilder->expr()->eq(
107 'tt_content.pid',
108 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
109 ),
110 $queryBuilder->expr()->neq(
111 'tt_content.t3_origuid',
112 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
113 )
114 )
115 ->execute()
116 ->fetchColumn(0);
117
118 return (int)$rowCount;
119 }
120
121 /**
122 * Fetch all available languages
123 *
124 * @param int $pageId
125 * @param int $colPos
126 * @param int $languageId
127 * @return array
128 */
129 public function fetchAvailableLanguages($pageId, $colPos, $languageId)
130 {
131 $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
132
133 $constraints = [
134 $queryBuilder->expr()->eq(
135 'tt_content.sys_language_uid',
136 $queryBuilder->quoteIdentifier('sys_language.uid')
137 ),
138 $queryBuilder->expr()->eq(
139 'tt_content.colPos',
140 $queryBuilder->createNamedParameter($colPos, \PDO::PARAM_INT)
141 ),
142 $queryBuilder->expr()->eq(
143 'tt_content.pid',
144 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
145 ),
146 $queryBuilder->expr()->neq(
147 'sys_language.uid',
148 $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
149 )
150 ];
151 $constraints += $this->getAllowedLanguageConstraintsForBackendUser();
152
153 $queryBuilder->select('sys_language.uid')
154 ->from('tt_content')
155 ->from('sys_language')
156 ->where(...$constraints)
157 ->groupBy('sys_language.uid')
158 ->orderBy('sys_language.sorting');
159
160 $result = $queryBuilder->execute()->fetchAll();
161
162 return $result;
163 }
164
165 /**
166 * Builds an additional where clause to exclude deleted records and setting the versioning placeholders
167 *
168 * @return string
169 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
170 */
171 public function getExcludeQueryPart()
172 {
173 GeneralUtility::logDeprecatedFunction();
174
175 return BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content');
176 }
177
178 /**
179 * Builds an additional where clause to exclude hidden languages and limit a backend user to its allowed languages,
180 * if the user is not an admin.
181 *
182 * @return string
183 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
184 */
185 public function getAllowedLanguagesForBackendUser()
186 {
187 GeneralUtility::logDeprecatedFunction();
188
189 $backendUser = $this->getBackendUser();
190 $additionalWhere = '';
191 if (!$backendUser->isAdmin()) {
192 $additionalWhere .= ' AND sys_language.hidden=0';
193
194 if (!empty($backendUser->user['allowed_languages'])) {
195 $additionalWhere .= ' AND sys_language.uid IN(' . implode(',', GeneralUtility::intExplode(',', $backendUser->user['allowed_languages'])) . ')';
196 }
197 }
198
199 return $additionalWhere;
200 }
201
202 /**
203 * Builds additional query constraints to exclude hidden languages and
204 * limit a backend user to its allowed languages (unless the user is an admin)
205 *
206 * @return array
207 */
208 protected function getAllowedLanguageConstraintsForBackendUser(): array
209 {
210 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('sys_language');
211 $constraints = [];
212
213 $backendUser = $this->getBackendUser();
214 if (!$backendUser->isAdmin()) {
215 if (!empty($GLOBALS['TCA']['sys_language']['ctrl']['enablecolumns']['disabled'])) {
216 $constraints[] = $queryBuilder->expr()->eq(
217 'sys_language.' . $GLOBALS['TCA']['sys_language']['ctrl']['enablecolumns']['disabled'],
218 $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
219 );
220 }
221
222 if (!empty($backendUser->user['allowed_languages'])) {
223 $constraints[] = $queryBuilder->expr()->in(
224 'sys_language.uid',
225 $queryBuilder->createNamedParameter(
226 GeneralUtility::intExplode(',', $backendUser->user['allowed_languages'], true),
227 Connection::PARAM_INT_ARRAY
228 )
229 );
230 }
231 }
232
233 return $constraints;
234 }
235
236 /**
237 * Get records for copy process
238 *
239 * @param int $pageId
240 * @param int $colPos
241 * @param int $destLanguageId
242 * @param int $languageId
243 * @param string $fields
244 * @return \Doctrine\DBAL\Driver\Statement
245 */
246 public function getRecordsToCopyDatabaseResult($pageId, $colPos, $destLanguageId, $languageId, $fields = '*')
247 {
248 $originalUids = [];
249
250 // Get original uid of existing elements triggered language / colpos
251 $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
252
253 $originalUidsStatement = $queryBuilder
254 ->select('t3_origuid')
255 ->from('tt_content')
256 ->where(
257 $queryBuilder->expr()->eq(
258 'sys_language_uid',
259 $queryBuilder->createNamedParameter($destLanguageId, \PDO::PARAM_INT)
260 ),
261 $queryBuilder->expr()->eq(
262 'tt_content.colPos',
263 $queryBuilder->createNamedParameter($colPos, \PDO::PARAM_INT)
264 ),
265 $queryBuilder->expr()->eq(
266 'tt_content.pid',
267 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
268 )
269 )
270 ->execute();
271
272 while ($origUid = $originalUidsStatement->fetchColumn(0)) {
273 $originalUids[] = (int)$origUid;
274 }
275
276 $queryBuilder->select(...GeneralUtility::trimExplode(',', $fields, true))
277 ->from('tt_content')
278 ->where(
279 $queryBuilder->expr()->eq(
280 'tt_content.sys_language_uid',
281 $queryBuilder->createNamedParameter($languageId, \PDO::PARAM_INT)
282 ),
283 $queryBuilder->expr()->eq(
284 'tt_content.colPos',
285 $queryBuilder->createNamedParameter($colPos, \PDO::PARAM_INT)
286 ),
287 $queryBuilder->expr()->eq(
288 'tt_content.pid',
289 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
290 )
291 )
292 ->orderBy('tt_content.sorting');
293
294 if (!empty($originalUids)) {
295 $queryBuilder
296 ->andWhere(
297 $queryBuilder->expr()->notIn(
298 'tt_content.uid',
299 $queryBuilder->createNamedParameter($originalUids, Connection::PARAM_INT_ARRAY)
300 )
301 );
302 }
303
304 return $queryBuilder->execute();
305 }
306
307 /**
308 * Fetches the localization for a given record.
309 *
310 * @FIXME: This method is a clone of BackendUtility::getRecordLocalization, using origUid instead of transOrigPointerField
311 *
312 * @param string $table Table name present in $GLOBALS['TCA']
313 * @param int $uid The uid of the record
314 * @param int $language The uid of the language record in sys_language
315 * @param string $andWhereClause Optional additional WHERE clause (default: '')
316 * @return mixed Multidimensional array with selected records; if none exist, FALSE is returned
317 *
318 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
319 */
320 public function getRecordLocalization($table, $uid, $language, $andWhereClause = '')
321 {
322 GeneralUtility::logDeprecatedFunction();
323 $recordLocalization = false;
324
325 // Pages still stores translations in the pages_language_overlay table, all other tables store in themself
326 if ($table === 'pages') {
327 $table = 'pages_language_overlay';
328 }
329
330 if (BackendUtility::isTableLocalizable($table)) {
331 $tcaCtrl = $GLOBALS['TCA'][$table]['ctrl'];
332
333 if (isset($tcaCtrl['origUid'])) {
334 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
335 ->getQueryBuilderForTable($table);
336 $queryBuilder->getRestrictions()
337 ->removeAll()
338 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
339 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
340
341 $queryBuilder->select('*')
342 ->from($table)
343 ->where(
344 $queryBuilder->expr()->eq(
345 $tcaCtrl['origUid'],
346 $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
347 ),
348 $queryBuilder->expr()->eq(
349 $tcaCtrl['languageField'],
350 $queryBuilder->createNamedParameter((int)$language, \PDO::PARAM_INT)
351 )
352 )
353 ->setMaxResults(1);
354
355 if ($andWhereClause) {
356 $queryBuilder->andWhere(QueryHelper::stripLogicalOperatorPrefix($andWhereClause));
357 }
358
359 $recordLocalization = $queryBuilder->execute()->fetchAll();
360 }
361 }
362 return $recordLocalization;
363 }
364
365 /**
366 * Returning uid of previous localized record, if any, for tables with a "sortby" column
367 * Used when new localized records are created so that localized records are sorted in the same order as the default language records
368 *
369 * @FIXME: This method is a clone of DataHandler::getPreviousLocalizedRecordUid which is protected there and uses
370 * BackendUtility::getRecordLocalization which we also needed to clone in this class. Also, this method takes two
371 * language arguments.
372 *
373 * @param string $table Table name
374 * @param int $uid Uid of default language record
375 * @param int $pid Pid of default language record
376 * @param int $sourceLanguage Language of origin
377 * @param int $destinationLanguage Language of localization
378 * @return int uid of record after which the localized record should be inserted
379 *
380 * @deprecated since TYPO3 v8, will be removed in TYPO3 v9
381 */
382 public function getPreviousLocalizedRecordUid($table, $uid, $pid, $sourceLanguage, $destinationLanguage)
383 {
384 GeneralUtility::logDeprecatedFunction();
385 $previousLocalizedRecordUid = $uid;
386 if ($GLOBALS['TCA'][$table] && $GLOBALS['TCA'][$table]['ctrl']['sortby']) {
387 $sortRow = $GLOBALS['TCA'][$table]['ctrl']['sortby'];
388 $select = [$sortRow, 'pid', 'uid'];
389 // For content elements, we also need the colPos
390 if ($table === 'tt_content') {
391 $select[] = 'colPos';
392 }
393 // Get the sort value of the default language record
394 $row = BackendUtility::getRecord($table, $uid, implode(',', $select));
395 if (is_array($row)) {
396 $queryBuilder = $this->getQueryBuilderWithWorkspaceRestriction('tt_content');
397
398 $queryBuilder->select(...$select)
399 ->from($table)
400 ->where(
401 $queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pid, \PDO::PARAM_INT)),
402 $queryBuilder->expr()->eq(
403 'sys_language_uid',
404 $queryBuilder->createNamedParameter($sourceLanguage, \PDO::PARAM_INT)
405 ),
406 $queryBuilder->expr()->lt(
407 $sortRow,
408 $queryBuilder->createNamedParameter($row[$sortRow], \PDO::PARAM_INT)
409 )
410 );
411
412 // Respect the colPos for content elements
413 if ($table === 'tt_content') {
414 $queryBuilder->andWhere(
415 $queryBuilder->expr()->eq(
416 'colPos',
417 $queryBuilder->createNamedParameter($row['colPos'], \PDO::PARAM_INT)
418 )
419 );
420 }
421
422 $previousRow = $queryBuilder->orderBy($sortRow, 'DESC')->execute()->fetch();
423
424 // If there is an element, find its localized record in specified localization language
425 if ($previousRow !== false) {
426 $previousLocalizedRecord = $this->getRecordLocalization(
427 $table,
428 $previousRow['uid'],
429 $destinationLanguage
430 );
431 if (is_array($previousLocalizedRecord[0])) {
432 $previousLocalizedRecordUid = $previousLocalizedRecord[0]['uid'];
433 }
434 }
435 }
436 }
437
438 return $previousLocalizedRecordUid;
439 }
440
441 /**
442 * Returns the current BE user.
443 *
444 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
445 */
446 protected function getBackendUser()
447 {
448 return $GLOBALS['BE_USER'];
449 }
450
451 /**
452 * Get a QueryBuilder for the given table with preconfigured restrictions
453 * to not retrieve workspace placeholders or deleted records.
454 *
455 * @param string $tableName
456 * @return \TYPO3\CMS\Core\Database\Query\QueryBuilder
457 */
458 protected function getQueryBuilderWithWorkspaceRestriction(string $tableName): QueryBuilder
459 {
460 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($tableName);
461 $queryBuilder->getRestrictions()
462 ->removeAll()
463 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
464 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
465
466 return $queryBuilder;
467 }
468 }