<?php
namespace Products\NotificationsBundle\Subscriber\OneRoster;
use Cms\CoreBundle\Entity\AbstractOneRosterEntity;
use Cms\CoreBundle\Entity\OneRoster\OneRosterOrg;
use Cms\CoreBundle\Entity\OneRoster\OneRosterUser;
use Cms\CoreBundle\Entity\OneRosterSync;
use Cms\CoreBundle\Events\OneRosterProcessEvent;
use Doctrine\Common\Util\ClassUtils;
use Products\NotificationsBundle\Entity\AbstractList;
use Products\NotificationsBundle\Entity\Lists\DistrictList;
use Products\NotificationsBundle\Entity\Lists\SchoolList;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
/**
* Class OneRosterOrgProcessSubscriber
* @package Products\NotificationsBundle\Subscriber\OneRoster
*/
final class OneRosterOrgProcessSubscriber extends AbstractNotificationsOneRosterSubscriber implements EventSubscriberInterface
{
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
OneRosterProcessEvent::EVENT__ORG => [
['syncList', 0],
],
];
}
/**
* @param OneRosterProcessEvent $event
*/
public function syncList(OneRosterProcessEvent $event): void
{
// ensure we are meant to process this
if ( ! $this->checkTypes($event->getJob(), [
OneRosterSync::STRATEGIES__NOTIFICATIONS__STAFF,
OneRosterSync::STRATEGIES__NOTIFICATIONS__FAMILY,
OneRosterSync::STRATEGIES__NOTIFICATIONS__STUDENTS,
OneRosterSync::STRATEGIES__NOTIFICATIONS__COMMUNITY,
])) {
return;
}
// get the org
$org = $event->getEntity();
if ( ! $org instanceof OneRosterOrg) {
throw new \Exception(sprintf(
'Org is not of proper type, got "%s".',
ClassUtils::getClass($org)
));
}
// branch on the org type
switch (true) {
case $org->isType(AbstractOneRosterEntity::ENUMS__ORG_TYPE__DISTRICT):
case $org->isType(AbstractOneRosterEntity::ENUMS__ORG_TYPE__SCHOOL):
break;
default:
// DEBUGGING
$event->getOutput()->writeln(sprintf(
'Org type for #%s is "%s", skipping...',
$org->getSourcedId(),
$org->getType()
));
return;
}
// if we have a district, ensure it is the same id that should be on the sync config
// also helps ensure we only have one district just in case incoming data gets funky
if ($org->getType() === AbstractOneRosterEntity::ENUMS__ORG_TYPE__DISTRICT && strcasecmp($org->getSourcedId(), $event->getSync()->getDistrictId()) !== 0) {
throw new \Exception(sprintf(
'District org found that does not match sync settings; got "%s", expected "%s".',
$org->getSourcedId(),
$event->getSync()->getDistrictId()
));
}
// if we are dealing with the district, we want to manage a district list
if ($org->getType() === AbstractOneRosterEntity::ENUMS__ORG_TYPE__DISTRICT && strcasecmp($org->getSourcedId(), $event->getSync()->getDistrictId()) === 0) {
// find existing district list
$lists = $this->em->getRepository(DistrictList::class)->findBy([
'onerosterId' => $org->getSourcedId(),
]);
// manage the types we are interested in
$types = array_values(array_filter([
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__STAFF])) ? OneRosterUser::TYPES__STAFF : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__FAMILY])) ? OneRosterUser::TYPES__FAMILY : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__STUDENTS])) ? OneRosterUser::TYPES__STUDENT : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__COMMUNITY])) ? OneRosterUser::TYPES__COMMUNITY : null,
]));
// loop over all the grades
$managed = [];
foreach ($types as $type) {
// try to find if we have a list already
$list = null;
foreach ($lists as $thing) {
if ($thing->getTypes() === $type) {
$managed[] = $list = $thing;
break;
}
}
// if we don't have one, need to make one
if ( ! $list) {
$managed[] = $list = new DistrictList();
}
// set things
$list
->setName(sprintf(
'[District] %s (%s)',
$org->getName(),
$this->translator->trans(sprintf(
'app.notifications.profiles.types.%s',
OneRosterUser::TYPES_LOOKUP[$type]
))
))
->setTypes($type)
->markFlag(AbstractList::FLAGS__FIXED)
->setOneRosterId($org->getSourcedId())
;
// handle deletion tracking
$list->setOneRosterArchived($org->isStatusToBeDeleted());
}
// make a diff and reset contacts to those that are not matched
// these will need trashed as they are extra and do not match what is in the record from oneroster
$removals = array_udiff($lists, $managed, function (DistrictList $a, DistrictList $b) {
return ($a === $b) ? 0 : $a->getId() <=> $b->getId();
});
// DEBUGGING
array_walk($removals, function (DistrictList $list) use ($event) {
$event->getOutput()->writeln(sprintf(
' Deleting %s (%s | %s | %s)',
ClassUtils::getClass($list),
$list->getName(),
$list->getOneRosterId(),
$list->getId() ?: '-'
));
});
array_walk($managed, function (DistrictList $list) use ($event) {
$event->getOutput()->writeln(sprintf(
' %s %s (%s | %s | %s)',
(empty($list->getId())) ? 'Generating' : 'Updating',
ClassUtils::getClass($list),
$list->getName(),
$list->getOneRosterId(),
$list->getId() ?: '-'
));
});
// everything is patched up, ready to do database work
$this->em->persistAll($managed);
$this->em->removeAll($removals);
$this->em->flush();
// need to nuke extra district lists that don't match us
$this->em->createQueryBuilder()
->delete(DistrictList::class, 'lists')
->andWhere('lists.onerosterId != :onerosterId')
->setParameter('onerosterId', $org->getSourcedId())
->getQuery()
->execute();
// need to nuke extra school lists in the even that district object used to be school
$this->em->createQueryBuilder()
->delete(SchoolList::class, 'lists')
->andWhere('lists.onerosterId = :onerosterId')
->setParameter('onerosterId', $org->getSourcedId())
->getQuery()
->execute();
// quit early
return;
}
// already handled the district
// if we are a single school, we just skip from here
// no need to manage existing data as the topic would have already been removed and fk constraints would kick in to delete the lists
if ($event->getSync()->hasFlag(OneRosterSync::FLAGS__SINGLE_SCHOOL)) {
// DEBUGGING
$event->getOutput()->writeln(sprintf(
'Skipping org "%s"; single school setup and this org is not the district',
$org->getSourcedId()
));
// quit early
return;
}
// load all of our existing lists, if any
$lists = $this->em->getRepository(SchoolList::class)->findBy([
'onerosterId' => $org->getSourcedId(),
]);
// manage the types we are interested in
$types = array_values(array_filter([
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__STAFF])) ? OneRosterUser::TYPES__STAFF : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__FAMILY])) ? OneRosterUser::TYPES__FAMILY : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__STUDENTS])) ? OneRosterUser::TYPES__STUDENT : null,
($this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__COMMUNITY])) ? OneRosterUser::TYPES__COMMUNITY : null,
]));
// loop over all the grades
$managed = [];
foreach ($types as $type) {
// try to find if we have a list already
$list = null;
foreach ($lists as $thing) {
if ($thing->getTypes() === $type) {
$managed[] = $list = $thing;
break;
}
}
// if we don't have one, need to make one
if ( ! $list) {
$managed[] = $list = new SchoolList();
}
// set things
$list
->setName(sprintf(
'%s (%s)',
$org->getName(),
$this->translator->trans(sprintf(
'app.notifications.profiles.types.%s',
OneRosterUser::TYPES_LOOKUP[$type]
))
))
->setTypes($type)
->markFlag(AbstractList::FLAGS__FIXED)
->setOneRosterId($org->getSourcedId())
;
// handle deletion tracking
$list->setOneRosterArchived($org->isStatusToBeDeleted());
}
// make a diff and reset contacts to those that are not matched
// these will need trashed as they are extra and do not match what is in the record from oneroster
$removals = array_udiff($lists, $managed, function (SchoolList $a, SchoolList $b) {
return ($a === $b) ? 0 : $a->getId() <=> $b->getId();
});
// DEBUGGING
array_walk($removals, function (SchoolList $list) use ($event) {
$event->getOutput()->writeln(sprintf(
' Deleting %s (%s | %s | %s)',
ClassUtils::getClass($list),
$list->getName(),
$list->getOneRosterId(),
$list->getId() ?: '-'
));
});
array_walk($managed, function (SchoolList $list) use ($event) {
$event->getOutput()->writeln(sprintf(
' %s %s (%s | %s | %s)',
(empty($list->getId())) ? 'Generating' : 'Updating',
ClassUtils::getClass($list),
$list->getName(),
$list->getOneRosterId(),
$list->getId() ?: '-'
));
});
// everything is patched up, ready to do database work
$this->em->persistAll($managed);
$this->em->removeAll($removals);
$this->em->flush();
}
}