diff --git a/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php b/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php index 59906adc208c4693f6f990f888f78451fa04fa8e..377815432ffbe10da42ec6fa59cdd74d5d0ed17e 100644 --- a/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php +++ b/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php @@ -21,4 +21,49 @@ use TYPO3\CMS\Backend\Form\Exception; */ class DatabaseRecordException extends Exception { + /** + * @var string Table name + */ + protected $tableName; + + /** + * @var int Table row uid + */ + protected $uid; + + /** + * Constructor overwrites default constructor. + * + * @param string $message Human readable error message + * @param int $code Exception code timestamp + * @param \Exception $previousException Possible exception from database layer + * @param string $tableName Table name query was working on + * @param int $uid Table row uid + */ + public function __construct($message, $code, \Exception $previousException = null, $tableName, $uid) + { + parent::__construct($message, $code, $previousException); + $this->tableName = $tableName; + $this->uid = $uid; + } + + /** + * Return table name + * + * @return string + */ + public function getTableName() + { + return $this->tableName; + } + + /** + * Return row uid + * + * @return int + */ + public function getUid() + { + return $this->uid; + } } diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractDatabaseRecordProvider.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractDatabaseRecordProvider.php index 04ef531361f099ebfd9e9dae2939f6f1c18702c0..234775f4708169a989a4206fd3fc0a4e3a191bc4 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractDatabaseRecordProvider.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/AbstractDatabaseRecordProvider.php @@ -57,7 +57,10 @@ abstract class AbstractDatabaseRecordProvider // and transformed to a message to the user or something throw new DatabaseRecordException( 'Record with uid ' . $uid . ' from table ' . $tableName . ' not found', - 1437656081 + 1437656081, + null, + $tableName, + (int)$uid ); } if (!is_array($row)) { diff --git a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php index 126c552e19edbd51aed2abdba99bc15a8a95a346..a8220c6889eb3e667f95f60b9a6bf8db254bbb41 100644 --- a/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php +++ b/typo3/sysext/backend/Classes/Form/FormDataProvider/TcaInline.php @@ -14,6 +14,7 @@ namespace TYPO3\CMS\Backend\Form\FormDataProvider; * The TYPO3 project - inspiring people to share! */ +use TYPO3\CMS\Backend\Form\Exception\DatabaseRecordException; use TYPO3\CMS\Backend\Form\FormDataCompiler; use TYPO3\CMS\Backend\Form\FormDataGroup\OnTheFly; use TYPO3\CMS\Backend\Form\FormDataGroup\TcaDatabaseRecord; @@ -22,8 +23,11 @@ use TYPO3\CMS\Backend\Form\InlineStackProcessor; use TYPO3\CMS\Backend\Utility\BackendUtility; use TYPO3\CMS\Core\Authentication\BackendUserAuthentication; use TYPO3\CMS\Core\Database\RelationHandler; +use TYPO3\CMS\Core\Messaging\FlashMessage; +use TYPO3\CMS\Core\Messaging\FlashMessageService; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Versioning\VersionState; +use TYPO3\CMS\Lang\LanguageService; /** * Resolve and prepare inline data. @@ -313,11 +317,26 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid ]; // For foreign_selector with useCombination $mainChild is the mm record - // and $combinationChild is the child-child. For "normal" relations, $mainChild - // is just the normal child record and $combinationChild is empty. + // and $combinationChild is the child-child. For 1:n "normal" relations, + // $mainChild is just the normal child record and $combinationChild is empty. $mainChild = $formDataCompiler->compile($formDataCompilerInput); if ($parentConfig['foreign_selector'] && $parentConfig['appearance']['useCombination']) { - $mainChild['combinationChild'] = $this->compileChildChild($mainChild, $parentConfig); + try { + $mainChild['combinationChild'] = $this->compileChildChild($mainChild, $parentConfig); + } catch (DatabaseRecordException $e) { + // The child could not be compiled, probably it was deleted and a dangling mm record + // exists. This is a data inconsistency, we catch this exception and create a flash message + $message = vsprintf( + $this->getLanguageService()->sL('LLL:EXT:backend/Resources/Private/Language/locallang.xlf:formEngine.databaseRecordErrorInlineChildChild'), + [ $e->getTableName(), $e->getUid(), $childTableName, (int)$childUid ] + ); + $flashMessage = GeneralUtility::makeInstance(FlashMessage::class, + $message, + '', + FlashMessage::ERROR + ); + GeneralUtility::makeInstance(FlashMessageService::class)->getMessageQueueByIdentifier()->enqueue($flashMessage); + } } return $mainChild; } @@ -449,4 +468,12 @@ class TcaInline extends AbstractDatabaseRecordProvider implements FormDataProvid { return $GLOBALS['BE_USER']; } + + /** + * @return LanguageService + */ + protected function getLanguageService() + { + return $GLOBALS['LANG']; + } } diff --git a/typo3/sysext/backend/Resources/Private/Language/locallang.xlf b/typo3/sysext/backend/Resources/Private/Language/locallang.xlf index a40bd931e387c2b8132c52eff67da33fadf38f1b..b91a1cc3b9f0c52f0e35b0a9c5bd97311db3911a 100644 --- a/typo3/sysext/backend/Resources/Private/Language/locallang.xlf +++ b/typo3/sysext/backend/Resources/Private/Language/locallang.xlf @@ -91,6 +91,12 @@ Have a nice day.</source> <trans-unit id="button.hidePageTsConfig"> <source>Hide PageTS-Config</source> </trans-unit> + <trans-unit id="formEngine.databaseRecordErrorInlineChildChild"> + <source>The record with uid %2$s from table %1$s could not be retrieved from the database. This data incosistency can occur if + a base record has been deleted but the intermediate record from table %3$s with uid %4$s still points to it. To fix + this situation, either delete the intermediate record, or recover the deleted record using the recycler module. + </source> + </trans-unit> </body> </file> </xliff> diff --git a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.inline.js b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.inline.js index ff5eb7fb977ea147fded04d7d383184fafd8a531..1284193a4c58fda86d2658cdafd8e5dda0d7d789 100644 --- a/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.inline.js +++ b/typo3/sysext/backend/Resources/Public/JavaScript/jsfunc.inline.js @@ -305,7 +305,7 @@ var inline = { showAjaxFailure: function (method, xhr) { inline.unlockAjaxMethod(method); - alert('Error: ' + xhr.status + "\n" + xhr.statusText); + top.TYPO3.Notification.error('Error ' + xhr.status, xhr.statusText, 0); }, // foreign_selector: used by selector box (type='select') diff --git a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php index f0d083570e4f9a330c525ba9f374681ad35f19b2..e273f602b9fb7b24ff49d73dc40704d50dc1c651 100644 --- a/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php +++ b/typo3/sysext/backend/Tests/Unit/Form/FormDataProvider/DatabaseEditRowTest.php @@ -138,6 +138,27 @@ class DatabaseEditRowTest extends UnitTestCase $this->subject->addData($input); } + /** + * @test + */ + public function addDataThrowsExceptionDatabaseRecordExceptionWithAdditionalInformationSet() + { + $input = [ + 'tableName' => 'tt_content', + 'command' => 'edit', + 'vanillaUid' => 10, + ]; + $this->dbProphecy->quoteStr(Argument::cetera())->willReturn($input['tableName']); + $this->dbProphecy->exec_SELECTgetSingleRow(Argument::cetera())->willReturn(false); + + try { + $this->subject->addData($input); + } catch (DatabaseRecordException $e) { + $this->assertSame('tt_content', $e->getTableName()); + $this->assertSame(10, $e->getUid()); + } + } + /** * @test */