From 921e3a5303f3727b854bf7e0000a45f312529528 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Thu, 1 Aug 2024 18:19:47 -0400 Subject: [PATCH 01/10] solved issue in unit tests where it couldnt find session --- README.md | 4 +- classes/local/au_helpers.php | 97 +++++---- classes/local/errorover.php | 32 ++- classes/local/grade_helpers.php | 298 +++++++++++++++++----------- classes/local/session_helpers.php | 4 +- cmi5PHP/tests/cmi5TestHelpers.php | 206 ++++++++++++++++--- cmi5PHP/tests/grade_helpersTest.php | 261 ++++++++++++++++++++---- version.php | 2 +- view.php | 2 +- 9 files changed, 672 insertions(+), 234 deletions(-) diff --git a/README.md b/README.md index 1f89969..66892ce 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ cmi5launch -A plug in for Moodle that allows the launch of cmi5 content which is then played in a cmi5 player and tracked to a separate LRS. +A plug in for Moodle that enables tracking for cmi5 content.This polugin acts as a bridge between Moodle and a cmi5 player, as well as Moodle and an LRS, enabling recording and tracking of cmi5 statements. It utilizes the CATAPULT suite's cmi5 player which launches the content as well as handles AU satisfication statements. It also links with an LRS to display those statements and others generated by moodle. + +Users can upload the cmi5 content as a Moodle activity. It also integrates with Moodles Gradebook API for grading purposes. diff --git a/classes/local/au_helpers.php b/classes/local/au_helpers.php index 5fad354..5911bce 100644 --- a/classes/local/au_helpers.php +++ b/classes/local/au_helpers.php @@ -25,6 +25,8 @@ use mod_cmi5launch\local\au; use mod_cmi5launch\local\errorover; +use mod_cmi5launch\local\session_helpers; + global $CFG; // Include the errorover (error override) funcs. @@ -32,50 +34,59 @@ defined('MOODLE_INTERNAL') || die(); -class au_helpers { - public function get_cmi5launch_retrieve_aus() { +class au_helpers +{ + public function get_cmi5launch_retrieve_aus() + { return [$this, 'cmi5launch_retrieve_aus']; } - public function get_cmi5launch_create_aus() { + public function get_cmi5launch_create_aus() + { return [$this, 'cmi5launch_create_aus']; } - public function get_cmi5launch_save_aus() { + public function get_cmi5launch_save_aus() + { return [$this, 'cmi5launch_save_aus']; } - public function get_cmi5launch_retrieve_aus_from_db() { + public function get_cmi5launch_retrieve_aus_from_db() + { return [$this, 'cmi5launch_retrieve_aus_from_db']; } + public function get_cmi5launch_update_au_for_user_grades() + { + return [$this, 'cmi5launch_update_au_for_user_grades']; + } /** * Parses and retrieves AUs from the returned info from CMI5 player. * @param mixed $returnedinfo * @return array */ - public function cmi5launch_retrieve_aus($returnedinfo) { - - $resultchunked = ""; - - - // Use our own more specific error handler, to give better info tto user. - set_error_handler('mod_cmi5launch\local\array_chunk_warning', E_WARNING); - + public function cmi5launch_retrieve_aus($returnedinfo) + { + + $resultchunked = ""; + + + // Use our own more specific error handler, to give better info tto user. + set_error_handler('mod_cmi5launch\local\array_chunk_warning', E_WARNING); + // The results come back as nested array under more then just AUs. // We only want the info pertaining to the AU. However, if the wrong info is passed array_chunk will through an exception. try { $resultchunked = array_chunk($returnedinfo["metadata"]["aus"], 1, ); - } - catch (\Exception $e) { + } catch (\Exception $e) { echo "Cannot retrieve AUs. Error found when trying to parse them from course creation: " . "Please check the connection to player or course format and try again. \n" . $e->getMessage() . "\n"; //exit; - } + } // Restore the error handler. restore_error_handler(); - + return $resultchunked; } @@ -94,7 +105,7 @@ public function cmi5launch_create_aus($austatements) // We should not be able to get here but what if null is pulled from record and passed in? // So in case it is given null. if ($austatements == null) { - + throw new nullException('Cannot retrieve AU information. AU statements from DB are: ' . $austatements, 0); } else { @@ -120,11 +131,12 @@ public function cmi5launch_create_aus($austatements) * @param mixed $auobjectarray * @return array */ - public function cmi5launch_save_aus($auobjectarray) { + public function cmi5launch_save_aus($auobjectarray) + { // Add userid to the record. global $DB, $USER, $cmi5launch; $table = "cmi5launch_aus"; - + // An array to hold the created ids. $auids = array(); @@ -138,20 +150,21 @@ public function cmi5launch_save_aus($auobjectarray) { // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. set_error_handler('mod_cmi5launch\local\sifting_data_warning', E_WARNING); set_exception_handler('mod_cmi5launch\local\exception_au'); - + //Check it's not null. if ($auobjectarray == null) { - throw new nullException('Cannot save AU information. AU object array is: null' , 0); + throw new nullException('Cannot save AU information. AU object array is: null', 0); } else { + // For each AU in array build a new record and save it. // Because of so many nested variables this needs to be done manually. foreach ($auobjectarray as $auobject) { - + // A try statement to catch any errors that may be thrown. try { - // Make a newrecord to save. + // Make a newrecord to save. $newrecord = new \stdClass(); // Assign the values to the new record. @@ -162,7 +175,10 @@ public function cmi5launch_save_aus($auobjectarray) { $newrecord->lmsid = json_decode(json_encode($auobject->lmsId, true)); $newrecord->url = $auobject->url; $newrecord->type = $auobject->type; - $title = json_decode(json_encode($auobject->title), true); + + //apparently thi covoluted methid is becessary shceck php unit tests MB + $title = json_decode(json_encode($auobject->title), true); + // $title = $auobject->title; $newrecord->title = $title[0]['text']; $newrecord->moveon = $auobject->moveOn; $newrecord->auindex = $auobject->auIndex; @@ -179,20 +195,21 @@ public function cmi5launch_save_aus($auobjectarray) { $newrecord->satisfied = $auobject->satisfied; $newrecord->moodlecourseid = $cmi5launch->id; + // Save the record and get the new id. $newid = $DB->insert_record($table, $newrecord, true); - + // Save new id to list to pass back. $auids[] = $newid; - + // This is for troubleshooting, so we know where the error is. $currentrecord++; - + // The set exception handler catches exceptionas that SLIP by, // so maybe DONT make it throwable and catch type errror } catch (\Throwable $e) { - - + + echo "Cannot save to DB. Stopped at record with ID number " . ($currentrecord) . "."; // This is the tricky part, we need to find out which field is missing. But because the error is thrown ON the field, we need to do some @@ -202,7 +219,7 @@ public function cmi5launch_save_aus($auobjectarray) { // Get the last ley of array $lastkey = array_key_last($items); - + // Heres thhe tricky part, the lastkey here is somewhere in the array we earlier made and the NEXT one would be the one that threw the error. // So now we can grab the key after the last one. $key = array_search($lastkey, $newrecorditems) + 1; @@ -218,33 +235,34 @@ public function cmi5launch_save_aus($auobjectarray) { // Restore default hadlers. restore_exception_handler(); restore_error_handler(); - + return $auids; } } - + /** * Retrieves AU info from DB, converts to AU object, and returns it. * @param mixed $auid * @return au|bool */ - public function cmi5launch_retrieve_aus_from_db($auid) { + public function cmi5launch_retrieve_aus_from_db($auid) + { global $DB; - $check = $DB->record_exists( 'cmi5launch_aus', ['id' => $auid], '*', IGNORE_MISSING); + $check = $DB->record_exists('cmi5launch_aus', ['id' => $auid], '*', IGNORE_MISSING); // If check is negative, the record does not exist. It should also throw error. // Moodle will throw the error, but we want to pass this message back ot user. if (!$check) { - throw new nullException("Error attempting to get AU data from DB. Check AU id. AU id is: " . $auid ."

", 0); + throw new nullException("Error attempting to get AU data from DB. Check AU id. AU id is: " . $auid . "

", 0); } else { - $auitem = $DB->get_record('cmi5launch_aus', array('id' => $auid)); + $auitem = $DB->get_record('cmi5launch_aus', array('id' => $auid)); $au = new au($auitem); @@ -252,7 +270,6 @@ public function cmi5launch_retrieve_aus_from_db($auid) { return $au; } - - } -} + } +} \ No newline at end of file diff --git a/classes/local/errorover.php b/classes/local/errorover.php index 5e60a27..fc0075c 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -42,6 +42,34 @@ function exception_au(\Throwable $exception) throw new fieldException('Error OVER: ' . $exception->getMessage(), 0); // exit; } +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function exception_grade(\Throwable $exception) +{ + + throw new nullException('Error in checking user grades: ' . $exception->getMessage(), 0); + // exit; +} + +function sifting_data_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + + throw new fieldException('Error: ' . $errstr, 0); + // exit; +} + function array_chunk_warning($errno, $errstr, $errfile, $errline) { // echo"Error stirn --- $errstr"; @@ -54,7 +82,7 @@ function array_chunk_warning($errno, $errstr, $errfile, $errline) } /// Ok, this i a different error handler - function sifting_data_warning($errno, $errstr, $errfile, $errline) + function grade_warning($errno, $errstr, $errfile, $errline) { // echo"Error stirn --- $errstr"; // echo"Error number --- $errno"; @@ -62,7 +90,7 @@ function sifting_data_warning($errno, $errstr, $errfile, $errline) // echo"Error errline --- $errline"; // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - throw new fieldException('Error: ' . $errstr, 0); + throw new nullException('Error in checking user grades: ' . $errstr, 0); // exit; } diff --git a/classes/local/grade_helpers.php b/classes/local/grade_helpers.php index fe78c85..4d7ef2e 100644 --- a/classes/local/grade_helpers.php +++ b/classes/local/grade_helpers.php @@ -26,17 +26,28 @@ defined('MOODLE_INTERNAL') || die(); use mod_cmi5launch\local\session_helpers; +use mod_cmi5launch\local\au_helpers; -class grade_helpers { - public function get_cmi5launch_check_user_grades_for_updates() { + +class grade_helpers +{ + public function get_cmi5launch_update_au_for_user_grades() + { + return [$this, 'cmi5launch_update_au_for_user_grades']; + } + + public function get_cmi5launch_check_user_grades_for_updates() + { return [$this, 'cmi5launch_check_user_grades_for_updates']; } - public function get_cmi5launch_highest_grade() { + public function get_cmi5launch_highest_grade() + { return [$this, 'cmi5launch_highest_grade']; } - public function get_cmi5launch_average_grade() { + public function get_cmi5launch_average_grade() + { return [$this, 'cmi5launch_average_grade']; } @@ -46,13 +57,14 @@ public function get_cmi5launch_average_grade() { * - or a singular int. * @return int */ - public function cmi5launch_average_grade($scores) { + public function cmi5launch_average_grade($scores) + { global $cmi5launch, $USER, $DB; // If it comes in as string, convert to array. if (is_string($scores)) { - + $scores = json_decode($scores, true); } @@ -69,7 +81,7 @@ public function cmi5launch_average_grade($scores) { } else { $averagegrade = 0; - + } // Now apply intval. @@ -83,7 +95,8 @@ public function cmi5launch_average_grade($scores) { * @param mixed $scores * @return int */ - public function cmi5launch_highest_grade($scores) { + public function cmi5launch_highest_grade($scores) + { global $cmi5launch, $USER, $DB; @@ -117,132 +130,187 @@ public function cmi5launch_highest_grade($scores) { * @param array $user - the user whose grades are being updated. * @return array */ - public function cmi5launch_check_user_grades_for_updates($user) { + public function cmi5launch_check_user_grades_for_updates($user) + { + + global $cmi5launch, $USER, $DB; + + // what is cmi5launch here in actual func? + + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\grade_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_grade'); + + // Check if record already exists. + $exists = $DB->record_exists('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $user->id]); + try { + // If it exists, we want to update it. + if (!$exists == false) { + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $user->id]); + + $auids = json_decode($userscourse->aus); + + + + // Update the AUs + $returnedinfo = $this->cmi5launch_update_au_for_user_grades($auids, $user); + + // Array to hold AU scores. + $auscores = $returnedinfo[0]; + $overallgrade = $returnedinfo[1]; + + // Update course record. + $userscourse->ausgrades = json_encode($auscores); + $DB->update_record("cmi5launch_usercourse", $userscourse); + //echo" 1 branch"; + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + // Return scores. + return $overallgrade; + + } else { + + // Should we return SOMEthing> + $nograde = array(0 => 'No grades to update. No record for user found in this course.'); + // Do nothing, there is no record for this user in this course. + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + return $nograde; + + } + } catch (\Throwable $e) { + + // If there is an error, return the error. + echo" Error in updating or checking user grades. Report this error to system administrator: ". $e->getMessage(); + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + return $e; + } + } + + /* + * Go through each Au, each Au will be responsible for updating its own session. + * @param mixed $auid + * @return au|bool + */ + public function cmi5launch_update_au_for_user_grades($auids, $user) + { global $cmi5launch, $USER, $DB; + echo " HELLO I SHOULDN'T BE HERE?"; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Array to hold AU scores. + $auscores = array(); + $overallgrade = array(); + + // Bring in functions and classes. $sessionhelper = new session_helpers; // Functions from other classes. $updatesession = $sessionhelper->cmi5launch_get_update_session(); - // Check if record already exists. - $exists = $DB->record_exists('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $user->id]); + // Go through each Au, each Au will be responsible for updating its own session. + foreach ($auids as $key => $auid) { + + + // Array to hold session scores for update. + $sessiongrades = array(); - // If it exists, we want to update it. - if (!$exists == false) { - - // Retrieve the record. - $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $user->id]); - - // User record may be null if user has not participated in course yet. - //if (!$userscourse == null) { - // should never be null if exosts? - // Retrieve AU ids. - $auids = (json_decode($userscourse->aus)); - - // Array to hold AU scores. - $auscores = array(); - $overallgrade = array(); - - // Go through each Au, each Au will be responsible for updating its own session. - foreach ($auids as $key => $auid) { - - // Array to hold session scores for update. - $sessiongrades = array(); - - // This uses the auid to pull the right record from the aus table. - $aurecord = $DB->get_record('cmi5launch_aus', ['id' => $auid]); - - // When it is null it is because the user has not launched the AU yet. - if (!$aurecord == null || false) { - - // Check if there are sessions. - if (!$aurecord->sessions == null) { - - // Retrieve session ids for this course. There may be more than one session. - $sessions = json_decode($aurecord->sessions, true); - - // Iterate through each session. - foreach ($sessions as $sessionid) { - - // Using current session id, retrieve session from DB. - $session = $DB->get_record('cmi5launch_sessions', ['sessionid' => $sessionid]); - - // Retrieve new info (if any) from CMI5 player and LRS on session. - $session = $updatesession($sessionid, $cmi5launch->id, $user); - - // Now if the session is complete, passed, or terminated, we want to update the AU. - // These come in order, so the last one is the current status, so update on each one, - // overwrite as you go, and the last one if final. - if ($session->iscompleted == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->completed = 1; - } - if ($session->ispassed == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->passed = 1; - } - if ($session->isterminated == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->terminated = 1; - } - - // Add the session grade to array. - $sessiongrades[] = $session->score; - } - // Save the session scores to AU, it is ok to overwrite. - $aurecord->scores = json_encode($sessiongrades, JSON_NUMERIC_CHECK); - - // Determine gradetype and use it to save overall grade to AU. - $gradetype = $cmi5launchsettings["grademethod"]; - - switch ($gradetype) { - - // GRADE_AUS_CMI5 = 0. - // GRADE_HIGHEST_CMI5 = 1. - // GRADE_AVERAGE_CMI5 = 2. - // GRADE_SUM_CMI5 = 3. - - case 1: - $aurecord->grade = $this->cmi5launch_highest_grade($sessiongrades); - break; - case 2: - $aurecord->grade = $this->cmi5launch_average_grade($sessiongrades); - break; - default: - echo "Gradetype not found."; - } - - // Save AU scores to corresponding title. - $auscores[$aurecord->lmsid] = array ($aurecord->title => $aurecord->scores); - - // Save an overall grade to array to be passed out to grade_update. - $overallgrade[] = $aurecord->grade; - - // Save Au title and their scores to AU. - // Save updates to DB. - $aurecord = $DB->update_record('cmi5launch_aus', $aurecord); + // This uses the auid to pull the right record from the aus table. + $aurecord = $DB->get_record('cmi5launch_aus', ['id' => $auid]); + + // When it is null it is because the user has not launched the AU yet. + if (!$aurecord == null || false) { + + // Check if there are sessions. + if (!$aurecord->sessions == null) { + + // Retrieve session ids for this course. There may be more than one session. + $sessions = json_decode($aurecord->sessions, true); + + // Iterate through each session. + foreach ($sessions as $sessionid) { + + // Using current session id, retrieve session from DB. + $session = $DB->get_record('cmi5launch_sessions', ['sessionid' => $sessionid]); + + // the update session is call the ission + // we need to moick IT + + // Retrieve new info (if any) from CMI5 player and LRS on session. + $session = $updatesession($sessionid, $cmi5launch->id, $user); + + // Now if the session is complete, passed, or terminated, we want to update the AU. + // These come in order, so the last one is the current status, so update on each one, + // overwrite as you go, and the last one if final. + if ($session->iscompleted == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->completed = 1; } + if ($session->ispassed == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->passed = 1; + } + if ($session->isterminated == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->terminated = 1; + } + + // Add the session grade to array. + $sessiongrades[] = $session->score; + } + // Save the session scores to AU, it is ok to overwrite. + $aurecord->scores = json_encode($sessiongrades, JSON_NUMERIC_CHECK); + + // Determine gradetype and use it to save overall grade to AU. + $gradetype = $cmi5launchsettings["grademethod"]; + + switch ($gradetype) { + + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + + case 1: + $aurecord->grade = $this->cmi5launch_highest_grade($sessiongrades); + break; + case 2: + $aurecord->grade = $this->cmi5launch_average_grade($sessiongrades); + break; + default: + echo "Gradetype not found."; } - } - // Update course record. - $userscourse->ausgrades = json_encode($auscores); - $DB->update_record("cmi5launch_usercourse", $userscourse); - // } + // Save AU scores to corresponding title. + $auscores[$aurecord->lmsid] = array($aurecord->title => $aurecord->scores); - // Return scores. - return $overallgrade; + // Save an overall grade to array to be passed out to grade_update. + $overallgrade[] = $aurecord->grade; - } else { - // Do nothing, there is no record for this user in this course. - return false; + // Save Au title and their scores to AU. + // Save updates to DB. + $aurecord = $DB->update_record('cmi5launch_aus', $aurecord); + } + } } + + //Array to hold answer + $toreturn = array(0 => $auscores, 1 => $overallgrade); + // we need to return auscores and overallgrade. + + return $toreturn; } + } diff --git a/classes/local/session_helpers.php b/classes/local/session_helpers.php index 22851d5..82e9e4a 100644 --- a/classes/local/session_helpers.php +++ b/classes/local/session_helpers.php @@ -71,7 +71,7 @@ public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { // Get updates from cmi5player. // This is sessioninfo from CMI5 player. - $sessioninfo = $getsessioninfo($sessionid, $cmi5id); + $sessioninfo = json_decode($getsessioninfo($sessionid, $cmi5id), true); // Update session. foreach ($sessioninfo as $key => $value) { @@ -126,7 +126,7 @@ public function cmi5launch_create_session($sessionid, $launchurl, $launchmethod) // Save record to table. $newid = $DB->insert_record($table, $newrecord, true); - + // Return value return $newid; diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php index 5db6485..60acc02 100644 --- a/cmi5PHP/tests/cmi5TestHelpers.php +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -10,7 +10,7 @@ use PHPUnit\Framework\TestCase; require_once(__DIR__ . '/../../classes/local/au_helpers.php'); -require_once(__DIR__ . '/../../classes/local/session_helpers.php'); +//require_once(__DIR__ . '/../../classes/local/session_helpers.php'); /** @@ -56,6 +56,7 @@ function maketestcmi5launch() $newid = $DB->insert_record('cmi5launch', $cmi5launch); + return $newid; } @@ -81,6 +82,10 @@ function deletetestcmi5launch_usercourse($ids) { global $DB, $cmi5launch; + // if id is int + if (!is_array($ids)) { + $ids = array($ids); + } foreach ($ids as $id) { // Delete the cmi5launch record $DB->delete_records('cmi5launch_usercourse', array('id' => $id)); @@ -95,7 +100,10 @@ function deletetestcmi5launch_usercourse($ids) function deletetestcmi5launch_aus($ids) { global $DB, $cmi5launch; - + // if id is int + if (!is_array($ids)) { + $ids = array($ids); + } foreach ($ids as $id) { // Delete the cmi5launch record $DB->delete_records('cmi5launch_aus', array('id' => $id)); @@ -112,6 +120,11 @@ function deletetestcmi5launch_sessions($ids) { global $DB, $cmi5launch; + // if id is int + if (!is_array($ids)) { + $ids = array($ids); + } + foreach ($ids as $id) { // Delete the cmi5launch record $DB->delete_records('cmi5launch_sessions', array('id' => $id)); @@ -233,6 +246,10 @@ function maketestaus ($testcourseid) 'moodlecourseid' => 'moodlecourseid' . $i, 'userid' => 'userid' . $i ); + + // maybe if we put the sessions here then we can skip the whole resaving thing + $testcoursesessionids = maketestsessions(); + $mockvalues['sessions'] = $testcoursesessionids; $aus[] = new au($mockvalues); } @@ -240,20 +257,12 @@ function maketestaus ($testcourseid) $auhelper = new au_helpers(); $saveau = $auhelper->get_cmi5launch_save_aus(); - // Ok what the hack is going on here with saveaus - /*echo"
"; - echo " Well let sprint them like the save aus wopuld separate them, "; - foreach ($aus as $auobject) { - echo "
"; - echo "AU ID: " . $auobject->id; - var_dump($auobject); - echo"
"; - } - echo"
"; -*/ + + + // Save AUs to test DB and save IDs. $newauids = $saveau($aus); - + // Return array of AU ids return $newauids; @@ -352,10 +361,13 @@ function maketestsessions () for ($i = 0; $i < 5; $i++) { - $sessionid[] = $i; + //$toaddtostring = strval($i); // Add i to each value so the AUs are unique. // Mock values to make sessions. + + // For some bizarre reason, retrieve sessions from db goes on SESSION id not ID?!?!?? + // THIS IS the problem? $mockvalues = array( 'id' => $i, 'sessionid' => 'sessionid' . $i, @@ -384,6 +396,7 @@ function maketestsessions () 'launchurl' => 'launchurl' . $i, ); $sessions[] = new session($mockvalues); + $sessionid[] = ('sessionid' . $i); } // Pass in the au index to retrieve a launchurl and session id. //$urldecoded = $cmi5launchretrieveurl($cmi5launch->id, $auindex); @@ -397,21 +410,25 @@ function maketestsessions () // And launch method. $launchmethod = 'testmethod'; - // Now save the fake sessions to the test database +// Ok so tomorrow make this so there are sessions actually creatd. $sessionhelper = new session_helpers(); -$createsession = $sessionhelper->cmi5launch_get_create_session(); - +//$createsession = $sessionhelper->cmi5launch_create_session(); // For each session id in the list, create a session. - foreach ($sessionid as $id) { - $newids[] = $createsession($id, $launchurl, $launchmethod); + foreach ($sessions as $session) { + // LEts test if it can retrieve here + $newid = $sessionhelper->cmi5launch_create_session($session->id, $launchurl, $launchmethod); + $newids[] = $newid; + //$sessionid[] = $session; + + } // Save AUs to test DB and save IDs. //$newauids = $createsession($aus); // Return array of session ids - return $newids; + return $sessionid; } @@ -419,31 +436,63 @@ function maketestsessions () * Heeelper func that assigns the sessions made to the aus for testing purposes. * @param mixed $auids * @param mixed $sessionids - * @return void + * @return array */ function assign_sessions_to_aus($auids, $sessionids){ + // What they are saying is the SESSIONS arent in the table so lets chekc why that may be + global $DB; // HElper function to assign sessions to aus $au_helpers = new au_helpers(); $retrieve_aus = $au_helpers->get_cmi5launch_retrieve_aus_from_db(); $save_aus = $au_helpers->get_cmi5launch_save_aus(); + //Array to holdd newly created AUs $newaus = array(); // First populate the aus with the sessions foreach ($auids as $auid ){ - // Assiging the sessions to the aus + + $check = $DB->record_exists( 'cmi5launch_aus', ['id' => $auid], '*', IGNORE_MISSING); + + + if (!$check) { + // If check is negative, the record does not exist. Throw error. + echo "

Error attempting to get AU data from DB. Check AU id.

