From 0b0bb11c6ea3c5e11ff1803e9b072a56b09128c6 Mon Sep 17 00:00:00 2001
From: Georg Ringer <georg.ringer@gmail.com>
Date: Wed, 9 Dec 2015 16:32:21 +0100
Subject: [PATCH] [BUGFIX] Show error message in page module for invalid
 backend layout

If there is a backend layout selected with no colPos defined inside the backend layout option, then the page
module fatals with an error.

A error message is now shown to display that at least one
colPos parameter must be set.

Resolves: #67276
Releases: master
Change-Id: I02120be981607d35efa31f49cc0ee4cc992172e4
Reviewed-on: https://review.typo3.org/45146
Reviewed-by: Andreas Fernandez <typo3@scripting-base.de>
Tested-by: Andreas Fernandez <typo3@scripting-base.de>
Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: Josef Glatz <josef.glatz@typo3.org>
Reviewed-by: Frans Saris <franssaris@gmail.com>
Tested-by: Frans Saris <franssaris@gmail.com>
---
 .../backend/Classes/View/PageLayoutView.php   | 202 ++++++++++--------
 .../Private/Language/locallang_layout.xlf     |   3 +
 2 files changed, 110 insertions(+), 95 deletions(-)

diff --git a/typo3/sysext/backend/Classes/View/PageLayoutView.php b/typo3/sysext/backend/Classes/View/PageLayoutView.php
index 2bbf59df914e..9b4f289b70bc 100644
--- a/typo3/sysext/backend/Classes/View/PageLayoutView.php
+++ b/typo3/sysext/backend/Classes/View/PageLayoutView.php
@@ -563,116 +563,128 @@ class PageLayoutView extends \TYPO3\CMS\Recordlist\RecordList\AbstractDatabaseRe
                 </div>
 				';
                 $editUidList = '';
