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