"; + } + // Assiging the sessions to the aus $au = $retrieve_aus($auid); + // I bet an argument one string being array is in the au itself + + + // Tomorrow: no that isnt it. Something else is throwin the problem. // Now the AU will have properties and we want to assign the sessionid array to the 'sessions' property - $au->sessions = $sessionids; + $au->sessions = json_encode($sessionids); + // Update AU in table with new info. + + //echo au here nd check what sessons itr and if id mathces + + + + + // Save the AU back to the DB. + $success = $DB->update_record('cmi5launch_aus', $au); + - $newaus[] = $au; + + $newaus[] = $au; } + // AHA! They are whole damn aus // now save the new aus back to db - $save_aus($newaus); - } + // we dont need to sve twice!!! + // $save_aus($newaus); + // Save us is to make new aus we need to save them back to DB + // Now update to table. + //return $newauids; + + } /** * Heeelper func that assigns the aus made to the course(s) for testing purposes. @@ -458,6 +507,7 @@ function assign_aus_to_courses($courseids, $auids){ // Mooonday- there are no course helpers so just call the course, assign the auids // and then save ther course with DB calls + // Retreive the courses // Imust have copied below from the func above, it doesn't seem relevant $au_helpers = new au_helpers(); @@ -466,21 +516,117 @@ function assign_aus_to_courses($courseids, $auids){ //Array to holdd newly created AUs $newaus = array(); + + // if its an int + if (!is_array($courseids)){ + $courseids = array($courseids); + } // First populate the aus with the sessions foreach ($courseids as $courseid ){ // Get the course $record = $DB->get_record('cmi5launch_usercourse', array('id' => $courseid)); + // somewhere between thie au ids above and being encoded below its effin uop // Assigning the sessions to the aus - $record->aus == $auids; - + // Its like its not assigning it. + + // AHA What is auids here? + + $record->aus = json_encode($auids); + // Save the course back to the db. - $DB->update_record('cmi5launch_usercourse', $record); + $success = $DB->update_record('cmi5launch_usercourse', $record); } } +// New class +class session_helpers2 +{ + /** + * Creates a session record in DB. + * @param mixed $sessionid - the session id + * @param mixed $launchurl - the launch url + * @param mixed $launchmethod - the launch method + * @return void + */ + public function cmi5launch_create_sessio6n($sessionid, $launchurl, $launchmethod) { + + global $DB, $CFG, $cmi5launch, $USER; + + $table = "cmi5launch_sessions"; + + // Make a new record to save. + $newrecord = new \stdClass(); + // Because of many nested properties, needs to be done manually. + $newrecord->sessionid = $sessionid; + $newrecord->launchurl = $launchurl; + $newrecord->tenantname = $USER->username; + $newrecord->launchmethod = $launchmethod; + // I think here is where we eed to implement : moodlecourseid + $newrecord->moodlecourseid = $cmi5launch->id; + // And userid! + $newrecord->userid = $USER->id; + + // Save record to table. + $newid = $DB->insert_record($table, $newrecord, true); + + // Return value + return $newid; + + } + // So now all the test has to do is inject THIS which will return as we please + // We will inject the damn func! // inject this one! +function updatesessione($sessionid, $cmi5launchid, $user) +{ + global $DB, $cmi5launch, $USER; + // Make new sessions, lets make five. + $sessions = array(); + $sessionids = array(); + + + $sessionids[] = $sessionid; + //$toaddtostring = strval($i); + // Add i to each value so the AUs are unique. + // Mock values to make sessions. + $mockvalues = array( + 'id' => $sessionid, + 'sessionid' => 'sessionid' . $sessionid, + 'userid' => 'userid' . $sessionid, + 'moodlecourseid' => 'moodlecourseid' . $sessionid, + 'registrationscoursesausid' => 'registrationscoursesausid' . $sessionid, + 'tenantname' => 'tenantname' . $sessionid, + 'createdat' => 'createdat' . $sessionid, + 'updatedat' => 'updatedat' . $sessionid, + 'code' => 'code' . $sessionid, + 'launchtokenid' => 'launchtokenid' . $sessionid, + 'lastrequesttime' => 'lastrequesttime' . $sessionid, + 'launchmode' => 'launchmode' . $sessionid, + 'masteryscore' => 'masteryscore' . $sessionid, + 'score' => 'score' . $sessionid, + 'islaunched' => 'islaunched' . $sessionid, + 'isinitialized' => 'isinitialized' . $sessionid, + 'duration' => 'duration' . $sessionid, + 'iscompleted' => 'iscompleted' . $sessionid, + 'ispassed' => 'ispassed' . $sessionid, + 'isfailed' => 'isfailed' . $sessionid, + 'isterminated' => 'isterminated' . $sessionid, + 'isabandoned' => 'isabandoned' . $sessionid, + 'progress' => 'progress' . $sessionid, + 'launchmethod' => 'launchmethod' . $sessionid, + 'launchurl' => 'launchurl' . $sessionid, + ); + $newsession = new session($mockvalues); + + echo " Ive been calllled"; + return $newsession; + } + + } + + + /* function get_file_get_contents() { diff --git a/cmi5PHP/tests/grade_helpersTest.php b/cmi5PHP/tests/grade_helpersTest.php index 6a8753c..2a808a3 100644 --- a/cmi5PHP/tests/grade_helpersTest.php +++ b/cmi5PHP/tests/grade_helpersTest.php @@ -2,7 +2,10 @@ namespace cmi5Test; +use Exception; use mod_cmi5launch\local\grade_helpers; +use mod_cmi5launch\local\nullException; +use mod_cmi5launch\local\fieldException; use PHPUnit\Framework\TestCase; use mod_cmi5launch\local\cmi5_connectors; @@ -19,32 +22,12 @@ class grade_helpersTest extends TestCase { + // Use setupbefore and after class sparingly. In this case, we don't want to use it to connect tests, but rather to // 'prep' the test db with values the tests can run against. public static function setUpBeforeClass(): void { - global $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $testcourseausids, $testcoursesessionids, $cmi5launchsettings; - - // Make a fake cmi5 launch record. - $cmi5launchid = maketestcmi5launch(); - - $cmi5launchsettings = array("cmi5launchtenanttoken" => "Testtoken", "cmi5launchplayerurl" => "http://test/launch.php", "cmi5launchcustomacchp" => "http://testhomepage.com"); - - // Override global variable and function so that it returns test data. - $USER = new \stdClass(); - $USER->username = "testname"; - $USER->id = 10; - - // Make test course, AUs and sessions. - $testcourseid = maketestcourse($cmi5launchid); - $testcourseausids = maketestaus($testcourseid); - $testcoursesessionids = maketestsessions($testcourseid); - - // Assign the sessions to AUs. - assign_sessions_to_aus($testcourseausids, $testcoursesessionids); - - // Assign the AUs to the course. - assign_aus_to_courses($testcourseid, $testcourseausids); + } public static function tearDownAfterClass(): void @@ -52,21 +35,22 @@ public static function tearDownAfterClass(): void global $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $testcourseausids, $testcoursesessionids, $cmi5launchsettings; // Delete the test record. - deletetestcmi5launch($cmi5launchid); + // deletetestcmi5launch($cmi5launchid); // Delete the test course. - deletetestcmi5launch_usercourse($cmi5launchid); + // deletetestcmi5launch_usercourse($cmi5launchid); // Delete the test AUs. - deletetestcmi5launch_aus($testcourseausids); + // deletetestcmi5launch_aus($testcourseausids); // Delete the test sessions. - deletetestcmi5launch_sessions($testcoursesessionids); + // deletetestcmi5launch_sessions($testcoursesessionids); + // Restore overridden global variable. unset($GLOBALS['USER']); - unset($GLOBALS['DB']); + // unset($GLOBALS['DB']); unset($GLOBALS['cmi5launchsettings']); unset($GLOBALS['cmi5launch']); unset($GLOBALS['cmi5launchid']); @@ -78,6 +62,30 @@ public static function tearDownAfterClass(): void protected function setUp(): void { + global $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $testcourseausids, $testcoursesessionids, $cmi5launchsettings; + + // Make a fake cmi5 launch record. + $cmi5launchid = maketestcmi5launch(); + + $cmi5launchsettings = array("cmi5launchtenanttoken" => "Testtoken", "cmi5launchplayerurl" => "http://test/launch.php", "cmi5launchcustomacchp" => "http://testhomepage.com"); + + // Override global variable and function so that it returns test data. + $USER = new \stdClass(); + $USER->username = "testname"; + $USER->id = 10; + + // Make test course, AUs and sessions. + $testcourseid = maketestcourse($cmi5launchid); + $testcourseausids = maketestaus($testcourseid); + $testcoursesessionids = maketestsessions(); + + // Assign the sessions to AUs. + $newaus = assign_sessions_to_aus($testcourseausids, $testcoursesessionids); + + //what are the testcourseauids here> + + // Assign the AUs to the course. + assign_aus_to_courses($testcourseid, $testcourseausids); } protected function tearDown(): void @@ -360,7 +368,14 @@ public function testcmi5launch_check_user_grades_for_updates() { global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + // what is cmi5launch here in test? + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + $auids = json_decode($userscourse->aus); // So the systems under test does a lot, may need refactoring // But we will need the testcourseid and userid to get the"usercourse" @@ -369,32 +384,194 @@ public function testcmi5launch_check_user_grades_for_updates() // well then again it already is a lot of calls so maybe its fine // I dunno, my brain says its frisday + // Array to return + $returnvalue = array(0 => array( + "lmsid" => array( + "Title of AU" => 80, + "Title of AU2" => 100), + ), + 1 => array( + "overallgrade" => array( + "0" => 80, + "1" => 100), + ), + ); + // so lets start by pretending to call finction under test // I have to start somewhere + // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') + ->onlyMethods(array('cmi5launch_update_au_for_user_grades')) + ->getMock(); + + + // Ok, the arrgs ar the same, the name is the same. What is YES! its not calling the mocked class!!!!! + // Mock returns json encoded data, as it would be from the player. + $mockedclass->expects($this->once()) + ->method('cmi5launch_update_au_for_user_grades') + ->with($auids, $USER) + ->willReturn($returnvalue); + // Bring in functions and classes. + $gradehelper = new grade_helpers; + + // Functions from other classes. + $checkusergrades = $mockedclass->get_cmi5launch_check_user_grades_for_updates(); + + $result = $checkusergrades($USER); + + // it should return only the overall grade, the other grades being for updating the records in DB. + $this->assertEquals($returnvalue[1], $result); + // IT should be an array + $this->assertIsArray($result); + + + } + + /* + * Test of the cmi5launch_highest_grade method. + * This one tests if their are no rades for updates + * @return void + */ + public function testcmi5launch_check_user_grades_for_updates_no_grade() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + + // If we pass it the wrong user id then it cant find the usercourse and we can test that path. + $USER->id = 100; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Retrieve the record. + // $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + // $auids = json_decode($userscourse->aus); + // So the systems under test does a lot, may need refactoring + // But we will need the testcourseid and userid to get the"usercourse" + // Ah the user course has aus in it and the function grabs them, then for each au grabs their session + // huhthese should be refactored into smaller functions. + // well then again it already is a lot of calls so maybe its fine + // I dunno, my brain says its frisday + + // so lets start by pretending to call finction under test + // I have to start somewhere + // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. + // Bring in functions and classes. $gradehelper = new grade_helpers; // Functions from other classes. - $updatesession = $gradehelper->get_cmi5launch_check_user_grades_for_updates(); + $checkusergrades = $gradehelper->get_cmi5launch_check_user_grades_for_updates(); - $result = $updatesession($USER); + $returnvalue = "No grades to update. No record for user found in this course."; + + $result = $checkusergrades($USER); + + // it should return only the overall grade, the other grades being for updating the records in DB. + $this->assertEquals($returnvalue, $result[0]); + // IT should be an array + $this->assertIsArray($result); + + } + /* + * Test of the cmi5launch_highest_grade method. + * This one tests if something goes wrong, and throws an exception + * @return void + */ + public function testcmi5launch_check_user_grades_for_updates_excep() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); - // Ok once called the function checks that the user course with - // cmi5launch->courseid and user->id exists - // If it doesn't returns false (this will be a separate test) - // if it does, continue... - // continuing on we retrieve it - // if usercourse is null (as oppossed to doesn't exit???) then they have net participaated in course yet and it return overall grade - // Except I found an error BECAUSE overall grade doesn't exist yet! - // So either it never fins null or and I need to nix that path, - // OR im lucky and need to declare overall elsewher - // wait, either way yhtis is lucky, how has this never thrown error? A usercourse cant be null right? Then it would have just been false. - // If yep, I think this whole branch is phony baloeny, not the return part, that is for EVERYONE, but the null part, there is no null + $auids = json_decode($userscourse->aus); + + // $auids = json_decode($userscourse->aus); + // so lets start by pretending to call finction under test + // I have to start somewhere + // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') + ->onlyMethods(array('cmi5launch_update_au_for_user_grades')) + ->getMock(); + + // If it returns null this should throw a null error exception. + $mockedclass->expects($this->once()) + ->method('cmi5launch_update_au_for_user_grades') + ->with($auids, $USER) + ->willReturn(null); + + // Functions from other classes. + $checkusergrades = $mockedclass->get_cmi5launch_check_user_grades_for_updates(); - // Ok, moving on the usercoure->aus are decoded, so we will want to make our test usercourse have aus + // Expected exceptions + $expected = " Error in updating or checking user grades. Report this error to system administrator: Error in checking user grades: Trying to access array offset on null"; + + + // Expected exceptions and messages + + $result = $checkusergrades($USER); + + // Because this exception is thrown by the error handler, not the SUT, test the output to ensure right exception was thrown. + $this->expectOutputString($expected); + + } + + /* + * Test of the cmi5launch_update_au_for_user_grades method. + + * @return void + */ + public function testcmi5launch_update_au_for_user_grades() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + // global $session_helper; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + + // The problem is that AUS needs to be an array of numbers and ita + // an actual au + $auids = json_decode($userscourse->aus); + + // So the systems under test does a lot, may need refactoring + // But we will need the testcourseid and userid to get the"usercourse" + // Ah the user course has aus in it and the function grabs them, then for each au grabs their session + // huhthese should be refactored into smaller functions. + // well then again it already is a lot of calls so maybe its fine + // I dunno, my brain says its frisday + + // Session to return to return + $returnvalue = new \stdClass(); + $returnvalue->iscompleted = 1; + $returnvalue->ispassed = 1; + $returnvalue->isterminated = 1; + $returnvalue->score = 80; + + // so lets start by pretending to call finction under test + // I have to start somewhere + // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') + ->addMethods(array('cmi5launch_get_update_session')) + ->getMock(); + + // Mock returns json encoded data, as it would be from the player. + $mockedclass->expects($this->once()) + ->method('cmi5launch_get_update_session'); + //->with($auids, $USER) + // ->willReturn($returnvalue); + + + // Bring in functions and classes. + $gradehelper = new grade_helpers; + + // Functions from other classes. + $updateau = $mockedclass->get_cmi5launch_update_au_for_user_grades(); - // B + $result = $updateau($auids, $USER); + // Because this exception is thrown by the error handler, not the SUT, test the output to ensure right exception was thrown. + $this->expectOutputString("test"); } } diff --git a/version.php b/version.php index e5b7f56..03d7656 100755 --- a/version.php +++ b/version.php @@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2024061115; // The current module version (Date: YYYYMMDDXX). +$plugin->version = 2024072517; // The current module version (Date: YYYYMMDDXX). $plugin->requires = 2015111000; // Requires Moodle 3.0 version. $plugin->cron = 0; // Period for cron to check this module (secs). diff --git a/view.php b/view.php index 7fde793..62c29d0 100755 --- a/view.php +++ b/view.php @@ -210,7 +210,7 @@ function mod_cmi5launch_launchexperience(registrationInfo) { $auscores = array(); // Query CMI5 player for updated registration info. -$registrationinfofromcmi5 = $getregistrationinfo($registrationid, $cmi5launch->id); +$registrationinfofromcmi5 = json_decode($getregistrationinfo($registrationid, $cmi5launch->id), true); // Take only info about AUs out of registrationinfofromcmi5. $ausfromcmi5 = array_chunk($registrationinfofromcmi5["metadata"]["moveOn"]["children"], 1, true); From 5e5de75a04412ab850c709087f9164a4c33e61fd Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Tue, 6 Aug 2024 14:07:50 -0400 Subject: [PATCH 02/10] Finished updating gradehelpers with try/catch and new unit tests --- classes/local/au_helpers.php | 4 +- classes/local/cmi5_connectors.php | 1 - classes/local/errorover.php | 24 +- classes/local/grade_helpers.php | 202 +++++++------- cmi5PHP/tests/cmi5TestHelpers.php | 224 +++++++++------- cmi5PHP/tests/grade_helpersTest.php | 392 +++++++++++++++++++--------- 6 files changed, 521 insertions(+), 326 deletions(-) diff --git a/classes/local/au_helpers.php b/classes/local/au_helpers.php index 5911bce..4ecdbff 100644 --- a/classes/local/au_helpers.php +++ b/classes/local/au_helpers.php @@ -31,6 +31,7 @@ global $CFG; // Include the errorover (error override) funcs. require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); +//require 'errorover.php'; defined('MOODLE_INTERNAL') || die(); @@ -166,7 +167,7 @@ public function cmi5launch_save_aus($auobjectarray) try { // Make a newrecord to save. $newrecord = new \stdClass(); - + // Assign the values to the new record. $newrecord->userid = $USER->id; $newrecord->attempt = $auobject->attempt; @@ -195,7 +196,6 @@ public function cmi5launch_save_aus($auobjectarray) $newrecord->satisfied = $auobject->satisfied; $newrecord->moodlecourseid = $cmi5launch->id; - // Save the record and get the new id. $newid = $DB->insert_record($table, $newrecord, true); diff --git a/classes/local/cmi5_connectors.php b/classes/local/cmi5_connectors.php index 1f3ab33..8a51360 100755 --- a/classes/local/cmi5_connectors.php +++ b/classes/local/cmi5_connectors.php @@ -27,7 +27,6 @@ use mod_cmi5launch\local\cmi5launch_helpers; // Include the errorover (error override) funcs. require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); - class cmi5_connectors { public function cmi5launch_get_create_tenant() { diff --git a/classes/local/errorover.php b/classes/local/errorover.php index fc0075c..bf44700 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -80,19 +80,19 @@ function array_chunk_warning($errno, $errstr, $errfile, $errline) throw new nullException('Cannot parse array. Error: ' . $errstr, 0); // exit; } + /// Ok, this i a different error handler + function grade_warning($errno, $errstr, $errfile, $errline) + { + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; + //echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + + throw new nullException('Error in checking user grades: ' . $errstr, 0); + // exit; + } - /// Ok, this i a different error handler - function grade_warning($errno, $errstr, $errfile, $errline) - { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - - throw new nullException('Error in checking user grades: ' . $errstr, 0); - // exit; - } /** * Define a custom exception class, this will make pour tests meaningful diff --git a/classes/local/grade_helpers.php b/classes/local/grade_helpers.php index 4d7ef2e..a63bc37 100644 --- a/classes/local/grade_helpers.php +++ b/classes/local/grade_helpers.php @@ -26,8 +26,7 @@ defined('MOODLE_INTERNAL') || die(); use mod_cmi5launch\local\session_helpers; -use mod_cmi5launch\local\au_helpers; - +require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); class grade_helpers { @@ -122,24 +121,25 @@ public function cmi5launch_highest_grade($scores) // Now apply intval. $highestgrade = intval($highestgrade); + + return $highestgrade; } + /** * Parses and retrieves AUs and their sessions from the returned info from CMI5 player and LRS and updates them. * @param array $user - the user whose grades are being updated. * @return array */ - public function cmi5launch_check_user_grades_for_updates($user) + public function cmi5launch_check_user_grades_for_updates($user, ) { global $cmi5launch, $USER, $DB; - // what is cmi5launch here in actual func? - - // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. - set_error_handler('mod_cmi5launch\local\grade_warning', E_WARNING); - set_exception_handler('mod_cmi5launch\local\exception_grade'); + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\grade_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_grade'); // Check if record already exists. $exists = $DB->record_exists('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $user->id]); @@ -153,11 +153,10 @@ public function cmi5launch_check_user_grades_for_updates($user) $auids = json_decode($userscourse->aus); + // Bring in functions and classes. + $sessionhelper = new session_helpers; - - // Update the AUs - $returnedinfo = $this->cmi5launch_update_au_for_user_grades($auids, $user); - + $returnedinfo = $this->cmi5launch_update_au_for_user_grades($sessionhelper, $auids, $user); // Array to hold AU scores. $auscores = $returnedinfo[0]; $overallgrade = $returnedinfo[1]; @@ -165,7 +164,6 @@ public function cmi5launch_check_user_grades_for_updates($user) // Update course record. $userscourse->ausgrades = json_encode($auscores); $DB->update_record("cmi5launch_usercourse", $userscourse); - //echo" 1 branch"; // Restore default hadlers. restore_exception_handler(); restore_error_handler(); @@ -174,12 +172,11 @@ public function cmi5launch_check_user_grades_for_updates($user) } else { - // Should we return SOMEthing> $nograde = array(0 => 'No grades to update. No record for user found in this course.'); // Do nothing, there is no record for this user in this course. // Restore default hadlers. - restore_exception_handler(); - restore_error_handler(); + restore_exception_handler(); + restore_error_handler(); return $nograde; } @@ -199,118 +196,127 @@ public function cmi5launch_check_user_grades_for_updates($user) * @param mixed $auid * @return au|bool */ - public function cmi5launch_update_au_for_user_grades($auids, $user) + public function cmi5launch_update_au_for_user_grades($session_helpers, $auids, $user) { global $cmi5launch, $USER, $DB; - echo " HELLO I SHOULDN'T BE HERE?"; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\grade_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_grade'); + // Array to hold AU scores. $auscores = array(); $overallgrade = array(); + try { + // Bring in functions and classes. + $sessionhelper = $session_helpers; + // Functions from other classes. + $updatesession = $sessionhelper->cmi5launch_get_update_session(); - // Bring in functions and classes. - $sessionhelper = new session_helpers; + // Go through each Au, each Au will be responsible for updating its own session. + foreach ($auids as $key => $auid) { - // Functions from other classes. - $updatesession = $sessionhelper->cmi5launch_get_update_session(); + // Array to hold session scores for update. + $sessiongrades = array(); - // Go through each Au, each Au will be responsible for updating its own session. - foreach ($auids as $key => $auid) { - - - // Array to hold session scores for update. - $sessiongrades = array(); + // This uses the auid to pull the right record from the aus table. + $aurecord = $DB->get_record('cmi5launch_aus', ['id' => $auid]); - // This uses the auid to pull the right record from the aus table. - $aurecord = $DB->get_record('cmi5launch_aus', ['id' => $auid]); + // When it is null it is because the user has not launched the AU yet. + if (!$aurecord == null || false) { - // When it is null it is because the user has not launched the AU yet. - if (!$aurecord == null || false) { + // Check if there are sessions. + if (!$aurecord->sessions == null) { - // Check if there are sessions. - if (!$aurecord->sessions == null) { + // Retrieve session ids for this course. There may be more than one session. + $sessions = json_decode($aurecord->sessions, true); - // Retrieve session ids for this course. There may be more than one session. - $sessions = json_decode($aurecord->sessions, true); - - // Iterate through each session. - foreach ($sessions as $sessionid) { + // Iterate through each session. + foreach ($sessions as $sessionid) { - // Using current session id, retrieve session from DB. - $session = $DB->get_record('cmi5launch_sessions', ['sessionid' => $sessionid]); + // Using current session id, retrieve session from DB. + $session = $DB->get_record('cmi5launch_sessions', ['sessionid' => $sessionid]); - // the update session is call the ission - // we need to moick IT + // Retrieve new info (if any) from CMI5 player and LRS on session. + $session = $updatesession($sessionid, $cmi5launch->id, $user); - // Retrieve new info (if any) from CMI5 player and LRS on session. - $session = $updatesession($sessionid, $cmi5launch->id, $user); + // Now if the session is complete, passed, or terminated, we want to update the AU. + // These come in order, so the last one is the current status, so update on each one, + // overwrite as you go, and the last one if final. + if ($session->iscompleted == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->completed = 1; + } + if ($session->ispassed == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->passed = 1; + } + if ($session->isterminated == 1) { + // 0 is no 1 is yes, these are from players + $aurecord->terminated = 1; + } - // Now if the session is complete, passed, or terminated, we want to update the AU. - // These come in order, so the last one is the current status, so update on each one, - // overwrite as you go, and the last one if final. - if ($session->iscompleted == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->completed = 1; - } - if ($session->ispassed == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->passed = 1; - } - if ($session->isterminated == 1) { - // 0 is no 1 is yes, these are from players - $aurecord->terminated = 1; + // Add the session grade to array. + $sessiongrades[] = $session->score; } + // Save the session scores to AU, it is ok to overwrite. + $aurecord->scores = json_encode($sessiongrades, JSON_NUMERIC_CHECK); - // Add the session grade to array. - $sessiongrades[] = $session->score; - } - // Save the session scores to AU, it is ok to overwrite. - $aurecord->scores = json_encode($sessiongrades, JSON_NUMERIC_CHECK); - - // Determine gradetype and use it to save overall grade to AU. - $gradetype = $cmi5launchsettings["grademethod"]; - - switch ($gradetype) { - - // GRADE_AUS_CMI5 = 0. - // GRADE_HIGHEST_CMI5 = 1. - // GRADE_AVERAGE_CMI5 = 2. - // GRADE_SUM_CMI5 = 3. - - case 1: - $aurecord->grade = $this->cmi5launch_highest_grade($sessiongrades); - break; - case 2: - $aurecord->grade = $this->cmi5launch_average_grade($sessiongrades); - break; - default: - echo "Gradetype not found."; - } + // Determine gradetype and use it to save overall grade to AU. + $gradetype = $cmi5launchsettings["grademethod"]; + + switch ($gradetype) { - // Save AU scores to corresponding title. - $auscores[$aurecord->lmsid] = array($aurecord->title => $aurecord->scores); + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. - // Save an overall grade to array to be passed out to grade_update. - $overallgrade[] = $aurecord->grade; + case 1: + $aurecord->grade = $this->cmi5launch_highest_grade($sessiongrades); + break; + case 2: + $aurecord->grade = $this->cmi5launch_average_grade($sessiongrades); + break; + default: + echo("Gradetype not found."); + } - // Save Au title and their scores to AU. - // Save updates to DB. - $aurecord = $DB->update_record('cmi5launch_aus', $aurecord); + // Save AU scores to corresponding title. + $auscores[$aurecord->lmsid] = array($aurecord->title => $aurecord->scores); + // Save an overall grade \to be passed out to grade_update. + $overallgrade = $aurecord->grade; + + + // Save Au title and their scores to AU. + // Save updates to DB. + $aurecord = $DB->update_record('cmi5launch_aus', $aurecord); + + } } } - } - //Array to hold answer - $toreturn = array(0 => $auscores, 1 => $overallgrade); - // we need to return auscores and overallgrade. + // Array to hold answer. + $toreturn = array(0 => $auscores, 1 => $overallgrade); - return $toreturn; - } + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + return $toreturn; + } catch (\Throwable $e) { + + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); + + // If there is an error, return the error. + throw new nullException(" Error in updating or checking user grades. Report this error to system administrator: ". $e->getMessage()); + } + } } diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php index 60acc02..c867dbc 100644 --- a/cmi5PHP/tests/cmi5TestHelpers.php +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -5,7 +5,7 @@ use mod_cmi5launch\local\au; use mod_cmi5launch\local\au_helpers; use mod_cmi5launch\local\session; -use mod_cmi5launch\local\session_helpers; +//use mod_cmi5launch\local\session_helpers; use PHPUnit\Framework\TestCase; @@ -221,10 +221,10 @@ function maketestaus ($testcourseid) 'attempt' => $i, 'url' => 'url' . $i, 'type' => 'type' . $i, - 'lmsid' => 'lmsid' . $i, + 'lmsId' => 'lmsid' . $i, 'grade' => 'grade' . $i, 'scores' => 'scores' . $i, - 'title' => array(0 => array('text' => $i)), + 'title' => array(0 => array('text' => 'The title text ' . $i)), 'moveon' => 'moveon' . $i, 'auindex' => $i, 'parents' => 'parents' . $i, @@ -250,7 +250,12 @@ function maketestaus ($testcourseid) // maybe if we put the sessions here then we can skip the whole resaving thing $testcoursesessionids = maketestsessions(); $mockvalues['sessions'] = $testcoursesessionids; - $aus[] = new au($mockvalues); + + + $newau = new au($mockvalues); + + + $aus[] = $newau; } // Now save the fake aus to the test database @@ -259,7 +264,6 @@ function maketestaus ($testcourseid) - // Save AUs to test DB and save IDs. $newauids = $saveau($aus); @@ -455,7 +459,6 @@ function assign_sessions_to_aus($auids, $sessionids){ $check = $DB->record_exists( 'cmi5launch_aus', ['id' => $auid], '*', IGNORE_MISSING); - if (!$check) { // If check is negative, the record does not exist. Throw error. echo "

