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