-                $rowArr = $contentRecordsPerColumn[$key];
-                $this->generateTtContentDataArray($rowArr);
-
-                foreach ((array)$rowArr as $rKey => $row) {
-                    $this->contentElementCache[$lP][$key][$row['uid']] = $row;
-                    if ($this->tt_contentConfig['languageMode']) {
-                        $languageColumn[$key][$lP] = $head[$key] . $content[$key];
-                        if (!$this->defLangBinding) {
-                            $languageColumn[$key][$lP] .= $this->newLanguageButton(
-                                $this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP),
-                                $lP,
-                                $key
-                            );
-                        }
-                    }
-                    if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
-                        $singleElementHTML = '';
-                        if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
-                            $defLanguageCount[$key][] = (isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']);
-                        }
-                        $editUidList .= $row['uid'] . ',';
-                        $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0;
-                        if (!$this->tt_contentConfig['languageMode']) {
-                            $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
-                        }
-                        $singleElementHTML .= $this->tt_content_drawHeader(
-                            $row,
-                            $this->tt_contentConfig['showInfo'] ? 15 : 5,
-                            $disableMoveAndNewButtons,
-                            !$this->tt_contentConfig['languageMode'],
-                            $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
-                        );
-                        $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
-                            . $this->tt_content_drawItem($row) . '</div>';
-                        $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>'
-                            . $this->tt_content_drawFooter($row);
-                        $isDisabled = $this->isDisabled('tt_content', $row);
-                        $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
-                        $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
-                        $highlightHeader = false;
-                        if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
-                            $highlightHeader = true;
-                        }
-                        $singleElementHTML = '<div class="t3-page-ce ' . ($highlightHeader ? 't3-page-ce-danger' : '') . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
-                            . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
+                if (!isset($contentRecordsPerColumn[$key]) || !is_array($contentRecordsPerColumn[$key])) {
+                    $message = GeneralUtility::makeInstance(
+                        FlashMessage::class,
+                        $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang_layout.xlf:error.invalidBackendLayout'),
+                        '',
+                        FlashMessage::WARNING
+                    );
+                    $service = GeneralUtility::makeInstance(FlashMessageService::class);
+                    $queue = $service->getMessageQueueByIdentifier();
+                    $queue->addMessage($message);
+                } else {
+                    $rowArr = $contentRecordsPerColumn[$key];
+                    $this->generateTtContentDataArray($rowArr);
 
+                    foreach ((array)$rowArr as $rKey => $row) {
+                        $this->contentElementCache[$lP][$key][$row['uid']] = $row;
                         if ($this->tt_contentConfig['languageMode']) {
-                            $singleElementHTML .= '<div class="t3-page-ce t3js-page-ce">';
+                            $languageColumn[$key][$lP] = $head[$key] . $content[$key];
+                            if (!$this->defLangBinding) {
+                                $languageColumn[$key][$lP] .= $this->newLanguageButton(
+                                    $this->getNonTranslatedTTcontentUids($defLanguageCount[$key], $id, $lP),
+                                    $lP,
+                                    $key
+                                );
+                            }
                         }
-                        $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id .
-                            '-' . StringUtility::getUniqueId() . '">';
-                        // Add icon "new content element below"
-                        if (!$disableMoveAndNewButtons
-                            && $this->getPageLayoutController()->pageIsNotLockedForEditors()
-                            && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
-                            && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
-                        ) {
-                            // New content element:
-                            if ($this->option_newWizard) {
-                                $urlParameters = [
-                                    'id'               => $row['pid'],
-                                    'sys_language_uid' => $row['sys_language_uid'],
-                                    'colPos'           => $row['colPos'],
-                                    'uid_pid'          => -$row['uid'],
-                                    'returnUrl'        => GeneralUtility::getIndpEnv('REQUEST_URI')
-                                ];
-                                $url = BackendUtility::getModuleUrl('new_content_element', $urlParameters);
-                            } else {
-                                $urlParameters = [
-                                    'edit' => [
-                                        'tt_content' => [
-                                            -$row['uid'] => 'new'
-                                        ]
-                                    ],
-                                    'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
-                                ];
-                                $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
+                        if (is_array($row) && !VersionState::cast($row['t3ver_state'])->equals(VersionState::DELETE_PLACEHOLDER)) {
+                            $singleElementHTML = '';
+                            if (!$lP && ($this->defLangBinding || $row['sys_language_uid'] != -1)) {
+                                $defLanguageCount[$key][] = (isset($row['_ORIG_uid']) ? $row['_ORIG_uid'] : $row['uid']);
                             }
-                            $singleElementHTML .= '
+                            $editUidList .= $row['uid'] . ',';
+                            $disableMoveAndNewButtons = $this->defLangBinding && $lP > 0;
+                            if (!$this->tt_contentConfig['languageMode']) {
+                                $singleElementHTML .= '<div class="t3-page-ce-dragitem" id="' . StringUtility::getUniqueId() . '">';
+                            }
+                            $singleElementHTML .= $this->tt_content_drawHeader(
+                                $row,
+                                $this->tt_contentConfig['showInfo'] ? 15 : 5,
+                                $disableMoveAndNewButtons,
+                                !$this->tt_contentConfig['languageMode'],
+                                $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
+                            );
+                            $innerContent = '<div ' . ($row['_ORIG_uid'] ? ' class="ver-element"' : '') . '>'
+                                . $this->tt_content_drawItem($row) . '</div>';
+                            $singleElementHTML .= '<div class="t3-page-ce-body-inner">' . $innerContent . '</div>'
+                                . $this->tt_content_drawFooter($row);
+                            $isDisabled = $this->isDisabled('tt_content', $row);
+                            $statusHidden = $isDisabled ? ' t3-page-ce-hidden t3js-hidden-record' : '';
+                            $displayNone = !$this->tt_contentConfig['showHidden'] && $isDisabled ? ' style="display: none;"' : '';
+                            $highlightHeader = false;
+                            if ($this->checkIfTranslationsExistInLanguage([], (int)$row['sys_language_uid']) && (int)$row['l18n_parent'] === 0) {
+                                $highlightHeader = true;
+                            }
+                            $singleElementHTML = '<div class="t3-page-ce ' . ($highlightHeader ? 't3-page-ce-danger' : '') . ' t3js-page-ce t3js-page-ce-sortable ' . $statusHidden . '" id="element-tt_content-'
+                                . $row['uid'] . '" data-table="tt_content" data-uid="' . $row['uid'] . '"' . $displayNone . '>' . $singleElementHTML . '</div>';
+
+                            if ($this->tt_contentConfig['languageMode']) {
+                                $singleElementHTML .= '<div class="t3-page-ce t3js-page-ce">';
+                            }
+                            $singleElementHTML .= '<div class="t3js-page-new-ce t3-page-ce-wrapper-new-ce" id="colpos-' . $key . '-' . 'page-' . $id .
+                                '-' . StringUtility::getUniqueId() . '">';
+                            // Add icon "new content element below"
+                            if (!$disableMoveAndNewButtons
+                                && $this->getPageLayoutController()->pageIsNotLockedForEditors()
+                                && $this->getBackendUser()->doesUserHaveAccess($this->pageinfo, Permission::CONTENT_EDIT)
+                                && (!$this->checkIfTranslationsExistInLanguage($contentRecordsPerColumn, $lP))
+                            ) {
+                                // New content element:
+                                if ($this->option_newWizard) {
+                                    $urlParameters = [
+                                        'id' => $row['pid'],
+                                        'sys_language_uid' => $row['sys_language_uid'],
+                                        'colPos' => $row['colPos'],
+                                        'uid_pid' => -$row['uid'],
+                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                                    ];
+                                    $url = BackendUtility::getModuleUrl('new_content_element', $urlParameters);
+                                } else {
+                                    $urlParameters = [
+                                        'edit' => [
+                                            'tt_content' => [
+                                                -$row['uid'] => 'new'
+                                            ]
+                                        ],
+                                        'returnUrl' => GeneralUtility::getIndpEnv('REQUEST_URI')
+                                    ];
+                                    $url = BackendUtility::getModuleUrl('record_edit', $urlParameters);
+                                }
+                                $singleElementHTML .= '
 								<a href="' . htmlspecialchars($url) . '" title="'
                                     . $this->getLanguageService()->getLL('newContentElement', true) . '" class="btn btn-default btn-sm">'
                                     . $this->iconFactory->getIcon('actions-document-new', Icon::SIZE_SMALL)->render()
                                     . ' '
                                     . $this->getLanguageService()->getLL('content', true) . '</a>
 							';
-                        }
-                        $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
-                        if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
-                            $defLangBinding[$key][$lP][$row[$lP ? 'l18n_parent' : 'uid']] = $singleElementHTML;
+                            }
+                            $singleElementHTML .= '</div></div><div class="t3-page-ce-dropzone-available t3js-page-ce-dropzone-available"></div></div>';
+                            if ($this->defLangBinding && $this->tt_contentConfig['languageMode']) {
+                                $defLangBinding[$key][$lP][$row[$lP ? 'l18n_parent' : 'uid']] = $singleElementHTML;
+                            } else {
+                                $content[$key] .= $singleElementHTML;
+                            }
                         } else {
-                            $content[$key] .= $singleElementHTML;
+                            unset($rowArr[$rKey]);
                         }
-                    } else {
-                        unset($rowArr[$rKey]);
                     }
-                }
-                $content[$key] .= '</div>';
-                $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $key);
-                $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this);
-                foreach ($tcaItems as $item) {
-                    if ($item[1] == $key) {
-                        $colTitle = $this->getLanguageService()->sL($item[0]);
+                    $content[$key] .= '</div>';
+                    $colTitle = BackendUtility::getProcessedValue('tt_content', 'colPos', $key);
+                    $tcaItems = GeneralUtility::callUserFunction(\TYPO3\CMS\Backend\View\BackendLayoutView::class . '->getColPosListItemsParsed', $id, $this);
+                    foreach ($tcaItems as $item) {
+                        if ($item[1] == $key) {
+                            $colTitle = $this->getLanguageService()->sL($item[0]);
+                        }
                     }
-                }
 
-                $pasteP = array('colPos' => $key, 'sys_language_uid' => $lP);
-                $editParam = $this->doEdit && !empty($rowArr)
-                    ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc
-                    : '';
-                $head[$key] .= $this->tt_content_drawColHeader($colTitle, $editParam, '', $pasteP);
+                    $pasteP = array('colPos' => $key, 'sys_language_uid' => $lP);
+                    $editParam = $this->doEdit && !empty($rowArr)
+                        ? '&edit[tt_content][' . $editUidList . ']=edit' . $pageTitleParamForAltDoc
+                        : '';
+                    $head[$key] .= $this->tt_content_drawColHeader($colTitle, $editParam, '', $pasteP);
+                }
             }
             // For each column, fit the rendered content into a table cell:
             $out = '';
diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf
index 38bdbede99eb..68a1e9f9eba2 100644
--- a/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf
+++ b/typo3/sysext/backend/Resources/Private/Language/locallang_layout.xlf
@@ -315,6 +315,9 @@
 					&lt;strong&gt;Use this when you want to have freedom in designing your translated website.&lt;/strong&gt;
 				</source>
 			</trans-unit>
+			<trans-unit id="error.invalidBackendLayout">
+				<source>The selected page layout is mis-configured, no columns are specified to be editable. Please update your backend layout to have at least one parameter "colPos" set.</source>
+			</trans-unit>
 		</body>
 	</file>
 </xliff>
-- 
GitLab