Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add shadow functionality to publishing process #243

Merged
merged 5 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions Content/Application/ContentCopier/ContentCopier.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,36 @@ public function copy(
ContentRichEntityInterface $sourceContentRichEntity,
array $sourceDimensionAttributes,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface {
$sourceDimensionContent = $this->contentResolver->resolve($sourceContentRichEntity, $sourceDimensionAttributes);

return $this->copyFromDimensionContent($sourceDimensionContent, $targetContentRichEntity, $targetDimensionAttributes);
return $this->copyFromDimensionContent($sourceDimensionContent, $targetContentRichEntity, $targetDimensionAttributes, $options);
}

public function copyFromDimensionContentCollection(
DimensionContentCollectionInterface $dimensionContentCollection,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface {
$sourceDimensionContent = $this->contentMerger->merge($dimensionContentCollection);

return $this->copyFromDimensionContent($sourceDimensionContent, $targetContentRichEntity, $targetDimensionAttributes);
return $this->copyFromDimensionContent($sourceDimensionContent, $targetContentRichEntity, $targetDimensionAttributes, $options);
}

public function copyFromDimensionContent(
DimensionContentInterface $dimensionContent,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface {
$data = $this->contentNormalizer->normalize($dimensionContent);
$data = \array_replace($this->contentNormalizer->normalize($dimensionContent), $options['data'] ?? []);

foreach (($options['ignoredAttributes'] ?? []) as $ignoredAttribute) {
unset($data[$ignoredAttribute]);
}

return $this->contentPersister->persist($targetContentRichEntity, $data, $targetDimensionAttributes);
}
Expand Down
12 changes: 9 additions & 3 deletions Content/Application/ContentCopier/ContentCopierInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,16 @@ interface ContentCopierInterface
* @param mixed[] $sourceDimensionAttributes
* @param ContentRichEntityInterface<T> $targetContentRichEntity
* @param mixed[] $targetDimensionAttributes
* @param array{data?: mixed[], ignoredAttributes?: string[]} $options the "data" allows given custom data to the target and "ignoredAttributes" avoids specific attributes to be copied
*
* @return T
*/
public function copy(
ContentRichEntityInterface $sourceContentRichEntity,
array $sourceDimensionAttributes,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface;

/**
Expand All @@ -42,13 +44,15 @@ public function copy(
* @param DimensionContentCollectionInterface<T> $dimensionContentCollection
* @param ContentRichEntityInterface<T> $targetContentRichEntity
* @param mixed[] $targetDimensionAttributes
* @param array{data?: mixed[], ignoredAttributes?: string[]} $options the "data" allows given custom data to the target and "ignoredAttributes" avoids specific attributes to be copied
*
* @return T
*/
public function copyFromDimensionContentCollection(
DimensionContentCollectionInterface $dimensionContentCollection,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface;

/**
Expand All @@ -57,12 +61,14 @@ public function copyFromDimensionContentCollection(
* @param T $dimensionContent
* @param ContentRichEntityInterface<T> $targetContentRichEntity
* @param mixed[] $targetDimensionAttributes
* @param array{data?: mixed[], ignoredAttributes?: string[]} $options the "data" allows given custom data to the target and "ignoredAttributes" avoids specific attributes to be copied
*
* @return T
*/
public function copyFromDimensionContent(
DimensionContentInterface $dimensionContent,
ContentRichEntityInterface $targetContentRichEntity,
array $targetDimensionAttributes
array $targetDimensionAttributes,
array $options = []
): DimensionContentInterface;
}
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,12 @@ public function map(

/** @var string $name */
$name = $property->getName();
if ('url' !== $name) {
throw new \RuntimeException(\sprintf(
'Expected a property with the name "url" but "%s" given.',
$name
)); // TODO move this validation to a compiler pass see also direct access of 'url' in PublishTransitionSubscriber class.
}

$currentRoutePath = $localizedDimensionContent->getTemplateData()[$name] ?? null;
if (!\array_key_exists($name, $data) && null !== $currentRoutePath) {
Expand Down Expand Up @@ -194,6 +200,7 @@ public function map(
private function getRouteProperty(StructureMetadata $metadata): ?PropertyMetadata
{
foreach ($metadata->getProperties() as $property) {
// TODO add support for page_tree_route field type: https://github.com/sulu/SuluContentBundle/issues/242
if ('route' === $property->getType()) {
return $property;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public function map(
DimensionContentInterface $localizedDimensionContent,
array $data
): void {
if (!$localizedDimensionContent instanceof ShadowInterface) {
if (!$unlocalizedDimensionContent instanceof ShadowInterface
|| !$localizedDimensionContent instanceof ShadowInterface
) {
return;
}

Expand All @@ -33,11 +35,19 @@ public function map(
/** @var string|null $shadowLocale */
$shadowLocale = $data['shadowLocale'] ?? null;

$locale = $localizedDimensionContent->getLocale();

$localizedDimensionContent->setShadowLocale(
$shadowOn
$shadowOn && $locale
? $shadowLocale
: null
);

if ($locale && $shadowLocale) {
$unlocalizedDimensionContent->addShadowLocale($locale, $shadowLocale);
} elseif ($locale) {
$unlocalizedDimensionContent->removeShadowLocale($locale);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,12 @@ public function map(
throw new \RuntimeException('Expected "template" to be set in the data array.');
}

list($unlocalizedData, $localizedData, $hasAnyValue) = $this->getTemplateData(
[$unlocalizedData, $localizedData, $hasAnyValue] = $this->getTemplateData(
$data,
$type,
$template
$template,
$unlocalizedDimensionContent->getTemplateData(),
$localizedDimensionContent->getTemplateData()
);

if (!isset($data['template']) && !$hasAnyValue) {
Expand All @@ -75,44 +77,47 @@ public function map(

$localizedDimensionContent->setTemplateKey($template);
$localizedDimensionContent->setTemplateData($localizedData);

$unlocalizedDimensionContent->setTemplateData(\array_merge(
$unlocalizedDimensionContent->getTemplateData(),
$unlocalizedData
));
$unlocalizedDimensionContent->setTemplateData($unlocalizedData);
}

/**
* @param mixed[] $data
* @param mixed[] $unlocalizedData
* @param mixed[] $localizedData
*
* @return array{
* 0: mixed[],
* 1: mixed[],
* 2: bool,
* 0: mixed[],
* 1: mixed[],
* 2: bool,
* }
*/
private function getTemplateData(array $data, string $type, string $template): array
{
private function getTemplateData(
array $data,
string $type,
string $template,
array $unlocalizedData,
array $localizedData
): array {
$metadata = $this->factory->getStructureMetadata($type, $template);

if (!$metadata) {
throw new \RuntimeException(\sprintf('Could not find structure "%s" of type "%s".', $template, $type));
}

$unlocalizedData = [];
$localizedData = [];
$hasAnyValue = false;

$defaultLocalizedData = $localizedData; // use existing localizedData only as default to remove not longer existing properties of the template
$localizedData = [];
foreach ($metadata->getProperties() as $property) {
$value = null;
$name = $property->getName();

// Float are converted to ints in php array as key so we need convert it to string
if (\is_float($name)) {
$name = (string) $name;
}

if (\array_key_exists($name, $data)) {
$value = $property->isLocalized() ? $defaultLocalizedData[$name] ?? null : $defaultLocalizedData[$name] ?? null;
if (\array_key_exists($name, $data)) { // values not explicitly given need to stay untouched for e.g. for shadow pages urls
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (\array_key_exists($name, $data)) { // values not explicitly given need to stay untouched for e.g. for shadow pages urls
if (\array_key_exists($name, $data)) { // values not explicitly given need to stay untouched for e.g. shadow pages urls

$hasAnyValue = true;
$value = $data[$name];
}
Expand Down
2 changes: 1 addition & 1 deletion Content/Application/ContentMerger/Merger/ShadowMerger.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public function merge(object $targetObject, object $sourceObject): void
$targetObject->setShadowLocale($shadowLocale);
}

foreach ($sourceObject->getShadowLocales() ?: [] as $locale => $shadowLocale) {
foreach (($sourceObject->getShadowLocales() ?? []) as $locale => $shadowLocale) {
$targetObject->addShadowLocale($locale, $shadowLocale);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentCollectionInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\TemplateInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\WorkflowInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\TransitionEvent;

/**
* @final
*
* @internal this class is internal and should not be extended from or used in another context
*/
class PublishTransitionSubscriber implements EventSubscriberInterface
{
/**
Expand Down Expand Up @@ -66,12 +73,69 @@ public function onPublish(TransitionEvent $transitionEvent): void
throw new \RuntimeException('No "contentRichEntity" given.');
}

$dimensionAttributes['stage'] = DimensionContentInterface::STAGE_LIVE;
$sourceDimensionAttributes = $dimensionAttributes;
$targetDimensionAttributes = $dimensionAttributes;
$targetDimensionAttributes['stage'] = DimensionContentInterface::STAGE_LIVE;

$shadowLocale = $dimensionContent instanceof ShadowInterface
? $dimensionContent->getShadowLocale()
: null;

/** @var string $locale */
$locale = $dimensionContent->getLocale();

if (!$shadowLocale) {
$publishedDimensionContent = $this->contentCopier->copyFromDimensionContentCollection(
$dimensionContentCollection,
$contentRichEntity,
$targetDimensionAttributes
);

if (!$publishedDimensionContent instanceof ShadowInterface) {
return;
}

$shadowLocales = $publishedDimensionContent->getShadowLocalesForLocale($locale);

foreach ($shadowLocales as $shadowLocale) {
$targetDimensionAttributes['locale'] = $shadowLocale;

$this->contentCopier->copyFromDimensionContentCollection(
$dimensionContentCollection,
$contentRichEntity,
$targetDimensionAttributes,
[
'ignoredAttributes' => [
'shadowOn',
'shadowLocale',
'url',
],
]
);
}

return;
}

$sourceDimensionAttributes['locale'] = $shadowLocale;
$sourceDimensionAttributes['stage'] = DimensionContentInterface::STAGE_LIVE;

$this->contentCopier->copyFromDimensionContentCollection(
$dimensionContentCollection,
$data = [
// @see \Sulu\Bundle\ContentBundle\Content\Application\ContentDataMapper\DataMapper\ShadowDataMapper::map
'shadowOn' => true,
'shadowLocale' => $shadowLocale,
];

if ($dimensionContent instanceof TemplateInterface) {
$data['url'] = $dimensionContent->getTemplateData()['url'] ?? null; // TODO get correct route property
}

$this->contentCopier->copy(
$contentRichEntity,
$sourceDimensionAttributes,
$contentRichEntity,
$dimensionAttributes
$targetDimensionAttributes,
['data' => $data]
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\TransitionEvent;

/**
* @final
*
* @internal this class is internal and should not be extended from or used in another context
*/
class RemoveDraftTransitionSubscriber implements EventSubscriberInterface
{
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@
use Sulu\Bundle\ContentBundle\Content\Domain\Exception\ContentNotFoundException;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ShadowInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Model\WorkflowInterface;
use Sulu\Bundle\ContentBundle\Content\Domain\Repository\DimensionContentRepositoryInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Workflow\Event\TransitionEvent;

/**
* @final
*
* @internal this class is internal and should not be extended from or used in another context
*/
class UnpublishTransitionSubscriber implements EventSubscriberInterface
{
/**
Expand Down Expand Up @@ -83,6 +89,10 @@ public function onUnpublish(TransitionEvent $transitionEvent): void
/** @var DimensionContentInterface $unlocalizedLiveDimensionContent */
$unlocalizedLiveDimensionContent = $dimensionContentCollection->getDimensionContent($unlocalizedLiveDimensionAttributes); // @phpstan-ignore-line we can not define the generic of DimensionContentInterface here
$unlocalizedLiveDimensionContent->removeAvailableLocale($locale);

if ($unlocalizedLiveDimensionContent instanceof ShadowInterface) {
$unlocalizedLiveDimensionContent->removeShadowLocale($locale);
}
}

$this->entityManager->remove($localizedLiveDimensionContent);
Expand Down
3 changes: 0 additions & 3 deletions Content/Domain/Model/RoutableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@

namespace Sulu\Bundle\ContentBundle\Content\Domain\Model;

/**
* Marker interface for autoloading the doctrine metadata for routables.
*/
interface RoutableInterface
{
public static function getResourceKey(): string;
Expand Down
11 changes: 11 additions & 0 deletions Content/Domain/Model/ShadowInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ public function addShadowLocale(string $locale, string $shadowLocale): void;
public function removeShadowLocale(string $locale): void;

/**
* Returns the locales which shadow the given locale.
*
* @return array<string, string>|null
*/
public function getShadowLocales(): ?array;

/**
* @internal should only be set by content bundle services not from outside
*
* Returns the locales which shadow the given locale
*
* @return string[]
*/
public function getShadowLocalesForLocale(string $shadowLocale): array;
}
Loading