From 5ccdeb32d3bfc1d94e40a8b9881bb048bbf72a1f Mon Sep 17 00:00:00 2001 From: Christian Kuhn <lolli@schwarzbu.ch> Date: Thu, 28 Apr 2016 19:09:04 +0200 Subject: [PATCH] [BUGFIX] Exception editing inline mm with deleted child child Have an inline m:m record and delete one child child that has an intermediate record pointing to it. Opening the parent throws a DatabaseRecordException. The patch extends this exception to add tableName and uid, then catches the exception in the inline data provider, creates a nice error message as flash message and continues displaying record. Change-Id: I1792716b4e5454b11499cb2ba684bac403b3f13d Resolves: #71719 Releases: master, 7.6 Reviewed-on: https://review.typo3.org/47959 Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl> Reviewed-by: Michael Oehlhof <typo3@oehlhof.de> Tested-by: Michael Oehlhof <typo3@oehlhof.de> Reviewed-by: Frank Naegler <frank.naegler@typo3.org> Tested-by: Frank Naegler <frank.naegler@typo3.org> --- .../Exception/DatabaseRecordException.php | 45 +++++++++++++++++++ .../AbstractDatabaseRecordProvider.php | 5 ++- .../Form/FormDataProvider/TcaInline.php | 33 ++++++++++++-- .../Resources/Private/Language/locallang.xlf | 6 +++ .../Public/JavaScript/jsfunc.inline.js | 2 +- .../FormDataProvider/DatabaseEditRowTest.php | 21 +++++++++ 6 files changed, 107 insertions(+), 5 deletions(-) diff --git a/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php b/typo3/sysext/backend/Classes/Form/Exception/DatabaseRecordException.php index 59906adc208c..377815432ffb 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 04ef531361f0..234775f47081 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 126c552e19ed..a8220c6889eb 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 a40bd931e387..b91a1cc3b9f0 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 ff5eb7fb977e..1284193a4c58 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 f0d083570e4f..e273f602b9fb 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 */ -- GitLab