<?php
namespace App\Controller\Web;
use App\Component\Renderer\RenderContext;
use App\Component\ViewLayer\Views\WebDocView;
use App\Controller\AbstractController;
use App\Entity\Content\Common\Props\SlugInterface;
use App\Util\Ulids;
use Cms\ContainerBundle\Entity\Container;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Service\SceneRenderer;
use Cms\DomainBundle\Entity\Domain;
use Cms\FrontendBundle\Exception\FrontendBuilderException;
use Cms\FrontendBundle\Service\FrontendBuilder;
use Cms\FrontendBundle\Service\ResolverManager;
use Cms\ThemeBundle\Service\PackageManager;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
*
*/
abstract class AbstractWebController extends AbstractController
{
/**
* @param string|Request|null $input
* @return Domain
*/
protected function determineDomain($input = null): Domain
{
switch (true) {
case is_string($input):
$host = $input;
break;
case empty($input):
$host = $this->getRequest()->getHost();
break;
case $input instanceof Request:
$host = $input->getHost();
break;
default:
throw new \LogicException();
}
$domain = $this->getResolverManager()->getDomainResolver()->resolveDomainByHostname(
$host
);
if ( ! $domain) {
throw new NotFoundHttpException();
}
return $domain;
}
/**
* @param string|null $home
* @return Container
*/
abstract protected function determineSite(?string $home = null): Container;
/**
* @param Container $root
* @param string|Container|null $path
* @return Container
*/
protected function determineDepartment(Container $root, $path): Container
{
switch (true) {
case $path instanceof Container:
$department = $this->getResolverManager()->getDepartmentResolver()->resolveRootByDepartment($path);
break;
default:
$department = $this->getResolverManager()->getDepartmentResolver()->resolveDepartmentByPath(
$root,
$path
);
}
if ( ! $department instanceof Container) {
throw new NotFoundHttpException();
}
return $department;
}
/**
* @param Container $department
* @return array
*/
protected function determineAncestry(Container $department): array
{
return $this->getResolverManager()->getDepartmentResolver()->resolveFurthestAncestorsWithDepartment(
$department
);
}
/**
* @param Request|null $request
* @return RenderContext
*/
public function contextualize(?Request $request = null): RenderContext
{
if ( ! $request) {
$request = $this->getRequest();
}
$params = $request->attributes->get('_route_params');
if ( ! $params['host']) {
throw new \RuntimeException();
}
return $this->generateRenderContext(
$domain = $this->determineDomain($params['host']),
$this->determineDepartment(
$this->determineSite($domain),
$params['path'] ?? null,
),
);
}
/**
* @param Domain $domain
* @param Container $department
* @return RenderContext
*/
public function generateRenderContext(
Domain $domain,
Container $department
): RenderContext
{
return new RenderContext(
$this->getRequest(),
$this->getEnvironment(),
$this->getParameter('dashboard.hostname'),
$domain->getTenant(),
$domain,
$this->determineAncestry($department),
$this->getResolverManager()->getSchoolResolver()->resolveSchoolByDepartment(
$department
),
$theme = $this->getResolverManager()->getThemeResolver()->resolveThemeByDepartment(
$department
),
$this->getPackageManager()->getPackageForTheme(
$theme
),
$this->getResolverManager()->getOuterLayoutResolver()->resolveOuterLayoutByDepartment(
$department
),
$this->getResolverManager()->getInnerLayoutResolver()->resolveInnerLayoutByDepartment(
$department
)
);
}
/**
* @param Request|null $request
* @return Response
* @throws FrontendBuilderException
*/
protected function legacy(?Request $request = null): Response
{
// default request if not given
if ( ! $request) {
$request = $this->getRequest();
}
// close the session as it isn't needed on the frontend
$this->closeSession();
try {
$response = $this->getFrontendBuilder()->build($request);
} catch (FrontendBuilderException $e) {
$response = new Response(
$this->getSceneRenderer()->render(new DocumentScene(
'@CmsCore/setup/default.html.twig',
array(
'key' => array_flip(
(new \ReflectionClass(FrontendBuilderException::class))->getConstants()
)[$e->getCode()],
'message' => $e->getMessage(),
)
)),
500,
[]
);
} catch (BadRequestHttpException $e) {
$response = new Response(null, 400, []);
} catch (\Exception $e) {
throw $e;
}
return $response;
}
/**
* @param string $host
* @param string|null $home
* @param string|null $path
* @return WebDocView
*/
protected function actionLanding(
string $host,
?string $home = null,
?string $path = null
): WebDocView
{
// generate context from the stuff we assembled
$context = $this->generateRenderContext(
$this->determineDomain($host),
$this->determineDepartment(
$this->determineSite($home),
$path
)
);
// set title for page
$context->getDom()->getTitle()
->setTitle('Feed')
->setSection($context->getDepartment()->getName())
->setProduct(null)
->setBrand($context->getTenant()->getName());
// get feed entries
$results = [];
return (new WebDocView([
'vars' => $context,
'results' => $results,
]))->setRenderContext($context);
}
/**
* @return ResolverManager|object
*/
protected function getResolverManager(): ResolverManager
{
return $this->get(__METHOD__);
}
/**
* @return PackageManager|object
*/
protected function getPackageManager(): PackageManager
{
return $this->get(__METHOD__);
}
/**
* @return FrontendBuilder|object
*/
protected function getFrontendBuilder(): FrontendBuilder
{
return $this->get(__METHOD__);
}
/**
* @return SceneRenderer|object
*/
protected function getSceneRenderer(): SceneRenderer
{
return $this->get(__METHOD__);
}
/**
* Helper to deal with accepting URLs to old migrated content that aren't using the new ID patterns.
*
* @param string $class
* @param string $id
* @param string $route
* @param array $params
* @return RedirectResponse|null
*/
protected function handleMigratedContent(
string $class,
string $id,
string $route,
array $params = []
): ?RedirectResponse
{
// if the id is an integer pattern, handle seeing if it is a migrated piece of content
if (is_numeric($id)) {
// attempt to load the content by this old id
$object = $this->getEntityManager()->getRepository($class)->findOneBy([
'migrationId' => (int) $id,
]);
// if something was found, we want to redirect it to the proper url
if ($object) {
// the redirection should fix the id and ensure the proper slug
return $this->redirectToRoute(
$route,
array_merge(
$params,
[
'id' => $object->getId(),
'slug' => ($object instanceof SlugInterface) ? $object->getSlug() : null,
],
),
);
}
}
return null;
}
/**
* Helper to deal with accepting URLs to content that aren't using the correct slug.
*
* @param string $class
* @param string $id
* @param string $slug
* @param string $route
* @param array $params
* @return RedirectResponse|null
*/
protected function handleIncorrectSlug(
string $class,
string $id,
string $slug,
string $route,
array $params = []
): ?RedirectResponse
{
// if the id is not ulid pattern, we need to skip all of this
if ( ! Ulids::test($id)) {
return null;
}
// attempt to load the content by the id
$object = $this->getEntityManager()->getRepository($class)->find($id);
// if something was found and the slug is not correct, we need to redirect
if ($object instanceof SlugInterface && $object->getSlug() !== $slug) {
// the redirection should fix the id and ensure the proper slug
return $this->redirectToRoute(
$route,
array_merge(
$params,
[
'slug' => $object->getSlug(),
],
),
);
}
return null;
}
}