<?php
namespace App\Service\Content;
use App\Entity\Content\AbstractObject;
use App\Entity\Content\Common\Props\LinkInterface;
use App\Entity\Content\Common\Props\SlugInterface;
use App\Entity\Content\Events\Event\EventObject;
use App\Entity\Content\Exhibits\Video\VideoObject;
use App\Entity\Content\Posts\Post\PostObject;
use App\Entity\Feed\AbstractEntry;
use App\Entity\Feed\Entry\AbstractContentEntry;
use App\Entity\Feed\Entry\ContentVideoEntry;
use App\Model\Content\Media\AbstractMedia;
use App\Service\MediaDecorator;
use Cms\ContainerBundle\Entity\Container;
use Cms\ContainerBundle\Entity\Containers\GenericContainer;
use Cms\ContainerBundle\Entity\Containers\IntranetContainer;
use Cms\ContainerBundle\Entity\Containers\PersonalContainer;
use Cms\FrontendBundle\Service\ResolverManager;
use Cms\ModuleBundle\Entity\Proxy;
use Cms\ModuleBundle\Model\ModuleConfig;
use Cms\Modules\PageBundle\Entity\Page\PageProxy;
use Spatie\Url\Url;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
use Laminas\Uri\Uri;
/**
*
*/
final class WebUrlGenerator
{
// DI
protected RouterInterface $router;
protected ResolverManager $resolver;
protected MediaDecorator $decorator;
/**
* @param RouterInterface $router
* @param ResolverManager $resolver
* @param MediaDecorator $decorator
*/
public function __construct(
RouterInterface $router,
ResolverManager $resolver,
MediaDecorator $decorator
)
{
$this->router = $router;
$this->resolver = $resolver;
$this->decorator = $decorator;
}
/**
* @param Container $department
* @param string|null $path
* @return Url|null
*/
public function catchall(Container $department, ?string $path = null): ?Url
{
// need to chunk up the path as well
$path = $path ? Url::fromString(urldecode($path)) : null;
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$department
);
// generate the route name we are looking for
$route = sprintf(
'app.web.%s.default.catchall.%s',
$root::ROUTING_SLUG,
(($root->getId() === $department->getId()) && ! $path) ? 'root' : 'sub'
);
// setup params
$params = array_merge(
[
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
),
],
(($root->getId() !== $department->getId()) || $path) ? [
'path' => trim((rtrim($this->resolver->getUrlResolver()->resolvePathForDepartment(
$department
), '/') . '/' . ltrim($path->getPath(), '/')), '/'),
] : [],
($root instanceof PersonalContainer) ? [
'home' => $root->getAccount()->getEmail(),
] : [],
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$department
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
))
;
if ($path) {
foreach ($path->getAllQueryParameters() as $key => $value) {
$url = $url->withQueryParameter($key, $value);
}
}
return $url;
}
/**
* @param Container $department
* @param mixed|string|ModuleConfig $module
* @return Url|null
*/
public function subscribe(Container $department, $module): ?Url
{
// get the module key if using a module
// TODO: this is a hack for old cms practices...
if ($module instanceof ModuleConfig) {
$module = $module->key();
}
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$department
);
// generate the route name we are looking for
$route = sprintf(
'app.web.%s.%s.subscribe.%s',
$root::ROUTING_SLUG,
$module,
($root->getId() === $department->getId()) ? 'root' : 'sub'
);
// setup params
$params = array_merge(
($root instanceof GenericContainer) ? [
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
),
] : [],
($root->getId() !== $department->getId()) ? [
'path' => $this->resolver->getUrlResolver()->resolvePathForDepartment(
$department
),
] : []
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$department
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
))
;
return $url;
}
/**
* @param Container $department
* @param mixed|string|ModuleConfig $module
* @return Url|null
*/
public function landing(Container $department, $module): ?Url
{
// get the module key if using a module
// TODO: this is a hack for old cms practices...
if ($module instanceof ModuleConfig) {
$module = $module->key();
}
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$department
);
// generate the route name we are looking for
$route = sprintf(
'app.web.%s.%s.landing.%s',
$root::ROUTING_SLUG,
$module,
($root->getId() === $department->getId()) ? 'root' : 'sub'
);
// setup params
$params = array_merge(
($root instanceof GenericContainer) ? [
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
),
] : [],
($root->getId() !== $department->getId()) ? [
'path' => $this->resolver->getUrlResolver()->resolvePathForDepartment(
$department
),
] : []
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$department
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$department
))
;
return $url;
}
/**
* @param mixed $thing
* @param bool $final
* @return Url|null
*/
public function view($thing, bool $final = false): ?Url
{
$related = ($thing instanceof AbstractObject) ? $thing : null;
if ($thing instanceof AbstractContentEntry) {
$related = $thing->getObject();
}
if ($final) {
switch (true) {
case $related instanceof LinkInterface && $related->getLink():
return ($related->isLinkInternal())
? $this->catchall($related->getDepartment(), $related->getLink())
: $related->getLinkAsUrl();
case $related instanceof VideoObject && $related->getFeature():
case $related instanceof ContentVideoEntry && $related->getFeature():
$this->decorator->urls($related->getFeature());
return Url::fromString(
$related->getFeature()->getDecorations()->get(AbstractMedia::DECORS__URLS)['direct'],
);
}
}
switch (true) {
case $thing instanceof EventObject:
case $thing instanceof AbstractObject && $thing->isUnpublished():
return null;
case $thing instanceof AbstractObject:
return $this->generateAbstractObject($thing);
case $thing instanceof AbstractEntry:
return $this->generateAbstractEntry($thing);
case $thing instanceof Proxy:
return $this->generateProxy($thing);
}
throw new \LogicException();
}
/**
* @param Request $request
* @param array $query
* @return string
*/
public function request(Request $request, array $query = []): string
{
$params = array_filter(array_merge(
$request->query->all(),
$query,
));
return (new Uri($request->getPathInfo()))->setQuery($params)->toString();
}
/**
* @param AbstractObject $object
* @return Url
*/
protected function generateAbstractObject(AbstractObject $object): Url
{
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$object->getDepartment()
);
// generate the route name we are looking for
$route = sprintf(
'app.web.%s.%s.view.%s',
$root::ROUTING_SLUG,
$object::ROUTING_SLUG,
// TODO: need to create event for worker so that resolvers can clear cache on each iteration...
($root->getId() === $object->getDepartment()->getId()) ? 'root' : 'sub'
);
// setup params
$params = array_merge(
[
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$object->getDepartment()
),
],
(($root->getId() !== $object->getDepartment()->getId())) ? [
'path' => $path = $this->resolver->getUrlResolver()->resolvePathForDepartment(
$object->getDepartment()
),
] : [],
($root instanceof IntranetContainer) ? [
'home' => $root->getSlug(),
] : [],
($root instanceof PersonalContainer) ? [
'user' => $root->getAccount()->getEmail(),
'home' => $root->getSlug(),
] : [],
[
'id' => $object->getId(),
'slug' => ($object instanceof SlugInterface) ? $object->getSlug() : null,
]
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$object->getDepartment()
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$object->getDepartment()
))
;
return $url;
}
/**
* @param AbstractEntry $entry
* @return Url
*/
protected function generateAbstractEntry(AbstractEntry $entry): Url
{
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$entry->getDepartment()
);
// generate the route name we are looking for
$route = sprintf(
'app.web.%s.%s.view.%s',
$root::ROUTING_SLUG,
$entry::ROUTING_SLUG,
($root->getId() === $entry->getDepartment()->getId()) ? 'root' : 'sub'
);
// setup params
$params = array_merge(
[
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$entry->getDepartment()
),
],
($root->getId() !== $entry->getDepartment()->getId()) ? [
'path' => $path = $this->resolver->getUrlResolver()->resolvePathForDepartment(
$entry->getDepartment()
),
] : [],
($root instanceof IntranetContainer) ? [
'home' => $root->getSlug(),
] : [],
($root instanceof PersonalContainer) ? [
'user' => $root->getAccount()->getEmail(),
'home' => $root->getSlug(),
] : [],
[
'id' => $entry->getId(),
'slug' => $entry->getSlug(),
]
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$entry->getDepartment()
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$entry->getDepartment()
))
;
return $url;
}
/**
* @param Proxy $object
* @return Url
*/
protected function generateProxy(Proxy $object): Url
{
// get the root department for the object
$root = $this->resolver->getDepartmentResolver()->resolveRootByDepartment(
$object->getDepartment()
);
// generate the route name we are looking for
$route = ($object instanceof PageProxy)
? sprintf(
'app.web.%s.default.catchall.sub',// always generating a sub because there will always be a path
$root::ROUTING_SLUG
)
: sprintf(
'app.web.%s.%s.view.%s',
$root::ROUTING_SLUG,
$object::ROUTING_SLUG,
($root->getId() === $object->getDepartment()->getId()) ? 'root' : 'sub'
)
;
// setup params
$path = null;
$params = array_merge(
[
'host' => $this->resolver->getUrlResolver()->resolveHostForDepartment(
$object->getDepartment()
),
],
($root->getId() !== $object->getDepartment()->getId()) ? [
'path' => $path = $this->resolver->getUrlResolver()->resolvePathForDepartment(
$object->getDepartment()
),
] : [],
($root instanceof IntranetContainer) ? [
'home' => $root->getSlug(),
] : [],
($root instanceof PersonalContainer) ? [
'user' => $root->getAccount()->getEmail(),
'home' => $root->getSlug(),
] : [],
($object instanceof PageProxy)
? [
'path' => implode('/', array_values(array_filter([
trim($path, '/'),
$object->getData()->getSlug(),
]))),
]
: [
'id' => $object->getId(),
'slug' => ($object instanceof SlugInterface) ? $object->getSlug() : null,
],
);
// get the raw string url
$url = $this->router->generate(
$route,
$params,
UrlGeneratorInterface::ABSOLUTE_URL
);
// convert to a more usable object
// set things like host and scheme
$url = Url::fromString($url)
->withScheme($this->resolver->getUrlResolver()->resolveSchemeForDepartment(
$object->getDepartment()
))
->withHost($this->resolver->getUrlResolver()->resolveHostForDepartment(
$object->getDepartment()
))
;
return $url;
}
}