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