Error attempting to get AU data from DB. Check AU id.

"; @@ -475,6 +478,8 @@ function assign_sessions_to_aus($auids, $sessionids){ + // what is au record? + // Save the AU back to the DB. $success = $DB->update_record('cmi5launch_aus', $au); @@ -540,90 +545,7 @@ function assign_aus_to_courses($courseids, $auids){ } } -// New class -class session_helpers2 -{ - /** - * Creates a session record in DB. - * @param mixed $sessionid - the session id - * @param mixed $launchurl - the launch url - * @param mixed $launchmethod - the launch method - * @return void - */ - public function cmi5launch_create_sessio6n($sessionid, $launchurl, $launchmethod) { - - global $DB, $CFG, $cmi5launch, $USER; - - $table = "cmi5launch_sessions"; - - // Make a new record to save. - $newrecord = new \stdClass(); - // Because of many nested properties, needs to be done manually. - $newrecord->sessionid = $sessionid; - $newrecord->launchurl = $launchurl; - $newrecord->tenantname = $USER->username; - $newrecord->launchmethod = $launchmethod; - // I think here is where we eed to implement : moodlecourseid - $newrecord->moodlecourseid = $cmi5launch->id; - // And userid! - $newrecord->userid = $USER->id; - - // Save record to table. - $newid = $DB->insert_record($table, $newrecord, true); - - // Return value - return $newid; - - } - // So now all the test has to do is inject THIS which will return as we please - // We will inject the damn func! // inject this one! -function updatesessione($sessionid, $cmi5launchid, $user) -{ - global $DB, $cmi5launch, $USER; - - // Make new sessions, lets make five. - $sessions = array(); - $sessionids = array(); - - - $sessionids[] = $sessionid; - //$toaddtostring = strval($i); - // Add i to each value so the AUs are unique. - // Mock values to make sessions. - $mockvalues = array( - 'id' => $sessionid, - 'sessionid' => 'sessionid' . $sessionid, - 'userid' => 'userid' . $sessionid, - 'moodlecourseid' => 'moodlecourseid' . $sessionid, - 'registrationscoursesausid' => 'registrationscoursesausid' . $sessionid, - 'tenantname' => 'tenantname' . $sessionid, - 'createdat' => 'createdat' . $sessionid, - 'updatedat' => 'updatedat' . $sessionid, - 'code' => 'code' . $sessionid, - 'launchtokenid' => 'launchtokenid' . $sessionid, - 'lastrequesttime' => 'lastrequesttime' . $sessionid, - 'launchmode' => 'launchmode' . $sessionid, - 'masteryscore' => 'masteryscore' . $sessionid, - 'score' => 'score' . $sessionid, - 'islaunched' => 'islaunched' . $sessionid, - 'isinitialized' => 'isinitialized' . $sessionid, - 'duration' => 'duration' . $sessionid, - 'iscompleted' => 'iscompleted' . $sessionid, - 'ispassed' => 'ispassed' . $sessionid, - 'isfailed' => 'isfailed' . $sessionid, - 'isterminated' => 'isterminated' . $sessionid, - 'isabandoned' => 'isabandoned' . $sessionid, - 'progress' => 'progress' . $sessionid, - 'launchmethod' => 'launchmethod' . $sessionid, - 'launchurl' => 'launchurl' . $sessionid, - ); - $newsession = new session($mockvalues); - echo " Ive been calllled"; - return $newsession; - } - - } @@ -685,4 +607,128 @@ function cmi5launch_test_stream_and_send_excep($options, $url) throw new \Exception('test error'); } + // Ok lets make a new strea_helpers to override the other and enable testing + // New class +class session_helpers +{ + public function cmi5launch_get_create_session() { + return [$this, 'cmi5launch_create_session']; + } + + public function cmi5launch_get_update_session() { + return [$this, 'cmi5launch_update_sessions']; + } + + public function cmi5launch_get_retrieve_sessions_from_db() { + return [$this, 'cmi5launch_retrieve_sessions_from_db']; + } + + /** + * Gets updated session information from CMI5 player + * @param mixed $sessionid - the session id + * @param mixed $cmi5id - cmi5 instance id + * @return + */ + public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { + + + // Ok, lets make sure we are calling THIS one + // And not the other one + // echo "Calling from duplicate class!"; + $returnvalue = new \stdClass(); + $returnvalue->iscompleted = 1; + $returnvalue->ispassed = 1; + $returnvalue->isterminated = 1; + + $returnvalue->score = 80; + + // $returnvalue->score = 80; + return $returnvalue; + } + + /** + * Creates a session record in DB. + * @param mixed $sessionid - the session id + * @param mixed $launchurl - the launch url + * @param mixed $launchmethod - the launch method + * @return void + */ + public function cmi5launch_create_session($sessionid, $launchurl, $launchmethod) { + + global $DB, $CFG, $cmi5launch, $USER; + + $table = "cmi5launch_sessions"; + + // Make a new record to save. + $newrecord = new \stdClass(); + // Because of many nested properties, needs to be done manually. + $newrecord->sessionid = $sessionid; + $newrecord->launchurl = $launchurl; + $newrecord->tenantname = $USER->username; + $newrecord->launchmethod = $launchmethod; + // I think here is where we eed to implement : moodlecourseid + $newrecord->moodlecourseid = $cmi5launch->id; + // And userid! + $newrecord->userid = $USER->id; + + // Save record to table. + $newid = $DB->insert_record($table, $newrecord, true); + + // Return value + return $newid; + + } + + /** + * Retrieves session from DB + * @param mixed $sessionid - the session id + * @return session + */ + public function cmi5launch_retrieve_sessions_from_db($sessionid) { + + global $DB, $CFG; + + $check = $DB->record_exists('cmi5launch_sessions', ['sessionid' => $sessionid], '*', IGNORE_MISSING); + + // If check is negative, the record does not exist. Throw error. + if (!$check) { + + echo "

Error attempting to get session data from DB. Check session id.

