public function testHandleWebspace() { $document = $this->prophesize(WebspaceBehavior::class); $this->persistEvent->getDocument()->willReturn($document); $this->inspector->getWebspace($document->reveal())->willReturn('example'); $this->accessor->set('webspaceName', 'example')->shouldBeCalled(); $this->subscriber->handleWebspace($this->persistEvent->reveal()); }
/** * Return a structure bridge corresponding to the given document. * * @param BasePageDocument $document * * @return PageBridge */ protected function documentToStructure(BasePageDocument $document) { $structure = $this->inspector->getStructureMetadata($document); $documentAlias = $this->inspector->getMetadata($document)->getAlias(); $structureBridge = $this->structureManager->wrapStructure($documentAlias, $structure); $structureBridge->setDocument($document); return $structureBridge; }
/** * @param AbstractMappingEvent $event */ public function handleWebspace(AbstractMappingEvent $event) { $document = $event->getDocument(); if (!$document instanceof WebspaceBehavior) { return; } $webspaceName = $this->inspector->getWebspace($document); $event->getAccessor()->set('webspaceName', $webspaceName); }
private function getResourceSegmentProperty($document) { $structure = $this->inspector->getStructureMetadata($document); $property = $structure->getPropertyByTagName('sulu.rlp'); if (!$property) { throw new \RuntimeException(sprintf('Structure "%s" does not have a "sulu.rlp" tag which is required for documents implementing the ' . 'ResourceSegmentBehavior. In "%s"', $structure->name, $structure->resource)); } return $property; }
/** * Adds the enabled shadow languages to the serialization. * * @param ObjectEvent $event */ public function onPostSerialize(ObjectEvent $event) { $document = $event->getObject(); if (!$document instanceof ShadowLocaleBehavior || !$this->documentRegistry->hasDocument($document)) { return; } $visitor = $event->getVisitor(); $visitor->addData('enabledShadowLanguages', $this->documentInspector->getShadowLocales($document)); }
/** * Adds a flag to indicate if the document has children. * * @param ObjectEvent $event */ public function onPostSerialize(ObjectEvent $event) { $document = $event->getObject(); if (!$document instanceof ChildrenBehavior || !$this->documentRegistry->hasDocument($document)) { return; } $visitor = $event->getVisitor(); $visitor->addData('hasSub', $this->documentInspector->hasChildren($document)); }
/** * Adds the relative path to the serialization. * * @param ObjectEvent $event */ public function onPostSerialize(ObjectEvent $event) { $visitor = $event->getVisitor(); $document = $event->getObject(); if (!$document instanceof PathBehavior || !$this->documentRegistry->hasDocument($document)) { return; } $visitor->addData('path', $this->documentInspector->getContentPath($document)); }
/** * {@inheritdoc} */ public function translateObject($object, $locale) { $document = $this->documentManager->find($this->inspector->getUuid($object), $locale); if ($document instanceof WorkflowStageBehavior && $this->context === SuluKernel::CONTEXT_ADMIN) { // set the workflowstage to test, so that the document will be indexed in the index for drafting // this change must not be persisted // is required because of the expression for the index name uses the workflowstage $document->setWorkflowStage(WorkflowStage::TEST); } return $document; }
public function setUp() { $this->factory = $this->prophesize(StructureMetadataFactory::class); $this->extensionManager = $this->prophesize(ExtensionManager::class); $this->inspector = $this->prophesize(DocumentInspector::class); $this->structure = $this->prophesize(StructureMetadata::class); $this->extension = $this->prophesize(ExtensionInterface::class); $this->propertyFactory = $this->prophesize(LegacyPropertyFactory::class); $typemap = ['page' => '\\Sulu\\Component\\Content\\Compat\\Structure\\PageBridge', 'home' => '\\Sulu\\Component\\Content\\Compat\\Structure\\PageBridge', 'snippet' => '\\Sulu\\Component\\Content\\Compat\\Structure\\SnippetBridge']; $this->structureManager = new StructureManager($this->factory->reveal(), $this->extensionManager->reveal(), $this->inspector->reveal(), $this->propertyFactory->reveal(), $typemap); }
/** * Prefix url of document with current resourcelocator prefix. * * @param IndexRebuildEvent $event */ public function onIndexRebuild(IndexRebuildEvent $event) { $output = $event->getOutput(); $filter = $event->getFilter(); $output->writeln('<info>Rebuilding content index</info>'); $typeMap = $this->baseMetadataFactory->getPhpcrTypeMap(); $phpcrTypes = []; foreach ($typeMap as $type) { $phpcrType = $type['phpcr_type']; if ($phpcrType !== 'sulu:path') { $phpcrTypes[] = sprintf('[jcr:mixinTypes] = "%s"', $phpcrType); } } $condition = implode(' or ', $phpcrTypes); // TODO: We cannot select all contents via. the parent type, see: https://github.com/jackalope/jackalope-doctrine-dbal/issues/217 $query = $this->documentManager->createQuery('SELECT * FROM [nt:unstructured] AS a WHERE ' . $condition); $count = []; $documents = $query->execute(); $progress = new ProgressHelper(); $progress->start($output, count($documents)); foreach ($documents as $document) { if ($document instanceof SecurityBehavior && !empty($document->getPermissions())) { $progress->advance(); continue; } $locales = $this->inspector->getLocales($document); foreach ($locales as $locale) { try { $this->documentManager->find($document->getUuid(), $locale); $documentClass = get_class($document); if ($filter && !preg_match('{' . $filter . '}', $documentClass)) { continue; } $this->searchManager->index($document, $locale); if (!isset($count[$documentClass])) { $count[$documentClass] = 0; } ++$count[$documentClass]; } catch (\Exception $e) { $output->writeln(sprintf('<error>Error indexing or de-indexing page (path: %s locale: %s)</error>: %s', $this->inspector->getPath($document), $locale, $e->getMessage())); } } $progress->advance(); } $output->writeln(''); foreach ($count as $className => $count) { if ($count == 0) { continue; } $output->writeln(sprintf('<comment>Content</comment>: %s <info>%s</info> indexed', $className, $count)); } }
/** * Updates the route for the given document after a move or copy. * * @param object $document * @param bool $generateRoutes If set to true a route in the routing tree will also be created */ private function updateRoute($document, $generateRoutes) { $locales = $this->documentInspector->getLocales($document); $webspaceKey = $this->documentInspector->getWebspace($document); $uuid = $this->documentInspector->getUuid($document); $path = $this->documentInspector->getPath($document); $parentUuid = $this->documentInspector->getUuid($this->documentInspector->getParent($document)); $defaultNode = $this->defaultSession->getNode($path); $liveNode = $this->liveSession->getNode($path); $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey($webspaceKey); foreach ($locales as $locale) { $localizedDocument = $this->documentManager->find($uuid, $locale); if ($localizedDocument->getRedirectType() !== RedirectType::NONE) { continue; } $resourceSegmentPropertyName = $this->encoder->localizedSystemName($this->getResourceSegmentProperty($localizedDocument)->getName(), $locale); $this->updateResourceSegmentProperty($defaultNode, $resourceSegmentPropertyName, $parentUuid, $webspaceKey, $locale); if ($liveNode->hasProperty($resourceSegmentPropertyName)) { $this->updateResourceSegmentProperty($liveNode, $resourceSegmentPropertyName, $parentUuid, $webspaceKey, $locale); // if the method is called with the generateRoutes flag it will create a new route // this happens on a move, but not on copy, because copy results in a draft page without url if ($generateRoutes) { $localizedDocument->setResourceSegment($liveNode->getPropertyValue($resourceSegmentPropertyName)); $resourceLocatorStrategy->save($localizedDocument, null); $localizedDocument->setResourceSegment($defaultNode->getPropertyValue($resourceSegmentPropertyName)); } } } }
/** * Adds the concrete languages available and the type (ghost or shadow) of the document to the serialization. * * @param ObjectEvent $event */ public function onPostSerialize(ObjectEvent $event) { $document = $event->getObject(); if (!$document instanceof LocaleBehavior || !$this->documentRegistry->hasDocument($document)) { return; } $visitor = $event->getVisitor(); $visitor->addData('concreteLanguages', $this->documentInspector->getConcreteLocales($document)); $localizationState = $this->documentInspector->getLocalizationState($document); if ($localizationState === LocalizationState::GHOST) { $visitor->addData('type', ['name' => 'ghost', 'value' => $document->getLocale()]); } if ($localizationState === LocalizationState::SHADOW) { $visitor->addData('type', ['name' => 'shadow', 'value' => $document->getLocale()]); } }
/** * It shuld lazily initialize a localized property. */ public function testGetLocalizedProperty() { $name = 'test'; $contentTypeName = 'hello'; $locale = 'fr'; $this->inspector->getLocale($this->document->reveal())->willReturn($locale); $this->propertyMetadata->isLocalized()->willReturn(true); $this->doGetProperty($name, $contentTypeName, $locale); }
/** * Prefix url of document with current resourcelocator prefix. * * @param IndexRebuildEvent $event */ public function onIndexRebuild(IndexRebuildEvent $event) { $output = $event->getOutput(); $purge = $event->getPurge(); $filter = $event->getFilter(); $output->writeln('<info>Rebuilding content index</info>'); // TODO: We cannot select all contents via. the parent type, see: https://github.com/jackalope/jackalope-doctrine-dbal/issues/217 $query = $this->documentManager->createQuery('SELECT * FROM [nt:unstructured] AS a WHERE [jcr:mixinTypes] = "sulu:page" or [jcr:mixinTypes] = "sulu:snippet"'); $count = []; if ($purge) { $this->purgeContentIndexes($output); } $documents = $query->execute(); $progress = new ProgressHelper(); $progress->start($output, count($documents)); foreach ($documents as $document) { $locales = $this->inspector->getLocales($document); foreach ($locales as $locale) { try { $this->documentManager->find($document->getUuid(), $locale); $documentClass = get_class($document); if ($filter && !preg_match('{' . $filter . '}', $documentClass)) { continue; } $this->searchManager->index($document, $locale); if (!isset($count[$documentClass])) { $count[$documentClass] = 0; } ++$count[$documentClass]; } catch (\Exception $e) { $output->writeln(sprintf('<error>Error indexing or de-indexing page (path: %s locale: %s)</error>: %s', $this->inspector->getPath($document), $locale, $e->getMessage())); } } $progress->advance(); } $output->writeln(''); foreach ($count as $className => $count) { if ($count == 0) { continue; } $output->writeln(sprintf('<comment>Content</comment>: %s <info>%s</info> indexed', $className, $count)); } }
private function validateShadow(ShadowLocaleBehavior $document) { if ($document->getLocale() === $document->getShadowLocale()) { throw new \RuntimeException(sprintf('Document cannot be a shadow of itself for locale "%s"', $document->getLocale())); } $locales = $this->inspector->getConcreteLocales($document); if (!in_array($document->getShadowLocale(), $locales)) { $this->inspector->getNode($document)->revert(); throw new \RuntimeException(sprintf('Attempting to create shadow for "%s" on a non-concrete locale "%s" for document at "%s". Concrete languages are "%s"', $document->getLocale(), $document->getShadowLocale(), $this->inspector->getPath($document), implode('", "', $locales))); } }
/** * Return a structure bridge corresponding to the given document. * * @param StructureBehavior $document * * @return StructureBridge * * @deprecated */ private function documentToStructure(StructureBehavior $document) { if (null === $document) { return; } $structure = $this->documentInspector->getStructureMetadata($document); $documentAlias = $this->documentInspector->getMetadata($document)->getAlias(); $structureBridge = $this->structureManager->wrapStructure($documentAlias, $structure); $structureBridge->setDocument($document); return $structureBridge; }
/** * It should persist data from extensions. */ public function testPersistExtensionsData() { $document = new TestExtensionDocument(['ext_1' => ['foo' => 'bar']]); $this->persistEvent->getDocument()->willReturn($document); $this->inspector->getWebspace($document)->willReturn('sulu_io'); $this->inspector->getLocale($document)->shouldBeCalled()->willReturn('de'); $this->namespaceRegistry->getPrefix('extension_localized')->willReturn('ext_prefix'); $this->extensionManager->getExtensions('foobar')->willReturn(['ext_1' => $this->extension->reveal()]); $this->extension->getName()->willReturn('ext_1'); $this->extension->setLanguageCode('de', 'ext_prefix', '')->shouldBeCalled(); $this->extension->save($this->node->reveal(), ['foo' => 'bar'], 'sulu_io', 'de')->shouldBeCalled(); $this->subscriber->handlePersist($this->persistEvent->reveal()); }
/** * Upgrades the node to new URL representation. * * @param NodeInterface $node The node to be upgraded * @param string $locale The locale of the node to be upgraded * @param array $properties The properties which are or contain URL fields * @param bool $addScheme Adds the scheme to URLs if true, removes the scheme otherwise */ private function upgradeNode(NodeInterface $node, $locale, $properties, $addScheme) { /** @var BasePageDocument $document */ $document = $this->documentManager->find($node->getIdentifier(), $locale); $documentLocales = $this->documentInspector->getLocales($document); if (!in_array($locale, $documentLocales)) { return; } foreach ($properties as $property) { $this->upgradeProperty($document->getStructure()->getProperty($property), $addScheme); } $this->documentManager->persist($document, $locale); }
/** * Returns all route-document which referees given document. * * @param $document * @param $webspaceKey * * @return array */ protected function findReferrer($document, $webspaceKey) { $routes = []; $referrers = $this->inspector->getReferrers($document); foreach ($referrers as $routeDocument) { if ($routeDocument instanceof RouteDocument) { $path = PathHelper::relativizePath($routeDocument->getPath(), $this->getRoutesPath($webspaceKey)); $routes[$path] = $routeDocument; $tmp = $this->findReferrer($routeDocument, $webspaceKey); $routes = array_merge($routes, $tmp); } } return $routes; }
/** * {@inheritdoc} */ public function save(ResourceSegmentBehavior $document) { $path = $document->getResourceSegment(); $webspaceKey = $this->documentInspector->getWebspace($document); $locale = $this->documentInspector->getLocale($document); $segmentKey = null; $webspaceRouteRootPath = $this->getWebspaceRouteNodeBasePath($webspaceKey, $locale, $segmentKey); try { $routeNodePath = $this->loadByContent($this->documentInspector->getNode($document), $webspaceKey, $locale, $segmentKey); $routeDocument = $this->documentManager->find($webspaceRouteRootPath . $routeNodePath, $locale, ['rehydrate' => false]); $routeDocumentPath = $webspaceRouteRootPath . $routeNodePath; } catch (ResourceLocatorNotFoundException $e) { $routeDocument = $this->documentManager->create('route'); $routeDocumentPath = $webspaceRouteRootPath . $path; } $routeDocument->setTargetDocument($document); try { $this->documentManager->persist($routeDocument, $locale, ['path' => $routeDocumentPath, 'auto_create' => true, 'override' => true]); $this->documentManager->publish($routeDocument, $locale); } catch (ItemExistsException $e) { throw new ResourceLocatorAlreadyExistsException($document->getResourceSegment(), $routeDocumentPath); } }
private function hydrate(AbstractMappingEvent $event) { $document = $event->getDocument(); $node = $event->getNode(); $locale = $this->inspector->getLocale($document); $webspaceName = $this->inspector->getWebspace($document); $structureType = $document->getStructureType(); if (null === $structureType) { return; } $prefix = $this->namespaceRegistry->getPrefix('extension_localized'); $extensionContainer = new ManagedExtensionContainer($structureType, $this->extensionManager, $node, $locale, $prefix, $this->internalPrefix, $webspaceName); $document->setExtensionsData($extensionContainer); }
/** * It should return any localizations if neither parent nor children. */ public function testWebspaceAnyLocalization() { $this->inspector->getWebspace($this->document->reveal())->willReturn(self::FIX_WEBSPACE); $this->inspector->getLocales($this->document->reveal())->willReturn(['de']); $this->webspace->getLocalization(self::FIX_LOCALE)->willReturn($this->localization1->reveal()); $this->localization1->getLocalization()->willReturn('en'); $this->localization2->getLocalization()->willReturn('de'); $this->hydrateEvent->getOption('load_ghost_content', true)->willReturn(true); $this->localization1->getParent()->willReturn(null); $this->localization1->getChildren()->willReturn([]); $this->webspace->getLocalizations()->willReturn([$this->localization2->reveal()]); $this->registry->updateLocale($this->document->reveal(), 'de', 'en')->shouldBeCalled(); $this->hydrateEvent->setLocale('de')->shouldBeCalled(); $this->subscriber->handleHydrate($this->hydrateEvent->reveal()); }
/** * Changes the old route to a history route and redirect to the new route. * * @param RouteBehavior $oldDocument * @param RouteBehavior $newDocument */ private function changeOldPathToHistoryRoutes(RouteBehavior $oldDocument, RouteBehavior $newDocument) { $oldDocument->setTargetDocument($newDocument); $oldDocument->setHistory(true); $oldRouteNode = $this->documentInspector->getNode($oldDocument); $oldRouteNode->setProperty(self::NODE_HISTORY_FIELD, true); foreach ($this->documentInspector->getReferrers($oldDocument) as $referrer) { if ($referrer instanceof RouteBehavior) { $referrer->setTargetDocument($newDocument); $referrer->setHistory(true); $this->documentManager->persist($referrer, null, ['path' => $this->documentInspector->getPath($referrer)]); $this->documentManager->publish($referrer, null); } } }
public function testOnIndexRebuild() { $event = $this->prophesize(IndexRebuildEvent::class); $query = $this->prophesize(Query::class); $document = $this->prophesize(StructureBehavior::class); $document->willImplement(UuidBehavior::class); $securableDocument = $this->prophesize(StructureBehavior::class); $securableDocument->willImplement(SecurityBehavior::class); $securableDocument->willImplement(UuidBehavior::class); $securableDocument->getPermissions()->willReturn([]); $securedDocument = $this->prophesize(StructureBehavior::class); $securedDocument->willImplement(SecurityBehavior::class); $securedDocument->willImplement(UuidBehavior::class); $securedDocument->getPermissions()->willReturn(['some' => 'permissions']); $typemap = [['phpcr_type' => 'page'], ['phpcr_type' => 'home'], ['phpcr_type' => 'snippet']]; $output = $this->prophesize(OutputInterface::class); $event->getOutput()->willReturn($output->reveal()); $event->getFilter()->shouldBeCalled(); $this->baseMetadataFactory->getPhpcrTypeMap()->shouldBeCalled()->willReturn($typemap); $this->documentManager->createQuery('SELECT * FROM [nt:unstructured] AS a WHERE [jcr:mixinTypes] = "page" or [jcr:mixinTypes] = "home" or [jcr:mixinTypes] = "snippet"')->shouldBeCalled()->willReturn($query->reveal()); $query->execute()->shouldBeCalled()->willReturn([$document->reveal(), $securableDocument->reveal(), $securedDocument->reveal()]); $this->inspector->getLocales($document->reveal())->shouldBeCalled()->willReturn(['de', 'en']); $document->getUuid()->shouldBeCalled()->willReturn('1'); $this->documentManager->find('1', 'en')->shouldBeCalled(); $this->searchManager->index($document->reveal(), 'en')->shouldBeCalled(); $this->documentManager->find('1', 'de')->shouldBeCalled(); $this->searchManager->index($document->reveal(), 'de')->shouldBeCalled(); $this->inspector->getLocales($securableDocument->reveal())->shouldBeCalled()->willReturn(['de']); $securableDocument->getUuid()->willReturn('2'); $this->documentManager->find('2', 'de')->shouldBeCalled(); $this->searchManager->index($securableDocument->reveal(), 'de')->shouldBeCalled(); $securedDocument->getUuid()->willReturn('3'); $this->documentManager->find('3', 'de')->shouldNotBeCalled(); $this->searchManager->index($securedDocument->reveal(), 'de')->shouldNotBeCalled(); $this->reindexListener->onIndexRebuild($event->reveal()); }
private function doGetProperty($name, $contentTypeName, $locale) { $this->propertyMetadata->getType()->willReturn($contentTypeName); $this->structureMetadata->getProperty($name)->willReturn($this->propertyMetadata); $this->contentTypeManager->get($contentTypeName)->willReturn($this->contentType->reveal()); if ($locale) { $this->propertyFactory->createTranslatedProperty($this->propertyMetadata->reveal(), $locale, Argument::type(StructureBridge::class))->willReturn($this->legacyProperty->reveal()); } else { $this->propertyFactory->createProperty($this->propertyMetadata->reveal(), $locale)->willReturn($this->legacyProperty->reveal()); } $this->inspector->getWebspace($this->document->reveal())->willReturn('sulu_io'); $this->inspector->getOriginalLocale($this->document->reveal())->willReturn($locale); $this->contentType->read($this->node->reveal(), $this->legacyProperty->reveal(), 'sulu_io', $locale, null)->shouldBeCalledTimes(1); $property = $this->structure->getProperty($name); $this->assertInstanceOf(PropertyValue::class, $property); $this->assertEquals($name, $property->getName()); }
/** * Upgrades the node to new date representation. * * @param NodeInterface $node The node to be upgraded * @param string $locale The locale of the node to be upgraded * @param array $properties The properties which are or contain date fields * @param bool $up */ private function upgradeNode(NodeInterface $node, $locale, array $properties, $up) { /** @var BasePageDocument $document */ $document = $this->documentManager->find($node->getIdentifier(), $locale); $documentLocales = $this->documentInspector->getLocales($document); if (!in_array($locale, $documentLocales)) { return; } foreach ($properties as $property) { if ($property['property'] instanceof BlockMetadata) { $this->upgradeBlockProperty($property['property'], $property['components'], $node, $locale, $up); } else { $this->upgradeProperty($property['property'], $node, $locale, $up); } } $this->documentManager->persist($document, $locale, ['auto_name' => false]); }
/** * It should throw an exception if the property is required but the value is null. * * @expectedException Sulu\Component\Content\Exception\MandatoryPropertyException */ public function testThrowExceptionPropertyRequired() { $document = new TestContentDocument($this->structure->reveal()); $document->setStructureType('foobar'); $this->persistEvent->getDocument()->willReturn($document); // map the structure type $this->persistEvent->getLocale()->willReturn('fr'); // map the content $this->inspector->getStructureMetadata($document)->willReturn($this->structureMetadata->reveal()); $this->inspector->getWebspace($document)->willReturn('webspace'); $this->structureMetadata->getProperties()->willReturn(['prop1' => $this->structureProperty->reveal()]); $this->structureProperty->isRequired()->willReturn(true); $this->structure->getProperty('prop1')->willReturn($this->propertyValue->reveal()); $this->propertyValue->getValue()->willReturn(null); $this->structureMetadata->getName()->willReturn('test'); $this->structureMetadata->getResource()->willReturn('/path/to/resource.xml'); $this->subscriber->handlePersist($this->persistEvent->reveal()); }
/** * Upgrades the node to new URL representation. * * @param NodeInterface $node The node to be upgraded * @param string $locale The locale of the node to be upgraded * @param array $properties The properties which are or contain URL fields * @param bool $addScheme Adds the scheme to URLs if true, removes the scheme otherwise */ private function upgradeNode(NodeInterface $node, $locale, array $properties, $addScheme) { /** @var BasePageDocument $document */ $document = $this->documentManager->find($node->getIdentifier(), $locale); $documentLocales = $this->documentInspector->getLocales($document); if (!in_array($locale, $documentLocales)) { return; } foreach ($properties as $property) { $propertyValue = $document->getStructure()->getProperty($property['property']->getName()); if ($property['property'] instanceof BlockMetadata) { $this->upgradeBlockProperty($property['property'], $property['components'], $propertyValue, $addScheme); } else { $this->upgradeProperty($propertyValue, $addScheme); } } $this->documentManager->persist($document, $locale); }
/** * Return available localizations. * * @param StructureBehavior $document * @param string $locale * * @return string */ public function getAvailableLocalization(StructureBehavior $document, $locale) { $availableLocales = $this->inspector->getLocales($document); if (in_array($locale, $availableLocales)) { return $locale; } $fallbackLocale = null; if ($document instanceof WebspaceBehavior) { $fallbackLocale = $this->localizationFinder->findAvailableLocale($this->inspector->getWebspace($document), $availableLocales, $locale); } if (!$fallbackLocale) { $fallbackLocale = reset($availableLocales); } if (!$fallbackLocale) { $fallbackLocale = $this->documentRegistry->getDefaultLocale(); } return $fallbackLocale; }
/** * Adds the breadcrumb to the serialization. * * @param StructureBehavior $document * @param VisitorInterface $visitor */ private function addBreadcrumb(StructureBehavior $document, VisitorInterface $visitor) { $items = []; $parentDocument = $this->inspector->getParent($document); while ($parentDocument instanceof StructureBehavior) { $item = []; if ($parentDocument instanceof UuidBehavior) { $item['uuid'] = $parentDocument->getUuid(); } $item['title'] = $parentDocument->getStructure()->getProperty('title')->getValue(); $items[] = $item; $parentDocument = $this->inspector->getParent($parentDocument); } $items = array_reverse($items); array_walk($items, function (&$item, $index) { $item['depth'] = $index; }); $visitor->addData('breadcrumb', $items); }