src/Products/SchoolNowBundle/Controller/Dashboard/DefaultController.php line 144

Open in your IDE?
  1. <?php
  2. namespace Products\SchoolNowBundle\Controller\Dashboard;
  3. use App\Component\ViewLayer\Views\AbstractHtmlView;
  4. use App\Component\ViewLayer\Views\AjaxHtmlView;
  5. use App\Component\ViewLayer\Views\DocHtmlView;
  6. use App\Component\ViewLayer\Views\JsonView;
  7. use App\Controller\PaginationTrait;
  8. use App\Doctrine\Repository\Feed\Entry\ContentEventSearch;
  9. use App\Doctrine\Repository\Feed\FeedSearch;
  10. use App\Entity\Content\AbstractObject;
  11. use App\Entity\Content\Events\Event\EventObject;
  12. use App\Entity\Feed\AbstractEntry;
  13. use App\Entity\Feed\Entry\AbstractContentEntry;
  14. use App\Entity\Feed\Entry\ContentEventEntry;
  15. use App\Entity\Feed\Entry\ContentGalleryEntry;
  16. use App\Entity\System\School;
  17. use App\Enum\ChannelEnum;
  18. use App\Entity\System\SocialAccount;
  19. use App\Form\Forms\Content\ObjectForm;
  20. use App\Form\Forms\DummyForm;
  21. use App\Model\Content\ObjectInterface;
  22. use App\Service\Content\ContentManager;
  23. use App\Service\Content\ContentSchoolProvider;
  24. use App\Service\Feed\FeedManager;
  25. use App\Util\Pagination;
  26. use Cms\CoreBundle\Util\DateTimeUtils;
  27. use Cms\Modules\AlertBundle\Model\Alert\AlertData;
  28. use Cms\Modules\CalendarBundle\Entity\Event\EventProxy;
  29. use Cms\SystemBundle\Entity\Announcement\SystemAnnouncement;
  30. use Doctrine\Common\Util\ClassUtils;
  31. use Platform\SecurityBundle\Entity\Identity\Account;
  32. use Products\NotificationsBundle\Entity\Broadcast;
  33. use Products\NotificationsBundle\Entity\Profile;
  34. use Products\NotificationsBundle\Entity\Recipients\EmailRecipient;
  35. use Products\SchoolNowBundle\Controller\AbstractDashboardController;
  36. use Products\SchoolNowBundle\Form\Forms\FeedSearchForm;
  37. use Products\SchoolNowBundle\Form\Forms\Posts\PostForm;
  38. use Products\SchoolNowBundle\Form\Forms\Posts\PostReviewForm;
  39. use Products\SchoolNowBundle\Service\PostLogic;
  40. use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
  41. use Symfony\Component\Routing\Annotation\Route;
  42. use Symfony\Component\HttpFoundation\RedirectResponse;
  43. use Symfony\Component\HttpFoundation\Request;
  44. use Symfony\Component\HttpFoundation\Response;
  45. use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  46. /**
  47.  * Class DefaultController
  48.  * @package Products\SchoolNowBundle\Controller\Dashboard
  49.  */
  50. final class DefaultController extends AbstractDashboardController
  51. {
  52.     use PaginationTrait;
  53.     public const ROUTES__MAIN 'app.schoolnow.dashboard.default.main';
  54.     public const ROUTES__FEEDS__NOTIFICATIONS 'app.schoolnow.dashboard.default.feeds.notifications';
  55.     public const ROUTES__FEEDS__CALENDAR 'app.schoolnow.dashboard.default.feeds.calendar';
  56.     public const ROUTES__FEEDS__PHOTOS 'app.schoolnow.dashboard.default.feeds.photos';
  57.     public const ROUTES__FEEDS__CONTENT 'app.schoolnow.dashboard.default.feeds.content';
  58.     public const ROUTES__CREATE 'app.schoolnow.dashboard.default.create';
  59.     public const ROUTES__PREVIEW 'app.schoolnow.dashboard.default.preview';
  60.     public const ROUTES__UPDATE 'app.schoolnow.dashboard.default.update';
  61.     public const ROUTES__SOCIAL_ACCOUNTS 'app.schoolnow.dashboard.default.social_accounts';
  62.     public const ROUTES__DELETE 'app.schoolnow.dashboard.default.delete';
  63.     public const ROUTES__DELETE_PREVIEW 'app.schoolnow.dashboard.default.delete_preview';
  64.     public const ROUTES__DRAFTS__LIST 'app.schoolnow.dashboard.default.drafts.list';
  65.     public const ROUTES__DRAFTS__UPDATE 'app.schoolnow.dashboard.default.drafts.update';
  66.     public const ROUTES__DRAFTS__DELETE 'app.schoolnow.dashboard.default.drafts.delete';
  67.     public const ROUTES__PIN 'app.schoolnow.dashboard.default.pin';
  68.     public const ROUTES__CREATE_REVIEW 'app.schoolnow.dashboard.default.create_review';
  69.     public const ROUTES__UPDATE_REVIEW 'app.schoolnow.dashboard.default.update_review';
  70.     public const SEARCH_PAGING Pagination::PAGE_LIMIT;
  71.     private ContentSchoolProvider $contentSchoolProvider;
  72.     /**
  73.      * @param ContentSchoolProvider $contentSchoolProvider
  74.      */
  75.     public function __construct(
  76.         ContentSchoolProvider $contentSchoolProvider
  77.     ) {
  78.         $this->contentSchoolProvider $contentSchoolProvider;
  79.     }
  80.     /**
  81.      * @return Account
  82.      */
  83.     protected function getCurrentUser(): Account
  84.     {
  85.         $user $this->getUser();
  86.         if ( ! $user instanceof Account) {
  87.             throw new \LogicException();
  88.         }
  89.         return $user;
  90.     }
  91.     /**
  92.      * @param int $pagination
  93.      * @return DocHtmlView|RedirectResponse
  94.      *
  95.      * @Route(
  96.      *     "{pagination}",
  97.      *     name = self::ROUTES__MAIN,
  98.      *     requirements = {
  99.      *         "pagination" = "[1-9]\d*",
  100.      *     },
  101.      *     defaults = {
  102.      *         "pagination" = 0,
  103.      *     },
  104.      * )
  105.      */
  106.     public function mainAction(int $pagination)
  107.     {
  108.         // search form logic
  109.         $form $this->createForm(
  110.             FeedSearchForm::class,
  111.             $search = (new FeedSearch())
  112.                 ->setUser($this->getCurrentUser())
  113.                 ->setPast(true)
  114.                 ->setVisibility(ObjectInterface::VISIBILITIES__PUBLISHED)
  115.                 ->setBoosted(true)
  116.                 ->setDeep(true)
  117.         );
  118.         $result $this->doSearch(
  119.             AbstractContentEntry::class,
  120.             'feed',
  121.             $search,
  122.             $form,
  123.             $pagination,
  124.         );
  125.         if ($result instanceof Response) {
  126.             return $result;
  127.         }
  128.         // handle searching
  129.         // if not searching, inject the notifications hackery into the top of the feed...
  130.         // try to grab notifications if any when on the first page
  131.         $notifications = [];
  132.         if ($pagination === && ! (($form->isSubmitted() && $form->isValid())) && $profile $this->getProfile()) {
  133.             $notifications $this->getEntityManager()->getRepository(Broadcast::class)->findByProfileAfterTimestamp(
  134.                 $profile,
  135.                 DateTimeUtils::beforeNow('PT24H')
  136.             );
  137.         }
  138.         $result['notifications'] = $notifications;
  139.         $result['isGrantedShare'] = ! empty($this->contentSchoolProvider->getSchools());
  140.         $result['announcements']  = $this->getEntityManager()->getRepository(SystemAnnouncement::class)->findTenantAnnouncements(
  141.             $this->getGlobalContext()->getTenant()
  142.         );
  143.         return $this->html($result);
  144.     }
  145.     /**
  146.      * @return Profile|null
  147.      */
  148.     protected function getProfile(): ?Profile
  149.     {
  150.         // first attempt to find a profile based on one-roster id
  151.         // this is the "safest" way to poll the info...
  152.         $profile null;
  153.         if ($this->getCurrentUser()->getOneRosterId()) {
  154.             $profile $this->getEntityManager()->getRepository(Profile::class)->findOneBy([
  155.                 'onerosterId' => $this->getCurrentUser()->getOneRosterId(),
  156.             ]);
  157.         }
  158.         // if that lookup does not find anything, search based on user email
  159.         // TOOD: is this 100% safe? could somebody change their email to obtain info they shouldn't have?
  160.         if ( ! $profile) {
  161.             $recipient $this->getEntityManager()->getRepository(EmailRecipient::class)->findOneBy([
  162.                 'contact' => $this->getCurrentUser()->getEmail(),
  163.             ]);
  164.             if ($recipient) {
  165.                 $profiles $recipient->getProfilesAsArray();
  166.                 if ($profiles) {
  167.                     $profile $profiles[0];
  168.                 }
  169.             }
  170.         }
  171.         return $profile;
  172.     }
  173.     /**
  174.      * @param Request $request
  175.      * @param int $pagination
  176.      * @return AbstractHtmlView|RedirectResponse
  177.      *
  178.      * @Route(
  179.      *     "/feeds/notifications",
  180.      *     name = self::ROUTES__FEEDS__NOTIFICATIONS,
  181.      *     requirements = {
  182.      *         "pagination" = "[1-9]\d*",
  183.      *     },
  184.      *     defaults = {
  185.      *         "pagination" = 0,
  186.      *     },
  187.      * )
  188.      */
  189.     public function feedsNotificationsAction(Request $requestint $pagination)
  190.     {
  191.         // only search for things if there is a profile
  192.         $entries = [];
  193.         if ($profile $this->getProfile()) {
  194.             // querying
  195.             $entries $this->getEntityManager()->getRepository(Broadcast::class)->findByProfile(
  196.                 $profile,
  197.                 true,
  198.                 self::SEARCH_PAGING,
  199.                 Pagination::offset($paginationself::SEARCH_PAGING)
  200.             );
  201.             // determine if we are out of bounds on the pagination
  202.             if (Pagination::outOfBounds($entries$paginationself::SEARCH_PAGING)) {
  203.                 return $this->redirectToRoute(self::ROUTES__FEEDS__NOTIFICATIONSarray_merge(
  204.                     $request->query->all(),
  205.                     ['pagination' => Pagination::maxPage($entriesself::SEARCH_PAGING)]
  206.                 ));
  207.             }
  208.         }
  209.         return $this->html([
  210.             'pagination' => $profile Pagination::controller(
  211.                 $entries,
  212.                 count($entries),
  213.                 $pagination,
  214.                 self::SEARCH_PAGING
  215.             ) : null,
  216.             'profile' => $profile,
  217.             'feed' => $entries,
  218.             'isGrantedShare' => ! empty($this->contentSchoolProvider->getSchools()),
  219.         ]);
  220.     }
  221.     /**
  222.      * @param Request $request
  223.      * @param int $pagination
  224.      * @return AbstractHtmlView|RedirectResponse
  225.      *
  226.      * @Route(
  227.      *     "/feeds/calendar/{pagination}",
  228.      *     name = self::ROUTES__FEEDS__CALENDAR,
  229.      *     requirements = {
  230.      *         "pagination" = "[1-9]\d*",
  231.      *     },
  232.      *     defaults = {
  233.      *         "pagination" = 0,
  234.      *     },
  235.      * )
  236.      */
  237.     public function feedsCalendarAction(Request $requestint $pagination)
  238.     {
  239.         // querying
  240.         $entries $this->getEntityManager()->getRepository(ContentEventEntry::class)->findBySearch(
  241.             (new ContentEventSearch())
  242.                 ->setUser($this->getCurrentUser())
  243.                 ->setPast(true)
  244.                 ->setVisibility(ObjectInterface::VISIBILITIES__PUBLISHED)
  245.                 ->setDeep(false),
  246.             self::SEARCH_PAGING,
  247.             Pagination::offset($paginationself::SEARCH_PAGING)
  248.         );
  249.         // determine if we are out of bounds on the pagination
  250.         if (Pagination::outOfBounds($entries$paginationself::SEARCH_PAGING)) {
  251.             return $this->redirectToRoute(self::ROUTES__FEEDS__CALENDARarray_merge(
  252.                 $request->query->all(),
  253.                 ['pagination' => Pagination::maxPage($entriesself::SEARCH_PAGING)]
  254.             ));
  255.         }
  256.         return $this->html([
  257.             'pagination' => Pagination::controller(
  258.                 $entries,
  259.                 count($entries),
  260.                 $pagination,
  261.                 self::SEARCH_PAGING
  262.             ),
  263.             'feed' => $entries,
  264.             'isGrantedShare' => ! empty($this->contentSchoolProvider->getSchools()),
  265.         ]);
  266.     }
  267.     /**
  268.      * @param Request $request
  269.      * @param int $pagination
  270.      * @return AbstractHtmlView|RedirectResponse
  271.      *
  272.      * @Route(
  273.      *     "/feeds/photos/{pagination}",
  274.      *     name = self::ROUTES__FEEDS__PHOTOS,
  275.      *     requirements = {
  276.      *         "pagination" = "[1-9]\d*",
  277.      *     },
  278.      *     defaults = {
  279.      *         "pagination" = 0,
  280.      *     },
  281.      * )
  282.      */
  283.     public function feedsPhotosAction(Request $requestint $pagination)
  284.     {
  285.         // querying
  286.         $entries $this->getEntityManager()->getRepository(ContentGalleryEntry::class)->findBySearch(
  287.             (new FeedSearch())
  288.                 ->setUser($this->getCurrentUser())
  289.                 ->setPast(true)
  290.                 ->setVisibility(ObjectInterface::VISIBILITIES__PUBLISHED)
  291.                 ->setBoosted(true)
  292.                 ->setDeep(true),
  293.             self::SEARCH_PAGING,
  294.             Pagination::offset($paginationself::SEARCH_PAGING)
  295.         );
  296.         // determine if we are out of bounds on the pagination
  297.         if (Pagination::outOfBounds($entries$paginationself::SEARCH_PAGING)) {
  298.             return $this->redirectToRoute(self::ROUTES__FEEDS__CALENDARarray_merge(
  299.                 $request->query->all(),
  300.                 ['pagination' => Pagination::maxPage($entriesself::SEARCH_PAGING)]
  301.             ));
  302.         }
  303.         return $this->html([
  304.             'pagination' => Pagination::controller(
  305.                 $entries,
  306.                 count($entries),
  307.                 $pagination,
  308.                 self::SEARCH_PAGING
  309.             ),
  310.             'feed' => $entries,
  311.             'isGrantedShare' => ! empty($this->contentSchoolProvider->getSchools()),
  312.         ]);
  313.     }
  314.     /**
  315.      * @param int $pagination
  316.      * @return JsonView|Response
  317.      *
  318.      * @Route(
  319.      *     "/feeds/content/{pagination}",
  320.      *     name = self::ROUTES__FEEDS__CONTENT,
  321.      *     requirements = {
  322.      *         "pagination" = "[1-9]\d*",
  323.      *     },
  324.      *     defaults = {
  325.      *         "pagination" = 0,
  326.      *     },
  327.      * )
  328.      */
  329.     public function feedsContentAjaxAction(int $pagination)
  330.     {
  331.         $result $this->doSearch(
  332.             AbstractContentEntry::class,
  333.             'entries',
  334.             (new FeedSearch())
  335.                 ->setUser($this->getCurrentUser())
  336.                 ->setPast(true)
  337.                 ->setVisibility(ObjectInterface::VISIBILITIES__PUBLISHED)
  338.                 ->setBoosted(true)
  339.                 ->setDeep(true),
  340.             FeedSearchForm::class,
  341.             $pagination,
  342.         );
  343.         if ($result instanceof Response) {
  344.             return $result;
  345.         }
  346.         return $this->jsonView([
  347.             'pagination' => $result['pagination'],
  348.             'results' => array_map(
  349.                 function (object $item) {
  350.                     return $this->getMobileApiSerializer()->serialize(
  351.                         $item
  352.                     );
  353.                 },
  354.                 $result['entries']->getIterator()->getArrayCopy()
  355.             ),
  356.         ]);
  357.     }
  358.     /**
  359.      * @param Request $request
  360.      * @return AbstractHtmlView|RedirectResponse
  361.      *
  362.      * @Route(
  363.      *     "/create",
  364.      *     name = self::ROUTES__CREATE,
  365.      * )
  366.      */
  367.     public function createAction(Request $request)
  368.     {
  369.         $schools $this->contentSchoolProvider->getSchools();
  370.         if ( ! $schools) {
  371.             throw new \RuntimeException();
  372.         }
  373.         // HACK: try to find a passed in cms event
  374.         $event null;
  375.         if ($request->query->has('event')) {
  376.             $event $this->getEntityManager()->getRepository(EventProxy::class)->find(
  377.                 $request->query->get('event')
  378.             );
  379.             if ( ! $event) {
  380.                 throw new \RuntimeException();
  381.             }
  382.             $sch $schools[0];
  383.             foreach ($schools as $school) {
  384.                 if ($school->getDepartment() === $event->getContainer()) {
  385.                     $sch $school;
  386.                     break;
  387.                 }
  388.             }
  389.             $event = [
  390.                 'school' => $sch,
  391.                 'type' => 'event',
  392.                 'headline' => $event->getData()->getTitle(),
  393.                 'startsAt' => $event->getData()->getStartDate(),
  394.                 'stopsAt' => $event->getData()->getEndDate(),
  395.                 'html' => $event->getData()->getDescription(),
  396.             ];
  397.         }
  398.         // create the form
  399.         $form $this->createForm(
  400.             PostForm::class,
  401.             $event ?: [
  402.                 'school' => $schools[0],
  403.                 'startsAt' => $startsAt DateTimeUtils::now(),
  404.                 'stopsAt' => (clone $startsAt)->add(new \DateInterval('PT1H')),
  405.             ],
  406.             [
  407.                 'schools' => $schools,
  408.             ]
  409.         );
  410.         // if this is a post, we might have review data coming in
  411.         // attach that form so the submission handling deals with that too
  412.         if ($request->isMethod('POST')) {
  413.             $form->add('review'PostReviewForm::class);
  414.         }
  415.         // handle submission
  416.         if ($this->handleForm($form)) {
  417.             // we are always creating a new item, publishing is optional and is checked later
  418.             $object $this->getPostLogic()->init(
  419.                 $form->getData()['type'],
  420.                 $form->getData()
  421.             );
  422.             $pinned false;
  423.             if ($this->isGranted('app.feed.pin'))
  424.             {
  425.                 $pinned = (bool) $form->get('pinned')->getData();
  426.             }
  427.             // create the object and feed entry
  428.             $this->getContentManager()->createObject(
  429.                 $object,
  430.                 FeedManager::BOOSTING[ClassUtils::getClass($object)] ? FeedManager::FLAGS__BOOST__SET FeedManager::FLAGS__BOOST__UNSET,
  431.                 $pinned FeedManager::FLAGS__PIN__SET FeedManager::FLAGS__PIN__UNSET
  432.             );
  433.             // determine if we are publishing and handle it if so
  434.             if ($object->getScheduledAt() === null && $form->get('action')->getData() === 'publish') {
  435.                 $this->getContentManager()->publishObject(
  436.                     $object,
  437.                     FeedManager::BOOSTING[ClassUtils::getClass($object)] ? FeedManager::FLAGS__BOOST__SET FeedManager::FLAGS__BOOST__UNSET,
  438.                     $pinned FeedManager::FLAGS__PIN__SET FeedManager::FLAGS__PIN__UNSET
  439.                 );
  440.             }
  441.             $this->getLoggingService()->createLog($object);
  442.             return $this->jumpOrRedirectToRoute(self::ROUTES__MAIN);
  443.         }
  444.         return $this->html([
  445.             'form' => $form->createView(),
  446.         ]);
  447.     }
  448.     /**
  449.      * @param Request $request
  450.      * @param AbstractContentEntry $entry
  451.      * @return AbstractHtmlView|RedirectResponse
  452.      *
  453.      * @Route(
  454.      *     "/{entry}/update",
  455.      *     name = self::ROUTES__UPDATE,
  456.      *     requirements = {
  457.      *         "entry" = "%app.routing.regexes.ulid%",
  458.      *     },
  459.      * )
  460.      * @ParamConverter(
  461.      *     "entry",
  462.      *     class = AbstractContentEntry::class,
  463.      * )
  464.      */
  465.     public function updateAction(Request $requestAbstractContentEntry $entry)
  466.     {
  467.         // AUDIT
  468.         $this->denyAccessUnlessGranted(
  469.             [
  470.                 sprintf('campussuite.cms.container.%s.manage'$entry->getDepartment()->getType()),
  471.                 'campussuite.cms.module.manage',
  472.             ],
  473.             [$entry$entry->getDepartment()],
  474.         );
  475.         // create the form
  476.         $form $this->createForm(
  477.             ObjectForm::map($object $entry->getObject()),
  478.             $object,
  479.             [
  480.                 'boosting' => false,
  481.                 'pinning' => false,
  482.             ]
  483.         );
  484.         if ($request->isMethod('POST')) {
  485.             $form->add('review'PostReviewForm::class);
  486.         }
  487.         $pinned $entry->isPinned();
  488.         // handle submission
  489.         if ($this->handleForm($form)) {
  490.             if ($this->isGranted('app.feed.pin')) {
  491.                 $pinned = (bool) $form->get('pinned')->getData();
  492.             }
  493.             // TODO: PERMS: this was removed in the perms branch, was it supposed to be?
  494.             $object->setReview($form->get('review')->getData() ?? []);
  495.             // update the object
  496.             $this->getContentManager()->updateObject(
  497.                 $object,
  498.                 FeedManager::FLAGS__BOOST__PRESERVE,
  499.                 $pinned FeedManager::FLAGS__PIN__SET FeedManager::FLAGS__PIN__UNSET,
  500.             );
  501.             // determine if we are publishing and handle it if so
  502.             if ($object->getScheduledAt() === null && $form->get('action')->getData() === 'publish') {
  503.                 $this->getContentManager()->publishObject(
  504.                     $object,
  505.                     FeedManager::FLAGS__BOOST__PRESERVE,
  506.                     FeedManager::FLAGS__PIN__PRESERVE
  507.                 );
  508.             }
  509.             $this->getLoggingService()->createLog($object);
  510.             return $this->jumpOrRedirectToRoute(self::ROUTES__MAIN);
  511.         }
  512.         return $this->html([
  513.             'entry' => $entry,
  514.             'object' => $object,
  515.             'form' => $form->createView(),
  516.         ]);
  517.     }
  518.     /**
  519.      * @param Request $request
  520.      * @param AbstractContentEntry $entry
  521.      * @return AjaxHtmlView
  522.      *
  523.      * @Route(
  524.      *     "/{entry}/social-accounts",
  525.      *     name = self::ROUTES__SOCIAL_ACCOUNTS,
  526.      *     requirements = {
  527.      *         "entry" = "%app.routing.regexes.ulid%",
  528.      *     },
  529.      * )
  530.      * @ParamConverter(
  531.      *     "entry",
  532.      *     class = AbstractContentEntry::class,
  533.      * )
  534.      */
  535.     public function socialPostsAction(Request $requestAbstractContentEntry $entry): AjaxHtmlView
  536.     {
  537.         // AUDIT
  538.         $this->denyAccessUnlessGranted(
  539.             [
  540.                 sprintf('campussuite.cms.container.%s.manage'$entry->getDepartment()->getType()),
  541.                 'campussuite.cms.module.manage',
  542.             ],
  543.             [$entry$entry->getDepartment()],
  544.         );
  545.         // make sure it is ajax
  546.         if ( ! $request->isXmlHttpRequest()) {
  547.             throw new NotFoundHttpException();
  548.         }
  549.         // make sure we have an object to be working with from the feed entry
  550.         // this is where the social posting information is held
  551.         $object $entry->getObject();
  552.         if ( ! $object instanceof AbstractObject) {
  553.             throw new \LogicException();
  554.         }
  555.         // get a set of all the social accounts for the customer
  556.         // optimizing the array to be associative with the social identifier as the key
  557.         // NOTE: may be technically possible for collisions of identifiers between social services, but highly unlikely...
  558.         $socialAccounts $this->getEntityManager()->getRepository(SocialAccount::class)->findAll();
  559.         $socialAccounts array_combine(
  560.             array_map(
  561.                 static function (SocialAccount $socialAccount) {
  562.                     return $socialAccount->getIdentifier();
  563.                 },
  564.                 $socialAccounts,
  565.             ),
  566.             $socialAccounts,
  567.         );
  568.         return $this->ajax([
  569.             'socialAccounts' => $socialAccounts,
  570.             'socialPosts' => $object->getSocialPosts(),
  571.         ]);
  572.     }
  573.     /**
  574.      * @param Request $request
  575.      * @param AbstractEntry $entry
  576.      * @return AjaxHtmlView|JsonView
  577.      *
  578.      * @Route(
  579.      *     "/{entry}/delete",
  580.      *     name = self::ROUTES__DELETE,
  581.      *     requirements = {
  582.      *         "entry" = "%app.routing.regexes.ulid%",
  583.      *     },
  584.      * )
  585.      * @Route(
  586.      *     "/{entry}/delete-preview",
  587.      *     name = self::ROUTES__DELETE_PREVIEW,
  588.      *     requirements = {
  589.      *         "entry" = "%app.routing.regexes.ulid%",
  590.      *     },
  591.      * )
  592.      * @ParamConverter(
  593.      *     "entry",
  594.      *     class = AbstractContentEntry::class,
  595.      * )
  596.      */
  597.     public function deleteAction(Request $requestAbstractContentEntry $entry)
  598.     {
  599.         // AUDIT
  600.         $this->denyAccessUnlessGranted(
  601.             [
  602.                 sprintf('campussuite.cms.container.%s.manage'$entry->getDepartment()->getType()),
  603.                 'campussuite.cms.module.manage',
  604.             ],
  605.             [$entry$entry->getDepartment()],
  606.         );
  607.         // make sure it is ajax
  608.         if ( ! $request->isXmlHttpRequest()) {
  609.             throw new NotFoundHttpException();
  610.         }
  611.         // make a form for submission purposes
  612.         $form $this->createForm(DummyForm::class);
  613.         // handle submission
  614.         if ($this->handleForm($form)) {
  615.             // delete the entry
  616.             $object $entry->getObject();
  617.             $id $entry->getId();
  618.             $this->getEntityManager()->transactional(
  619.                 function () use ($entry$object) {
  620.                     $this->getFeedManager()->delete($entry);
  621.                     if ($object) {
  622.                         $this->getContentManager()->deleteObject($object);
  623.                     }
  624.                 }
  625.             );
  626.             $this->getLoggingService()->createLog($object$id);
  627.             // send back redirect
  628.             return $this->jsonView([
  629.                 'redirect' => true,
  630.             ]);
  631.         }
  632.         return $this->ajax([
  633.             'entry' => $entry,
  634.             'form' => $form->createView(),
  635.         ]);
  636.     }
  637.     /**
  638.      * @param string $entry
  639.      * @return AjaxHtmlView
  640.      *
  641.      * @Route(
  642.      *     "/{entry}/preview",
  643.      *     name = self::ROUTES__PREVIEW,
  644.      *     requirements = {
  645.      *         "entry" = "%app.routing.regexes.ulid%",
  646.      *     },
  647.      * )
  648.      */
  649.     public function previewAction(string $entry): AjaxHtmlView
  650.     {
  651.         $entity $this->getEntityManager()->getRepository(AbstractContentEntry::class)->find($entry);
  652.         if ( ! $entity) {
  653.             $entity $this->getEntityManager()->getRepository(Broadcast::class)->find($entry);
  654.         }
  655.         if ( ! $entity) {
  656.             throw new NotFoundHttpException();
  657.         }
  658.         return $this->ajax([
  659.             'entry' => $entity,
  660.         ]);
  661.     }
  662.     /**
  663.      * @param Request $request
  664.      * @param int $pagination
  665.      * @return AbstractHtmlView|RedirectResponse
  666.      *
  667.      * @Route(
  668.      *     "/drafts/list",
  669.      *     name = self::ROUTES__DRAFTS__LIST,
  670.      * )
  671.      *
  672.      * @Route(
  673.      *     "/drafts/list/{pagination}",
  674.      *     name = self::ROUTES__DRAFTS__LIST,
  675.      *     requirements = {
  676.      *         "pagination" = "[1-9]\d*",
  677.      *     },
  678.      *     defaults = {
  679.      *         "pagination" = 0,
  680.      *     },
  681.      * )
  682.      */
  683.     public function draftsListAction(Request $requestint $pagination)
  684.     {
  685.         // search form logic
  686.         $this->handleSearch(
  687.             $form $this->createForm(
  688.                 FeedSearchForm::class,
  689.                 $search = (new FeedSearch())
  690.                     ->setUser($this->getCurrentUser())
  691.                     ->setPast(true)
  692.                     ->setCutoff(DateTimeUtils::max())
  693.                     ->setVisibility(ObjectInterface::VISIBILITIES__UNPUBLISHED)
  694.             ),
  695.         );
  696.         // querying
  697.         $drafts $this->getEntityManager()->getRepository(AbstractContentEntry::class)->findBySearch(
  698.             $search,
  699.             self::SEARCH_PAGING.
  700.             Pagination::offset($paginationself::SEARCH_PAGING),
  701.         );
  702.         // determine if we are out of bounds on the pagination
  703.         if (Pagination::outOfBounds($drafts$paginationself::SEARCH_PAGING)) {
  704.             return $this->redirectToRoute(self::ROUTES__FEEDS__CALENDARarray_merge(
  705.                 $request->query->all(),
  706.                 ['pagination' => Pagination::maxPage($draftsself::SEARCH_PAGING)]
  707.             ));
  708.         }
  709.         return $this->html([
  710.             'pagination' => Pagination::controller(
  711.                 $drafts,
  712.                 count($drafts),
  713.                 $pagination,
  714.                 self::SEARCH_PAGING
  715.             ),
  716.             'form' => $form->createView(),
  717.             'drafts' => $drafts,
  718.             'isGrantedShare' => ! empty($this->contentSchoolProvider->getSchools()),
  719.         ]);
  720.     }
  721.     /**
  722.      * @return AbstractHtmlView|RedirectResponse
  723.      *
  724.      * @Route(
  725.      *     "/drafts/{draft}/update",
  726.      *     name = self::ROUTES__DRAFTS__UPDATE,
  727.      *     requirements = {
  728.      *         "draft" = "%app.routing.regexes.ulid%",
  729.      *     },
  730.      * )
  731.      * @ParamConverter(
  732.      *     "draft",
  733.      *     class = AbstractContentEntry::class,
  734.      * )
  735.      */
  736.     public function draftsUpdateAction(AbstractContentEntry $draft)
  737.     {
  738.         // AUDIT
  739.         $this->denyAccessUnlessGranted(
  740.             [
  741.                 sprintf('campussuite.cms.container.%s.manage'$draft->getDepartment()->getType()),
  742.                 'campussuite.cms.module.manage',
  743.             ],
  744.             [$draft$draft->getDepartment()],
  745.         );
  746.         // make sure the entry is not published
  747.         if ($draft->isPublished()) {
  748.             throw new \RuntimeException();
  749.         }
  750.         // create the form
  751.         $form $this->createForm(PostForm::class);
  752.         // handle submission
  753.         if ($this->handleForm($form)) {
  754.             // we are always creating a new item, publishing is optional and is checked later
  755.             $object $this->getPostLogic()->init(
  756.                 $form->getData()['type'],
  757.                 $form->getData()
  758.             );
  759.             // determine if we are publishing and handle it if so
  760.             if ($form->get('action')->getData() === 'publish') {
  761.                 $this->getContentManager()->publishDraft($object);
  762.             }
  763.             $this->getLoggingService()->createLog($object);
  764.             return $this->jumpOrRedirectToRoute(self::ROUTES__MAIN);
  765.         }
  766.         return $this->html([
  767.             'form' => $form->createView(),
  768.         ]);
  769.     }
  770.     /**
  771.      * @param Request $request
  772.      * @param AbstractContentEntry $draft
  773.      * @return AjaxHtmlView|JsonView
  774.      *
  775.      * @Route(
  776.      *     "/drafts/{draft}/delete",
  777.      *     name = self::ROUTES__DRAFTS__DELETE,
  778.      *     requirements = {
  779.      *         "draft" = "%app.routing.regexes.ulid%",
  780.      *     },
  781.      * )
  782.      * @ParamConverter(
  783.      *     "draft",
  784.      *     class = AbstractContentEntry::class,
  785.      * )
  786.      */
  787.     public function draftDeleteAction(Request $requestAbstractContentEntry $draft)
  788.     {
  789.         // AUDIT
  790.         $this->denyAccessUnlessGranted(
  791.             [
  792.                 sprintf('campussuite.cms.container.%s.manage'$draft->getDepartment()->getType()),
  793.                 'campussuite.cms.module.manage',
  794.             ],
  795.             [$draft$draft->getDepartment()],
  796.         );
  797.         // make sure it is ajax
  798.         if ( ! $request->isXmlHttpRequest()) {
  799.             throw new NotFoundHttpException();
  800.         }
  801.         // make sure the entry is not published
  802.         if ($draft->isPublished()) {
  803.             throw new \RuntimeException();
  804.         }
  805.         // make a form for submission purposes
  806.         $form $this->createForm(DummyForm::class);
  807.         // handle submission
  808.         if ($this->handleForm($form)) {
  809.             // delete the entry
  810.             $object $draft->getObject();
  811.             $id $object $object->getId() : null;
  812.             $this->getEntityManager()->transactional(
  813.                 function () use ($draft$object) {
  814.                     $this->getFeedManager()->delete($draft);
  815.                     if ($object) {
  816.                         $this->getContentManager()->deleteObject($object);
  817.                     }
  818.                 }
  819.             );
  820.             if ($id) {
  821.                 $this->getLoggingService()->createLog($object$id);
  822.             }
  823.             // send back redirect
  824.             return $this->jsonView([
  825.                 'redirect' => true,
  826.             ]);
  827.         }
  828.         return $this->ajax([
  829.             'draft' => $draft,
  830.             'form' => $form->createView(),
  831.         ]);
  832.     }
  833.     /**
  834.      * @param Request $request
  835.      * @param AbstractContentEntry $entry
  836.      * @return JsonView
  837.      *
  838.      * @Route(
  839.      *     "/pin/{entry}",
  840.      *     name = self::ROUTES__PIN,
  841.      *     methods = {"POST"},
  842.      *     requirements = {
  843.      *         "entry" = "%app.routing.regexes.ulid%",
  844.      *     },
  845.      * )
  846.      * @ParamConverter(
  847.      *     "entry",
  848.      *     class = AbstractContentEntry::class,
  849.      * )
  850.      */
  851.     public function pinAction(Request $requestAbstractContentEntry $entry): JsonView
  852.     {
  853.         // AUDIT
  854. //        $this->denyAccessUnlessGranted(
  855. //            [
  856. //                sprintf('campussuite.cms.container.%s.manage', $entry->getDepartment()->getType()),
  857. //                'campussuite.cms.module.manage',
  858. //            ],
  859. //            [$entry, $entry->getDepartment()],
  860. //        );
  861.         $this->denyAccessUnlessGranted(
  862.             'app.feed.pin',
  863.             [$entry$entry->getDepartment()]
  864.         );
  865.         // make sure it is ajax
  866.         if ( ! $request->isXmlHttpRequest()) {
  867.             throw new NotFoundHttpException();
  868.         }
  869.         // flip the pinned state
  870.         $this->getEntityManager()->save(
  871.             $entry
  872.                 ->setPinnedAt(
  873.                     $entry->isPinned()
  874.                         ? null
  875.                         DateTimeUtils::now()
  876.                 )
  877.                 ->setBoosted($entry->isPinned() || ! $entry instanceof ContentEventEntry)
  878.         );
  879.         $this->getLoggingService()->createLog($entry->getObject());
  880.         return $this->jsonView([
  881.             'entry' => $entry->getId(),
  882.             'pinned' => $entry->isPinned(),
  883.         ]);
  884.     }
  885.     /**
  886.      * @param Request $request
  887.      * @param AbstractContentEntry|null $entry
  888.      * @return AjaxHtmlView
  889.      *
  890.      * @Route(
  891.      *     "/create/review",
  892.      *     name = self::ROUTES__CREATE_REVIEW,
  893.      *     methods = {"POST"},
  894.      * )
  895.      * @Route(
  896.      *     "/{entry}/update/review",
  897.      *     name = self::ROUTES__UPDATE_REVIEW,
  898.      *     methods = {"POST"},
  899.      *     requirements = {
  900.      *         "entry" = "%app.routing.regexes.ulid%",
  901.      *     },
  902.      * )
  903.      * @ParamConverter(
  904.      *     "entry",
  905.      *     class = AbstractContentEntry::class,
  906.      * )
  907.      */
  908.     public function reviewAction(Request $request, ?AbstractContentEntry $entry null): AjaxHtmlView
  909.     {
  910.         // make sure it is ajax
  911.         if ( ! $request->isXmlHttpRequest()) {
  912.             throw new NotFoundHttpException();
  913.         }
  914.         $formData $request->request->get('post_form');
  915.         // resolve the school
  916.         if ($entry && ($object $entry->getObject())) {
  917.             // AUDIT
  918.             $this->denyAccessUnlessGranted(
  919.                 [
  920.                     sprintf('campussuite.cms.container.%s.manage'$entry->getDepartment()->getType()),
  921.                     'campussuite.cms.module.manage',
  922.                 ],
  923.                 [$entry$entry->getDepartment()],
  924.             );
  925.             $school $this->getResolverManager()->getSchoolResolver()->resolveSchoolByDepartment(
  926.                 $object->getDepartment(),
  927.             );
  928.             $mainForm $this->createForm(
  929.                 ObjectForm::map($object),
  930.                 $object,
  931.                 [
  932.                     'boosting' => false,
  933.                     'pinning' => false,
  934.                 ]
  935.             );
  936.             $formName $mainForm->getName();
  937.             // submit "new" form data
  938.             $mainForm->submit($request->request->get($formName));
  939.             $reviewData = [
  940.                 'review' =>  $object->getReview()
  941.             ];
  942.             $isEventType $object instanceof EventObject;
  943.         } else if ( ! empty($formData['school'])) {
  944.             $school $this->getEntityManager()->getRepository(School::class)->find(
  945.                 (int)$formData['school']
  946.             );
  947.             if ( ! $school) {
  948.                 throw new \RuntimeException();
  949.             }
  950.             // AUDIT
  951.             $cls AbstractEntry::TYPE_CLASSES[$formData['type']];
  952.             $this->denyAccessUnlessGranted(
  953.                 [
  954.                     sprintf('campussuite.cms.container.%s.manage'$school->getDepartment()->getType()),
  955.                     'campussuite.cms.module.manage',
  956.                 ],
  957.                 [new $cls(), $school->getDepartment()],
  958.             );
  959.             $formName 'post_form';
  960.             $mainForm $this->createForm(
  961.                 PostForm::class,
  962.                 null,
  963.                 [
  964.                     'schools' => [$school],
  965.                 ]
  966.             );
  967.             $mainForm->submit($formData);
  968.             $reviewData = [
  969.                 'review' => [
  970.                     'scheduled' => false,
  971.                     'channels' => ChannelEnum::WEBSITE ChannelEnum::APP,
  972.                     'websiteLevel' => AlertData::LEVELS__INFORMATIVE,
  973.                     'websiteBehavior' => AlertData::BEHAVIORS__NONE
  974.                 ],
  975.             ];
  976.             $isEventType = isset($formData['type']) && $formData['type'] === AbstractEntry::TYPES__EVENT;
  977.         } else {
  978.             throw new \LogicException();
  979.         }
  980.         if ( ! $school) {
  981.             throw new \RuntimeException();
  982.         }
  983.         $form $this
  984.             ->createNamedBuilder($formName$reviewData)
  985.             ->add('review'PostReviewForm::class)
  986.             ->getForm();
  987.         return $this->ajax([
  988.             'school' => $school,
  989.             'entry' => $entry,
  990.             'isEventType' => $isEventType,
  991.             'form' => $form->createView(),
  992.             'mainForm' => $mainForm->createView(),
  993.             'mainFormData' => $mainForm->getData(),
  994.         ]);
  995.     }
  996.     /**
  997.      * @return PostLogic|object
  998.      */
  999.     private function getPostLogic(): PostLogic
  1000.     {
  1001.         return $this->get(__METHOD__);
  1002.     }
  1003. }