"; + echo "
";
+            var_dump($sessionid);
+            echo "
"; + + } else { + + $sessionitem = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + $session = new session($sessionitem); + + } + + // Return new session object. + return $session; + } + +} + +// To pass to the test and cause exception +class session_helpers2 +{ + /** + * Gets updated session information from CMI5 player and throw exception + * @param mixed $sessionid - the session id + * @param mixed $cmi5id - cmi5 instance id + * @return + */ + public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { + + // $returnvalue->score = 80; + return null; + } +} + + + + ?> diff --git a/cmi5PHP/tests/grade_helpersTest.php b/cmi5PHP/tests/grade_helpersTest.php index 2a808a3..a18304e 100644 --- a/cmi5PHP/tests/grade_helpersTest.php +++ b/cmi5PHP/tests/grade_helpersTest.php @@ -2,13 +2,9 @@ namespace cmi5Test; -use Exception; use mod_cmi5launch\local\grade_helpers; use mod_cmi5launch\local\nullException; -use mod_cmi5launch\local\fieldException; use PHPUnit\Framework\TestCase; -use mod_cmi5launch\local\cmi5_connectors; - require_once( "cmi5TestHelpers.php"); @@ -34,23 +30,10 @@ public static function tearDownAfterClass(): void { global $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $testcourseausids, $testcoursesessionids, $cmi5launchsettings; - // Delete the test record. - // deletetestcmi5launch($cmi5launchid); - - // Delete the test course. - // deletetestcmi5launch_usercourse($cmi5launchid); - - // Delete the test AUs. - // deletetestcmi5launch_aus($testcourseausids); - - // Delete the test sessions. - // deletetestcmi5launch_sessions($testcoursesessionids); - - + // Restore overridden global variable. unset($GLOBALS['USER']); - // unset($GLOBALS['DB']); unset($GLOBALS['cmi5launchsettings']); unset($GLOBALS['cmi5launch']); unset($GLOBALS['cmi5launchid']); @@ -67,7 +50,24 @@ protected function setUp(): void // Make a fake cmi5 launch record. $cmi5launchid = maketestcmi5launch(); - $cmi5launchsettings = array("cmi5launchtenanttoken" => "Testtoken", "cmi5launchplayerurl" => "http://test/launch.php", "cmi5launchcustomacchp" => "http://testhomepage.com"); + // Assign the new id as the cmi5launch id. + $cmi5launch->id = $cmi5launchid; + // Update db + $DB->update_record('cmi5launch', $cmi5launch); + // Gradetypes for ref + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + $cmi5launchsettings = array( + "cmi5launchlrsendpoint" => "Test LRS point", + "cmi5launchlrslogin" => "Test LRS login", + "cmi5launchlrspass" => "Test LRS password", + "cmi5launchtenanttoken" => "Testtoken", + "cmi5launchplayerurl" => "http://test/launch.php", + "cmi5launchcustomacchp" => "http://testhomepage.com", + "grademethod" => 1 + ); // Override global variable and function so that it returns test data. $USER = new \stdClass(); @@ -75,22 +75,20 @@ protected function setUp(): void $USER->id = 10; // Make test course, AUs and sessions. - $testcourseid = maketestcourse($cmi5launchid); + $testcourseid = maketestcourse($cmi5launchid); $testcourseausids = maketestaus($testcourseid); $testcoursesessionids = maketestsessions(); // Assign the sessions to AUs. $newaus = assign_sessions_to_aus($testcourseausids, $testcoursesessionids); - //what are the testcourseauids here> - // Assign the AUs to the course. assign_aus_to_courses($testcourseid, $testcourseausids); - } + } protected function tearDown(): void { - + } @@ -106,10 +104,9 @@ public function testcmi5launch_average_grade_multiple() // Scores as an array. $scoresarray = array (1,2,3,4,5); - // Scores as a (json_encoded) string. - $scoresstring = json_encode($scoresarray); + // Scores as a (json_encoded) string. + $scoresstring = json_encode($scoresarray); - // So the average of either should be. $average = 3; @@ -133,6 +130,7 @@ public function testcmi5launch_average_grade_multiple() $this->assertIsInt($resultarray); } + /* * Test of the cmi5launch_average_grade method. * This takes scores and averages them. @@ -141,7 +139,6 @@ public function testcmi5launch_average_grade_multiple() */ public function testcmi5launch_average_grade_singular() { - // Scores as an array. $scoresarray = array (0 => 3); @@ -359,9 +356,8 @@ public function testcmi5launch_highest_grade_zero() } /* - * Test of the cmi5launch_highest_grade method. - * This takes scores and returns the highest one of them. - * // We need to test with scores being a 0 and string and int. + * Test of the cmi5launch_check_user_grades_for_updates method. + * Parses and retrieves AUs and their sessions from the returned info from CMI5 player and LRS and updates them. * @return void */ public function testcmi5launch_check_user_grades_for_updates() @@ -377,14 +373,8 @@ public function testcmi5launch_check_user_grades_for_updates() $auids = json_decode($userscourse->aus); - // So the systems under test does a lot, may need refactoring - // But we will need the testcourseid and userid to get the"usercourse" - // Ah the user course has aus in it and the function grabs them, then for each au grabs their session - // huhthese should be refactored into smaller functions. - // well then again it already is a lot of calls so maybe its fine - // I dunno, my brain says its frisday - // Array to return + // Array to return. $returnvalue = array(0 => array( "lmsid" => array( "Title of AU" => 80, @@ -397,20 +387,19 @@ public function testcmi5launch_check_user_grades_for_updates() ), ); - // so lets start by pretending to call finction under test - // I have to start somewhere // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') ->onlyMethods(array('cmi5launch_update_au_for_user_grades')) ->getMock(); - - // Ok, the arrgs ar the same, the name is the same. What is YES! its not calling the mocked class!!!!! + $sessionhelper = new \mod_cmi5launch\local\session_helpers;; + // Mock returns json encoded data, as it would be from the player. $mockedclass->expects($this->once()) ->method('cmi5launch_update_au_for_user_grades') - ->with($auids, $USER) + ->with($sessionhelper, $auids, $USER) ->willReturn($returnvalue); + // Bring in functions and classes. $gradehelper = new grade_helpers; @@ -419,17 +408,16 @@ public function testcmi5launch_check_user_grades_for_updates() $result = $checkusergrades($USER); - // it should return only the overall grade, the other grades being for updating the records in DB. + // It should return only the overall grade, the other grades being for updating the records in DB. $this->assertEquals($returnvalue[1], $result); - // IT should be an array + // It should be an array. $this->assertIsArray($result); - } - /* - * Test of the cmi5launch_highest_grade method. - * This one tests if their are no rades for updates + /* + * Test of the cmi5launch_check_user_grades_for_updates method. + * This one tests if their are no grades for updates. * @return void */ public function testcmi5launch_check_user_grades_for_updates_no_grade() @@ -439,22 +427,7 @@ public function testcmi5launch_check_user_grades_for_updates_no_grade() // If we pass it the wrong user id then it cant find the usercourse and we can test that path. $USER->id = 100; $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - // Retrieve the record. - // $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); - - // $auids = json_decode($userscourse->aus); - - // So the systems under test does a lot, may need refactoring - // But we will need the testcourseid and userid to get the"usercourse" - // Ah the user course has aus in it and the function grabs them, then for each au grabs their session - // huhthese should be refactored into smaller functions. - // well then again it already is a lot of calls so maybe its fine - // I dunno, my brain says its frisday - - // so lets start by pretending to call finction under test - // I have to start somewhere - // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. - + // Bring in functions and classes. $gradehelper = new grade_helpers; @@ -465,14 +438,14 @@ public function testcmi5launch_check_user_grades_for_updates_no_grade() $result = $checkusergrades($USER); - // it should return only the overall grade, the other grades being for updating the records in DB. + // It should return only the overall grade, the other grades being for updating the records in DB. $this->assertEquals($returnvalue, $result[0]); - // IT should be an array + // It should be an array. $this->assertIsArray($result); } /* - * Test of the cmi5launch_highest_grade method. + * Test of the cmi5launch_check_user_grades_for_update. * This one tests if something goes wrong, and throws an exception * @return void */ @@ -481,99 +454,270 @@ public function testcmi5launch_check_user_grades_for_updates_excep() global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - // Retrieve the record. - $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); - $auids = json_decode($userscourse->aus); + $auids = json_decode($userscourse->aus); - // $auids = json_decode($userscourse->aus); - // so lets start by pretending to call finction under test - // I have to start somewhere // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') ->onlyMethods(array('cmi5launch_update_au_for_user_grades')) ->getMock(); - - // If it returns null this should throw a null error exception. + $sessionhelper = new \mod_cmi5launch\local\session_helpers; $mockedclass->expects($this->once()) ->method('cmi5launch_update_au_for_user_grades') - ->with($auids, $USER) + ->with($sessionhelper, $auids, $USER) ->willReturn(null); // Functions from other classes. $checkusergrades = $mockedclass->get_cmi5launch_check_user_grades_for_updates(); - // Expected exceptions + // Expected exceptions. $expected = " Error in updating or checking user grades. Report this error to system administrator: Error in checking user grades: Trying to access array offset on null"; - - - // Expected exceptions and messages $result = $checkusergrades($USER); - // Because this exception is thrown by the error handler, not the SUT, test the output to ensure right exception was thrown. - $this->expectOutputString($expected); + // Because this exception is thrown by the error handler, not the SUT, test the output to ensure right exception was thrown. + $this->expectOutputString($expected); } - /* + /* * Test of the cmi5launch_update_au_for_user_grades method. - + * This one tests if the grades are returned highest. * @return void */ - public function testcmi5launch_update_au_for_user_grades() + public function testcmi5launch_update_au_for_user_grades_highest() { global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; - // global $session_helper; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - // Retrieve the record. - $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + $cmi5launchsettings["grademethod"] = 1; + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); - - // The problem is that AUS needs to be an array of numbers and ita - // an actual au + // Call the TEST version of session helpers to pass. + $sessionhelper = new \cmi5Test\session_helpers; + $auids = json_decode($userscourse->aus); - // So the systems under test does a lot, may need refactoring - // But we will need the testcourseid and userid to get the"usercourse" - // Ah the user course has aus in it and the function grabs them, then for each au grabs their session - // huhthese should be refactored into smaller functions. - // well then again it already is a lot of calls so maybe its fine - // I dunno, my brain says its frisday - - // Session to return to return - $returnvalue = new \stdClass(); - $returnvalue->iscompleted = 1; - $returnvalue->ispassed = 1; - $returnvalue->isterminated = 1; - $returnvalue->score = 80; - - // so lets start by pretending to call finction under test - // I have to start somewhere - // Mock a cmi5 connector object but only stub ONE method, as we want to test the others. - - $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\grade_helpers') - ->addMethods(array('cmi5launch_get_update_session')) - ->getMock(); + // Bring in functions and classes. + $gradehelper = new grade_helpers; - // Mock returns json encoded data, as it would be from the player. - $mockedclass->expects($this->once()) - ->method('cmi5launch_get_update_session'); - //->with($auids, $USER) - // ->willReturn($returnvalue); - + // Functions from other classes. + $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); + + $result = $updateau($sessionhelper, $auids, $USER); + // Result should be an array. + $this->assertIsArray($result); + + // And it is an array of two parts, one for the lmsid, and within it, the title and score of that title. + // And the second, the overall grade of ALL the aus (lmsids). + $this->assertArrayHasKey(0, $result); + // The lmsid and title will have different number endings but should all contain the same base string, + $lmsid = $result[0]; + $this->assertIsArray($lmsid); + + $this->assertStringStartsWith('lmsid', array_key_first($lmsid)); + + // Now finagle to get the title array + $title = $result[0][array_key_first($lmsid)]; + + // Shouild be an array where the title is key and the value is score + $this->assertIsArray($title); + $this->assertStringStartsWith('The title text', array_key_first($title)); + // And finally the value of the 'title' is a json string equalling all the session scores, in this case we + // made five and each was 80. + $scoresshouldbe = "[80,80,80,80,80]"; + $this->assertEquals($scoresshouldbe, $title[array_key_first($title)]); + + // And finally the overall grade which in this instance is the highest of all the scores, or just 80. + $this->assertArrayHasKey(1, $result); + $this->assertEquals(80, $result[1]); + } + + + /* + * Test of the cmi5launch_update_au_for_user_grades method. + * This one tests if the grades are returned averaged. + * @return void + */ + public function testcmi5launch_update_au_for_user_grades_average() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + // Gradetype for reference. + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + $cmi5launchsettings["grademethod"] = 2; + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + // Implement the duplicate of session helper to pass through. + $sessionhelper = new \cmi5Test\session_helpers; + + $auids = json_decode($userscourse->aus); + + // Bring in functions and classes. + $gradehelper = new grade_helpers; + + // Functions from other classes. + $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); + + $result = $updateau($sessionhelper, $auids, $USER); + + $this->assertIsArray($result); - // Bring in functions and classes. + // And it is an array of two parts, one for the lmsid, and within it, the title and score of that title + // and the second, the overall grade of ALL the aus (lmsids). + $this->assertArrayHasKey(0, $result); + // The lmsid and title will have different number endings but should all contain the same base string, + $lmsid = $result[0]; + $this->assertIsArray($lmsid); + + $this->assertStringStartsWith('lmsid', array_key_first($lmsid)); + + // Now finagle to get the title array. + $title = $result[0][array_key_first($lmsid)]; + + // Should be an array where the title is key and the value is score. + $this->assertIsArray($title); + $this->assertStringStartsWith('The title text', array_key_first($title)); + + // And finally the value of the 'title' is a json string equalling all the session scores, in this case we + // made five and each was 80. + $scoresshouldbe = "[80,80,80,80,80]"; + $this->assertEquals($scoresshouldbe, $title[array_key_first($title)]); + + // And finally the overall grade which in this instance is the average to all the scores, or just 80. + $this->assertArrayHasKey(1, $result); + + $this->assertEquals(80, $result[1]); + } + + + /* + * Test of the cmi5launch_update_au_for_user_grades method. + * Tests the function if there is a bad gradetype selected (this shouldn't ever happen). + * @return void + */ + public function testcmi5launch_update_au_for_user_grades_bad_gradetype() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + // Gradetype for ref, passing in invalid value. + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + $cmi5launchsettings["grademethod"] = 6; + + // Retrieve the record. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $cmi5launch->courseid, 'userid' => $USER->id]); + + // Implement the duplicate of session helper to pass through function. + $sessionhelper = new \cmi5Test\session_helpers; + + $auids = json_decode($userscourse->aus); + + // Bring in functions and classes. $gradehelper = new grade_helpers; // Functions from other classes. - $updateau = $mockedclass->get_cmi5launch_update_au_for_user_grades(); + $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); + + $result = $updateau($sessionhelper, $auids, $USER); + + // There will be five of these, one for each au. + $this->expectOutputString("Gradetype not found.Gradetype not found.Gradetype not found.Gradetype not found.Gradetype not found."); + + $this->assertIsArray($result); + + // And it is an array of two parts, one for the lmsid, and within it, the title and score of that title + // and the second, the overall grade of ALL the aus (lmsids). + $this->assertArrayHasKey(0, $result); + // The lmsid and title will have different number endings but should all contain the same base string, + $lmsid = $result[0]; + $this->assertIsArray($lmsid); + + $this->assertStringStartsWith('lmsid', array_key_first($lmsid)); + + // Now finagle to get the title array + $title = $result[0][array_key_first($lmsid)]; + + // Shouild be an array where the title is key and the value is score. + $this->assertIsArray($title); + $this->assertStringStartsWith('The title text', array_key_first($title)); + + // And the value of the 'title' is a json string equalling all the session scores, in this case we + // made five and each was 80. + $scoresshouldbe = "[80,80,80,80,80]"; + $this->assertEquals($scoresshouldbe, $title[array_key_first($title)]); + + // And finally the overall grade in this instance will still be 80, because that is the saved value and hasnt been changed. + $this->assertArrayHasKey(1, $result); + + $this->assertEquals(80, $result[1]); + } + + + /* + * Test of the cmi5launch_update_au_for_user_grades method. + // Testing when exception is thrown + * @return void + */ + public function testcmi5launch_update_au_for_user_grades_excep() + { + global $cmi5launch, $USER, $DB, $testcourseid, $cmi5launchsettings; + + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + // Gradetype for reference. + // GRADE_AUS_CMI5 = 0. + // GRADE_HIGHEST_CMI5 = 1. + // GRADE_AVERAGE_CMI5 = 2. + // GRADE_SUM_CMI5 = 3. + $cmi5launchsettings["grademethod"] = 1; + + // Implement the duplicate of session helper to pass through function. + $sessionhelper = new \cmi5Test\session_helpers; + + // By passing the auid as a string the function should throw an exception. + $auids = "Throw an error"; + + // Bring in functions and classes. + $gradehelper = new grade_helpers; + + // Functions from other classes. + $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); + + // The expected is built bby the two messages knowing 'title' is an empty array. + $expected = "Error in updating or checking user grades. Report this error to system administrator: Error in checking user grades:"; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage($expected); - $result = $updateau($auids, $USER); - // Because this exception is thrown by the error handler, not the SUT, test the output to ensure right exception was thrown. - $this->expectOutputString("test"); + + $result = $updateau($sessionhelper, $auids, $USER); + } + } From 109c0f7db6e3fcd24ea09e7c5bcc1400b53f7aa9 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Tue, 13 Aug 2024 09:04:35 -0400 Subject: [PATCH 03/10] Finished progress unit test and refactor --- classes/local/errorover.php | 62 ++ classes/local/progress.php | 469 ++++++----- cmi5PHP/tests/cmi5TestHelpers.php | 550 +++++++++---- cmi5PHP/tests/grade_helpersTest.php | 2 +- cmi5PHP/tests/progressTest.php | 1132 ++++++++++++++++++++++++--- 5 files changed, 1726 insertions(+), 489 deletions(-) diff --git a/classes/local/errorover.php b/classes/local/errorover.php index bf44700..ec38500 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -57,7 +57,69 @@ function exception_grade(\Throwable $exception) throw new nullException('Error in checking user grades: ' . $exception->getMessage(), 0); // exit; } +function progress_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + echo " unable to get name "; + // throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +function progresslrsreq_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function exception_progresslrsreq(\Throwable $exception) +{ + + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +function progresslrs_warning($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + + throw new nullException('Error in retrieving statements from LRS ' . $errstr, 0); + // exit; +} +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function exception_progresslrs(\Throwable $exception) +{ + + throw new nullException('Error in retrieving statements from LRS ' . $exception->getMessage(), 0); + // exit; +} function sifting_data_warning($errno, $errstr, $errfile, $errline) { // echo"Error stirn --- $errstr"; diff --git a/classes/local/progress.php b/classes/local/progress.php index 9394ba0..579633d 100644 --- a/classes/local/progress.php +++ b/classes/local/progress.php @@ -45,6 +45,12 @@ public function cmi5launch_get_request_statements_from_lrs() { * @return array */ public function cmi5launch_request_statements_from_lrs($registrationid, $session) { + + + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrs_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrs'); + // Array to hold result. $result = array(); @@ -55,14 +61,10 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session 'since' => $session->createdat, ); - // Try and retrieve statements + // Try and retrieve statements. try { - $statements = $this->cmi5launch_send_request_to_lrs($data, $session->id); -/* - if ($statements === false || $statements == null) { - throw new \Exception ("No statements found."); - } - */ + $statements = $this->cmi5launch_send_request_to_lrs('cmi5launch_stream_and_send', $data, $session->id); + // The results come back as nested array under more then statements. We only want statements, and we want them unique. $statement = array_chunk($statements["statements"], 1); @@ -75,9 +77,21 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session array_push($result, array($registrationid => $current)); } + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); + return $result; + } catch (\Throwable $e) { - echo 'Trouble retrieving statements from LRS. Caught exception: ', $e->getMessage(), "\n"; + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + + // If there is an error, return the error. + throw new nullException('Trouble retrieving statements from LRS. Caught exception: '. $e->getMessage()); + } } @@ -87,18 +101,32 @@ public function cmi5launch_request_statements_from_lrs($registrationid, $session * @param mixed $id * @return mixed */ - public function cmi5launch_send_request_to_lrs($data, $id) { + public function cmi5launch_send_request_to_lrs($cmi5launch_stream_and_send, $data, $id) { $settings = cmi5launch_settings($id); - // Url to request statements from. - $url = $settings['cmi5launchlrsendpoint'] . "statements"; - // Build query with data above. - $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + // Assign passed in function to variable. + $stream = $cmi5launch_stream_and_send; + // Make sure LRS settings are there. + try { + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + } + catch (\Throwable $e) { + + // Throw exception if settings are missing. + Throw new nullException('Unable to retrieve LRS settings. Caught exception: '. $e->getMessage() . " Check LRS settings are correct."); + } - // LRS username and password. - $user = $settings['cmi5launchlrslogin']; - $pass = $settings['cmi5launchlrspass']; + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrsreq_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrsreq'); // Use key 'http' even if you send the request to https://... // There can be multiple headers but as an array under the ONE header. @@ -113,23 +141,29 @@ public function cmi5launch_send_request_to_lrs($data, $id) { ), ), ); - // The options are here placed into a stream to be sent. - $context = stream_context_create($options); - // Sends the stream to the specified URL and stores results. - // The false is use_include_path, which we dont want in this case, we want to go to the url. try { - $result = file_get_contents($url, false, $context); - + //By calling the function this way, it enables encapsulation of the function and allows for testing. + //It is an extra step, but necessary for required PHP Unit testing. + $result = call_user_func($stream, $options, $url); + + // Decode result. $resultdecoded = json_decode($result, true); + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + return $resultdecoded; + } catch (\Throwable $e) { - echo 'Unable to communicate with LRS. Caught exception: ', $e->getMessage(), "\n"; - echo "
"; - echo " Check LRS is up, username and password are correct, and LRS endpoint is correct."; - } + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); - + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $e->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + } } /** @@ -139,9 +173,22 @@ public function cmi5launch_send_request_to_lrs($data, $id) { * @return mixed - actor */ public function cmi5launch_retrieve_actor($resultarray, $registrationid) { + + // If it fails to parse array it should throw an error, this shouldn't stop execution, but catch so we can send a better message to user. + try { + // Actor should be in statement. + $actor = $resultarray[$registrationid][0]["actor"]["account"]["name"]; + + return $actor; + + } catch (\Throwable $e) { + + // If there is an error, echo the error. - $actor = $resultarray[$registrationid][0]["actor"]["account"]["name"]; - return $actor; + echo('Unable to retrieve actor name from LRS. Caught exception: '. $e->getMessage()); + + return "(Actor name not retrieved)"; + } } /** @@ -150,31 +197,41 @@ public function cmi5launch_retrieve_actor($resultarray, $registrationid) { * @param mixed $registrationid - the registration id * @return mixed - verb */ - public function cmi5launch_retrieve_verbs($resultarray, $registrationid) { - - // Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present. - $verbinfo = $resultarray[$registrationid][0]["verb"]; - $display = array_key_exists("display", $verbinfo); - - // If it is null then there is no display, so go by verb id. - if (!$display) { - // Retrieve id. - $verbid = $resultarray[$registrationid][0]["verb"]["id"]; - - // Splits id in two on 'verbs/', we want the end which is the actual verb. - $split = explode('verbs/', $verbid); - $verb = $split[1]; - - } else { - // If it is not null then there is a language easy to read version of verb display, such as 'en' or 'en-us'. - $verblang = $resultarray[$registrationid][0]["verb"]["display"]; - // Retrieve the language. - $lang = array_key_first($verblang); - // Use it to retrieve verb. - $verb = [$verblang][0][$lang]; - } + public function cmi5launch_retrieve_verb($resultarray, $registrationid) + { + + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + + try { + // Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present. + $verbinfo = $resultarray[$registrationid][0]["verb"]; + $display = array_key_exists("display", $verbinfo); + + // If it is null then there is no display, so go by verb id. + if (!$display) { + // Retrieve id. + $verbid = $resultarray[$registrationid][0]["verb"]["id"]; - return $verb; + // Splits id in two on 'verbs/', we want the end which is the actual verb. + $split = explode('verbs/', $verbid); + $verb = $split[1]; + + } else { + // If it is not null then there is a language easy to read version of verb display, such as 'en' or 'en-us'. + $verblang = $resultarray[$registrationid][0]["verb"]["display"]; + // Retrieve the language. + $lang = array_key_first($verblang); + // Use it to retrieve verb. + $verb = [$verblang][0][$lang]; + } + + return $verb; + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve verb from LRS. Caught exception: '. $e->getMessage()); + return "(Verb not retrieved)"; + } } /** @@ -189,66 +246,58 @@ public function cmi5launch_retrieve_verbs($resultarray, $registrationid) { public function cmi5launch_retrieve_object_name($resultarray, $registrationid) { global $CFG; + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { - // First find the object, it should always be second level of statement (so third level array). - if (array_key_exists("object", $resultarray[$registrationid][0])) { - - if (array_key_exists("definition", $resultarray[$registrationid][0]["object"])) { - - // If 'definition' exists, check if 'name' does. - if (array_key_exists("name", $resultarray[$registrationid][0]["object"]["definition"])) { - - // Retrieve the name. - $objectarray = $resultarray[$registrationid][0]["object"]["definition"]["name"]; - - // There may be more than one languages string to choose from. First we want to - // select the language that matches the language of the course, then if not available, the first key. - // System language setting. - $language = $CFG->lang; - if (array_key_exists($language, $objectarray)) { - $object = $objectarray[$language]; - } else { - $defaultlanguage = array_key_first($objectarray); - $object = $objectarray[$defaultlanguage]; + // First find the object, it should always be second level of statement (so third level array). + if (array_key_exists("object", $resultarray[$registrationid][0])) { + + if (array_key_exists("definition", $resultarray[$registrationid][0]["object"])) { + + // If 'definition' exists, check if 'name' does. + if (array_key_exists("name", $resultarray[$registrationid][0]["object"]["definition"])) { + + // Retrieve the name. + $objectarray = $resultarray[$registrationid][0]["object"]["definition"]["name"]; + + // There may be more than one languages string to choose from. First we want to + // select the language that matches the language of the course, then if not available, the first key. + // System language setting. + $language = $CFG->lang; + + if (array_key_exists($language, $objectarray)) { + $object = $objectarray[$language]; + + } else { + $defaultlanguage = array_key_first($objectarray); + $object = $objectarray[$defaultlanguage]; + + } + return $object; } - return $object; - } - } else if (array_key_exists("id", $resultarray[$registrationid][0]["object"])) { + } else if (array_key_exists("id", $resultarray[$registrationid][0]["object"])) { + + // If name is missing check for id. + // Retrieve id. + $object = $resultarray[$registrationid][0]["object"]["id"]; + + return $object; - // If name is missing check for id. - // Retrieve id. - $object = $resultarray[$registrationid][0]["object"]["id"]; - return $object; + } else { + + return "(Object name not retrieved/there is no object in this statement)"; + } } else { - - // If both name and id are missing throw error. - $this->cmi5launch_statement_retrieval_error("Object name and id "); + + return "(Object name not retrieved/there is no object in this statement)"; } - - } else { - - $this->cmi5launch_statement_retrieval_error("Object "); - } - } - - /** - * Error message for statment retrieval to mark if something is missing - * @param mixed $missingitem - the missing item(s) - * @return void - */ - public function cmi5launch_statement_retrieval_error($missingitem) { - - global $CFG; - - // If admin debugging is enabled. - if ($CFG->debugdeveloper) { - - // Print that it is missing. - echo "
"; - echo $missingitem . "is missing from this statement."; - echo "
"; + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve object name from LRS. Caught exception: '. $e->getMessage()); + return "(Object name not retrieved)"; } } @@ -260,20 +309,28 @@ public function cmi5launch_statement_retrieval_error($missingitem) { */ public function cmi5launch_retrieve_timestamp($resultarray, $registrationid) { - // Verify this statement has a 'timestamp' param. - if (array_key_exists("timestamp", $resultarray[$registrationid][0])) { + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { + // Verify this statement has a 'timestamp' param. + if (array_key_exists("timestamp", $resultarray[$registrationid][0])) { - $date = new \DateTime($resultarray[$registrationid][0]["timestamp"], new \DateTimeZone('US/Eastern')); + $date = new \DateTime($resultarray[$registrationid][0]["timestamp"], new \DateTimeZone('US/Eastern')); - $date->setTimezone(new \DateTimeZone('America/New_York')); + $date->setTimezone(new \DateTimeZone('America/New_York')); - $date = $date->format('d-m-Y' . " " . 'h:i a'); + $date = $date->format('d-m-Y' . " " . 'h:i a'); - return $date; + return $date; - } else { + } else { - $this->cmi5launch_statement_retrieval_error("Timestamp "); + return "(Timestamp not retrieved or not present in statement)"; + } + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve timestamp from LRS. Caught exception: '. $e->getMessage()); + return "(Timestamp not retrieved)"; } } @@ -293,40 +350,48 @@ public function cmi5launch_retrieve_score($resultarray, $registrationid) { // Variable to hold score. $score = null; - // Verify this statement has a 'result' param. - if (array_key_exists("result", $resultarray[$registrationid][0])) { - - // If it exists, retrieve it. - $resultinfo = $resultarray[$registrationid][0]["result"]; - - $score = array_key_exists("score", $resultinfo); + // Encase the whole thing in a try catch block to catch any errors. + // If an array key isn't there it will throw a warning. It will not stop execution but catching it will enable us to send better error messages. + try { + // Verify this statement has a 'result' param. + if (array_key_exists("result", $resultarray[$registrationid][0])) { - // If it is null then the item in question doesn't exist in this statement. - if ($score) { + // If it exists, retrieve it. + $resultinfo = $resultarray[$registrationid][0]["result"]; - $score = $resultarray[$registrationid][0]["result"]["score"]; + // If it is null then the item in question doesn't exist in this statement. + if (array_key_exists("score", $resultinfo)) { - // Raw score preferred to scaled. - if ($score["raw"]) { + $score = $resultarray[$registrationid][0]["result"]["score"]; +; + // Raw score preferred to scaled. + if (array_key_exists("raw", $score)) { - $returnscore = $score["raw"]; - return $returnscore; - } else if ($score["scaled"]) { + $returnscore = $score["raw"]; + + return $returnscore; - $returnscore = round($score["scaled"], 2); - return $returnscore; + } else if (array_key_exists("scaled", $score)) { + + $returnscore = round($score["scaled"], 2); + + return $returnscore; + } } + else { + + return "(Score not retrieved or not present in statement)"; + } + } else { + + return "(Score not retrieved or not present in statement)"; } - } else { - - // If admin debugging is enabled. - if ($CFG->debugdeveloper) { - - // Print that it is missing. - echo "
"; - echo 'No score in statement with id ' .$resultarray[$registrationid][0]['id']; - echo "
"; - } + } catch (\Throwable $e) { + + // If there is an error, echo the error. + echo('Unable to retrieve score from LRS. Caught exception: '. $e->getMessage()); + + return "(Score not retrieved)"; } } @@ -345,95 +410,73 @@ public function cmi5launch_retrieve_statements($registrationid, $session) { // Array to hold score and be returned. $returnscore = 0; - $resultdecoded = $this->cmi5launch_request_statements_from_lrs($registrationid, $session); - - // We need to sort the statements by finding their session id - // parse through array 'ext' to find the one holding session id. - foreach ($resultdecoded as $singlestatement) { - - $code = $session->code; - $currentsessionid = ""; - $ext = $singlestatement[$registrationid][0]["context"]["extensions"]; - foreach ($ext as $key => $value) { + // Wrap in a try catch block to catch any errors. + try { + $resultdecoded = $this->cmi5launch_request_statements_from_lrs($registrationid, $session); + + // We need to sort the statements by finding their session id + // parse through array 'ext' to find the one holding session id. + foreach ($resultdecoded as $singlestatement) { - // If key contains "sessionid" in string. - if (str_contains($key, "sessionid")) { - $currentsessionid = $value; - } - } + $code = $session->code; + $currentsessionid = ""; - // Now if code equals currentsessionid, this is a statement pertaining to this session. - if ($code === $currentsessionid) { + // what is first array key in statement? It is the registration id. + + //// There should always be an extension but in case. + try { + $ext = $singlestatement[$registrationid][0]["context"]["extensions"]; - $actor = $this->cmi5launch_retrieve_actor($singlestatement, $registrationid); - $verb = $this->cmi5launch_retrieve_verbs($singlestatement, $registrationid); - $object = $this->cmi5launch_retrieve_object_name($singlestatement, $registrationid); - $date = $this->cmi5launch_retrieve_timestamp($singlestatement, $registrationid); - $score = $this->cmi5launch_retrieve_score($singlestatement, $registrationid); + foreach ($ext as $key => $value) { - // If a session has more than one score, we only want the highest. - if (!$score == null && $score > $returnscore) { + // If key contains "sessionid" in string. + if (str_contains($key, "sessionid")) { - $returnscore = $score; + $currentsessionid = $value; + } + } + } catch (\Throwable $e) { + // If there is an error, echo the error. + echo('Unable to retrieve session id from LRS. Caught exception: '. $e->getMessage() . ". There may not be an extension key in statement."); } - // Update to return. - $progressupdate[] = "$actor $verb $object on $date"; - - } - - } - - $session->progress = json_encode($progressupdate); - - $session->score = $returnscore; - - return $session; - } + // Now if code equals currentsessionid, this is a statement pertaining to this session. + if ($code === $currentsessionid) { - // An error message catcher. - /** - * Function to test returns from cmi5 player and display error message if found to be false - * // or not 200. - * @param mixed $resulttotest - The result to test. - * @param string $type - The type missing to be added to the error message. - * @return bool - */ - public static function cmi5launch_progress_error_message($resulttotest, $type) { - - // Decode result because if it is not 200 then something went wrong - // If it's a string, decode it. - if (is_string($resulttotest)) { - $resulttest = json_decode($resulttotest, true); - } else { - $resulttest = $resulttotest; - } + $actor = $this->cmi5launch_retrieve_actor($singlestatement, $registrationid); + $verb = $this->cmi5launch_retrieve_verb($singlestatement, $registrationid); + $object = $this->cmi5launch_retrieve_object_name($singlestatement, $registrationid); + $date = $this->cmi5launch_retrieve_timestamp($singlestatement, $registrationid); + $score = $this->cmi5launch_retrieve_score($singlestatement, $registrationid); - // I think splittin these to return two seperate messages deppennnding on whether player is running is better. - // Player cannot return an error if not runnin, - if ($resulttest === false ){ + // If a session has more than one score, we only want the highest. + if (!$score == null && $score > $returnscore) { - echo "
"; + $returnscore = $score; + } - echo "Something went wrong " . $type . ". LRS is not communicating. Is it running?"; + // Update to return. + $progressupdate[] = "$actor $verb $object on $date"; - echo "
"; + } - return false; - } - else if( array_key_exists("statusCode", $resulttest) && $resulttest["statusCode"] != 200) { + } - echo "
"; + $session->progress = json_encode($progressupdate); - echo "Something went wrong " . $type . ". CMI5 Player returned " . $resulttest["statusCode"] . " error. With message '" - . $resulttest["message"] . "'." ; - echo "
"; + $session->score = $returnscore; - return false; - } else { + return $session; - // No errors, continue. - return true; + } catch (\Throwable $e) { + + // If there is an error, echo the error. + echo('Unable to retrieve statements from LRS. Caught exception: '. $e->getMessage()); + + + return "(Statements not retrieved)"; } } + + } diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php index c867dbc..69658ca 100644 --- a/cmi5PHP/tests/cmi5TestHelpers.php +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -127,7 +127,7 @@ function deletetestcmi5launch_sessions($ids) foreach ($ids as $id) { // Delete the cmi5launch record - $DB->delete_records('cmi5launch_sessions', array('id' => $id)); + $DB->delete_records('cmi5launch_sessions', array('sessionid' => $id)); } } @@ -173,43 +173,6 @@ function maketestaus ($testcourseid) { global $DB, $cmi5launch, $USER; - // Mock values to make AUs. - /* - $mockvalues = array( - 'id' => 'id', - 'attempt' => 'attempt', - 'url' => 'url', - 'type' => 'type', - 'lmsid' => 'lmsid', - 'grade' => 'grade', - 'scores' => 'scores', - 'title' => array(0 => array('text' => 'title')), - 'moveon' => 'moveon', - 'auindex' => 'auindex', - 'parents' => 'parents', - 'objectives' => 'objectives', - 'description' => array(0 => array('text' => 'description')), - 'activitytype' => 'activitytype', - 'launchmethod' => 'launchmethod', - 'masteryscore' => 'masteryscore', - 'satisfied' => 'satisfied', - 'launchurl' => 'launchurl', - 'sessions' => 'sessions', - 'progress' => 'progress', - 'noattempt' => 'noattempt', - 'completed' => 'completed', - 'passed' => 'passed', - 'inprogress' => 'inprogress', - 'masteryScore' => 'masteryScore', - 'launchMethod' => 'launchMethod', - 'lmsId' => 'lmsId', - 'moveOn' => 'moveOn', - 'auIndex' => 'auIndex', - 'activityType' => 'activityType', - 'moodlecourseid' => 'moodlecourseid', - 'userid' => 'userid' - ); -*/ // Make new AUs, lets make five. $aus = array(); for ($i = 0; $i < 5; $i++) { @@ -273,10 +236,313 @@ function maketestaus ($testcourseid) } /** - * Create fake statements for testing. - * @param mixed $amountomake - amount of statements to make. - * @return array $statements - array of statements - */ + * Create fake statement values for testing. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvalues($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z' , + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "actorname" . $i, + ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + "display" => array( + "en" => ("verbdisplay" . $i), + ), + ), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array ( + + "en" => 'objectname' . $i), + "description" => "description" . $i, + "type" => "type" . $i, + ), + ), + 'context' => array ( + "context" => "context" . $i, + "contexttype" => "contexttype" . $i, + "contextparent" => "contextparent" . $i, + "extensions" => array ( + "extensions" => "extensions" . $i, + "sessionid" => "code" . $i, + ), + ), + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "raw" => 80 + $i, + "scaled" => $i, + ), + ), + 'stored' => 'stored' . $i, + 'authority' => array ( + "authority" => "authority" . $i, + + ), + 'version' => "version" . $i, + 'code' => 'code' . $i, + 'progress' => 'progress' . $i, + 'score' => 'score' . $i, + ), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no 'display' key in the verb. + * It also has a scaled score for testing in testcmi5launch_retrieve_score_scaled_score + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnodisplay($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "name" . $i, + ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + ), + 'version' => "version" . $i, + + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "scaled" => $i, + ), + ), + ) + )); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object key. + * // It also has no result and is used in testcmi5launch_retrieve_result_no_result + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobject($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i,), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object definition key but does have an id. + * // This also has no timestamp so it can be used for testing in testcmi5launch_retrieve_timestamp_no_time + * // And no score in 'result' to be use in testin testcmi5launch_retrieve_score_no_score. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectdef($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'object' => array ( + "id" => "objectid" . $i), + + "result" => array (), + ) + )); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has no object/def/name key.. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectname($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i,), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has an object but nothing in it . + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluesnoobjectid($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'object' => array () + ) + ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + + /** + * Create fake statement values for testing. + * This one has no matching lanuage string. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluenomatchinglang($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => 'timestamp' . $i, + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array( + "fr" => "fr" . $i, + "es" => "name" . $i, + ), + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + ) ) + ); + } + + // Return the statement values. + return $statementvalues; +} + + /** + * Create fake statement values for testing. + * This one has a matching lanuage value. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ +function maketeststatementsvaluematchinglang($amounttomake, $registrationid) +{ + for($i = 0; $i < $amounttomake; $i++) + { + // Mock statements that should be returned. + $statementvalues[] = array( + $registrationid => array( + // They are nested under 0 since array_chunk is used. + 0 => array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z', + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array( + "en" => "en-us" . $i, + "es" => "name" . $i, + ), + "description" => "description" . $i, + "type" => "type" . $i, + ), + + ), + )) + ); + } + + // Return the statement values. + return $statementvalues; +} + +/** + * Create fake statements for testing. + * @param mixed $amountomake - amount of statements to make. + * @return array $statements - array of statements + */ function maketeststatements($amounttomake) { // Iterate through amount to make, and use i to make different 'registration ids' @@ -294,58 +560,63 @@ function maketeststatements($amounttomake) // Array to hold the statements. // Maybe I need to change the stucture so the $i is first and next to statements instead of now as its nested - /*old - $statement['statements'][] = array( - $i => array(*/ - $statement['statements'][$i] = array( - // 'id' => 'idune' . $i, - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), + $newstatement = array( + 'timestamp' => '2024-' . $i . '0-00T00:00:00.000Z' , + 'actor' => array ( + "firstname" => "firstname" . $i, + "lastname" => "lastname" . $i, + "account" => array ( + "homePage" => "homePage" . $i, + "name" => "actorname" . $i, ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ) - , - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), + ), + 'verb' => array ( + "id" => "verbs/verbid" . $i, + "display" => array( + "en" => ("verbdisplay" . $i), ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, + ), + 'object' => array ( + "id" => "objectid" . $i, + "definition" => array ( + "name" => array ( + + "en" => 'objectname' . $i), + "description" => "description" . $i, + "type" => "type" . $i, ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), + ), + 'context' => array ( + "context" => "context" . $i, + "contexttype" => "contexttype" . $i, + "contextparent" => "contextparent" . $i, + "extensions" => array ( + "extensions" => "extensions" . $i, + "sessionid" => "code" . $i, ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - + ), + "result" => array ( + "result" => "result" . $i, + "score" => array ( + "raw" => 80 + $i, + "scaled" => $i, ), - 'version' => "version" . $i, + ), + 'stored' => 'stored' . $i, + 'authority' => array ( + "authority" => "authority" . $i, + + ), + 'version' => "version" . $i, + 'code' => 'code' . $i, + 'progress' => 'progress' . $i, + 'score' => 'score' . $i, ); - $statements[] = $statement; + $statements[] = $newstatement; } + $statement['statements'] = $statements; // Return array of statements return $statement; } @@ -365,16 +636,13 @@ function maketestsessions () for ($i = 0; $i < 5; $i++) { - - //$toaddtostring = strval($i); - // Add i to each value so the AUs are unique. - // Mock values to make sessions. // For some bizarre reason, retrieve sessions from db goes on SESSION id not ID?!?!?? // THIS IS the problem? $mockvalues = array( 'id' => $i, - 'sessionid' => 'sessionid' . $i, + // Can't save string to DB. + 'sessionid' => $i, 'userid' => 'userid' . $i, 'moodlecourseid' => 'moodlecourseid' . $i, 'registrationscoursesausid' => 'registrationscoursesausid' . $i, @@ -400,30 +668,24 @@ function maketestsessions () 'launchurl' => 'launchurl' . $i, ); $sessions[] = new session($mockvalues); - $sessionid[] = ('sessionid' . $i); + $sessionid[] = ( $i); } - // Pass in the au index to retrieve a launchurl and session id. -//$urldecoded = $cmi5launchretrieveurl($cmi5launch->id, $auindex); - -// Retrieve and store session id in the aus table. -//$sessionid = intval($urldecoded['id']); - - -// Retrieve the launch url. -$launchurl = 'testurl'; -// And launch method. -$launchmethod = 'testmethod'; -// Now save the fake sessions to the test database -// Ok so tomorrow make this so there are sessions actually creatd. -$sessionhelper = new session_helpers(); -//$createsession = $sessionhelper->cmi5launch_create_session(); - // For each session id in the list, create a session. - foreach ($sessions as $session) { - // LEts test if it can retrieve here - $newid = $sessionhelper->cmi5launch_create_session($session->id, $launchurl, $launchmethod); - $newids[] = $newid; - //$sessionid[] = $session; + // Retrieve the launch url. + $launchurl = 'testurl'; + // And launch method. + $launchmethod = 'testmethod'; + + // Now save the fake sessions to the test database + // Ok so tomorrow make this so there are sessions actually creatd. + $sessionhelper = new session_helpers(); + //$createsession = $sessionhelper->cmi5launch_create_session(); + // For each session id in the list, create a session. + foreach ($sessions as $session) { + // LEts test if it can retrieve here + $newid = $sessionhelper->cmi5launch_create_session($session->id, $launchurl, $launchmethod); + $newids[] = $newid; + //$sessionid[] = $session; @@ -437,7 +699,7 @@ function maketestsessions () } /** - * Heeelper func that assigns the sessions made to the aus for testing purposes. + * Helper func that assigns the sessions made to the aus for testing purposes. * @param mixed $auids * @param mixed $sessionids * @return array @@ -472,14 +734,6 @@ function assign_sessions_to_aus($auids, $sessionids){ // Tomorrow: no that isnt it. Something else is throwin the problem. // Now the AU will have properties and we want to assign the sessionid array to the 'sessions' property $au->sessions = json_encode($sessionids); - // Update AU in table with new info. - - //echo au here nd check what sessons itr and if id mathces - - - - // what is au record? - // Save the AU back to the DB. $success = $DB->update_record('cmi5launch_aus', $au); @@ -489,13 +743,6 @@ function assign_sessions_to_aus($auids, $sessionids){ $newaus[] = $au; } - // AHA! They are whole damn aus - // now save the new aus back to db - // we dont need to sve twice!!! - // $save_aus($newaus); - // Save us is to make new aus we need to save them back to DB - // Now update to table. - //return $newauids; } @@ -545,36 +792,27 @@ function assign_aus_to_courses($courseids, $auids){ } } - - - - - -/* - function get_file_get_contents() { - return ['file_get_contents']; -} -*/ - global $file_get_contents; - // Ok lets make a file_et_contents for this namespace, we know it should return a json string? - // And what should it receive? just the reular arguments? - /** - * A local file_get_contents to overide the PHP function for testing. - * As we do not want to actually get the file, we just want to test the function calling that function. - * - */ - function file_get_contents($url, $use_include_path = false, $context = null, $offset = 0, $maxlen = null) + // So now all the test has to do is inject THIS which will return as we please + function cmi5launch_test_stream_and_send_pass_lrs($options, $url) { - // Do we wamt to test whats passed in as well? pr ois that not necessary? - // MAybe we can return the args as json encoded string? - return json_encode(func_get_args()); + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // Lets pass in the 'return' value as the option. + // Encode because it would be a string comiiiiing from LRS. + return json_encode($statement); } - // And what should it receive? just the reular arguments? - /** - * A local file_get_contents to overide the PHP function for testing. - * As we do not want to actually get the file, we just want to test the function calling that function. - * - */ + + // So now all the test has to do is inject THIS which will return as we please + function cmi5launch_test_stream_and_send_excep_lrs($options, $url) + { + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // Lets pass in the 'return' value as the option. + // Encode because it would be a string comiiiiing from LRS. + return json_encode($statement); + } // So now all the test has to do is inject THIS which will return as we please function cmi5launch_test_stream_and_send_pass($options, $url) diff --git a/cmi5PHP/tests/grade_helpersTest.php b/cmi5PHP/tests/grade_helpersTest.php index a18304e..1286f4d 100644 --- a/cmi5PHP/tests/grade_helpersTest.php +++ b/cmi5PHP/tests/grade_helpersTest.php @@ -705,7 +705,7 @@ public function testcmi5launch_update_au_for_user_grades_excep() // Functions from other classes. $updateau = $gradehelper->get_cmi5launch_update_au_for_user_grades(); - // The expected is built bby the two messages knowing 'title' is an empty array. + // The expected is built by the two messages knowing 'title' is an empty array. $expected = "Error in updating or checking user grades. Report this error to system administrator: Error in checking user grades:"; // Catch the exception. diff --git a/cmi5PHP/tests/progressTest.php b/cmi5PHP/tests/progressTest.php index 014332f..209ac39 100644 --- a/cmi5PHP/tests/progressTest.php +++ b/cmi5PHP/tests/progressTest.php @@ -3,6 +3,8 @@ use PHPUnit\Framework\TestCase; use mod_cmi5launch\local\cmi5_connectors; +use mod_cmi5launch\local\nullException; +use mod_cmi5launch\local\session; require_once( "cmi5TestHelpers.php"); @@ -51,9 +53,18 @@ protected function setUp(): void { global $sessionids, $DB, $cmi5launch, $cmi5launchid, $USER, $testcourseid, $cmi5launchsettings; - $cmi5launchsettings = array("cmi5launchtenanttoken" => "Testtoken", "cmi5launchplayerurl" => "http://test/launch.php", "cmi5launchcustomacchp" => "http://testhomepage.com"); + $cmi5launchsettings = array( + "cmi5launchlrsendpoint" => "Test LRS point", + "cmi5launchlrslogin" => "Test LRS login", + "cmi5launchlrspass" => "Test LRS password", + "cmi5launchtenanttoken" => "Testtoken", + "cmi5launchplayerurl" => "http://test/launch.php", + "cmi5launchcustomacchp" => "http://testhomepage.com", + "grademethod" => 1 + ); + // Override global variable and function so that it returns test data. $USER = new \stdClass(); $USER->username = "testname"; @@ -68,9 +79,12 @@ protected function setUp(): void protected function tearDown(): void { + global $sessionids; // Restore overridden global variable. unset($GLOBALS['USER']); unset($GLOBALS['cmi5launchsettings']); + + deletetestcmi5launch_sessions($sessionids); } /** @@ -93,66 +107,13 @@ public function testcmi5launch_request_statements_from_lrs_pass() $registrationid = "registrationid"; // Statement values that should be returned from mock. - $statementvalues = array(); + $statementvalues = maketeststatementsvalues($amount, $registrationid); - for($i = 0; $i < $amount; $i++) - { - // Mock statements that should be returned. - $statementvalues[] = array( - $registrationid => array( - // They are nested under 0 since array_chunk is used. - 0 => array( - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), - ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ), - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), - ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, - ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), - ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - - ), - 'version' => "version" . $i, - ), - ) - ); - } - // Retrieve a sessionid, we'll just use the first one. $sessionid = $sessionids[0]; // Retrieve a session from the DB as an object. - $session = $DB->get_record('cmi5launch_sessions', array('id' => $sessionid), '*', MUST_EXIST); + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); // Mock data as it will be passed to stub. $data = array( @@ -160,6 +121,9 @@ public function testcmi5launch_request_statements_from_lrs_pass() 'since' => $session->createdat, ); + // Function that will be called in function under test. + $testfunction = 'cmi5launch_stream_and_send'; + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other methods. // Create a mock of the send_request class as we don't actually want // to create a new course in the player. @@ -169,7 +133,7 @@ public function testcmi5launch_request_statements_from_lrs_pass() $csc->expects($this->once()) ->method('cmi5launch_send_request_to_lrs') - ->with($data, $session->id) + ->with($testfunction, $data, $session->id) ->willReturn($statements); // Call the method under test. @@ -187,78 +151,1008 @@ public function testcmi5launch_request_statements_from_lrs_pass() * Test of the cmi5launch_request_statements_from_lrs with a fail condition. * @return void */ - public function testcmi5launch_request_statements_from_lrs_fail(){ + public function testcmi5launch_request_statements_from_lrs_excep(){ global $DB, $cmi5launch, $cmi5launchid, $sessionids; - // Amount of statements to make for testing. - // The same amount of statements to make for testing will match the amount - // of statement values returned from mock. - $amount = 5; + + // Test registrationid to pass. + $registrationid = "registrationid"; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); - // Make fake statements for testing. - $statements = maketeststatements($amount); + // Mock data as it will be passed to stub. + $data = array( + 'registration' => $registrationid, + 'since' => $session->createdat, + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5launch_stream_and_send'; + + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other methods. + // Create a mock of the send_request class as we don't actually want + // to create a new course in the player. + $csc = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_send_request_to_lrs' )) + ->getMock(); + + // If this returns null it should trrigger an error in the function. + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_lrs') + ->with($testfunction, $data, $session->id) + ->willReturn(null); + + // The expected is built by the two messages knowing 'title' is an empty array. + $expected = "Trouble retrieving statements from LRS. Caught exception: "; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage($expected); + + // Call the method under test. + $result = $csc->cmi5launch_request_statements_from_lrs($registrationid,$session); + + + } + /** + * Test of the cmi5launch_send_request_to_lrs with a pass condition. + * @return void + */ + public function testcmi5launch_send_request_to_lrs(){ + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Mock data as it will be passed to stub. + $data = array( + 'registration' => "registrationid", + 'since' => "Test time", + ); + + // Retrieve settings like they will be in SUT. + $settings = cmi5launch_settings($cmi5launch->id); + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + + // Test registrationid to pass. $registrationid = "registrationid"; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); - // Statement values that should be returned from mock. - $statementvalues = array(); - - - for($i = 0; $i < $amount; $i++) + // Mock data as it will be passed to stub. + // Use key 'http' even if you send the request to https://... + // There can be multiple headers but as an array under the ONE header. + // Content(body) must be JSON encoded here, as that is what CMI5 player accepts. + $options = array( + 'http' => array( + 'method' => 'GET', + 'header' => array( + 'Authorization: Basic ' . base64_encode("$user:$pass"), + "Content-Type: application/json\r\n" . + "X-Experience-API-Version:1.0.3", + ), + ), + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_test_stream_and_send_pass_lrs'; + + // Make a fake statement to return from the mock. + $statement = maketeststatements(1); + + // The result we expect back is the statment, decoded. So it's original form. + $expected = $statement; + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + // Check the result is as expected. + $this->assertEquals($expected, $result, "Expected result to match statementvalues "); + // The return should also be an array, since it is decoded with the 'tue' flag. + $this->assertIsArray($result,"Expected retrieved object to be array" ); + } + + /** + * Test of the cmi5launch_send_request_to_lrs with a fail condition. + * Throws an exception at the first try/catch where it is retrievin settings. + * @return void + */ + public function testcmi5launch_send_request_to_lrs_fail_settings(){ + + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Make data that is not array to throw error in settings try/catch + $data = ("Test string to throw error"); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); + + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_test_stream_and_send_pass_lrs'; + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // The expected is built bby the two messages knowing 'title' is an empty array. + $expected = "Unable to retrieve LRS settings. Caught exception: "; + + + $this->expectExceptionMessage($expected); + // Catch the exception. + $this->expectException(nullException::class); + + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + } + + /** + * Test of the cmi5launch_send_request_to_lrs with a fail condition. + * Throws an exception at the second try/catch, where it tries to communicte with LRS. + * @return void + */ + public function testcmi5launch_send_request_to_lrs_fail_excep(){ + + global $DB, $cmi5launch, $cmi5launchid, $sessionids; + + // Mock data as it will be passed to stub. + $data = array( + 'registration' => "registrationid", + 'since' => "Test time", + ); + + // Retrieve settings like they will be in SUT. + $settings = cmi5launch_settings($cmi5launch->id); + // Url to request statements from. + $url = $settings['cmi5launchlrsendpoint'] . "statements"; + // Build query with data above. + $url = $url . '?' . http_build_query($data, "", '&', PHP_QUERY_RFC1738); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[0]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid), '*', MUST_EXIST); + + // Mock data as it will be passed to stub. + // Use key 'http' even if you send the request to https://... + // There can be multiple headers but as an array under the ONE header. + // Content(body) must be JSON encoded here, as that is what CMI5 player accepts. + $options = array( + 'http' => array( + 'method' => 'GET', + 'header' => array( + 'Authorization: Basic ' . base64_encode("$user:$pass"), + "Content-Type: application/json\r\n" . + "X-Experience-API-Version:1.0.3", + ), + ), + ); + + // Function that will be called in function under test. + $testfunction = 'cmi5Test\cmi5launch_stream_and_send_excep_lrs'; + + + // The expected is built bby the two messages knowing 'title' is an empty array. + $expected = 'Unable to communicate with LRS. Caught exception: ' ; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage($expected); + + // New proggress + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $progress->cmi5launch_send_request_to_lrs($testfunction, $data, $session->id); + + } + /** + * Test of the cmi5launch_retrieve_actor with a pass condition. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_actor_pass() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "actorname0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_actor($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_actor with a fail condition. + * Fail at retrieving the actors name. + * @return void + */ + public function testcmi5launch_retrieve_actor_fail() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Actor name not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + // Pass a string that is not in the statement to cause an error. + $result = $progress->cmi5launch_retrieve_actor($statement[0], "purple"); + + $expectedoutput = 'Unable to retrieve actor name from LRS. Caught exception: Undefined array key "purple"'; + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + $this->expectOutputString($expectedoutput); + } + /** + * Test of the cmi5launch_retrieve_verb with a pass condition. + * (This one tests if verb has display option). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_verb_pass() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "verbdisplay0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_verb($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } +/** + * Test of the cmi5launch_retrieve_verb with a pass condition. + * (This one tests if verb doesn't have a display option). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_verb_pass_no_display() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnodisplay(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "verbid0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_verb($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_actor with a fail condition. + * Fail at retrieving the verb . + * @return void + */ + public function testcmi5launch_retrieve_verb_fail() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Verb not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + // Pass a string that is not in the statement to cause an error. + $result = $progress->cmi5launch_retrieve_verb($statement[0], "purple"); + + $expectedoutput = 'Unable to retrieve verb from LRS. Caught exception: Undefined array key "purple"'; + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + $this->expectOutputString($expectedoutput); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object isn't there). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_object_key() + { + // Branch one + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved/there is no object in this statement)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object doesn't have a definition key and no id ). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_object_def_key() + { + //Branch 2 + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectid(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved/there is no object in this statement)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object doesnt have a definition key, + * but does have an object id key ). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_object_id_exists() + { + // Branch 3 + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "objectid0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object has everything and languae is specified matches the cfg language (should be 'en')). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_matching_lang() + { + // Branch 5 + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluematchinglang(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "en-us0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if object has everything and languae doesnt match the cfg language). + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_pass_no_matching_lang() + + { + // Branch 4. + + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluenomatchinglang(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "fr0"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + /** + * Test of the cmi5launch_retrieve_object with a pass condition. + * (This one tests if the exceptions are caught and thrown correctly. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_object_excep() + { + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $expected = "(Object name not retrieved)"; + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_object_name($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve object name from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + /** + * Test of the cmi5launch_retrieve_timestamp with a pass condition. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // The actor name will be 'name1' Because 1 was passed but index starts at '0' in value making function. + $faketime = "2024-00-00T00:00:00.000Z"; + + // Turn expected into a date so it matches what's leaving the function. + $expected = new \DateTime($faketime, new \DateTimeZone('US/Eastern')); + + $expected->setTimezone(new \DateTimeZone('America/New_York')); + + $expected = $expected->format('d-m-Y' . " " . 'h:i a'); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + // echo" result . " . $result; + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_timestamp with a pass condition. + * Tests else branch if timestamp is not present. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp_no_time() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + $expected = "(Timestamp not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + + /** + * Test of the cmi5launch_retrieve_timestamp with a fail condition. + * Tests error try/catch. + * Successfully retrieves actors name. + * @return void + */ + public function testcmi5launch_retrieve_timestamp_excep() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + $expected = "(Timestamp not retrieved)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_timestamp($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a string"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve timestamp from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully retrieves a score + * // This one retrieves raw score + * . + * @return void + */ + public function testcmi5launch_retrieve_score_raw_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvalues(1, $registrationid); + + // Make the statement have a score. + $statement[0][$registrationid][0]['result']['score']['raw'] = 10; + $expected = 10; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsInt($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully retrieves a scaled score + * . + * @return void + */ + public function testcmi5launch_retrieve_score_scaled_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnodisplay(1, $registrationid); + + // Make the statement have a score. + // Give it a float to test rounding. + $statement[0][$registrationid][0]['result']['score']['scaled'] = 10.504; + + $expected = 10.5; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsFloat($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully returns a message there is no score in the statement. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_no_score() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobjectdef(1, $registrationid); + + $expected = "(Score not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + + /** + * Test of the cmi5launch_retrieve_score with a pass condition. + * Successfully returns a message there is no result in the statement. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_no_result() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + $expected = "(Score not retrieved or not present in statement)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + } + + /** + * Test of the cmi5launch_retrieve_score with a fail condition. + * Successfully returns an exception. + * . + * @return void + */ + public function testcmi5launch_retrieve_score_excep() + + { + global $CFG; + + // Fake registration id. + $registrationid = "registrationid"; + // Make a test statement to draw name from. + $statement = "String to throw error"; + + $expected = "(Score not retrieved)"; + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Because we have a new function to make these values in a test environment, the + // statement comes wrapped in an array, so access first level of array. + $result = $progress->cmi5launch_retrieve_score($statement[0], $registrationid); + + // The result should be a string. + $this->assertIsString($result, "Expected result to be a integer"); + // and should equal expected value. + $this->assertEquals($expected, $result, "Expected result to match expected value"); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve score from LRS. Caught exception: Cannot access offset of type string on string'; + $this->expectOutputString($expectedoutput); + } + + /** + * Test of the cmi5launch_retrieve_statements with a pass condition. + * Successfully returns a session object. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements() + { - // Mock statements that should be returned. - $statementvalues[] = array( - $registrationid => array( - // They are nested under 0 since array_chunk is used. - 0 => array( - 'timestamp' => 'timestamp' . $i, - 'actor' => array ( - "firstname" => "firstname" . $i, - "lastname" => "lastname" . $i, - "account" => array ( - "homePage" => "homePage" . $i, - "name" => "name" . $i, - ), - ), - 'verb' => array ( - "id" => "verbid" . $i, - "display" => array( - "en" => "verbdisplay" . $i, - ), - ), - 'object' => array ( - "id" => "objectid" . $i, - "definition" => array ( - "name" => "name" . $i, - "description" => "description" . $i, - "type" => "type" . $i, - ), - ), - 'context' => array ( - "context" => "context" . $i, - "contexttype" => "contexttype" . $i, - "contextparent" => "contextparent" . $i, - ), - "result" => array ( - "result" => "result" . $i, - "score" => array ( - "raw" => "raw" . $i, - "scaled" => "scaled" . $i, - ), - ), - 'stored' => 'stored' . $i, - 'authority' => array ( - "authority" => "authority" . $i, - - ), - 'version' => "version" . $i, - ), - ) - ); + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = (maketeststatementsvalues(1, $registrationid)); + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // The result should be a session. + $this->assertIsObject($result, "Expected result to be a session"); + // and should equal expected value. + $this->assertEquals($newsession, $result, "Expected result to match expected value"); + } - + + /** + * Test of the cmi5launch_retrieve_statements with a condition. + * Successfully catches an exception thrown when trying to retrieve extension info. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements_excep_ext() + + { + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = maketeststatementsvaluesnoobject(1, $registrationid); + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve session id from LRS. Caught exception: Undefined array key "context". There may not be an extension key in statement.'; + $this->expectOutputString($expectedoutput); + } + /** + * Test of the cmi5launch_retrieve_statements with a condition. + * Successfully catches an exception thrown. + * . + * @return void + */ + public function testcmi5launch_retrieve_statements_excep() + + { + global $CFG, $DB, $sessionids; + + // First create a fake session to pass to the function. + $sessions = maketestsessions(); + + // Retrieve a sessionid, we'll just use the first one. + $sessionid = $sessionids[1]; + + // Retrieve a session from the DB as an object. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // + // add a code to session + $session->code = "code0"; + // add a score to be graded + $session->score = 80; + // add a progress to be graded + + // Fake registration id. + $registrationid = "registrationid"; + // Make test statements to pass through from the mock function; + $statement = "String to throw error"; + + // Make an array to add information to the session. + $newprogress = json_encode(["actorname0 verbdisplay0 objectname0 on 29-11-2023 07:00 pm"]); + $newscore = (80); + + // Copy the session so that we can tweak it and make how it should be then + // Compare with sessin object returned from the function. + $newsession = $session; + // Now update the session to match the expected session. + // Add the progress and score to the session. + $newsession->progress = $newprogress; + $newsession->score = $newscore; + + $mockedclass = $this->getMockBuilder('mod_cmi5launch\local\progress') + ->onlyMethods(array('cmi5launch_request_statements_from_lrs')) + ->getMock(); + + // Mock returns statements, as it would be from the LRS. + $mockedclass->expects($this->once()) + ->method('cmi5launch_request_statements_from_lrs') + ->with($registrationid, $session) + ->willReturn($statement); + + // Progress class and SUT. + $progress = new \mod_cmi5launch\local\progress(); + // Call the method under test. + $result = $mockedclass->cmi5launch_retrieve_statements($registrationid, $session); + + // There will also be an output message. + $expectedoutput = 'Unable to retrieve statements from LRS. Caught exception: foreach() argument must be of type array|object, string given'; + $this->expectOutputString($expectedoutput); + + } } \ No newline at end of file From 5a3be5d225d8396bd81f35d6e2a752e6e7d0ad9f Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Wed, 14 Aug 2024 14:08:22 -0400 Subject: [PATCH 04/10] finished session_helpers --- AUview.php | 6 +- classes/local/errorover.php | 33 +++- classes/local/grade_helpers.php | 6 +- classes/local/session_helpers.php | 169 +++++++++-------- cmi5PHP/tests/cmi5TestHelpers.php | 69 +++++-- cmi5PHP/tests/session_helpersTest.php | 257 ++++++++++++++++++++++++++ session_report.php | 5 +- 7 files changed, 441 insertions(+), 104 deletions(-) create mode 100644 cmi5PHP/tests/session_helpersTest.php diff --git a/AUview.php b/AUview.php index 3444e4d..a385868 100755 --- a/AUview.php +++ b/AUview.php @@ -36,7 +36,7 @@ $auhelper = new au_helpers; $sessionhelper = new session_helpers; $retrievesession = $sessionhelper->cmi5launch_get_retrieve_sessions_from_db(); -$updatesession = $sessionhelper->cmi5launch_get_update_session(); +//$updatesession = $sessionhelper->cmi5launch_get_update_session(); $retrieveaus = $auhelper->get_cmi5launch_retrieve_aus_from_db(); // MB - Not currently using events, but may in future. @@ -149,8 +149,8 @@ function mod_cmi5launch_launchexperience(registration) { foreach ($sessionids as $key => $sessionid) { // Get the session from DB with session id. - $session = $retrievesession($sessionid); - + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // Array to hold data for table. $sessioninfo = array(); diff --git a/classes/local/errorover.php b/classes/local/errorover.php index ec38500..8fba574 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -36,7 +36,7 @@ function exception_au(\Throwable $exception) { // echo"Error stirn --- $errstr"; // echo"Error number --- $errno"; - echo " EHAT?"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean throw new fieldException('Error OVER: ' . $exception->getMessage(), 0); @@ -76,9 +76,10 @@ function progresslrsreq_warning($errno, $errstr, $errfile, $errline) // echo"Error errline --- $errline"; // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $errstr. " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); // exit; } + /** * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. * @param mixed $errno @@ -120,6 +121,34 @@ function exception_progresslrs(\Throwable $exception) throw new nullException('Error in retrieving statements from LRS ' . $exception->getMessage(), 0); // exit; } + +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function session_warning(\Throwable $exception) +{ + + throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); + // exit; +} +function sesssion_exception($errno, $errstr, $errfile, $errline) +{ + // echo"Error stirn --- $errstr"; + // echo"Error number --- $errno"; +//echo"Error errfile --- $errfile"; + // echo"Error errline --- $errline"; + // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean + + throw new nullException('Error in retrieving statements from LRS ' . $errstr, 0); + // exit; +} + function sifting_data_warning($errno, $errstr, $errfile, $errline) { // echo"Error stirn --- $errstr"; diff --git a/classes/local/grade_helpers.php b/classes/local/grade_helpers.php index a63bc37..87f944c 100644 --- a/classes/local/grade_helpers.php +++ b/classes/local/grade_helpers.php @@ -202,6 +202,10 @@ public function cmi5launch_update_au_for_user_grades($session_helpers, $auids, $ $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + // Instantiate progress and cmi5_connectors class to pass. + $progress = new progress; + $cmi5 = new cmi5_connectors; + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. set_error_handler('mod_cmi5launch\local\grade_warning', E_WARNING); set_exception_handler('mod_cmi5launch\local\exception_grade'); @@ -241,7 +245,7 @@ public function cmi5launch_update_au_for_user_grades($session_helpers, $auids, $ $session = $DB->get_record('cmi5launch_sessions', ['sessionid' => $sessionid]); // Retrieve new info (if any) from CMI5 player and LRS on session. - $session = $updatesession($sessionid, $cmi5launch->id, $user); + $session = $updatesession($progress, $cmi5, $sessionid, $cmi5launch->id, $user); // Now if the session is complete, passed, or terminated, we want to update the AU. // These come in order, so the last one is the current status, so update on each one, diff --git a/classes/local/session_helpers.php b/classes/local/session_helpers.php index 82e9e4a..7ed3b8a 100644 --- a/classes/local/session_helpers.php +++ b/classes/local/session_helpers.php @@ -48,54 +48,71 @@ public function cmi5launch_get_retrieve_sessions_from_db() { * @param mixed $cmi5id - cmi5 instance id * @return session */ - public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { - + // can we inject classes not methods? + public function cmi5launch_update_sessions($progress, $cmi5, $sessionid, $cmi5launchid, $user) { + global $CFG, $DB, $cmi5launch, $USER; - $connector = new cmi5_connectors; - $progress = new progress; + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrs_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrs'); + + $connector = new $cmi5(); + $progress = new $progress(); $getsessioninfo = $connector->cmi5launch_get_session_info(); $getprogress = $progress->cmi5launch_get_retrieve_statements(); - - // Get the session from DB with session id. - $session = $this->cmi5launch_retrieve_sessions_from_db($sessionid); - - // Reload cmi5 instance. - $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); - - // Reload user course instance. - $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $user->id]); - - // Get updates from the LRS as well. - $session = $getprogress($userscourse->registrationid, $session); - - // Get updates from cmi5player. - // This is sessioninfo from CMI5 player. - $sessioninfo = json_decode($getsessioninfo($sessionid, $cmi5id), true); - - // Update session. - foreach ($sessioninfo as $key => $value) { - // We don't want to overwrite ids. - // If the property exists and it's not id or sessionid, set it to lowercase and - // encode value if it is array. (DB needs properties in lowercase, but player returns camelcase). - if (property_exists($session, $key ) && $key != 'id' && $key != 'sessionid') { - - // If it's an array, encode it so it can be saved to DB. - if (is_array($value)) { - $value = json_encode($value); - } - - if (is_string($key)) { - $key = mb_convert_case($key, MB_CASE_LOWER, "UTF-8"); + + try { + // Get the session from DB with session id. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + // Reload cmi5 instance. + $record = $DB->get_record('cmi5launch', array('id' => $cmi5launchid)); + + // Reload user course instance. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $user->id]); + // Get updates from the LRS as well. + $session = $getprogress($userscourse->registrationid, $session); + // Get updates from cmi5player. + // This is sessioninfo from CMI5 player. + $sessioninfo = json_decode($getsessioninfo($sessionid, $cmi5launchid), true); + // Update session. + foreach ($sessioninfo as $key => $value) { + // We don't want to overwrite ids. + // If the property exists and it's not id or sessionid, set it to lowercase and + // encode value if it is array. (DB needs properties in lowercase, but player returns camelcase). + if (property_exists($session, $key) && $key != 'id' && $key != 'sessionid') { + + // If it's an array, encode it so it can be saved to DB. + if (is_array($value)) { + $value = json_encode($value); + } + + if (is_string($key)) { + $key = mb_convert_case($key, MB_CASE_LOWER, "UTF-8"); + } + + $session->$key = $value; } - - $session->$key = $value; } - } - // Now update to table. - $DB->update_record('cmi5launch_sessions', $session); + // Now update to table. + $DB->update_record('cmi5launch_sessions', $session); + + } catch (\Throwable $e) { + + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); + // If there is an error, return the error. + throw new nullException("Error in updating session. Report this error to system administrator: ". $e->getMessage()); + + } + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); + return $session; } @@ -110,57 +127,47 @@ public function cmi5launch_create_session($sessionid, $launchurl, $launchmethod) global $DB, $CFG, $cmi5launch, $USER; - $table = "cmi5launch_sessions"; - - // Make a new record to save. - $newrecord = new \stdClass(); - // Because of many nested properties, needs to be done manually. - $newrecord->sessionid = $sessionid; - $newrecord->launchurl = $launchurl; - $newrecord->tenantname = $USER->username; - $newrecord->launchmethod = $launchmethod; - // I think here is where we eed to implement : moodlecourseid - $newrecord->moodlecourseid = $cmi5launch->id; - // And userid! - $newrecord->userid = $USER->id; - - // Save record to table. - $newid = $DB->insert_record($table, $newrecord, true); - - // Return value - return $newid; + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\progresslrs_warning', E_WARNING); + set_exception_handler('mod_cmi5launch\local\exception_progresslrs'); - } - /** - * Retrieves session from DB - * @param mixed $sessionid - the session id - * @return session - */ - public function cmi5launch_retrieve_sessions_from_db($sessionid) { + // Put a try here to catch if anything goes wrong. + try { - global $DB, $CFG; + $table = "cmi5launch_sessions"; - $check = $DB->record_exists('cmi5launch_sessions', ['sessionid' => $sessionid], '*', IGNORE_MISSING); + // Make a new record to save. + $newrecord = new \stdClass(); + // Because of many nested properties, needs to be done manually. + $newrecord->sessionid = $sessionid; + $newrecord->launchurl = $launchurl; + $newrecord->tenantname = $USER->username; + $newrecord->launchmethod = $launchmethod; + // I think here is where we eed to implement : moodlecourseid + $newrecord->moodlecourseid = $cmi5launch->id; + // And userid! + $newrecord->userid = $USER->id; - // If check is negative, the record does not exist. Throw error. - if (!$check) { + // Save record to table. + $newid = $DB->insert_record($table, $newrecord, true); - echo "

