/** * Performs the synchronisation of members. */ public function execute() { if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return; } // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong. if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return; } $this->dataconnector = new data_connector(); // Get all the enabled tools. $tools = helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'membersync' => 1)); foreach ($tools as $tool) { mtrace("Starting - Member sync for published tool '{$tool->id}' for course '{$tool->courseid}'."); // Variables to keep track of information to display later. $usercount = 0; $enrolcount = 0; $unenrolcount = 0; // Fetch consumer records mapped to this tool. $consumers = $this->dataconnector->get_consumers_mapped_to_tool($tool->id); // Perform processing for each consumer. foreach ($consumers as $consumer) { mtrace("Requesting membership service for the tool consumer '{$consumer->getRecordId()}'"); // Get members through this tool consumer. $members = $this->fetch_members_from_consumer($consumer); // Check if we were able to fetch the members. if ($members === false) { mtrace("Skipping - Membership service request failed.\n"); continue; } // Fetched members count. $membercount = count($members); mtrace("{$membercount} members received.\n"); // Process member information. list($usercount, $enrolcount) = $this->sync_member_information($tool, $consumer, $members); } // Now we check if we have to unenrol users who were not listed. if ($this->should_sync_unenrol($tool->membersyncmode)) { $unenrolcount = $this->sync_unenrol($tool); } mtrace("Completed - Synced members for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; enrolled {$enrolcount} members; unenrolled {$unenrolcount} members.\n"); } // Sync the user profile photos. mtrace("Started - Syncing user profile images."); $countsyncedimages = $this->sync_profile_images(); mtrace("Completed - Synced {$countsyncedimages} profile images."); }
/** * Performs the synchronisation of members. * * @return bool|void */ public function execute() { global $CFG, $DB; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php'; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php'; // Check if the authentication plugin is disabled. if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return true; } // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong. if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return true; } // Get all the enabled tools. if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'membersync' => 1))) { $ltiplugin = enrol_get_plugin('lti'); $consumers = array(); $currentusers = array(); $userphotos = array(); foreach ($tools as $tool) { mtrace("Starting - Member sync for shared tool '{$tool->id}' for the course '{$tool->courseid}'."); // Variables to keep track of information to display later. $usercount = 0; $enrolcount = 0; $unenrolcount = 0; // We check for all the users - users can access the same tool from different consumers. if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) { foreach ($ltiusers as $ltiuser) { $mtracecontent = "for the user '{$ltiuser->userid}' in the tool '{$tool->id}' for the course " . "'{$tool->courseid}'"; $usercount++; // Check if we do not have a membershipsurl - this can happen if the sync process has an unexpected error. if (!$ltiuser->membershipsurl) { mtrace("Skipping - Empty membershipsurl {$mtracecontent}."); continue; } // Check if we do not have a membershipsid - this can happen if the sync process has an unexpected error. if (!$ltiuser->membershipsid) { mtrace("Skipping - Empty membershipsid {$mtracecontent}."); continue; } $consumer = sha1($ltiuser->membershipsurl . ':' . $ltiuser->membershipsid . ':' . $ltiuser->consumerkey . ':' . $ltiuser->consumersecret); if (in_array($consumer, $consumers)) { // We have already synchronised with this consumer. continue; } $consumers[] = $consumer; $params = array('lti_message_type' => self::LTI_MESSAGE_TYPE, 'id' => $ltiuser->membershipsid, 'lti_version' => self::LTI_VERSION); mtrace("Calling memberships url '{$ltiuser->membershipsurl}' with body '" . json_encode($params) . "'"); try { $response = sendOAuthParamsPOST('POST', $ltiuser->membershipsurl, $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/x-www-form-urlencoded', $params); } catch (\Exception $e) { mtrace("Skipping - No response received {$mtracecontent} from '{$ltiuser->membershipsurl}'"); mtrace($e->getMessage()); continue; } // Check the response from the consumer. $data = new \SimpleXMLElement($response); // Check if we did not receive a valid response. if (empty($data->statusinfo)) { mtrace("Skipping - Bad response received {$mtracecontent} from '{$ltiuser->membershipsurl}'"); mtrace('Skipping - Error parsing the XML received \'' . substr($response, 0, 125) . '\' ... (Displaying only 125 chars)'); continue; } // Check if we did not receive a valid response. if (strpos(strtolower($data->statusinfo->codemajor), 'success') === false) { mtrace('Skipping - Error received from the remote system: ' . $data->statusinfo->codemajor . ' ' . $data->statusinfo->severity . ' ' . $data->statusinfo->codeminor); continue; } $members = $data->memberships->member; mtrace(count($members) . ' members received.'); foreach ($members as $member) { // Set the user data. $user = new \stdClass(); $user->username = \enrol_lti\helper::create_username($ltiuser->consumerkey, $member->user_id); $user->firstname = \core_user::clean_field($member->person_name_given, 'firstname'); $user->lastname = \core_user::clean_field($member->person_name_family, 'lastname'); $user->email = \core_user::clean_field($member->person_contact_email_primary, 'email'); // Get the user data from the LTI consumer. $user = \enrol_lti\helper::assign_user_tool_data($tool, $user); if (!($dbuser = $DB->get_record('user', array('username' => $user->username, 'deleted' => 0)))) { if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) { // If the email was stripped/not set then fill it with a default one. This // stops the user from being redirected to edit their profile page. if (empty($user->email)) { $user->email = $user->username . "@example.com"; } $user->auth = 'lti'; $user->id = user_create_user($user); // Add the information to the necessary arrays. $currentusers[] = $user->id; $userphotos[$user->id] = $member->user_image; } } else { // If email is empty remove it, so we don't update the user with an empty email. if (empty($user->email)) { unset($user->email); } $user->id = $dbuser->id; user_update_user($user); // Add the information to the necessary arrays. $currentusers[] = $user->id; $userphotos[$user->id] = $member->user_image; } if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_NEW) { // Enrol the user in the course. \enrol_lti\helper::enrol_user($tool, $user->id); } } } // Now we check if we have to unenrol users who were not listed. if ($tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_ENROL_AND_UNENROL || $tool->membersyncmode == \enrol_lti\helper::MEMBER_SYNC_UNENROL_MISSING) { // Go through the users and check if any were never listed, if so, remove them. foreach ($ltiusers as $ltiuser) { if (!in_array($ltiuser->userid, $currentusers)) { $instance = new \stdClass(); $instance->id = $tool->enrolid; $instance->courseid = $tool->courseid; $instance->enrol = 'lti'; $ltiplugin->unenrol_user($instance, $ltiuser->id); } } } } mtrace("Completed - Synced members for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; enrolled {$enrolcount} members; unenrolled {$unenrolcount} members."); mtrace(""); } // Sync the user profile photos. mtrace("Started - Syncing user profile images."); $counter = 0; if (!empty($userphotos)) { foreach ($userphotos as $userid => $url) { if ($url) { $result = \enrol_lti\helper::update_user_profile_image($userid, $url); if ($result === \enrol_lti\helper::PROFILE_IMAGE_UPDATE_SUCCESSFUL) { $counter++; mtrace("Profile image succesfully downloaded and created for user '{$userid}' from {$url}."); } else { mtrace($result); } } } } mtrace("Completed - Synced {$counter} profile images."); } }
/** * Performs the synchronisation of grades. * * @return bool|void */ public function execute() { global $DB, $CFG; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuth.php'; require_once $CFG->dirroot . '/enrol/lti/ims-blti/OAuthBody.php'; require_once $CFG->dirroot . '/lib/completionlib.php'; require_once $CFG->libdir . '/gradelib.php'; require_once $CFG->dirroot . '/grade/querylib.php'; // Check if the authentication plugin is disabled. if (!is_enabled_auth('lti')) { mtrace('Skipping task - ' . get_string('pluginnotenabled', 'auth', get_string('pluginname', 'auth_lti'))); return true; } // Check if the enrolment plugin is disabled - isn't really necessary as the task should not run if // the plugin is disabled, but there is no harm in making sure core hasn't done something wrong. if (!enrol_is_enabled('lti')) { mtrace('Skipping task - ' . get_string('enrolisdisabled', 'enrol_lti')); return true; } // Get all the enabled tools. if ($tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED, 'gradesync' => 1))) { foreach ($tools as $tool) { mtrace("Starting - Grade sync for shared tool '{$tool->id}' for the course '{$tool->courseid}'."); // Variables to keep track of information to display later. $usercount = 0; $sendcount = 0; // We check for all the users - users can access the same tool from different consumers. if ($ltiusers = $DB->get_records('enrol_lti_users', array('toolid' => $tool->id), 'lastaccess DESC')) { $completion = new \completion_info(get_course($tool->courseid)); foreach ($ltiusers as $ltiuser) { $mtracecontent = "for the user '{$ltiuser->userid}' in the tool '{$tool->id}' for the course " . "'{$tool->courseid}'"; $usercount = $usercount + 1; // Check if we do not have a serviceurl - this can happen if the sync process has an unexpected error. if (empty($ltiuser->serviceurl)) { mtrace("Skipping - Empty serviceurl {$mtracecontent}."); continue; } // Check if we do not have a sourceid - this can happen if the sync process has an unexpected error. if (empty($ltiuser->sourceid)) { mtrace("Skipping - Empty sourceid {$mtracecontent}."); continue; } // Need a valid context to continue. if (!($context = \context::instance_by_id($tool->contextid))) { mtrace("Failed - Invalid contextid '{$tool->contextid}' for the tool '{$tool->id}'."); continue; } // Ok, let's get the grade. $grade = false; if ($context->contextlevel == CONTEXT_COURSE) { // Check if the user did not completed the course when it was required. if ($tool->gradesynccompletion && !$completion->is_course_complete($ltiuser->userid)) { mtrace("Skipping - Course not completed {$mtracecontent}."); continue; } // Get the grade. if ($grade = grade_get_course_grade($ltiuser->userid, $tool->courseid)) { $grademax = floatval($grade->item->grademax); $grade = $grade->grade; } } else { if ($context->contextlevel == CONTEXT_MODULE) { $cm = get_coursemodule_from_id(false, $context->instanceid, 0, false, MUST_EXIST); if ($tool->gradesynccompletion) { $data = $completion->get_data($cm, false, $ltiuser->userid); if ($data->completionstate != COMPLETION_COMPLETE_PASS && $data->completionstate != COMPLETION_COMPLETE) { mtrace("Skipping - Activity not completed {$mtracecontent}."); continue; } } $grades = grade_get_grades($cm->course, 'mod', $cm->modname, $cm->instance, $ltiuser->userid); if (!empty($grades->items[0]->grades)) { $grade = reset($grades->items[0]->grades); if (!empty($grade->item)) { $grademax = floatval($grade->item->grademax); } else { $grademax = floatval($grades->items[0]->grademax); } $grade = $grade->grade; } } } if ($grade === false || $grade === null || strlen($grade) < 1) { mtrace("Skipping - Invalid grade {$mtracecontent}."); continue; } // No need to be dividing by zero. if (empty($grademax)) { mtrace("Skipping - Invalid grade {$mtracecontent}."); continue; } // This can happen if the sync process has an unexpected error. if ($grade == $ltiuser->lastgrade) { mtrace("Not sent - The grade {$mtracecontent} was not sent as the grades are the same."); continue; } // Sync with the external system. $floatgrade = $grade / $grademax; $body = \enrol_lti\helper::create_service_body($ltiuser->sourceid, $floatgrade); try { $response = sendOAuthBodyPOST('POST', $ltiuser->serviceurl, $ltiuser->consumerkey, $ltiuser->consumersecret, 'application/xml', $body); } catch (\Exception $e) { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); mtrace($e->getMessage()); continue; } if (strpos(strtolower($response), 'success') !== false) { $DB->set_field('enrol_lti_users', 'lastgrade', intval($grade), array('id' => $ltiuser->id)); mtrace("Success - The grade '{$floatgrade}' {$mtracecontent} was sent."); $sendcount = $sendcount + 1; } else { mtrace("Failed - The grade '{$floatgrade}' {$mtracecontent} failed to send."); } } } mtrace("Completed - Synced grades for tool '{$tool->id}' in the course '{$tool->courseid}'. " . "Processed {$usercount} users; sent {$sendcount} grades."); mtrace(""); } } }
/** * Query the reader. Store results in the object for use by build_table. * * @param int $pagesize size of page for paginated displayed table. * @param bool $useinitialsbar do you want to use the initials bar. */ public function query_db($pagesize, $useinitialsbar = true) { $total = \enrol_lti\helper::count_lti_tools(array('courseid' => $this->courseid)); $this->pagesize($pagesize, $total); $tools = \enrol_lti\helper::get_lti_tools(array('courseid' => $this->courseid), $this->get_page_start(), $this->get_page_size()); $this->rawdata = $tools; // Set initial bars. if ($useinitialsbar) { $this->initialbars($total > $pagesize); } }
/** * Test returning the list of available tools. */ public function test_get_lti_tools() { // Create two tools belonging to the same course. $course1 = $this->getDataGenerator()->create_course(); $data = new stdClass(); $data->courseid = $course1->id; $tool1 = $this->create_tool($data); $tool2 = $this->create_tool($data); // Create two more tools in a separate course. $course2 = $this->getDataGenerator()->create_course(); $data = new stdClass(); $data->courseid = $course2->id; $tool3 = $this->create_tool($data); // Set the next tool to disabled. $data->status = ENROL_INSTANCE_DISABLED; $tool4 = $this->create_tool($data); // Get all the tools. $tools = \enrol_lti\helper::get_lti_tools(); // Check that we got all the tools. $this->assertEquals(4, count($tools)); // Get all the tools in course 1. $tools = \enrol_lti\helper::get_lti_tools(array('courseid' => $course1->id)); // Check that we got all the tools in course 1. $this->assertEquals(2, count($tools)); $this->assertTrue(isset($tools[$tool1->id])); $this->assertTrue(isset($tools[$tool2->id])); // Get all the tools in course 2 that are disabled. $tools = \enrol_lti\helper::get_lti_tools(array('courseid' => $course2->id, 'status' => ENROL_INSTANCE_DISABLED)); // Check that we got all the tools in course 2 that are disabled. $this->assertEquals(1, count($tools)); $this->assertTrue(isset($tools[$tool4->id])); // Get all the tools that are enabled. $tools = \enrol_lti\helper::get_lti_tools(array('status' => ENROL_INSTANCE_ENABLED)); // Check that we got all the tools that are enabled. $this->assertEquals(3, count($tools)); $this->assertTrue(isset($tools[$tool1->id])); $this->assertTrue(isset($tools[$tool2->id])); $this->assertTrue(isset($tools[$tool3->id])); }