[FOLLOWUP][BUGFIX] Fix CSRF protection token in localization overview
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Controller / TranslationStatusController.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Controller;
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\Tree\View\PageTreeView;
18 use TYPO3\CMS\Backend\Utility\BackendUtility;
19 use TYPO3\CMS\Backend\Utility\IconUtility;
20 use TYPO3\CMS\Core\Utility\GeneralUtility;
21
22 /**
23 * Class for displaying translation status of pages in the tree.
24 */
25 class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunctionModule {
26
27 /**
28 * Returns the menu array
29 *
30 * @return array
31 */
32 public function modMenu() {
33 $lang = $this->getLanguageService();
34 $menuArray = array(
35 'depth' => array(
36 0 => $lang->getLL('depth_0'),
37 1 => $lang->getLL('depth_1'),
38 2 => $lang->getLL('depth_2'),
39 3 => $lang->getLL('depth_3'),
40 999 => $lang->sL('LLL:EXT:lang/locallang_core.xlf:labels.depth_infi')
41 )
42 );
43 // Languages:
44 $lang = $this->getSystemLanguages();
45 $menuArray['lang'] = array(
46 0 => '[All]'
47 );
48 foreach ($lang as $langRec) {
49 $menuArray['lang'][$langRec['uid']] = $langRec['title'];
50 }
51 return $menuArray;
52 }
53
54 /**
55 * MAIN function for page information of localization
56 *
57 * @return string Output HTML for the module.
58 */
59 public function main() {
60 $theOutput = $this->pObj->doc->header($this->getLanguageService()->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_title'));
61 if ($this->pObj->id) {
62 // Depth selector:
63 $theOutput .= '<div class="form-inline form-inline-spaced">';
64 $h_func = BackendUtility::getDropdownMenu($this->pObj->id, 'SET[depth]', $this->pObj->MOD_SETTINGS['depth'], $this->pObj->MOD_MENU['depth']);
65 $h_func .= BackendUtility::getDropdownMenu($this->pObj->id, 'SET[lang]', $this->pObj->MOD_SETTINGS['lang'], $this->pObj->MOD_MENU['lang']);
66 $theOutput .= $h_func;
67 // Add CSH:
68 $theOutput .= BackendUtility::cshItem('_MOD_web_info', 'lang', NULL, '|<br />');
69 $theOutput .= '</div>';
70 // Showing the tree:
71 // Initialize starting point of page tree:
72 $treeStartingPoint = (int)$this->pObj->id;
73 $treeStartingRecord = BackendUtility::getRecordWSOL('pages', $treeStartingPoint);
74 $depth = $this->pObj->MOD_SETTINGS['depth'];
75 // Initialize tree object:
76 $tree = GeneralUtility::makeInstance(PageTreeView::class);
77 $tree->init('AND ' . $this->getBackendUser()->getPagePermsClause(1));
78 $tree->addField('l18n_cfg');
79 // Creating top icon; the current page
80 $HTML = IconUtility::getSpriteIconForRecord('pages', $treeStartingRecord);
81 $tree->tree[] = array(
82 'row' => $treeStartingRecord,
83 'HTML' => $HTML
84 );
85 // Create the tree from starting point:
86 if ($depth) {
87 $tree->getTree($treeStartingPoint, $depth, '');
88 }
89 // Render information table:
90 $theOutput .= $this->renderL10nTable($tree);
91 }
92 return $theOutput;
93 }
94
95 /**
96 * Rendering the localization information table.
97 *
98 * @param array $tree The Page tree data
99 * @return string HTML for the localization information table.
100 */
101 public function renderL10nTable(&$tree) {
102 $lang = $this->getLanguageService();
103 // System languages retrieved:
104 $languages = $this->getSystemLanguages();
105 // Title length:
106 $titleLen = $this->getBackendUser()->uc['titleLen'];
107 // Put together the TREE:
108 $output = '';
109 $newOL_js = array();
110 $langRecUids = array();
111 foreach ($tree->tree as $data) {
112 $tCells = array();
113 $langRecUids[0][] = $data['row']['uid'];
114 // Page icons / titles etc.
115 $tCells[] = '<td' . ($data['row']['_CSSCLASS'] ? ' class="' . $data['row']['_CSSCLASS'] . '"' : '') . '>' .
116 ($data['depthData'] ?: '') .
117 $GLOBALS['SOBE']->doc->wrapClickMenuOnIcon($data['HTML'], 'pages', $data['row']['uid']) .
118 '<a href="#" onclick="' . htmlspecialchars(
119 'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=0"); return false;'
120 ) . '" title="' . $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPage') . '">' .
121 htmlspecialchars(GeneralUtility::fixed_lgd_cs($data['row']['title'], $titleLen)) .
122 '</a>' .
123 ((string)$data['row']['nav_title'] !== '' ? ' [Nav: <em>' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($data['row']['nav_title'], $titleLen)) . '</em>]' : '') .
124 '</td>';
125 // DEFAULT language:
126 // "View page" link is created:
127 $viewPageLink = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::viewOnClick(
128 $data['row']['uid'], $GLOBALS['BACK_PATH'], '', '', '', '&L=###LANG_UID###')
129 ) . '" title="' . $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewPage') . '">' .
130 IconUtility::getSpriteIcon('actions-document-view') . '</a>';
131 $status = $data['row']['l18n_cfg'] & 1 ? 'danger' : 'success';
132 // Create links:
133 $info = '';
134 $editUid = $data['row']['uid'];
135 $params = '&edit[pages][' . $editUid . ']=edit';
136 $info .= '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params))
137 . '" title="' . $lang->sL(
138 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editDefaultLanguagePage'
139 ) . '">' . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
140 $info .= str_replace('###LANG_UID###', '0', $viewPageLink);
141 $info .= '&nbsp;';
142 $info .= $data['row']['l18n_cfg'] & 1 ? '<span title="' . $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.1', TRUE) . '">D</span>' : '&nbsp;';
143 $info .= GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) ? '<span title="' . $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.2', TRUE) . '">N</span>' : '&nbsp;';
144 // Put into cell:
145 $tCells[] = '<td class="' . $status . ' col-border-left">' . $info . '</td>';
146 $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
147 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
148 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], 0) . '</td>';
149 $modSharedTSconfig = BackendUtility::getModTSconfig($data['row']['uid'], 'mod.SHARED');
150 $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages']) ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], TRUE) : array();
151 // Traverse system languages:
152 foreach ($languages as $langRow) {
153 if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
154 $row = $this->getLangStatus($data['row']['uid'], $langRow['uid']);
155 $info = '';
156 if (is_array($row)) {
157 $langRecUids[$langRow['uid']][] = $row['uid'];
158 $status = $row['_HIDDEN'] ? (GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || $data['row']['l18n_cfg'] & 1 ? 'danger' : '') : 'success';
159 $icon = IconUtility::getSpriteIconForRecord(
160 'pages_language_overlay',
161 $row,
162 array('class' => 'c-recIcon')
163 );
164 $info = $icon . htmlspecialchars(
165 GeneralUtility::fixed_lgd_cs($row['title'], $titleLen)
166 ) . ((string)$row['nav_title'] !== '' ? ' [Nav: <em>' . htmlspecialchars(
167 GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen)
168 ) . '</em>]' : '') . ($row['_COUNT'] > 1 ? '<div>' . $lang->sL(
169 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_badThingThereAre'
170 ) . '</div>' : '');
171 $tCells[] = '<td class="' . $status . ' col-border-left">' .
172 '<a href="#" onclick="' . htmlspecialchars(
173 'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=' . $langRow['uid'] . '"); return false;'
174 ) . '" title="' . $lang->sL(
175 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageLang'
176 ) . '">' . $info . '</a></td>';
177 // Edit whole record:
178 $info = '';
179 $editUid = $row['uid'];
180 $params = '&edit[pages_language_overlay][' . $editUid . ']=edit';
181 $info .= '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params))
182 . '" title="' . $lang->sL(
183 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLanguageOverlayRecord'
184 ) . '">' . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
185 $info .= str_replace('###LANG_UID###', $langRow['uid'], $viewPageLink);
186 $tCells[] = '<td class="' . $status . '">' . $info . '</td>';
187 $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
188 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
189 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], $langRow['uid']) . '</td>';
190 } else {
191 if (in_array($langRow['uid'], $disableLanguages)) {
192 // Language has been disabled for this page
193 $status = 'danger';
194 $info = '';
195 } else {
196 $status = GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || $data['row']['l18n_cfg'] & 1 ? 'danger' : '';
197 $info = '<input type="checkbox" name="newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']" value="1" />';
198 $newOL_js[$langRow['uid']] .= '
199 +(document.webinfoForm['
200 . GeneralUtility::quoteJSvalue('newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']')
201 . '].checked ? '
202 . GeneralUtility::quoteJSvalue('&edit[pages_language_overlay][' . $data['row']['uid'] . ']=new')
203 . ' : \'\')
204 ';
205 }
206 $tCells[] = '<td class="' . $status . ' col-border-left">&nbsp;</td>';
207 $tCells[] = '<td class="' . $status . '">&nbsp;</td>';
208 $tCells[] = '<td class="' . $status . '">' . $info . '</td>';
209 }
210 }
211 }
212 $output .= '
213 <tr>
214 ' . implode('
215 ', $tCells) . '
216 </tr>';
217 }
218 // Put together HEADER:
219 $tCells = array();
220 $tCells[] = '<td>' . $lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_page') . ':</td>';
221 if (is_array($langRecUids[0])) {
222 $params = '&edit[pages][' . implode(',', $langRecUids[0]) . ']=edit&columnsOnly=title,nav_title,l18n_cfg,hidden';
223 $editIco = '<a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params))
224 . '" title="' . $lang->sL(
225 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageProperties'
226 ) . '">' . IconUtility::getSpriteIcon('actions-document-open') . '</a>';
227 } else {
228 $editIco = '';
229 }
230 $tCells[] = '<td class="col-border-left" colspan="2">' . $lang->sL(
231 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_default'
232 ) . ':' . $editIco . '</td>';
233 foreach ($languages as $langRow) {
234 if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
235 // Title:
236 $tCells[] = '<td class="col-border-left">' . htmlspecialchars($langRow['title']) . '</td>';
237 // Edit language overlay records:
238 if (is_array($langRecUids[$langRow['uid']])) {
239 $params = '&edit[pages_language_overlay][' .
240 implode(',', $langRecUids[$langRow['uid']]) .
241 ']=edit&columnsOnly=title,nav_title,hidden';
242 $tCells[] = '<td><a href="#" onclick="' . htmlspecialchars(BackendUtility::editOnClick($params))
243 . '" title="' . $lang->sL(
244 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLangOverlays'
245 ) . '">' . IconUtility::getSpriteIcon('actions-document-open') . '</a></td>';
246 } else {
247 $tCells[] = '<td>&nbsp;</td>';
248 }
249 // Create new overlay records:
250 $params = '&columnsOnly=title,hidden,sys_language_uid&overrideVals[pages_language_overlay][sys_language_uid]=' . $langRow['uid'];
251 $onClick = BackendUtility::editOnClick($params);
252 if (!empty($newOL_js[$langRow['uid']])) {
253 $onClickArray = explode('?', $onClick, 2);
254 $lastElement = array_pop($onClickArray);
255 array_push($onClickArray, '\'' . $newOL_js[$langRow['uid']] . ' + \'&' . $lastElement);
256 $onClick = implode('?', $onClickArray);
257 }
258 $tCells[] = '<td><a href="#" onclick="' . htmlspecialchars($onClick)
259 . '" title="' . $lang->sL(
260 'LLL:EXT:frontend/Resources/Private/Language/locallang_webinfo.xlf:lang_getlangsta_createNewTranslationHeaders'
261 ) . '">' . IconUtility::getSpriteIcon('actions-document-new') . '</a></td>';
262 }
263 }
264
265 $output =
266 '<div class="table-fit">' .
267 '<table class="table table-striped table-hover" id="langTable">' .
268 '<thead>' .
269 '<tr>' .
270 implode('', $tCells) .
271 '</tr>' .
272 '</thead>' .
273 '<tbody>' .
274 $output .
275 '</tbody>' .
276 '</table>' .
277 '</div>';
278 return $output;
279 }
280
281 /**
282 * Selects all system languages (from sys_language)
283 *
284 * @return array System language records in an array.
285 */
286 public function getSystemLanguages() {
287 if (!$this->getBackendUser()->user['admin'] && $this->getBackendUser()->groupData['allowed_languages'] !== '') {
288 $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
289 }
290 $res = $this->getDatabaseConnection()->exec_SELECTquery('*', 'sys_language', '1=1' . BackendUtility::deleteClause('sys_language'));
291 $outputArray = array();
292 while ($row = $this->getDatabaseConnection()->sql_fetch_assoc($res)) {
293 if (is_array($allowed_languages) && !empty($allowed_languages)) {
294 if (isset($allowed_languages[$row['uid']])) {
295 $outputArray[] = $row;
296 }
297 } else {
298 $outputArray[] = $row;
299 }
300 }
301 $this->getDatabaseConnection()->sql_free_result($res);
302 return $outputArray;
303 }
304
305 /**
306 * Get an alternative language record for a specific page / language
307 *
308 * @param int $pageId Page ID to look up for.
309 * @param int $langId Language UID to select for.
310 * @return array pages_languages_overlay record
311 */
312 public function getLangStatus($pageId, $langId) {
313 $res = $this->getDatabaseConnection()->exec_SELECTquery(
314 '*',
315 'pages_language_overlay',
316 'pid=' . (int)$pageId .
317 ' AND sys_language_uid=' . (int)$langId .
318 BackendUtility::deleteClause('pages_language_overlay') .
319 BackendUtility::versioningPlaceholderClause('pages_language_overlay')
320 );
321 $row = $this->getDatabaseConnection()->sql_fetch_assoc($res);
322 BackendUtility::workspaceOL('pages_language_overlay', $row);
323 if (is_array($row)) {
324 $row['_COUNT'] = $this->getDatabaseConnection()->sql_num_rows($res);
325 $row['_HIDDEN'] = $row['hidden'] || (int)$row['endtime'] > 0 && (int)$row['endtime'] < $GLOBALS['EXEC_TIME'] || $GLOBALS['EXEC_TIME'] < (int)$row['starttime'];
326 }
327 return $row;
328 }
329
330 /**
331 * Counting content elements for a single language on a page.
332 *
333 * @param int $pageId Page id to select for.
334 * @param int $sysLang Sys language uid
335 * @return int Number of content elements from the PID where the language is set to a certain value.
336 */
337 public function getContentElementCount($pageId, $sysLang) {
338 $count = $this->getDatabaseConnection()->exec_SELECTcountRows('uid', 'tt_content', 'pid=' . (int)$pageId . ' AND sys_language_uid=' . (int)$sysLang . BackendUtility::deleteClause('tt_content') . BackendUtility::versioningPlaceholderClause('tt_content'));
339 return $count ?: '-';
340 }
341
342 /**
343 * Returns LanguageService
344 *
345 * @return \TYPO3\CMS\Lang\LanguageService
346 */
347 protected function getLanguageService() {
348 return $GLOBALS['LANG'];
349 }
350
351 /**
352 * Returns the database connection
353 *
354 * @return \TYPO3\CMS\Core\Database\DatabaseConnection
355 */
356 protected function getDatabaseConnection() {
357 return $GLOBALS['TYPO3_DB'];
358 }
359
360 /**
361 * Returns the current BE user.
362 *
363 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
364 */
365 protected function getBackendUser() {
366 return $GLOBALS['BE_USER'];
367 }
368
369 }