Error attempting to get session data from DB. Check session id.

"; - echo "
";
-            var_dump($sessionid);
-            echo "
"; + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); - } else { + // Return value + return $newid; + } catch (\Throwable $e) { - $sessionitem = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); - $session = new session($sessionitem); + // If there is an error, return the error. + throw new nullException("Error in creating session. Report this error to system administrator: ". $e->getMessage()); } - - // Return new session object. - return $session; } -} + } diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php index 69658ca..94a40bc 100644 --- a/cmi5PHP/tests/cmi5TestHelpers.php +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -867,7 +867,7 @@ public function cmi5launch_get_retrieve_sessions_from_db() { * @param mixed $cmi5id - cmi5 instance id * @return */ - public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { + public function cmi5launch_update_sessions2($sessionid, $cmi5id, $user) { // Ok, lets make sure we are calling THIS one @@ -947,26 +947,63 @@ public function cmi5launch_retrieve_sessions_from_db($sessionid) { // Return new session object. return $session; } - } + // MAke a new progress class for overriding + // New class +class progress{ -// To pass to the test and cause exception -class session_helpers2 -{ - /** - * Gets updated session information from CMI5 player and throw exception - * @param mixed $sessionid - the session id - * @param mixed $cmi5id - cmi5 instance id - * @return - */ - public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { - - // $returnvalue->score = 80; - return null; + public function cmi5launch_get_retrieve_statements() { + return [$this, 'cmi5launch_retrieve_statements']; } -} +public function cmi5launch_retrieve_statements($registrationid, $session) { + + global $DB, $CFG, $cmi5launch, $USER; + // We need session objects to test the progress class + // Make a fake session object. + $sessionlrs = $session; + + // Change the session to have some new values from LRS. + $sessionlrs->isterminated = 1; + $sessionlrs->launchurl = "http://test.com"; + + return $sessionlrs; + + } + +} + // MAke a new cmi5 connectors class for overriding + // New class + class cmi5_connectors{ + public function cmi5launch_get_session_info() { + return [$this, 'cmi5launch_retrieve_session_info_from_player']; + } + public function cmi5launch_retrieve_session_info_from_player($sessionid, $id) { + global $DB, $CFG, $cmi5launch, $USER; + // We need session objects to test the progress class + // Make a fake session object. + $sessionids = maketestsessions(); + + // We just need one session to test this. + $sessionid = $sessionids[0]; + + // Get the session from DB with session id. + $sessioncmi5 = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + // Change the session from cmi5 to have some new values. + $sessioncmi5->score = 100; + $sessioncmi5->iscompleted = 1; + $sessioncmi5->ispassed = 1; + $sessioncmi5->launchmethod = "ownWindow"; + // Change the session to have some new values from to match lrs values. + $sessioncmi5->isterminated = 1; + $sessioncmi5->launchurl = "http://test.com"; + + return json_encode($sessioncmi5); + } + + } ?> diff --git a/cmi5PHP/tests/session_helpersTest.php b/cmi5PHP/tests/session_helpersTest.php new file mode 100644 index 0000000..4fa601d --- /dev/null +++ b/cmi5PHP/tests/session_helpersTest.php @@ -0,0 +1,257 @@ + "Test LRS point", + "cmi5launchlrslogin" => "Test LRS login", + "cmi5launchlrspass" => "Test LRS password", + "cmi5launchtenanttoken" => "Testtoken", + "cmi5launchplayerurl" => "http://test/launch.php", + "cmi5launchcustomacchp" => "http://testhomepage.com", + "grademethod" => 1 + ); + + // Override global variable and function so that it returns test data. + $USER = new \stdClass(); + $USER->username = "testname"; + $USER->id = 10; + + $testcourseid = maketestcourse($cmi5launchid); + + // We need session objects to test the progress class + // Make a fake session object. + $sessionids = maketestsessions(); + } + + protected function tearDown(): void + { + global $sessionids; + // Restore overridden global variable. + unset($GLOBALS['USER']); + unset($GLOBALS['cmi5launchsettings']); + + deletetestcmi5launch_sessions($sessionids); + } + + + /** + * Test of the cmi5launch_update_sessions with a pass condition. + * @return void + */ + public function testcmi5launch_update_sessions() + { + global $DB, $cmi5launch, $cmi5launchid, $sessionids, $USER; + + // We just need one session to test this. + $sessionid = $sessionids[0]; + + // Retrieve the session object. + // Get the session from DB with session id. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + // So expected session should be the same as session with the other two changes. + $sessionexpected = $session; + $sessionexpected->score = 100; + $sessionexpected->iscompleted = 1; + $sessionexpected->ispassed = 1; + $sessionexpected->launchmethod = "ownWindow"; + $sessionexpected->isterminated = 1; + $sessionexpected->launchurl = "http://test.com"; + + // New session_helpers from mod_cmi5launch + $helpers = new \mod_cmi5launch\local\session_helpers(); + + + $progress = new \cmi5Test\progress(); + $cmi5 = new \cmi5Test\cmi5_connectors(); + + // Result of the function. + $helpers->cmi5launch_update_sessions($progress, $cmi5, $sessionid, $cmi5launchid, $USER); + + // So the func doesn't return anything, but we can check the session object in the db. + // Get the session from DB with session id. + $result = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + // Result should be a session object. + $this->assertIsObject($result, "Result should be an object."); + + // Check that the result is the same as the expected session. + $this->assertEquals($sessionexpected, $result, "Result should be the same as the expected session."); + } + + + /** + * Test of the cmi5launch_update_sessions with an exception. + * @return void + */ + public function testcmi5launch_update_sessions_excep() + { + global $DB, $cmi5launch, $cmi5launchid, $sessionids, $USER; + + // We just need one session to test this. + $sessionid = $sessionids[0]; + + // Retrieve the session object. + // Get the session from DB with session id. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + $exceptionmessage = 'Error in updating session. Report this error to system administrator: Attempt to assign property "isterminated" on false'; + // So expected session should be the same as session with the other two changes. + $sessionexpected = $session; + $sessionexpected->score = 100; + $sessionexpected->iscompleted = 1; + $sessionexpected->ispassed = 1; + $sessionexpected->launchmethod = "ownWindow"; + $sessionexpected->isterminated = 1; + $sessionexpected->launchurl = "http://test.com"; + + // New session_helpers from mod_cmi5launch + $helpers = new \mod_cmi5launch\local\session_helpers(); + + $progress = new \cmi5Test\progress(); + $cmi5 = new \cmi5Test\cmi5_connectors(); + + // Wait, i bet this is being thrown in the cmi5 connectors error message func and so we need to catch + // the correct output not an exception + $output = "

