-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
abf0a9d
commit d314357
Showing
8 changed files
with
525 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Versioned; | ||
|
||
use SilverStripe\Core\Injector\Injectable; | ||
use SilverStripe\ORM\DataList; | ||
use SilverStripe\ORM\DataObject; | ||
use SilverStripe\ORM\SS_List; | ||
|
||
/** | ||
* Class RecursiveStagesService | ||
* | ||
* Functionality for detecting the need of publishing nested objects owned by common parent / ancestor object | ||
* | ||
* @package SilverStripe\Versioned | ||
*/ | ||
class RecursiveStagesService | ||
{ | ||
use Injectable; | ||
|
||
/** | ||
* Strong ownership uses 'owns' configuration to determine relationships | ||
*/ | ||
public const OWNERSHIP_STRONG = 'strong'; | ||
|
||
/** | ||
* Strong ownership uses 'cascade_duplicates' configuration to determine relationships | ||
*/ | ||
public const OWNERSHIP_WEAK = 'weak'; | ||
|
||
/** | ||
* Determine if content differs on stages including nested objects | ||
* | ||
* @param DataObject $object | ||
* @param string $mode | ||
* @return bool | ||
*/ | ||
public function stagesDifferRecursive(DataObject $object, string $mode): bool | ||
{ | ||
if (!$object->exists()) { | ||
return false; | ||
} | ||
|
||
$items = [$object]; | ||
|
||
// compare existing content | ||
while ($item = array_shift($items)) { | ||
if ($this->checkNeedPublishingItem($item)) { | ||
return true; | ||
} | ||
|
||
$relatedObjects = $this->findOwnedObjects($item, $mode); | ||
$items = array_merge($items, $relatedObjects); | ||
} | ||
|
||
// compare deleted content | ||
$draftIdentifiers = $this->findOwnedIdentifiers($object, $mode, Versioned::DRAFT); | ||
$liveIdentifiers = $this->findOwnedIdentifiers($object, $mode, Versioned::LIVE); | ||
|
||
return $draftIdentifiers !== $liveIdentifiers; | ||
} | ||
|
||
/** | ||
* Find all identifiers for owned objects | ||
* | ||
* @param DataObject $object | ||
* @param string $mode | ||
* @param string $stage | ||
* @return array | ||
*/ | ||
protected function findOwnedIdentifiers(DataObject $object, string $mode, string $stage): array | ||
{ | ||
$ids = Versioned::withVersionedMode(function () use ($object, $mode, $stage): array { | ||
Versioned::set_stage($stage); | ||
|
||
$object = DataObject::get_by_id($object->ClassName, $object->ID); | ||
|
||
if ($object === null) { | ||
return []; | ||
} | ||
|
||
$items = [$object]; | ||
$ids = []; | ||
|
||
while ($object = array_shift($items)) { | ||
$ids[] = implode('_', [$object->baseClass(), $object->ID]); | ||
$relatedObjects = $this->findOwnedObjects($object, $mode); | ||
$items = array_merge($items, $relatedObjects); | ||
} | ||
|
||
return $ids; | ||
}); | ||
|
||
sort($ids, SORT_STRING); | ||
|
||
return array_values($ids); | ||
} | ||
|
||
/** | ||
* This lookup will attempt to find "Strongly owned" objects | ||
* such objects are unable to exist without the current object | ||
* We will use "cascade_duplicates" setting for this purpose as we can assume that if an object needs to be | ||
* duplicated along with the owner object, it uses the strong ownership relation | ||
* | ||
* "Weakly owned" objects could be looked up via "owns" setting | ||
* Such objects can exist even without the owner objects as they are often used as shared objects | ||
* managed independently of their owners | ||
* | ||
* @param DataObject $object | ||
* @param string $mode | ||
* @return array | ||
*/ | ||
protected function findOwnedObjects(DataObject $object, string $mode): array | ||
{ | ||
$ownershipType = $mode === self::OWNERSHIP_WEAK | ||
? 'owns' | ||
: 'cascade_duplicates'; | ||
|
||
$relations = (array) $object->config()->get($ownershipType); | ||
$relations = array_unique($relations); | ||
$result = []; | ||
|
||
foreach ($relations as $relation) { | ||
$relation = (string) $relation; | ||
|
||
if (!$relation) { | ||
continue; | ||
} | ||
|
||
$relationData = $object->{$relation}(); | ||
|
||
if ($relationData instanceof DataObject) { | ||
if (!$relationData->exists()) { | ||
continue; | ||
} | ||
|
||
$result[] = $relationData; | ||
|
||
continue; | ||
} | ||
|
||
if (!$relationData instanceof SS_List) { | ||
continue; | ||
} | ||
|
||
foreach ($relationData as $relatedObject) { | ||
if (!$relatedObject instanceof DataObject || !$relatedObject->exists()) { | ||
continue; | ||
} | ||
|
||
$result[] = $relatedObject; | ||
} | ||
} | ||
|
||
return $result; | ||
} | ||
|
||
/** | ||
* @param DataObject|Versioned $item | ||
* @return bool | ||
*/ | ||
protected function checkNeedPublishingItem(DataObject $item): bool | ||
{ | ||
if ($item->hasExtension(Versioned::class)) { | ||
/** @var $item Versioned */ | ||
return !$item->isPublished() || $item->stagesDiffer(); | ||
} | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Versioned\Tests; | ||
|
||
use SilverStripe\Dev\SapphireTest; | ||
use SilverStripe\ORM\ValidationException; | ||
use SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\ChildObject; | ||
use SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\ColumnObject; | ||
use SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\GroupObject; | ||
use SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\PrimaryObject; | ||
use SilverStripe\Versioned\Versioned; | ||
|
||
class RecursiveStagesServiceTest extends SapphireTest | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
protected static $fixture_file = 'RecursiveStagesServiceTest.yml'; | ||
|
||
/** | ||
* @var string[] | ||
*/ | ||
protected static $extra_dataobjects = [ | ||
PrimaryObject::class, | ||
ColumnObject::class, | ||
GroupObject::class, | ||
ChildObject::class, | ||
]; | ||
|
||
protected static $required_extensions = [ | ||
PrimaryObject::class => [ | ||
Versioned::class, | ||
], | ||
ColumnObject::class => [ | ||
Versioned::class, | ||
], | ||
GroupObject::class => [ | ||
Versioned::class, | ||
], | ||
ChildObject::class => [ | ||
Versioned::class, | ||
], | ||
]; | ||
|
||
/** | ||
* @param string $class | ||
* @param string $identifier | ||
* @param bool $delete | ||
* @throws ValidationException | ||
* @dataProvider objectsProvider | ||
*/ | ||
public function testStageDiffersRecursive(string $class, string $identifier, bool $delete): void | ||
{ | ||
/** @var PrimaryObject|Versioned $primaryItem */ | ||
$primaryItem = $this->objFromFixture(PrimaryObject::class, 'primary-object-1'); | ||
$primaryItem->publishRecursive(); | ||
|
||
$this->assertFalse($primaryItem->stagesDifferRecursive()); | ||
|
||
$record = $this->objFromFixture($class, $identifier); | ||
|
||
if ($delete) { | ||
$record->delete(); | ||
} else { | ||
$record->Title = 'New Title'; | ||
$record->write(); | ||
} | ||
|
||
$this->assertTrue($primaryItem->stagesDifferRecursive()); | ||
} | ||
|
||
public function testStageDiffersRecursiveWithInvalidObject(): void | ||
{ | ||
/** @var PrimaryObject|Versioned $primaryItem */ | ||
$primaryItem = PrimaryObject::create(); | ||
|
||
$this->assertFalse($primaryItem->stagesDifferRecursive()); | ||
} | ||
|
||
public function objectsProvider(): array | ||
{ | ||
return [ | ||
[PrimaryObject::class, 'primary-object-1', false], | ||
[ColumnObject::class, 'column-1', false], | ||
[ColumnObject::class, 'column-1', true], | ||
[GroupObject::class, 'group-1', false], | ||
[GroupObject::class, 'group-1', true], | ||
[ChildObject::class, 'child-object-1', false], | ||
[ChildObject::class, 'child-object-1', true], | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# site-config-1 | ||
# -> primary-object-1 (top level publish object) | ||
# --> column-1 | ||
# ---> group-1 | ||
# ----> child-object-1 | ||
|
||
SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\PrimaryObject: | ||
primary-object-1: | ||
Title: PrimaryObject1 | ||
|
||
SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\ColumnObject: | ||
column-1: | ||
Title: Column1 | ||
PrimaryObject: =>SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\PrimaryObject.primary-object-1 | ||
|
||
SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\GroupObject: | ||
group-1: | ||
Title: Group1 | ||
Column: =>SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\ColumnObject.column-1 | ||
|
||
SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\ChildObject: | ||
child-object-1: | ||
Title: Item1 | ||
Group: =>SilverStripe\Versioned\Tests\RecursiveStagesServiceTest\GroupObject.group-1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Versioned\Tests\RecursiveStagesServiceTest; | ||
|
||
use SilverStripe\Dev\TestOnly; | ||
use SilverStripe\ORM\DataObject; | ||
|
||
/** | ||
* Class ChildObject | ||
* | ||
* @property string $Title | ||
* @property int $GroupID | ||
* @method GroupObject Group() | ||
* @package SilverStripe\Versioned\Tests\RecursiveStagesServiceTest | ||
*/ | ||
class ChildObject extends DataObject implements TestOnly | ||
{ | ||
/** | ||
* @var string | ||
*/ | ||
private static $table_name = 'RecursiveStagesServiceTest_ChildObject'; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private static $db = [ | ||
'Title' => 'Varchar(255)', | ||
]; | ||
|
||
/** | ||
* @var array | ||
*/ | ||
private static $has_one = [ | ||
'Group' => GroupObject::class, | ||
]; | ||
} |
Oops, something went wrong.