mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-09-30 05:09:06 +02:00
FIX: Graceful degradation if obsolete classnames in ChangeSetItem (fixes #6065)
NEW: Add SilverStripe\ORM\UnexpectedDataException class. This change provides more graceful handling of the case where a ChangeSetItem is referencing a no-longer-existing class. The new exception, SilverStripe\ORM\UnexpectedDataException, is intended to be available for throwing whenever we have unexpected data in the database. It can be trapped by the relevant UIs and more graceful errors than HTTP 500s can be provided.
This commit is contained in:
parent
fc72434ea5
commit
c52adad1fe
10
ORM/UnexpectedDataException.php
Normal file
10
ORM/UnexpectedDataException.php
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace SilverStripe\ORM;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw this exception whenever unexpected data is found.
|
||||||
|
*/
|
||||||
|
class UnexpectedDataException extends \Exception
|
||||||
|
{
|
||||||
|
}
|
@ -11,6 +11,7 @@ use SilverStripe\ORM\HasManyList;
|
|||||||
use SilverStripe\ORM\ValidationException;
|
use SilverStripe\ORM\ValidationException;
|
||||||
use SilverStripe\ORM\DB;
|
use SilverStripe\ORM\DB;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\UnexpectedDataException;
|
||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
@ -202,6 +203,7 @@ class ChangeSet extends DataObject {
|
|||||||
$explicit[$explicitKey] = true;
|
$explicit[$explicitKey] = true;
|
||||||
|
|
||||||
foreach ($item->findReferenced() as $referee) {
|
foreach ($item->findReferenced() as $referee) {
|
||||||
|
try {
|
||||||
/** @var DataObject $referee */
|
/** @var DataObject $referee */
|
||||||
$key = $this->implicitKey($referee);
|
$key = $this->implicitKey($referee);
|
||||||
|
|
||||||
@ -211,6 +213,10 @@ class ChangeSet extends DataObject {
|
|||||||
];
|
];
|
||||||
|
|
||||||
$references[$key][] = $item->ID;
|
$references[$key][] = $item->ID;
|
||||||
|
|
||||||
|
// Skip any bad records
|
||||||
|
} catch(UnexpectedDataException $e) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ use SilverStripe\ORM\DataList;
|
|||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
use SilverStripe\ORM\ManyManyList;
|
use SilverStripe\ORM\ManyManyList;
|
||||||
use SilverStripe\ORM\SS_List;
|
use SilverStripe\ORM\SS_List;
|
||||||
|
use SilverStripe\ORM\UnexpectedDataException;
|
||||||
use SilverStripe\Security\Member;
|
use SilverStripe\Security\Member;
|
||||||
use SilverStripe\Security\Permission;
|
use SilverStripe\Security\Permission;
|
||||||
use BadMethodCallException;
|
use BadMethodCallException;
|
||||||
@ -112,6 +113,10 @@ class ChangeSetItem extends DataObject implements Thumbnail {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public function getChangeType() {
|
public function getChangeType() {
|
||||||
|
if(!class_exists($this->ObjectClass)) {
|
||||||
|
throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
|
||||||
|
}
|
||||||
|
|
||||||
// Get change versions
|
// Get change versions
|
||||||
if($this->VersionBefore || $this->VersionAfter) {
|
if($this->VersionBefore || $this->VersionAfter) {
|
||||||
$draftVersion = $this->VersionAfter; // After publishing draft was written to stage
|
$draftVersion = $this->VersionAfter; // After publishing draft was written to stage
|
||||||
@ -144,6 +149,10 @@ class ChangeSetItem extends DataObject implements Thumbnail {
|
|||||||
* @return Versioned|DataObject
|
* @return Versioned|DataObject
|
||||||
*/
|
*/
|
||||||
protected function getObjectInStage($stage) {
|
protected function getObjectInStage($stage) {
|
||||||
|
if(!class_exists($this->ObjectClass)) {
|
||||||
|
throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
|
||||||
|
}
|
||||||
|
|
||||||
return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
|
return Versioned::get_by_stage($this->ObjectClass, $stage)->byID($this->ObjectID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +162,10 @@ class ChangeSetItem extends DataObject implements Thumbnail {
|
|||||||
* @return Versioned|DataObject
|
* @return Versioned|DataObject
|
||||||
*/
|
*/
|
||||||
protected function getObjectLatestVersion() {
|
protected function getObjectLatestVersion() {
|
||||||
|
if(!class_exists($this->ObjectClass)) {
|
||||||
|
throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
|
||||||
|
}
|
||||||
|
|
||||||
return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
|
return Versioned::get_latest_version($this->ObjectClass, $this->ObjectID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,6 +193,10 @@ class ChangeSetItem extends DataObject implements Thumbnail {
|
|||||||
* Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
|
* Note: Unlike Versioned::doPublish() and Versioned::doUnpublish, this action is not recursive.
|
||||||
*/
|
*/
|
||||||
public function publish() {
|
public function publish() {
|
||||||
|
if(!class_exists($this->ObjectClass)) {
|
||||||
|
throw new UnexpectedDataException("Invalid Class '{$this->ObjectClass}' in ChangeSetItem #{$this->ID}");
|
||||||
|
}
|
||||||
|
|
||||||
// Logical checks prior to publish
|
// Logical checks prior to publish
|
||||||
if(!$this->canPublish()) {
|
if(!$this->canPublish()) {
|
||||||
throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
|
throw new Exception("The current member does not have permission to publish this ChangeSetItem.");
|
||||||
|
@ -14,6 +14,7 @@ use SilverStripe\ORM\SS_List;
|
|||||||
use SilverStripe\ORM\Versioning\ChangeSet;
|
use SilverStripe\ORM\Versioning\ChangeSet;
|
||||||
use SilverStripe\ORM\Versioning\ChangeSetItem;
|
use SilverStripe\ORM\Versioning\ChangeSetItem;
|
||||||
use SilverStripe\ORM\DataObject;
|
use SilverStripe\ORM\DataObject;
|
||||||
|
use SilverStripe\ORM\UnexpectedDataException;
|
||||||
use SilverStripe\Security\SecurityToken;
|
use SilverStripe\Security\SecurityToken;
|
||||||
use SilverStripe\Security\PermissionProvider;
|
use SilverStripe\Security\PermissionProvider;
|
||||||
use LogicException;
|
use LogicException;
|
||||||
@ -256,9 +257,6 @@ JSON;
|
|||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
protected function getChangeSetResource(ChangeSet $changeSet) {
|
protected function getChangeSetResource(ChangeSet $changeSet) {
|
||||||
// Before presenting the changeset to the client,
|
|
||||||
// synchronise it with new changes.
|
|
||||||
$changeSet->sync();
|
|
||||||
$hal = [
|
$hal = [
|
||||||
'_links' => [
|
'_links' => [
|
||||||
'self' => [
|
'self' => [
|
||||||
@ -267,14 +265,21 @@ JSON;
|
|||||||
],
|
],
|
||||||
'ID' => $changeSet->ID,
|
'ID' => $changeSet->ID,
|
||||||
'Name' => $changeSet->Name,
|
'Name' => $changeSet->Name,
|
||||||
'Description' => $changeSet->getDescription(),
|
|
||||||
'Created' => $changeSet->Created,
|
'Created' => $changeSet->Created,
|
||||||
'LastEdited' => $changeSet->LastEdited,
|
'LastEdited' => $changeSet->LastEdited,
|
||||||
'State' => $changeSet->State,
|
'State' => $changeSet->State,
|
||||||
'canEdit' => $changeSet->canEdit(),
|
'canEdit' => $changeSet->canEdit(),
|
||||||
'canPublish' => $changeSet->canPublish(),
|
'canPublish' => false,
|
||||||
'_embedded' => ['items' => []]
|
'_embedded' => ['items' => []]
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Before presenting the changeset to the client,
|
||||||
|
// synchronise it with new changes.
|
||||||
|
try {
|
||||||
|
$changeSet->sync();
|
||||||
|
$hal['Description'] = $changeSet->getDescription();
|
||||||
|
$hal['canPublish'] = $changeSet->canPublish();
|
||||||
|
|
||||||
foreach($changeSet->Changes() as $changeSetItem) {
|
foreach($changeSet->Changes() as $changeSetItem) {
|
||||||
if(!$changeSetItem) {
|
if(!$changeSetItem) {
|
||||||
continue;
|
continue;
|
||||||
@ -285,6 +290,12 @@ JSON;
|
|||||||
$hal['_embedded']['items'][] = $resource;
|
$hal['_embedded']['items'][] = $resource;
|
||||||
}
|
}
|
||||||
$hal['ChangesCount'] = count($hal['_embedded']['items']);
|
$hal['ChangesCount'] = count($hal['_embedded']['items']);
|
||||||
|
|
||||||
|
// An unexpected data exception means that the database is corrupt
|
||||||
|
} catch(UnexpectedDataException $e) {
|
||||||
|
$hal['Description'] = 'Corrupt database! ' . $e->getMessage();
|
||||||
|
$hal['ChangesCount'] = '-';
|
||||||
|
}
|
||||||
return $hal;
|
return $hal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
40
admin/tests/CampaignAdminTest.php
Normal file
40
admin/tests/CampaignAdminTest.php
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use SilverStripe\Dev\SapphireTest;
|
||||||
|
use SilverStripe\Admin\CampaignAdmin;
|
||||||
|
use SilverStripe\ORM\Versioning\ChangeSet;
|
||||||
|
use SilverStripe\ORM\UnexpectedDataException;
|
||||||
|
|
||||||
|
class CampaignAdminTest extends SapphireTest
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a protected method on an object via reflection
|
||||||
|
*
|
||||||
|
* @param object $object The object to call the method on
|
||||||
|
* @param string $method The name of the method
|
||||||
|
* @param array $args The arguments to pass to the method
|
||||||
|
*/
|
||||||
|
function callProtectedMethod($object, $method, $args = []) {
|
||||||
|
$class = new ReflectionClass(get_class($object));
|
||||||
|
$methodObj = $class->getMethod($method);
|
||||||
|
$methodObj->setAccessible(true);
|
||||||
|
return $methodObj->invokeArgs($object, $args);
|
||||||
|
}
|
||||||
|
|
||||||
|
function testInvalidDataHandling() {
|
||||||
|
$changeset = new CampaignAdminTest_InvalidChangeSet();
|
||||||
|
$admin = new CampaignAdmin();
|
||||||
|
|
||||||
|
$result = $this->callProtectedMethod($admin, 'getChangeSetResource', [$changeset] );
|
||||||
|
$this->assertEquals('Corrupt database! bad data' , $result['Description']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CampaignAdminTest_InvalidChangeSet extends ChangeSet
|
||||||
|
{
|
||||||
|
function sync()
|
||||||
|
{
|
||||||
|
throw new UnexpectedDataException("bad data");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user