Commit 5ccdeb32 authored by Christian Kuhn's avatar Christian Kuhn Committed by Frank Nägler
Browse files

[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's avatarWouter Wolters <typo3@wouterwolters.nl>
Reviewed-by: default avatarMichael Oehlhof <typo3@oehlhof.de>
Tested-by: default avatarMichael Oehlhof <typo3@oehlhof.de>
Reviewed-by: Frank Nägler's avatarFrank Naegler <frank.naegler@typo3.org>
Tested-by: Frank Nägler's avatarFrank Naegler <frank.naegler@typo3.org>
parent 93d3a9a3
......@@ -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;
}
}
......@@ -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)) {
......
......@@ -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'];
}
}
......@@ -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>
......@@ -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')
......
......@@ -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
*/
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment