<?php
namespace Cms\Modules\CalendarBundle\Service;
use App\Doctrine\Repository\Content\Events\Event\EventObjectRepository;
use App\Entity\Content\Events\Event\EventObject;
use Cms\AssetsBundle\Model\Structure\TitleStructure;
use Cms\ContainerBundle\Doctrine\ContainerRepository;
use Cms\ContainerBundle\Entity\Container;
use Cms\CoreBundle\Model\Search\AbstractSearcher;
use Cms\CoreBundle\Util\DateTimeUtils;
use Cms\FrontendBundle\Model\FrontendGlobals;
use Cms\ModuleBundle\Entity\ModuleEntity;
use Cms\ModuleBundle\Entity\Proxy;
use Cms\ModuleBundle\Exception\ModuleConfigException;
use Cms\ModuleBundle\Model\ModuleConfig;
use Cms\ModuleBundle\Model\Traits\FrontendActions\CascadingSharesTrait;
use Cms\ModuleBundle\Model\Traits\FrontendActions\FeedSecurityTrait;
use Cms\ModuleBundle\Model\Traits\FrontendActions\FeedTrait;
use Cms\ModuleBundle\Model\Traits\FrontendActions\IcalFeedTrait;
use Cms\ModuleBundle\Model\Traits\FrontendActions\SocialMetaTrait;
use Cms\Modules\CalendarBundle\Doctrine\Event\EventProxyRepository;
use Cms\Modules\CalendarBundle\Entity\Event\EventProxy;
use Cms\Modules\CalendarBundle\Entity\ModuleSettings;
use Cms\Modules\CalendarBundle\Model\CalendarBuilder;
use Cms\Modules\CalendarBundle\Model\CalendarBuilder\Event;
use Cms\Modules\CalendarBundle\Service\Search\EventSearcher;
use Cms\Modules\CalendarBundle\Service\Social\ShareService;
use Cms\Modules\CalendarBundle\Service\Social\Types\Google;
use Cms\Modules\CalendarBundle\Service\Social\Types\Microsoft;
use Cms\Widgets\Html\Html;
use DateTime;
use Doctrine\Common\Util\ClassUtils;
use Platform\SecurityBundle\Model\OAuth\OAuthOptions;
use Platform\SecurityBundle\Service\OAuth\OAuthProviderService;
use Platform\SecurityBundle\Service\OAuth\Providers\MicrosoftOAuthProvider;
use Platform\SecurityBundle\Util\OAuth\ServiceFactory\MicrosoftOAuth2ServiceFactory;
use SplObjectStorage;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Laminas\Uri\Uri;
/**
* Class CalendarModuleConfig
* @package Cms\Modules\CalendarBundle\Service
*/
final class CalendarModuleConfig extends ModuleConfig
{
const NAME = 'Calendar';
const COOKIES__VIEW_MODE = 'campussuite__cms__modules__calendar__view_mode';
use IcalFeedTrait;
use FeedTrait;
use FeedSecurityTrait;
use SocialMetaTrait;
use CascadingSharesTrait;
/**
* @return EventProxyRepository
*/
private function getProxyRepository(): EventProxyRepository
{
return $this->getEntityManager()->getRepository(EventProxy::class);
}
/**
* @return EventObjectRepository
*/
private function getObjectRepository(): EventObjectRepository
{
return $this->getEntityManager()->getRepository(EventObject::class);
}
/**
* @param FrontendGlobals $globals
* @return bool
*/
private function isGraphicalView(FrontendGlobals $globals)
{
$viewFromCookies = $globals->getRequest()->cookies->get(self::COOKIES__VIEW_MODE);
if ($viewFromCookies) {
return $viewFromCookies == ModuleSettings::VIEW_MODE_GRAPHICAL;
}
/** @var ModuleSettings $moduleSettings */
$moduleSettings = $globals->getModuleSettings();
return $moduleSettings->getViewMode() == ModuleSettings::VIEW_MODE_GRAPHICAL;
}
/**
* {@inheritdoc}
* @param EventProxy $proxy
*/
public function urlify(Proxy $proxy = null)
{
if ($proxy === null) {
return '/' . $this->key();
}
return sprintf(
'/%s/%s/%s',
$this->key(),
$proxy->getId(),
$proxy->getData()->getSlug()
);
}
/**
* {@inheritdoc}
*/
public function frontend(FrontendGlobals $globals)
{
$extra = $globals->getExtra();
//override inner layout from module settings
/** @var ModuleSettings $settings */
$settings = $globals->getModuleSettings();
if (empty($settings->getInnerLayout()) === false) {
$globals->setInnerLayout(
$settings->getInnerLayout(),
true
);
}
// Maybe is there a better way to set/get departments data via a method
// in the frontend globals object (e.g. getQuery() the same way as getExtra())
// instead of getting query data from the request? */
$departments = $globals->getRequest()->query->has('departments')
? explode(',', $globals->getRequest()->query->get('departments'))
: [];
switch (true) {
case preg_match('/^\\/$/', $extra, $matches):
return $this->frontendList($globals);
// SCHOOLNOW: modify regex to allow for ulid values
case preg_match('/^\\/details\\/([0-9]+|[0-7][0-9a-hA-HjJkKmMnNp-tP-Tv-zV-Z]{25})$/', $extra, $matches):
return $this->ajaxDetailsView($globals, array(
'event' => $matches[1]
));
case preg_match('/^\\/monthly\\/([0-9]{4})\\/(0[1-9]|1[0-2])$/', $extra, $matches):
return $this->frontendMonthView($globals, array(
'year' => $matches[1],
'month' => $matches[2],
'departments' => $departments,
));
// SCHOOLNOW: modify regex to allow for ulid values
case preg_match('/^\\/([0-9]+|[0-7][0-9a-hA-HjJkKmMnNp-tP-Tv-zV-Z]{25})\\/([-a-zA-Z0-9]+)$/', $extra, $matches):
return $this->frontendView($globals, array(
'id' => $matches[1],
'slug' => $matches[2],
));
case preg_match('/^\\/monthly\\/([0-9]{4})\\/(0[1-9]|1[0-2])\\/print$/', $extra, $matches):
return $this->frontendMonthView($globals, array(
'year' => $matches[1],
'month' => $matches[2],
'departments' => $departments,
), true);
case preg_match('/\\/print$/', $extra):
$now = DateTimeUtils::current();
return $this->frontendMonthView(
$globals,
array(
'year' => $now->format('Y'),
'month' => $now->format('m'),
'departments' => $departments,
),
true
);
case $this->isPrivateFeed($globals->getContainer(), $this->name()):
return $this->loginRedirect($globals->getRequest());
case preg_match('/\\/subscribe$/', $extra):
return $this->frontendSubscribe($globals);
case preg_match('/\\/feed\\/ical(?:\\.ics)?$/', $extra):
return $this->icalFeed(
$this->getFeedBuilder(),
$globals,
array(
'departments' => $departments,
)
);
case preg_match('/^\\/download\\/(\\d+)\\/ical$/', $extra, $matches):
return $this->icalFeed(
$this->getFeedBuilder(),
$globals,
array(
'event' => $matches[1]
)
);
case preg_match('/^\\/add\\/(\\d+)$/', $extra, $matches):
return $this->addToCalendar(
$globals,
array(
'event' => $matches[1],
'provider' => $globals->getRequest()->query->get('provider')
)
);
case preg_match('/^\\/share\\/(\\d+)$/', $extra, $matches):
return $this->shareTo(
$matches[1],
$globals
);
default:
throw new NotFoundHttpException();
}
}
/**
* @param FrontendGlobals $globals
* @param array $params
* @param bool $print
* @return Response|array
* @throws ModuleConfigException
*/
private function frontendMonthView(FrontendGlobals $globals, array $params, $print = false)
{
// extract params
$year = intval($params['year']);
$month = intval($params['month']);
// get selected departments from the query search string
$selectedDepsIds = array_key_exists('departments', $params) ? array_filter($params['departments']) : [];
// generate the dates, based on locale stuff
$start = DateTimeUtils::make(
sprintf(
'%s-%s-01 00:00:00',
$year,
$month
),
null,
$this->getLocaleManager()->effectiveTimezone()
);
// generate some important dates based on the input
$monthStart = DateTimeUtils::firstOfMonth($start);
$monthEnd = DateTimeUtils::lastOfMonth($start);
$monthBefore = DateTimeUtils::firstOfMonth($start)->modify('-1 month');
$monthAfter = DateTimeUtils::firstOfMonth($start)->modify('+1 month');
if ($print === true) {
// after should be the full number of months we want to cover
// end is one less than that
$monthEnd = DateTimeUtils::lastOfMonth(DateTimeUtils::firstOfMonth($start)->modify('+0 month'));
$monthAfter = DateTimeUtils::firstOfMonth($start)->modify('+1 month');
}
// make sure that we don't extend beyond a range
// this ensures that the calendar stuff does not loop indefinitely to past or future months
$min = DateTimeUtils::beforeCurrent('P8Y');
$max = DateTimeUtils::afterCurrent('P2Y');
if ($start < $min || $start > $max) {
throw new NotFoundHttpException();
}
// set title
if (method_exists($globals->getModuleSettings(), 'getTitle')
&& ! empty($globals->getModuleSettings()->getTitle())
) {
$globals->getAssetsOrganizer()->getTitles()
->add(new TitleStructure($globals->getModuleSettings()->getTitle()))
->add(new TitleStructure($monthStart->format('F Y')));
} else {
$globals->getAssetsOrganizer()->getTitles()
->add(new TitleStructure($globals->getModule()->name()))
->add(new TitleStructure($monthStart->format('F Y')))
->add(new TitleStructure($globals->getContainer()->getName()));
}
// get the module settings for this department
/** @var ModuleSettings $settings */
$settings = $this->loadSettings($globals->getContainer());
/** @var ContainerRepository $containerRepo */
$containerRepo = $this->getEntityManager()
->getRepository(
Container::class
);
// get all the departments that need to show in the calendar selections
if ( ! empty($settings->getSelections())) {
$departments = $containerRepo->findBy(
array(
'id' => $settings->getSelections(),
),
array(
'name' => 'ASC',
)
);
} else {
$departments = $containerRepo->findModuleViewableHierarchy(
$globals->getContainers()[count($globals->getContainers()) - 1],
$this->key()
);
}
$shares = $containerRepo->findBy(
array(
'id' => $this->flattenContainers($globals->getContainer(), ModuleSettings::class),
),
array(
'name' => 'ASC',
)
);
$expansions = new SplObjectStorage();
if (count($selectedDepsIds)) {
$queryDepartments = $selectedDepartments = $containerRepo->searchContainers($selectedDepsIds);
$expansionDepartments = [];
foreach ($selectedDepartments as $selectedDepartment) {
if ($selectedDepartment === $globals->getContainer()) {
continue;
}
$expandedDepartment = $this->flattenContainers($selectedDepartment, ModuleSettings::class);
if ( ! is_array($expandedDepartment)) {
continue;
}
array_shift($expandedDepartment);
$expandedDepartments = $containerRepo->findBy(
array(
'id' => $expandedDepartment,
),
array(
'name' => 'ASC',
)
);
$expansionDepartments = array_merge(
$expansionDepartments,
$expandedDepartments
);
foreach ($expandedDepartments as $dept) {
if ( ! in_array($dept, $selectedDepartments, true)) {
$expansions->offsetSet($dept, $selectedDepartment);
}
}
}
if (in_array($globals->getContainer(), $queryDepartments, true)) {
$queryDepartments = array_values(array_filter(array_unique(array_merge(
$queryDepartments,
$shares
))));
}
$queryDepartments = array_values(array_filter(array_unique(array_merge(
$queryDepartments,
$expansionDepartments
))));
} else {
// get the departments that are already showing in the calendar
$selectedDepartments = array(
$globals->getContainer(),
);
$queryDepartments = $shares;
}
// get the diff of the calendars that are yet to be selected from the possible options
$unselectedDepartments = array_diff($departments, $selectedDepartments);
// find the items
/** @var array|EventProxy[] $items */
$items = $this->getProxyRepository()->createQueryBuilder('items')
->andWhere('items.container IN (:container)')
->setParameter('container', $queryDepartments)
->andWhere('((items.data_startDate >= :monthStart AND items.data_startDate < :monthAfter)
OR (items.data_endDate > :monthStart AND items.data_endDate < :monthAfter)
OR (items.data_startDate < :monthStart AND items.data_endDate >= :monthAfter))')
// TODO: these values are in local times, do we need to convert to utc before using in query?
->setParameter('monthStart', $monthStart)
->setParameter('monthAfter', $monthAfter)
->addOrderBy('items.data_startDate', 'ASC')
->addOrderBy('items.data_endDate', 'ASC')
->getQuery()
->getResult();
// create a calendar builder to help us process and display the events easier
$builder = (new CalendarBuilder($start, array(
'container' => $globals->getContainer(),
'shares' => (in_array($globals->getContainer(), $selectedDepartments, true)) ? array_values(array_diff($shares, $selectedDepartments)) : [],
'expansions' => $expansions,
)))
->mergeEvents($items);
// get today's date
$today = DateTimeUtils::today(
$this->getLocaleManager()->effectiveTimezone(),
false
);
// if doing print view, show the print ui
if ($print) {
$content = $this->getTwig()->render(
$this->getDatabaseLoader()->determine(
$globals->getTheme(),
sprintf(
'/modules/%s/Print/build/tpl.html.twig',
$globals->getModule()->name()
)
),
array(
'_globals' => $globals,
'today' => $today,
'monthStart' => $monthStart,
'monthEnd' => $monthEnd,
'monthBefore' => ($monthBefore >= $min) ? $monthBefore : null,
'monthAfter' => ($monthAfter <= $max) ? $monthAfter : null,
'builder' => $builder,
'departments' => $departments,
'selectedDepartments' => $selectedDepartments,
'unselectedDepartments' => $unselectedDepartments,
'sharedDepartments' => $shares,
)
);
return new Response($content);
}
// render content from twig file
$content = $this->getTwig()->render(
$this->getDatabaseLoader()->determine(
$globals->getTheme(),
sprintf(
'/modules/%s/Graphical/build/tpl.html.twig',
$globals->getModule()->name()
)
),
array(
'_globals' => $globals,
'_share' => $this->getShareService(),
'today' => $today,
'monthStart' => $monthStart,
'monthEnd' => $monthEnd,
'monthBefore' => ($monthBefore >= $min) ? $monthBefore : null,
'monthAfter' => ($monthAfter <= $max) ? $monthAfter : null,
'builder' => $builder,
'departments' => $departments,
'selectedDepartments' => $selectedDepartments,
'unselectedDepartments' => $unselectedDepartments,
'sharedDepartments' => $shares,
'isGraphicalView' => $this->isGraphicalView($globals),
)
);
// done
return array(
array(
'content' => array(
(new Html())
->setContent($content),
),
),
);
}
/**
* @param FrontendGlobals $globals
* @return RedirectResponse
*/
private function frontendList(FrontendGlobals $globals)
{
// HACK: jump to graphical view
$now = DateTimeUtils::current();
return new RedirectResponse(sprintf(
'%s://%s/%s/monthly/%s/%s',
$globals->getRequest()->getScheme(),
$globals->getRequest()->getHost(),
trim($globals->getPath(), '/'),
$now->format('Y'),
$now->format('m')
));
}
/**
* @param FrontendGlobals $globals
* @param array $params
* @return array|RedirectResponse
* @throws ModuleConfigException
*/
private function frontendView(FrontendGlobals $globals, array $params)
{
// need to grab only the item requested
/** @var EventProxyRepository $repo */
$repo = $this->getEntityManager()->getRepository(EventProxy::class);
/** @var EventProxy|null $item */
$item = $repo->find($params['id']);
// check for item, if none, throw 404
if ($item === null) {
throw new NotFoundHttpException();
}
// set the thing on the globals
$globals->setThing($item);
// handle non-proxy preview
if ($globals->getThingOverride() instanceof ModuleEntity) {
$globals->setThing($item->setData(
$globals->getThingOverride()->getData()
));
}
// set social meta tags
$this->setSocialMeta(
$globals,
$item->getData()->getSocialMetadata()
);
//set title
$globals->getAssetsOrganizer()->getTitles()
->add(new TitleStructure($item->getData()->getTitle()))
->add(new TitleStructure(
$this->getFormattedDatePeriod(
$item->getData()->getStartDate(),
$item->getData()->getEndDate(),
$item->getData()->getAllDay()
)
))
->add(new TitleStructure($globals->getContainer()->getName()));
// check slugs, and if incorrect, redirect to proper url with slug
if ($item->getData()->getSlug() !== $params['slug']) {
return new RedirectResponse(sprintf(
'%s/%s/%s/%s',
$globals->pathPrefix(),
$globals->getModule()->key(),
$item->getId(),
$item->getData()->getSlug()
));
}
// generate the dates, based on locale stuff
$start = DateTimeUtils::changeTimezone(
$item->getData()->getStartDate(),
$this->getLocaleManager()->effectiveTimezone()
);
// render the content
$content = $this->getTwig()->render(
$this->getDatabaseLoader()->determine(
$globals->getTheme(),
sprintf(
'/modules/%s/View/build/tpl.html.twig',
$globals->getModule()->name()
)
),
array(
'_globals' => $globals,
'_share' => $this->getShareService(),
'event' => new Event(
array(
'timezone' => $start->getTimezone(),
'start' => DateTimeUtils::firstOfMonth($start),
'end' => DateTimeUtils::lastOfMonth($start),
'container' => $globals->getContainer(),
'shares' => $this->getEntityManager()
->getRepository(ClassUtils::getClass($globals->getContainer()))
->findBy(
array(
'id' => $this->flattenContainers($globals->getContainer(), ModuleSettings::class),
),
array(
'name' => 'ASC',
)
)
,
),
$item
)
)
);
// pass back as we need it
return array(
array(
'content' => array(
(new Html())
->setContent($content),
),
),
);
}
/**
* {@inheritdoc}
*/
public function types()
{
return array(EventProxy::TYPE);
}
/**
* {@inheritdoc}
*/
public function typesRoots()
{
return array(EventProxy::TYPE);
}
/**
* {@inheritdoc}
*/
public function typesChildren($type)
{
return [];
}
/**
* {@inheritdoc}
*/
public function collectionClass($classname)
{
}
/**
* @param DateTime $eventStartDate
* @param DateTime|null $eventEndDate
* @param bool $allDay
* @return string
*/
private function getFormattedDatePeriod(
DateTime $eventStartDate,
DateTime $eventEndDate = null,
bool $allDay = false
) {
$timezone = DateTimeUtils::timezone($this->getLocaleManager()->effectiveTimezone());
$startDate = clone $eventStartDate;
$startDate->setTimezone($timezone);
if (empty($eventEndDate)) {
// example: May 23, 2017
$dateString = $startDate->format('F j, Y');
} else {
$endDate = clone $eventEndDate;
$endDate->setTimezone($timezone);
// For all day multi day events, the last day ends the next day, so we have to subtract one day
if ($allDay) {
$endDate = $endDate
->sub(new \DateInterval('P1D'));
}
$sameYear = ($startDate->format('Y') === $endDate->format('Y'));
$sameMonth = ($startDate->format('m') === $endDate->format('m'));
$sameDay = ($startDate->format('d') === $endDate->format('d'));
switch (true) {
case ! $sameYear:
// example: December 23, 2017 - January 10, 2018
$dateString = sprintf(
'%s - %s',
$startDate->format('F j, Y'),
$endDate->format('F j, Y')
);
break;
case ! $sameMonth:
// example: May 23 - June 16, 2017
$dateString = sprintf(
'%s - %s, %s',
$startDate->format('F j'),
$endDate->format('F j'),
$startDate->format('Y')
);
break;
case ! $sameDay:
// example: May 23 - 28, 2017
$dateString = sprintf(
'%s %s - %s, %s',
$startDate->format('F'),
$startDate->format('j'),
$endDate->format('j'),
$startDate->format('Y')
);
break;
default:
// example: May 23, 2017
$dateString = $startDate->format('F j, Y');
break;
}
}
return $dateString;
}
/**
* Return details html content based on the event id
* @param FrontendGlobals $globals
* @param array $params
* @return mixed|string
*/
private function ajaxDetailsView(FrontendGlobals $globals, array $params)
{
if ( ! $globals->getRequest()->isXmlHttpRequest()) {
throw new BadRequestHttpException();
}
$eventId = intval($params['event']);
/** @var EventProxy $event */
$event = $this->getEntityManager()
->getRepository(EventProxy::class)->findExact($eventId);
if (empty($event)) {
throw new NotFoundHttpException(sprintf('Event #%s not found.', $eventId));
}
// generate the dates, based on locale stuff
$start = DateTimeUtils::changeTimezone(
$event->getData()->getStartDate(),
$this->getLocaleManager()->effectiveTimezone()
);
$content = $this->getTwig()->render(
$this->getDatabaseLoader()->determine(
$globals->getTheme(),
sprintf(
'/modules/%s/GraphicalDetails/build/tpl.html.twig',
$globals->getModule()->name()
)
),
array(
'_globals' => $globals,
'_share' => $this->getShareService(),
'event' => new Event(array(
'timezone' => $start->getTimezone(),
'start' => DateTimeUtils::firstOfMonth($start),
'end' => DateTimeUtils::lastOfMonth($start),
'container' => $globals->getContainer(),
'shares' => $this->getEntityManager()
->getRepository(ClassUtils::getClass($globals->getContainer()))
->findBy(
array(
'id' => $this->flattenContainers($globals->getContainer(), ModuleSettings::class),
),
array(
'name' => 'ASC',
)
)
,
), $event),
)
);
return new Response($content);
}
/**
* Handle add to calendar events via providers (google, outlook, etc.)
*
* @param FrontendGlobals $globals
* @param array $params
* @return Response
* @throws \Exception
*/
private function addToCalendar(FrontendGlobals $globals, array $params)
{
$provider = $params['provider'];
$eventId = intval($params['event']);
if (empty($provider)) {
throw new \Exception('Type cannot be empty.');
}
/** @var EventProxy $event */
$event = $this->getEntityManager()
->getRepository(EventProxy::class)->findExact($eventId);
switch ($provider) {
case Microsoft::TYPE:
return $this->getMicrosoft()->handler(
$globals,
$event,
$globals->getRequest()->query->get('code'),
$globals->getRequest()->query->get('state')
);
case Google::TYPE:
return $this->getGoogle()->handler($event);
default:
throw new \Exception(sprintf('Invalid provider: `%s`', $provider));
}
}
/**
* Just wrap creating an external link (reduce the count of OAuth2 API calls)
*
* @param $eventId
* @param FrontendGlobals $globals
* @return Response
* @throws \Exception
*/
private function shareTo($eventId, FrontendGlobals $globals)
{
$provider = $globals->getRequest()->query->get('provider');
/** @var EventProxy $event */
$event = $this->getEntityManager()
->getRepository(EventProxy::class)
->findExact($eventId);
switch ($provider) {
case Microsoft::TYPE:
return new RedirectResponse($this->getOAuthProviderService()->getProvider(MicrosoftOAuthProvider::ID)->getAuthorizationUri(
$globals->getTenant(),
new OAuthOptions([
'scopes' => [
MicrosoftOAuth2ServiceFactory::SCOPE_CALENDARS_READ,
MicrosoftOAuth2ServiceFactory::SCOPE_CALENDARS_READ_WRITE,
],
'state' => [
'redirect' => (new Uri(sprintf(
'%s/calendar/add/%s?provider=%s',
$this->getContainerService()->getFrontLink(
$globals->getContainer()
),
$event->getId(),
Microsoft::TYPE
)))->toString()
],
])
));
case Google::TYPE:
return new RedirectResponse((new Uri(sprintf(
'%s/calendar/add/%s?provider=%s',
$this->getContainerService()->getFrontLink(
$event->getContainer()
),
$event->getId(),
Google::TYPE
)))->toString());
default:
throw new \Exception(sprintf('Invalid provider: `%s`', $provider));
}
}
/**
* @param array|Container[] $selectedDeps
* @param int $limit
* @param int $offset
* @return array|EventProxy[]
*/
public function findCalendarItems(array $selectedDeps, int $limit, int $offset): array
{
// check paging
if ($limit <= 0) {
throw new \Exception();
}
if ($offset < 0) {
throw new \Exception();
}
// get the department ids for the schools we are searching for
if ( ! $selectedDeps) {
return [];
}
$selectedDepsIds = array_map(
static function (Container $dep) {
return $dep->getId();
},
$selectedDeps
);
// start date should be today
$start = DateTimeUtils::today(
$this->getLocaleManager()->effectiveTimezone()
);
// make sure that we don't extend beyond a range
// this ensures that the calendar stuff does not loop indefinitely to past or future months
$max = DateTimeUtils::afterCurrent('P2Y');
if ($start > $max) {
throw new NotFoundHttpException();
}
// get the module settings for this department
/** @var ModuleSettings $settings */
$settings = $this->loadSettings($selectedDeps[0]);
/** @var ContainerRepository $containerRepo */
$containerRepo = $this->getEntityManager()->getRepository(
ClassUtils::getClass($selectedDeps[0])
);
// get all the departments that need to show in the calendar selections
if ( ! empty($settings->getSelections())) {
$departments = $containerRepo->findBy(
array(
'id' => $settings->getSelections(),
),
array(
'name' => 'ASC',
)
);
} else {
$departments = $containerRepo->findModuleViewableHierarchy(
$selectedDeps[0],
$this->key()
);
}
$shares = $containerRepo->findBy(
array(
'id' => $this->flattenContainers($selectedDeps[0], ModuleSettings::class),
),
array(
'name' => 'ASC',
)
);
$expansions = new SplObjectStorage();
if (count($selectedDepsIds)) {
$queryDepartments = $selectedDepartments = $containerRepo->searchContainers($selectedDepsIds);
$expansionDepartments = [];
foreach ($selectedDepartments as $selectedDepartment) {
if ($selectedDepartment === $selectedDeps[0]) {
continue;
}
$expandedDepartment = $this->flattenContainers($selectedDepartment, ModuleSettings::class);
if ( ! is_array($expandedDepartment)) {
continue;
}
array_shift($expandedDepartment);
$expandedDepartments = $containerRepo->findBy(
array(
'id' => $expandedDepartment,
),
array(
'name' => 'ASC',
)
);
$expansionDepartments = array_merge(
$expansionDepartments,
$expandedDepartments
);
foreach ($expandedDepartments as $dept) {
if ( ! in_array($dept, $selectedDepartments, true)) {
$expansions->offsetSet($dept, $selectedDepartment);
}
}
}
if (in_array($selectedDeps[0], $queryDepartments, true)) {
$queryDepartments = array_values(array_filter(array_unique(array_merge(
$queryDepartments,
$shares
))));
}
$queryDepartments = array_values(array_filter(array_unique(array_merge(
$queryDepartments,
$expansionDepartments
))));
} else {
// get the departments that are already showing in the calendar
$selectedDepartments = array(
$selectedDeps[0],
);
$queryDepartments = $shares;
}
// get the diff of the calendars that are yet to be selected from the possible options
$unselectedDepartments = array_diff($departments, $selectedDepartments);
// find the items
/** @var array|EventProxy[] $items */
$items = $this->getProxyRepository()->createQueryBuilder('items')
->andWhere('items.container IN (:container)')
->setParameter('container', $queryDepartments)
->andWhere('((items.data_startDate >= :start)
OR (items.data_endDate > :start)
OR (items.data_startDate < :start AND items.data_endDate >= :start))')
// TODO: these values are in local times, do we need to convert to utc before using in query?
->setParameter('start', $start)
->addOrderBy('items.data_startDate', 'ASC')
->addOrderBy('items.data_endDate', 'ASC')
->getQuery()
->setMaxResults($limit)
->setFirstResult($offset)
->getResult();
return $items;
}
/**
* @return OAuthProviderService|object
*/
private function getOAuthProviderService(): OAuthProviderService
{
return $this->container->get(__METHOD__);
}
/**
* @return ShareService|object
*/
private function getShareService(): ShareService
{
return $this->container->get(__METHOD__);
}
/**
* @return Google|object
*/
private function getGoogle(): Google
{
return $this->container->get(__METHOD__);
}
/**
* @return Microsoft|object
*/
private function getMicrosoft(): Microsoft
{
return $this->container->get(__METHOD__);
}
/**
* @return EventSearcher|object
*/
public function getEventSearcher(): EventSearcher
{
return $this->container->get(__METHOD__);
}
/**
* {@inheritDoc}
* @return EventSearcher
*/
public function getSearcher(): AbstractSearcher
{
return $this->getEventSearcher();
}
}