Error attempting to get session data from DB. Check session id."; + // $this->assertStringStartsWith($output); + $this->expectExceptionMessage($exceptionmessage); + $this->expectException(nullException::class); + // Pass null so that an exception is thrown. + // Result of the function. + $helpers->cmi5launch_update_sessions($progress, $cmi5, null, $cmi5launchid, $USER); + + + } + + + /** + * Test of the cmi5launch_create_session with a pass condition. + * @return void + */ + public function testcmi5launch_create_session() + { + global $DB, $cmi5launch, $cmi5launchid, $sessionids, $USER; + + // Fake values. + $sessionid = '100'; + $launchurl = "http://test.com"; + $launchmethod = "ownWindow"; + $tenantname = $USER->username; + + // New session_helpers from mod_cmi5launch + $helpers = new \mod_cmi5launch\local\session_helpers(); + + // Result of the function. + $resultid = $helpers->cmi5launch_create_session($sessionid, $launchurl, $launchmethod); + + // Returns a new id. + $this->assertIsInt($resultid, "Result should be an int."); + + // Now retrieve it back from the db and make sure it matches. + // So the func doesn't return anything, but we can check the session object in the db. + // Get the session from DB with session id. + $result = $DB->get_record('cmi5launch_sessions', array('id' => $resultid)); + + // Result should be a session object. + $this->assertIsObject($result, "Result should be an object."); + + // Maybe we can just assert the newrecord fields that are different. + // Check results sessioid, launchurl, tenantname, launchmethod. + + // Check that the result is the same as the expected session. + $this->assertEquals($sessionid, $result->sessionid, "Result should be the same as the expected session."); + $this->assertEquals($launchurl, $result->launchurl, "Result should be the same as the expected session."); + $this->assertEquals($tenantname, $result->tenantname, "Result should be the same as the expected session."); + $this->assertEquals($launchmethod, $result->launchmethod, "Result should be the same as the expected session."); + } + + + /** + * Test of the cmi5launch_create_session with a fail condition. + * Catches an exception. + * @return void + */ + public function testcmi5launch_create_session_excep() + { + global $DB, $cmi5launch, $cmi5launchid, $sessionids, $USER; + + // LEt's make user not have id? + $USER = null; + // Fake values. + $sessionid = 100; + $launchurl = "http://test.com"; + $launchmethod = "ownWindow"; + + // New session_helpers from mod_cmi5launch + $helpers = new \mod_cmi5launch\local\session_helpers(); + + $exceptionmessage = 'Error in creating session. Report this error to system administrator: '; + + $this->expectExceptionMessage($exceptionmessage); + $this->expectException(nullException::class); + + // Pass null so that an exception is thrown. + + // Result of the function. + + // Result of the function. + $resultid = $helpers->cmi5launch_create_session($sessionid, $launchurl, $launchmethod); + + } + + + +} diff --git a/session_report.php b/session_report.php index be1d918..9050963 100644 --- a/session_report.php +++ b/session_report.php @@ -49,6 +49,9 @@ // External classes and functions. $sessionhelper = new session_helpers; $aushelpers = new au_helpers; +// Instantiate progress and cmi5_connectors to pass. +$progress = new \mod_cmi5launch\local\progress; +$cmi5connectors = new \mod_cmi5launch\local\cmi5_connectors; $updatesession = $sessionhelper->cmi5launch_get_update_session(); $getaus = $aushelpers->get_cmi5launch_retrieve_aus_from_db(); @@ -178,7 +181,7 @@ // There may be more than one session. foreach ($sessions as $sessionid) { - $session = $updatesession($sessionid, $cmi5launch->id, $user); + $session = $updatesession($progress, $cmi5, $sessionid, $cmi5launch->id, $user); // Add score to array for AU. $sessionscores[] = $session->score; From a922e69120466f4b63654c4838d2eb25ea04de78 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Wed, 14 Aug 2024 15:54:51 -0400 Subject: [PATCH 05/10] Finished updating session --- classes/local/session.php | 12 ++ cmi5PHP/tests/sessionTest.php | 241 ++++++++++++++++++++++++++++++++++ view.php | 5 +- 3 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 cmi5PHP/tests/sessionTest.php diff --git a/classes/local/session.php b/classes/local/session.php index 283c2ee..fc3a3d5 100644 --- a/classes/local/session.php +++ b/classes/local/session.php @@ -21,7 +21,12 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ + namespace mod_cmi5launch\local; +// Include the errorover (error override) funcs. +require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); + + defined('MOODLE_INTERNAL') || die(); class session { @@ -42,6 +47,13 @@ class session { // Constructs sessions. Is fed array and where array key matches property, sets the property. public function __construct($statement) { + // What can go wrong here? It could be that a statement is null + // or that the statement is not an array or not an object. + if (is_null($statement) || (!is_array($statement) && !is_object($statement) )) { + + throw new nullException('Statement to build session is null or not an array/object.', 0); + } + foreach ($statement as $key => $value) { $this->$key = ($value); diff --git a/cmi5PHP/tests/sessionTest.php b/cmi5PHP/tests/sessionTest.php new file mode 100644 index 0000000..54b26eb --- /dev/null +++ b/cmi5PHP/tests/sessionTest.php @@ -0,0 +1,241 @@ +sessionproperties = array( + 'id', + 'tenantname', + 'tenantId', + 'registrationsCoursesAusId', + 'lmsid', + 'progress', + 'aulaunchurl', + 'launchurl', + 'grade', + 'createdAt', + 'updatedAt', + 'registrationCourseAusId', + 'code', + 'lastRequestTime', + 'launchTokenId', + 'launchMode', + 'masteryScore', + 'isLaunched', + 'isInitialized', + 'initializedAt', + 'isCompleted', + 'isPassed', + 'isFailed', + 'isTerminated', + 'isAbandoned', + 'courseid', + 'completed', + 'passed', + 'inprogress', + 'sessionid', + 'userid', + 'registrationscoursesausid', + 'createdat', + 'updatedat', + 'launchtokenid', + 'lastrequesttime', + 'launchmode', + 'masteryscore', + 'tenantid', + 'score', + 'response', + 'islaunched', + 'isinitialized', + 'initializedat', + 'duration', + 'iscompleted', + 'ispassed', + 'isfailed', + 'isterminated', + 'isabandoned', + 'launchmethod', + 'moodlecourseid' + ); + + $this->emptystatement = array(); + + // A good test would be to test the constructor with a statement that has all the properties set. + $this->mockstatementvalues = array( + 'id' => 'id', + 'tenantname' => 'tenantname', + 'tenantId' => 'tenantId', + 'registrationsCoursesAusId' => 'registrationsCoursesAusId', + 'lmsid' => 'lmsid', + 'progress' => 'progress', + 'aulaunchurl' => 'aulaunchurl', + 'launchurl' => 'launchurl', + 'grade' => 'grade', + 'createdAt' => 'createdAt', + 'updatedAt' => 'updatedAt', + 'registrationCourseAusId' => 'registrationCourseAusId', + 'code' => 'code', + 'lastRequestTime' => 'lastRequestTime', + 'launchTokenId' => 'launchTokenId', + 'launchMode' => 'launchMode', + 'masteryScore' => 'masteryScore', + 'isLaunched' => 'isLaunched', + 'isInitialized' => 'isInitialized', + 'initializedAt' => 'initializedAt', + 'isCompleted' => 'isCompleted', + 'isPassed' => 'isPassed', + 'isFailed' => 'isFailed', + 'isTerminated' => 'isTerminated', + 'isAbandoned' => 'isAbandoned', + 'courseid' => 'courseid', + 'completed'=> 'completed', + 'passed' => 'passed', + 'inprogress' => 'inprogress', + 'sessionid' => 'sessionid', + 'userid' => 'userid', + 'registrationscoursesausid' => 'registrationscoursesausid', + 'createdat' => 'createdat', + 'updatedat'=> 'updatedat', + 'launchtokenid' => 'launchtokenid', + 'lastrequesttime'=> 'lastrequesttime', + 'launchmode'=> 'launchmode', + 'masteryscore' => 'masteryscore', + 'tenantid' => 'tenantid', + 'score' => 'score', + 'response' => 'response', + 'islaunched' => 'islaunched', + 'isinitialized' => 'isinitialized', + 'initializedat' => 'initializedat', + 'duration' => 'duration', + 'iscompleted' => 'iscompleted', + 'ispassed' => 'ispassed', + 'isfailed' => 'isfailed', + 'isterminated' => 'isterminated', + 'isabandoned' => 'isabandoned', + 'launchmethod' => 'launchmethod', + 'moodlecourseid' => 'moodlecourseid' + ); + + } + + protected function tearDown(): void + { + + } + + /** + * Test of session constructor class + * Should instantiate an session object with no values. + * @return void + */ + public function testInstantiationWithEmpty() + { + // Make an session object with no values. + $obj = new session($this->emptystatement); + + // Assert its an AU object. + $this->assertInstanceOf(session::class, $obj); + + // It is saying session is not transversable. Implementing traversable in session is breaking the code, typecast the object as array for dirty fix. + // Make sure the session object does not have any 'extra' properties, only the amount passed in + $expectedAmount = count($this->sessionproperties); + $sessionArray = (array) $obj; + $this->assertCount($expectedAmount, $sessionArray, "Session has $expectedAmount properties"); + + //Properties exists and are empty + foreach ($sessionArray as $property => $value) { + + // If value is 'progress' it's an array not null. + if ($property == 'progress') { + + $this->assertArrayHasKey($property, $sessionArray, "$property exists"); + $this->assertIsArray($value, "$property empty"); + + }else{ + + $this->assertArrayHasKey($property, $sessionArray, "$property exists"); + $this->assertNull($value, "$property empty"); + } + } + } + + /** + * Test of session constructor class. + * Should instantiate a session object with values. + * @return void + */ + public function testInstantiationWithValues() + { + $obj = new session($this->mockstatementvalues); + + // Assert it's an AU object? + $this->assertInstanceOf(session::class, $obj); + + // It is saying AU is not transversable. Implementing traversable in AU is breaking the code, typecast the object as array for dirty fix. + // Make sure the AU object does not have any 'extra' properties, only the amount passed in + $expectedAmount = count($this->sessionproperties); + $sessionArray = (array) $obj; + $this->assertCount($expectedAmount, $sessionArray, "Serssion has $expectedAmount properties"); + + //Properties exists and are correct (value should equal name of property) + foreach ($sessionArray as $property => $value) { + + $this->assertArrayHasKey($property, $sessionArray, "$property exists"); + $this->assertEquals($property, $value, "$value does not equal $property"); + } + } + + /** + * Test of session constructor class exceptions. This one tests if statement is null. + * @return void + */ + public function testInstantiation_except_null() + { + // Null statement to send and trigger exception. + $nullstatement = null; + + // Expected message + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage("Statement to build session is null or not an array/object." ); + + $obj = new session($nullstatement); + + } + + /** + * Test of AU constructor class exceptions. This one tests if statement passed in is not an array. + * @return void + */ + public function testInstantiation_except_nonarray() + { + // Null statement to send and trigger exception. + $nullstatement = "string"; + + // Catch the exception. + $this->expectException(nullException::class); + $this->expectExceptionMessage("Statement to build session is null or not an array/object." ); + + $obj = new session($nullstatement); + + } + + +} \ No newline at end of file diff --git a/view.php b/view.php index 62c29d0..f7479f9 100755 --- a/view.php +++ b/view.php @@ -271,8 +271,9 @@ function mod_cmi5launch_launchexperience(registrationInfo) { // Cycle through them looking to see if any were passed and/or completed. foreach ($sessions as $key => $value) { - $ausession = $getsessioninfo($value); - + // Get the session from DB with session id. + $ausession = $DB->get_record('cmi5launch_sessions', array('sessionid' => $value)); + if ($ausession->iscompleted == "1") { $completedfound = true; } From ce5fd27bcd0d640d4a3ca807e2402f723cde4995 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Wed, 14 Aug 2024 19:57:29 -0400 Subject: [PATCH 06/10] All unit tests passing --- AUview.php | 26 ++-- classes/local/au_helpers.php | 7 + classes/local/cmi5_connectors.php | 9 +- classes/local/errorover.php | 195 +++++++------------------- classes/local/token_form.php | 3 - cmi5PHP/tests/ausHelpersTest.php | 2 - cmi5PHP/tests/cmi5TestHelpers.php | 4 +- cmi5PHP/tests/cmi5_connectorsTest.php | 50 ++++--- cmi5PHP/tests/errorOverTest.php | 36 ----- cmi5PHP/tests/grade_helpersTest.php | 6 +- 10 files changed, 113 insertions(+), 225 deletions(-) delete mode 100644 cmi5PHP/tests/errorOverTest.php diff --git a/AUview.php b/AUview.php index a385868..4f628c3 100755 --- a/AUview.php +++ b/AUview.php @@ -15,7 +15,7 @@ // along with Moodle. If not, see . /** - * Prints an AUs session information annd allows start of new one. + * Prints an AUs session information and allows start of new one. * @copyright 2023 Megan Bohland * @copyright Based on work by 2013 Andrew Downes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -36,7 +36,6 @@ $auhelper = new au_helpers; $sessionhelper = new session_helpers; $retrievesession = $sessionhelper->cmi5launch_get_retrieve_sessions_from_db(); -//$updatesession = $sessionhelper->cmi5launch_get_update_session(); $retrieveaus = $auhelper->get_cmi5launch_retrieve_aus_from_db(); // MB - Not currently using events, but may in future. @@ -154,16 +153,23 @@ function mod_cmi5launch_launchexperience(registration) { // Array to hold data for table. $sessioninfo = array(); - // Retrieve createdAt and format. - $date = new DateTime($session->createdat, new DateTimeZone('US/Eastern')); - $date->setTimezone(new DateTimeZone('America/New_York')); - $sessioninfo[] = $date->format('D d M Y H:i:s'); + + if ($session->createdat != null) { - // Retrieve lastRequestTime and format. - $date = new DateTime($session->lastrequesttime, new DateTimeZone('US/Eastern')); - $date->setTimezone(new DateTimeZone('America/New_York')); - $sessioninfo[] = $date->format('D d M Y H:i:s'); + // Retrieve createdAt and format. + $date = new DateTime($session->createdat, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + // date_timezone_set($date, new DateTimeZone('America/New_York')); + $sessioninfo[] = $date->format('D d M Y H:i:s'); + } + + if ($session->lastrequesttime != null) { + // Retrieve lastRequestTime and format. + $date = new DateTime($session->lastrequesttime, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + $sessioninfo[] = $date->format('D d M Y H:i:s'); + } // Add progress to table. $sessioninfo[] = ("

