/** * Recursively copies nested sequence blocks for rollover. * * @param CurriculumInventorySequenceBlockInterface $block The block to copy. * @param CurriculumInventoryReportInterface $newReport The new report to roll over into. * @param CurriculumInventoryAcademicLevelInterface[] $newLevels A map of new academic levels, indexed by level. * @param CurriculumInventorySequenceBlockInterface|null $newParent The new parent block for this copy. */ protected function rolloverSequenceBlock(CurriculumInventorySequenceBlockInterface $block, CurriculumInventoryReportInterface $newReport, array $newLevels, CurriculumInventorySequenceBlockInterface $newParent = null) { /* @var CurriculumInventorySequenceBlockInterface $newBlock */ $newBlock = $this->sequenceBlockManager->create(); $newBlock->setReport($newReport); $newBlock->setAcademicLevel($newLevels[$block->getAcademicLevel()->getLevel()]); $newBlock->setDescription($block->getDescription()); $newBlock->setEndDate($block->getEndDate()); $newBlock->setStartDate($block->getStartDate()); $newBlock->setChildSequenceOrder($block->getChildSequenceOrder()); $newBlock->setDuration($block->getDuration()); $newBlock->setTitle($block->getTitle()); $newBlock->setOrderInSequence($block->getOrderInSequence()); $newBlock->setMinimum($block->getMinimum()); $newBlock->setMaximum($block->getMaximum()); $newBlock->setTrack($block->hasTrack()); $newBlock->setRequired($block->getRequired()); if ($newParent) { $newBlock->setParent($newParent); $newParent->addChild($newBlock); } $newReport->addSequenceBlock($newBlock); $this->sequenceBlockManager->update($newBlock, false, false); foreach ($block->getChildren() as $child) { $this->rolloverSequenceBlock($child, $newReport, $newLevels, $newBlock); } }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $output->writeln('<info>Starting former student synchronization process.</info>'); $filter = $input->getArgument('filter'); $formerStudents = $this->directory->findByLdapFilter($filter); if (!$formerStudents) { $output->writeln("<error>{$filter} returned no results.</error>"); return; } $output->writeln('<info>Found ' . count($formerStudents) . ' former students in the directory.</info>'); $formerStudentsCampusIds = array_map(function (array $arr) { return $arr['campusId']; }, $formerStudents); $notFormerStudents = $this->userManager->findUsersWhoAreNotFormerStudents($formerStudentsCampusIds); $usersToUpdate = $notFormerStudents->filter(function (UserInterface $user) { return !$user->isUserSyncIgnore(); }); if (!$usersToUpdate->count() > 0) { $output->writeln("<info>There are no students to update.</info>"); return; } $output->writeln('<info>There are ' . $usersToUpdate->count() . ' students in Ilios who will be marked as a Former Student.</info>'); $rows = $usersToUpdate->map(function (UserInterface $user) { return [$user->getCampusId(), $user->getFirstName(), $user->getLastName(), $user->getEmail()]; })->toArray(); $table = new Table($output); $table->setHeaders(array('Campus ID', 'First', 'Last', 'Email'))->setRows($rows); $table->render(); $helper = $this->getHelper('question'); $output->writeln(''); $question = new ConfirmationQuestion('<question>Do you wish to mark these users as Former Students? </question>' . "\n", true); if ($helper->ask($input, $output, $question)) { /* @var UserRoleInterface $formerStudentRole */ $formerStudentRole = $this->userRoleManager->findOneBy(array('title' => 'Former Student')); /* @var UserInterface $user */ foreach ($usersToUpdate as $user) { $formerStudentRole->addUser($user); $user->addRole($formerStudentRole); $this->userManager->update($user, false); } $this->userRoleManager->update($formerStudentRole); $output->writeln('<info>Former students updated successfully!</info>'); } else { $output->writeln('<comment>Update canceled,</comment>'); } }
/** * @param SessionInterface $newSession * @param SessionInterface $origSession * @param $daysOffset */ protected function rolloverIlmSession(SessionInterface $newSession, SessionInterface $origSession, $daysOffset) { /* @var IlmSessionInterface $origIlmSession */ if ($origIlmSession = $origSession->getIlmSession()) { /* @var IlmSessionInterface $newIlmSession */ $newIlmSession = $this->ilmSessionManager->create(); $newIlmSession->setHours($origIlmSession->getHours()); $newSession->setIlmSession($newIlmSession); $newDueDate = $this->getAdjustedDate($origIlmSession->getDueDate(), $daysOffset); $newIlmSession->setDueDate($newDueDate); $this->ilmSessionManager->update($newIlmSession, false, false); } }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $filter = $input->getArgument('filter'); $schoolId = $input->getArgument('schoolId'); $school = $this->schoolManager->findOneBy(['id' => $schoolId]); if (!$school) { throw new \Exception("School with id {$schoolId} could not be found."); } $output->writeln("<info>Searching for new students to add to " . $school->getTitle() . ".</info>"); $students = $this->directory->findByLdapFilter($filter); if (!$students) { $output->writeln("<error>{$filter} returned no results.</error>"); return; } $output->writeln('<info>Found ' . count($students) . ' students in the directory.</info>'); $campusIds = $this->userManager->getAllCampusIds(); $newStudents = array_filter($students, function (array $arr) use($campusIds) { return !in_array($arr['campusId'], $campusIds); }); if (!count($newStudents) > 0) { $output->writeln("<info>There are no new students to add.</info>"); return; } $output->writeln('<info>There are ' . count($newStudents) . ' new students to be added to ' . $school->getTitle() . '.</info>'); $rows = array_map(function (array $arr) { return [$arr['campusId'], $arr['firstName'], $arr['lastName'], $arr['email']]; }, $newStudents); $table = new Table($output); $table->setHeaders(array('Campus ID', 'First', 'Last', 'Email'))->setRows($rows); $table->render(); $helper = $this->getHelper('question'); $output->writeln(''); $question = new ConfirmationQuestion('<question>Do you wish to add these students to ' . $school->getTitle() . '? </question>' . "\n", true); if ($helper->ask($input, $output, $question)) { $studentRole = $this->userRoleManager->findOneBy(array('title' => 'Student')); foreach ($newStudents as $userRecord) { if (empty($userRecord['email'])) { $output->writeln('<error>Unable to add student ' . var_export($userRecord, true) . ' they have no email address.</error>'); continue; } if (empty($userRecord['campusId'])) { $output->writeln('<error>Unable to add student ' . var_export($userRecord, true) . ' they have no campus ID.</error>'); continue; } if (empty($userRecord['username'])) { $output->writeln('<error>Unable to add student ' . var_export($userRecord, true) . ' they have no username.</error>'); continue; } $user = $this->userManager->create(); $user->setFirstName($userRecord['firstName']); $user->setLastName($userRecord['lastName']); $user->setEmail($userRecord['email']); $user->setCampusId($userRecord['campusId']); $user->setAddedViaIlios(true); $user->setEnabled(true); $user->setSchool($school); $user->setUserSyncIgnore(false); $user->addRole($studentRole); $this->userManager->update($user); $authentication = $this->authenticationManager->create(); $authentication->setUser($user); $authentication->setUsername($userRecord['username']); $this->authenticationManager->update($authentication, false); $studentRole->addUser($user); $this->userRoleManager->update($studentRole); $output->writeln('<info>Success! New student #' . $user->getId() . ' ' . $user->getFirstAndLastName() . ' created.</info>'); } } else { $output->writeln('<comment>Update canceled.</comment>'); } }
/** * Retrieves a curriculum inventory in a data structure that lends itself for an easy transformation into * XML-formatted report. * * @param CurriculumInventoryReportInterface $invReport The report object. * @return array An associated array, containing the inventory. * Data is keyed off by: * 'report' ... The inventory report entity. * 'institution' ... An object representing the curriculum inventory's owning institution * 'events' ... An array of events, keyed off by event id. Each event is represented as assoc. array. * 'expectations' ... An associative array of arrays, each sub-array containing a * list of a different type of "competency object" within the curriculum. * These types are program objectives, course objectives and session objectives. * The keys for these type-specific sub-arrays are: * 'program_objectives' * 'course_objectives' * 'session_objectives' * 'framework' ... The competency framework data set. * 'includes' ... Identifiers of the various competency objects referenced in the framework. * 'pcrs_ids' * 'program_objective_ids' * 'course_objective_ids' * 'session_objective_ids' * 'relations' ... Relations between the various competencies within the framework * 'program_objectives_to_pcrs' * 'course_objectives_to_program_objectives' * 'session_objectives_to_course_objectives' * 'sequence_block_references' ...relationships maps between sequence blocks and other curricular entities. * 'events' ... maps sequence blocks to events * 'competency_objects' .. maps sequence blocks to competency objects * * @throws \Exception */ public function getCurriculumInventory(CurriculumInventoryReportInterface $invReport) { // report validation $program = $invReport->getProgram(); if (!$program) { throw new \Exception('No program found for report with id ' . $invReport->getId() . '.'); } $school = $program->getSchool(); if (!$school) { throw new \Exception('No school found for program with id = ' . $program->getId() . '.'); } /** @var CurriculumInventoryInstitutionInterface $institution */ $institution = $this->institutionManager->findOneBy(['school' => $school->getId()]); if (!$institution) { throw new \Exception('No curriculum inventory institution found for school with id = ' . $school->getId() . '.'); } $events = $this->reportManager->getEvents($invReport); $keywords = $this->reportManager->getEventKeywords($invReport); $resourceTypes = $this->reportManager->getEventResourceTypes($invReport); $eventRefsForSeqBlocks = $this->reportManager->getEventReferencesForSequenceBlocks($invReport); $programObjectives = $this->reportManager->getProgramObjectives($invReport); $sessionObjectives = $this->reportManager->getSessionObjectives($invReport); $courseObjectives = $this->reportManager->getCourseObjectives($invReport); $compObjRefsForSeqBlocks = $this->reportManager->getCompetencyObjectReferencesForSequenceBlocks($invReport); $compRefsForEvents = $this->reportManager->getCompetencyObjectReferencesForEvents($invReport); // The various objective type are all "Competency Objects" in the context of reporting the curriculum inventory. // The are grouped in the "Expectations" section of the report, lump 'em together here. $expectations = []; $expectations['program_objectives'] = $programObjectives; $expectations['session_objectives'] = $sessionObjectives; $expectations['course_objectives'] = $courseObjectives; // Build out the competency framework information and added to $expectations. $pcrs = $this->reportManager->getPcrs($invReport); $pcrsIds = array_keys($pcrs); $programObjectiveIds = array_keys($programObjectives); $courseObjectiveIds = array_keys($courseObjectives); $sessionObjectiveIds = array_keys($sessionObjectives); $includes = ['pcrs_ids' => [], 'program_objective_ids' => [], 'course_objective_ids' => [], 'session_objective_ids' => []]; $relations = ['program_objectives_to_pcrs' => [], 'course_objectives_to_program_objectives' => [], 'session_objectives_to_course_objectives' => []]; $rel = $this->reportManager->getProgramObjectivesToPcrsRelations($programObjectiveIds, $pcrsIds); $relations['program_objectives_to_pcrs'] = $rel['relations']; $includes['pcrs_ids'] = $rel['pcrs_ids']; $includes['program_objective_ids'] = $rel['program_objective_ids']; $rel = $this->reportManager->getCourseObjectivesToProgramObjectivesRelations($courseObjectiveIds, $programObjectiveIds); $relations['course_objectives_to_program_objectives'] = $rel['relations']; $includes['program_objective_ids'] = array_values(array_unique(array_merge($includes['program_objective_ids'], $rel['program_objective_ids']))); $includes['course_objective_ids'] = $rel['course_objective_ids']; $rel = $this->reportManager->getSessionObjectivesToCourseObjectivesRelations($sessionObjectiveIds, $courseObjectiveIds); $relations['session_objectives_to_course_objectives'] = $rel['relations']; $includes['course_objective_ids'] = array_values(array_unique(array_merge($includes['course_objective_ids'], $rel['course_objective_ids']))); $includes['session_objective_ids'] = $rel['session_objective_ids']; $expectations['framework'] = ['includes' => $includes, 'relations' => $relations]; // // transmogrify inventory data for reporting and fill in the blanks // $events = $this->addKeywordsToEvents($events, $keywords); $events = $this->addResourceTypesToEvents($events, $resourceTypes); $events = $this->addCompetencyObjectReferencesToEvents($events, $compRefsForEvents); // // aggregate inventory into single return-array // $rhett = []; $rhett['report'] = $invReport; $rhett['expectations'] = $expectations; $rhett['institution'] = $institution; $rhett['events'] = $events; $rhett['sequence_block_references'] = ['events' => $eventRefsForSeqBlocks, 'competency_objects' => $compObjRefsForSeqBlocks]; return $rhett; }
/** * Reorder the entire sequence if on of the blocks changes position. * @param int $oldValue * @param CurriculumInventorySequenceBlockInterface $block * @param ManagerInterface $manager * @throws \OutOfRangeException */ protected function reorderBlocksInSequenceOnOrderChange($oldValue, CurriculumInventorySequenceBlockInterface $block, ManagerInterface $manager) { $parent = $block->getParent(); if (!$parent) { return; } if ($parent->getChildSequenceOrder() !== CurriculumInventorySequenceBlockInterface::ORDERED) { return; } $newValue = $block->getOrderInSequence(); $blocks = $parent->getChildrenAsSortedList(); $blocks = array_filter($blocks, function ($sibling) use($block) { return $sibling->getId() !== $block->getId(); }); $blocks = array_values($blocks); $minRange = 1; $maxRange = count($blocks) + 1; if ($newValue < $minRange || $newValue > $maxRange) { throw new \OutOfRangeException("The given order-in-sequence value {$newValue} falls outside the range {$minRange} - {$maxRange}."); } if ($oldValue === $newValue) { return; } array_splice($blocks, $block->getOrderInSequence() - 1, 0, [$block]); for ($i = 0, $n = count($blocks); $i < $n; $i++) { /* @var CurriculumInventorySequenceBlockInterface $current */ $current = $blocks[$i]; $j = $i + 1; if ($current->getId() !== $block && $current->getOrderInSequence() !== $j) { $current->setOrderInSequence($j); $manager->update($current, false, false); } } }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { // prevent this command to run on a non-empty user store. $existingUser = $this->userManager->findOneBy([]); if (!empty($existingUser)) { throw new \Exception('Sorry, at least one user record already exists. Cannot create a "first" user account.'); } $schools = $this->schoolManager->findBy([], ['title' => 'ASC']); // check if any school data is present before invoking the form helper // to prevent the form from breaking on missing school data further downstream. if (empty($schools)) { throw new \Exception('No schools found. Please load schools into this Ilios instance first.'); } $schoolId = $input->getOption('school'); if (!$schoolId) { $schoolTitles = []; /* @var SchoolInterface $school */ foreach ($schools as $school) { $schoolTitles[$school->getTitle()] = $school->getId(); } $helper = $this->getHelper('question'); $question = new ChoiceQuestion("What is this user's primary school?", array_keys($schoolTitles)); $question->setErrorMessage('School %s is invalid.'); $schoolTitle = $helper->ask($input, $output, $question); $schoolId = $schoolTitles[$schoolTitle]; } $school = $this->schoolManager->findOneBy(['id' => $schoolId]); if (!$school) { throw new \Exception("School with id {$schoolId} could not be found."); } $email = $input->getOption('email'); if (!$email) { $question = new Question("What is the user's Email Address? "); $question->setValidator(function ($answer) { if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) { throw new \RuntimeException("Email is not valid"); } return $answer; }); $email = $this->getHelper('question')->ask($input, $output, $question); } $user = $this->userManager->create(); $user->setFirstName(self::FIRST_NAME); $user->setMiddleName(date('Y-m-d_h.i.s')); $user->setLastName(self::LAST_NAME); $user->setEmail($email); $user->setAddedViaIlios(true); $user->setEnabled(true); $user->setUserSyncIgnore(false); $user->addRole($this->userRoleManager->findOneBy(['title' => 'Developer'])); $user->addRole($this->userRoleManager->findOneBy(['title' => 'Course Director'])); $user->setSchool($school); $this->userManager->update($user); $authentication = $this->authenticationManager->create(); $authentication->setUser($user); $user->setAuthentication($authentication); $encodedPassword = $this->passwordEncoder->encodePassword($user, self::PASSWORD); $authentication->setUsername(self::USERNAME); $authentication->setPasswordBcrypt($encodedPassword); $this->authenticationManager->update($authentication); $output->writeln('Success!'); $output->writeln('A user account has been created.'); $output->writeln(sprintf("You may now log in as '%s' with the password '%s'.", self::USERNAME, self::PASSWORD)); $output->writeln('Please change this password as soon as possible.'); }
/** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { $isDryRun = $input->getOption('dry-run'); $alerts = $this->alertManager->findBy(['dispatched' => false, 'tableName' => 'offering']); if (!count($alerts)) { $output->writeln("<info>No undispatched offering alerts found.</info>"); return; } $templateCache = []; $sent = 0; // email out change alerts /* @var AlertInterface $alert */ foreach ($alerts as $alert) { $output->writeln("<info>Processing offering change alert {$alert->getId()}.</info>"); $offering = $this->offeringManager->findOneBy(['id' => $alert->getTableRowId()]); if (!$offering) { $output->writeln("<warning>No offering with id {$alert->getTableRowId()}," . " unable to send change alert with id {$alert->getId()}.</warning>"); continue; } // do not send alerts for deleted stuff. $deleted = !$offering->getSession() || !$offering->getSession()->getCourse() || !$offering->getSession()->getCourse()->getSchool(); if ($deleted) { // @todo print another warning here? [ST 2015/09/30] continue; } $schools = $alert->getRecipients(); if ($schools->isEmpty()) { $output->writeln("<error>No alert recipient for offering change alert {$alert->getId()}.</error>"); continue; } // Technically, there could be multiple school as recipients to a given alert. // The db schema allows for it. // In practice, there is really only ever one school recipient. // So take the first one and run with it for determining recipients/rendering the email template. // [ST 2015/10/05] /* @var SchoolInterface $school */ $school = $schools->first(); $recipients = trim($school->getChangeAlertRecipients()); if ('' === $recipients) { $output->writeln("<error>Recipient without email for offering change alert {$alert->getId()}.</error>"); continue; } $recipients = array_map('trim', explode(',', $recipients)); // get change alert history from audit logs $history = $this->auditLogManager->findBy(['objectId' => $alert->getId(), 'objectClass' => 'alert'], ['createdAt' => 'asc']); $history = array_filter($history, function (AuditLogInterface $auditLog) { $user = $auditLog->getUser(); return isset($user); }); $subject = $offering->getSession()->getCourse()->getExternalId() . ' - ' . $offering->getStartDate()->format('m/d/Y'); if (!array_key_exists($school->getId(), $templateCache)) { $template = $this->getTemplatePath($school); $templateCache[$school->getId()] = $template; } $template = $templateCache[$school->getId()]; $messageBody = $this->templatingEngine->render($template, ['alert' => $alert, 'history' => $history, 'offering' => $offering, 'timezone' => $this->timezone]); $message = \Swift_Message::newInstance()->setSubject($subject)->setTo($recipients)->setFrom($school->getIliosAdministratorEmail())->setContentType('text/plain')->setBody($messageBody)->setMaxLineLength(998); if ($isDryRun) { $output->writeln($message->getHeaders()->toString()); $output->writeln($message->getBody()); } else { $this->mailer->send($message); } $sent++; } if (!$isDryRun) { // Mark all alerts as dispatched, regardless as to whether an actual email // was sent or not. // This is consistent with the Ilios v2 implementation of this process. // @todo Reassess the validity of this step. [ST 2015/10/01] foreach ($alerts as $alert) { $alert->setDispatched(true); $this->alertManager->update($alert); } $dispatched = count($alerts); $output->writeln("<info>Sent {$sent} offering change alert notifications.</info>"); $output->writeln("<info>Marked {$dispatched} offering change alerts as dispatched.</info>"); } }
/** * Retrieves an the corresponding entity for a given data array. * * @param array $data * @return mixed the entity */ protected function getEntity(array $data) { return $this->em->findOneBy(['id' => $data[0]]); }