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