" . implode("\n ", json_decode($session->progress) ) . "
"); diff --git a/classes/local/au_helpers.php b/classes/local/au_helpers.php index 4ecdbff..259081a 100644 --- a/classes/local/au_helpers.php +++ b/classes/local/au_helpers.php @@ -155,6 +155,10 @@ public function cmi5launch_save_aus($auobjectarray) //Check it's not null. if ($auobjectarray == null) { + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + throw new nullException('Cannot save AU information. AU object array is: null', 0); } else { @@ -229,6 +233,9 @@ public function cmi5launch_save_aus($auobjectarray) // Now use the found missing value to give feedback to user. echo " One of the fields is incorrect. Check data for field '$missing'. " . $e->getMessage() . "\n"; + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); } } diff --git a/classes/local/cmi5_connectors.php b/classes/local/cmi5_connectors.php index 8a51360..b79b551 100755 --- a/classes/local/cmi5_connectors.php +++ b/classes/local/cmi5_connectors.php @@ -504,8 +504,11 @@ public function cmi5launch_send_request_to_cmi5_player_post($cmi5launch_stream_a // Return response. return $result; - }catch(\Throwable $e) { - + }catch(\Throwable $e) { + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); // throw new playerException("communicating with player, sending or crafting a POST request: " . $e); } @@ -614,7 +617,7 @@ public function cmi5launch_connectors_error_message($resulttotest, $type) { if ($resulttest === false ){ - $errormessage = $type . ". CMI5 Player is not communicating. Is it running?"; + $errormessage = $type . " CMI5 Player is not communicating. Is it running?"; throw new playerException($errormessage); } diff --git a/classes/local/errorover.php b/classes/local/errorover.php index 8fba574..1cc8679 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -15,9 +15,9 @@ // along with Moodle. If not, see . /** - * Lets see if this works. making an ovrride error class, or several + * Error class with overridden functions for error and warning handling. * - * @copyright 2023 Megan Bohland + * @copyright 2024 Megan Bohland * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ @@ -34,16 +34,11 @@ */ function exception_au(\Throwable $exception) { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; - - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - - throw new fieldException('Error OVER: ' . $exception->getMessage(), 0); - // exit; + throw new nullException('Error OVER: ' . $exception->getMessage(), 0); } + /** - * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * An exception handler to use in grade cases when many different exceptions for data errors may be thrown. * @param mixed $errno * @param mixed $errstr * @param mixed $errfile @@ -53,31 +48,23 @@ function exception_au(\Throwable $exception) */ function exception_grade(\Throwable $exception) { - throw new nullException('Error in checking user grades: ' . $exception->getMessage(), 0); - // exit; -} -function progress_warning($errno, $errstr, $errfile, $errline) -{ - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - echo " unable to get name "; - // throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); - // exit; } + +/** + * An error handler to use in progress cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ function progresslrsreq_warning($errno, $errstr, $errfile, $errline) { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean throw new nullException('Unable to communicate with LRS. Caught exception: ' . $errstr. " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); - // exit; + } /** @@ -93,19 +80,21 @@ function exception_progresslrsreq(\Throwable $exception) { throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); - // exit; } +/** + * A warning handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ function progresslrs_warning($errno, $errstr, $errfile, $errline) { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - throw new nullException('Error in retrieving statements from LRS ' . $errstr, 0); - // exit; } + /** * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. * @param mixed $errno @@ -119,11 +108,10 @@ function exception_progresslrs(\Throwable $exception) { throw new nullException('Error in retrieving statements from LRS ' . $exception->getMessage(), 0); - // exit; } /** - * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * A warning handler to use in AU cases when many different exceptions for data errors may be thrown. * @param mixed $errno * @param mixed $errstr * @param mixed $errfile @@ -131,88 +119,42 @@ function exception_progresslrs(\Throwable $exception) * @throws \mod_cmi5launch\local\nullException * @return never */ -function session_warning(\Throwable $exception) -{ - - throw new nullException('Unable to communicate with LRS. Caught exception: ' . $exception->getMessage() . " Check LRS is up, username and password are correct, and LRS endpoint is correct.", 0); - // exit; -} -function sesssion_exception($errno, $errstr, $errfile, $errline) -{ - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - - throw new nullException('Error in retrieving statements from LRS ' . $errstr, 0); - // exit; -} - function sifting_data_warning($errno, $errstr, $errfile, $errline) { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; -//echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - - throw new fieldException('Error: ' . $errstr, 0); + throw new nullException('Error: ' . $errstr, 0); // exit; } +/** + * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ function array_chunk_warning($errno, $errstr, $errfile, $errline) { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; - - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - throw new nullException('Cannot parse array. Error: ' . $errstr, 0); - // exit; } - /// Ok, this i a different error handler - function grade_warning($errno, $errstr, $errfile, $errline) - { - // echo"Error stirn --- $errstr"; - // echo"Error number --- $errno"; - //echo"Error errfile --- $errfile"; - // echo"Error errline --- $errline"; - // Maybe we can construct the new errors here. This would allow the error personalization? And keep main code clean - - throw new nullException('Error in checking user grades: ' . $errstr, 0); - // exit; - } - /** - * Define a custom exception class, this will make pour tests meaningful - * from php webpage: "Custom exception classes can allow you to write tests that prove your exceptions - * are meaningful. Usually testing exceptions, you either assert the message equals -*something in which case you can't change the message format without refactoring, -*or not make any assertions at all in which case you can get misleading messages -*later down the line. Especially if your $e->getMessage is something complicated -*like a var_dump'ed context array." + * An grade handler to use in AU cases when many different exceptions for data errors may be thrown. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never */ -class nullException extends \Exception -{ - // Redefine the exception so message isn't optional - public function __construct($message, $code = 0, Throwable $previous = null) { - // some code + function grade_warning($errno, $errstr, $errfile, $errline) + { - // make sure everything is assigned properly - parent::__construct($message, $code, $previous); + throw new nullException('Error in checking user grades: ' . $errstr, 0); } - // custom string representation of object (what is returned with echo) - public function __toString(): string { - return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; - } - public function customFunction() { - echo "A custom function for this type of exception\n"; - } -} /** * Define a custom exception class, this will make pour tests meaningful * from php webpage: "Custom exception classes can allow you to write tests that prove your exceptions @@ -222,11 +164,9 @@ public function customFunction() { *later down the line. Especially if your $e->getMessage is something complicated *like a var_dump'ed context array." */ -class missingException extends \Exception +class nullException extends \Exception { // Redefine the exception so message isn't optional - // I want an exception that takkkes what is missing and adds it to messsssage? - // Is this possivlbe? public function __construct($message, $code = 0, Throwable $previous = null) { // some code @@ -234,51 +174,16 @@ public function __construct($message, $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); } - // custom string representation of object (what is returned with echo) public function __toString(): string { return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; - // maybe here? } public function customFunction() { - echo " This error to string :"; - // $this->getTraceAsString(); + echo "A custom function for this type of exception\n"; } } -/** - * Define a custom exception class, this will make pour tests meaningful - * from php webpage: "Custom exception classes can allow you to write tests that prove your exceptions - * are meaningful. Usually testing exceptions, you either assert the message equals -*something in which case you can't change the message format without refactoring, -*or not make any assertions at all in which case you can get misleading messages -*later down the line. Especially if your $e->getMessage is something complicated -*like a var_dump'ed context array." - */ -class fieldException extends \Exception -{ - // Redefine the exception so message isn't optional - // I want an exception that takkkes what is missing and adds it to messsssage? - // Is this possivlbe? - public function __construct($message, $code = 0, Throwable $previous = null) { - // some code - - // make sure everything is assigned properly - parent::__construct($message, $code, $previous); - } - - - // custom string representation of object (what is returned with echo) - public function __toString(): string { - return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; - // maybe here? - } - public function customFunction() { - echo " This error to string :"; - // $this->getTraceAsString(); - } -} /** * Define a custom exception class, this will make pour tests meaningful * from php webpage: "Custom exception classes can allow you to write tests that prove your exceptions @@ -314,5 +219,3 @@ public function customFunction() { // $this->getTraceAsString(); } } - -// If all my exceptions are the same, just diff names, are they necessary? Do any of my try/catches really differentiate? \ No newline at end of file diff --git a/classes/local/token_form.php b/classes/local/token_form.php index c98e7f4..990e10c 100644 --- a/classes/local/token_form.php +++ b/classes/local/token_form.php @@ -32,9 +32,6 @@ public function definition() { // A common convention is to store it in a variable, such as `$mform`. $mform = $this->_form; // Don't forget the underscore! - // Elements are diff inputs in form - - // Add elements to your form. Second arg is the name of element $mform->addElement('text', 'cmi5token', get_string('cmi5launchtenanttoken', 'cmi5launch')); // Set type of element. diff --git a/cmi5PHP/tests/ausHelpersTest.php b/cmi5PHP/tests/ausHelpersTest.php index 3d4584e..e5513de 100644 --- a/cmi5PHP/tests/ausHelpersTest.php +++ b/cmi5PHP/tests/ausHelpersTest.php @@ -1,8 +1,6 @@ willReturn($errormessage); // Expected exceptions - $exceptionmessage = "Player communication error. Something went wrong retrieving the registration information. CMI5 Player returned 400 error. With message 'website not found'." ; + $exceptionmessage = "Player communication error. Something went wrong creating the tenant CMI5 Player returned 400 error. With message 'website not found'." ; // the correct output not an exception @@ -469,7 +485,7 @@ public function testcmi5launch_retrieve_registration_with_get_fail() ->willReturn($errormessage); // Expected exceptions - $exceptionmessage = "Player communication error. Something went wrong retrieving the registration. CMI5 Player returned 404 error. With message 'testmessage'" ; + $exceptionmessage = "Player communication error. Something went wrong retrieving the registration CMI5 Player returned 404 error. With message 'testmessage'." ; // Expected exceptions and messages $this->expectExceptionMessage($exceptionmessage); @@ -689,7 +705,7 @@ public function testcmi5launch_retrieve_registration_with_post_fail() ->willReturn($errormessage); // Expected exceptions - $exceptionmessage = "Player communication error. Something went wrong retrieving the registration. CMI5 Player returned 404 error. With message 'testmessage'" ; + $exceptionmessage = "Player communication error. Something went wrong retrieving the registration CMI5 Player returned 404 error. With message 'testmessage'" ; // Expected exceptions and messages $this->expectExceptionMessage($exceptionmessage); @@ -1126,8 +1142,8 @@ public function testcmi5launch_send_request_to_cmi5_player_post_with_one_arg() // Call the method under test. $test = $helper->cmi5launch_send_request_to_cmi5_player_post($testfunction, $data, $url, $filetype, $token); - // If the right message is displayed the try/catch wworked! - $this->expectOutputString($exceptionmessage); + // If the right message is displayed the try/catch worked. + $this->assertEquals($returnvalue, $test, "The return value should be the same as the return value from the mocked method."); } @@ -1141,11 +1157,7 @@ public function testcmi5launch_send_request_to_cmi5_player_post_with_one_arg_fai // We send the TEST function to the function under test now! $testfunction = 'cmi5Test\cmi5launch_test_stream_and_send_excep'; // Which returns the 'options' parameter passed to it. - // The player returns a string under normal circumstances. - $returnvalue = json_encode(array( - "statusCode" => 200, - "Response" => "Successful Post", - )); + // The data to be passed to the mocked method. $data = array( @@ -1180,11 +1192,6 @@ public function testcmi5launch_send_request_to_cmi5_player_post_with_one_arg_fai // Call the method under test. // Note: by not sending an actual function, this will cause an exception and allow testing of try/catch and error override. $test = $helper->cmi5launch_send_request_to_cmi5_player_post('testfunction', $data, $url, $filetype, $token); - - // And the return should be a string. - $this->assertIsString($test); - // And it should be the same as the return value. - $this->assertEquals($test, $returnvalue); } @@ -1226,9 +1233,6 @@ public function testcmi5launch_send_request_to_cmi5_player_post_with_two_args() $username = "testname"; $password = "testpassword"; - - - // This is the SUT? $helper = new cmi5_connectors; $test = $helper->cmi5launch_send_request_to_cmi5_player_post($testfunction, $data, $url, $filetype, $username, $password); @@ -1318,11 +1322,11 @@ public function testcmi5launch_send_request_to_cmi5_player_with_get_pass() // Get class and the function under test. $helper = new cmi5_connectors; $test = $helper->cmi5launch_send_request_to_cmi5_player_get($testfunction, $token, $url); - + // And the return should be an array. $this->assertIsString($test); // And it should be the same as the return value. - $this->assertEquals($test, json_decode($returnvalue, true) ); + $this->assertEquals($test, $returnvalue, true) ; } @@ -1559,7 +1563,7 @@ public function testcmi5launch_connectors_error_message_path_one() // The expected error message to be output. - $exceptionmessage ="Something went wrong " . $type . ". CMI5 Player is not communicating. Is it running?"; + $exceptionmessage ="Something went wrong " . $type . " CMI5 Player is not communicating. Is it running?"; // Expected exceptions and messages diff --git a/cmi5PHP/tests/errorOverTest.php b/cmi5PHP/tests/errorOverTest.php deleted file mode 100644 index 38d4c7c..0000000 --- a/cmi5PHP/tests/errorOverTest.php +++ /dev/null @@ -1,36 +0,0 @@ -expectException(nullException::class); - - //can we just call it? - // is this necessary? errorover::array_chunk_warning(E_WARNING, "This is a test error", "testfile.php", 1); - - - } -} \ No newline at end of file diff --git a/cmi5PHP/tests/grade_helpersTest.php b/cmi5PHP/tests/grade_helpersTest.php index 1286f4d..b683cb9 100644 --- a/cmi5PHP/tests/grade_helpersTest.php +++ b/cmi5PHP/tests/grade_helpersTest.php @@ -84,11 +84,15 @@ protected function setUp(): void // Assign the AUs to the course. assign_aus_to_courses($testcourseid, $testcourseausids); + + // Delete testcoursesessionids. + deletetestcmi5launch_sessions($testcoursesessionids); } protected function tearDown(): void { - + global $sessionids; + deletetestcmi5launch_sessions($sessionids); } From bce43dae26176164f50bf090fa8fdc7cd3b2f235 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Thu, 15 Aug 2024 12:06:30 -0400 Subject: [PATCH 07/10] AUview page updated --- AUview.php | 146 ++++++++++++++++++--------------- classes/local/errorover.php | 57 +++++++++++++ cmi5PHP/tests/progressTest.php | 13 +-- launch.php | 5 +- 4 files changed, 143 insertions(+), 78 deletions(-) diff --git a/AUview.php b/AUview.php index 4f628c3..a5dfcbf 100755 --- a/AUview.php +++ b/AUview.php @@ -22,12 +22,17 @@ */ use mod_cmi5launch\local\session_helpers; +use mod_cmi5launch\local\customException; use mod_cmi5launch\local\au_helpers; - require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); require('header.php'); require_once("$CFG->dirroot/lib/outputcomponents.php"); + +// Include the errorover (error override) funcs. +require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); + + require_login($course, false, $cm); global $cmi5launch, $USER; @@ -99,12 +104,10 @@ function mod_cmi5launch_launchexperience(registration) { id); @@ -121,65 +124,78 @@ function mod_cmi5launch_launchexperience(registration) { // Reload user course instance. $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); -// Retrieve the registration id. -$regid = $userscourse->registrationid; - // If it is null there have been no previous sessions. if (!$au->sessions == null) { - // Array to hold info for table population. - $tabledata = array(); - - // Build table. - $table = new html_table(); - $table->id = 'cmi5launch_auSessionTable'; - $table->caption = get_string('modulenameplural', 'cmi5launch'); - $table->head = array( - get_string('cmi5launchviewfirstlaunched', 'cmi5launch'), - get_string('cmi5launchviewlastlaunched', 'cmi5launch'), - get_string('cmi5launchviewprogress', 'cmi5launch'), - get_string('cmi5launchviewgradeheader', 'cmi5launch'), - ); - - // Retrieve session ids. - $sessionids = json_decode($au->sessions); - - // Iterate through each session by id. - foreach ($sessionids as $key => $sessionid) { - - // Get the session from DB with session id. - $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); - - // Array to hold data for table. - $sessioninfo = array(); - - - if ($session->createdat != null) { - - // Retrieve createdAt and format. - $date = new DateTime($session->createdat, new DateTimeZone('US/Eastern')); - $date->setTimezone(new DateTimeZone('America/New_York')); - // date_timezone_set($date, new DateTimeZone('America/New_York')); - $sessioninfo[] = $date->format('D d M Y H:i:s'); - } + try{ - if ($session->lastrequesttime != null) { + // Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. + set_error_handler('mod_cmi5launch\local\custom_warningAU', E_WARNING); + set_exception_handler('mod_cmi5launch\local\custom_warningAU'); - // Retrieve lastRequestTime and format. - $date = new DateTime($session->lastrequesttime, new DateTimeZone('US/Eastern')); - $date->setTimezone(new DateTimeZone('America/New_York')); - $sessioninfo[] = $date->format('D d M Y H:i:s'); - } - // Add progress to table. - $sessioninfo[] = ("
" . implode("\n ", json_decode($session->progress) ) . "
"); + // Array to hold info for table population. + $tabledata = array(); + + // Build table. + $table = new html_table(); + $table->id = 'cmi5launch_auSessionTable'; + $table->caption = get_string('modulenameplural', 'cmi5launch'); + $table->head = array( + get_string('cmi5launchviewfirstlaunched', 'cmi5launch'), + get_string('cmi5launchviewlastlaunched', 'cmi5launch'), + get_string('cmi5launchviewprogress', 'cmi5launch'), + get_string('cmi5launchviewgradeheader', 'cmi5launch'), + ); + + + // Retrieve session ids. + $sessionids = json_decode($au->sessions); + + // Iterate through each session by id. + foreach ($sessionids as $key => $sessionid) { + + // Get the session from DB with session id. + $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); - // Add score to table. - $sessioninfo[] = $session->score; - // Add score to array for AU. - $sessionscores[] = $session->score; + // Array to hold data for table. + $sessioninfo = array(); - // Add to be fed to table. - $tabledata[] = $sessioninfo; + + if ($session->createdat != null) { + + // Retrieve createdAt and format. + $date = new DateTime($session->createdat, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + // date_timezone_set($date, new DateTimeZone('America/New_York')); + $sessioninfo[] = $date->format('D d M Y H:i:s'); + } + + if ($session->lastrequesttime != null) { + + // Retrieve lastRequestTime and format. + $date = new DateTime($session->lastrequesttime, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + $sessioninfo[] = $date->format('D d M Y H:i:s'); + } + // Add progress to table. + $sessioninfo[] = ("
" . implode("\n ", json_decode($session->progress)) . "
"); + + // Add score to table. + $sessioninfo[] = $session->score; + // Add score to array for AU. + $sessionscores[] = $session->score; + + // Add to be fed to table. + $tabledata[] = $sessioninfo; + } + } catch (Exception $e) { + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + + // Throw an exception. + throw new customException('loading session table on AUview page. Report this to system administrator: ' . $e->getMessage() . 'Check that session information is present in DB and session id is correct.' , 0); } // Write table. @@ -188,17 +204,17 @@ function mod_cmi5launch_launchexperience(registration) { // Update AU in table with new info. $DB->update_record('cmi5launch_aus', $au); -} -// Build the new session link. -$newsession = "true"; -// Create a string to pass the auid and new session info to next page (launch.php). -$infofornextpage = $auid . "," . $newsession; + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); +} +// Pass the auid and new session info to next page (launch.php). // New attempt button. echo "

"; diff --git a/classes/local/errorover.php b/classes/local/errorover.php index 1cc8679..5b2fcba 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -67,6 +67,24 @@ function progresslrsreq_warning($errno, $errstr, $errfile, $errline) } +/** + * An warning handler to use to post better warnings to users for troubleshooting. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function custom_warningAU($errno, $errstr, $errfile, $errline) +{ + echo "Error loading session table on AUview page. Report this to system administrator:
$errstr at $errfile on $errline: Check that session information is present in DB and session id is correct."; + + exit; + +} + + /** * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. * @param mixed $errno @@ -219,3 +237,42 @@ public function customFunction() { // $this->getTraceAsString(); } } + +/** + * Define a custom exception class, this will make pour tests meaningful + * This is a catchall custom + * from php webpage: "Custom exception classes can allow you to write tests that prove your exceptions + * are meaningful. Usually testing exceptions, you either assert the message equals +*something in which case you can't change the message format without refactoring, +*or not make any assertions at all in which case you can get misleading messages +*later down the line. Especially if your $e->getMessage is something complicated +*like a var_dump'ed context array." + */ +class customException extends \Exception +{ + // Redefine the exception so message isn't optional + // I want an exception that takkkes what is missing and adds it to messsssage? + // Is this possivlbe? + public function __construct($message, $code = 0, Throwable $previous = null) { + // some code + + // Ah maybe here is where I can differentiate them + $playermessage = "Caught error. Something went wrong " . $message; + // make sure everything is assigned properly + parent::__construct($playermessage, $code, $previous); + + echo"$playermessage"; + } + + + // custom string representation of object (what is returned with echo) + public function __toString(): string { + return __CLASS__ . ": [{$this->code}]: {$this->message}\n"; + // maybe here? + } + + public function customFunction() { + // echo " This error to string :"; + // $this->getTraceAsString(); + } +} diff --git a/cmi5PHP/tests/progressTest.php b/cmi5PHP/tests/progressTest.php index 209ac39..b6a7bb8 100644 --- a/cmi5PHP/tests/progressTest.php +++ b/cmi5PHP/tests/progressTest.php @@ -79,11 +79,11 @@ protected function setUp(): void protected function tearDown(): void { - global $sessionids; + global $sessionids, $testcourseid, $cmi5launchsettings; // Restore overridden global variable. unset($GLOBALS['USER']); unset($GLOBALS['cmi5launchsettings']); - + deletetestcmi5launch_usercourse($testcourseid); deletetestcmi5launch_sessions($sessionids); } @@ -974,9 +974,6 @@ public function testcmi5launch_retrieve_statements() { global $CFG, $DB, $sessionids; - // First create a fake session to pass to the function. - $sessions = maketestsessions(); - // Retrieve a sessionid, we'll just use the first one. $sessionid = $sessionids[1]; @@ -1039,9 +1036,6 @@ public function testcmi5launch_retrieve_statements_excep_ext() { global $CFG, $DB, $sessionids; - // First create a fake session to pass to the function. - $sessions = maketestsessions(); - // Retrieve a sessionid, we'll just use the first one. $sessionid = $sessionids[1]; @@ -1103,9 +1097,6 @@ public function testcmi5launch_retrieve_statements_excep() { global $CFG, $DB, $sessionids; - // First create a fake session to pass to the function. - $sessions = maketestsessions(); - // Retrieve a sessionid, we'll just use the first one. $sessionid = $sessionids[1]; diff --git a/launch.php b/launch.php index e00846b..2c13de1 100755 --- a/launch.php +++ b/launch.php @@ -56,7 +56,7 @@ $savesession = $sessionhelper->cmi5launch_get_create_session(); $cmi5launchretrieveurl = $connectors->cmi5launch_get_retrieve_url(); $retrieveaus = $auhelper->get_cmi5launch_retrieve_aus_from_db(); - +/* // Retrieve registration id and au index (from AUview.php). $fromauview = required_param('launchform_registration', PARAM_TEXT); @@ -65,7 +65,8 @@ // Retrieve AU OR session id. $id = array_shift($idandstatus); - +*/ +$id = required_param('launchform_registration', PARAM_TEXT); // Reload cmi5 instance. $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); From f6db1116abf1a46de8d077c104e5d12e43b67832 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Thu, 15 Aug 2024 13:52:19 -0400 Subject: [PATCH 08/10] Launch.php finished with new try/catch code cleanup --- classes/local/errorover.php | 14 +++++ launch.php | 115 ++++++++++++++++++------------------ 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/classes/local/errorover.php b/classes/local/errorover.php index 5b2fcba..6732fb6 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -83,7 +83,21 @@ function custom_warningAU($errno, $errstr, $errfile, $errline) exit; } +/** + * An warning handler to use to post better warnings to users for troubleshooting. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function custom_warning($errno, $errstr, $errfile, $errline) +{ + + throw new customException('Error launching experience. Report this to system administrator:
'. $errstr .' at '. $errfile .' on ' .$errline, 0); +} /** * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. diff --git a/launch.php b/launch.php index 2c13de1..9f9fa73 100755 --- a/launch.php +++ b/launch.php @@ -15,8 +15,8 @@ // along with Moodle. If not, see . /** - * Launches the experience with the requested registration - * + * Launches the experience with the requested registration number. + * The cmi5 player does the actual playing. This file enables handling of launch url from the player and data saving. * @copyright 2023 Megan Bohland * @copyright Based on work by 2013 Andrew Downes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -24,13 +24,15 @@ use mod_cmi5launch\local\cmi5_connectors; use mod_cmi5launch\local\au_helpers; +use mod_cmi5launch\local\customException; use mod_cmi5launch\local\session_helpers; require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); require('header.php'); require_once("$CFG->dirroot/lib/outputcomponents.php"); -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once('header.php'); + +// Include the errorover (error override) funcs. +require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); require_login($course, false, $cm); @@ -56,83 +58,82 @@ $savesession = $sessionhelper->cmi5launch_get_create_session(); $cmi5launchretrieveurl = $connectors->cmi5launch_get_retrieve_url(); $retrieveaus = $auhelper->get_cmi5launch_retrieve_aus_from_db(); -/* -// Retrieve registration id and au index (from AUview.php). -$fromauview = required_param('launchform_registration', PARAM_TEXT); -// Break it into array (AU or session id is first index). -$idandstatus = explode(",", $fromauview); - -// Retrieve AU OR session id. -$id = array_shift($idandstatus); -*/ +// Retrieve the registration id from previous page. $id = required_param('launchform_registration', PARAM_TEXT); + // Reload cmi5 instance. $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); // Retrieve user's course record. $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); -// Retrieve registration id. -$registrationid = $userscourse->registrationid; +// To hold launch url. +$location = ""; + +// Most of the functions below have their own error handling. We will encapsulate here in case there are any php errors, such +// as json_decode not working, etc. +// Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. +set_error_handler('mod_cmi5launch\local\custom_warning', E_WARNING); +//set_exception_handler('mod_cmi5launch\local\customException'); -if (empty($registrationid)) { - echo "
" . get_string('cmi5launch_regidempty', 'cmi5launch') . "
"; +try { + // Retrieve AUs. + $au = $retrieveaus($id); - // Failed to connect to LRS. - if ($CFG->debug == 32767) { + // Retrieve the au index. + $auindex = $au->auindex; - echo "

