[BUGFIX] Add missing column in database query
[Packages/TYPO3.CMS.git] / typo3 / sysext / tstemplate / Classes / Controller / TypoScriptTemplateModuleController.php
1 <?php
2 namespace TYPO3\CMS\Tstemplate\Controller;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use Psr\Http\Message\ResponseInterface;
18 use Psr\Http\Message\ServerRequestInterface;
19 use TYPO3\CMS\Backend\Module\BaseScriptClass;
20 use TYPO3\CMS\Backend\Template\Components\ButtonBar;
21 use TYPO3\CMS\Backend\Template\ModuleTemplate;
22 use TYPO3\CMS\Backend\Utility\BackendUtility;
23 use TYPO3\CMS\Core\Database\ConnectionPool;
24 use TYPO3\CMS\Core\Database\Query\Restriction\BackendWorkspaceRestriction;
25 use TYPO3\CMS\Core\Database\Query\Restriction\DeletedRestriction;
26 use TYPO3\CMS\Core\DataHandling\DataHandler;
27 use TYPO3\CMS\Core\Http\HtmlResponse;
28 use TYPO3\CMS\Core\Imaging\Icon;
29 use TYPO3\CMS\Core\Type\Bitmask\Permission;
30 use TYPO3\CMS\Core\TypoScript\ExtendedTemplateService;
31 use TYPO3\CMS\Core\Utility\GeneralUtility;
32 use TYPO3\CMS\Core\Utility\MathUtility;
33 use TYPO3\CMS\Fluid\View\StandaloneView;
34 use TYPO3\CMS\Fluid\ViewHelpers\Be\InfoboxViewHelper;
35
36 /**
37 * Module: TypoScript Tools
38 */
39 class TypoScriptTemplateModuleController extends BaseScriptClass
40 {
41 /**
42 * @var string
43 */
44 public $perms_clause;
45
46 /**
47 * @var string
48 */
49 public $sObj;
50
51 /**
52 * @var string
53 */
54 public $edit;
55
56 /**
57 * @var string
58 */
59 public $textExtensions = 'html,htm,txt,css,tmpl,inc,js';
60
61 /**
62 * @var string
63 */
64 public $modMenu_type = '';
65
66 /**
67 * @var string
68 */
69 public $modMenu_dontValidateList = '';
70
71 /**
72 * @var string
73 */
74 public $modMenu_setDefaultList = '';
75
76 /**
77 * @var array
78 */
79 public $pageinfo = [];
80
81 /**
82 * @var bool
83 */
84 public $access = false;
85
86 /**
87 * The name of the module
88 *
89 * @var string
90 */
91 protected $moduleName = 'web_ts';
92
93 /**
94 * ModuleTemplate Container
95 *
96 * @var ModuleTemplate
97 */
98 protected $moduleTemplate;
99
100 /**
101 * @var ExtendedTemplateService
102 */
103 protected $templateService;
104
105 /**
106 * Constructor
107 */
108 public function __construct()
109 {
110 $this->moduleTemplate = GeneralUtility::makeInstance(ModuleTemplate::class);
111 $this->getLanguageService()->includeLLFile('EXT:tstemplate/Resources/Private/Language/locallang.xlf');
112
113 $this->MCONF = [
114 'name' => $this->moduleName
115 ];
116 $this->moduleTemplate->addJavaScriptCode(
117 'jumpToUrl',
118 '
119 function jumpToUrl(URL) {
120 window.location.href = URL;
121 return false;
122 }
123 '
124 );
125 }
126
127 /**
128 * Init
129 */
130 public function init()
131 {
132 parent::init();
133 $this->id = (int)GeneralUtility::_GP('id');
134 $this->sObj = GeneralUtility::_GP('sObj');
135 $this->edit = GeneralUtility::_GP('edit');
136 $this->perms_clause = $this->getBackendUser()->getPagePermsClause(Permission::PAGE_SHOW);
137 }
138
139 /**
140 * Clear cache
141 */
142 public function clearCache()
143 {
144 if (GeneralUtility::_GP('clear_all_cache')) {
145 /** @var DataHandler $tce */
146 $tce = GeneralUtility::makeInstance(DataHandler::class);
147 $tce->start([], []);
148 $tce->clear_cacheCmd('all');
149 }
150 }
151
152 /**
153 * Main
154 */
155 public function main()
156 {
157 // Access check...
158 // The page will show only if there is a valid page and if this page may be viewed by the user
159 $this->pageinfo = BackendUtility::readPageAccess($this->id, $this->perms_clause);
160 $this->access = is_array($this->pageinfo);
161 $view = $this->getFluidTemplateObject('tstemplate');
162 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
163 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
164 if ($this->id && $this->access) {
165 $urlParameters = [
166 'id' => $this->id,
167 'template' => 'all'
168 ];
169 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters);
170
171 // JavaScript
172 $this->moduleTemplate->addJavaScriptCode(
173 'TSTemplateInlineJS',
174 'function uFormUrl(aname) {
175 document.forms[0].action = ' . GeneralUtility::quoteJSvalue($aHref . '#') . '+aname;
176 }
177 function brPoint(lnumber,t) {
178 window.location.href = '
179 . GeneralUtility::quoteJSvalue(
180 $aHref . '&SET[function]=TYPO3\\CMS\\Tstemplate\\Controller\\'
181 . 'TypoScriptTemplateObjectBrowserModuleFunctionController&SET[ts_browser_type]='
182 ) . '+(t?"setup":"const")+"&breakPointLN="+lnumber;
183 return false;
184 }
185 if (top.fsMod) top.fsMod.recentIds["web"] = ' . $this->id . ';'
186 );
187 $this->moduleTemplate->getPageRenderer()->addCssInlineBlock(
188 'TSTemplateInlineStyle',
189 'TABLE#typo3-objectBrowser { width: 100%; margin-bottom: 24px; }
190 TABLE#typo3-objectBrowser A { text-decoration: none; }
191 TABLE#typo3-objectBrowser .comment { color: maroon; font-weight: bold; }
192 .ts-typoscript { width: 100%; }
193 .tsob-search-submit {margin-left: 3px; margin-right: 3px;}
194 .tst-analyzer-options { margin:5px 0; }'
195 );
196 // Setting up the context sensitive menu:
197 $this->moduleTemplate->getPageRenderer()->loadRequireJsModule('TYPO3/CMS/Backend/ContextMenu');
198 // Build the module content
199 $view->assign('actionName', $aHref);
200 $view->assign('typoscriptTemplateModuleContent', $this->getExtObjContent());
201 // Setting up the buttons and markers for docheader
202 $this->getButtons();
203 $this->generateMenu();
204 } else {
205 $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('pages');
206 $queryBuilder->getRestrictions()
207 ->removeAll()
208 ->add(GeneralUtility::makeInstance(DeletedRestriction::class))
209 ->add(GeneralUtility::makeInstance(BackendWorkspaceRestriction::class));
210
211 $result = $queryBuilder->select('pages.uid')
212 ->addSelectLiteral(
213 $queryBuilder->expr()->count('*', 'count'),
214 $queryBuilder->expr()->max('sys_template.root', 'root_max_val'),
215 $queryBuilder->expr()->min('sys_template.root', 'root_min_val')
216 )
217 ->from('pages')
218 ->from('sys_template')
219 ->where($queryBuilder->expr()->eq('pages.uid', $queryBuilder->quoteIdentifier('sys_template.pid')))
220 ->groupBy('pages.uid', 'pages.pid', 'pages.sorting')
221 ->orderBy('pages.pid')
222 ->addOrderBy('pages.sorting')
223 ->execute();
224
225 $pArray = [];
226 while ($record = $result->fetch()) {
227 $this->setInPageArray($pArray, BackendUtility::BEgetRootLine($record['uid'], 'AND 1=1'), $record);
228 }
229
230 $view->getRenderingContext()->setControllerAction('PageZero');
231 $view->assign('templateList', $this->renderList($pArray));
232
233 // RENDER LIST of pages with templates, END
234 // Setting up the buttons and markers for docheader
235 $this->getButtons();
236 }
237 $this->content = $view->render();
238 }
239
240 /**
241 * Generates the menu based on $this->MOD_MENU
242 *
243 * @throws \InvalidArgumentException
244 */
245 protected function generateMenu()
246 {
247 $menu = $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->makeMenu();
248 $menu->setIdentifier('WebFuncJumpMenu');
249 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
250 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
251 foreach ($this->MOD_MENU['function'] as $controller => $title) {
252 $item = $menu
253 ->makeMenuItem()
254 ->setHref(
255 (string)$uriBuilder->buildUriFromRoute(
256 $this->moduleName,
257 [
258 'id' => $this->id,
259 'SET' => [
260 'function' => $controller
261 ]
262 ]
263 )
264 )
265 ->setTitle($title);
266 if ($controller === $this->MOD_SETTINGS['function']) {
267 $item->setActive(true);
268 }
269 $menu->addMenuItem($item);
270 }
271 $this->moduleTemplate->getDocHeaderComponent()->getMenuRegistry()->addMenu($menu);
272 }
273
274 /**
275 * Injects the request object for the current request or subrequest
276 * Then checks for module functions that have hooked in, and renders menu etc.
277 *
278 * @param ServerRequestInterface $request the current request
279 * @return ResponseInterface the response with the content
280 */
281 public function mainAction(ServerRequestInterface $request): ResponseInterface
282 {
283 $GLOBALS['SOBE'] = $this;
284 $this->init();
285
286 // Checking for first level external objects
287 $this->checkExtObj();
288
289 $this->clearCache();
290 $this->main();
291
292 $this->moduleTemplate->setContent($this->content);
293 return new HtmlResponse($this->moduleTemplate->renderContent());
294 }
295
296 /**
297 * Create the panel of buttons for submitting the form or otherwise perform operations.
298 */
299 protected function getButtons()
300 {
301 $buttonBar = $this->moduleTemplate->getDocHeaderComponent()->getButtonBar();
302 $lang = $this->getLanguageService();
303
304 if ($this->id && $this->access) {
305 // View page
306 $viewButton = $buttonBar->makeLinkButton()
307 ->setHref('#')
308 ->setOnClick(BackendUtility::viewOnClick(
309 $this->pageinfo['uid'],
310 '',
311 BackendUtility::BEgetRootLine($this->pageinfo['uid'])
312 ))
313 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.showPage'))
314 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon('actions-view-page', Icon::SIZE_SMALL));
315 $buttonBar->addButton($viewButton, ButtonBar::BUTTON_POSITION_LEFT, 99);
316
317 if ($this->extClassConf['name'] === TypoScriptTemplateInformationModuleFunctionController::class) {
318 // NEW button
319 $urlParameters = [
320 'id' => $this->id,
321 'template' => 'all',
322 'createExtension' => 'new'
323 ];
324 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
325 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
326 $newButton = $buttonBar->makeLinkButton()
327 ->setHref((string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters))
328 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:db_new.php.pagetitle'))
329 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
330 'actions-add',
331 Icon::SIZE_SMALL
332 ));
333 $buttonBar->addButton($newButton);
334 } elseif ($this->extClassConf['name'] === TypoScriptTemplateConstantEditorModuleFunctionController::class
335 && !empty($this->MOD_MENU['constant_editor_cat'])) {
336 // SAVE button
337 $saveButton = $buttonBar->makeInputButton()
338 ->setName('_savedok')
339 ->setValue('1')
340 ->setForm('TypoScriptTemplateModuleController')
341 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:rm.saveDoc'))
342 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
343 'actions-document-save',
344 Icon::SIZE_SMALL
345 ))
346 ->setShowLabelText(true);
347 $buttonBar->addButton($saveButton);
348 } elseif ($this->extClassConf['name'] === TypoScriptTemplateObjectBrowserModuleFunctionController::class) {
349 if (!empty($this->sObj)) {
350 // BACK
351 $urlParameters = [
352 'id' => $this->id
353 ];
354 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
355 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
356 $backButton = $buttonBar->makeLinkButton()
357 ->setHref((string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters))
358 ->setClasses('typo3-goBack')
359 ->setTitle($lang->sL('LLL:EXT:lang/Resources/Private/Language/locallang_core.xlf:labels.goBack'))
360 ->setIcon($this->moduleTemplate->getIconFactory()->getIcon(
361 'actions-view-go-back',
362 Icon::SIZE_SMALL
363 ));
364 $buttonBar->addButton($backButton);
365 }
366 }
367 }
368 // Shortcut
369 $shortcutButton = $buttonBar->makeShortcutButton()
370 ->setModuleName($this->MCONF['name'])
371 ->setGetVariables(['id', 'route']);
372 $buttonBar->addButton($shortcutButton);
373 }
374
375 // OTHER FUNCTIONS:
376 /**
377 * Wrap title for link in template
378 *
379 * @param string $title
380 * @param string $onlyKey
381 * @return string
382 */
383 public function linkWrapTemplateTitle($title, $onlyKey = '')
384 {
385 $urlParameters = [
386 'id' => $this->id
387 ];
388 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
389 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
390 $aHref = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters);
391 if ($onlyKey) {
392 $title = '<a href="' . htmlspecialchars($aHref . '&e[' . $onlyKey . ']=1&SET[function]=TYPO3\\CMS\\Tstemplate\\Controller\\TypoScriptTemplateInformationModuleFunctionController') . '">' . htmlspecialchars($title) . '</a>';
393 } else {
394 $title = '<a href="' . htmlspecialchars($aHref . '&e[constants]=1&e[config]=1&SET[function]=TYPO3\\CMS\\Tstemplate\\Controller\\TypoScriptTemplateInformationModuleFunctionController') . '">' . htmlspecialchars($title) . '</a>';
395 }
396 return $title;
397 }
398
399 /**
400 * No template
401 *
402 * @param int $newStandardTemplate
403 * @return string
404 */
405 public function noTemplate($newStandardTemplate = 0)
406 {
407 $this->templateService = GeneralUtility::makeInstance(ExtendedTemplateService::class);
408 $this->templateService->init();
409
410 $moduleContent['state'] = InfoboxViewHelper::STATE_INFO;
411
412 // New standard?
413 if ($newStandardTemplate) {
414 $selector = '';
415 $staticsText = '';
416 // Hook to change output, implemented for statictemplates
417 $hookObject = $this->getHookObjectForAction('newStandardTemplateView');
418 if (!empty($hookObject)) {
419 $reference = [
420 'selectorHtml' => &$selector,
421 'staticsText' => &$staticsText
422 ];
423 GeneralUtility::callUserFunction(
424 $hookObject,
425 $reference,
426 $this
427 );
428 $selector = $reference['selectorHtml'];
429 $staticsText = $reference['staticsText'];
430 }
431 // Extension?
432 $moduleContent['staticsText'] = $staticsText;
433 $moduleContent['selector'] = $selector;
434 }
435 // Go to previous Page with Template...
436 $previousPage = $this->templateService->ext_prevPageWithTemplate($this->id, $this->perms_clause);
437 if ($previousPage) {
438 $urlParameters = [
439 'id' => $previousPage['uid']
440 ];
441 /** @var \TYPO3\CMS\Backend\Routing\UriBuilder $uriBuilder */
442 $uriBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Backend\Routing\UriBuilder::class);
443 $previousPage['aHref'] = (string)$uriBuilder->buildUriFromRoute('web_ts', $urlParameters);
444 $moduleContent['previousPage'] = $previousPage;
445 }
446 $view = $this->getFluidTemplateObject('tstemplate', 'NoTemplate');
447 $view->assign('content', $moduleContent);
448 return $view->render();
449 }
450
451 /**
452 * Render template menu
453 *
454 * @return string
455 */
456 public function templateMenu()
457 {
458 $this->templateService = GeneralUtility::makeInstance(ExtendedTemplateService::class);
459 $this->templateService->init();
460
461 $all = $this->templateService->ext_getAllTemplates($this->id);
462 if (count($all) > 1) {
463 $this->MOD_MENU['templatesOnPage'] = [];
464 foreach ($all as $d) {
465 $this->MOD_MENU['templatesOnPage'][$d['uid']] = $d['title'];
466 }
467 }
468 $this->MOD_SETTINGS = BackendUtility::getModuleData(
469 $this->MOD_MENU,
470 GeneralUtility::_GP('SET'),
471 $this->MCONF['name'],
472 $this->modMenu_type,
473 $this->modMenu_dontValidateList,
474 $this->modMenu_setDefaultList
475 );
476 return BackendUtility::getFuncMenu(
477 $this->id,
478 'SET[templatesOnPage]',
479 $this->MOD_SETTINGS['templatesOnPage'],
480 $this->MOD_MENU['templatesOnPage']
481 );
482 }
483
484 /**
485 * Create template
486 *
487 * @param int $id
488 * @param int $actTemplateId
489 * @return string
490 */
491 public function createTemplate($id, $actTemplateId = 0)
492 {
493 $recData = [];
494 /** @var DataHandler $tce */
495 $tce = GeneralUtility::makeInstance(DataHandler::class);
496
497 if (GeneralUtility::_GP('createExtension')) {
498 $recData['sys_template']['NEW'] = [
499 'pid' => $actTemplateId ? -1 * $actTemplateId : $id,
500 'title' => '+ext'
501 ];
502 $tce->start($recData, []);
503 $tce->process_datamap();
504 } elseif (GeneralUtility::_GP('newWebsite')) {
505 // Hook to handle row data, implemented for statictemplates
506 $hookObject = $this->getHookObjectForAction('newStandardTemplateHandler');
507 if (!empty($hookObject)) {
508 $reference = [
509 'recData' => &$recData,
510 'id' => $id,
511 ];
512 GeneralUtility::callUserFunction(
513 $hookObject,
514 $reference,
515 $this
516 );
517 $recData = $reference['recData'];
518 } else {
519 $recData['sys_template']['NEW'] = [
520 'pid' => $id,
521 'title' => $this->getLanguageService()->getLL('titleNewSite'),
522 'sorting' => 0,
523 'root' => 1,
524 'clear' => 3,
525 'config' => '
526 # Default PAGE object:
527 page = PAGE
528 page.10 = TEXT
529 page.10.value = HELLO WORLD!
530 '
531 ];
532 }
533 $tce->start($recData, []);
534 $tce->process_datamap();
535 $tce->clear_cacheCmd('all');
536 }
537 return $tce->substNEWwithIDs['NEW'];
538 }
539
540 // RENDER LIST of pages with templates, BEGIN
541 /**
542 * Set page in array
543 *
544 * @param array $pArray
545 * @param array $rlArr
546 * @param array $row
547 */
548 public function setInPageArray(&$pArray, $rlArr, $row)
549 {
550 ksort($rlArr);
551 reset($rlArr);
552 if (!$rlArr[0]['uid']) {
553 array_shift($rlArr);
554 }
555 $cEl = current($rlArr);
556 $pArray[$cEl['uid']] = $cEl['title'];
557 array_shift($rlArr);
558 if (!empty($rlArr)) {
559 $key = $cEl['uid'] . '.';
560 if (empty($pArray[$key])) {
561 $pArray[$key] = [];
562 }
563 $this->setInPageArray($pArray[$key], $rlArr, $row);
564 } else {
565 $key = $cEl['uid'] . '_';
566 $pArray[$key] = $row;
567 }
568 }
569
570 /**
571 * Get the list
572 *
573 * @param array $pArray
574 * @param array $lines
575 * @param int $c
576 * @return array
577 */
578 public function renderList($pArray, $lines = [], $c = 0)
579 {
580 if (!is_array($pArray)) {
581 return $lines;
582 }
583
584 $statusCheckedIcon = $this->moduleTemplate->getIconFactory()
585 ->getIcon('status-status-checked', Icon::SIZE_SMALL)->render();
586 $i = 0;
587 foreach ($pArray as $k => $v) {
588 if (MathUtility::canBeInterpretedAsInteger($k)) {
589 $line = [];
590 $key = $k . '_';
591 $line['marginLeft'] = $c * 20;
592 $line['class'] = ($i++ % 2 === 0 ? 'bgColor4' : 'bgColor6');
593 $line['pageTitle'] = GeneralUtility::fixed_lgd_cs($pArray[$k], 30);
594 $line['icon'] = $this->moduleTemplate->getIconFactory()
595 ->getIconForRecord(
596 'pages',
597 BackendUtility::getRecordWSOL('pages', $k),
598 Icon::SIZE_SMALL
599 )->render();
600 if (!empty($pArray[$key])) {
601 $line['href'] = GeneralUtility::linkThisScript(['id' => (int)$k]);
602 $line['title'] = 'ID: ' . (int)$k;
603 $line['count'] = $pArray[$k . '_']['count'];
604 $line['root_max_val'] = ($pArray[$key]['root_max_val'] > 0 ? $statusCheckedIcon : '&nbsp;');
605 $line['root_min_val'] = ($pArray[$key]['root_min_val'] === 0 ? $statusCheckedIcon : '&nbsp;');
606 } else {
607 $line['href'] = '';
608 $line['title'] = '';
609 $line['count'] = '';
610 $line['root_max_val'] = '';
611 $line['root_min_val'] = '';
612 }
613 $lines[] = $line;
614 $lines = $this->renderList($pArray[$k . '.'], $lines, $c + 1);
615 }
616 }
617 return $lines;
618 }
619
620 /**
621 * Returns a new standalone view, shorthand function
622 *
623 * @param string $extensionName
624 * @param string $templateName
625 * @return StandaloneView
626 */
627 protected function getFluidTemplateObject($extensionName, $templateName = 'Main')
628 {
629 /** @var StandaloneView $view */
630 $view = GeneralUtility::makeInstance(StandaloneView::class);
631 $view->getRenderingContext()->getTemplatePaths()->fillDefaultsByPackageName($extensionName);
632 $view->getRenderingContext()->setControllerAction($templateName);
633 $view->getRequest()->setControllerExtensionName('tstemplate');
634 return $view;
635 }
636
637 /**
638 * @param string $action
639 * @return string
640 */
641 protected function getHookObjectForAction($action)
642 {
643 if (!empty($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class][$action])) {
644 return $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS'][self::class][$action];
645 }
646 return null;
647 }
648 }