c44e63c9b3f3a3765e3100496f51a4ff9e9696b0
[Packages/TYPO3.CMS.git] / typo3 / sysext / info / Classes / Controller / TranslationStatusController.php
1 <?php
2
3 namespace TYPO3\CMS\Info\Controller;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Backend\Tree\View\PageTreeView;
19 use TYPO3\CMS\Backend\Utility\BackendUtility;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
22 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
23 use TYPO3\CMS\Core\Imaging\Icon;
24 use TYPO3\CMS\Core\Imaging\IconFactory;
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26
27 /**
28 * Class for displaying translation status of pages in the tree.
29 */
30 class TranslationStatusController extends \TYPO3\CMS\Backend\Module\AbstractFunctionModule
31 {
32 /**
33 * @var IconFactory
34 */
35 protected $iconFactory;
36
37 /**
38 * Construct for initialize class variables
39 */
40 public function __construct()
41 {
42 $this->iconFactory = GeneralUtility::makeInstance(IconFactory::class);
43 }
44
45 /**
46 * Returns the menu array
47 *
48 * @return array
49 */
50 public function modMenu()
51 {
52 $lang = $this->getLanguageService();
53 $menuArray = [
54 'depth' => [
55 0 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_0'),
56 1 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_1'),
57 2 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_2'),
58 3 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_3'),
59 4 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_4'),
60 999 => $lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.depth_infi')
61 ]
62 ];
63 // Languages:
64 $lang = $this->getSystemLanguages();
65 $menuArray['lang'] = [
66 0 => '[All]'
67 ];
68 foreach ($lang as $langRec) {
69 $menuArray['lang'][$langRec['uid']] = $langRec['title'];
70 }
71 return $menuArray;
72 }
73
74 /**
75 * MAIN function for page information of localization
76 *
77 * @return string Output HTML for the module.
78 */
79 public function main()
80 {
81 $theOutput = '<h1>' . htmlspecialchars($this->getLanguageService()->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_title')) . '</h1>';
82 if ($this->pObj->id) {
83 // Depth selector:
84 $theOutput .= '<div class="form-inline form-inline-spaced">';
85 $h_func = BackendUtility::getDropdownMenu($this->pObj->id, 'SET[depth]', $this->pObj->MOD_SETTINGS['depth'], $this->pObj->MOD_MENU['depth']);
86 $h_func .= BackendUtility::getDropdownMenu($this->pObj->id, 'SET[lang]', $this->pObj->MOD_SETTINGS['lang'], $this->pObj->MOD_MENU['lang']);
87 $theOutput .= $h_func;
88 // Add CSH:
89 $theOutput .= BackendUtility::cshItem('_MOD_web_info', 'lang', null, '<div class="form-group"><span class="btn btn-default btn-sm">|</span></div><br />');
90 $theOutput .= '</div>';
91 // Showing the tree:
92 // Initialize starting point of page tree:
93 $treeStartingPoint = (int)$this->pObj->id;
94 $treeStartingRecord = BackendUtility::getRecordWSOL('pages', $treeStartingPoint);
95 $depth = $this->pObj->MOD_SETTINGS['depth'];
96 // Initialize tree object:
97 $tree = GeneralUtility::makeInstance(PageTreeView::class);
98 $tree->init('AND ' . $this->getBackendUser()->getPagePermsClause(1));
99 $tree->addField('l18n_cfg');
100 // Creating top icon; the current page
101 $HTML = $this->iconFactory->getIconForRecord('pages', $treeStartingRecord, Icon::SIZE_SMALL)->render();
102 $tree->tree[] = [
103 'row' => $treeStartingRecord,
104 'HTML' => $HTML
105 ];
106 // Create the tree from starting point:
107 if ($depth) {
108 $tree->getTree($treeStartingPoint, $depth, '');
109 }
110 // Render information table:
111 $theOutput .= $this->renderL10nTable($tree);
112 }
113 return $theOutput;
114 }
115
116 /**
117 * Rendering the localization information table.
118 *
119 * @param array $tree The Page tree data
120 * @return string HTML for the localization information table.
121 */
122 public function renderL10nTable(&$tree)
123 {
124 $lang = $this->getLanguageService();
125 // System languages retrieved:
126 $languages = $this->getSystemLanguages();
127 // Title length:
128 $titleLen = $this->getBackendUser()->uc['titleLen'];
129 // Put together the TREE:
130 $output = '';
131 $newOL_js = [];
132 $langRecUids = [];
133 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
134 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
135 foreach ($tree->tree as $data) {
136 $tCells = [];
137 $langRecUids[0][] = $data['row']['uid'];
138 // Page icons / titles etc.
139 $tCells[] = '<td' . ($data['row']['_CSSCLASS'] ? ' class="' . $data['row']['_CSSCLASS'] . '"' : '') . '>' .
140 ($data['depthData'] ?: '') .
141 BackendUtility::wrapClickMenuOnIcon($data['HTML'], 'pages', $data['row']['uid']) .
142 '<a href="#" onclick="' . htmlspecialchars(
143 'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=0"); return false;'
144 ) . '" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPage') . '">' .
145 htmlspecialchars(GeneralUtility::fixed_lgd_cs($data['row']['title'], $titleLen)) .
146 '</a>' .
147 ((string)$data['row']['nav_title'] !== '' ? ' [Nav: <em>' . htmlspecialchars(GeneralUtility::fixed_lgd_cs($data['row']['nav_title'], $titleLen)) . '</em>]' : '') .
148 '</td>';
149 // DEFAULT language:
150 // "View page" link is created:
151 $viewPageLink = '<a href="#" onclick="' . htmlspecialchars(
152 BackendUtility::viewOnClick(
153 $data['row']['uid'],
154 '',
155 null,
156 '',
157 '',
158 '&L=###LANG_UID###'
159 )
160 ) . '" class="btn btn-default" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewPage') . '">' .
161 $this->iconFactory->getIcon('actions-view', Icon::SIZE_SMALL)->render() . '</a>';
162 $status = GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : 'success';
163 // Create links:
164 $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
165 'edit' => [
166 'pages' => [
167 $data['row']['uid'] => 'edit'
168 ]
169 ],
170 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
171 ]);
172 $info = '<a href="#" onclick="' . htmlspecialchars(
173 BackendUtility::viewOnClick(
174 $data['row']['uid'],
175 '',
176 null,
177 '',
178 '',
179 ''
180 )
181 ) . '" class="btn btn-default" title="' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_viewPage') . '">' .
182 $this->iconFactory->getIcon('actions-view-page', Icon::SIZE_SMALL)->render() . '</a>';
183 $info .= '<a href="' . htmlspecialchars($editUrl)
184 . '" class="btn btn-default" title="' . $lang->sL(
185 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editDefaultLanguagePage'
186 ) . '">' . $this->iconFactory->getIcon('actions-page-open', Icon::SIZE_SMALL)->render() . '</a>';
187 $info .= '&nbsp;';
188 $info .= GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.1')) . '">D</span>' : '&nbsp;';
189 $info .= GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) ? '<span title="' . htmlspecialchars($lang->sL('LLL:EXT:frontend/Resources/Private/Language/locallang_tca.xlf:pages.l18n_cfg.I.2')) . '">N</span>' : '&nbsp;';
190 // Put into cell:
191 $tCells[] = '<td class="' . $status . ' col-border-left"><div class="btn-group">' . $info . '</div></td>';
192 $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
193 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
194 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], 0) . '</td>';
195 $modSharedTSconfig = BackendUtility::getModTSconfig($data['row']['uid'], 'mod.SHARED');
196 $disableLanguages = isset($modSharedTSconfig['properties']['disableLanguages']) ? GeneralUtility::trimExplode(',', $modSharedTSconfig['properties']['disableLanguages'], true) : [];
197 // Traverse system languages:
198 foreach ($languages as $langRow) {
199 if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
200 $row = $this->getLangStatus($data['row']['uid'], $langRow['uid']);
201 $info = '';
202 if (is_array($row)) {
203 $langRecUids[$langRow['uid']][] = $row['uid'];
204 $status = $row['_HIDDEN'] ? (GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '') : 'success';
205 $icon = $this->iconFactory->getIconForRecord('pages', $row, Icon::SIZE_SMALL)->render();
206 $info = $icon . htmlspecialchars(
207 GeneralUtility::fixed_lgd_cs($row['title'], $titleLen)
208 ) . ((string)$row['nav_title'] !== '' ? ' [Nav: <em>' . htmlspecialchars(
209 GeneralUtility::fixed_lgd_cs($row['nav_title'], $titleLen)
210 ) . '</em>]' : '') . ($row['_COUNT'] > 1 ? '<div>' . $lang->sL(
211 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_badThingThereAre'
212 ) . '</div>' : '');
213 $tCells[] = '<td class="' . $status . ' col-border-left">' .
214 '<a href="#" onclick="' . htmlspecialchars(
215 'top.loadEditId(' . (int)$data['row']['uid'] . ',"&SET[language]=' . $langRow['uid'] . '"); return false;'
216 ) . '" title="' . $lang->sL(
217 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageLang'
218 ) . '">' . $info . '</a></td>';
219 // Edit whole record:
220 // Create links:
221 $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
222 'edit' => [
223 'pages' => [
224 $row['uid'] => 'edit'
225 ]
226 ],
227 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
228 ]);
229 $info = str_replace('###LANG_UID###', $langRow['uid'], $viewPageLink);
230 $info .= '<a href="' . htmlspecialchars($editUrl)
231 . '" class="btn btn-default" title="' . $lang->sL(
232 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLanguageOverlayRecord'
233 ) . '">' . $this->iconFactory->getIcon('actions-open', Icon::SIZE_SMALL)->render() . '</a>';
234 $tCells[] = '<td class="' . $status . '"><div class="btn-group">' . $info . '</div></td>';
235 $tCells[] = '<td class="' . $status . '" title="' . $lang->sL(
236 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_CEcount'
237 ) . '" align="center">' . $this->getContentElementCount($data['row']['uid'], $langRow['uid']) . '</td>';
238 } else {
239 if (in_array($langRow['uid'], $disableLanguages)) {
240 // Language has been disabled for this page
241 $status = 'danger';
242 $info = '';
243 } else {
244 $status = GeneralUtility::hideIfNotTranslated($data['row']['l18n_cfg']) || GeneralUtility::hideIfDefaultLanguage($data['row']['l18n_cfg']) ? 'danger' : '';
245 $info = '<div class="btn-group"><label class="btn btn-default btn-checkbox">';
246 $info .= '<input type="checkbox" name="newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']" value="1" />';
247 $info .= '<span class="t3-icon fa"></span></label></div>';
248 $newOL_js[$langRow['uid']] .= '
249 +(document.webinfoForm['
250 . GeneralUtility::quoteJSvalue('newOL[' . $langRow['uid'] . '][' . $data['row']['uid'] . ']')
251 . '].checked ? '
252 . GeneralUtility::quoteJSvalue('&edit[pages][' . $data['row']['uid'] . ']=new')
253 . ' : \'\')
254 ';
255 }
256 $tCells[] = '<td class="' . $status . ' col-border-left">&nbsp;</td>';
257 $tCells[] = '<td class="' . $status . '">&nbsp;</td>';
258 $tCells[] = '<td class="' . $status . '">' . $info . '</td>';
259 }
260 }
261 }
262 $output .= '
263 <tr>
264 ' . implode('
265 ', $tCells) . '
266 </tr>';
267 }
268 // Put together HEADER:
269 $tCells = [];
270 $tCells[] = '<td>' . $lang->sL('LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_page') . ':</td>';
271 if (is_array($langRecUids[0])) {
272 $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
273 'edit' => [
274 'pages' => [
275 implode(',', $langRecUids[0]) => 'edit'
276 ]
277 ],
278 'columnsOnly' => 'title,nav_title,l18n_cfg,hidden',
279 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
280 ]);
281 $editIco = '<a href="' . htmlspecialchars($editUrl)
282 . '" class="btn btn-default" title="' . $lang->sL(
283 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editPageProperties'
284 ) . '">' . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
285 } else {
286 $editIco = '';
287 }
288 $tCells[] = '<td class="col-border-left" colspan="2">' . $lang->sL(
289 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_default'
290 ) . ':' . $editIco . '</td>';
291 foreach ($languages as $langRow) {
292 if ($this->pObj->MOD_SETTINGS['lang'] == 0 || (int)$this->pObj->MOD_SETTINGS['lang'] === (int)$langRow['uid']) {
293 // Title:
294 $tCells[] = '<td class="col-border-left">' . htmlspecialchars($langRow['title']) . '</td>';
295 // Edit language overlay records:
296 if (is_array($langRecUids[$langRow['uid']])) {
297 $editUrl = (string)$uriBuilder->buildUriFromRoute('record_edit', [
298 'edit' => [
299 'pages' => [
300 implode(',', $langRecUids[$langRow['uid']]) => 'edit'
301 ]
302 ],
303 'columnsOnly' => 'title,nav_title,hidden',
304 'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
305 ]);
306 $editButton = '<a href="' . htmlspecialchars($editUrl)
307 . '" class="btn btn-default" title="' . $lang->sL(
308 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_renderl10n_editLangOverlays'
309 ) . '">' . $this->iconFactory->getIcon('actions-document-open', Icon::SIZE_SMALL)->render() . '</a>';
310 } else {
311 $editButton = '';
312 }
313 // Create new overlay records:
314 $params = '&columnsOnly=title,hidden,sys_language_uid&overrideVals[pages][sys_language_uid]=' . $langRow['uid'];
315 $onClick = BackendUtility::editOnClick($params);
316 if (!empty($newOL_js[$langRow['uid']])) {
317 $onClickArray = explode('?', $onClick, 2);
318 $lastElement = array_pop($onClickArray);
319 $onClickArray[] = '\'' . $newOL_js[$langRow['uid']] . ' + \'&' . $lastElement;
320 $onClick = implode('?', $onClickArray);
321 }
322 $newButton = '<a href="#" class="btn btn-default" onclick="' . htmlspecialchars($onClick)
323 . '" title="' . $lang->sL(
324 'LLL:EXT:info/Resources/Private/Language/locallang_webinfo.xlf:lang_getlangsta_createNewTranslationHeaders'
325 ) . '">' . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render() . '</a>';
326
327 $tCells[] = '<td class="btn-group">' . $editButton . $newButton . '</td>';
328 $tCells[] = '<td>&nbsp;</td>';
329 }
330 }
331
332 $output =
333 '<div class="table-fit">' .
334 '<table class="table table-striped table-hover" id="langTable">' .
335 '<thead>' .
336 '<tr>' .
337 implode('', $tCells) .
338 '</tr>' .
339 '</thead>' .
340 '<tbody>' .
341 $output .
342 '</tbody>' .
343 '</table>' .
344 '</div>';
345 return $output;
346 }
347
348 /**
349 * Selects all system languages (from sys_language)
350 *
351 * @return array System language records in an array.
352 */
353 public function getSystemLanguages()
354 {
355 if (!$this->getBackendUser()->isAdmin() && $this->getBackendUser()->groupData['allowed_languages'] !== '') {
356 $allowed_languages = array_flip(explode(',', $this->getBackendUser()->groupData['allowed_languages']));
357 }
358 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
359 ->getQueryBuilderForTable('sys_language');
360 $queryBuilder
361 ->getRestrictions()
362 ->removeAll();
363 $queryBuilder
364 ->select('*')
365 ->from('sys_language')
366 ->orderBy('sorting');
367 $res = $queryBuilder->execute();
368 $outputArray = [];
369 if (is_array($allowed_languages) && !empty($allowed_languages)) {
370 while ($output = $res->fetch()) {
371 if (isset($allowed_languages[$output['uid']])) {
372 $outputArray[] = $output;
373 }
374 }
375 } else {
376 $outputArray = $res->fetchAll();
377 }
378 return $outputArray;
379 }
380
381 /**
382 * Get an alternative language record for a specific page / language
383 *
384 * @param int $pageId Page ID to look up for.
385 * @param int $langId Language UID to select for.
386 * @return array translated pages record
387 */
388 public function getLangStatus($pageId, $langId)
389 {
390 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
391 ->getQueryBuilderForTable('pages');
392 $queryBuilder
393 ->getRestrictions()
394 ->removeAll()
395 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class))
396 ->add(GeneralUtility::makeInstance(DeletedRestriction::class));
397 $result = $queryBuilder
398 ->select('*')
399 ->from('pages')
400 ->where(
401 $queryBuilder->expr()->eq(
402 $GLOBALS['TCA']['pages']['ctrl']['transOrigPointerField'],
403 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
404 )
405 )
406 ->andWhere(
407 $queryBuilder->expr()->eq(
408 $GLOBALS['TCA']['pages']['ctrl']['languageField'],
409 $queryBuilder->createNamedParameter($langId, \PDO::PARAM_INT)
410 )
411 )
412 ->execute();
413
414 $row = $result->fetch();
415 BackendUtility::workspaceOL('pages', $row);
416 if (is_array($row)) {
417 $row['_COUNT'] = $result->rowCount();
418 $row['_HIDDEN'] = $row['hidden'] || (int)$row['endtime'] > 0 && (int)$row['endtime'] < $GLOBALS['EXEC_TIME'] || $GLOBALS['EXEC_TIME'] < (int)$row['starttime'];
419 }
420 $result->closeCursor();
421 return $row;
422 }
423
424 /**
425 * Counting content elements for a single language on a page.
426 *
427 * @param int $pageId Page id to select for.
428 * @param int $sysLang Sys language uid
429 * @return int Number of content elements from the PID where the language is set to a certain value.
430 */
431 public function getContentElementCount($pageId, $sysLang)
432 {
433 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
434 ->getQueryBuilderForTable('pages');
435 $queryBuilder->getRestrictions()
436 ->removeAll()
437 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
438 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
439 $count = $queryBuilder
440 ->count('uid')
441 ->from('tt_content')
442 ->where(
443 $queryBuilder->expr()->eq(
444 'pid',
445 $queryBuilder->createNamedParameter($pageId, \PDO::PARAM_INT)
446 )
447 )
448 ->andWhere(
449 $queryBuilder->expr()->eq(
450 'sys_language_uid',
451 $queryBuilder->createNamedParameter($sysLang, \PDO::PARAM_INT)
452 )
453 )
454 ->execute()
455 ->fetchColumn(0);
456 return $count ?: '-';
457 }
458
459 /**
460 * Returns LanguageService
461 *
462 * @return \TYPO3\CMS\Core\Localization\LanguageService
463 */
464 protected function getLanguageService()
465 {
466 return $GLOBALS['LANG'];
467 }
468
469 /**
470 * Returns the current BE user.
471 *
472 * @return \TYPO3\CMS\Core\Authentication\BackendUserAuthentication
473 */
474 protected function getBackendUser()
475 {
476 return $GLOBALS['BE_USER'];
477 }
478 }