[BUGFIX] Use HTTP 400 instead of 500 when client is faulty
[Packages/TYPO3.CMS.git] / typo3 / sysext / backend / Classes / Controller / Page / LocalizationController.php
1 <?php
2 namespace TYPO3\CMS\Backend\Controller\Page;
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 Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Configuration\TranslationConfigurationProvider;
20 use TYPO3\CMS\Backend\Domain\Repository\Localization\LocalizationRepository;
21 use TYPO3\CMS\Core\DataHandling\DataHandler;
22 use TYPO3\CMS\Core\Imaging\Icon;
23 use TYPO3\CMS\Core\Imaging\IconFactory;
24 use TYPO3\CMS\Core\Utility\GeneralUtility;
25
26 /**
27 * LocalizationController handles the AJAX requests for record localization
28 */
29 class LocalizationController
30 {
31 /**
32 * @const string
33 */
34 const ACTION_COPY = 'copyFromLanguage';
35
36 /**
37 * @const string
38 */
39 const ACTION_LOCALIZE = 'localize';
40
41 /**
42 * @var IconFactory
43 */
44 protected $iconFactory;
45
46 /**
47 * @var LocalizationRepository
48 */
49 protected $localizationRepository;
50
51 /**
52 * Constructor
53 */
54 public function __construct()
55 {
56 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
57 $this->localizationRepository = GeneralUtility::makeInstance(LocalizationRepository::class);
58 }
59
60 /**
61 * Get used languages in a colPos of a page
62 *
63 * @param ServerRequestInterface $request
64 * @param ResponseInterface $response
65 * @return ResponseInterface
66 */
67 public function getUsedLanguagesInPageAndColumn(ServerRequestInterface $request, ResponseInterface $response)
68 {
69 $params = $request->getQueryParams();
70 if (!isset($params['pageId'], $params['colPos'], $params['languageId'])) {
71 $response = $response->withStatus(400);
72 return $response;
73 }
74
75 $pageId = (int)$params['pageId'];
76 $colPos = (int)$params['colPos'];
77 $languageId = (int)$params['languageId'];
78
79 /** @var TranslationConfigurationProvider $translationProvider */
80 $translationProvider = GeneralUtility::makeInstance(TranslationConfigurationProvider::class);
81 $systemLanguages = $translationProvider->getSystemLanguages($pageId);
82
83 $availableLanguages = [];
84
85 // First check whether column has localized records
86 $elementsInColumnCount = $this->localizationRepository->getLocalizedRecordCount($pageId, $colPos, $languageId);
87
88 if ($elementsInColumnCount === 0) {
89 $fetchedAvailableLanguages = $this->localizationRepository->fetchAvailableLanguages($pageId, $colPos, $languageId);
90 $availableLanguages[] = $systemLanguages[0];
91
92 foreach ($fetchedAvailableLanguages as $language) {
93 if (isset($systemLanguages[$language['uid']])) {
94 $availableLanguages[] = $systemLanguages[$language['uid']];
95 }
96 }
97 } else {
98 $result = $this->localizationRepository->fetchOriginLanguage($pageId, $colPos, $languageId);
99 $availableLanguages[] = $systemLanguages[$result['sys_language_uid']];
100 }
101
102 // Pre-render all flag icons
103 foreach ($availableLanguages as &$language) {
104 if ($language['flagIcon'] === 'empty-empty') {
105 $language['flagIcon'] = '';
106 } else {
107 $language['flagIcon'] = $this->iconFactory->getIcon($language['flagIcon'], Icon::SIZE_SMALL)->render();
108 }
109 }
110
111 $response->getBody()->write(json_encode($availableLanguages));
112 return $response;
113 }
114
115 /**
116 * Get a prepared summary of records being translated
117 *
118 * @param ServerRequestInterface $request
119 * @param ResponseInterface $response
120 * @return ResponseInterface
121 */
122 public function getRecordLocalizeSummary(ServerRequestInterface $request, ResponseInterface $response)
123 {
124 $params = $request->getQueryParams();
125 if (!isset($params['pageId'], $params['colPos'], $params['destLanguageId'], $params['languageId'])) {
126 $response = $response->withStatus(400);
127 return $response;
128 }
129
130 $records = [];
131 $result = $this->localizationRepository->getRecordsToCopyDatabaseResult(
132 $params['pageId'],
133 $params['colPos'],
134 $params['destLanguageId'],
135 $params['languageId'],
136 '*'
137 );
138
139 while ($row = $result->fetch()) {
140 $records[] = [
141 'icon' => $this->iconFactory->getIconForRecord('tt_content', $row, Icon::SIZE_SMALL)->render(),
142 'title' => $row[$GLOBALS['TCA']['tt_content']['ctrl']['label']],
143 'uid' => $row['uid']
144 ];
145 }
146
147 $response->getBody()->write(json_encode($records));
148 return $response;
149 }
150
151 /**
152 * @param ServerRequestInterface $request
153 * @param ResponseInterface $response
154 * @return ResponseInterface
155 */
156 public function localizeRecords(ServerRequestInterface $request, ResponseInterface $response)
157 {
158 $params = $request->getQueryParams();
159 if (!isset($params['pageId'], $params['srcLanguageId'], $params['destLanguageId'], $params['action'], $params['uidList'])) {
160 $response = $response->withStatus(400);
161 return $response;
162 }
163
164 if ($params['action'] !== static::ACTION_COPY && $params['action'] !== static::ACTION_LOCALIZE) {
165 $response->getBody()->write('Invalid action "' . $params['action'] . '" called.');
166 $response = $response->withStatus(400);
167 return $response;
168 }
169
170 // Filter transmitted but invalid uids
171 $params['uidList'] = $this->filterInvalidUids(
172 (int)$params['pageId'],
173 (int)$params['colPos'],
174 (int)$params['destLanguageId'],
175 (int)$params['srcLanguageId'],
176 $params['uidList']
177 );
178
179 $this->process($params);
180
181 $response->getBody()->write(json_encode([]));
182 return $response;
183 }
184
185 /**
186 * Gets all possible UIDs of a page, colPos and language that might be processed and removes invalid UIDs that might
187 * be smuggled in.
188 *
189 * @param int $pageId
190 * @param int $colPos
191 * @param int $destLanguageId
192 * @param int $srcLanguageId
193 * @param array $transmittedUidList
194 * @return array
195 */
196 protected function filterInvalidUids(
197 int $pageId,
198 int $colPos,
199 int $destLanguageId,
200 int $srcLanguageId,
201 array $transmittedUidList
202 ): array {
203 // Get all valid uids that can be processed
204 $validUidList = $result = $this->localizationRepository->getRecordsToCopyDatabaseResult(
205 $pageId,
206 $colPos,
207 $destLanguageId,
208 $srcLanguageId,
209 'uid'
210 );
211
212 return array_intersect(array_unique($transmittedUidList), array_column($validUidList->fetchAll(), 'uid'));
213 }
214
215 /**
216 * Processes the localization actions
217 *
218 * @param array $params
219 */
220 protected function process($params)
221 {
222 $destLanguageId = (int)$params['destLanguageId'];
223
224 // Build command map
225 $cmd = [
226 'tt_content' => []
227 ];
228
229 if (isset($params['uidList']) && is_array($params['uidList'])) {
230 foreach ($params['uidList'] as $currentUid) {
231 if ($params['action'] === static::ACTION_LOCALIZE) {
232 $cmd['tt_content'][$currentUid] = [
233 'localize' => $destLanguageId
234 ];
235 } else {
236 $cmd['tt_content'][$currentUid] = [
237 'copyToLanguage' => $destLanguageId,
238 ];
239 }
240 }
241 }
242
243 $dataHandler = GeneralUtility::makeInstance(DataHandler::class);
244 $dataHandler->start([], $cmd);
245 $dataHandler->process_cmdmap();
246 }
247 }