<?php
namespace Products\NotificationsBundle\Subscriber\OneRoster;
use Cms\CoreBundle\Entity\OneRoster\OneRosterUser;
use Cms\CoreBundle\Entity\OneRosterSync;
use Cms\CoreBundle\Events\OneRosterLinkEvent;
use Doctrine\Common\Util\ClassUtils;
use Products\NotificationsBundle\Entity\Profile;
use Products\NotificationsBundle\Entity\ProfileRelationship;
use Products\NotificationsBundle\Entity\Student;
final class OneRosterUserLinkSubscriber extends AbstractNotificationsOneRosterSubscriber
{
/**
* {@inheritdoc}
*/
public static function getSubscribedEvents(): array
{
return [
OneRosterLinkEvent::EVENT__USER => [
['associateStudents', 0],
],
];
}
/**
* @param OneRosterLinkEvent $event
*/
public function associateStudents(OneRosterLinkEvent $event): void
{
// ensure we are meant to process this
if ( ! $this->checkTypes($event->getJob(), [
OneRosterSync::STRATEGIES__NOTIFICATIONS__FAMILY,
OneRosterSync::STRATEGIES__NOTIFICATIONS__STUDENTS,
])) {
return;
}
// get the user
$user = $event->getEntity();
if ( ! $user instanceof OneRosterUser) {
throw new \RuntimeException(sprintf(
'User is not of proper type, got "%s".',
ClassUtils::getClass($user)
));
}
// branch on the role type
switch (true) {
case $this->checkTypes($event->getJob(), [OneRosterSync::STRATEGIES__NOTIFICATIONS__FAMILY]) && $user->isRoleFamily():
// noop, code will continue after the switch statement
break;
default:
// DEBUGGING
$event->getOutput()->writeln(sprintf(
'User role for #%s is "%s", skipping...',
$user->getSourcedId(),
$user->getRole()
));
// return to prevent further processing
return;
}
// load up the profile
$profile = $this->em->getRepository(Profile::class)->findOneBy([
'onerosterId' => $user->getSourcedId(),
]);
// if we don't have a profile we have a problem
if ( ! $profile) {
throw new \RuntimeException(sprintf(
'Could not load profile "%s".',
$user->getSourcedId()
));
}
// load up all the students based on oneroster ids
$students = $this->em->getRepository(Student::class)->findBy([
'onerosterId' => $user->getAgentsSourcedIds(),
]);
// we should have found the same amount
if (count($students) !== count($user->getAgents())) {
// TODO: just log the issue and allow it to continue...
// throw new \RuntimeException(sprintf(
// 'Could not load all students, missing: "%s".',
// implode('", "', array_diff(
// array_map(
// static function (Student $student) {
// return $student->getOneRosterId();
// },
// $students
// ),
// array_column($user->getAgents(), 'sourcedId')
// ))
// ));
}
// if there is relevant metadata, grab it
// TODO: where is this used?
$metadata = [];
if ($user->hasMetadataEntry('_children')) {
$metadata = $user->getMetadataEntry('_children') ?: $metadata;
}
// load existing relationships
$relationships = $this->em->getRepository(ProfileRelationship::class)->findBy([
'profile' => $profile,
]);
// set on the profile
$managed = [];
foreach ($students as $student) {
// if either the profile or the student is "discarded", do not manage this relationship
if ($student->isDiscarded() || $profile->isDiscarded()) {
continue;
}
// try to find an existing relationship
$relationship = null;
foreach ($relationships as $thing) {
if ($thing->getStudent() === $student) {
$relationship = $thing;
break;
}
}
// make if not found
if ( ! $relationship) {
$relationship = new ProfileRelationship();
}
// set things
$managed[] = $relationship
->setProfile($profile)
->setStudent($student)
->setOneRosterId($profile->getOneRosterId())
->setOneRosterArchived($profile->isOneRosterArchived())
;
// see if the student we are attaching has relationship metadata
if (array_key_exists($student->getOneRosterId(), $metadata)) {
$relationship->setMetadata($metadata[$student->getOneRosterId()] ?: []);
}
}
// DEBUGGING
array_walk($managed, static function (ProfileRelationship $relationship) use ($event) {
$event->getOutput()->writeln(sprintf(
' %s %s (%s >> %s | %s | %s)',
(empty($relationship->getId())) ? 'Generating' : 'Updating',
ClassUtils::getClass($relationship),
$relationship->getProfile()->getCensoredName(),
$relationship->getStudent()->getCensoredName(),
$relationship->getOneRosterId(),
$relationship->getId() ?: '-'
));
});
// go ahead and save the things we know want to keep
// this allows us to get ids that are needed in the following checks
$this->em->saveAll($managed);
// determine which ones we need to remove
$removals = array_udiff($relationships, $managed, static function (ProfileRelationship $a, ProfileRelationship $b) {
return ($a === $b) ? 0 : $a->getId() <=> $b->getId();
});
// DEBUGGING
array_walk($removals, static function (ProfileRelationship $relationship) use ($event, $profile) {
try {
$event->getOutput()->writeln(sprintf(
' Deleting %s (%s >> %s | %s | %s)',
ClassUtils::getClass($relationship),
$relationship->getProfile()->getCensoredName(),
($relationship->getStudent()) ? $relationship->getStudent()->getCensoredName() : '?',
$relationship->getOneRosterId(),
$relationship->getId() ?: '-'
));
} catch (\Throwable $e) {
// TODO: remove this at some point if it appears to no longer be needed...
throw new \RuntimeException(sprintf(
'REL ID: %s | REL OR: %s | REL PRO ID: %s | REL STU ID: %s | PRO ID: %s',
$relationship->getId() ?: '-',
$relationship->getOneRosterId() ?: '-',
$relationship->getProfile() ? $relationship->getProfile()->getId() : '-',
$relationship->getStudent() ? $relationship->getStudent()->getId() : '-',
$profile->getId() ?: '-'
), 0, $e);
}
});
// remove things we no longer need
$this->em->deleteAll($removals);
}
}