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