<?php
/*
* This file is part of Sulu.
*
* (c) Sulu GmbH
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace Sulu\Component\Content\Types\ResourceLocator\Mapper;
use PHPCR\ItemExistsException;
use PHPCR\NodeInterface;
use PHPCR\PathNotFoundException;
use PHPCR\Util\PathHelper;
use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
use Sulu\Component\Content\Exception\ResourceLocatorAlreadyExistsException;
use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
use Sulu\Component\Content\Types\ResourceLocator\ResourceLocatorInformation;
use Sulu\Component\DocumentManager\DocumentManagerInterface;
use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
/**
* Manages resource-locators in phpcr.
*/
class PhpcrMapper implements ResourceLocatorMapperInterface
{
/**
* @var SessionManagerInterface
*/
private $sessionManager;
/**
* @var DocumentManagerInterface
*/
private $documentManager;
/**
* @var DocumentInspector
*/
private $documentInspector;
public function __construct(
SessionManagerInterface $sessionManager,
DocumentManagerInterface $documentManager,
DocumentInspector $documentInspector
) {
$this->sessionManager = $sessionManager;
$this->documentManager = $documentManager;
$this->documentInspector = $documentInspector;
}
public function save(ResourceSegmentBehavior $document)
{
$path = $document->getResourceSegment();
$webspaceKey = $this->documentInspector->getWebspace($document);
$locale = $this->documentInspector->getOriginalLocale($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, $e);
}
}
public function loadByContent(NodeInterface $contentNode, $webspaceKey, $languageCode, $segmentKey = null)
{
$result = $this->iterateRouteNodes(
$contentNode,
function($resourceLocator, NodeInterface $node) {
if (false === $node->getPropertyValue('sulu:history') && false !== $resourceLocator) {
return $resourceLocator;
}
return false;
},
$webspaceKey,
$languageCode,
$segmentKey
);
if (null !== $result) {
return $result;
}
throw new ResourceLocatorNotFoundException();
}
/**
* Iterates over all route nodes assigned by the given node, and executes the callback on it.
*
* @param callable $callback will be called foreach route node (stops and return value if not false)
* @param string $webspaceKey
* @param string $languageCode
* @param string $segmentKey
*
* @return \PHPCR\NodeInterface
*/
private function iterateRouteNodes(
NodeInterface $node,
$callback,
$webspaceKey,
$languageCode,
$segmentKey = null
) {
if ($node->isNew()) {
return null;
}
$routePath = $this->sessionManager->getRoutePath($webspaceKey, $languageCode);
// search for references with name 'content'
foreach ($node->getReferences('sulu:content') as $ref) {
if ($ref instanceof \PHPCR\PropertyInterface) {
$routeNode = $ref->getParent();
if (0 !== \strpos($routeNode->getPath(), $routePath)) {
continue;
}
$resourceLocator = $this->getResourceLocator(
$ref->getParent()->getPath(),
$webspaceKey,
$languageCode,
$segmentKey
);
$result = $callback($resourceLocator, $routeNode);
if (false !== $result) {
return $result;
}
}
}
return null;
}
public function loadByContentUuid($uuid, $webspaceKey, $languageCode, $segmentKey = null)
{
$session = $this->sessionManager->getSession();
$contentNode = $session->getNodeByIdentifier($uuid);
return $this->loadByContent($contentNode, $webspaceKey, $languageCode, $segmentKey);
}
public function loadHistoryByContentUuid($uuid, $webspaceKey, $languageCode, $segmentKey = null)
{
// get content node
$session = $this->sessionManager->getSession();
$contentNode = $session->getNodeByIdentifier($uuid);
// get current path node
$pathNode = $this->iterateRouteNodes(
$contentNode,
function($resourceLocator, NodeInterface $node) {
if (false === $node->getPropertyValue('sulu:history') && false !== $resourceLocator) {
return $node;
} else {
return false;
}
},
$webspaceKey,
$languageCode,
$segmentKey
);
// iterate over history of path node
$result = [];
if (!$pathNode) {
return $result;
}
$this->iterateRouteNodes(
$pathNode,
function($resourceLocator, NodeInterface $node) use (&$result) {
if (false !== $resourceLocator) {
// add resourceLocator
$result[] = new ResourceLocatorInformation(
//backward compability
$resourceLocator,
$node->getPropertyValueWithDefault('sulu:created', new \DateTime()),
$node->getIdentifier()
);
}
return false;
},
$webspaceKey,
$languageCode,
$segmentKey
);
// sort history descending
\usort(
$result,
function(ResourceLocatorInformation $item1, ResourceLocatorInformation $item2) {
return $item1->getCreated() < $item2->getCreated();
}
);
return $result;
}
public function loadByResourceLocator($resourceLocator, $webspaceKey, $languageCode, $segmentKey = null)
{
$resourceLocator = \ltrim($resourceLocator, '/');
$path = \sprintf(
'%s/%s',
$this->getWebspaceRouteNodeBasePath($webspaceKey, $languageCode, $segmentKey),
$resourceLocator
);
try {
if ('' !== $resourceLocator) {
if (!PathHelper::assertValidAbsolutePath($path, false, false)) {
throw new ResourceLocatorNotFoundException(\sprintf('Path "%s" not found', $path));
}
// get requested resource locator route node
$route = $this->sessionManager->getSession()->getNode($path);
} else {
// get home page route node
$route = $this->getWebspaceRouteNode($webspaceKey, $languageCode, $segmentKey);
}
} catch (PathNotFoundException $e) {
throw new ResourceLocatorNotFoundException(\sprintf('Path "%s" not found', $path), null, $e);
}
if ($route->hasProperty('sulu:content') && $route->hasProperty('sulu:history')) {
if (!$route->getPropertyValue('sulu:history')) {
/** @var NodeInterface $content */
$content = $route->getPropertyValue('sulu:content');
return $content->getIdentifier();
} else {
// get path from history node
/** @var NodeInterface $realPath */
$realPath = $route->getPropertyValue('sulu:content');
throw new ResourceLocatorMovedException(
$this->getResourceLocator($realPath->getPath(), $webspaceKey, $languageCode, $segmentKey),
$realPath->getIdentifier()
);
}
} else {
throw new ResourceLocatorNotFoundException(\sprintf(
'Route has "%s" does not have either the "sulu:content" or "sulu:history" properties',
$route->getPath()
));
}
}
public function unique($path, $webspaceKey, $languageCode, $segmentKey = null)
{
$routes = $this->getWebspaceRouteNode($webspaceKey, $languageCode, $segmentKey);
return $this->isUnique($routes, $path);
}
public function getUniquePath($path, $webspaceKey, $languageCode, $segmentKey = null/*, $uuid = null*/)
{
$uuid = null;
if (\func_num_args() >= 5) {
$uuid = \func_get_arg(4);
}
$routes = $this->getWebspaceRouteNode($webspaceKey, $languageCode, $segmentKey);
if ($this->isUnique($routes, $path, $uuid)) {
// path is already unique
return $path;
} else {
// append -
$path .= '-';
// init counter
$i = 1;
// while $path-$i is not unique raise counter
while (!$this->isUnique($routes, $path . $i, $uuid)) {
++$i;
}
// result is unique
return $path . $i;
}
}
public function deleteById($id, $languageCode, $segmentKey = null)
{
$routeDocument = $this->documentManager->find($id, $languageCode);
$this->documentManager->remove($routeDocument);
}
public function getParentPath($uuid, $webspaceKey, $languageCode, $segmentKey = null)
{
$session = $this->sessionManager->getSession();
$contentNode = $session->getNodeByIdentifier($uuid);
$parentNode = $contentNode->getParent();
try {
return $this->loadByContent($parentNode, $webspaceKey, $languageCode, $segmentKey);
} catch (ResourceLocatorNotFoundException $ex) {
// parent node donĀ“t have a resource locator
return;
}
}
/**
* Check if path is unique from given $root node.
*
* @param NodeInterface $root route node
* @param string $path requested path
*
* @return bool path is unique
*/
private function isUnique(NodeInterface $root, $path, $uuid = null)
{
$path = \ltrim($path, '/');
if (!$root->hasNode($path)) {
return true;
}
if (!$uuid) {
return false;
}
$route = $root->getNode($path);
return $route->hasProperty('sulu:content')
&& $route->getPropertyValue('sulu:content')->getIdentifier() === $uuid;
}
/**
* Returns base node of routes from phpcr.
*
* @param string $webspaceKey current session
* @param string $languageCode
* @param string $segmentKey
*
* @return \PHPCR\NodeInterface base node of routes
*/
private function getWebspaceRouteNode($webspaceKey, $languageCode, $segmentKey)
{
return $this->sessionManager->getRouteNode($webspaceKey, $languageCode, $segmentKey);
}
/**
* Returns base path of routes from phpcr.
*
* @param string $webspaceKey current session
* @param string $languageCode
* @param string $segmentKey
*
* @return string
*/
private function getWebspaceRouteNodeBasePath($webspaceKey, $languageCode, $segmentKey)
{
return $this->sessionManager->getRoutePath($webspaceKey, $languageCode, $segmentKey);
}
/**
* Returns the abspath.
*
* @param string $relPath
* @param string $webspaceKey
* @param string $languageCode
* @param string $segmentKey
*
* @return string
*/
private function getPath($relPath, $webspaceKey, $languageCode, $segmentKey)
{
$basePath = $this->getWebspaceRouteNodeBasePath($webspaceKey, $languageCode, $segmentKey);
return '/' . \ltrim($basePath, '/') . ('' !== $relPath ? '/' . \ltrim($relPath, '/') : '');
}
/**
* Returns resource-locator.
*
* @param string $path
* @param string $webspaceKey
* @param string $languageCode
* @param string $segmentKey
*
* @return string
*/
private function getResourceLocator($path, $webspaceKey, $languageCode, $segmentKey)
{
$basePath = $this->getWebspaceRouteNodeBasePath($webspaceKey, $languageCode, $segmentKey);
if ($path === $basePath) {
return '/';
}
if (false !== \strpos($path, $basePath . '/')) {
$result = \str_replace($basePath . '/', '/', $path);
if (0 === \strpos($result, '/')) {
return $result;
}
}
return false;
}
}