Error attempting to get registration id querystring parameter.

"; + // Pass in the au index to retrieve a launchurl and session id. + $urldecoded = $cmi5launchretrieveurl($cmi5launch->id, $auindex); - die(); - } -} + // Retrieve and store session id in the aus table. + $sessionid = intval($urldecoded['id']); -// To hold launch url. -$location = ""; + // Check if there are previous sessions. + if (!$au->sessions == null) { + // We don't want to overwrite so retrieve the sessions before changing them. + $sessionlist = json_decode($au->sessions); + // Now add the new session number. + $sessionlist[] = $sessionid; -// Retrieve AUs. -$au = $retrieveaus($id); + } else { + // If it is null just start fresh. + $sessionlist = array(); + $sessionlist[] = $sessionid; + } -// Retrieve the au index. -$auindex = $au->auindex; + // Save sessions. + $au->sessions = json_encode($sessionlist); -// Pass in the au index to retrieve a launchurl and session id. -$urldecoded = $cmi5launchretrieveurl($cmi5launch->id, $auindex); + // The record needs to updated in DB. + $updated = $DB->update_record('cmi5launch_aus', $au, true); -// Retrieve and store session id in the aus table. -$sessionid = intval($urldecoded['id']); + // Retrieve the launch url. + $location = $urldecoded['url']; + // And launch method. + $launchmethod = $urldecoded['launchMethod']; -// Check if there are previous sessions. -if (!$au->sessions == null) { - // We don't want to overwrite so retrieve the sessions before changing them. - $sessionlist = json_decode($au->sessions); - // Now add the new session number. - $sessionlist[] = $sessionid; +} catch (\Throwable $e) { + // Restore default handlers. + restore_exception_handler(); + restore_error_handler(); -} else { - // If it is null just start fresh. - $sessionlist = array(); - $sessionlist[] = $sessionid; + // If there is an error, return the error. + throw new customException("Error in launching experience. Report this error to system administrator: ". $e->getMessage()); } + // Create and save session object to session table. + $savesession($sessionid, $location, $launchmethod); -// Save sessions. -$au->sessions = json_encode($sessionlist); - -// The record needs to updated in DB. -$updated = $DB->update_record('cmi5launch_aus', $au, true); + // Last thing check for updates. + cmi5launch_update_grades($cmi5launch, $USER->id); -// Retrieve the launch url. -$location = $urldecoded['url']; -// And launch method. -$launchmethod = $urldecoded['launchMethod']; + header("Location: " . $location); -// Create and save session object to session table. -$savesession($sessionid, $location, $launchmethod); -// Last thing check for updates. -cmi5launch_update_grades($cmi5launch, $USER->id); -header("Location: ". $location); +// Restore default handlers. +restore_exception_handler(); +restore_error_handler(); exit; From 58f5601a8a0a956f159e78f9070b2799f92342c5 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Thu, 15 Aug 2024 15:47:22 -0400 Subject: [PATCH 09/10] Finished updating code with try/catch --- AUview.php | 1 - classes/local/errorover.php | 15 ++ classes/local/session_helpers.php | 20 ++ view.php | 403 +++++++++++++++++------------- 4 files changed, 258 insertions(+), 181 deletions(-) diff --git a/AUview.php b/AUview.php index a5dfcbf..1315075 100755 --- a/AUview.php +++ b/AUview.php @@ -160,7 +160,6 @@ function mod_cmi5launch_launchexperience(registration) { // Array to hold data for table. $sessioninfo = array(); - if ($session->createdat != null) { // Retrieve createdAt and format. diff --git a/classes/local/errorover.php b/classes/local/errorover.php index 6732fb6..998e2f1 100644 --- a/classes/local/errorover.php +++ b/classes/local/errorover.php @@ -99,6 +99,21 @@ function custom_warning($errno, $errstr, $errfile, $errline) } +/** + * An warning handler to use to post better warnings to users for troubleshooting. + * @param mixed $errno + * @param mixed $errstr + * @param mixed $errfile + * @param mixed $errline + * @throws \mod_cmi5launch\local\nullException + * @return never + */ +function custom_warningview($errno, $errstr, $errfile, $errline) +{ + + throw new customException('error on main view page. Report this to system administrator:
'. $errstr .' at '. $errfile .' on ' .$errline, 0); + +} /** * An exception handler to use in AU cases when many different exceptions for data errors may be thrown. * @param mixed $errno diff --git a/classes/local/session_helpers.php b/classes/local/session_helpers.php index 7ed3b8a..1bceeea 100644 --- a/classes/local/session_helpers.php +++ b/classes/local/session_helpers.php @@ -78,9 +78,22 @@ public function cmi5launch_update_sessions($progress, $cmi5, $sessionid, $cmi5la $sessioninfo = json_decode($getsessioninfo($sessionid, $cmi5launchid), true); // Update session. foreach ($sessioninfo as $key => $value) { + + // Is the problem key value is not matching due to caps? // We don't want to overwrite ids. // If the property exists and it's not id or sessionid, set it to lowercase and // encode value if it is array. (DB needs properties in lowercase, but player returns camelcase). + + + // If it's an array, encode it so it can be saved to DB. + if (is_array($value)) { + $value = json_encode($value); + } + + if (is_string($key)) { + $key = mb_convert_case($key, MB_CASE_LOWER, "UTF-8"); + } + if (property_exists($session, $key) && $key != 'id' && $key != 'sessionid') { // If it's an array, encode it so it can be saved to DB. @@ -152,6 +165,13 @@ public function cmi5launch_create_session($sessionid, $launchurl, $launchmethod) // Save record to table. $newid = $DB->insert_record($table, $newrecord, true); + // Instantiate progress and cmi5_connectors class to pass. + $progress = new progress; + $cmi5 = new cmi5_connectors; + + // Retrieve new info (if any) from CMI5 player and LRS on session. + $session = $this->cmi5launch_update_sessions($progress, $cmi5, $sessionid, $cmi5launch->id, $USER); + // Restore default handlers. restore_exception_handler(); restore_error_handler(); diff --git a/view.php b/view.php index f7479f9..9cc3c38 100755 --- a/view.php +++ b/view.php @@ -15,12 +15,14 @@ // along with Moodle. If not, see . /** - * Displays the AU's of a course and their progress. + * Displays the AU's of a course and their current progress (satisfied/ not satisifed etc.). + * Also allows for launching of the AU. * @copyright 2023 Megan Bohland * @copyright Based on work by 2013 Andrew Downes * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use mod_cmi5launch\local\customException; use mod_cmi5launch\local\progress; use mod_cmi5launch\local\course; use mod_cmi5launch\local\cmi5_connectors; @@ -30,6 +32,10 @@ require_once("../../config.php"); require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); require('header.php'); + +// Include the errorover (error override) funcs. +require_once ($CFG->dirroot . '/mod/cmi5launch/classes/local/errorover.php'); + require_login($course, false, $cm); // Bring in functions and classes. @@ -130,63 +136,78 @@ function mod_cmi5launch_launchexperience(registrationInfo) { // Check if a course record exists for this user yet. $exists = $DB->record_exists('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); + +// Set error and exception handler to catch and override the default PHP error messages, to make messages more user friendly. +set_error_handler('mod_cmi5launch\local\custom_warningview', E_WARNING); +set_exception_handler('mod_cmi5launch\local\custom_warningview'); -// If it does not exist, create it. -if ($exists == false) { - - // Make a new course record. - $userscourse = new course($record); +try { + // If it does not exist, create it. + if ($exists == false) { - // Retreive user id. - $userscourse->userid = $USER->id; + // Make a new course record. + $userscourse = new course($record); - // Build url to pass as returnUrl. - $returnurl = $CFG->wwwroot .'/mod/cmi5launch/view.php'. '?id=' .$cm->id; - $userscourse->returnurl = $returnurl; + // Retreive user id. + $userscourse->userid = $USER->id; + // Build url to pass as returnUrl. + $returnurl = $CFG->wwwroot . '/mod/cmi5launch/view.php' . '?id=' . $cm->id; + $userscourse->returnurl = $returnurl; - // Assign new record a registration id. - $registrationid = $getregistration($userscourse->courseid, $cmi5launch->id); - $userscourse->registrationid = $registrationid; + // Assign new record a registration id. + $registrationid = $getregistration($userscourse->courseid, $cmi5launch->id); + $userscourse->registrationid = $registrationid; - // Retreive the Moodle course id - - $userscourse->moodlecourseid = $cm->instance; + // Retreive the Moodle course id - // Retrieve AU ids for this user/course. - $aus = json_decode($record->aus); + $userscourse->moodlecourseid = $cm->instance; - // We should not even be able to et here if these are false on record - - $auids = $saveaus($createaus($aus)); - $userscourse->aus = (json_encode($auids)); + // Retrieve AU ids for this user/course. + $aus = json_decode($record->aus); - // Save new record to DB. - $newid = $DB->insert_record('cmi5launch_usercourse', $userscourse); + // We should not even be able to et here if these are false on record + $auids = $saveaus($createaus($aus)); + $userscourse->aus = (json_encode($auids)); - // Now assign id created by DB. - $userscourse->id = $newid; + // Save new record to DB. + $newid = $DB->insert_record('cmi5launch_usercourse', $userscourse); -} else { // Record exists. + // Now assign id created by DB. + $userscourse->id = $newid; - // We have a record, so we need to retrieve it. - $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); + } else { // Record exists. - // Retrieve registration id. - $registrationid = $userscourse->registrationid; + // We have a record, so we need to retrieve it. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); - // We need to verify if there is a registration id. Sometimes errors with player can cause a null id, in that case we want to - // retrieve a new one. - if ($registrationid == null) { // Retrieve registration id. - $registrationid = $getregistration($record->courseid, $cmi5launch->id); - // Update course record. - $userscourse->registrationid = $registrationid; - // Update DB. - $DB->update_record("cmi5launch_usercourse", $userscourse); + $registrationid = $userscourse->registrationid; + + // We need to verify if there is a registration id. Sometimes errors with player can cause a null id, in that case we want to + // retrieve a new one. + if ($registrationid == null) { + // Retrieve registration id. + $registrationid = $getregistration($record->courseid, $cmi5launch->id); + // Update course record. + $userscourse->registrationid = $registrationid; + // Update DB. + $DB->update_record("cmi5launch_usercourse", $userscourse); + } + // Retrieve AU ids. + $auids = (json_decode($userscourse->aus)); +// $auids = (json_decode($userscourse)); + } - // Retrieve AU ids. - $auids = (json_decode($userscourse->aus) ); + +} catch (Exception $e) { + + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + + // If there is an error, display it. + throw new customException('Creating or retrieving user course record. Contact your system administrator with error: ' . $e->getMessage(), 0); } // Array to hold info for table population. @@ -208,196 +229,218 @@ function mod_cmi5launch_launchexperience(registrationInfo) { // Array to hold Au scores. $auscores = array(); +try { + // Query CMI5 player for updated registration info. + $registrationinfofromcmi5 = json_decode($getregistrationinfo($registrationid, $cmi5launch->id), true); -// Query CMI5 player for updated registration info. -$registrationinfofromcmi5 = json_decode($getregistrationinfo($registrationid, $cmi5launch->id), true); - -// Take only info about AUs out of registrationinfofromcmi5. -$ausfromcmi5 = array_chunk($registrationinfofromcmi5["metadata"]["moveOn"]["children"], 1, true); + // Take only info about AUs out of registrationinfofromcmi5. + $ausfromcmi5 = array_chunk($registrationinfofromcmi5["metadata"]["moveOn"]["children"], 1, true); -// Cycle through AU IDs making AU objects and checking progress. -foreach ($auids as $key => $auid) { + // Cycle through AU IDs making AU objects and checking progress. + foreach ($auids as $key => $auid) { - // Array to hold scores for AU. - $sessionscores = array(); - $au = $getaus($auid); + // Array to hold scores for AU. + $sessionscores = array(); + $au = $getaus($auid); - // Verify object is an au object. - if (!is_a($au, 'mod_cmi5launch\local\au', false)) { + // Verify object is an au object. + if (!is_a($au, 'mod_cmi5launch\local\au', false)) { - $reason = "Excepted AU, found "; - var_dump($au); - throw new moodle_exception($reason, 'cmi5launch', '', $warnings[$reason]); - } - - // Retrieve AU's lmsID. - $aulmsid = $au->lmsid; - - // To hold if the au is satisfied. - $ausatisfied = ""; - - // Cycle through AUs (or blocks) in registration info from player, we are looking for the one - // that matches our AU lmsID. - foreach ($ausfromcmi5 as $key => $value) { - - // Check for the AUs satisfied status. Compare with lmsId to find status for that instance. - $ausatisfied = cmi5launch_find_au_satisfied($value, $aulmsid); - // If au satisfied is ever true then we found it, once satisified it - // doesn't matter if others have failed or were also satisified. - if ($ausatisfied == "true") { - break; - - // This elseif was built as a failsafe. Very rarely there may be an instance where the player issues - // a duplicate lms id or registration number. For example, this can happen if the server crashes while a course - // is being made or updated. - // However, under normal circumstances, the AU LMSID should always match at least one of the AUs returned by player. - } else if ($ausatisfied = "No ids match") { - - // If there are sessions for this AU. - if ($au->sessions != null) { - - // Retrieve session ids for this AU from DB. - $sessions = json_decode($au->sessions, true); - $sessionhelpers = new session_helpers; - $getsessioninfo = $sessionhelpers->cmi5launch_get_retrieve_sessions_from_db(); - - // Retrieve what this AU needs to moveon. We will search through the session data to see if it is fulfilled. - $aumoveon = $au->moveon; + $reason = "Excepted AU, found "; + var_dump($au); + throw new moodle_exception($reason, 'cmi5launch', '', $warnings[$reason]); + } - // Hold if completed or passed is found. - $completedfound = false; - $passedfound = false; + // Retrieve AU's lmsID. + $aulmsid = $au->lmsid; - // Cycle through them looking to see if any were passed and/or completed. - foreach ($sessions as $key => $value) { + // To hold if the au is satisfied. + $ausatisfied = ""; - // Get the session from DB with session id. - $ausession = $DB->get_record('cmi5launch_sessions', array('sessionid' => $value)); - - if ($ausession->iscompleted == "1") { - $completedfound = true; - } - if ($ausession->ispassed == "1") { - $passedfound = true; - } + // Cycle through AUs (or blocks) in registration info from player, we are looking for the one + // that matches our AU lmsID. + foreach ($ausfromcmi5 as $key => $value) { - // See if the pass and completed fulfill move on value for AU. - switch ($aumoveon) { - case "Completed": - if ($completedfound == true) { - $ausatisfied = "true"; - }; - break; - case "Passed": - if ($passedfound == true) { - $ausatisfied = "true"; - }; - break; - case "CompletedOrPassed": - if ($completedfound == true || $passedfound == true) { - $ausatisfied = "true"; - }; - break; - case "CompletedAndPassed": - if ($completedfound == true && $passedfound == true) { - $ausatisfied = "true"; - }; + // Check for the AUs satisfied status. Compare with lmsId to find status for that instance. + $ausatisfied = cmi5launch_find_au_satisfied($value, $aulmsid); + // If au satisfied is ever true then we found it, once satisified it + // doesn't matter if others have failed or were also satisified. + if ($ausatisfied == "true") { + break; + + // This elseif was built as a failsafe. Very rarely there may be an instance where the player issues + // a duplicate lms id or registration number. For example, this can happen if the server crashes while a course + // is being made or updated. + // However, under normal circumstances, the AU LMSID should always match at least one of the AUs returned by player. + } else if ($ausatisfied = "No ids match") { + + // If there are sessions for this AU. + if ($au->sessions != null) { + + // Retrieve session ids for this AU from DB. + $sessions = json_decode($au->sessions, true); + $sessionhelpers = new session_helpers; + $getsessioninfo = $sessionhelpers->cmi5launch_get_retrieve_sessions_from_db(); + + // Retrieve what this AU needs to moveon. We will search through the session data to see if it is fulfilled. + $aumoveon = $au->moveon; + + // Hold if completed or passed is found. + $completedfound = false; + $passedfound = false; + + // Cycle through them looking to see if any were passed and/or completed. + foreach ($sessions as $key => $value) { + + // Get the session from DB with session id. + $ausession = $DB->get_record('cmi5launch_sessions', array('sessionid' => $value)); + + if ($ausession->iscompleted == "1") { + $completedfound = true; + } + if ($ausession->ispassed == "1") { + $passedfound = true; + } + + // See if the pass and completed fulfill move on value for AU. + switch ($aumoveon) { + case "Completed": + if ($completedfound == true) { + $ausatisfied = "true"; + } + ; + break; + case "Passed": + if ($passedfound == true) { + $ausatisfied = "true"; + } + ; + break; + case "CompletedOrPassed": + if ($completedfound == true || $passedfound == true) { + $ausatisfied = "true"; + } + ; + break; + case "CompletedAndPassed": + if ($completedfound == true && $passedfound == true) { + $ausatisfied = "true"; + } + ; + break; + } + + // If even one AU satisifed is met, then the AU is satisfied overall. Later or earlier sessions don't matter. + if ($ausatisfied == "true") { break; - } - - // If even one AU satisifed is met, then the AU is satisfied overall. Later or earlier sessions don't matter. - if ($ausatisfied == "true") { - break; + } } } } } - } - // If the 'sessions' in this AU are null we know this hasn't even been attempted. - if ($au->sessions == null) { - - $austatus = "Not attempted"; - - } else { + // If the 'sessions' in this AU are null we know this hasn't even been attempted. + if ($au->sessions == null) { - // Retrieve AUs moveon specification. - $aumoveon = $au->moveon; + $austatus = "Not attempted"; - // If it's been attempted but no moveon value. - if ($aumoveon == "NotApplicable") { - $austatus = "viewed"; } else { - // IF it DOES have a moveon value. - // If satisifed is returned true. - if ($ausatisfied == "true") { - $austatus = "Satisfied"; - // Also update AU. - $au->satisfied = "true"; - } else { + // Retrieve AUs moveon specification. + $aumoveon = $au->moveon; - // If not, its in progress. - $austatus = "In Progress"; - // Also update AU. - $au->satisfied = "false"; + // If it's been attempted but no moveon value. + if ($aumoveon == "NotApplicable") { + $austatus = "viewed"; + } else { + // IF it DOES have a moveon value. + // If satisifed is returned true. + if ($ausatisfied == "true") { + + $austatus = "Satisfied"; + // Also update AU. + $au->satisfied = "true"; + } else { + + // If not, its in progress. + $austatus = "In Progress"; + // Also update AU. + $au->satisfied = "false"; + } } } - } - // Create array of info to place in table. - $auinfo = array(); + // Create array of info to place in table. + $auinfo = array(); - // Assign au name, progress, and index. - $auinfo[] = $au->title; - $auinfo[] = ($austatus); + // Assign au name, progress, and index. + $auinfo[] = $au->title; + $auinfo[] = ($austatus); - $grade = 0; + $grade = 0; - // Retrieve grade. - if (!$au->grade == 0 || $au->grade == null) { + // Retrieve grade. + if (!$au->grade == 0 || $au->grade == null) { - $grade = $au->grade; + $grade = $au->grade; - $auinfo[] = ($grade); - } else if ($au->grade == 0) { + $auinfo[] = ($grade); + } else if ($au->grade == 0) { - // Display the 0. - $auinfo[] = ($grade); - } else { - // There is no grade, leave blank. - $auinfo[] = (" "); - } + // Display the 0. + $auinfo[] = ($grade); + // TODO - This needs to be more interactive, course creators need to be able top control + // whether a satisified AU is considered a 0 or not, but for now, if satisfied, don't show 0. + if ($austatus == "Satisfied") { + $auinfo[2] = " "; + } + + } else { + // There is no grade, leave blank. + $auinfo[] = (" "); + } $auindex = $au->auindex; // AU id for next page (to be loaded). - $infofornextpage = $auid; + // $infofornextpage = $auid; // Assign au link to auviews. $auinfo[] = ""; // Add to be fed to table. $tabledata[] = $auinfo; // Update AU scores. - $auscores[$au->lmsid] = array ($au->title => $au->scores); + $auscores[$au->lmsid] = array($au->title => $au->scores); // Update the AU in DB. $DB->update_record("cmi5launch_aus", $au); -} + } -// Add our newly updated auscores array to the course record. -$userscourse->ausgrades = json_encode($auscores); + // Add our newly updated auscores array to the course record. + $userscourse->ausgrades = json_encode($auscores); +} catch (Exception $e) { + // Restore default hadlers. + restore_exception_handler(); + restore_error_handler(); + + // If there is an error, display it. + throw new customException('retrieving and displaying AU satisfied status and grade. Contact your system administrator with error: ' . $e->getMessage(), 0); +} // Lastly, update our course table. $updated = $DB->update_record("cmi5launch_usercourse", $userscourse); // This feeds the table. $table->data = $tabledata; +// Restore default hadlers. +restore_exception_handler(); +restore_error_handler(); + echo html_writer::table($table); // Add a form to be posted based on the attempt selected. From 24e098cb7efc03ffd0d2c5ccdda454914579d5e6 Mon Sep 17 00:00:00 2001 From: ADLMeganBohland Date: Thu, 15 Aug 2024 15:47:56 -0400 Subject: [PATCH 10/10] Typo fixed --- cmi5setup.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/cmi5setup.php b/cmi5setup.php index 4a7d7cb..dda63d0 100644 --- a/cmi5setup.php +++ b/cmi5setup.php @@ -22,7 +22,7 @@ */ -// NEeded for moodle pae. sets up loabl +// Needed for moodle page. require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); // Tell moodle about our page, tell it what the url is.\\ @@ -51,13 +51,7 @@ $playerpass = get_config('cmi5launch', 'cmi5launchbasepass'); $playerpass = null; -echo "
"; -echo"Settings are: "; -echo $playerurl . " "; -echo "
"; -echo $playername; -echo "
"; -echo $playerpass; + // If the settings are not set, then display the first form. if(!$playerurl || !$playername || !$playerpass){