<?php
namespace Cms\ModuleBundle\Controller;
use Cms\ContainerBundle\Entity\Container;
use Cms\ContainerBundle\Entity\Containers\PersonalContainer;
use Cms\ContainerBundle\Service\ContainerService;
use Cms\CoreBundle\Doctrine\Hydrators\SingleColumnHydrator;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\AjaxScene;
use Cms\CoreBundle\Model\Scenes\DashboardScenes\DocumentScene;
use Cms\CoreBundle\Model\Search\AbstractSearcher;
use Cms\CoreBundle\Model\Search\Search;
use Cms\CoreBundle\Model\Search\SearchableInterface;
use Cms\CoreBundle\Service\ContextManager;
use Cms\CoreBundle\Service\Slugger;
use Cms\CoreBundle\Service\Transcoding\Transcoder;
use Cms\CoreBundle\Util\Controller;
use Cms\CoreBundle\Util\Doctrine\EntityManager;
use Cms\LogBundle\Service\FeedingService;
use Cms\ModuleBundle\Doctrine\ModuleSettingsRepository;
use Cms\ModuleBundle\Doctrine\ProxyRepository;
use Cms\ModuleBundle\Entity\Draft;
use Cms\ModuleBundle\Entity\History;
use Cms\ModuleBundle\Entity\ModuleEntity;
use Cms\ModuleBundle\Entity\ModuleSettings;
use Cms\ModuleBundle\Entity\Proxy;
use Cms\ModuleBundle\Entity\Revision;
use Cms\ModuleBundle\Form\Type\Actions\DuplicateType;
use Cms\ModuleBundle\Form\Type\Actions\MoveType;
use Cms\ModuleBundle\Form\Type\ShareType;
use Cms\ModuleBundle\Model\Action;
use Cms\ModuleBundle\Model\Data;
use Cms\ModuleBundle\Model\Interfaces\Shareable\ShareableInterface;
use Cms\ModuleBundle\Model\ModuleConfig;
use Cms\ModuleBundle\Service\ContentManager;
use Cms\ModuleBundle\Service\DraftManager;
use Cms\ModuleBundle\Service\ModuleManager;
use Cms\ModuleBundle\Service\PublicationService;
use Cms\ModuleBundle\Service\Search\ModuleDraftSearcher;
use Cms\ModuleBundle\Service\Search\ModuleHistorySearcher;
use Cms\ModuleBundle\Service\Search\ModuleRevisionSearcher;
use Cms\Modules\AlertBundle\Entity\Alert\AlertDraft;
use Cms\Modules\BlogBundle\Entity\Post\PostDraft;
use Cms\Modules\CalendarBundle\Entity\Event\EventDraft;
use Cms\Modules\GalleryBundle\Entity\Gallery\GalleryDraft;
use Cms\Modules\NewsBundle\Entity\Article\ArticleDraft;
use Cms\Modules\PageBundle\Entity\Page\PageDraft;
use Cms\Modules\PageBundle\Entity\Page\PageProxy;
use Cms\Modules\PeopleBundle\Entity\Profile\ProfileDraft;
use Cms\Modules\PeopleBundle\Entity\Profile\ProfileProxy;
use Cms\Modules\PeopleBundle\Service\PeopleModuleConfig;
use Cms\Modules\QuestionBundle\Entity\Entry\EntryDraft;
use Cms\Modules\SnippetBundle\Entity\Snippet\SnippetDraft;
use Cms\SyncBundle\Model\Interfaces\Syncable\SyncableInterface;
use Cms\Widgets\Html\Html;
use Cms\Widgets\Row\Column;
use Cms\Widgets\Row\Row;
use Cms\WorkflowsBundle\Entity\Content\AlertContent;
use Cms\WorkflowsBundle\Entity\Content\BlogContent;
use Cms\WorkflowsBundle\Entity\Content\CalendarContent;
use Cms\WorkflowsBundle\Entity\Content\GalleryContent;
use Cms\WorkflowsBundle\Entity\Content\NewsContent;
use Cms\WorkflowsBundle\Entity\Content\PageContent;
use Cms\WorkflowsBundle\Entity\Content\PeopleContent;
use Cms\WorkflowsBundle\Entity\Content\QuestionsContent;
use Cms\WorkflowsBundle\Entity\Content\SnippetContent;
use Cms\WorkflowsBundle\Entity\Publication\ScheduledPublication;
use Cms\WorkflowsBundle\Entity\WorkflowContent;
use Cms\WorkflowsBundle\Entity\WorkflowSubmission;
use Cms\WorkflowsBundle\Service\Publication\Publisher;
use Cms\WorkflowsBundle\Service\WorkflowsManager;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Util\ClassUtils;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
/**
* Class ContentController
* @package Cms\ModuleBundle\Controller
*
* @Route(
* "/{container}/content/{module}",
* requirements = {
* "container" = "[1-9]\d*",
* "module" = "[a-z]+"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
*/
final class ContentController extends Controller
{
public const ROUTES__DRAFT_CLONE = 'campussuite.cms.module.dashboard.content.draft_clone';
public const ROUTES__DRAFT_DELETE = 'campussuite.cms.module.dashboard.content.draft_delete';
public const ROUTES__DRAFT_LIST = 'campussuite.cms.module.dashboard.content.draft_list';
public const ROUTES__DRAFT_MODIFY = 'campussuite.cms.module.dashboard.content.draft_modify';
public const ROUTES__DRAFT_PUBLISH = 'campussuite.cms.module.dashboard.content.draft_publish';
public const ROUTES__DRAFT_PREVIEW = 'campussuite.cms.module.dashboard.content.draft_preview';
public const ROUTES__HISTORY_LIST = 'campussuite.cms.module.dashboard.content.history_list';
public const ROUTES__HISTORY_REVERT = 'campussuite.cms.module.dashboard.content.history_revert';
public const ROUTES__HISTORY_PREVIEW = 'campussuite.cms.module.dashboard.content.history_preview';
public const ROUTES__PROXY_CLONE = 'campussuite.cms.module.dashboard.content.proxy_clone';
public const ROUTES__PROXY_CREATE = 'campussuite.cms.module.dashboard.content.proxy_create';
public const ROUTES__PROXY_DELETE = 'campussuite.cms.module.dashboard.content.proxy_delete';
public const ROUTES__PROXY_LIST = 'campussuite.cms.module.dashboard.content.proxy_list';
public const ROUTES__PROXY_MODIFY = 'campussuite.cms.module.dashboard.content.proxy_modify';
public const ROUTES__PROXY_AUDIT = 'campussuite.cms.module.dashboard.content.proxy_audit';
public const ROUTES__PROXY_MOVE = 'campussuite.cms.module.dashboard.content.proxy_move';
public const ROUTES__PROXY_SHARE = 'campussuite.cms.module.dashboard.content.proxy_share';
public const ROUTES__PROXY_BULK_DELETE = 'campussuite.cms.module.dashboard.content.proxy_bulk_delete';
public const ROUTES__PROXY_BULK_MOVE = 'campussuite.cms.module.dashboard.content.proxy_bulk_move';
public const ROUTES__PROXY_BULK_DUPLICATE = 'campussuite.cms.module.dashboard.content.proxy_bulk_duplicate';
public const ROUTES__PROXY_PREVIEW = 'campussuite.cms.module.dashboard.content.proxy_preview';
public const ROUTES__REVISION_LIST = 'campussuite.cms.module.dashboard.content.revision_list';
public const ROUTES__REVISION_REVERT = 'campussuite.cms.module.dashboard.content.revision_revert';
public const ROUTES__REVISION_PREVIEW = 'campussuite.cms.module.dashboard.content.revision_preview';
public const ROUTES__SETTINGS = 'campussuite.cms.module.dashboard.content.settings';
public const ROUTES__MODIFY_CONFLICTS = 'campussuite.cms.module.dashboard.content.modify_conflicts';
/**
* {@inheritdoc}
*
* Overriding the parent view so that we can add the module config to every twig rendering.
*/
public function view($template = null, array $parameters = []): DocumentScene
{
$view = parent::view($template, $parameters);
$view->setParameter('moduleConfig', $this->getModuleConfig());
if ($view->hasParameter('container') && $view->getParameter('container') instanceof Container) {
/** @var Container $container */
$container = $view->getParameter('container');
$view
->setParameter(
'ancestors',
$container->getAncestors()
)
->setParameter(
'parent',
$container->getParent()
)
->setParameter(
'favorite',
$this->determineFavorite($container)
);
}
return $view;
}
/**
* {@inheritdoc}
*
* Overriding the parent view, so we can add the module config to every twig rendering.
*/
public function viewAjax($template = null, array $parameters = []): AjaxScene
{
return parent::viewAjax($template, $parameters)
->setParameter('moduleConfig', $this->getModuleConfig());
}
/**
* Attempts to use module data tied to a request to load up the module configuration for the current module.
*
* @return ModuleConfig
*/
private function getModuleConfig(): ModuleConfig
{
// get module
$module = $this->getRequest()->attributes->get('module');
if (empty($module)) {
throw new \RuntimeException();
}
// can return from cache now
return $this->getModuleManager()->getModuleConfiguration($module);
}
/**
* @param Container $container
* @return Search|RedirectResponse
* @throws \Exception
*/
private function parseModuleTypeSearch(Container $container)
{
// use info from the request to generate the proper search objects
// need to do in the context of the container we are currently in
return $this->getModuleConfig()->getSearcher()->handleRequest(
$this->getRequest(),
$container,
);
}
/**
* @param Search $search
* @param Container $container
* @return array|Proxy[]
* @throws \Exception
*/
private function performModuleTypeSearch(Search $search, Container $container)
{
// get the repo for the type of thing we are looking for
/** @var ProxyRepository $repo */
$repo = $this->getEntityManager()->getRepository(sprintf(
'Cms\\Modules\\%sBundle\\Entity\\%s\\%sProxy',
$this->getModuleConfig()->name(),
$this->getModuleConfig()->types()[0],
$this->getModuleConfig()->types()[0]
));
// should be searchable
if ( ! $repo instanceof SearchableInterface) {
throw new \Exception();
}
// get the module settings
$settings = $this->getEntityManager()->getRepository($this->getModuleConfig()->settingsClass())->findOneBy(array(
'container' => $container,
));
// get the results of the search in the context of the current container
return $repo->search($search, $container, $settings);
}
/**
* @param Container $container
* @return Container|null
*/
private function determineFavorite(Container $container)
{
// TODO: the return on this does not make sense, but doing it for BC, should be fixed but involves fixing other areas as well...
if ($this->getGlobalContext()->getEffectiveAccount()->getFavorites()->contains($container)) {
return $container;
}
return null;
}
/**
* Common controller for handling quick previews of live content.
*
* @param Container $container
* @param Proxy $proxy
* @param History|null $history
* @param Draft|null $draft
* @param Revision|null $revision
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/preview",
* name = ContentController::ROUTES__PROXY_PREVIEW,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
* @Route(
* "/{proxy}/history/{history}/preview",
* name = ContentController::ROUTES__HISTORY_PREVIEW,
* requirements = {
* "proxy" = "[1-9]\d*",
* "history" = "[1-9]\d*"
* }
* )
* @Route(
* "/{proxy}/draft/{draft}/preview",
* name = ContentController::ROUTES__DRAFT_PREVIEW,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*"
* }
* )
* @Route(
* "/{proxy}/draft/{draft}/revision/{revision}/preview",
* name = ContentController::ROUTES__REVISION_PREVIEW,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*",
* "revision" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "history",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "revision",
* converter = "cms__module_entity"
* )
*/
public function previewAction(Container $container, Proxy $proxy, History $history = null, Draft $draft = null, Revision $revision = null)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// determine what object we are trying to find
$content = null;
$backLink = null;
switch (true) {
case ! empty($revision):
$content = $revision;
$backLink = $this->generateUrl(
self::ROUTES__REVISION_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
'proxy' => $proxy->getId(),
'draft' => $draft->getId(),
)
);
break;
case ! empty($draft):
$content = $draft;
$backLink = $this->generateUrl(
self::ROUTES__DRAFT_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
'proxy' => $proxy->getId(),
)
);
break;
case ! empty($history):
$content = $history;
$backLink = $this->generateUrl(
self::ROUTES__HISTORY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
'proxy' => $proxy->getId(),
)
);
break;
case ! empty($proxy):
$content = $proxy;
$backLink = $this->generateUrl(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
break;
}
if (empty($content)) {
throw new \Exception();
}
return $this->view(
array(
'container' => $container,
'content' => $content,
'backLink' => $backLink,
'proxy' => $proxy,
'history' => $history,
'draft' => $draft,
'revision' => $revision,
)
);
}
/**
* @param Request $request
* @param Container $container
* @return DocumentScene|AjaxScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "",
* name = ContentController::ROUTES__PROXY_LIST
* )
*/
public function proxyListAction(Request $request, Container $container)
{
// generate and run search
$search = $this->parseModuleTypeSearch($container);
if ($search instanceof RedirectResponse) {
return $search;
}
$proxies = $this->performModuleTypeSearch($search, $container);
// handle lazyloading
if ($request->isXmlHttpRequest()) {
return $this->viewAjax(
'@CmsModule/Content/includes/proxies.html.twig',
array(
'container' => $container,
'search' => $search,
'proxies' => $proxies,
)
);
}
return $this->view(
array(
'favorites' => $this->getGlobalContext()->getEffectiveAccount()->getFavorites(),
'all' => $this->getEntityManager()->getRepository(Container::class)->findAllHierarchy($container),
'container' => $container,
'search' => $search,
'proxies' => $proxies,
)
);
}
/**
* @param Request $request
* @param Container $container
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/bulk/delete",
* name = ContentController::ROUTES__PROXY_BULK_DELETE
* )
*/
public function proxyBulkDeleteAction(Request $request, Container $container)
{
// get the ids of what we are trying to delete
$ids = array_values(array_filter(json_decode($request->request->get('data', []))));
if (empty($ids)) {
throw new \Exception();
}
// find all the things we want to delete
/** @var array|Proxy[] $things */
$things = array_values(array_filter(array_map(
function(Proxy $proxy) use($container) {
if ($proxy->getContainer()->getId() !== $container->getId()) {
return null;
}
return $proxy;
},
$this->getRepository($this->getModuleConfig()->classesProxy())->findExacts($ids)
)));
// check all objects
foreach ($things as $thing) {
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $thing)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($thing instanceof ShareableInterface && ! $thing->isMaster()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $thing);
}
// check for submission
if ($this->handleBulk() && $this->handleSecureDelete()) {
// do as a transaction
$this->getActivityLogger()->backup();
try {
$this->getEntityManager()->transactional(
function(EntityManager $em) use ($things) {
foreach ($things as $thing) {
// record log
$this->getActivityLogger()->createLog($thing, $thing->getId());
// do removal
$this->getContentManager()->proxyDelete(
$thing
);
}
// make changes
$em->flush();
$this->getActivityLogger()->commit();
}
);
} catch (\Exception $e) {
$this->getActivityLogger()->restore();
throw $e;
}
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'things' => $things,
'data' => $ids,
)
);
}
/**
* @param Request $request
* @param Container $container
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/bulk/move",
* name = ContentController::ROUTES__PROXY_BULK_MOVE
* )
*/
public function proxyBulkMoveAction(Request $request, Container $container)
{
// get the ids of what we are trying to move
$ids = array_values(array_filter(json_decode($request->request->get('data'))));
if (empty($ids)) {
throw new \Exception();
}
// find all the things we want to move
/** @var array|Proxy[] $things */
$things = array_values(array_filter(array_map(
function(Proxy $proxy) use($container) {
if ($proxy->getContainer()->getId() !== $container->getId()) {
return null;
}
return $proxy;
},
$this->getRepository($this->getModuleConfig()->classesProxy())->findExacts($ids)
)));
// check all objects
foreach ($things as $thing) {
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $thing)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($thing instanceof ShareableInterface && ! $thing->isMaster()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $thing);
}
// create form
$form = $this->createForm(
MoveType::class,
array(
'container' => $container,
),
[]
);
// check for submission
if ($this->handleBulk() && $this->handleForm($form)) {
// AUDIT
// check that we have access to the target department
try {
$this->auditContainer($form->getData()['container'], $things[0]);
} catch (\Exception $e) {
$form->addError(new FormError(
'You do not have permission to move content into the selected department, please choose a different department.'
));
}
// make sure we still have no errors
if ($form->isValid()) {
// do as a transaction
$this->getActivityLogger()->backup();
try {
$this->getEntityManager()->transactional(
function(EntityManager $em) use ($things, $form) {
foreach ($things as $thing) {
// record log
$this->getActivityLogger()->createLog($thing, $thing);
// do move
$this->getContentManager()->proxyMove(
$thing,
$form->getData()['container']
);
}
// make changes
$em->flush();
$this->getActivityLogger()->commit();
}
);
} catch (\Exception $e) {
$this->getActivityLogger()->restore();
throw $e;
}
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
}
return $this->view(
array(
'form' => $form->createView(),
'container' => $container,
'things' => $things,
'data' => $ids,
)
);
}
/**
* @param Request $request
* @param Container $container
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/bulk/duplicate",
* name = ContentController::ROUTES__PROXY_BULK_DUPLICATE
* )
*/
public function proxyBulkDuplicateAction(Request $request, Container $container)
{
// get the ids of what we are trying to move
$ids = array_values(array_filter(json_decode($request->request->get('data'))));
if (empty($ids)) {
throw new \Exception();
}
// find all the things we want to move
/** @var array|Proxy[] $things */
$things = array_values(array_filter(array_map(
function(Proxy $proxy) use($container) {
if ($proxy->getContainer()->getId() !== $container->getId()) {
return null;
}
return $proxy;
},
$this->getRepository($this->getModuleConfig()->classesProxy())->findExacts($ids)
)));
// check all objects
foreach ($things as $thing) {
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $thing)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($thing instanceof ShareableInterface && ! $thing->isMaster()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $thing);
}
// create form
$form = $this->createForm(
DuplicateType::class,
array(
'container' => $container,
),
[]
);
// check for submission
if ($this->handleBulk() && $this->handleForm($form)) {
// do as a transaction
$this->getActivityLogger()->backup();
try {
$this->getEntityManager()->transactional(
function(EntityManager $em) use ($things, $form) {
foreach ($things as $thing) {
// record log
$this->getActivityLogger()->createLog($thing, $thing);
// do move
$this->getContentManager()->proxyDuplicate(
$thing,
$form->getData()['container']
);
}
// make changes
$em->flush();
$this->getActivityLogger()->commit();
}
);
} catch (\Exception $e) {
$this->getActivityLogger()->restore();
throw $e;
}
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'form' => $form->createView(),
'container' => $container,
'things' => $things,
'data' => $ids,
)
);
}
/**
* @param Container $department
* @param mixed $object
* @param array<string> $attributes
*/
private function auditContainer(
Container $department,
$object = null,
$attributes = []
): void
{
// attach the alias for general content management to any attributes already passed in
$attributes[] = '@app.cms.access.'.$department::DISCR;
// handle somebody being allowed to manage their own people profiles
// TODO: this should only really apply to the edit action, not delete...
if (($object instanceof ProfileProxy || $object instanceof ProfileDraft) && strtolower($object->getData()->get('email')) === strtolower($this->getGlobalContext()->getEffectiveAccount()->getEmail()))
{
$attributes[] = 'campussuite.cms.me.profiles';
}
// check permissions
$this->denyAccessUnlessGranted(
$attributes,
[$object, $department],
);
}
/**
* @param Request $request
* @param Container $container
* @param string|null $handler
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/create/{handler}",
* name = ContentController::ROUTES__PROXY_CREATE,
* requirements = {
* "handler" = "[a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
*/
public function proxyCreateAction(
FeedingService $feedingService,
Request $request,
Container $container,
?string $handler = null
)
{
// generate a new thing
$class = $this->getModuleConfig()->classesProxy();
/** @var Proxy $proxy */
$proxy = new $class();
// AUDIT
// If we pass a subject with module object, the voter logic will also check permissions for the object type, not just generic access
$this->auditContainer($container, $proxy);
// TODO: this needs handled in a more dynamic way...
// see if we have any prefill data
$prefills = array_filter(
$request->query->all(),
function ($key) {
return (strpos($key, 'prefill_') === 0);
},
ARRAY_FILTER_USE_KEY
);
$prefilled = false;
if ( ! empty($prefills)) {
switch ($this->getModuleConfig()->key()) {
case 'page':
/** @var PageProxy $proxy */
foreach ($prefills as $k => $v) {
$prefilled = true;
if ($v !== null && $v !== '') {
switch (str_replace('prefill_', '', $k)) {
case 'name':
$proxy->getData()
->setTitle($v)
->setSlug($this->getSlugger()->slugify($v));
break;
case 'nav':
$proxy->getData()->setAppendToNav( ! empty($v));
break;
case 'layout':
$v = intval($v);
$content = [
'content' => [],
];
switch ($v) {
case 2:
$content['content'][] = (new Row())
->setStructure('2/3 1/3')
->setColumns([
(new Column())
->setChildren([
(new Html())
->setContent('<h1>Edit your heading here...</h1><p>Edit your copy for the page here...</p>'),
]),
(new Column())
->setChildren([
(new Html())
->setContent('<h3>Edit your sidebar heading here...</h3><p>Edit your copy for the sidebar here...</p>'),
]),
]);
break;
case 1:
$content['content'][] = (new Row())
->setStructure('1/1')
->setColumns([
(new Column())
->setChildren([
(new Html())
->setContent('<h1>Edit your heading here...</h1><p>Edit your copy for the page here...</p>'),
]),
]);
break;
default:
/*
$content['content'][] = (new Html())
->setContent(implode('', array_map(
function ($html) {
return '<p>'.$html.'</p>';
},
$faker->sentences()
)));
*/
}
$proxy->getData()->setContent($this->getTranscoder()->encode($content, Transcoder::WRAPPING__JSON));
break;
}
}
}
break;
}
}
// generate a form for content
$dataForm = $this->createForm(
$this->getModuleConfig()->formClass(ClassUtils::getClass($proxy->getData())),
$proxy->getData(),
array(
'container' => $container,
'create' => true,
'prefilled' => $prefilled,
)
);
// create forms for each action: immediate, new draft, draft revision, schedule
/** @var array|FormInterface[] $actionForms */
$actionForms = $this->getContentManager()->assembleProxyCreateActions(
$this->getRequest(),
$container,
$this->getGlobalContext()->getEffectiveAccount()
);
// determine which action form we are talking about, if applicable
if ( ! empty($handler) && ! array_key_exists($handler, $actionForms)) {
throw new \Exception();
}
// handle form submission
$valid = $this->handleForm($dataForm);
if ( ! empty($handler)) {
$valid = $this->handleForm($actionForms[$handler]['form']) && $valid;
}
if ($valid) {
// get stuff
/** @var Data $data */
$data = $dataForm->getData();
/** @var Action $action */
$action = $actionForms[$handler]['form']->getData();
// handle action
$result = $this->getContentManager()->proxyCreate(
$action,
$container,
$data
);
// LOG
if ($result instanceof ScheduledPublication) {
$result = $result->getContent()->getDraft();
}
$this->getActivityLogger()->createLog($result);
// RECENT FEED
$feedingService->log($result);
// jump to content if live
if ($result instanceof Proxy && $result->isJumpable()) {
return $this->redirect(
$this->getPublicationService()
->getFrontLink($result, true, $result->getContainer())
);
}
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'dataForm' => $dataForm->createView(),
'actionForms' => array_map(
function (array $data) {
$data['form'] = $data['form']->createView();
return $data;
},
$actionForms
),
'autoShow' => ($prefilled === true),
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param string|null $handler
* @return DocumentScene|RedirectResponse|Response
* @throws \Exception
*
* @Route(
* "/{proxy}/conflicts/{handler}",
* name = ContentController::ROUTES__MODIFY_CONFLICTS,
* requirements = {
* "proxy" = "[1-9]\d*",
* "handler" = "[-a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function conflictsAction(Container $container, Proxy $proxy, $handler = null)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($proxy instanceof ShareableInterface && ! $proxy->isMaster()) {
throw new \Exception();
}
// if syncable, do not allow editing
if ($proxy instanceof SyncableInterface && $proxy->isSynced()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $proxy);
// if the proxy has a draft, load up a submission and possible publication
$submission = null;
$publication = null;
if ( ! empty($proxy->getDraft())) {
// TODO: needs to be a method for like a service probably...
switch (true) {
case $proxy->getDraft() instanceof AlertDraft:
$cls = AlertContent::class;
break;
case $proxy->getDraft() instanceof ArticleDraft:
// TODO: need to refactor code to fit naming conventions
$cls = NewsContent::class;
break;
case $proxy->getDraft() instanceof EntryDraft:
// TODO: need to refactor code to fit naming conventions
$cls = QuestionsContent::class;
break;
case $proxy->getDraft() instanceof EventDraft:
// TODO: need to refactor code to fit naming conventions
$cls = CalendarContent::class;
break;
case $proxy->getDraft() instanceof GalleryDraft:
$cls = GalleryContent::class;
break;
case $proxy->getDraft() instanceof PageDraft:
$cls = PageContent::class;
break;
case $proxy->getDraft() instanceof PostDraft:
// TODO: need to refactor code to fit naming conventions
$cls = BlogContent::class;
break;
case $proxy->getDraft() instanceof ProfileDraft:
// TODO: need to refactor code to fit naming conventions
$cls = PeopleContent::class;
break;
case $proxy->getDraft() instanceof SnippetDraft:
$cls = SnippetContent::class;
break;
default:
throw new \Exception();
}
/** @var WorkflowContent $content */
$content = $this->getEntityManager()->getRepository($cls)->findOneBy(array(
'draft' => $proxy->getDraft(),
));
if ( ! empty($content)) {
// TODO: this does not work for some reason, even though there is a field for submission on the object
//$submission = $content->getSubmission();
$submission = $this->getEntityManager()->getRepository(WorkflowSubmission::class)->findOneBy(array(
'content' => $content,
'status' => WorkflowSubmission::SUBMISSION_STATUSES__APPROVAL,
));
// TODO: publication stuff needs improved...
$publication = $content->getPublication();
if ( ! empty($publication) && $publication->getStatus() !== ScheduledPublication::STATUSES__SCHEDULED) {
$publication = null;
}
}
}
// handle cases that cause us no problem
switch (true) {
// placeholder; no real proxy but draft that is not in review or anything
case ($proxy->isPlaceholder() && empty($submission) && empty($publication)):
return $this->redirectToRoute(
self::ROUTES__DRAFT_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
'draft' => $proxy->getDraft()->getId(),
)
)
);
// just a proxy
case ( ! $proxy->isPlaceholder() && empty($proxy->getDraft())):
return $this->redirectToRoute(
self::ROUTES__PROXY_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
)
)
);
}
// generate the possible options for us
$options = array(
'proxy-cancel-draft' => array(
'label' => 'Delete Draft and Edit Live',
'message' => 'The current draft will be deleted and you will be brought to the live content editor.',
),
'proxy-cancel-review' => array(
'label' => 'Cancel Review, Delete Draft, and Edit Live',
'message' => 'The current active draft is in review. This review will be cancelled, the draft will be deleted, and you will then be directed to the live content editor.',
),
'proxy-cancel-publication' => array(
'label' => 'Cancel Publication, Delete Draft, and Edit Live',
'message' => 'The current active draft is scheduled for future publication. This publication will be cancelled, the draft will be deleted, and you will then be directed to the live content editor.',
),
'draft' => array(
'label' => 'Edit Draft',
'message' => 'The current draft will stay intact, and you will be directed to the draft editor. If the draft is in active review, it will be modifed and the review will be updated.',
),
'draft-cancel-review' => array(
'label' => 'Cancel Review and Edit Draft',
'message' => 'The current active draft is in review. This review will be cancelled, and you will be directed to the draft editor. The updated draft can then be re-submitted for review again later.',
),
'draft-cancel-publication' => array(
'label' => 'Cancel Publication and Edit Draft',
'message' => 'The current active draft is scheduled for future publication. This publication will be cancelled, and you will be directed to the draft editor.',
),
);
// specific options for the case
switch (true) {
// no proxy, but with draft in review
case ($proxy->isPlaceholder() && ! empty($submission)):
$options = array(
'draft' => $options['draft'],
'draft-cancel-review' => $options['draft-cancel-review'],
);
break;
// no proxy, but with draft scheduled
case ($proxy->isPlaceholder() && ! empty($publication)):
$options = array(
'draft-cancel-publication' => $options['draft-cancel-publication'],
);
break;
// proxy, but with draft in review
case ( ! empty($submission)):
$options = array(
'draft' => $options['draft'],
'draft-cancel-review' => $options['draft-cancel-review'],
'proxy-cancel-review' => $options['proxy-cancel-review'],
);
break;
// proxy, but with draft scheduled
case ( ! empty($publication)):
$options = array(
'draft-cancel-publication' => $options['draft-cancel-publication'],
'proxy-cancel-publication' => $options['proxy-cancel-publication'],
);
break;
// proxy, but with draft
case ( ! empty($proxy->getDraft())):
$options = array(
'draft' => $options['draft'],
'proxy-cancel-draft' => $options['proxy-cancel-draft'],
);
break;
}
// handle
if ( ! empty($handler)) {
// handle actions
switch ($handler) {
// modify proxy, remove draft
case 'proxy-cancel-draft':
$this->getDraftManager()->closeDraft($proxy->getDraft());
//$this->getActivityLogger()->createLog($proxy);
return $this->redirectToRoute(
self::ROUTES__PROXY_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
)
)
);
// modify proxy, cancel review and draft
case 'proxy-cancel-review':
$this->getWorkflowsManager()->cancel($submission, $this->getGlobalContext()->getEffectiveAccount(), null);
$this->getDraftManager()->closeDraft($proxy->getDraft());
//$this->getActivityLogger()->createLog($proxy);
return $this->redirectToRoute(
self::ROUTES__PROXY_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
)
)
);
// modify proxy, cancel publication and draft
case 'proxy-cancel-publication':
$this->getPublisher()->cancel($publication);
$this->getDraftManager()->closeDraft($proxy->getDraft());
//$this->getActivityLogger()->createLog($proxy);
return $this->redirectToRoute(
self::ROUTES__PROXY_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
)
)
);
// modify draft
case 'draft':
return $this->redirectToRoute(
self::ROUTES__DRAFT_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
'draft' => $proxy->getDraft()->getId(),
)
)
);
// modify draft, but cancel review
case 'draft-cancel-review':
$this->getWorkflowsManager()->cancel($submission, $this->getGlobalContext()->getEffectiveAccount(), null);
//$this->getActivityLogger()->createLog($proxy->getDraft());
return $this->redirectToRoute(
self::ROUTES__DRAFT_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
'draft' => $proxy->getDraft()->getId(),
)
)
);
// modify draft, but cancel publication
case 'draft-cancel-publication':
$this->getPublisher()->cancel($publication);
//$this->getActivityLogger()->createLog($proxy->getDraft());
return $this->redirectToRoute(
self::ROUTES__DRAFT_MODIFY,
array_merge(
$this->getRequest()->query->all(),
$this->getRequest()->attributes->get('_route_params'),
array(
'handler' => null,
'draft' => $proxy->getDraft()->getId(),
)
)
);
}
// shouldn't get this far; all cases should be handled
throw new \Exception();
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'options' => $options,
'preview' => $proxy->getDraft(),
'submission' => $submission,
'publication' => $publication,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft|null $draft
* @param string|null $handler
* @return DocumentScene|RedirectResponse|Response
* @throws \Exception
*
* @Route(
* "/{proxy}/modify/{handler}",
* name = ContentController::ROUTES__PROXY_MODIFY,
* requirements = {
* "proxy" = "[1-9]\d*",
* "handler" = "[a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
* @Route(
* "/{proxy}/draft/{draft}/modify/{handler}",
* name = ContentController::ROUTES__DRAFT_MODIFY,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*",
* "handler" = "[a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
*/
public function modifyAction(
FeedingService $feedingService,
Container $container,
Proxy $proxy,
Draft $draft = null,
?string $handler = null
)
{
// ensure that all the objects are associated properly
if ($draft === null) {
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
} elseif (($response = $this->validateObjects($container, $proxy, $draft)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($proxy instanceof ShareableInterface && ! $proxy->isMaster()) {
throw new \Exception();
}
// if syncable, do not allow editing
if ($proxy instanceof SyncableInterface && $proxy->isSynced()) {
throw new \Exception();
}
// AUDIT
if ( ! empty($draft)) {
$this->auditContainer($container, $draft);
} else {
$this->auditContainer($container, $proxy);
}
// get object
/** @var ModuleEntity $content */
if ( ! empty($draft)) {
$content = clone $draft;
} else {
$content = clone $proxy;
}
// lock the item
$lck = $this->trapLocked(( ! empty($draft)) ? $draft : $proxy, $this->generateUrl(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
));
if ( ! empty($lck)) {
return $lck;
}
// generate a form for content
$dataForm = $this->createForm(
$this->getModuleConfig()->formClass(ClassUtils::getClass($content->getData())),
$content->getData(),
array(
'container' => $container,
'create' => false,
)
);
// create forms for each action: immediate, new draft, draft revision, schedule
/** @var array|FormInterface[] $actionForms */
if ( ! empty($draft)) {
$actionForms = $this->getContentManager()->assembleDraftModifyActions(
$this->getRequest(),
$container,
$this->getGlobalContext()->getEffectiveAccount(),
$draft
);
} else {
$actionForms = $this->getContentManager()->assembleProxyModifyActions(
$this->getRequest(),
$container,
$this->getGlobalContext()->getEffectiveAccount()
);
}
// determine which action form we are talking about, if applicable
if ( ! empty($handler) && ! array_key_exists($handler, $actionForms)) {
throw new \Exception();
}
// handle form submission
$valid = $this->handleForm($dataForm);
if ( ! empty($handler)) {
$valid = $this->handleForm($actionForms[$handler]['form']) && $valid;
}
if ($valid) {
// get stuff
/** @var Data $data */
$data = $dataForm->getData();
/** @var Action $action */
$action = $actionForms[$handler]['form']->getData();
// handle actions
if ( ! empty($draft)) {
$result = $this->getContentManager()->draftModify(
$action,
$draft,
$data
);
} else {
$result = $this->getContentManager()->proxyModify(
$action,
$proxy,
$data
);
}
// release any locks
$this->getLocksmith()->releaseLock(( ! empty($draft)) ? $draft : $proxy);
// LOG
if ($result instanceof ScheduledPublication) {
$result = $result->getContent()->getDraft();
}
$this->getActivityLogger()->createLog($result);
// RECENT FEED
$feedingService->log($result);
return $this->redirectToRouteOrOverride(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
'dataForm' => $dataForm->createView(),
'actionForms' => array_map(
function (array $data) {
$data['form'] = $data['form']->createView();
return $data;
},
$actionForms
),
)
);
}
/**
* @param string $action
* @return FormInterface
* @throws \Exception
*/
private function createActionForm($action)
{
// generate classes needed
$formClass = sprintf(
'Cms\\ModuleBundle\\Form\\Type\\Actions\\%sActionType',
$action
);
if ( ! class_exists($formClass)) {
throw new \Exception();
}
$modelClass = sprintf(
'Cms\\ModuleBundle\\Model\\Actions\\%sAction',
$action
);
if ( ! class_exists($modelClass)) {
throw new \Exception();
}
// create and return the form
return $this->createForm(
$formClass,
new $modelClass(),
array(
'action' => $this->generateUrl(
$this->getRequest()
->attributes->get('_route'),
array_merge(
$this->getRequest()
->attributes->get('_route_params'),
array(
'handler' => $action,
'redirectTo' => $this->getRequest()
->query->get('redirectTo', null),
)
)
),
));
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft|null $draft
* @param string|null $handler
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/clone/{handler}",
* name = ContentController::ROUTES__PROXY_CLONE,
* requirements = {
* "proxy" = "[1-9]\d*",
* "handler" = "[a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
* @Route(
* "/{proxy}/draft/{draft}/clone/{handler}",
* name = ContentController::ROUTES__DRAFT_CLONE,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*",
* "handler" = "[a-zA-Z]+"
* },
* defaults = {
* "handler" = null
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
*/
public function cloneAction(
FeedingService $feedingService,
Container $container,
Proxy $proxy,
Draft $draft = null,
?string $handler = null
)
{
// ensure that all the objects are associated properly
if ($draft === null) {
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
} elseif (($response = $this->validateObjects($container, $proxy, $draft)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($proxy instanceof ShareableInterface && ! $proxy->isMaster()) {
throw new \Exception();
}
// if syncable, do not allow editing
if ($proxy instanceof SyncableInterface && $proxy->isSynced()) {
throw new \Exception();
}
// AUDIT
if (empty($draft)) {
$this->auditContainer($container, $proxy);
} else {
$this->auditContainer($container, $draft);
}
// get object
/** @var ModuleEntity $cont */
if (empty($draft)) {
// do the cloning
$content = clone $proxy;
// if cloning a proxy, need to try to help ensure that the slug is unique
if ($content->getData()->has('slug')) {
$content->getData()->set('slug', sprintf(
'%s-clone',
$content->getData()->get('slug')
));
}
// also need to make sure that new clone does not have any history or drafts tied to it
// TODO: should we be doing this elsewhere?
$content
->setHistory(null)
->setDraft(null);
} else {
// do the cloning
$content = clone $draft;
// also need to make sure that new clone does not have any revision tied to it
// TODO: should we be doing this elsewhere?
$content
->setRevision(null);
}
// fix shared stuff; cloning messes this up
if ($content instanceof ShareableInterface) {
$content->setSharedProxy($content);
}
// create the form that handles the data pieces of the module item
$dataForm = $this->createForm(
$this->getModuleConfig()->formClass(ClassUtils::getClass($content->getData())),
$content->getData(),
array(
'container' => $container,
'create' => true,
'clone' => true,
)
);
// create forms for each action that can be done once editing is finished
/** @var array|FormInterface[] $actionForms */
if ( ! empty($draft)) {
$actionForms = $this->getContentManager()->assembleDraftCloneActions(
$this->getRequest(),
$container,
$this->getGlobalContext()->getEffectiveAccount(),
$draft
);
} else {
$actionForms = $this->getContentManager()->assembleProxyCloneActions(
$this->getRequest(),
$container,
$this->getGlobalContext()->getEffectiveAccount(),
$proxy
);
}
// determine which action form we are talking about, if applicable
if ( ! empty($handler) && ! array_key_exists($handler, $actionForms)) {
throw new \Exception('There is no handler for this action.');
}
// handle form submission
$valid = $this->handleForm($dataForm);
if ( ! empty($handler)) {
$valid = $this->handleForm($actionForms[$handler]['form']) && $valid;
}
if ($valid) {
// get stuff from the forms
/** @var Data $data */
$data = $dataForm->getData();
/** @var Action $action */
$action = $actionForms[$handler]['form']->getData();
// handle actions
if ($content instanceof Draft) {
$result = $this->getContentManager()->draftClone(
$action,
$content,
$data
);
} else {
$result = $this->getContentManager()->proxyClone(
$action,
$content,
$data
);
}
// LOG
if ($result instanceof ScheduledPublication) {
$result = $result->getContent()->getDraft();
}
$this->getActivityLogger()->createLog($result);
// RECENT FEED
$feedingService->log($result);
return $this->redirectToRouteOrOverride(
($result instanceof Draft) ? self::ROUTES__DRAFT_LIST : self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
'proxy' => ($result instanceof Draft) ? $proxy->getId() : null,
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
'dataForm' => $dataForm->createView(),
'actionForms' => array_map(
function (array $data) {
$data['form'] = $data['form']->createView();
return $data;
},
$actionForms
),
)
);
}
/**
* @param Request $request
* @param Container $container
* @param Proxy $proxy
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/move",
* name = ContentController::ROUTES__PROXY_MOVE,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function proxyMoveAction(Request $request, Container $container, Proxy $proxy)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// make sure we are not a share
if ($proxy instanceof ShareableInterface && ! $proxy->isMaster()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $proxy);
// create form
$form = $this->createForm(
MoveType::class,
array(
'container' => $proxy->getContainer(),
),
[]
);
// attempt form processing
if ($this->handleForm($form, $request)) {
// AUDIT
// check that we have access to the target department
try {
$this->auditContainer($form->getData()['container'], $proxy);
} catch (\Exception $e) {
$form->addError(new FormError(
'You do not have permission to move content into the selected department, please choose a different department.'
));
}
// make sure we still have no errors
if ($form->isValid()) {
// TODO: need to clear out any share if one exists
// TODO: allow user to select in form whether or not to keep a share to the old department?
// TODO: do we need to restrict where it can be moved; i.e. keeping within same tree root or ensuring container types are equal?
// do the share via manager
$this->getContentManager()->proxyMove(
$proxy,
$form->getData()['container']
);
// record log
$this->getActivityLogger()->createLog($proxy);
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $proxy->getContainer()->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'form' => $form->createView(),
)
);
}
/**
* @param Request $request
* @param Container $container
* @param Proxy $proxy
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* TODO: only allow shares to departments we have access to...
* @Route(
* "/{proxy}/share",
* name = ContentController::ROUTES__PROXY_SHARE,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function proxyShareAction(Request $request, Container $container, Proxy $proxy)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// make sure we are not already a share
if ($proxy instanceof ShareableInterface && ! $proxy->isMaster()) {
throw new \Exception();
}
// AUDIT
$this->auditContainer($container, $proxy);
$existing = array_map(
function (Proxy $proxy) {
return $proxy->getContainer();
},
$this->getEntityManager()->createQueryBuilder()
->select('shares')
->from(ClassUtils::getClass($proxy), 'shares')
->leftJoin('shares.container', 'containers')
->andWhere('shares.sharedProxy = :proxy')
->andWhere('shares.sharedProxy <> shares.id')
->setParameter('proxy', $proxy)
->getQuery()
->getResult()
);
$form = $this->createForm(
ShareType::class,
[
'containers' => $existing,
],
[
'container' => $container,
]
);
if ($this->handleForm($form, $request)) {
$containers = $form->getData()['containers'];
if ($containers instanceof ArrayCollection) {
$containers = $containers->toArray();
}
if (empty($containers)) {
$containers = [];
}
$this->getEntityManager()->transactional(
function (EntityManager $em) use ($proxy, $containers, $container) {
// get scalar ids
$ids = array_map(
function (Container $container) {
return $container->getId();
},
$containers
);
// run bulk update to delete shares not needed
$deleted = $em->createQueryBuilder()
->delete(ClassUtils::getClass($proxy), 'shares')
->andWhere('shares.sharedProxy = :proxy')
->andWhere('shares.sharedProxy <> shares.id')
->setParameter('proxy', $proxy);
if (count($ids) > 0) {
$deleted
->andWhere('shares.container NOT IN (:containers)')
->setParameter('containers', $ids);
}
$deleted
->getQuery()
->execute();
// determine ids we need to add
$existing = array_map(
function ($value) {
return intval($value);
},
$em->createQueryBuilder()
->select('container.id')
->from(ClassUtils::getClass($proxy), 'shares')
->leftJoin('shares.container', 'container')
->andWhere('shares.sharedProxy = :proxy')
->andWhere('shares.sharedProxy <> shares.id')
->setParameter('proxy', $proxy)
->getQuery()
->execute(null, SingleColumnHydrator::HYDRATOR)
);
// create objects of ones needing to add
/** @var array $diffs */
$diffs = array_diff($ids, $existing);
foreach ($diffs as $diff) {
/** @var Proxy $share */
$share = clone ($proxy);
// skip ourselves
$cont = $em->getRepository(Container::class)->findExact($diff);
if ($cont === $container) {
continue;
}
$share
->setContainer($cont)
->setHistory(null)
->setSharedChecksum(
( ! empty($proxy->getHistory()))
? $proxy->getHistory()->getId()
: null
)
->setSharedProxy($proxy);
$em->persist($share);
}
// save them
$em->flush();
}
);
// record log
$this->getActivityLogger()->createLog($proxy);
// redirect to parent index
return $this->redirectToRouteOrOverride(
self::ROUTES__PROXY_LIST,
array(
'container' => $proxy->getContainer()->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'form' => $form->createView(),
)
);
}
/**
* @param Request $request
* @param Container $container
* @param Proxy $proxy
* @return DocumentScene|AjaxScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/history",
* name = ContentController::ROUTES__HISTORY_LIST,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function historyListAction(Request $request, Container $container, Proxy $proxy)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $proxy);
// handle searching
$search = $this->getModuleHistorySearcher()->handleRequest($request, $container);
if ($search instanceof RedirectResponse) {
return $search;
}
$repo = $this->getEntityManager()->getRepository($this->getModuleConfig()->classesHistory());
if ( ! $repo instanceof SearchableInterface) {
throw new \Exception();
}
$histories = $repo->search($search, $proxy);
// if we are an ajax request, we should be lazyloading
if ($request->isXmlHttpRequest()) {
return $this->viewAjax(
'@CmsModule/Content/includes/histories.html.twig',
array(
'container' => $container,
'proxy' => $proxy,
'search' => $search,
'histories' => $histories,
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'search' => $search,
'histories' => $histories,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft $draft
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/draft/{draft}/publish",
* name = ContentController::ROUTES__DRAFT_PUBLISH,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
*/
public function draftPublishAction(Container $container, Proxy $proxy, Draft $draft)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy, $draft)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $proxy);
// see if we are submitted
if ($this->handleSubmission()) {
// use manager
$this->getDraftManager()->draftPublish($draft);
// record log
$this->getActivityLogger()->createLog($draft);
return $this->redirectToRoute(
self::ROUTES__DRAFT_LIST,
array(
'container' => $container->getId(),
'proxy' => $proxy->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param History $history
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/history/{history}/revert",
* name = ContentController::ROUTES__HISTORY_REVERT,
* requirements = {
* "proxy" = "[1-9]\d*",
* "history" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "history",
* converter = "cms__module_entity"
* )
*/
public function historyRevertAction(Container $container, Proxy $proxy, History $history)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $proxy);
// see if we are submitted
if ($this->handleSubmission()) {
// use manager
$this->getContentManager()->historyRestore($history);
// record log
$this->getActivityLogger()->createLog($proxy);
return $this->redirectToRoute(
self::ROUTES__HISTORY_LIST,
array(
'container' => $container->getId(),
'proxy' => $proxy->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'history' => $history,
)
);
}
/**
* @param Request $request
* @param Container $container
* @param Proxy $proxy
* @return DocumentScene|AjaxScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/draft",
* name = ContentController::ROUTES__DRAFT_LIST,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function draftListAction(Request $request, Container $container, Proxy $proxy)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $proxy);
// handle searching
$search = $this->getModuleDraftSearcher()->handleRequest($request, $container);
if ($search instanceof RedirectResponse) {
return $search;
}
$repo = $this->getEntityManager()->getRepository($this->getModuleConfig()->classesDraft());
if ( ! $repo instanceof SearchableInterface) {
throw new \Exception();
}
$drafts = $repo->search($search, $proxy);
// if we are an ajax request, we should be lazyloading
if ($request->isXmlHttpRequest()) {
return $this->viewAjax(
'@CmsModule/Content/includes/drafts.html.twig',
array(
'container' => $container,
'proxy' => $proxy,
'search' => $search,
'drafts' => $drafts,
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'search' => $search,
'drafts' => $drafts,
)
);
}
/**
* @param Request $request
* @param Container $container
* @param Proxy $proxy
* @param Draft $draft
* @return DocumentScene|AjaxScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/draft/{draft}/revision",
* name = ContentController::ROUTES__REVISION_LIST,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
*/
public function revisionListAction(Request $request, Container $container, Proxy $proxy, Draft $draft)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy, $draft)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $draft);
// handle searching
$search = $this->getModuleRevisionSearcher()->handleRequest($request, $container);
if ($search instanceof RedirectResponse) {
return $search;
}
$repo = $this->getEntityManager()->getRepository($this->getModuleConfig()->classesRevision());
if ( ! $repo instanceof SearchableInterface) {
throw new \Exception();
}
$revisions = $repo->search($search, $draft);
// if we are an ajax request, we should be lazyloading
if ($request->isXmlHttpRequest()) {
return $this->viewAjax(
'@CmsModule/Content/includes/revisions.html.twig',
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
'search' => $search,
'revisions' => $revisions,
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
'search' => $search,
'revisions' => $revisions,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft|null $draft
* @param Revision|null $revision
* @return RedirectResponse|null
*/
private function validateObjects(Container $container, Proxy $proxy, Draft $draft = null, Revision $revision = null): ?RedirectResponse
{
$redirectRoute = $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
[
'container' => $proxy->getContainer()->getId(),
'module' => $this->getModuleConfig()->key(),
]
);
// check proxy and container
if ($proxy->getContainer()->getId() !== $container->getId()) {
return $redirectRoute;
}
// handle draft if given
if ( ! empty($draft) || ! empty($revision)) {
if ($draft->getProxy()->getId() !== $proxy->getId()) {
return $redirectRoute;
}
}
// handle revision if given
if ( ! empty($revision)) {
if ($revision->getDraft()->getId() !== $draft->getId()) {
return $redirectRoute;
}
}
return null;
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft $draft
* @param Revision $revision
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/draft/{draft}/revision/{revision}/revert",
* name = ContentController::ROUTES__REVISION_REVERT,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*",
* "revision" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "revision",
* converter = "cms__module_entity"
* )
*/
public function revisionRevertAction(Container $container, Proxy $proxy, Draft $draft, Revision $revision)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy, $draft, $revision)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $draft);
// see if we are submitted
if ($this->handleSubmission()) {
// use manager
$this->getContentManager()->revisionRestore($revision);
// record log
$this->getActivityLogger()->createLog($draft);
return $this->redirectToRoute(
self::ROUTES__DRAFT_LIST,
array(
'container' => $container->getId(),
'proxy' => $proxy->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
'revision' => $revision,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/delete",
* name = ContentController::ROUTES__PROXY_DELETE,
* requirements = {
* "proxy" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
*/
public function proxyDeleteAction(Container $container, Proxy $proxy)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $proxy);
// process submission
if ($this->handleSubmission()) {
// run it through the content manager
$id = $this->getContentManager()->proxyDelete($proxy);
// record log
$this->getActivityLogger()->createLog($proxy, $id);
return $this->redirectToRoute(
self::ROUTES__PROXY_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
)
);
}
/**
* @param Container $container
* @param Proxy $proxy
* @param Draft $draft
* @return DocumentScene|RedirectResponse
* @throws \Exception
*
* @Route(
* "/{proxy}/draft/{draft}/delete",
* name = ContentController::ROUTES__DRAFT_DELETE,
* requirements = {
* "proxy" = "[1-9]\d*",
* "draft" = "[1-9]\d*"
* }
* )
*
* @ParamConverter(
* "container",
* converter = "cms__container"
* )
* @ParamConverter(
* "proxy",
* converter = "cms__module_entity"
* )
* @ParamConverter(
* "draft",
* converter = "cms__module_entity"
* )
*/
public function draftDeleteAction(Container $container, Proxy $proxy, Draft $draft)
{
// ensure that all the objects are associated properly
if (($response = $this->validateObjects($container, $proxy, $draft)) instanceof Response) {
return $response;
}
// AUDIT
$this->auditContainer($container, $draft);
// process submission
if ($this->handleSubmission()) {
// run it through the manager
$id = $this->getContentManager()->draftDelete($draft);
// record log
$this->getActivityLogger()->createLog($draft, $id);
return $this->redirectToRoute(
self::ROUTES__DRAFT_LIST,
array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
'proxy' => $proxy->getId(),
)
);
}
return $this->view(
array(
'container' => $container,
'proxy' => $proxy,
'draft' => $draft,
)
);
}
/**
* @param Container $container
* @return ModuleSettings
*/
private function loadSettings(Container $container)
{
// generate the class name for the settings
$class = $this->getModuleConfig()->settingsClass();
// obtain the repository
/** @var ModuleSettingsRepository $repo */
$repo = $this->getEntityManager()->getRepository($class);
// try to find one given the container
$settings = $repo->findOneByContainer($container);
// if none, make a new copy, link up to container
if (empty($settings)) {
/** @var ModuleSettings $settings */
$settings = new $class();
$settings->setContainer($container);
}
// done
return $settings;
}
/**
* @param Container $container
* @return DocumentScene|RedirectResponse
*
* @Route(
* "/settings",
* name = ContentController::ROUTES__SETTINGS
* )
*/
public function settingsAction(Container $container)
{
// AUDIT
$this->auditContainer($container);
// find settings or make a new one if none
$settings = $this->loadSettings($container);
// create form
$class = $this->getModuleConfig()->settingsForm();
$form = $this->createForm($class, $settings);
// handle form if needed
if ($this->handleForm($form)) {
// save settings
$this->getEntityManager()->save($settings);
// record log
$this->getActivityLogger()->createLog($settings);
// back to viewing list of module data
return $this->redirectToRoute(self::ROUTES__PROXY_LIST, array(
'container' => $container->getId(),
'module' => $this->getModuleConfig()->key(),
));
}
return $this->view(
array(
'container' => $container,
'form' => $form->createView(),
)
);
}
/**
* @return ContentManager|object
*/
private function getContentManager(): ContentManager
{
return $this->get(__METHOD__);
}
/**
* @return ModuleManager|object
*/
private function getModuleManager(): ModuleManager
{
return $this->get(__METHOD__);
}
/**
* @return Slugger|object
*/
private function getSlugger(): Slugger
{
return $this->get(__METHOD__);
}
/**
* @return Transcoder|object
*/
private function getTranscoder(): Transcoder
{
return $this->get(__METHOD__);
}
/**
* @return PublicationService|object
*/
private function getPublicationService(): PublicationService
{
return $this->get(__METHOD__);
}
/**
* @return DraftManager|object
*/
private function getDraftManager(): DraftManager
{
return $this->get(__METHOD__);
}
/**
* @return WorkflowsManager|object
*/
private function getWorkflowsManager(): WorkflowsManager
{
return $this->get(__METHOD__);
}
/**
* @return Publisher|object
*/
private function getPublisher(): Publisher
{
return $this->get(__METHOD__);
}
/**
* @return ModuleHistorySearcher|object
*/
private function getModuleHistorySearcher(): ModuleHistorySearcher
{
return $this->get(__METHOD__);
}
/**
* @return ModuleDraftSearcher|object
*/
private function getModuleDraftSearcher(): ModuleDraftSearcher
{
return $this->get(__METHOD__);
}
/**
* @return ModuleRevisionSearcher|object
*/
private function getModuleRevisionSearcher(): ModuleRevisionSearcher
{
return $this->get(__METHOD__);
}
}