diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dbe9c82 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index aef8c13..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "github.copilot.enable": { - - "*": true, - "plaintext": false, - "markdown": false, - "scminput": false - }, - "github.copilot.advanced": {} -} \ No newline at end of file diff --git a/AUview.php b/AUview.php index c6da1a8..13318ea 100755 --- a/AUview.php +++ b/AUview.php @@ -1,259 +1,211 @@ -. - -/** - * Prints an AUs session information annd allows retreival of session or start of new one. - * - * @copyright 2023 Megan Bohland - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -//namespace cmi5; -//For some reason using the namespace cmi5; here breaks the code. -//It cannot find html_table class. - - -require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); -require('header.php'); - -//For connecting to Progress class - MB -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/Progress.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/sessionHelpers.php"); -require_once("$CFG->dirroot/lib/outputcomponents.php"); - -global $cmi5launch, $USER; - -//Classes and functions -$aus_helpers = new Au_Helpers; -$ses_helpers = new Session_Helpers; -$progress = new progress; -$getProgress = $progress->getRetrieveStatement(); -$updateSession = $ses_helpers->getUpdateSession(); -$getAUs = $aus_helpers->getAUsFromDB(); - -// Trigger module viewed event. -$event = \mod_cmi5launch\event\course_module_viewed::create(array( - 'objectid' => $cmi5launch->id, - 'context' => $context, -)); -$event->add_record_snapshot('course', $course); -$event->add_record_snapshot('cmi5launch', $cmi5launch); -$event->add_record_snapshot('course_modules', $cm); -$event->trigger(); - -// Print the page header. -$PAGE->set_url('/mod/cmi5launch/view.php', array('id' => $cm->id)); -$PAGE->set_title(format_string($cmi5launch->name)); -$PAGE->set_heading(format_string($course->fullname)); -$PAGE->set_context($context); -$PAGE->requires->jquery(); - -// Output starts here. -echo $OUTPUT->header(); - -if ($cmi5launch->intro) { // Conditions to show the intro can change to look for own settings or whatever. - echo $OUTPUT->box( - format_module_intro('cmi5launch', $cmi5launch, $cm->id), - 'generalbox mod_introbox', - 'cmi5launchintro' - ); -} - -// TODO: Put all the php inserted data as parameters on the functions and put the functions in a separate JS file. -?> - - -get_record('cmi5launch', array('id' => $cmi5launch->id)); - -//Reload user course instance -$usersCourse = $DB->get_record('cmi5launch_course', ['courseid' => $record->courseid, 'userid' => $USER->id]); - -//Retrieve the registration id -$regid = $usersCourse->registrationid; - -//TODO -//For later to change student view vs teacher -//Lets check for the certain capability and display a message if it is found/not found -//Excellent! The test works, now lets introduce like a flag, and use that to display progress or not -//Well call it canSee for now -/* -$canSee = true | false; -//now change it based on capability -$context = context_module::instance($cm->id); -if (has_capability('mod/cmi5launch:addinstance', $context)) { - //This is someone we want to let see grades/progress!!!"; - $canSee = true; -}else{ - //This is not someone to see grades!"; - echo "
"; - $canSee = false; -} -*/ - -//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'), - get_string('cmi5launchviewlaunchlinkheader', 'cmi5launch'), - ); - - //Retrieve session ids - $sessionIDs = json_decode($au->sessions); - - //Iterate through each session by id - foreach($sessionIDs as $key => $sessionID){ - - //Retrieve new info (if any) from CMI5 player on session - $session = $updateSession($sessionID, $cmi5launch->id); - - //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'); - - ///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'); - - //Get progress from LRS - $session = $getProgress($regid, $cmi5launch->id, $session); - $sessionInfo[] = ("
" . implode("\n ", json_decode($session->progress) ) . "
"); - - //add score to table - $sessionInfo[] = $session->score; - //Add score to array for AU - $sessionScores[] = $session->score; - - //Update session in DB - $DB->update_record('cmi5launch_sessions', $session); - - //Build launch link to continue session - $newSession = "false"; - $infoForNextPage = $sessionID . "," . $newSession; - - $sessionInfo[] = "" - . get_string('cmi5launchviewlaunchlink', 'cmi5launch') . ""; - - //add to be fed to table - $tableData[] = $sessionInfo; - } - - //Write table - $table->data = $tableData; - echo html_writer::table($table); - - //Save the session scores to AU, it is ok to overwrite - $au->scores = json_encode($sessionScores); - - //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; -//New attempt -echo "

" - . get_string('cmi5launch_attempt', 'cmi5launch') - . "

"; - -// Add a form to be posted based on the attempt selected. -?> -
- - - -
-footer(); \ No newline at end of file +. + +/** + * Prints an AUs session information annd 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 + */ + +use mod_cmi5launch\local\session_helpers; +use mod_cmi5launch\local\au_helpers; + +require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +require('header.php'); +require_once("$CFG->dirroot/lib/outputcomponents.php"); + +require_login($course, false, $cm); + +global $cmi5launch, $USER; + +// Classes and functions. +$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. +/* +// Trigger module viewed event. +$event = \mod_cmi5launch\event\course_module_viewed::create(array( + 'objectid' => $cmi5launch->id, + 'context' => $context, +)); +$event->add_record_snapshot('course', $course); +$event->add_record_snapshot('cmi5launch', $cmi5launch); +$event->add_record_snapshot('course_modules', $cm); +$event->trigger(); +*/ + +// Print the page header. +$PAGE->set_url('/mod/cmi5launch/view.php', array('id' => $cm->id)); +$PAGE->set_title(format_string($cmi5launch->name)); +$PAGE->set_heading(format_string($course->fullname)); +$PAGE->set_context($context); +$PAGE->requires->jquery(); + +// Output starts here. +echo $OUTPUT->header(); + +// Create the back button. +?> +
+ + +
+ + + +id); + +// Retrieve appropriate AU from DB. +$au = $retrieveaus($auid); + +// Array to hold session scores for the AU. +$sessionscores = array(); + +// 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]); + +// 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 = $retrievesession($sessionid); + + // 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'); + + // Retrieve lastRequestTime and format. + $date = new DateTime($session->updatedat, 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; + } + + // Write table. + $table->data = $tabledata; + echo html_writer::table($table); + + // 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; + +// New attempt button. +echo "

"; + +// Add a form to be posted based on the attempt selected. +?> +
+ + + +
+ +footer(); diff --git a/README.md b/README.md index 5122a7c..2e2184c 100755 --- a/README.md +++ b/README.md @@ -1,59 +1,161 @@ 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. +### Thanks + +This plugin is based on the xAPI Launch Link (Tin can launch) plugin developed by David Pesce and Andrew Downes. That plugin is located at https://github.com/davidpesce/moodle-mod_tincanlaunch. + +We also based many functions off examples from SCORM plugin that is packaged with Moodle. + ## What you will need To use this plugin you will need the following: + + * Moodle 4.0+ + + * Login details for the admin account + + * A Moodle course setup where you would like to add the activity + + * A cmi5 package in compliance with the cmi5 Specification (https://github.com/AICC/CMI-5_Spec_Current/blob/quartz/cmi5_spec.md) + + * A Learning Record Store (LRS) as defined in the xAPI specification (https://opensource.ieee.org/xapi/xapi-base-standard-documentation) + + * A running instance of the cmi5 prototype player (https://github.com/adlnet/CATAPULT/tree/main/player) + + * A copy of this plugin + ## Installation Instructions + Once the plugin is downloaded, be sure it is entirely in a file named 'cmi5launch.' Then compress that file into zip. The zip file _must_ be named 'cmi5_moodle.zip' to be uploaded to Moodle. + ### Install plugin in Moodle + To install the CMI5 plugin into Moodle - + + - Go to _Site Administration_ + + - Go to _Plugins_ + + - Click on _Install Plugins_ + + - Currently the only way to install plugin is via zip file + + - Under _Install plugin from ZIP file_ upload the plugin. It must be in a zip file named 'cmi5_moodle.zip'. + + - Follow the Moodle instructions to upload plugin to the site -Once Moodle has finished uploading the plugin it will brin up the _cmi5 Launch Link_ settings page. This will enable you to set the information to connect to your LRS and instance of your running CMI5 player. These are the fields: + + +Once Moodle has finished uploading the plugin it will bring up the _cmi5 Launch Link_ settings page. This will enable you to set the information to connect to your LRS and instance of your running CMI5 player. These are the fields: + + + - Endpoint + + - The LRS endpoint, to enable the plugin to communicate with your LRS. Ex: https://lrsendpoint.com/data/xAPI/. Must include trailing forward slash. + + - LRS integration + + - This is an optional dropdown for addtional LRS's. Defaults to 'None.' + + - LRS: Basic Username + + - The basic username for connecting to your LRS. + + - LRS: Basic Password + + - Your password for connecting to your LRS. + + - Duration + + - Used with 'LRS integrated basic authentication'. Requests the LRS to keep credentials valid for this number of minutes. Default is set to 9000. + + - Custom account homepage + + - If entered, Moodle will use this homePage in conjunction with the ID number user profile field to identify the learner. Defaults to https://moodle.com. + + - Identify by email + + - If selected, learners will be identified by their email address if they have one recorded in Moodle. + + - cmi5 Player URL - -The URL to communicate with the CMI5 player, can include port number(e.g. http://player.example.com or http://localhost:63398). Must NOT include a trailing forward slash. + : The URL to communicate with the CMI5 player, can include port number(e.g. http://player.example.com or http://localhost:63398). Must NOT include a trailing forward slash. + + + - cmi5 Player: Basic Username + + - This is the tenant username for the CMI5 player + + - cmi5 Player: Basic Password + + - This is the basic password to login to player. (IS this needed? check code - MB) + + - cmi5 Player: Bearer Token + + - The cmi5 tenant bearer token (should be a long string). -There are two sections "Default ccalues foor CMI5 LAunch Link activity grades" and "Default values for CMI5 Launch Link activity attempts and completion." These can be ignored for now as they are works in progress for future features. + + + +The next section "Default values for CMI5 Launch Link activity grades" deals with the grade setting. + + +- Grading method + + + - How the overall grade for CMI5 activities is calculated. If "Highest" the highest grade will be used, if "Average" the scores will be averaged. + + +- Maximum grade + + + - The Maxium the grade can be (default 100). + + +The section "Default values for CMI5 Launch Link activity attempts and completion" can be ignored for now as it is a work in progress for future features. + The plugin is now accessible as a resource to be added to a Moodle course! Under your Moodle courses in edit mode select _Add activity or resource_ and you will see _cmi5 Launch Link_ as an available option. Currently the Launch link can take a folder or XML compliant with cmi5 specification. @@ -62,78 +164,278 @@ The plugin is now accessible as a resource to be added to a Moodle course! Under This flowchart shows the path a user takes to get to a cmi5 Lesson Link. Once the link is clicked, the cmi5 Player opens in a new tab or window. The Moodle Application negotiates the connection by supplying credentials, tenant, and the current user session information. The Lesson Link contains a token in which both sides can track the user. + ```mermaid + flowchart TB + subgraph MOODLE[Moodle Application] + direction TB + Course(Course) --> Assignment(Assignment) --> Activity --> Link + Webhook[Progress\nEndpoint] + subgraph Activity + Link(Lesson Link) + end + end + MOODLE -.-> MoodleDB[(Moodle DB)] subgraph cmi5[cmi5 Player] + direction TB + Lesson --> Lesson + end cmi5 -.-> Webhook + Link <---> cmi5 + Link -.-> LRS[(LRS)] + cmi5 -.-> cmi5DB[(cmi5 DB)] + cmi5 -.-> LRS + ``` + + ## Sequence diagrams for connecting to CMI5 player + Following are the two functions Moodle uses to create a course and retrieve a course URL from the CMI5 player. + ### Create course + ```mermaid + sequenceDiagram + title: Create Course - + participant Moodle + participant CMI5 + participant C-DB as CMI5's MySQL Database + participant M-DB as Moodle's MySQL Database - - + Moodle->>+CMI5: Send POST to /api/v1/course + break error + CMI5-->>+Moodle: response other than 200 + Note over Moodle, CMI5: Check content-type, package, or token + end + CMI5->>+C-DB: Create course in DB + CMI5->>+Moodle: 200, returns JSON body + Moodle->>+M-DB: Save lmsId, Id, course metadata ``` + + ### Retrieve launch URL + + ```mermaid + sequenceDiagram + title: Retrieve URL - + participant Moodle + participant CMI5 + participant M-DB as Moodle's MySQL Database - - + Moodle->>+CMI5: Send POST to /api/v1/course/{courseId}/launchurl/{AU index} + break error + CMI5-->>+Moodle: response other than 200 + Note over Moodle, CMI5: Check actor account info, returnurl, token + end - + + CMI5->>+Moodle: 200, returns JSON body with session id, launch method, and launch url + + Moodle->>+M-DB: Save returned course info to DB ``` ## User progress -The cmi5 player tracks user progress by tracking whether an AU is satisfied or not. The LRS is a better way to track a users progress in any detail. Currently the Moodle plugin queries the LRS for progress. This in shows AUs as complete, in-progress, or not attempted. It also breaks down sessions into detail, ex; " The actor watched video at certain time". +The CMI5 player tracks user progress by tracking whether an AU is satisfied or not. The LRS is a better way to track a users progress in any detail. Currently the Moodle plugin queries the LRS for progress. This in shows AUs as complete, in-progress, or not attempted. It also breaks down sessions into detail, ex; " The actor watched video at certain time". + + +### Notes + + +#### Note about large packages. + + +You can control the size of file uploads to Moodle in Site Administration/General/Security/Site Security/Maximum uploaded file size. This setting is capped by the PHP settings post_max_size and upload_max_filesize, as well as the Apache setting LimitRequestBody. Be sure to increase these values, as well as php_max_execution or php_memory_limit if files are hanging. These can be adjusted in php.ini. If using Apache or similar their config files will need to be adjusted instead. + + +#### Notes about capabilities. + +This plugin has a built in capability that allows users to view all the grades for a course, showing all enrolled user's grades. This is 'mod/cmi5launch:viewgrades'. It is automatically assigned to the roles of 'teacher' and 'course creator'. It can be added to other roles as required. + + +## Database tables + + +This plugin use several of it's own tables. Their full descriptions can be seen in _db->install.xml_. This plugin tries to use the correct size and datatypes to account for different database languages Moodle supports. If there is a field error, database fields can be adjust in _Site Administration -> Development -> XMLDB editor_. + + +### Table - cmi5launch + +This table is the main activity table. It holds the basics of a CMI5 activity. + +| Field name | Field Type | Description | +| :---------- | :------------ |:------------ | +| id | int | Main ID assigned by Moodle| +| course | int | Course (id) cmi5launch activity belongs to | +| name | char | Name of course | +| intro | text | General introduction of the cmi5launch activity | +| introformat | int | Format of the intro field (MOODLE, HTML, MARKDOWN...) | +| cmi5activityid | char | The LMS id | +| registrationid | char | Course registration ID returned by CMI5 player. We want to use this to connect to LRS | +| returnurl | char | Tenants return URL, where it will go after closing course from launch URL | +| courseid | int | This is the course ID returned from the CMI5 player | +| cmi5verbid | char | Unique verb ID | +| cmi5expiry | int | Number of days to expire completion after | +| overridedefaults | int | Determines if the activity is inheriting the global defaults or has overridden them at the activity level | +| cmi5multipleregs | int | Allow multiple registrations (not recommended) | +| timecreated | int | The time the course was created | +| timemodified | int | Last time the course was modified | +| courseinfo | text | The full string of course info | +| aus | text | The AUs of the course, saved as array | + +### Table - cmi5launch_usercourse + +This table holds particular instances of a CMI5 activity (unique to user). + +| Field name | Field Type | Description | +| :---------- | :------------ |:------------ | +| id | int | Main ID for tracking | +| courseid | int | Course ID, assigned by CMI5 player | +| userid | int | User ID, combined with courseid can be used to retrieve unique records | +| cmi5launchurl | char | Launch URL | +| introformat | int | Format of the intro field (MOODLE, HTML, MARKDOWN...) | +| cmi5activityid | char | The LMS id | +| registrationid | char | Course registration ID returned by CMI5 player. We want to use this to connect to LRS | +| returnurl | char | Tenants return URL, where it will go after closing course from launch URL | +| aus | text | The AUs of the course, their DB IDs, saved as array | +| ausgrades | text | All the AUs and their grades (overall session grades) saved in this format: AU lmsid => [AU Title => [Scores from that title's sessions] ] | +| grade | int | The current overall grade for the course | + + +### Table cmi5launch_player + +This table stores the variables for API communication with the CMI5 player. + +| Field name | Field Type | Description | +| :---------- | :------------ |:------------ | +| id | int | The id of returned course IN MOODLE (This ID matches with the field ID in the cmi5launch table.) Moodle assigns ids sequentially. CMI5 also will assign course ids. These are separate. | +| name | char | Name field to hold the course name in Moodle. This may be different than the course name in it's own xml file. | +| tenantid | int | The Tenant ID. | +| tenantname | char | The name of the user for communication with the CMI5 player. This is set in plugin settings | +| courseid | int | The id of the returned course set by CMI5 player | +| Launchmethod | char | Whether the course's retrieved URL opens in it's own window or a new one | +| returnurl | char | The tenants return URL, where browser will redirect to after closing the course from launch URL | +| homepage | char | The tenant's homepage | +| registrationid | char | Course registration id returned by CMI5 player | +| sessionid | int | ID created for each session, when the AU launch url is requested | +| launchurl | char | The AU launchurl generated by and returned from the CMI5 player | +| cmi5playerurl | char | The URL the CMI5 player is located at | +| cmi5playerport | int | The port the CMI5 player can be communicated with (if applicable) | + + +### Table cmi5launch_sessions + +This table stores information pertaining to each users individual session. Just as one course can have multiple AUs, one AU can have multiple sessions. Every time the user requests a launch link from the player a new session is started. + +| Field name | Field Type | Description | +| :---------- | :------------ |:------------ | +| id | int | Main ID for tracking, assigned by Moodle | +| sessionid | int | The session ID. Created by the CMI5 player and returned after launch URL request | +| userid | int | The user ID, which when combined with a course id can be used to retrieve unique records | +| registrationscoursesausid | int | ID assigned by player to AUs | +| tenantname | char | The tenant name | +| lmsid | int | The lmsid of the AU to match with object id from the LRS for pulling progress info | +| createdat | char | The time a session was started | +| updatedat | char | The time the session was last updated | +| code | char | Unique code for each session assigned by CMI5 player | +| launchtokenid | char | Launchtoken assigned by CMI5 player | +| lastrequesttime | char | Time a session was last requested | +| launchmode | char | Launch mode, which is separate then launchmethod. It can be 'Normal', 'Browse', or 'Review' | +| masteryscore | int | The amount the score goes toward a masteryscore (may not be applicable) | +| score | int | The score of the session (returned from the 'result' parameter in statements from the LRS) | +| response | text | The response returned from 'result' parameter (in the statements from the LRS) | +| islaunched | int | Whether the session has been launched | +| isinitialized | int | Whether the session has been initialized | +| initializedat | char | Time a session was initialized | +| duration | char | Time a session lasted ( retrieved from 'result' parameter in statements from LRS) | +| iscompleted | int | Whether the session has been completed | +| ispassed | int | Whether the session has been passed | +| isfailed | int | Whether the session has been failed | +| isterminated | int | Whether the session has been terminated | +| isabandoned | int | Whether the session has been abandoned | +| progress | text | The full string of the sessions progress reported in a statement from the LRS | +| courseid | int | The id of the course generated by the CMI5 player | +| launchmethod | char | Whether the course's retrieved URL opens in it own window or not | +| launchurl | char | The launch URL returned from the CMI5 player | + + + +### Table cmi5launch_aus + +This table stores information pertaining to each users individual AU. One course can have multiple AUs. + +| Field name | Field Type | Description | +| :---------- | :------------ |:------------ | +| id | int | Main ID for tracking, assigned by Moodle | +| attempt | int | The attempt of the AU, ie, first, second, third, etc. | +| launchmethod | char | Whether the course's retrieved URL opens in it own window or no | +| lmsid | char | The AU ID from the course packet. The singular CMI5 compliant ID | +| url | char | The ending to be added to an URL to direct to a specific AU | +| type | char | The AU activity type | +| title | char | The AU title from the course package | +| moveon | char | The requirements for the AU to be marked as done | +| auindex | int | The AU's index (used to separate the AUs in a CMI5 activity | +| parents | char | The AU's parent activities | +| objectives | text | The AU's objectives | +| description | text | The AU's description | +| activitytype | char | Activity type of AU | +| masteryscore | int | The amount the score goes toward a masteryscore (may not be applicable) | +| completed | int | Whether an AU has met completed criteria (0 if false, 1 if true) | +| passed | int | Whether an AU has met passed criteria (0 if false, 1 if true) | +| inprogress | int | Whether an AU is in progress or not (has been started) (0 if false, 1 if true) | +| noattempt | int | Whether an AU has been attempted (0 if false, 1 if true) +| satisfied | char | Whether an AU has been satisfied (true, false, or null ) | +| sessions | text | The session IDs of the AU, saved as array | +| scores | text | The session's scores, saved as array | +| grade | int | The overall grade of the AU (based on what grade type setting is diff --git a/backup/moodle2/backup_cmi5launch_activity_task.class.php b/backup/moodle2/backup_cmi5launch_activity_task.class.php new file mode 100755 index 0000000..cee9d0b --- /dev/null +++ b/backup/moodle2/backup_cmi5launch_activity_task.class.php @@ -0,0 +1,73 @@ +. + +/** + * Description of cmi5launch backup task + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_stepslib.php'); // Because it exists (must). +require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_settingslib.php'); // Because it exists (optional). + +class backup_cmi5launch_activity_task extends backup_activity_task { + + /** + * Define (add) particular settings this activity can have. + * + * @return void + */ + protected function define_my_settings() { + // No particular settings for this activity. + } + + /** + * Define (add) particular steps this activity can have. + * + * @return void + */ + protected function define_my_steps() { + // Module cmi5launch only has one structure step. + $this->add_step(new backup_cmi5launch_activity_structure_step('cmi5launch_structure', 'cmi5launch.xml')); + } + + /** + * Code the transformations to perform in the activity in + * order to get transportable (encoded) links. + * + * @param string $content + * @return string encoded content + */ + public static function encode_content_links($content) { + global $CFG; + + $base = preg_quote($CFG->wwwroot, "/"); + + // Link to the list of cmi5launchs. + $search = "/($base\/mod\/cmi5launch\/index.php\?id=)([0-9]+)/"; + $content = preg_replace($search, '$@cmi5launchINDEX*$2@$', $content); + + $search = "/($base\/mod\/cmi5launch\/view.php\?id=)([0-9]+)/"; + $content = preg_replace($search, '$@cmi5launchVIEWBYID*$2@$', $content); + + return $content; + } +} diff --git a/cmi5PHP/src/aus.php b/backup/moodle2/backup_cmi5launch_settingslib.php old mode 100644 new mode 100755 similarity index 51% rename from cmi5PHP/src/aus.php rename to backup/moodle2/backup_cmi5launch_settingslib.php index c2689df..7c90bb5 --- a/cmi5PHP/src/aus.php +++ b/backup/moodle2/backup_cmi5launch_settingslib.php @@ -13,26 +13,18 @@ // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - *Class to handle Assignable Units -* -* @copyright 2023 Megan Bohland -* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later -*/ - -class Au { -// Properties - public $id, $url, $type, $lmsid, $grade, $scores, $title, $moveon, $auindex, $parents, $objectives, $description, $activitytype, $launchmethod, $masteryscore, $satisfied, $launchurl, $sessionid, $sessions, $progress, $noattempt, $completed, $passed, $inprogress; - - // Methods - //Constructs AUs. Is fed array and where array key matches property, sets the property. - function __construct($statement){ +/** + * Description of cmi5launch backup settings + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ - foreach($statement as $key => $value){ +defined('MOODLE_INTERNAL') || die; - $this->$key = ($value); - } - } -} -?> \ No newline at end of file + // This activity has not particular settings but the inherited from the generic + // backup_activity_task so here there isn't any class definition, like the ones + // existing in /backup/moodle2/backup_settingslib.php (activities section). diff --git a/backup/moodle2/backup_cmi5launch_stepslib.php b/backup/moodle2/backup_cmi5launch_stepslib.php new file mode 100755 index 0000000..e2e1a62 --- /dev/null +++ b/backup/moodle2/backup_cmi5launch_stepslib.php @@ -0,0 +1,47 @@ +. + +/** + * Define all the backup steps that will be used by the backup_cmi5launch_activity_task + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Structure step to backup one cmi5launch activity + */ +class backup_cmi5launch_activity_structure_step extends backup_activity_structure_step { + + protected function define_structure() { + + // Define each element separated. + $cmi5launch = new backup_nested_element('cmi5launch', array('id'), array( + 'name', 'intro', 'introformat', 'cmi5launchurl', 'cmi5activityid', + 'cmi5verbid', 'overridedefaults', 'cmi5multipleregs', 'timecreated', + 'timemodified', 'courseinfo', 'aus', 'grade')); + + // Define sources. + $cmi5launch->set_source_table('cmi5launch', array('id' => backup::VAR_ACTIVITYID)); + + // Return the root element (cmi5launch), wrapped into standard activity structure. + return $this->prepare_activity_structure($cmi5launch); + } +} diff --git a/backup/moodle2/backup_tincanlaunch_activity_task.class.php b/backup/moodle2/backup_tincanlaunch_activity_task.class.php index d80d239..1c3c26b 100755 --- a/backup/moodle2/backup_tincanlaunch_activity_task.class.php +++ b/backup/moodle2/backup_tincanlaunch_activity_task.class.php @@ -1,72 +1,72 @@ -. - -/** - * Description of cmi5launch backup task - * - * @package mod_cmi5launch - * @copyright 2016 onward Remote-Learner.net Inc - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die; - -require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_stepslib.php'); // Because it exists (must). -require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_settingslib.php'); // Because it exists (optional). - -class backup_cmi5launch_activity_task extends backup_activity_task { - - /** - * Define (add) particular settings this activity can have. - * - * @return void - */ - protected function define_my_settings() { - // No particular settings for this activity. - } - - /** - * Define (add) particular steps this activity can have. - * - * @return void - */ - protected function define_my_steps() { - // Module cmi5launch only has one structure step. - $this->add_step(new backup_cmi5launch_activity_structure_step('cmi5launch_structure', 'cmi5launch.xml')); - } - - /** - * Code the transformations to perform in the activity in - * order to get transportable (encoded) links. - * - * @param string $content - * @return string encoded content - */ - static public function encode_content_links($content) { - global $CFG; - - $base = preg_quote($CFG->wwwroot, "/"); - - // Link to the list of cmi5launchs. - $search = "/($base\/mod\/cmi5launch\/index.php\?id=)([0-9]+)/"; - $content = preg_replace($search, '$@cmi5launchINDEX*$2@$', $content); - - $search = "/($base\/mod\/cmi5launch\/view.php\?id=)([0-9]+)/"; - $content = preg_replace($search, '$@cmi5launchVIEWBYID*$2@$', $content); - - return $content; - } -} +. + +/** + * Description of cmi5launch backup task + * + * @package mod_cmi5launch + * @copyright 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_stepslib.php'); // Because it exists (must). +require_once($CFG->dirroot.'/mod/cmi5launch/backup/moodle2/backup_cmi5launch_settingslib.php'); // Because it exists (optional). + +class backup_tincan_activity_task extends backup_activity_task { + + /** + * Define (add) particular settings this activity can have. + * + * @return void + */ + protected function define_my_settings() { + // No particular settings for this activity. + } + + /** + * Define (add) particular steps this activity can have. + * + * @return void + */ + protected function define_my_steps() { + // Module cmi5launch only has one structure step. + $this->add_step(new backup_cmi5launch_activity_structure_step('cmi5launch_structure', 'cmi5launch.xml')); + } + + /** + * Code the transformations to perform in the activity in + * order to get transportable (encoded) links. + * + * @param string $content + * @return string encoded content + */ + public static function encode_content_links($content) { + global $CFG; + + $base = preg_quote($CFG->wwwroot, "/"); + + // Link to the list of cmi5launchs. + $search = "/($base\/mod\/cmi5launch\/index.php\?id=)([0-9]+)/"; + $content = preg_replace($search, '$@cmi5launchINDEX*$2@$', $content); + + $search = "/($base\/mod\/cmi5launch\/view.php\?id=)([0-9]+)/"; + $content = preg_replace($search, '$@cmi5launchVIEWBYID*$2@$', $content); + + return $content; + } +} diff --git a/backup/moodle2/backup_tincanlaunch_settingslib.php b/backup/moodle2/backup_tincanlaunch_settingslib.php index 8d3e609..befeeb5 100755 --- a/backup/moodle2/backup_tincanlaunch_settingslib.php +++ b/backup/moodle2/backup_tincanlaunch_settingslib.php @@ -1,29 +1,29 @@ -. - -/** - * Description of cmi5launch backup settings - * - * @package mod_cmi5launch - * @copyright 2016 onward Remote-Learner.net Inc - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die; - - // This activity has not particular settings but the inherited from the generic - // backup_activity_task so here there isn't any class definition, like the ones - // existing in /backup/moodle2/backup_settingslib.php (activities section). +. + +/** + * Description of cmi5launch backup settings + * + * @package mod_cmi5launch + * @copyright 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + + // This activity has not particular settings but the inherited from the generic + // backup_activity_task so here there isn't any class definition, like the ones + // existing in /backup/moodle2/backup_settingslib.php (activities section). diff --git a/backup/moodle2/backup_tincanlaunch_stepslib.php b/backup/moodle2/backup_tincanlaunch_stepslib.php index 3e247b8..d4dfc2c 100755 --- a/backup/moodle2/backup_tincanlaunch_stepslib.php +++ b/backup/moodle2/backup_tincanlaunch_stepslib.php @@ -1,46 +1,46 @@ -. - -/** - * Define all the backup steps that will be used by the backup_cmi5launch_activity_task - * - * @package mod_cmi5launch - * @copyright 2016 onward Remote-Learner.net Inc - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die; - -/** - * Structure step to backup one cmi5launch activity - */ -class backup_cmi5launch_activity_structure_step extends backup_activity_structure_step { - - protected function define_structure() { - - // Define each element separated. - $cmi5launch = new backup_nested_element('cmi5launch', array('id'), array( - 'name', 'intro', 'introformat', 'cmi5launchurl', 'cmi5activityid', - 'cmi5verbid', 'overridedefaults', 'cmi5multipleregs', 'timecreated', - 'timemodified')); - - // Define sources. - $cmi5launch->set_source_table('cmi5launch', array('id' => backup::VAR_ACTIVITYID)); - - // Return the root element (cmi5launch), wrapped into standard activity structure. - return $this->prepare_activity_structure($cmi5launch); - } -} +. + +/** + * Define all the backup steps that will be used by the backup_cmi5launch_activity_task + * + * @package mod_cmi5launch + * @copyright 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Structure step to backup one cmi5launch activity + */ +class backup_tincan_activity_structure_step extends backup_activity_structure_step { + + protected function define_structure() { + + // Define each element separated. + $cmi5launch = new backup_nested_element('cmi5launch', array('id'), array( + 'name', 'intro', 'introformat', 'cmi5launchurl', 'cmi5activityid', + 'cmi5verbid', 'overridedefaults', 'cmi5multipleregs', 'timecreated', + 'timemodified')); + + // Define sources. + $cmi5launch->set_source_table('cmi5launch', array('id' => backup::VAR_ACTIVITYID)); + + // Return the root element (cmi5launch), wrapped into standard activity structure. + return $this->prepare_activity_structure($cmi5launch); + } +} diff --git a/backup/moodle2/restore_cmi5launch_stepslib .php b/backup/moodle2/restore_cmi5launch_stepslib .php new file mode 100755 index 0000000..f3d92d6 --- /dev/null +++ b/backup/moodle2/restore_cmi5launch_stepslib .php @@ -0,0 +1,64 @@ +. + +/** + * Define all the restore steps that will be used by the restore_cmi5launch_activity_task + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Structure step to restore one cmi5launch activity + */ +class restore_cmi5launch_activity_structure_step extends restore_activity_structure_step { + + protected function define_structure() { + + $paths = array(); + + $paths[] = new restore_path_element('cmi5launch', '/activity/cmi5launch'); + + // Return the paths wrapped into standard activity structure. + return $this->prepare_activity_structure($paths); + } + + /** + * Process cmi5launch tag information + * @param array $data information + */ + protected function process_cmi5launch($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + $data->course = $this->get_courseid(); + + $newitemid = $DB->insert_record('cmi5launch', $data); + $this->apply_activity_instance($newitemid); + } + + protected function after_execute() { + global $DB; + + // Add cmi5launch related files. + $this->add_related_files('mod_cmi5launch', 'intro', null); + } +} diff --git a/backup/moodle2/restore_tincanlaunch_activity_task.class.php b/backup/moodle2/restore_tincanlaunch_activity_task.class.php index df79144..8ea1ab5 100755 --- a/backup/moodle2/restore_tincanlaunch_activity_task.class.php +++ b/backup/moodle2/restore_tincanlaunch_activity_task.class.php @@ -1,125 +1,125 @@ -. - -/** - * Description of cmi5launch restore task - * - * @package mod_cmi5launch - * @copyright 2016 onward Remote-Learner.net Inc - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot . '/mod/cmi5launch/backup/moodle2/restore_cmi5launch_stepslib.php'); // Because it exists (must). - -class restore_cmi5launch_activity_task extends restore_activity_task { - - /** - * Define (add) particular settings this activity can have. - * - * @return void - */ - protected function define_my_settings() { - // No particular settings for this activity. - } - - /** - * Define (add) particular steps this activity can have. - * - * @return void - */ - protected function define_my_steps() { - // Choice only has one structure step. - $this->add_step(new restore_cmi5launch_activity_structure_step('cmi5launch_structure', 'cmi5launch.xml')); - } - - /** - * Define the contents in the activity that must be - * processed by the link decoder. - * - * @return array - */ - static public function define_decode_contents() { - $contents = array(); - - $contents[] = new restore_decode_content('cmi5launch', array('intro'), 'cmi5launch'); - - return $contents; - } - - /** - * Define the decoding rules for links belonging - * to the activity to be executed by the link decoder. - * - * @return array - */ - static public function define_decode_rules() { - $rules = array(); - - // List of cmi5launchs in course. - $rules[] = new restore_decode_rule('cmi5LAUNCHINDEX', '/mod/cmi5launch/index.php?id=$1', 'course'); - - // cmi5launch by cm->id. - $rules[] = new restore_decode_rule('cmi5LAUNCHVIEWBYID', '/mod/cmi5launch/view.php?id=$1', 'course_module'); - - // cmi5launch by cmi5launch->id. - $rules[] = new restore_decode_rule('cmi5LAUNCHVIEWBYB', '/mod/cmi5launch/view.php?b=$1', 'cmi5launch'); - - // Convert old cmi5launch links MDL-33362 & MDL-35007. - $rules[] = new restore_decode_rule('cmi5LAUNCHSTART', '/mod/cmi5launch/view.php?id=$1', 'course_module'); - - return $rules; - } - - /** - * Define the restore log rules that will be applied - * by the {@link restore_logs_processor} when restoring - * cmi5launch logs. It must return one array - * of {@link restore_log_rule} objects. - * - * @return array - */ - static public function define_restore_log_rules() { - $rules = array(); - - $rules[] = new restore_log_rule('cmi5launch', 'add', 'view.php?id={course_module}', '{cmi5launch}'); - $rules[] = new restore_log_rule('cmi5launch', 'update', 'view.php?id={course_module}', '{cmi5launch}'); - $rules[] = new restore_log_rule('cmi5launch', 'view', 'view.php?id={course_module}', '{cmi5launch}'); - - return $rules; - } - - /** - * Define the restore log rules that will be applied - * by the {@link restore_logs_processor} when restoring - * course logs. It must return one array - * of {@link restore_log_rule} objects. - * - * Note this rules are applied when restoring course logs - * by the restore final task, but are defined here at - * activity level. All them are rules not linked to any module instance (cmid = 0). - * - * @return array - */ - static public function define_restore_log_rules_for_course() { - $rules = array(); - - $rules[] = new restore_log_rule('cmi5launch', 'view all', 'index.php?id={course}', null); - - return $rules; - } -} +. + +/** + * Description of cmi5launch restore task + * + * @package mod_cmi5launch + * @copyright 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot . '/mod/cmi5launch/backup/moodle2/restore_cmi5launch_stepslib.php'); // Because it exists (must). + +class restore_cmi5launch_activity_task extends restore_activity_task { + + /** + * Define (add) particular settings this activity can have. + * + * @return void + */ + protected function define_my_settings() { + // No particular settings for this activity. + } + + /** + * Define (add) particular steps this activity can have. + * + * @return void + */ + protected function define_my_steps() { + // Choice only has one structure step. + $this->add_step(new restore_cmi5launch_activity_structure_step('cmi5launch_structure', 'cmi5launch.xml')); + } + + /** + * Define the contents in the activity that must be + * processed by the link decoder. + * + * @return array + */ + public static function define_decode_contents() { + $contents = array(); + + $contents[] = new restore_decode_content('cmi5launch', array('intro'), 'cmi5launch'); + + return $contents; + } + + /** + * Define the decoding rules for links belonging + * to the activity to be executed by the link decoder. + * + * @return array + */ + public static function define_decode_rules() { + $rules = array(); + + // List of cmi5launchs in course. + $rules[] = new restore_decode_rule('cmi5LAUNCHINDEX', '/mod/cmi5launch/index.php?id=$1', 'course'); + + // Cmi5launch by cm->id. + $rules[] = new restore_decode_rule('cmi5LAUNCHVIEWBYID', '/mod/cmi5launch/view.php?id=$1', 'course_module'); + + // Cmi5launch by cmi5launch->id. + $rules[] = new restore_decode_rule('cmi5LAUNCHVIEWBYB', '/mod/cmi5launch/view.php?b=$1', 'cmi5launch'); + + // Convert old cmi5launch links MDL-33362 & MDL-35007. + $rules[] = new restore_decode_rule('cmi5LAUNCHSTART', '/mod/cmi5launch/view.php?id=$1', 'course_module'); + + return $rules; + } + + /** + * Define the restore log rules that will be applied + * by the {@link restore_logs_processor} when restoring + * cmi5launch logs. It must return one array + * of {@link restore_log_rule} objects. + * + * @return array + */ + public static function define_restore_log_rules() { + $rules = array(); + + $rules[] = new restore_log_rule('cmi5launch', 'add', 'view.php?id={course_module}', '{cmi5launch}'); + $rules[] = new restore_log_rule('cmi5launch', 'update', 'view.php?id={course_module}', '{cmi5launch}'); + $rules[] = new restore_log_rule('cmi5launch', 'view', 'view.php?id={course_module}', '{cmi5launch}'); + + return $rules; + } + + /** + * Define the restore log rules that will be applied + * by the {@link restore_logs_processor} when restoring + * course logs. It must return one array + * of {@link restore_log_rule} objects. + * + * Note this rules are applied when restoring course logs + * by the restore final task, but are defined here at + * activity level. All them are rules not linked to any module instance (cmid = 0). + * + * @return array + */ + public static function define_restore_log_rules_for_course() { + $rules = array(); + + $rules[] = new restore_log_rule('cmi5launch', 'view all', 'index.php?id={course}', null); + + return $rules; + } +} diff --git a/backup/moodle2/restore_tincanlaunch_stepslib.php b/backup/moodle2/restore_tincanlaunch_stepslib.php index e9800d2..fb0b940 100755 --- a/backup/moodle2/restore_tincanlaunch_stepslib.php +++ b/backup/moodle2/restore_tincanlaunch_stepslib.php @@ -1,63 +1,63 @@ -. - -/** - * Define all the restore steps that will be used by the restore_cmi5launch_activity_task - * - * @package mod_cmi5launch - * @copyright 2016 onward Remote-Learner.net Inc - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die; - -/** - * Structure step to restore one cmi5launch activity - */ -class restore_cmi5launch_activity_structure_step extends restore_activity_structure_step { - - protected function define_structure() { - - $paths = array(); - - $paths[] = new restore_path_element('cmi5launch', '/activity/cmi5launch'); - - // Return the paths wrapped into standard activity structure. - return $this->prepare_activity_structure($paths); - } - - /** - * Process cmi5launch tag information - * @param array $data information - */ - protected function process_cmi5launch($data) { - global $DB; - - $data = (object)$data; - $oldid = $data->id; - $data->course = $this->get_courseid(); - - $newitemid = $DB->insert_record('cmi5launch', $data); - $this->apply_activity_instance($newitemid); - } - - protected function after_execute() { - global $DB; - - // Add cmi5launch related files. - $this->add_related_files('mod_cmi5launch', 'intro', null); - } -} +. + +/** + * Define all the restore steps that will be used by the restore_cmi5launch_activity_task + * + * @package mod_cmi5launch + * @copyright 2016 onward Remote-Learner.net Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die; + +/** + * Structure step to restore one cmi5launch activity + */ +class restore_tincan_activity_structure_step extends restore_activity_structure_step { + + protected function define_structure() { + + $paths = array(); + + $paths[] = new restore_path_element('cmi5launch', '/activity/cmi5launch'); + + // Return the paths wrapped into standard activity structure. + return $this->prepare_activity_structure($paths); + } + + /** + * Process cmi5launch tag information + * @param array $data information + */ + protected function process_cmi5launch($data) { + global $DB; + + $data = (object)$data; + $oldid = $data->id; + $data->course = $this->get_courseid(); + + $newitemid = $DB->insert_record('cmi5launch', $data); + $this->apply_activity_instance($newitemid); + } + + protected function after_execute() { + global $DB; + + // Add cmi5launch related files. + $this->add_related_files('mod_cmi5launch', 'intro', null); + } +} diff --git a/classes/event/activity_completed.php b/classes/event/activity_completed.php index 989e1c6..957f9cd 100755 --- a/classes/event/activity_completed.php +++ b/classes/event/activity_completed.php @@ -1,93 +1,93 @@ -. - -/** - * The mod_cmi5launch activity completed event. - * - * @package mod_cmi5launch - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_cmi5launch\event; -defined('MOODLE_INTERNAL') || die(); - -/** - * The mod_cmi5launch activity completed event class. - * - * @property-read array $other { - * Extra information about event properties. - * - * - string loadedcontent: A reference to the content loaded. - * - int instanceid: (optional) Instance id of the cmi5 activity. - * } - * - * @package mod_cmi5launch - * @since Moodle 2.7 - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class activity_completed extends \core\event\base { - - /** - * Init method. - */ - protected function init() { - $this->data['crud'] = 'u'; - $this->data['edulevel'] = self::LEVEL_PARTICIPATING; - $this->data['objecttable'] = 'cmi5launch'; - } - - /** - * Returns non-localised description of what happened. - * - * @return string - */ - public function get_description() { - return "The user with id '$this->userid' completed the activity with id '$this->objectid' for the cmi5 with " . - "course module id '$this->contextinstanceid'."; - } - - /** - * Returns localised general event name. - * - * @return string - */ - public static function get_name() { - return get_string('eventactivitycompleted', 'mod_cmi5launch'); - } - - /** - * Get URL related to the action - * - * @return \moodle_url - */ - public function get_url() { - return new \moodle_url( - '/mod/cmi5launch/launch.php', - array('id' => $this->contextinstanceid, 'activityid' => $this->objectid) - ); - } - - /** - * Replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - return array($this->courseid, 'cmi5launch', 'launch', 'launch.php?id=' . $this->contextinstanceid, - '', $this->contextinstanceid); - } - -} +. + +/** + * The mod_cmi5launch activity completed event. + * + * @package mod_cmi5launch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\event; +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_cmi5launch activity completed event class. + * + * @property-read array $other { + * Extra information about event properties. + * + * - string loadedcontent: A reference to the content loaded. + * - int instanceid: (optional) Instance id of the cmi5 activity. + * } + * + * @package mod_cmi5launch + * @since Moodle 2.7 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_completed extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['crud'] = 'u'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'cmi5launch'; + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' completed the activity with id '$this->objectid' for the cmi5 with " . + "course module id '$this->contextinstanceid'."; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventactivitycompleted', 'mod_cmi5launch'); + } + + /** + * Get URL related to the action + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url( + '/mod/cmi5launch/launch.php', + array('id' => $this->contextinstanceid, 'activityid' => $this->objectid) + ); + } + + /** + * Replace add_to_log() statement. + * + * @return array of parameters to be passed to legacy add_to_log() function. + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'cmi5launch', 'launch', 'launch.php?id=' . $this->contextinstanceid, + '', $this->contextinstanceid); + } + +} diff --git a/classes/event/activity_launched.php b/classes/event/activity_launched.php index 83604c3..91dcb65 100755 --- a/classes/event/activity_launched.php +++ b/classes/event/activity_launched.php @@ -1,93 +1,93 @@ -. - -/** - * The mod_cmi5launch activity launched event. - * - * @package mod_cmi5launch - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_cmi5launch\event; -defined('MOODLE_INTERNAL') || die(); - -/** - * The mod_cmi5launch activity launched event class. - * - * @property-read array $other { - * Extra information about event properties. - * - * - string loadedcontent: A reference to the content loaded. - * - int instanceid: (optional) Instance id of the cmi5 activity. - * } - * - * @package mod_cmi5launch - * @since Moodle 2.7 - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class activity_launched extends \core\event\base { - - /** - * Init method. - */ - protected function init() { - $this->data['crud'] = 'r'; - $this->data['edulevel'] = self::LEVEL_PARTICIPATING; - $this->data['objecttable'] = 'cmi5launch'; - } - - /** - * Returns non-localised description of what happened. - * - * @return string - */ - public function get_description() { - return "The user with id '$this->userid' launched the activity with id '$this->objectid' for the cmi5 with " . - "course module id '$this->contextinstanceid'."; - } - - /** - * Returns localised general event name. - * - * @return string - */ - public static function get_name() { - return get_string('eventactivitylaunched', 'mod_cmi5launch'); - } - - /** - * Get URL related to the action - * - * @return \moodle_url - */ - public function get_url() { - return new \moodle_url( - '/mod/cmi5launch/launch.php', - array('id' => $this->contextinstanceid, 'activityid' => $this->objectid) - ); - } - - /** - * Replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - return array($this->courseid, 'cmi5launch', 'launch', 'launch.php?id=' . $this->contextinstanceid, - '', $this->contextinstanceid); - } - -} +. + +/** + * The mod_cmi5launch activity launched event. + * + * @package mod_cmi5launch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\event; +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_cmi5launch activity launched event class. + * + * @property-read array $other { + * Extra information about event properties. + * + * - string loadedcontent: A reference to the content loaded. + * - int instanceid: (optional) Instance id of the cmi5 activity. + * } + * + * @package mod_cmi5launch + * @since Moodle 2.7 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class activity_launched extends \core\event\base { + + /** + * Init method. + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'cmi5launch'; + } + + /** + * Returns non-localised description of what happened. + * + * @return string + */ + public function get_description() { + return "The user with id '$this->userid' launched the activity with id '$this->objectid' for the cmi5 with " . + "course module id '$this->contextinstanceid'."; + } + + /** + * Returns localised general event name. + * + * @return string + */ + public static function get_name() { + return get_string('eventactivitylaunched', 'mod_cmi5launch'); + } + + /** + * Get URL related to the action + * + * @return \moodle_url + */ + public function get_url() { + return new \moodle_url( + '/mod/cmi5launch/launch.php', + array('id' => $this->contextinstanceid, 'activityid' => $this->objectid) + ); + } + + /** + * Replace add_to_log() statement. + * + * @return array of parameters to be passed to legacy add_to_log() function. + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'cmi5launch', 'launch', 'launch.php?id=' . $this->contextinstanceid, + '', $this->contextinstanceid); + } + +} diff --git a/classes/event/course_module_instance_list_viewed.php b/classes/event/course_module_instance_list_viewed.php index 914a876..056b3e1 100755 --- a/classes/event/course_module_instance_list_viewed.php +++ b/classes/event/course_module_instance_list_viewed.php @@ -1,36 +1,36 @@ -. - -/** - * The mod_cmi5launch instance list viewed event. - * - * @package mod_cmi5launch - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_cmi5launch\event; -defined('MOODLE_INTERNAL') || die(); - -/** - * The mod_cmi5launch instance list viewed event class. - * - * @package mod_cmi5launch - * @since Moodle 2.7 - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { -} - +. + +/** + * The mod_cmi5launch instance list viewed event. + * + * @package mod_cmi5launch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\event; +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_cmi5launch instance list viewed event class. + * + * @package mod_cmi5launch + * @since Moodle 2.7 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_module_instance_list_viewed extends \core\event\course_module_instance_list_viewed { +} + diff --git a/classes/event/course_module_viewed.php b/classes/event/course_module_viewed.php index 75ad855..e6add44 100755 --- a/classes/event/course_module_viewed.php +++ b/classes/event/course_module_viewed.php @@ -1,55 +1,55 @@ -. - -/** - * The mod_cmi5launch course module viewed event. - * - * @package mod_cmi5launch - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_cmi5launch\event; -defined('MOODLE_INTERNAL') || die(); - -/** - * The mod_cmi5launch course module viewed event class. - * - * @package mod_cmi5launch - * @since Moodle 2.7 - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class course_module_viewed extends \core\event\course_module_viewed { - - /** - * Init method. - */ - protected function init() { - $this->data['crud'] = 'r'; - $this->data['edulevel'] = self::LEVEL_PARTICIPATING; - $this->data['objecttable'] = 'cmi5launch'; - } - - /** - * Replace add_to_log() statement. - * - * @return array of parameters to be passed to legacy add_to_log() function. - */ - protected function get_legacy_logdata() { - return array($this->courseid, 'cmi5launch', 'pre-view', 'view.php?id=' . $this->contextinstanceid, $this->objectid, - $this->contextinstanceid); - } -} - +. + +/** + * The mod_cmi5launch course module viewed event. + * + * @package mod_cmi5launch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\event; +defined('MOODLE_INTERNAL') || die(); + +/** + * The mod_cmi5launch course module viewed event class. + * + * @package mod_cmi5launch + * @since Moodle 2.7 + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_module_viewed extends \core\event\course_module_viewed { + + /** + * Init method. + */ + protected function init() { + $this->data['crud'] = 'r'; + $this->data['edulevel'] = self::LEVEL_PARTICIPATING; + $this->data['objecttable'] = 'cmi5launch'; + } + + /** + * Replace add_to_log() statement. + * + * @return array of parameters to be passed to legacy add_to_log() function. + */ + protected function get_legacy_logdata() { + return array($this->courseid, 'cmi5launch', 'pre-view', 'view.php?id=' . $this->contextinstanceid, $this->objectid, + $this->contextinstanceid); + } +} + diff --git a/classes/local/au.php b/classes/local/au.php new file mode 100755 index 0000000..55e2d4d --- /dev/null +++ b/classes/local/au.php @@ -0,0 +1,42 @@ +. +/** + * Class to handle Assignable Units. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_cmi5launch\local; + +class au { + + // Lowercase values are for saving to DB. + public $id, $attempt, $url, $type, $lmsid, $grade, $scores, $title, $moveon, $auindex, $parents, $objectives, + $description, $activitytype, $launchmethod, $masteryscore, $satisfied, $launchurl, $sessions, $progress, $noattempt, + $completed, $passed, $inprogress, $userid, $moodlecourseid; + + // Uppercase values because that's how they come from player. + // Moodle wants all lowercase, but we need to be able to receive the data from the player. + public $launchMethod, $lmsId, $moveOn, $auIndex, $activityType, $masteryScore; + // Constructs AUs. Is fed array and where array key matches property, sets the property. + public function __construct($statement) { + + foreach ($statement as $key => $value) { + + $this->$key = ($value); + } + } +} diff --git a/classes/local/au_helpers.php b/classes/local/au_helpers.php new file mode 100755 index 0000000..5785ad5 --- /dev/null +++ b/classes/local/au_helpers.php @@ -0,0 +1,166 @@ +. + +/** + * Helper class for AUs + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\local; + +use mod_cmi5launch\local\au; +defined('MOODLE_INTERNAL') || die(); + +class au_helpers { + public function get_cmi5launch_retrieve_aus() { + return [$this, 'cmi5launch_retrieve_aus']; + } + public function get_cmi5launch_create_aus() { + return [$this, 'cmi5launch_create_aus']; + } + public function get_cmi5launch_save_aus() { + return [$this, 'cmi5launch_save_aus']; + } + public function get_cmi5launch_retrieve_aus_from_db() { + return [$this, 'cmi5launch_retrieve_aus_from_db']; + } + + /** + * Parses and retrieves AUs from the returned info from CMI5 player. + * @param mixed $returnedinfo + * @return array + */ + public function cmi5launch_retrieve_aus($returnedinfo) { + // The results come back as nested array under more then just AUs. + // We only want the info pertaining to the AU. + $resultchunked = array_chunk($returnedinfo["metadata"]["aus"], 1, ); + + return $resultchunked; + } + + /** + * So it should be fed an array of statements that then assigns the values to + * several aus, and then returns them as au objects. + * @param mixed $austatements + * @return array + */ + public function cmi5launch_create_aus($austatements) { + + // Needs to return our new AU objects. + $newaus = array(); + + foreach ($austatements as $int => $info) { + + // The aus come back decoded from DB nestled in an array. + // So they are the first key, which is '0'. + $statement = $info[0]; + + $au = new au($statement); + + // Assign the newly created au to the return array. + $newaus[] = $au; + } + + // Return our new list of AU. + return $newaus; + } + + /** + * Takes a list of AUs and record and saves to the DB. + * @param mixed $auobjectarray + * @return array + */ + 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(); + + // 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) { + + // Make a newrecord to save. + $newrecord = new \stdClass(); + + $newrecord->userid = $USER->id; + $newrecord->attempt = $auobject->attempt; + $newrecord->auid = $auobject->id; + $newrecord->launchmethod = $auobject->launchMethod; + $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); + $newrecord->title = $title[0]['text']; + $newrecord->moveon = $auobject->moveOn; + $newrecord->auindex = $auobject->auIndex; + $newrecord->parents = json_encode($auobject->parents, true); + $newrecord->objectives = json_encode($auobject->objectives); + $desc = json_decode(json_encode($auobject->description), true); + $newrecord->description = $desc[0]['text']; + $newrecord->activitytype = $auobject->activityType; + $newrecord->masteryscore = $auobject->masteryscore; + $newrecord->completed = $auobject->completed; + $newrecord->passed = $auobject->passed; + $newrecord->inprogress = $auobject->inprogress; + $newrecord->noattempt = $auobject->noattempt; + $newrecord->satisfied = $auobject->satisfied; + // And HERE we can add the moodlecourseid. + $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; + } + + 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) { + + global $DB; + + $check = $DB->record_exists( 'cmi5launch_aus', ['id' => $auid], '*', IGNORE_MISSING); + + // If check is negative, the record does not exist. It should so throw error. + // Moodle will throw the error, but we want to pass this message back ot user. + if (!$check) { + + echo "

Error attempting to get AU data from DB. Check AU id. AU id is: " . $auid ."

"; + + return false; + } else { + + $auitem = $DB->get_record('cmi5launch_aus', array('id' => $auid)); + + $au = new au($auitem); + } + + // Return our new list of AU. + return $au; + } + +} diff --git a/classes/local/cmi5_connectors.php b/classes/local/cmi5_connectors.php new file mode 100755 index 0000000..82ab582 --- /dev/null +++ b/classes/local/cmi5_connectors.php @@ -0,0 +1,482 @@ +. + +/** + * Class to hold ways to communicate with CMI5 player through its API's. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + namespace mod_cmi5launch\local; + defined('MOODLE_INTERNAL') || die(); + +class cmi5_connectors { + + public function cmi5launch_get_create_tenant() { + return [$this, 'cmi5launch_create_tenant']; + } + public function cmi5launch_get_retrieve_token() { + return [$this, 'cmi5launch_retrieve_token']; + } + public function cmi5launch_get_retrieve_url() { + return [$this, 'cmi5launch_retrieve_url']; + } + public function cmi5launch_get_create_course() { + return [$this, 'cmi5launch_create_course']; + } + public function cmi5launch_get_session_info() { + return [$this, 'cmi5launch_retrieve_session_info_from_player']; + + } + public function cmi5launch_get_registration_with_post() { + return [$this, 'cmi5launch_retrieve_registration_with_post']; + } + public function cmi5launch_get_registration_with_get() { + return [$this, 'cmi5launch_retrieve_registration_with_get']; + } + public function cmi5launch_get_send_request_to_cmi5_player_post() { + return [$this, 'cmi5launch_send_request_to_cmi5_player_post']; + + } + + /** + * Function to create a course. + * @param mixed $id - tenant id in Moodle. + * @param mixed $tenanttoken - tenant bearer token. + * @param mixed $filename -- The filename of the course to be imported, to be added to url POST request. + * @return bool|string - Response from cmi5 player. + */ + public function cmi5launch_create_course($id, $tenanttoken, $filename) { + + global $DB, $CFG; + $settings = cmi5launch_settings($id); + + // Build URL to import course to. + $url = $settings['cmi5launchplayerurl'] . "/api/v1/course"; + + // To determine the headers. + $filetype = "zip"; + + $databody = $filename->get_content(); + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_post($databody, $url, $filetype, $tenanttoken); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "creating the course"); + + if ($resulttest == true) { + // Return an array with course info. + return $result; + } + } + + /** + * Function to create a tenant. + * @param $urltosend - URL retrieved from user in URL textbox. + * @param $username - username. + * @param $password - password. + * @param $newtenantname - the name the new tenant will be, retreived from Tenant Name textbox. + */ + public function cmi5launch_create_tenant($urltosend, $username, $password, $newtenantname) { + + global $CFG; + + // The body of the request must be made as array first. + $data = array( + 'code' => $newtenantname); + + // To determine the headers. + $filetype = "json"; + + // Data needs to be JSON encoded. + $data = json_encode($data); + + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_post($data, $urltosend, $filetype, $username, $password); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "creating the tenant"); + + if ($resulttest == true) { + + // Decode returned response into array. + $returnedinfo = json_decode($result, true); + + // Return an array with tenant name and info. + return $returnedinfo; + }; + } + + /** + * Function to retrieve registration from cmi5 player. + * This way uses the registration ID and GET request. + * Registration is "code" in returned json body. + * @param $registration - registration UUID + * @param $id - cmi5 launch id + */ + public function cmi5launch_retrieve_registration_with_get($registration, $id) { + + $settings = cmi5launch_settings($id); + + $token = $settings['cmi5launchtenanttoken']; + $playerurl = $settings['cmi5launchplayerurl']; + + global $CFG; + + // Build URL for launch URL request. + $url = $playerurl . "/api/v1/registration/" . $registration; + + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_get($token, $url); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "retrieving the registration"); + + if ($resulttest == true) { + + return $result; + } + } + + /** + * Function to retrieve registration from cmi5 player. + * This way uses the course id and actor name. + * As this is a POST request it returns a new code everytime it is called. + * Registration is "code" in returned json body. + * @param $courseid - course id - The course ID in the CMI5 player. + * @param $id - the course id in MOODLE. + */ + public function cmi5launch_retrieve_registration_with_post($courseid, $id) { + + global $USER; + + $settings = cmi5launch_settings($id); + + $actor = $USER->username; + $token = $settings['cmi5launchtenanttoken']; + $playerurl = $settings['cmi5launchplayerurl']; + $homepage = $settings['cmi5launchcustomacchp']; + global $CFG; + + // Build URL for launch URL request. + $url = $playerurl . "/api/v1/registration"; + + // The body of the request must be made as array first. + $data = array( + 'courseId' => $courseid, + 'actor' => array( + 'account' => array( + "homePage" => $homepage, + "name" => $actor, + ), + ), + ); + + // Data needs to be JSON encoded. + $data = json_encode($data); + // To determine the headers. + $filetype = "json"; + + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_post($data, $url, $filetype, $token); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "retrieving the registration"); + + // Catch errors. + if ($resulttest == true) { + + $registrationinfo = json_decode($result, true); + + // The returned 'registration info' is a large json object. + // Code is the registration id we want. + $registration = $registrationinfo["code"]; + + return $registration; + } + } + + /** + * Function to retrieve a token from cmi5 player. + * @param $url - URL to send request to + * @param $username - username + * @param $password - password + * @param $audience - the name the of the audience using the token, + * @param #tenantid - the id of the tenant + */ + public function cmi5launch_retrieve_token($url, $username, $password, $audience, $tenantid) { + + global $CFG; + + // The body of the request must be made as array first. + $data = array( + 'tenantId' => $tenantid, + 'audience' => $audience, + ); + $filetype = "json"; + + // Data needs to be JSON encoded. + $data = json_encode($data); + + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_post($data, $url, $filetype, $username, $password); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "retrieving the registration"); + + if ($resulttest == true) { + + return $result; + } + } + + /** + * Function to retrieve a launch URL for an AU. + * @param $id - courses's ID in MOODLE to retrieve corect record. + * @param $auindex -AU's index to send to request for launch url. + */ + public function cmi5launch_retrieve_url($id, $auindex) { + + global $DB, $USER; + + // Retrieve actor record, this enables correct actor info for URL storage. + $record = $DB->get_record("cmi5launch", array('id' => $id)); + + $settings = cmi5launch_settings($id); + + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); + + $registrationid = $userscourse->registrationid; + + $homepage = $settings['cmi5launchcustomacchp']; + $returnurl = $userscourse->returnurl; + $actor = $USER->username; + $token = $settings['cmi5launchtenanttoken']; + $playerurl = $settings['cmi5launchplayerurl']; + $courseid = $userscourse->courseid; + + // Build URL for launch URL request. + $url = $playerurl . "/api/v1/course/" . $courseid ."/launch-url/" . $auindex; + + $data = array( + 'actor' => array( + 'account' => array( + "homePage" => $homepage, + "name" => $actor, + ), + ), + 'returnUrl' => $returnurl, + 'reg' => $registrationid, + ); + + // To determine the headers. + $filetype = "json"; + + // Data needs to be JSON encoded. + $data = json_encode($data); + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_post($data, $url, $filetype, $token); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "retrieving launch url"); + + // Catch errors. + if ($resulttest == true) { + + // Only return the URL. + $urldecoded = json_decode($result, true); + + return $urldecoded; + } + } + + /** + * Function to construct, send an URL, and save result as POST message to player. + * @param $databody - the data that will be used to construct the body of request as JSON. + * @param $url - The URL the request will be sent to. + * @param $filetype - The type of file being sent, either zip or json. + * @param ...$tokenorpassword is a variable length param. If one is passed, it is $token, if two it is $username and $password. + * @return - $result is the response from cmi5 player. + */ + public function cmi5launch_send_request_to_cmi5_player_post($databody, $url, $filetype, ...$tokenorpassword) { + + // Determine content type to be used in header. + // It is also the same as accepted type. + $contenttype = $filetype; + if ($contenttype == "zip") { + $contenttype = "application/zip\r\n"; + } else if ("json") { + $contenttype = "application/json\r\n"; + } + + // If number of args is greater than one it is for retrieving tenant info and args are username and password. + if (count($tokenorpassword) == 2 ) { + + $username = $tokenorpassword[0]; + $password = $tokenorpassword[1]; + + // 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' => 'POST', + 'header' => array('Authorization: Basic '. base64_encode("$username:$password"), + "Content-Type: " .$contenttype . + "Accept: " . $contenttype), + 'content' => ($databody), + ), + ); + + // 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. + $result = file_get_contents( $url, false, $context ); + + // Else the args are what we need for posting a course. + } else { + + // First arg will be token. + $token = $tokenorpassword[0]; + + // 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 + // JSON_UNESCAPED_SLASHES used so http addresses are displayed correctly. + $options = array( + 'http' => array( + 'method' => 'POST', + 'ignore_errors' => true, + 'header' => array("Authorization: Bearer ". $token, + "Content-Type: " .$contenttype . + "Accept: " . $contenttype), + 'content' => ($databody), + ), + ); + + // The options are 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. + $result = file_get_contents( $url, false, $context ); + + } + + // Return response. + return $result; + } + + /** + * Function to construct and send GET request to CMI5 player. + * @param $token - the token that will be used to authenticate the request. + * @param $url - The URL the request will be sent to. + * @return - $sessionDecoded is the response from cmi5 player. + */ + public function cmi5launch_send_request_to_cmi5_player_get($token, $url) { + // 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 + // JSON_UNESCAPED_SLASHES used so http addresses are displayed correctly. + $options = array ( + 'http' => array ( + 'method' => 'GET', + 'ignore_errors' => true, + 'header' => array ("Authorization: Bearer ". $token, + "Content-Type: application/json\r\n" . + "Accept: application/json\r\n"), + ), + ); + + // 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. False is to not use_include_path, we want to go to the url. + $launchresponse = file_get_contents( $url, false, $context ); + + $sessiondecoded = json_decode($launchresponse, true); + + return $sessiondecoded; + } + + /** + * Retrieve session info from cmi5player + * @param mixed $sessionid - the session id to retrieve + * @param mixed $id - cmi5 id + * @return mixed $sessionDecoded - the session info from cmi5 player. + */ + public function cmi5launch_retrieve_session_info_from_player($sessionid, $id) { + + global $DB; + + $settings = cmi5launch_settings($id); + + $token = $settings['cmi5launchtenanttoken']; + $playerurl = $settings['cmi5launchplayerurl']; + + // Build URL for launch URL request. + $url = $playerurl . "/api/v1/session/" . $sessionid; + + // Sends the stream to the specified URL. + $result = $this->cmi5launch_send_request_to_cmi5_player_get($token, $url); + + // Check result and display message if not 200. + $resulttest = $this->cmi5launch_connectors_error_message($result, "retrieving session info"); + + if ($resulttest == true) { + + return $result; + } + } + + + // 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 function cmi5launch_connectors_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; + } + + if ($resulttest === false || array_key_exists("statusCode", $resulttest) && $resulttest["statusCode"] != 200) { + + echo "
"; + + echo "Something went wrong " . $type . ". CMI5 Player returned " . var_dump($resulttotest); + + echo "
"; + + return false; + } else { + + // No errors, continue. + return true; + } + } +} diff --git a/classes/local/course.php b/classes/local/course.php new file mode 100755 index 0000000..e595672 --- /dev/null +++ b/classes/local/course.php @@ -0,0 +1,60 @@ +. + +/** + * Class to handle invidual courses. + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + namespace mod_cmi5launch\local; + defined('MOODLE_INTERNAL') || die(); + +class course { + public $id, $url, $ausgrades, $type, $lmsid, $grade, $scores, $title, $moveon, $auindex, + $parents, $objectives, $launchurl, $sessions = array(), $sessionid, $returnurl, $description = [], $activitytype, $launchmethod, + $masteryscore, $progress, $noattempt, $completed, $passed, $inprogress, $satisfied, $moodlecourseid; + + // The id assigned by cmi5 player. + public $courseid; + + // The user id who is taking the course. + public $userid; + + // The registration id assigned by the CMI5 player. + public $registrationid; + + // Array of AUs in the course. + public $aus = array(); + + // Constructs courses. Is fed array and where array key matches property, sets the property. + public function __construct($statement) { + + foreach ($statement as $key => $value) { + + // If the key exists as a property, set it. + if (property_exists($this, $key) ) { + + $this->$key = ($value); + + // We want the ID to be null here, so we can assign it later. + if ($key == 'id') { + $this->$key = null; + } + } + } + } +} diff --git a/classes/local/grade_helpers.php b/classes/local/grade_helpers.php new file mode 100755 index 0000000..6bd3af9 --- /dev/null +++ b/classes/local/grade_helpers.php @@ -0,0 +1,243 @@ +. + +/** + * A class to help with grading functions such as LRS querying and grade calculation. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\local; + +defined('MOODLE_INTERNAL') || die(); + +use mod_cmi5launch\local\session_helpers; + +class grade_helpers { + public function get_cmi5launch_check_user_grades_for_updates() { + return [$this, 'cmi5launch_check_user_grades_for_updates']; + } + + public function get_cmi5launch_highest_grade() { + return [$this, 'cmi5launch_highest_grade']; + } + + public function get_cmi5launch_average_grade() { + return [$this, 'cmi5launch_average_grade']; + } + + /** + * Takes in an array of scores and returns the average grade. + * @param mixed $scores + * @return int + */ + 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); + } + // If it isn't an array it (array_sum) doesn't work. + if (!$scores == null && is_array($scores)) { + + // Find the average of the scores. + $averagegrade = (array_sum($scores) / count($scores)); + + } else if (!$scores == null && !is_array($scores)) { + + // If it's an int, it's a single value so average is itself. + $averagegrade = $scores; + } else { + $averagegrade = 0; + } + + // Now apply intval. + $averagegrade = intval($averagegrade); + + return $averagegrade; + } + + /** + * Takes in an array of scores and returns the highest grade. + * @param mixed $scores + * @return int + */ + public function cmi5launch_highest_grade($scores) { + + global $cmi5launch, $USER, $DB; + + // Highest equals 0 to start. + $highestgrade = 0; + + // First check if scores is a string, if a string we need it to be array. + if (is_string($scores)) { + $scores = json_decode($scores, true); + } + + if (!$scores == null && is_array($scores)) { + + // Find the highest grade. + $highestgrade = max($scores); + + } else if ($scores > $highestgrade && !is_array($scores)) { + + // If it's an int, it's a single value so highest is itself. + $highestgrade = $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) { + + global $cmi5launch, $USER, $DB; + + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + // 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]); + + // 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) { + + // 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) { + + // MOD_CMI5LAUNCH_AUS_GRADE = 0. + // MOD_CMI5LAUNCH_GRADE_HIGHEST = 1. + // MOD_CMI5LAUNCH_GRADE_AVERAGE = 2. + // MOD_CMI5LAUNCH_GRADE_SUM = 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); + } + } + } + + // Update course record. + $userscourse->ausgrades = json_encode($auscores); + $DB->update_record("cmi5launch_usercourse", $userscourse); + } + + // Return scores. + return $overallgrade; + + } else { + + // Do nothing, there is no record for this user in this course. + return false; + + } + } +} diff --git a/classes/local/progress.php b/classes/local/progress.php new file mode 100755 index 0000000..15b874b --- /dev/null +++ b/classes/local/progress.php @@ -0,0 +1,377 @@ +. + +/** + * //Class to retrieve progress statements from LRS + * //Holds methods for tracking and displaying student progress + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace mod_cmi5launch\local; + +defined('MOODLE_INTERNAL') || die(); + +class progress { + + public function cmi5launch_get_retrieve_statements() { + return [$this, 'cmi5launch_retrieve_statements']; + } + + public function cmi5launch_get_request_completion_info() { + return [$this, 'cmi5launch_request_completion_info']; + } + + public function cmi5launch_get_request_statements_from_lrs() { + return [$this, 'cmi5launch_request_statements_from_lrs']; + } + + /** + * Send request to LRS and receive statements. + * @param mixed $registrationid - registration id + * @param mixed $session - a session object + * @return array + */ + public function cmi5launch_request_statements_from_lrs($registrationid, $session) { + // Array to hold result. + $result = array(); + + // When searching by reg id, which is the option available to Moodle, + // many results are returned, so iterating through them is necessary. + $data = array( + 'registration' => $registrationid, + 'since' => $session->createdat, + ); + + $statements = $this->cmi5launch_send_request_to_lrs($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); + + $length = count($statement); + + for ($i = 0; $i < $length; $i++) { + + // This separates the larger statement into the separate sessions and verbs. + $current = ($statement[$i]); + array_push($result, array($registrationid => $current)); + } + + return $result; + } + + /** + * Builds and sends requests to LRS + * @param mixed $data + * @param mixed $id + * @return mixed + */ + public function cmi5launch_send_request_to_lrs($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); + + // LRS username and password. + $user = $settings['cmi5launchlrslogin']; + $pass = $settings['cmi5launchlrspass']; + + // 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", + ), + ), + ); + // 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. + $result = file_get_contents($url, false, $context); + + $resultdecoded = json_decode($result, true); + + return $resultdecoded; + } + + /** + * Returns an actor (name) retrieved from collected LRS data based on registration id + * @param mixed $resultarray - data retrieved from LRS, usually an array + * @param mixed $i - the registration id + * @return mixed - actor + */ + public function cmi5launch_retrieve_actor($resultarray, $registrationid) { + + $actor = $resultarray[$registrationid][0]["actor"]["account"]["name"]; + return $actor; + } + + /** + * Returns a verb retrieved from collected LRS data based on registration id + * @param mixed $resultarray - data retrieved from LRS, usually an array + * @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]; + } + + return $verb; + } + + /** + * Returns a name (the au title) retrieved from collected LRS data based on registration id + * Statements are returned in an array, with the registration id as the key. + * Often they are nested, and sometimes in differnt order, so to avoid errors we need to check for each piece as a key. + * Then if found, use that key to navigate. + * @param mixed $resultarray - data retrieved from LRS, usually an array + * @param mixed $registrationid - the registration id + * @return mixed - object name + */ + public function cmi5launch_retrieve_object_name($resultarray, $registrationid) { + + global $CFG; + + // 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; + } + + } 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; + + } else { + + // If both name and id are missing throw error. + $this->cmi5launch_statement_retrieval_error("Object name and id "); + } + + } 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 "
"; + } + } + + /** + * Returns a timestamp retrieved from collected LRS data based on registration id + * @param mixed $resultarray - data retrieved from LRS, usually an array + * @param mixed $registrationid - the registration id + * @return string - date/time + */ + public function cmi5launch_retrieve_timestamp($resultarray, $registrationid) { + + // 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->setTimezone(new \DateTimeZone('America/New_York')); + + $date = $date->format('d-m-Y' . " " . 'h:i a'); + + return $date; + + } else { + + $this->cmi5launch_statement_retrieval_error("Timestamp "); + } + } + + /** + * Returns an actor's score retrieved from collected LRS data based on registration id + * Statements are returned in an array, with the registration id as the key. + * Often they are nested, and sometimes in differnt order, so to avoid errors we need to check for each piece as a key. + * Then if found, use that key to navigate. + * @param mixed $resultarray - data retrieved from LRS, usually an array + * @param mixed $registrationid - the registration id + * @return mixed + */ + public function cmi5launch_retrieve_score($resultarray, $registrationid) { + + global $CFG; + + // 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); + + // If it is null then the item in question doesn't exist in this statement. + if ($score) { + + $score = $resultarray[$registrationid][0]["result"]["score"]; + + // Raw score preferred to scaled. + if ($score["raw"]) { + + $returnscore = $score["raw"]; + return $returnscore; + } else if ($score["scaled"]) { + + $returnscore = round($score["scaled"], 2); + return $returnscore; + } + } + } 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 "
"; + } + } + } + + + /** + * Retrieves xAPI statements from LRS. + * @param mixed $registrationid - the registration id. + * @param mixed $session - session item to be updated. + * @return array + */ + public function cmi5launch_retrieve_statements($registrationid, $session) { + + // Array to hold verbs and be returned. + $progressupdate = array(); + + // 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) { + + // If key contains "sessionid" in string. + if (str_contains($key, "sessionid")) { + $currentsessionid = $value; + } + } + + // Now if code equals currentsessionid, this is a statement pertaining to this session. + if ($code === $currentsessionid) { + + $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); + + // If a session has more than one score, we only want the highest. + if (!$score == null && $score > $returnscore) { + + $returnscore = $score; + } + + // Update to return. + $progressupdate[] = "$actor $verb $object on $date"; + + } + + } + + $session->progress = json_encode($progressupdate); + + $session->score = $returnscore; + + return $session; + } +} diff --git a/classes/local/session.php b/classes/local/session.php new file mode 100755 index 0000000..58fefb7 --- /dev/null +++ b/classes/local/session.php @@ -0,0 +1,53 @@ +. + +/** + * Class to handle Sessions. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\local; +defined('MOODLE_INTERNAL') || die(); + +class session { + // Properties, these need to be capitilized as they are because that's how they are returned in statements and need to be saved. + // Id is session id. + public $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; + + // Database properties, that need to be lower case. + public $sessionid, $userid, $registrationscoursesausid, $createdat, $updatedat, $launchtokenid, $lastrequesttime, $launchmode, $masteryscore, $tenantid, + $score, $response, $islaunched, $isinitialized, $initializedat, $duration, $iscompleted, $ispassed, $isfailed, $isterminated, $isabandoned, $launchmethod, $moodlecourseid; + + + // Constructs sessions. Is fed array and where array key matches property, sets the property. + public function __construct($statement) { + + foreach ($statement as $key => $value) { + + + $this->$key = ($value); + } + + } + +} diff --git a/classes/local/session_helpers.php b/classes/local/session_helpers.php new file mode 100755 index 0000000..6853535 --- /dev/null +++ b/classes/local/session_helpers.php @@ -0,0 +1,162 @@ +. + +/** + * Helper class for sessions -MB + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\local; +defined('MOODLE_INTERNAL') || die(); + +use mod_cmi5launch\local\cmi5_connectors; +use mod_cmi5launch\local\session; + + +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 session + */ + public function cmi5launch_update_sessions($sessionid, $cmi5id, $user) { + + global $CFG, $DB, $cmi5launch, $USER; + + $connector = new cmi5_connectors; + $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 = $getsessioninfo($sessionid, $cmi5id); + + // 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; + } + } + + // Now update to table. + $DB->update_record('cmi5launch_sessions', $session); + + return $session; + } + + /** + * 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. + $DB->insert_record($table, $newrecord, true); + } + + /** + * 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; + } + +} diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php new file mode 100644 index 0000000..5dc4905 --- /dev/null +++ b/classes/privacy/provider.php @@ -0,0 +1,440 @@ +. + +/** + * Class to implement Moodle's privacy APIs. + * + * @copyright 2024 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + namespace mod_cmi5launch\privacy; + + use core_privacy\local\metadata\collection; + use core_privacy\local\request\approved_contextlist; + use core_privacy\local\request\approved_userlist; + use core_privacy\local\request\contextlist; + use core_privacy\local\request\helper; + use core_privacy\local\request\userlist; + use core_privacy\local\request\writer; +class provider implements + // This plugin does store personal user data. + \core_privacy\local\metadata\provider, + \core_privacy\local\request\core_userlist_provider, + \core_privacy\local\request\plugin\provider + { + + public static function get_metadata(collection $collection): collection { + + // Database tables. + $collection->add_database_table( + 'cmi5launch_usercourse', + [ + 'id' => 'privacy:metadata:cmi5launch_usercourse:id', + 'userid' => 'privacy:metadata:cmi5launch_usercourse:userid', + 'registrationid' => 'privacy:metadata:cmi5launch_usercourse:registrationid', + 'ausgrades' => 'privacy:metadata:cmi5launch_usercourse:ausgrades', + 'grade' => 'privacy:metadata:cmi5launch_usercourse:grade', + ], + + 'privacy:metadata:cmi5launch_usercourse', + ); + $collection->add_database_table( + 'cmi5launch_sessions', + [ + 'id' => 'privacy:metadata:cmi5launch_sessions:id', + 'sessionid' => 'privacy:metadata:cmi5launch_sessions:sessionid', + 'userid' => 'privacy:metadata:cmi5launch_sessions:userid', + 'registrationscoursesausid' => 'privacy:metadata:cmi5launch_sessions:registrationscoursesausid', + 'createdat' => 'privacy:metadata:cmi5launch_sessions:createdat', + 'updatedat' => 'privacy:metadata:cmi5launch_sessions:updatedat', + 'code' => 'privacy:metadata:cmi5launch_sessions:code', + 'launchtokenid' => 'privacy:metadata:cmi5launch_sessions:launchtokenid', + 'lastrequesttime' => 'privacy:metadata:cmi5launch_sessions:lastrequesttime', + 'score' => 'privacy:metadata:cmi5launch_sessions:score', + 'islaunched' => 'privacy:metadata:cmi5launch_sessions:islaunched', + 'isinitialized' => 'privacy:metadata:cmi5launch_sessions:isinitialized', + 'initializedat' => 'privacy:metadata:cmi5launch_sessions:initializedat', + 'duration' => 'privacy:metadata:cmi5launch_sessions:duration', + 'iscompleted' => 'privacy:metadata:cmi5launch_sessions:iscompleted', + 'ispassed' => 'privacy:metadata:cmi5launch_sessions:ispassed', + 'isfailed' => 'privacy:metadata:cmi5launch_sessions:isfailed', + 'isterminated' => 'privacy:metadata:cmi5launch_sessions:isterminated', + 'isabandoned' => 'privacy:metadata:cmi5launch_sessions:isabandoned', + 'progress' => 'privacy:metadata:cmi5launch_sessions:progress', + 'launchurl' => 'privacy:metadata:cmi5launch_sessions:launchurl', + + ], + + 'privacy:metadata:cmi5launch_sessions', + ); + + $collection->add_database_table( + 'cmi5launch_aus', + [ + 'id' => 'privacy:metadata:cmi5launch_aus:id', + 'userid' => 'privacy:metadata:cmi5launch_aus:userid', + 'attempt' => 'privacy:metadata:cmi5launch_aus:attempt', + 'lmsid' => 'privacy:metadata:cmi5launch_aus:lmsid', + 'completed' => 'privacy:metadata:cmi5launch_aus:completed', + 'passed' => 'privacy:metadata:cmi5launch_aus:passed', + 'inprogress' => 'privacy:metadata:cmi5launch_aus:inprogress', + 'noattempt' => 'privacy:metadata:cmi5launch_aus:noattempt', + 'satisfied' => 'privacy:metadata:cmi5launch_aus:satisfied', + 'sessions' => 'privacy:metadata:cmi5launch_aus:sessions', + 'scores' => 'privacy:metadata:cmi5launch_aus:scores', + 'grade' => 'privacy:metadata:cmi5launch_aus:grade', + ], + + 'privacy:metadata:cmi5launch_aus', + ); + + // External systems. + $collection->add_external_location_link('lrs', [ + 'registrationid' => 'privacy:metadata:lrs:registrationid', + 'createdat' => 'privacy:metadata:lrs:createdat', + ], 'privacy:metadata:lrs'); + + $collection->add_external_location_link('cmi5_player', [ + 'registrationid' => 'privacy:metadata:cmi5_player:registrationid', + 'actor' => 'privacy:metadata:cmi5_player:actor', + 'courseid' => 'privacy:metadata:cmi5_player:courseid', + 'returnurl' => 'privacy:metadata:cmi5_player:returnurl', + 'sessionid' => 'privacy:metadata:cmi5_player:sessionid', + ], 'privacy:metadata:cmi5_player'); + + + return $collection; + } + + + /** + * Export all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts to export information for. + */ + public static function export_user_data(approved_contextlist $contextlist) { + + global $DB; + + if (empty($contextlist)) { + echo"empty contexts"; + return; + } + + // Get the user. + $user = $contextlist->get_user(); + $userid = $user->id; + + // Get the list of contexts that contain user information for the specified user. + // (The context->instanceid = cm->id and the cm.instance equals moodlecourseid). + foreach ($contextlist as $context) { + + $data = helper::get_context_data($context, $user); + + // Retrieve the coursemodule + $cm = get_coursemodule_from_id('cmi5launch', $context->instanceid); + + // The course modules instance correlates to the moodle course id in our tables. + // Combined with the user id, we can get the specific records we need. + $mid = $cm->instance; + $params = array ('userid' => $userid, 'moodlecourseid'=> $mid); + + // Start getting data on usercourse table + $recordset = $DB->get_recordset('cmi5launch_usercourse', $params); + + // To hold the course data. + $coursedata = []; + + // Cycle through recordset in case there are multiple. + foreach ($recordset as $record) { + + // Make user friendly names for data. + $userfriendly = array( 'ID of instance' => $record->id, + 'User ID' => $record->userid, + 'Course ID' => $record->courseid, + 'Moodle Course ID' => $record->moodlecourseid, + 'Registration ID' => $record->registrationid, + 'Return URL' => $record->returnurl, + 'AUs of instance' => $record->aus, + 'Grades of AUs' => $record->ausgrades, + 'Overall grade of instance' => $record->grade); + + // Then add as ONE item in array, that way if there is more than one it unpacks nicely. + $coursedata = ['User information' => $userfriendly]; + } + + // Combine the course data with the usercourse data. + $contextdata = (object)array_merge((array)$data, $coursedata); + + // Write data out. + writer::with_context($context)->export_data( + ['Course info pertaining to user'], + (object) $contextdata + ); + + // Now get the AU data. + $recordset = $DB->get_recordset('cmi5launch_aus', $params); + + // To hold the AU data. + $ausdata = []; + + // Cycle through recordset in case there are multiple. + foreach ($recordset as $record) { + + // Make user friendly names for data display. + $userfriendly = array( 'ID of AU instance' => $record->id, + 'User ID' => $record->userid, + 'The attempt number of the AU' => $record->attempt, + 'LMS ID of the AU' => $record->lmsid, + 'Moodle Course ID' => $record->moodlecourseid, + 'URL of the AU' => $record->url, + 'The type of the AU' => $record->type, + 'The title of the AU' => $record->title, + 'The description of the AU' => $record->description, + 'The objectives of the AU' => $record->objectives, + 'The move on value of the AU' => $record->moveon, + 'The AU index of the AU' => $record->auindex, + 'The cmi5 activity type of the AU' => $record->activitytype, + 'The amount it goes to mastery score' => $record->masteryscore, + 'Whether it has been completed' => $record->completed, + 'Whether it has been passed' => $record->passed, + 'Whether it is in progress' => $record->inprogress, + 'Whether it has been satisfied' => $record->satisfied, + 'Whether it has not been attempted' => $record->noattempt, + 'This AUs individual session\'s IDs' => $record->sessions, + 'The scores of the AU, as array' => $record->scores, + 'The overall grade of the AU' => $record->grade, + ); + + // Then add as ONE item in array, that way if there is more than one it unpacks nicely. + $ausdata = ['AU info' => $userfriendly]; + } + + // Combine the course data with the au data. + $contextdata = (object)array_merge((array)$data, $ausdata); + + // Write data out. + writer::with_context($context)->export_data( + ['AU info pertaining to user'], + (object) $contextdata + ); + + // Now get the sessions data. + $recordset = $DB->get_recordset('cmi5launch_sessions', $params); + + // To hold session information. + $sessiondata = []; + + // Cycle through recordset in case there are multiple. + foreach ($recordset as $record) { + + // Make user friendly names for display. + $userfriendly = array( 'ID of session instance' => $record->id, + 'Session ID' => $record->sessionid, + 'User ID' => $record->userid, + 'Moodle Course ID' => $record->moodlecourseid, + 'Registration Courses AUs ID' => $record->registrationscoursesausid, + 'Time a session was started' => $record->creeatedat, + 'Time a session was updated' => $record->updatedat, + 'Code' => $record->code, + 'Launch token ID' => $record->launchtokenid, + 'Last request time' => $record->lastrequesttime, + 'Amount it goes to Mastery Score' => $record->masteryscore, + 'The score of the session' => $record->score, + 'If it was launched' => $record->islaunched, + 'If it was initialized' => $record->isinitialized, + 'Time it was initialized' => $record->initializedat, + 'Duration of session' => $record->duration, + 'If it was completed' => $record->iscompleted, + 'If it was passed' => $record->ispassed, + 'If it was failed' => $record->isfailed, + 'If it was terminated' => $record->isterminated, + 'If it was abandoned' => $record->isabandoned, + 'Progress of session in recordments from LRS' => ("
" . implode("\n ", json_decode($record->progress) ) . "
"), + 'Launch URL' => $record->launchurl); + + // Then add as ONE item in array, that way if there is more than one it unpacks nicely. + $sessiondata = ['Session info' => $userfriendly]; + } + + // Combine the course data with the session data. + $contextdata = (object)array_merge((array)$data, $sessiondata); + + // Write data out. + writer::with_context($context)->export_data( + ['Session info pertaining to user'], + (object) $contextdata + ); + + } + + } + + + /** + * Get the list of contexts that contain user information for the specified user. + * + * @param int $userid The user to search. + * @return contextlist $contextlist The contextlist containing the list of contexts used in this plugin. + */ + public static function get_contexts_for_userid(int $userid) : contextlist { + + + global $DB; + + $sql = "SELECT ctx.id + FROM {context} ctx + JOIN {course_modules} cm + ON cm.id = ctx.instanceid + AND ctx.contextlevel = :context + JOIN {modules} m + ON m.id = cm.module + AND m.name = 'cmi5launch' + JOIN {%s} c5l + ON c5l.moodlecourseid = cm.instance + WHERE c5l.userid = :userid"; + + $params = ['context' => CONTEXT_MODULE, 'userid' => $userid]; + + $contextlist = new contextlist(); + $contextlist->add_from_sql(sprintf($sql, 'cmi5launch_usercourse'), $params); + $contextlist->add_from_sql(sprintf($sql, 'cmi5launch_sessions'), $params); + $contextlist->add_from_sql(sprintf($sql, 'cmi5launch_aus'), $params); + + return $contextlist; + } + + /** + * Get the list of users who have data within a context. + * + * @param userlist $userlist The userlist containing the list of users who have data in this context/plugin combination. + */ + public static function get_users_in_context(userlist $userlist) { + $context = $userlist->get_context(); + + if (!is_a($context, \context_module::class)) { + return; + } + + $sql = "SELECT c5l.userid + FROM {%s} c5l + JOIN {modules} m + ON m.name = 'cmi5launch' + JOIN {course_modules} cm + ON cm.module = m.id + JOIN {context} ctx + ON ctx.instanceid = cm.id + AND ctx.contextlevel = :modlevel + WHERE ctx.id = :contextid"; + + $params = ['modlevel' => CONTEXT_MODULE, 'contextid' => $context->id]; + + $userlist->add_from_sql('userid', sprintf($sql, 'cmi5launch_usercourse'), $params); + $userlist->add_from_sql('userid', sprintf($sql, 'cmi5launch_sessions'), $params); + $userlist->add_from_sql('userid', sprintf($sql, 'cmi5launch_aus'), $params); + + } + + /** + * Delete all user data which matches the specified context. + * + * @param context $context A user context. + */ + public static function delete_data_for_all_users_in_context(\context $context) { + + global $DB; + + // This should not happen, but just in case. + if ($context->contextlevel != CONTEXT_MODULE) { + return; + } + + $cm = get_coursemodule_from_id('cmi5launch', $context->instanceid); + if (!$cm) { + return; + } + + // This table needs a diferent key, but to be deleted still + $DB->delete_records('cmi5launch', ['id' => $cm->instance]); + + // Tables to delete from with same key. + $tables = ['cmi5launch_usercourse', 'cmi5launch_sessions', 'cmi5launch_aus']; + + foreach ($tables as $table) { + + $DB->delete_records($table, ['moodlecourseid' => $cm->instance]); + + } + } + + + /** + * Delete all user data for the specified user, in the specified contexts. + * + * @param approved_contextlist $contextlist The approved contexts and user information to delete information for. + */ + public static function delete_data_for_user(approved_contextlist $contextlist) { + global $DB; + + if (empty($contextlist->count())) { + return; + } + $userid = $contextlist->get_user()->id; + + + foreach ($contextlist->get_contexts() as $context) { + + //Retrieve the instance id from the context. + $instanceid = $DB->get_field('course_modules', 'instance', ['id' => $context->instanceid], MUST_EXIST); + + // Tables to delete from with same key if context matches. + $tables = ['cmi5launch_usercourse', 'cmi5launch_sessions', 'cmi5launch_aus']; + + foreach ($tables as $table) { + + $sql = array("moodlecourseid" => $instanceid, "userid" => $userid); + + $deleted = $DB->delete_records($table, $sql); + + } + } + } + + /** + * Delete multiple users within a single context. + * + * @param approved_userlist $userlist The approved context and user information to delete information for. + */ + public static function delete_data_for_users(approved_userlist $userlist) { + + global $DB; + + $context = $userlist->get_context(); + + $cm = $DB->get_record('course_modules', ['id' => $context->instanceid]); + + list($userinsql, $userinparams) = $DB->get_in_or_equal($userlist->get_userids(), SQL_PARAMS_NAMED); + $params = array_merge(['moodlecourseid' => $cm->instance], $userinparams); + $sql = "moodlecourseid = :moodlecourseid AND userid {$userinsql}"; + + $DB->delete_records_select('cmi5lauch_usercourse', $sql, $params); + $DB->delete_records_select('cmi5lauch_sessions', $sql, $params); + $DB->delete_records_select('cmi5lauch_aus', $sql, $params); + } + + + } diff --git a/classes/task/check_completion.php b/classes/task/check_completion.php index 6b68950..4b07d6c 100755 --- a/classes/task/check_completion.php +++ b/classes/task/check_completion.php @@ -1,85 +1,85 @@ -. - -/** - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -namespace mod_cmi5launch\task; -defined('MOODLE_INTERNAL') || die(); -require_once(dirname(dirname(dirname(__FILE__))).'/lib.php'); -require_once($CFG->dirroot.'/lib/completionlib.php'); - -class check_completion extends \core\task\scheduled_task { - public function get_name() { - return get_string('checkcompletion', 'mod_cmi5launch'); - } - - public function execute() { - global $DB; - - $module = $DB->get_record('modules', array('name' => 'cmi5launch'), '*', MUST_EXIST); - $modules = $DB->get_records('cmi5launch'); - $courses = array(); // Cache course data incase the multiple modules exist in a course. - - foreach ($modules as $cmi5launch) { - echo ('Checking module id '.$cmi5launch->id.'. '.PHP_EOL); - $cm = $DB->get_record( - 'course_modules', - array('module' => $module->id, - 'instance' => $cmi5launch->id), - '*', - MUST_EXIST - ); - if (!isset($courses[$cm->course])) { - $courses[$cm->course] = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); - $courses[$cm->course]->enrolments = $DB->get_records('user_enrolments', array('status' => 0)); - } - $course = $courses[$cm->course]; - $completion = new \completion_info($course); - - $possibleresult = COMPLETION_COMPLETE; - - if ($cmi5launch->cmi5expiry > 0) { - $possibleresult = COMPLETION_UNKNOWN; - } - - if ($completion->is_enabled($cm) && $cmi5launch->cmi5verbid) { - foreach ($course->enrolments as $enrolment) { - echo ('Checking user id '.$enrolment->userid.'. '); - $oldstate = $completion->get_data($cm, false, $enrolment->userid)->completionstate; - echo ('Old completion state was '.$oldstate.'. '); - $completion->update_state($cm, $possibleresult, $enrolment->userid); - $newstate = $completion->get_data($cm, false, $enrolment->userid)->completionstate; - echo ('New completion state is '.$newstate.'. '.PHP_EOL); - if ($oldstate !== $newstate) { - // Trigger Activity completed event. - $event = \mod_cmi5launch\event\activity_completed::create(array( - 'objectid' => $cmi5launch->id, - 'context' => \context_module::instance($cm->id), - 'userid' => $enrolment->userid - )); - $event->add_record_snapshot('course_modules', $cm); - $event->add_record_snapshot('cmi5launch', $cmi5launch); - $event->trigger(); - } - } - } - } - } -} \ No newline at end of file +. + +/** + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace mod_cmi5launch\task; +defined('MOODLE_INTERNAL') || die(); +require_once(dirname(dirname(dirname(__FILE__))).'/lib.php'); +require_once($CFG->dirroot.'/lib/completionlib.php'); + +class check_completion extends \core\task\scheduled_task { + public function get_name() { + return get_string('checkcompletion', 'mod_cmi5launch'); + } + + public function execute() { + global $DB; + + $module = $DB->get_record('modules', array('name' => 'cmi5launch'), '*', MUST_EXIST); + $modules = $DB->get_records('cmi5launch'); + $courses = array(); // Cache course data incase the multiple modules exist in a course. + + foreach ($modules as $cmi5launch) { + echo ('Checking module id '.$cmi5launch->id.'. '.PHP_EOL); + $cm = $DB->get_record( + 'course_modules', + array('module' => $module->id, + 'instance' => $cmi5launch->id), + '*', + MUST_EXIST + ); + if (!isset($courses[$cm->course])) { + $courses[$cm->course] = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + $courses[$cm->course]->enrolments = $DB->get_records('user_enrolments', array('status' => 0)); + } + $course = $courses[$cm->course]; + $completion = new \completion_info($course); + + $possibleresult = COMPLETION_COMPLETE; + + if ($cmi5launch->cmi5expiry > 0) { + $possibleresult = COMPLETION_UNKNOWN; + } + + if ($completion->is_enabled($cm) && $cmi5launch->cmi5verbid) { + foreach ($course->enrolments as $enrolment) { + echo ('Checking user id '.$enrolment->userid.'. '); + $oldstate = $completion->get_data($cm, false, $enrolment->userid)->completionstate; + echo ('Old completion state was '.$oldstate.'. '); + $completion->update_state($cm, $possibleresult, $enrolment->userid); + $newstate = $completion->get_data($cm, false, $enrolment->userid)->completionstate; + echo ('New completion state is '.$newstate.'. '.PHP_EOL); + if ($oldstate !== $newstate) { + // Trigger Activity completed event. + $event = \mod_cmi5launch\event\activity_completed::create(array( + 'objectid' => $cmi5launch->id, + 'context' => \context_module::instance($cm->id), + 'userid' => $enrolment->userid, + )); + $event->add_record_snapshot('course_modules', $cm); + $event->add_record_snapshot('cmi5launch', $cmi5launch); + $event->trigger(); + } + } + } + } + } +} diff --git a/cmi5PHP/src/About.php b/cmi5PHP/src/About.php deleted file mode 100755 index a3d5421..0000000 --- a/cmi5PHP/src/About.php +++ /dev/null @@ -1,55 +0,0 @@ -_fromArray($arg); - } - - if (! isset($this->version)) { - $this->setVersion(array()); - } - if (! isset($this->extensions)) { - $this->setExtensions(array()); - } - } - - public function setVersion($value) { $this->version = $value; return $this; } - public function getVersion() { return $this->version; } - - public function setExtensions($value) { - if (! $value instanceof Extensions) { - $value = new Extensions($value); - } - - $this->extensions = $value; - - return $this; - } - public function getExtensions() { return $this->extensions; } -} diff --git a/cmi5PHP/src/Activity.php b/cmi5PHP/src/Activity.php deleted file mode 100755 index 8110336..0000000 --- a/cmi5PHP/src/Activity.php +++ /dev/null @@ -1,55 +0,0 @@ -_fromArray($arg); - } - } - - public function getObjectType() { return $this->objectType; } - - // FEATURE: check IRI? - public function setId($value) { $this->id = $value; return $this; } - public function getId() { return $this->id; } - - public function setDefinition($value) { - if (! $value instanceof ActivityDefinition && is_array($value)) { - $value = new ActivityDefinition($value); - } - - $this->definition = $value; - - return $this; - } - public function getDefinition() { return $this->definition; } -} diff --git a/cmi5PHP/src/ActivityDefinition.php b/cmi5PHP/src/ActivityDefinition.php deleted file mode 100755 index 291008d..0000000 --- a/cmi5PHP/src/ActivityDefinition.php +++ /dev/null @@ -1,115 +0,0 @@ -_fromArray($arg); - } - - foreach ( - [ - 'name', - 'description', - 'extensions', - ] as $k - ) { - $method = 'set' . ucfirst($k); - - if (! isset($this->$k)) { - $this->$method(array()); - } - } - } - - // FEATURE: check URI? - public function setType($value) { $this->type = $value; return $this; } - public function getType() { return $this->type; } - - public function setName($value) { - if (! $value instanceof LanguageMap) { - $value = new LanguageMap($value); - } - - $this->name = $value; - - return $this; - } - public function getName() { return $this->name; } - - public function setDescription($value) { - if (! $value instanceof LanguageMap) { - $value = new LanguageMap($value); - } - - $this->description = $value; - - return $this; - } - public function getDescription() { return $this->description; } - - public function setMoreInfo($value) { $this->moreInfo = $value; return $this; } - public function getMoreInfo() { return $this->moreInfo; } - - public function setExtensions($value) { - if (! $value instanceof Extensions) { - $value = new Extensions($value); - } - - $this->extensions = $value; - - return $this; - } - public function getExtensions() { return $this->extensions; } - - public function setInteractionType($value) { $this->interactionType = $value; return $this; } - public function getInteractionType() { return $this->interactionType; } - public function setCorrectResponsesPattern($value) { $this->correctResponsesPattern = $value; return $this; } - public function getCorrectResponsesPattern() { return $this->correctResponsesPattern; } - - // TODO: make these arrays of InteractionComponents - public function setChoices($value) { $this->choices = $value; return $this; } - public function getChoices() { return $this->choices; } - public function setScale($value) { $this->scale = $value; return $this; } - public function getScale() { return $this->scale; } - public function setSource($value) { $this->source = $value; return $this; } - public function getSource() { return $this->source; } - public function setTarget($value) { $this->target = $value; return $this; } - public function getTarget() { return $this->target; } - public function setSteps($value) { $this->steps = $value; return $this; } - public function getSteps() { return $this->steps; } -} diff --git a/cmi5PHP/src/ActivityProfile.php b/cmi5PHP/src/ActivityProfile.php deleted file mode 100755 index d2f25ff..0000000 --- a/cmi5PHP/src/ActivityProfile.php +++ /dev/null @@ -1,34 +0,0 @@ -activity = $value; - - return $this; - } - public function getActivity() { return $this->activity; } -} diff --git a/cmi5PHP/src/Agent.php b/cmi5PHP/src/Agent.php deleted file mode 100755 index ce50a51..0000000 --- a/cmi5PHP/src/Agent.php +++ /dev/null @@ -1,166 +0,0 @@ -_fromArray($arg); - } - } - - public function asVersion($version) { - $result = array( - 'objectType' => $this->objectType - ); - - if (isset($this->name)) { - $result['name'] = $this->name; - } - - // - // only one of these, note that if 'account' has been set - // but is returned empty then no IFI will be included - // - if (isset($this->account)) { - $versioned_acct = $this->account->asVersion($version); - if (! empty($versioned_acct)) { - $result['account'] = $versioned_acct; - } - } - elseif (isset($this->mbox_sha1sum)) { - $result['mbox_sha1sum'] = $this->mbox_sha1sum; - } - elseif (isset($this->mbox)) { - $result['mbox'] = $this->mbox; - } - elseif (isset($this->openid)) { - $result['openid'] = $this->openid; - } - - return $result; - } - - public function isIdentified() { - return (isset($this->mbox) || isset($this->mbox_sha1sum) || isset($this->openid) || isset($this->account)); - } - - // - // having multiple IFIs set shouldn't cause a problem here - // so long as all of their values match their counterparts - // - // we could allow for multiple IFIs in the `this` object and - // ignore them missing in the signature but discussion ruled - // against that need since the serialization via asVersion - // shouldn't result in that ever for a valid statement - // - public function compareWithSignature($fromSig) { - // - // mbox and mbox_sha1sum are a special case where they can be - // equal but have to be transformed for comparison, check them - // first and short circuit - // - if (isset($this->mbox) && isset($fromSig->mbox_sha1sum)) { - if ($this->getMbox_sha1sum() === $fromSig->mbox_sha1sum) { - return array('success' => true, 'reason' => null); - } - - return array('success' => false, 'reason' => 'Comparison of this.mbox to signature.mbox_sha1sum failed: no match'); - } - elseif (isset($fromSig->mbox) && isset($this->mbox_sha1sum)) { - if ($fromSig->getMbox_sha1sum() === $this->mbox_sha1sum) { - return array('success' => true, 'reason' => null); - } - - return array('success' => false, 'reason' => 'Comparison of this.mbox_sha1sum to signature.mbox failed: no match'); - } - - foreach (array('mbox', 'mbox_sha1sum', 'openid') as $property) { - if (isset($this->$property) || isset($fromSig->$property)) { - if ($this->$property !== $fromSig->$property) { - return array('success' => false, 'reason' => "Comparison of $property failed: value is not the same"); - } - } - } - if (isset($this->account) || isset($fromSig->account)) { - if (! isset($fromSig->account)) { - return array('success' => false, 'reason' => "Comparison of account failed: value not in signature"); - } - if (! isset($this->account)) { - return array('success' => false, 'reason' => "Comparison of account failed: value not in this"); - } - - $acctResult = $this->account->compareWithSignature($fromSig->account); - if (! $acctResult['success']) { - return array('success' => false, 'reason' => "Comparison of account failed: " . $acctResult['reason']); - } - } - - return array('success' => true, 'reason' => null); - } - - public function getObjectType() { return $this->objectType; } - - public function setName($value) { $this->name = $value; return $this; } - public function getName() { return $this->name; } - - public function setMbox($value) { - if (isset($value) && (! (stripos($value, 'mailto:') === 0))) { - $value = 'mailto:' . $value; - } - $this->mbox = $value; - return $this; - } - public function getMbox() { return $this->mbox; } - - public function setMbox_sha1sum($value) { $this->mbox_sha1sum = $value; return $this; } - public function getMbox_sha1sum() { - if (isset($this->mbox_sha1sum)) { - return $this->mbox_sha1sum; - } - - if (isset($this->mbox)) { - return sha1($this->mbox); - } - } - public function setOpenid($value) { $this->openid = $value; return $this; } - public function getOpenid() { return $this->openid; } - - public function setAccount($value) { - if (! $value instanceof AgentAccount && is_array($value)) { - $value = new AgentAccount($value); - } - - $this->account = $value; - - return $this; - } - public function getAccount() { return $this->account; } -} diff --git a/cmi5PHP/src/AgentAccount.php b/cmi5PHP/src/AgentAccount.php deleted file mode 100755 index b1350d9..0000000 --- a/cmi5PHP/src/AgentAccount.php +++ /dev/null @@ -1,39 +0,0 @@ -_fromArray($arg); - } - } - - public function setName($value) { $this->name = $value; return $this; } - public function getName() { return $this->name; } - public function setHomePage($value) { $this->homePage = $value; return $this; } - public function getHomePage() { return $this->homePage; } -} diff --git a/cmi5PHP/src/AgentProfile.php b/cmi5PHP/src/AgentProfile.php deleted file mode 100755 index 05879a9..0000000 --- a/cmi5PHP/src/AgentProfile.php +++ /dev/null @@ -1,39 +0,0 @@ -agent = $value; - - return $this; - } - public function getAgent() { return $this->agent; } -} diff --git a/cmi5PHP/src/ArraySetterTrait.php b/cmi5PHP/src/ArraySetterTrait.php deleted file mode 100755 index 46195e6..0000000 --- a/cmi5PHP/src/ArraySetterTrait.php +++ /dev/null @@ -1,30 +0,0 @@ - $v) { - $method = 'set' . ucfirst($k); - if (isset($options[$k]) && method_exists($this, $method)) { - $this->$method($options[$k]); - } - } - } -} diff --git a/cmi5PHP/src/AsVersionTrait.php b/cmi5PHP/src/AsVersionTrait.php deleted file mode 100755 index 87f7eb0..0000000 --- a/cmi5PHP/src/AsVersionTrait.php +++ /dev/null @@ -1,84 +0,0 @@ - $value) { - // - // skip properties that start with an underscore to allow - // storing information that isn't included in statement - // structure etc. (see Attachment.content for example) - // - if (strpos($property, '_') === 0) { - continue; - } - - if ($value instanceof VersionableInterface) { - $value = $value->asVersion($version); - } - elseif (is_array($value) && !empty($value)) { - $tmp_value = array(); - foreach ($value as $element) { - if ($element instanceof VersionableInterface) { - array_push($tmp_value, $element->asVersion($version)); - } - else { - array_push($tmp_value, $element); - } - } - $value = $tmp_value; - } - - if (isset($value) && (!is_array($value) || !empty($value))) { - $result[$property] = $value; - } - } - - if (method_exists($this, '_asVersion')) { - $this->_asVersion($result, $version); - } - - return $result; - } - - /** - * Prevent external mutation - * - * @param string $property - * @param mixed $value - * @throws DomainException - */ - final public function __set($property, $value) { - throw new DomainException(__CLASS__ . ' is immutable'); - } -} diff --git a/cmi5PHP/src/Attachment.php b/cmi5PHP/src/Attachment.php deleted file mode 100755 index 5c0388e..0000000 --- a/cmi5PHP/src/Attachment.php +++ /dev/null @@ -1,102 +0,0 @@ -_fromArray($arg); - - if (isset($arg['content'])) { - $this->setContent($arg['content']); - } - } - - foreach ( - [ - 'display', - 'description', - ] as $k - ) { - $method = 'set' . ucfirst($k); - - if (! isset($this->$k)) { - $this->$method(array()); - } - } - } - - public function setUsageType($value) { $this->usageType = $value; return $this; } - public function getUsageType() { return $this->usageType; } - - public function setDisplay($value) { - if (! $value instanceof LanguageMap) { - $value = new LanguageMap($value); - } - - $this->display = $value; - - return $this; - } - public function getDisplay() { return $this->display; } - - public function setDescription($value) { - if (! $value instanceof LanguageMap) { - $value = new LanguageMap($value); - } - - $this->description = $value; - - return $this; - } - public function getDescription() { return $this->description; } - - public function setContentType($value) { $this->contentType = $value; return $this; } - public function getContentType() { return $this->contentType; } - public function setLength($value) { $this->length = $value; return $this; } - public function getLength() { return $this->length; } - public function setSha2($value) { $this->sha2 = $value; return $this; } - public function getSha2() { return $this->sha2; } - public function setFileUrl($value) { $this->fileUrl = $value; return $this; } - public function getFileUrl() { return $this->fileUrl; } - - public function setContent($value) { - $this->_content = $value; - $this->setLength(strlen($value)); - $this->setSha2(hash("sha256", $value)); - return $this; - } - public function getContent() { return $this->_content; } - public function hasContent() { return isset($this->_content); } -} diff --git a/cmi5PHP/src/ComparableInterface.php b/cmi5PHP/src/ComparableInterface.php deleted file mode 100755 index b8deb8c..0000000 --- a/cmi5PHP/src/ComparableInterface.php +++ /dev/null @@ -1,23 +0,0 @@ -_fromArray($arg); - } - - foreach ( - [ - 'contextActivities', - 'extensions', - ] as $k - ) { - $method = 'set' . ucfirst($k); - - if (! isset($this->$k)) { - $this->$method(array()); - } - } - } - - public function setRegistration($value) { - if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { - throw new InvalidArgumentException('arg1 must be a UUID'); - } - $this->registration = $value; - return $this; - } - public function getRegistration() { return $this->registration; } - - public function setInstructor($value) { - if (! ($value instanceof Agent || $value instanceof Group) && is_array($value)) { - if (isset($value['objectType']) && $value['objectType'] === "Group") { - $value = new Group($value); - } - else { - $value = new Agent($value); - } - } - - $this->instructor = $value; - - return $this; - } - public function getInstructor() { return $this->instructor; } - - public function setTeam($value) { - if (! $value instanceof Group && is_array($value)) { - $value = new Group($value); - } - - $this->team = $value; - - return $this; - } - public function getTeam() { return $this->team; } - - public function setContextActivities($value) { - if (! $value instanceof ContextActivities) { - $value = new ContextActivities($value); - } - - $this->contextActivities = $value; - - return $this; - } - public function getContextActivities() { return $this->contextActivities; } - - public function setRevision($value) { $this->revision = $value; return $this; } - public function getRevision() { return $this->revision; } - public function setPlatform($value) { $this->platform = $value; return $this; } - public function getPlatform() { return $this->platform; } - public function setLanguage($value) { $this->language = $value; return $this; } - public function getLanguage() { return $this->language; } - - public function setStatement($value) { - if (! $value instanceof StatementRef && is_array($value)) { - $value = new StatementRef($value); - } - - $this->statement = $value; - - return $this; - } - public function getStatement() { return $this->statement; } - - public function setExtensions($value) { - if (! $value instanceof Extensions) { - $value = new Extensions($value); - } - - $this->extensions = $value; - - return $this; - } - public function getExtensions() { return $this->extensions; } -} diff --git a/cmi5PHP/src/ContextActivities.php b/cmi5PHP/src/ContextActivities.php deleted file mode 100755 index 634d2e5..0000000 --- a/cmi5PHP/src/ContextActivities.php +++ /dev/null @@ -1,68 +0,0 @@ -_fromArray($arg); - } - } - - private function _listSetter($prop, $value) { - if (is_array($value)) { - if (isset($value['id'])) { - array_push($this->$prop, new Activity($value)); - } - else { - foreach ($value as $k => $v) { - if (! $value[$k] instanceof Activity) { - $value[$k] = new Activity($value[$k]); - } - } - $this->$prop = $value; - } - } - elseif ($value instanceof Activity) { - array_push($this->$prop, $value); - } - else { - throw new \InvalidArgumentException('type of arg1 must be Activity, array of Activity properties, or array of Activity/array of Activity properties'); - } - return $this; - } - - public function setCategory($value) { return $this->_listSetter('category', $value); } - public function getCategory() { return $this->category; } - public function setParent($value) { return $this->_listSetter('parent', $value); } - public function getParent() { return $this->parent; } - public function setGrouping($value) { return $this->_listSetter('grouping', $value); } - public function getGrouping() { return $this->grouping; } - public function setOther($value) { return $this->_listSetter('other', $value); } - public function getOther() { return $this->other; } -} diff --git a/cmi5PHP/src/Document.php b/cmi5PHP/src/Document.php deleted file mode 100755 index ff862ef..0000000 --- a/cmi5PHP/src/Document.php +++ /dev/null @@ -1,67 +0,0 @@ -_fromArray($arg); - } - } - - public function setId($value) { $this->id = $value; return $this; } - public function getId() { return $this->id; } - public function setContentType($value) { $this->contentType = $value; return $this; } - public function getContentType() { return $this->contentType; } - public function setContent($value) { $this->content = $value; return $this; } - public function getContent() { return $this->content; } - public function setEtag($value) { $this->etag = $value; return $this; } - public function getEtag() { return $this->etag; } - - public function setTimestamp($value) { - if (isset($value)) { - if ($value instanceof \DateTime) { - // Use format('c') instead of format(\DateTime::ISO8601) due to bug in format(\DateTime::ISO8601) that generates an invalid timestamp. - $value = $value->format('c'); - } - elseif (is_string($value)) { - $value = $value; - } - else { - throw new \InvalidArgumentException('type of arg1 must be string or DateTime'); - } - } - - $this->timestamp = $value; - - return $this; - } - public function getTimestamp() { return $this->timestamp; } -} diff --git a/cmi5PHP/src/Extensions.php b/cmi5PHP/src/Extensions.php deleted file mode 100755 index 43b4f10..0000000 --- a/cmi5PHP/src/Extensions.php +++ /dev/null @@ -1,46 +0,0 @@ -_map; - - $keys = array_unique( - array_merge( - isset($this->_map) ? array_keys($this->_map) : array(), - isset($sigMap) ? array_keys($sigMap) : array() - ) - ); - - foreach ($keys as $key) { - if (! isset($sigMap[$key])) { - return array('success' => false, 'reason' => "$key not in signature"); - } - if (! isset($this->_map[$key])) { - return array('success' => false, 'reason' => "$key not in this"); - } - if ($this->_map[$key] != $sigMap[$key]) { - return array('success' => false, 'reason' => "$key does not match"); - } - } - - return array('success' => true, 'reason' => null); - } -} diff --git a/cmi5PHP/src/FromJSONTrait.php b/cmi5PHP/src/FromJSONTrait.php deleted file mode 100755 index b16a30d..0000000 --- a/cmi5PHP/src/FromJSONTrait.php +++ /dev/null @@ -1,35 +0,0 @@ -member)) { - $this->setMember(array()); - } - } - - public function asVersion($version) { - $result = parent::asVersion($version); - - if (count($this->member) > 0) { - $result['member'] = array(); - - foreach ($this->member as $v) { - array_push($result['member'], $v->asVersion($version)); - } - } - - return $result; - } - - public function compareWithSignature($fromSig) { - // - // if this group is identified then it is the comparison - // of the identifier that matters - // - if ($this->isIdentified() || $fromSig->isIdentified()) { - return parent::compareWithSignature($fromSig); - } - - // - // anonymous groups get their member list compared, - // short circuit when they don't have the same length - // - if (count($this->member) !== count($fromSig->member)) { - return array('success' => false, 'reason' => 'Comparison of member list failed: array lengths differ'); - } - - for ($i = 0; $i < count($this->member); $i++) { - $comparison = $this->member[$i]->compareWithSignature($fromSig->member[$i]); - if (! $comparison['success']) { - return array('success' => false, 'reason' => "Comparison of member $i failed: " . $comparison['reason']); - } - } - - return array('success' => true, 'reason' => null); - } - - public function setMember($value) { - foreach ($value as $k => $v) { - if (! $v instanceof Agent) { - $value[$k] = new Agent($v); - } - } - - $this->member = $value; - - return $this; - } - public function getMember() { return $this->member; } - public function addMember($value) { - if (! $value instanceof Agent) { - $value = new Agent($value); - } - - array_push($this->member, $value); - - return $this; - } -} diff --git a/cmi5PHP/src/JSONParseErrorException.php b/cmi5PHP/src/JSONParseErrorException.php deleted file mode 100755 index d3addbc..0000000 --- a/cmi5PHP/src/JSONParseErrorException.php +++ /dev/null @@ -1,49 +0,0 @@ -malformedValue = $malformedValue; - $this->jsonErrorNumber = (int) $jsonErrorNumber; - $this->jsonErrorMessage = $jsonErrorMessage; - - $message = sprintf(self::$format, $malformedValue, $jsonErrorMessage, $jsonErrorNumber); - - parent::__construct($message, $jsonErrorNumber, $previous); - } - - public function malformedValue() { - return $this->malformedValue; - } - - public function jsonErrorNumber() { - return $this->jsonErrorNumber; - } - - public function jsonErrorMessage() { - return $this->jsonErrorMessage; - } -} diff --git a/cmi5PHP/src/LRSInterface.php b/cmi5PHP/src/LRSInterface.php deleted file mode 100755 index 7eeb8f3..0000000 --- a/cmi5PHP/src/LRSInterface.php +++ /dev/null @@ -1,49 +0,0 @@ -success = (bool) $success; - $this->content = $content; - $this->httpResponse = $httpResponse; - } -} diff --git a/cmi5PHP/src/LanguageMap.php b/cmi5PHP/src/LanguageMap.php deleted file mode 100755 index f841fa6..0000000 --- a/cmi5PHP/src/LanguageMap.php +++ /dev/null @@ -1,45 +0,0 @@ -_map); - $preferredLanguage = $negotiator->getBest($acceptLanguage, $availableLanguages); - - $key = $availableLanguages[0]; - if (isset($preferredLanguage)) { - $key = $preferredLanguage->getValue(); - } - elseif (isset($this->_map['und'])) { - $key = 'und'; - } - - return $this->_map[$key]; - } -} diff --git a/cmi5PHP/src/Map.php b/cmi5PHP/src/Map.php deleted file mode 100755 index 685c5d6..0000000 --- a/cmi5PHP/src/Map.php +++ /dev/null @@ -1,61 +0,0 @@ -_map = func_get_arg(0); - } - else { - $this->_map = array(); - } - } - - public function asVersion($version = null) { - if (! $this->isEmpty()) { - return $this->_map; - } - } - - public function set($code, $value) { - $this->_map[$code] = $value; - } - private function _unset($code) { - unset($this->_map[$code]); - } - - public function isEmpty() { - return count($this->_map) === 0; - } - - public function __call($func, $args) { - switch ($func) { - case 'unset': - return $this->_unset($args[0]); - break; - default: - throw new \BadMethodCallException(get_class($this) . "::$func() does not exist"); - } - } -} diff --git a/cmi5PHP/src/Person.php b/cmi5PHP/src/Person.php deleted file mode 100755 index 1cd572b..0000000 --- a/cmi5PHP/src/Person.php +++ /dev/null @@ -1,84 +0,0 @@ -_fromArray($arg); - } - } - - public function asVersion($version) { - $result = array( - 'objectType' => $this->objectType - ); - if (isset($this->name)) { - $result['name'] = $this->name; - } - if (isset($this->account)) { - $result['account'] = array(); - foreach ($this->account as $account) { - if (! $account instanceof AgentAccount && is_array($account)) { - $account = new AgentAccount($account); - } - array_push($result['account'], $account->asVersion($version)); - } - } - if (isset($this->mbox_sha1sum)) { - $result['mbox_sha1sum'] = $this->mbox_sha1sum; - } - if (isset($this->mbox)) { - $result['mbox'] = $this->mbox; - } - if (isset($this->openid)) { - $result['openid'] = $this->openid; - } - - return $result; - } - - public function getObjectType() { return $this->objectType; } - - public function setName($value) { $this->name = $value; return $this; } - public function getName() { return $this->name; } - - public function setMbox($value) { $this->mbox = $value; return $this; } - public function getMbox() { return $this->mbox; } - - public function setMbox_sha1sum($value) { $this->mbox_sha1sum = $value; return $this; } - public function getMbox_sha1sum() {return $this->mbox_sha1sum;} - - public function setOpenid($value) { $this->openid = $value; return $this; } - public function getOpenid() { return $this->openid; } - - public function setAccount($value) { $this->account = $value; return $this; } - public function getAccount() { return $this->account; } -} diff --git a/cmi5PHP/src/Progress.php b/cmi5PHP/src/Progress.php deleted file mode 100644 index 72fbcf3..0000000 --- a/cmi5PHP/src/Progress.php +++ /dev/null @@ -1,401 +0,0 @@ - $regId, - 'since' => $session->createdAt - ); - - $statements = $this->sendRequestToLRS($data, $regId); - //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); - - $length = count($statement); - - for ($i = 0; $i < $length; $i++){ - - //This separates the larger statement into the separate sessions and verbs - $current = ($statement[$i]); - array_push($result, array ($regId => $current) ); - } - - return $result; - } - - - /** - * Builds and sends requests to LRS - * @param mixed $data - * @param mixed $id - * @return mixed - */ - public function sendRequestToLRS($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); - - //LRS username and password - $user = $settings['cmi5launchlrslogin']; - $pass = $settings['cmi5launchlrspass']; - - // 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", - ) - ) - ); - //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) - $result = file_get_contents( $url, false, $context ); - - $resultDecoded = json_decode($result, true); - - return $resultDecoded; - } - - /** - * Returns an actor (name) retrieved from collected LRS data based on registration id - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $i - the registration id - * @return mixed - actor - */ - public function retrieveActor($info, $regid){ - - $actor = $info[$regid][0]["actor"]["account"]["name"]; - return $actor; - } - - /** - * Returns a verb retrieved from collected LRS data based on registration id - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $i - the registration id - * @return mixed - verb - */ - public function retrieveVerbsOrig($resultChunked, $i){ - - //Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present - $verbInfo = $resultChunked[0][0][$i]["statements"][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 = $resultChunked[$i][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 = $resultChunked[$i][0]["verb"]["display"]; - //Retreive the language - $lang = array_key_first($verbLang); - //use it to retreive verb - $verb = [$verbLang][0][$lang]; - } - return $verb; - } - - public function retrieveVerbs($resultChunked, $i){ - - //Some verbs do not have an easy to display 'language' option, we need to check if 'display' is present - $verbInfo = $resultChunked[$i][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 = $resultChunked[$i][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 = $resultChunked[$i][0]["verb"]["display"]; - //Retreive the language - $lang = array_key_first($verbLang); - //use it to retreive verb - $verb = [$verbLang][0][$lang]; - } - return $verb; - } - - /** - * Returns an object (au title?) retrieved from collected LRS data based on registration id - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $i - the registration id - * @return mixed - object name - */ - public function retrieveObject($resultChunked, $i){ - //THIS is the SECOND chunk, this is the problem - $objectInfo = $resultChunked[$i][0]["object"]; - $definition = array_key_exists("definition", $objectInfo); - //If it is null then there is no "definition", so go by object id - if(!$definition ){ - //retrieve id - $object = $resultChunked[$i][0]["object"]["id"]; - //I have noticed that in the LRS when it can't find a name it references the WHOLE id as in "actor did WHOLEID", so I will do the same here - }else{ - //IF it is not null then there is a language easy to read version of object definition, such as 'en' or 'en-us' - $objectLang = $resultChunked[$i][0]["object"]["definition"]["name"]; - //Retreive the language - $lang = array_key_first($objectLang); - //use it to retreive verb - $object = [$objectLang][0][$lang]; - } - return $object; - } - /** - * TODO MB - This is able to get all results for later grading - * Result params when returned with statements can have 5 fields (not including extensions) - * Success - a true/false to provide for a pass/fail of Activity - * Completion - a true/false to provide for completion of Activity - * Score - takes a Score object - * Response - a string value that can contain anything, such as an answer to a question - * Duration - length of time taken for experience - * - * We are concerned with the top three for Moodle reporting purposes - * - * Summary of retrieveResult - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $i - the registration id - * @return mixed - */ - public function retrieveResult($resultChunked, $i){ - - //Verify this statement has a 'result' param - if (array_key_exists("result", $resultChunked ) ) - { - //If it exists, grab it - $resultInfo = $resultChunked[$i][0]["result"]; - - //Check which keys exist in 'result' - $success = array_key_exists("success", $resultInfo); - $completion = array_key_exists("completion", $resultInfo); - $score = array_key_exists("score", $resultInfo); - //Andy seeemed interested in durations? - $duration = array_key_exists("score", $resultInfo); - $response = array_key_exists("response", $resultInfo); - - } - - //How should we save and return these infos? A key value array maybe? - //If it is null then the item in question doesn't exist in this statement - if($success){ - //no need to make new variable, save over - $success = $resultChunked[$i][0]["result"]["success"]; - - //now that we have success, save to db. This means we need an object right? Can we update afield? - //even if we could we need id to find it... - }else{ - } - - //Maybe it would be better to just have a 'retrieveScore' for now - } - - /** - * Returns a timestamp retrieved from collected LRS data based on registration id - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $i - the registration id - * @return string - date/time - */ - public function retrieveTimestamp($resultChunked, $i){ - - $date = new DateTime($resultChunked[$i][0]["timestamp"], new DateTimeZone('US/Eastern')); - - $date->setTimezone(new DateTimeZone('America/New_York')); - - $date = $date->format('d-m-Y' . " ". 'h:i a'); - - return $date; - } - - /** - * - * Summary of retrieveScore - * @param mixed $resultChunked - data retrieved from LRS, usually an array - * @param mixed $registrationid - the registration id - * @return mixed - */ - //Ok, if we change so session id goes through, can we update DB in this func - public function retrieveScore($resultChunked, $registrationid){ - - //variable to hold score - $score = null; - - //Verify this statement has a 'result' param - if (array_key_exists("result", $resultChunked[$registrationid][0] ) ) - { - //If it exists, grab it - $resultInfo = $resultChunked[$registrationid][0]["result"]; - - $score = array_key_exists("score", $resultInfo); - - } - - //If it is null then the item in question doesn't exist in this statement - if ($score) { - - $score = $resultChunked[$registrationid][0]["result"]["score"]; - - //Raw score preferred to scaled - if($score["raw"]){ - - $returnScore = $score["raw"]; - return $returnScore; - } - elseif($score["scaled"]){ - - $returnScore = round($score["scaled"], 2) ; - return $returnScore; - } - - } - - } - - /** - * Summary of retrieveStatement - * //Retrieves statements from LRS - * @param mixed $regId - * @param mixed $id - * @param mixed $lmsId - * @return array - */ - - public function retrieveStatement($regId, $id, $session) - { - //Array to hold verbs and be returned - $progressUpdate = array(); - //Array to hold score and be returned - $returnScore = 0; - - $resultDecoded = $this->requestLRSinfo($regId, $session); - - //We need to sort the statements by finding their session id - //parse through array 'ext' to find the one holding session id, - //grab id and go with it - - foreach($resultDecoded as $singleStatment){ - - //We need to sort the statements by finding their session id - //parse through array 'ext' to find the one holding session id, - //grab id and go and compare to saved session 'code' - $code = $session->code; - $currentSessID = ""; - $ext = $singleStatment[$regId][0]["context"]["extensions"]; - foreach ($ext as $key => $value) { - - //if key contains "sessionid" in string - if(str_contains($key, "sessionid")){ - $currentSessID= $value; - } - } - - //Now if code equals currentSessID, this is a statement pertaining to this session - if($code == $currentSessID){ - - $actor = $this->retrieveActor($singleStatment, $regId); - $verb = $this->retrieveVerbs($singleStatment, $regId); - $object = $this->retrieveObject($singleStatment, $regId); - $date = $this->retrieveTimestamp($singleStatment, $regId); - $score = $this->retrieveScore($singleStatment, $regId); - - //If a session has more than one score, we only want the highest - if(!$score == null && $score > $returnScore){ - $returnScore = $score; - } - //Update to return - $progressUpdate[] = "$actor $verb $object on $date"; - - } - - } - $session->progress = json_encode($progressUpdate); - $session->score = $returnScore; - - return $session; - } - - /** - * Summary of prettyProgress - * I dont know if we need this anymore -TODO - * @param mixed $arrayOfStatements - * @return array - */ - public function prettyProgress($arrayOfStatements){ - $length = count($arrayOfStatements); - - //Why is iteration unreachable? It's reachable in the other test file - for($i = 0; $i < $length; $i++){ - - $actor = $this->retrieveActor($arrayOfStatements, $i); - $verb = $this->retrieveVerbs($arrayOfStatements, $i); - $object = $this->retrieveObject($arrayOfStatements, $i); - $date = $this->retrieveTimestamp($arrayOfStatements, $i); - - //Maybe make this an overloaded func that can print this and /or just verbs - //Like if you pass in verbs it gives only verbs - //Wait....this is the above smh - //BUT, this is the only one with the resultChunked info, so lets pass back shtuff - //and let it have the string stuff added later, then easy to parse - //Could even pas as actor=>actor - //OR object=> actor, verb, date. Then we can sort it by au! - $progressUpdate[] = "$actor $verb $object on $date"; - - } - - return $progressUpdate; - } -} -?> \ No newline at end of file diff --git a/cmi5PHP/src/RemoteLRS.php b/cmi5PHP/src/RemoteLRS.php deleted file mode 100755 index bd26927..0000000 --- a/cmi5PHP/src/RemoteLRS.php +++ /dev/null @@ -1,1225 +0,0 @@ - 'contentType', - 'Date' => 'date', - 'Last-Modified' => 'lastModified', - 'Etag' => 'etag', - 'X-Experience-API-Consistent-Through' => 'apiConsistentThrough', - 'X-Experience-API-Version' => 'apiVersion', - ); - protected $endpoint; - protected $version; - protected $auth; - protected $proxy; - protected $headers; - protected $extended; - - public function __construct() { - $_num_args = func_num_args(); - if ($_num_args == 1) { - $arg = func_get_arg(0); - - $this->_fromArray($arg); - - if (! isset($this->version)) { - $this->setVersion(Version::latest()); - } - if (! isset($this->auth) && isset($arg['username']) && isset($arg['password'])) { - $this->setAuth($arg['username'], $arg['password']); - } - } - elseif ($_num_args === 3) { - $this->setEndpoint(func_get_arg(0)); - $this->setVersion(func_get_arg(1)); - $this->setAuth(func_get_arg(2)); - } - elseif ($_num_args === 4) { - $this->setEndpoint(func_get_arg(0)); - $this->setVersion(func_get_arg(1)); - $this->setAuth(func_get_arg(2), func_get_arg(3)); - } - else { - $this->setVersion(Version::latest()); - } - } - - protected function sendRequest($method, $resource) { - $options = func_num_args() === 3 ? func_get_arg(2) : array(); - - // - // allow for full path requests, for instance as used by the - // moreStatements method which is based on server root rather - // than the stored endpoint - // - $url = $resource; - if (! preg_match('/^http/', $resource)) { - $url = $this->endpoint . $resource; - } - $http = array( - // - // redirects are not part of the spec so LRSs shouldn't be returning them - // - 'max_redirects' => 0, - - // - // this is here for some proxy handling - // - 'request_fulluri' => 1, - - // - // switching this to false causes non-2xx/3xx status codes to throw exceptions - // but we need to handle the "error" status codes ourselves in some cases - // - 'ignore_errors' => true, - - 'method' => $method, - 'header' => array( - 'X-Experience-API-Version: ' . $this->version - ), - ); - if (isset($this->auth)) { - array_push($http['header'], 'Authorization: ' . $this->auth); - } - if (isset($this->proxy)) { - $http['proxy'] = $this->proxy; - } - - if (isset($this->headers) && count($this->headers) > 0) { - foreach ($this->headers as $k => $v) { - array_push($http['header'], "$k: $v"); - } - } - - if (isset($options['headers'])) { - foreach ($options['headers'] as $k => $v) { - array_push($http['header'], "$k: $v"); - } - } - //TODO-Passing null as a second param is deprecated. - if (isset($options['params']) && count($options['params']) > 0) { - $url .= '?' . http_build_query($options['params'], null, '&', PHP_QUERY_RFC3986); - } - - if (($method === 'PUT' || $method === 'POST') && isset($options['content'])) { - $http['content'] = $options['content']; - if (is_string($options['content'])) { - array_push($http['header'], 'Content-length: ' . strlen($options['content'])); - } - } - - $success = false; - - // - // errors from fopen are reported to PHP as E_WARNING which prevents us - // from getting a reasonable message, so set an error handler here for - // the immediate call to turn it into an exception, and then restore - // normal handling - // - - set_error_handler( - function ($errno, $errstr, $errfile, $errline, array $errcontext) { - throw new \ErrorException($errstr, 0, $errno, $errfile, $errline); - } - ); - - $fp = null; - $response = null; - - try { - $context = stream_context_create(array( 'http' => $http )); - $fp = fopen($url, 'rb', false, $context); - - if (! $fp) { - $content = "Request failed: $php_errormsg"; - } - } - catch (\ErrorException $ex) { - $content = "Request failed: $ex"; - } - - restore_error_handler(); - - if ($fp) { - $metadata = stream_get_meta_data($fp); - $content = stream_get_contents($fp); - - $response = $this->_parseMetadata($metadata, $options); - - // - // keep a copy of the raw content, the methods expecting - // an LRS response may handle the content, for instance - // querying statements takes the returned value and converts - // it to Statement objects (really StatementsResult but who - // is counting), etc. but a user may want the original raw - // returned content untouched, do the same with the metadata - // because it feels like a good practice - // - $response['_content'] = $content; - $response['_metadata'] = $metadata; - - // - // Content-Type won't be set in the case of a 204 (and potentially others) - // - - - if (isset($response['headers']['contentType']) && $response['headers']['contentType'] === "multipart/mixed") { - $content = $this->_parseMultipart($response['headers']['contentTypeBoundary'], $content); - } - - if (($response['status'] >= 200 && $response['status'] < 300) || ($response['status'] === 404 && isset($options['ignore404']) && $options['ignore404'])) { - $success = true; - } - elseif ($response['status'] >= 300 && $response['status'] < 400) { - $content = "Unsupported status code: " . $response['status'] . " (LRS should not redirect)"; - } - } - - - return new LRSResponse($success, $content, $response); - } - - private function _parseMetadata($metadata) { - $result = array(); - - // simulate a 100 Continue to cause our loop - // to run until it sets something other than a 100 - $result['status'] = 100; - - while ($result['status'] == 100) { - $status_line = array_shift($metadata['wrapper_data']); - $status_parts = explode(' ', $status_line); - $result['status'] = intval($status_parts[1]); - } - - // - // pull out whitelisted headers - // - foreach (self::$whitelistedHeaders as $header => $prop) { - foreach ($metadata['wrapper_data'] as $line) { - if (stripos($line, $header . ':') === 0) { - $result['headers'][$prop] = ltrim(substr($line, (strlen($header . ':')))); - break; - } - } - } - - if (isset($result['headers']['contentType'])) { - $contentType_parts = array_map('trim', explode(';', $result['headers']['contentType'])); - - $result['headers']['contentType'] = $contentType_parts[0]; - for ($i = 1; $i < count($contentType_parts); $i++) { - $pair = array_map('trim', explode("=", $contentType_parts[$i], 2)); - if ($pair[0] === 'charset') { - $result['headers']['contentTypeCharset'] = $pair[1]; - } - elseif ($pair[0] === 'boundary') { - $result['headers']['contentTypeBoundary'] = $pair[1]; - } - } - } - - return $result; - } - - private function _parseMultipart($boundary, $content) { - $parts = array(); - - foreach (explode("--$boundary", $content) as $part) { - $part = ltrim($part, "\r\n"); - if ($part === '') { - continue; - } - elseif ($part === '--') { - break; - } - list($header, $body) = explode("\r\n\r\n", $part, 2); - - // - // the body has a CRLF on it before the boundary per the RFC - // so we need to remove it, but we only want to remove one - // because the body itself may include a trailing CRLF so - // PHP's rtrim function won't work in this case because it - // removes all of them - // - $body = preg_replace('/\r\n$/', '', $body, 1); - - array_push( - $parts, - array( - 'headers' => $this->_parseHeaders($header), - 'body' => $body - ) - ); - } - - return $parts; - } - - // - // Taken from http://www.php.net/manual/en/function.http-parse-headers.php#112917 - // and modified to: make folded work too, return status in first key. - // - // as suggested here: http://php.net/manual/en/function.http-parse-headers.php#112986 - // - // adapted to private method, and force headers to lowercase for easy detection - // - private function _parseHeaders($raw_headers) { - $headers = array(); - $key = ''; // [+] - - foreach(explode("\n", $raw_headers) as $i => $h) { - $h = explode(':', $h, 2); - $h[0] = strtolower($h[0]); - - if (isset($h[1])) { - if (! isset($headers[$h[0]])) { - $headers[$h[0]] = trim($h[1]); - } - elseif (is_array($headers[$h[0]])) { - // $tmp = array_merge($headers[$h[0]], array(trim($h[1]))); // [-] - // $headers[$h[0]] = $tmp; // [-] - $headers[$h[0]] = array_merge($headers[$h[0]], array(trim($h[1]))); // [+] - } - else { - // $tmp = array_merge(array($headers[$h[0]]), array(trim($h[1]))); // [-] - // $headers[$h[0]] = $tmp; // [-] - $headers[$h[0]] = array_merge(array($headers[$h[0]]), array(trim($h[1]))); // [+] - } - - $key = $h[0]; // [+] - } - else { // [+] - if (substr($h[0], 0, 1) == "\t") {// [+] - $headers[$key] .= "\r\n\t".trim($h[0]); // [+] - } - elseif (! $key) {// [+] - $headers[0] = trim($h[0]);trim($h[0]); // [+] - } - } // [+] - } - - return $headers; - } - - private function _buildAttachmentContent(&$requestCfg, $attachments) { - $boundary = Util::getUUID(); - $origContent = $requestCfg['content']; - - $requestCfg['content'] = '--' . $boundary . "\r\n"; - $requestCfg['content'] .= "Content-Type: application/json\r\n"; - $requestCfg['content'] .= "\r\n"; - $requestCfg['content'] .= $origContent; - - $attachmentContent = ''; - foreach ($attachments as $attachment) { - $attachmentContent .= '--' . $boundary . "\r\n"; - $attachmentContent .= 'Content-Type: ' . $attachment->getContentType() . "\r\n"; - $attachmentContent .= "Content-Transfer-Encoding: binary\r\n"; - $attachmentContent .= "X-Experience-API-Hash: " . $attachment->getSha2() . "\r\n"; - $attachmentContent .= "\r\n"; - $attachmentContent .= $attachment->getContent(); - $attachmentContent .= "\r\n"; - } - $attachmentContent .= '--' . $boundary . '--'; - - $requestCfg['headers']['Content-Type'] = 'multipart/mixed; boundary=' . $boundary; - $requestCfg['content'] .= "\r\n" . $attachmentContent; - } - - public function about() { - $response = $this->sendRequest('GET', 'about'); - - if ($response->success) { - $response->content = About::FromJSON($response->content); - } - - return $response; - } - - public function saveStatement($statement) { - if (! $statement instanceof Statement) { - $statement = new Statement($statement); - } - - //Ok, trial- MB - //Triaal for what megan? smh - // global $DB; - - // $DB->set_field("trial", "statement", $statement, null); - - /////// - - - $requestCfg = array( - 'headers' => array( - 'Content-Type' => 'application/json' - ), - 'content' => json_encode($statement->asVersion($this->version), JSON_UNESCAPED_SLASHES) - ); - - if ($statement->hasAttachmentsWithContent()) { - $this->_buildAttachmentContent($requestCfg, $statement->getAttachments()); - } - - $method = 'POST'; - if ($statement->hasId()) { - $method = 'PUT'; - $requestCfg['params'] = array('statementId' => $statement->getId()); - } - - $response = $this->sendRequest($method, 'statements', $requestCfg); - - if ($response->success) { - if (! $statement->hasId()) { - $parsed_content = json_decode($response->content, true); - - $statement->setId($parsed_content[0]); - } - - // - // save statement either returns no content when there is an id - // or returns the id when there wasn't, either way the caller - // may have called us with a statement configuration rather than - // a Statement object, so provide them back the Statement object - // as the content in either case on success - // - $response->content = $statement; - } - - return $response; - } - - //MB Lets see if we can save statements here - public function saveStatements($statements) { - - $versioned_statements = array(); - $attachments_map = array(); - foreach ($statements as $i => $st) { - if (! $st instanceof Statement) { - $st = new Statement($st); - $statements[$i] = $st; - } - $versioned_statements[$i] = $st->asVersion($this->version); - - if ($st->hasAttachmentsWithContent()) { - foreach ($st->getAttachments() as $attachment) { - if (! isset($attachments_map[$attachment->getSha2()])) { - $attachments_map[$attachment->getSha2()] = $attachment; - } - } - } - } - - $requestCfg = array( - 'headers' => array( - 'Content-Type' => 'application/json' - ), - 'content' => json_encode($versioned_statements, JSON_UNESCAPED_SLASHES), - ); - if (! empty($attachments_map)) { - $this->_buildAttachmentContent($requestCfg, array_values($attachments_map)); - } - - $response = $this->sendRequest('POST', 'statements', $requestCfg); - - if ($response->success) { - $parsed_content = json_decode($response->content, true); - foreach ($parsed_content as $i => $stId) { - $statements[$i]->setId($stId); - } - - $response->content = $statements; - } - - - return $response; - } - - //TODO INVEST - // - //Is this a freaking retreiveStatment, like I made for progress? - public function retrieveStatement($id, $options = array()) { - if (! isset($options['voided'])) { - $options['voided'] = false; - } - if (! isset($options['attachments'])) { - $options['attachments'] = false; - } - - $params = array(); - if ($options['voided']) { - $params['voidedStatementId'] = $id; - } - else { - $params['statementId'] = $id; - } - if ($options['attachments']) { - $params['attachments'] = 'true'; - } - - $response = $this->sendRequest( - 'GET', - 'statements', - array( - 'params' => $params - ) - ); - - if ($response->success) { - if (is_array($response->content)) { - $orig = $response->httpResponse['_multipartContent'] = $response->content; - - $response->content = Statement::FromJSON($orig[0]['body']); - - $attachmentsByHash = array(); - for ($i = 1; $i < count($orig); $i++) { - $attachmentsByHash[$orig[$i]['headers']['x-experience-api-hash']] = $orig[$i]; - } - - foreach ($response->content->getAttachments() as $attachment) { - $attachment->setContent($attachmentsByHash[$attachment->getSha2()]['body']); - } - } - else { - $response->content = Statement::FromJSON($response->content); - } - } - - return $response; - } - - public function retrieveVoidedStatement($id, $options = array()) { - $options['voided'] = true; - return $this->retrieveStatement($id, $options); - } - - private function _queryStatementsRequestParams($query) { - $result = array(); - - foreach (array('agent') as $k) { - if (isset($query[$k])) { - $result[$k] = json_encode($query[$k]->asVersion($this->version)); - } - } - foreach ( - array( - 'verb', - 'activity', - ) as $k - ) { - if (isset($query[$k])) { - $result[$k] = $query[$k]->getId(); - } - } - foreach ( - array( - 'ascending', - 'related_activities', - 'related_agents', - 'attachments', - ) as $k - ) { - if (isset($query[$k])) { - $result[$k] = $query[$k] ? 'true' : 'false'; - } - } - foreach ( - array( - 'registration', - 'since', - 'until', - 'limit', - 'format', - ) as $k - ) { - if (isset($query[$k])) { - $result[$k] = $query[$k]; - } - } - - return $result; - } - - private function _queryStatementsResult(&$response) { - if (is_array($response->content)) { - $orig = $response->httpResponse['_multipartContent'] = $response->content; - - $response->content = StatementsResult::FromJSON($orig[0]['body']); - - $attachmentsByHash = array(); - for ($i = 1; $i < count($orig); $i++) { - $attachmentsByHash[$orig[$i]['headers']['x-experience-api-hash']] = $orig[$i]; - } - - foreach ($response->content->getStatements() as $st) { - foreach ($st->getAttachments() as $attachment) { - $attachment->setContent($attachmentsByHash[$attachment->getSha2()]['body']); - } - } - - return; - } - - $response->content = StatementsResult::fromJSON($response->content); - - return; - } - - public function queryStatements($query) { - $requestCfg = array( - 'params' => $this->_queryStatementsRequestParams($query), - ); - if (func_num_args() > 1) { - $options = func_get_arg(1); - - if (isset($options)) { - if (isset($options['headers'])) { - $requestCfg['headers'] = $options['headers']; - } - } - } - - $response = $this->sendRequest('GET', 'statements', $requestCfg); - - if ($response->success) { - $this->_queryStatementsResult($response); - } - - return $response; - } - - public function moreStatements($moreUrl) { - if ($moreUrl instanceof StatementsResult) { - $moreUrl = $moreUrl->getMore(); - } - $moreUrl = $this->getEndpointServerRoot() . $moreUrl; - - $response = $this->sendRequest('GET', $moreUrl); - - if ($response->success) { - $this->_queryStatementsResult($response); - } - - return $response; - } - - public function retrieveStateIds($activity, $agent) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - - $requestCfg = array( - 'params' => array( - 'activityId' => $activity->getId(), - 'agent' => json_encode($agent->asVersion($this->version)), - ), - ); - if (func_num_args() > 2) { - $options = func_get_arg(2); - if (isset($options)) { - if (isset($options['registration'])) { - $requestCfg['params']['registration'] = $options['registration']; - } - if (isset($options['since'])) { - $requestCfg['params']['since'] = $options['since']; - } - } - } - - $response = $this->sendRequest('GET', 'activities/state', $requestCfg); - - if ($response->success) { - $response->content = json_decode($response->content); - } - - return $response; - } - - public function retrieveState($activity, $agent, $id) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - $registration = null; - - $requestCfg = array( - 'params' => array( - 'activityId' => $activity->getId(), - 'agent' => json_encode($agent->asVersion($this->version)), - 'stateId' => $id, - ), - 'ignore404' => true, - ); - if (func_num_args() > 3) { - $options = func_get_arg(3); - if (isset($options)) { - if (isset($options['registration'])) { - $requestCfg['params']['registration'] = $registration = $options['registration']; - } - } - } - - $response = $this->sendRequest('GET', 'activities/state', $requestCfg); - - if ($response->success) { - $doc = new State( - array( - 'id' => $id, - 'content' => $response->content, - 'activity' => $activity, - 'agent' => $agent, - ) - ); - - - if (isset($registration)) { - $doc->setRegistration($registration); - } - if (isset($response->httpResponse['headers']['lastModified'])) { - $doc->setTimestamp($response->httpResponse['headers']['lastModified']); - } - if (isset($response->httpResponse['headers']['contentType'])) { - $doc->setContentType($response->httpResponse['headers']['contentType']); - } - if (isset($response->httpResponse['headers']['etag'])) { - $doc->setEtag($response->httpResponse['headers']['etag']); - } - - $response->content = $doc; - } - - return $response; - } - - public function saveState($activity, $agent, $id, $content) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - - $contentType = 'application/octet-stream'; - - $requestCfg = array( - 'headers' => array( - 'Content-Type' => $contentType, - ), - 'params' => array( - 'activityId' => $activity->getId(), - 'agent' => json_encode($agent->asVersion($this->version)), - 'stateId' => $id, - ), - 'content' => $content, - ); - $registration = null; - if (func_num_args() > 4) { - $options = func_get_arg(4); - if (isset($options)) { - if (isset($options['contentType'])) { - $requestCfg['headers']['Content-Type'] = $contentType = $options['contentType']; - } - if (isset($options['etag'])) { - $requestCfg['headers']['If-Match'] = $options['etag']; - } - if (isset($options['registration'])) { - $requestCfg['params']['registration'] = $registration = $options['registration']; - } - } - } - - $response = $this->sendRequest('PUT', 'activities/state', $requestCfg); - - if ($response->success) { - $doc = new State( - array( - 'id' => $id, - 'content' => $content, - 'contentType' => $contentType, - 'etag' => sha1($content), - 'activity' => $activity, - 'agent' => $agent, - ) - ); - if (isset($registration)) { - $doc->setRegistration($registration); - } - if (isset($response->httpResponse['headers']['date'])) { - $doc->setTimestamp($response->httpResponse['headers']['date']); - } - - $response->content = $doc; - } - - return $response; - } - - // - // this is a separate private method because the implementation - // of deleteState and clearState are essentially identical but - // I didn't want to make it easy to call deleteState accidentally - // without an id therefore clearing all of the state when only - // one id was desired to be deleted, so clearState is an explicit - // separate method signature - // - // TODO: Etag? - private function _deleteState($activity, $agent, $id) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - - $requestCfg = array( - 'params' => array( - 'activityId' => $activity->getId(), - 'agent' => json_encode($agent->asVersion($this->version)), - ) - ); - if (isset($id)) { - $requestCfg['params']['stateId'] = $id; - } - - if (func_num_args() > 3) { - $options = func_get_arg(3); - if (isset($options)) { - if (isset($options['registration'])) { - $requestCfg['params']['registration'] = $options['registration']; - } - } - } - - $response = $this->sendRequest('DELETE', 'activities/state', $requestCfg); - - return $response; - } - - public function deleteState($activity, $agent, $id) { - return call_user_func_array(array($this, '_deleteState'), func_get_args()); - } - - public function clearState($activity, $agent) { - $args = array($activity, $agent, null); - - $numArgs = func_num_args(); - if ($numArgs > 2) { - $args = array_merge($args, array_slice(func_get_args(), 2)); - } - - return call_user_func_array(array($this, '_deleteState'), $args); - } - - public function retrieveActivityProfileIds($activity) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - - $requestCfg = array( - 'params' => array( - 'activityId' => $activity->getId() - ) - ); - if (func_num_args() > 1) { - $options = func_get_arg(1); - if (isset($options)) { - if (isset($options['since'])) { - $requestCfg['params']['since'] = $options['since']; - } - } - } - - $response = $this->sendRequest('GET', 'activities/profile', $requestCfg); - - if ($response->success) { - $response->content = json_decode($response->content); - } - - return $response; - } - - public function retrieveActivityProfile($activity, $id) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - $response = $this->sendRequest( - 'GET', - 'activities/profile', - array( - 'params' => array( - 'activityId' => $activity->getId(), - 'profileId' => $id, - ), - 'ignore404' => true, - ) - ); - - if ($response->success) { - $doc = new ActivityProfile( - array( - 'id' => $id, - 'content' => $response->content, - 'activity' => $activity, - ) - ); - if (isset($response->httpResponse['headers']['lastModified'])) { - $doc->setTimestamp($response->httpResponse['headers']['lastModified']); - } - if (isset($response->httpResponse['headers']['contentType'])) { - $doc->setContentType($response->httpResponse['headers']['contentType']); - } - if (isset($response->httpResponse['headers']['etag'])) { - $doc->setEtag($response->httpResponse['headers']['etag']); - } - - $response->content = $doc; - } - - return $response; - } - - public function saveActivityProfile($activity, $id, $content) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - - $contentType = 'application/octet-stream'; - - $requestCfg = array( - 'headers' => array( - 'Content-Type' => $contentType, - ), - 'params' => array( - 'activityId' => $activity->getId(), - 'profileId' => $id, - ), - 'content' => $content, - ); - if (func_num_args() > 3) { - $options = func_get_arg(3); - if (isset($options)) { - if (isset($options['contentType'])) { - $requestCfg['headers']['Content-Type'] = $contentType = $options['contentType']; - } - if (isset($options['etag'])) { - $requestCfg['headers']['If-Match'] = $options['etag']; - } - else { - $requestCfg['headers']['If-None-Match'] = '*'; - } - } - } - - $response = $this->sendRequest('PUT', 'activities/profile', $requestCfg); - - if ($response->success) { - $doc = new ActivityProfile( - array( - 'id' => $id, - 'content' => $content, - 'contentType' => $contentType, - 'etag' => sha1($content), - 'activity' => $activity, - ) - ); - if (isset($response->httpResponse['headers']['date'])) { - $doc->setTimestamp($response->httpResponse['headers']['date']); - } - - $response->content = $doc; - } - - return $response; - } - - // TODO: Etag? - public function deleteActivityProfile($activity, $id) { - if (! $activity instanceof Activity) { - $activity = new Activity($activity); - } - $response = $this->sendRequest( - 'DELETE', - 'activities/profile', - array( - 'params' => array( - 'activityId' => $activity->getId(), - 'profileId' => $id, - ), - ) - ); - - return $response; - } - - public function retrieveActivity($activityid) { - $headers = array('Accept-language: *'); - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $headers = array('Accept-language: ' . $_SERVER['HTTP_ACCEPT_LANGUAGE'] . ', *'); - } - - $response = $this->sendRequest( - 'GET', - 'activities', - array( - 'params' => array( - 'activityId' => $activityid, - ), - 'headers' => $headers - ) - ); - - if ($response->success) { - $response->content = new Activity(json_decode($response->content, true)); - } - - return $response; - } - - // TODO: groups? - public function retrieveAgentProfileIds($agent) { - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - - $requestCfg = array( - 'params' => array( - 'agent' => json_encode($agent->asVersion($this->version)) - ) - ); - if (func_num_args() > 1) { - $options = func_get_arg(1); - if (isset($options)) { - if (isset($options['since'])) { - $requestCfg['params']['since'] = $options['since']; - } - } - } - - $response = $this->sendRequest('GET', 'agents/profile', $requestCfg); - - if ($response->success) { - $response->content = json_decode($response->content); - } - - return $response; - } - - public function retrieveAgentProfile($agent, $id) { - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - $response = $this->sendRequest( - 'GET', - 'agents/profile', - array( - 'params' => array( - 'agent' => json_encode($agent->asVersion($this->version)), - 'profileId' => $id, - ), - 'ignore404' => true, - ) - ); - - if ($response->success) { - $doc = new AgentProfile( - array( - 'id' => $id, - 'content' => $response->content, - 'agent' => $agent, - ) - ); - if (isset($response->httpResponse['headers']['lastModified'])) { - $doc->setTimestamp($response->httpResponse['headers']['lastModified']); - } - if (isset($response->httpResponse['headers']['contentType'])) { - $doc->setContentType($response->httpResponse['headers']['contentType']); - } - if (isset($response->httpResponse['headers']['etag'])) { - $doc->setEtag($response->httpResponse['headers']['etag']); - } - - $response->content = $doc; - } - - return $response; - } - - public function saveAgentProfile($agent, $id, $content) { - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - - $contentType = 'application/octet-stream'; - - $requestCfg = array( - 'headers' => array( - 'Content-Type' => $contentType, - ), - 'params' => array( - 'agent' => json_encode($agent->asVersion($this->version)), - 'profileId' => $id, - ), - 'content' => $content, - ); - if (func_num_args() > 3) { - $options = func_get_arg(3); - if (isset($options)) { - if (isset($options['contentType'])) { - $requestCfg['headers']['Content-Type'] = $contentType = $options['contentType']; - } - if (isset($options['etag'])) { - $requestCfg['headers']['If-Match'] = $options['etag']; - } - else { - $requestCfg['headers']['If-None-Match'] = '*'; - } - } - } - - $response = $this->sendRequest('PUT', 'agents/profile', $requestCfg); - - if ($response->success) { - $doc = new AgentProfile( - array( - 'id' => $id, - 'content' => $content, - 'contentType' => $contentType, - 'etag' => sha1($content), - 'agent' => $agent, - ) - ); - if (isset($response->httpResponse['headers']['date'])) { - $doc->setTimestamp($response->httpResponse['headers']['date']); - } - - $response->content = $doc; - } - - return $response; - } - - // TODO: Etag? - public function deleteAgentProfile($agent, $id) { - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - $response = $this->sendRequest( - 'DELETE', - 'agents/profile', - array( - 'params' => array( - 'agent' => json_encode($agent->asVersion($this->version)), - 'profileId' => $id, - ), - ) - ); - - return $response; - } - - public function retrievePerson($agent) { - if (! $agent instanceof Agent) { - $agent = new Agent($agent); - } - $response = $this->sendRequest( - 'GET', - 'agents', - array( - 'params' => array( - 'agent' => json_encode($agent->asVersion($this->version)), - ) - ) - ); - - if ($response->success) { - $response->content = new Person(json_decode($response->content, true)); - } - - return $response; - } - - // FEATURE: check is URL - public function setEndpoint($value) { - if (substr($value, -1) != "/") { - $value .= "/"; - } - $this->endpoint = $value; - return $this; - } - public function getEndpoint() { return $this->endpoint; } - public function getEndpointServerRoot() { - $parsed = parse_url($this->endpoint); - - $root = $parsed['scheme'] . '://' . $parsed['host']; - if (isset($parsed['port'])) { - $root .= ":" . $parsed['port']; - } - - return $root; - } - - public function setVersion($value) { - if (! in_array($value, Version::supported(), true)) { - throw new \InvalidArgumentException("Unsupported version: $value"); - } - $this->version = $value; - return $this; - } - public function getVersion() { return $this->version; } - - public function setAuth() { - $_num_args = func_num_args(); - if ($_num_args == 1) { - $this->auth = func_get_arg(0); - } - elseif ($_num_args == 2) { - $this->auth = 'Basic ' . base64_encode(func_get_arg(0) . ':' . func_get_arg(1)); - } - else { - throw new \BadMethodCallException('setAuth requires 1 or 2 arguments'); - } - return $this; - } - public function getAuth() { return $this->auth; } - - public function setProxy($value) { - $this->proxy = $value; - return $this; - } - public function getProxy() { return $this->proxy; } - - public function setHeaders($value) { - $this->headers = $value; - return $this; - } - public function getHeaders() { return $this->headers; } -} - - diff --git a/cmi5PHP/src/Result.php b/cmi5PHP/src/Result.php deleted file mode 100755 index 2956b4b..0000000 --- a/cmi5PHP/src/Result.php +++ /dev/null @@ -1,82 +0,0 @@ -_fromArray($arg); - } - - if (! isset($this->extensions)) { - $this->setExtensions(array()); - } - } - - private function _asVersion(&$result, $version) { - // - // empty string is an invalid duration - // - if (isset($result['duration']) && $result['duration'] == '') { - unset($result['duration']); - } - } - - public function setScore($value) { - if (! $value instanceof Score && is_array($value)) { - $value = new Score($value); - } - - $this->score = $value; - - return $this; - } - public function getScore() { return $this->score; } - - public function setSuccess($value) { $this->success = (bool) $value; return $this; } - public function getSuccess() { return $this->success; } - public function setCompletion($value) { $this->completion = (bool) $value; return $this; } - public function getCompletion() { return $this->completion; } - public function setDuration($value) { $this->duration = $value; return $this; } - public function getDuration() { return $this->duration; } - public function setResponse($value) { $this->response = $value; return $this; } - public function getResponse() { return $this->response; } - - public function setExtensions($value) { - if (! $value instanceof Extensions) { - $value = new Extensions($value); - } - - $this->extensions = $value; - - return $this; - } - public function getExtensions() { return $this->extensions; } -} diff --git a/cmi5PHP/src/Score.php b/cmi5PHP/src/Score.php deleted file mode 100755 index 9783aeb..0000000 --- a/cmi5PHP/src/Score.php +++ /dev/null @@ -1,196 +0,0 @@ - $aRawValue, - 'min' => $aMin, - 'max' => $aMax, - 'scaled' => $aScaledValue - ]; - } - $this->_fromArray($aRawValue); - } - - /** - * @param float $value - * @throws InvalidArgumentException - * @return self - */ - public function setScaled($value) { - if ($value < static::SCALE_MIN) { - throw new InvalidArgumentException( - sprintf( "Value must be greater than or equal to %s [%s]", static::SCALE_MIN, $value) - ); - } - if ($value > static::SCALE_MAX) { - throw new InvalidArgumentException( - sprintf( "Value must be less than or equal to %s [%s]", static::SCALE_MAX, $value) - ); - } - $this->scaled = (float) $value; - return $this; - } - - /** - * @return null|float - */ - public function getScaled() { - return $this->scaled; - } - - /** - * @param float $value - * @throws InvalidArgumentException - * @return self - */ - public function setRaw($value) { - if (isset($this->min) && $value < $this->min) { - throw new InvalidArgumentException( - sprintf("Value must be greater than or equal to 'min' (%s) [%s]", $this->min, $value) - ); - } - if (isset($this->max) && $value > $this->max) { - throw new InvalidArgumentException( - sprintf("Value must be less than or equal to 'max' (%s) [%s]", $this->max, $value) - ); - } - - $this->raw = (float) $value; - return $this; - } - - /** - * @return null|float - */ - public function getRaw() { - return $this->raw; - } - - /** - * @param float $value - * @throws InvalidArgumentException - * @return self - */ - public function setMin($value) { - if (isset($this->raw) && $value > $this->raw) { - throw new InvalidArgumentException( - sprintf("Value must be less than or equal to 'raw' (%s) [%s]", $this->raw, $value) - ); - } - if (isset($this->max) && $value >= $this->max) { - throw new InvalidArgumentException( - sprintf("Value must be less than 'max' (%s) [%s]", $this->max, $value) - ); - } - $this->min = (float) $value; - return $this; - } - - /** - * @return null|float - */ - public function getMin() { - return $this->min; - } - - /** - * @param float $value - * @throws InvalidArgumentException - * @return self - */ - public function setMax($value) { - if (isset($this->raw) && $value < $this->raw) { - throw new InvalidArgumentException( - sprintf("Value must be greater than or equal to 'raw' (%s) [%s]", $this->raw, $value) - ); - } - if (isset($this->min) && $value <= $this->min) { - throw new InvalidArgumentException( - sprintf("Value must be greater than 'min' (%s) [%s]", $this->min, $value) - ); - } - $this->max = (float) $value; - return $this; - } - - /** - * @return null|float - */ - public function getMax() { - return $this->max; - } -} diff --git a/cmi5PHP/src/SignatureComparisonTrait.php b/cmi5PHP/src/SignatureComparisonTrait.php deleted file mode 100755 index 4ce7f7c..0000000 --- a/cmi5PHP/src/SignatureComparisonTrait.php +++ /dev/null @@ -1,112 +0,0 @@ - $value) { - // - // skip properties that start with an underscore to allow - // storing information that isn't included in statement - // structure etc. (see Attachment.content for example) - // - // also allow a class to specify a list of additional - // properties that should not be included in verification - // - if (strpos($property, '_') === 0 || $property === 'objectType' || in_array($property, $skip)) { - continue; - } - - $result = self::doMatch($value, $fromSig->$property, $property); - if (! $result['success']) { - return $result; - } - } - - return array( - 'success' => true, - 'reason' => null - ); - } - - private static function doMatch($a, $b, $description) { - $result = array( - 'success' => false, - 'reason' => null - ); - if ((isset($a) && ! isset($b)) || (isset($b) && ! isset($a))) { - $result['reason'] = "Comparison of $description failed: value not present in this or signature"; - return $result; - } - - if (is_object($a) && ! ($b instanceof $a)) { - $result['reason'] = "Comparison of $description failed: not a " . get_class($a) . " value"; - return $result; - } - - if ($a instanceof ComparableInterface) { - $comparison = $a->compareWithSignature($b); - if (! $comparison['success']) { - $result['reason'] = "Comparison of $description failed: " . $comparison['reason']; - return $result; - } - } - else { - if (is_array($a)) { - if (! is_array($b)) { - $result['reason'] = "Comparison of $description failed: not an array in signature"; - return $result; - } - - if (count($a) !== count($b)) { - $result['reason'] = "Comparison of $description failed: array lengths differ"; - return $result; - } - - for ($i = 0; $i < count($a); $i++) { - $comparison = self::doMatch($a[$i], $b[$i], $description . "[$i]"); - if (! $comparison['success']) { - return $comparison; - } - } - } - else { - if ($a != $b) { - $result['reason'] = "Comparison of $description failed: value is not the same"; - return $result; - } - } - } - - $result['success'] = true; - return $result; - } -} diff --git a/cmi5PHP/src/State.php b/cmi5PHP/src/State.php deleted file mode 100755 index 6a9e71a..0000000 --- a/cmi5PHP/src/State.php +++ /dev/null @@ -1,63 +0,0 @@ -activity = $value; - - return $this; - } - public function getActivity() { return $this->activity; } - - public function setAgent($value) { - if ((! $value instanceof Agent && ! $value instanceof Group) && is_array($value)) { - if (isset($value['objectType']) && $value['objectType'] === 'Group') { - $value = new Group($value); - } - else { - $value = new Agent($value); - } - } - - $this->agent = $value; - - return $this; - } - public function getAgent() { return $this->agent; } - - public function setRegistration($value) { - if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { - throw new \InvalidArgumentException('arg1 must be a UUID'); - } - - $this->registration = $value; - - return $this; - } - public function getRegistration() { return $this->registration; } -} diff --git a/cmi5PHP/src/Statement.php b/cmi5PHP/src/Statement.php deleted file mode 100755 index ef7e86e..0000000 --- a/cmi5PHP/src/Statement.php +++ /dev/null @@ -1,431 +0,0 @@ -setObject($arg['object']); - } - } - if (! isset($this->attachments)) { - $this->setAttachments(array()); - } - } - - public function stamp() { - $this->setId(Util::getUUID()); - $this->setTimestamp(Util::getTimestamp()); - - return $this; - } - - public function compareWithSignature($fromSig) { - foreach (array('id', 'attachments') as $property) { - if (! isset($this->$property) && ! isset($fromSig->$property)) { - continue; - } - if (isset($this->$property) && ! isset($fromSig->$property)) { - return array('success' => false, 'reason' => "Comparison of $property failed: value not in signature"); - } - if (isset($fromSig->$property) && ! isset($this->$property)) { - return array('success' => false, 'reason' => "Comparison of $property failed: value not in this"); - } - } - if (isset($this->id)) { - if ($this->id !== $fromSig->id) { - return array('success' => false, 'reason' => 'Comparison of id failed: value is not the same'); - } - } - if (isset($this->attachments)) { - if (count($this->attachments) !== count($fromSig->attachments)) { - return array('success' => false, 'reason' => 'Comparison of attachments list failed: array lengths differ'); - } - - for ($i = 0; $i < count($this->attachments); $i++) { - $comparison = $this->attachments[$i]->compareWithSignature($fromSig->attachments[$i]); - if (! $comparison['success']) { - return array('success' => false, 'reason' => "Comparison of attachment $i failed: " . $comparison['reason']); - } - } - } - - return parent::compareWithSignature($fromSig); - } - - private function serializeForSignature($version) { - if (! isset($this->actor)) { - throw new \InvalidArgumentException('actor must be present in signed statement'); - } - if (! isset($this->verb)) { - throw new \InvalidArgumentException('verb must be present in signed statement'); - } - if (! isset($this->target)) { - throw new \InvalidArgumentException('object must be present in signed statement'); - } - - $result = $this->asVersion($version); - $result['version'] = $version; - - foreach (['authority', 'stored'] as $prop) { - unset($result[$prop]); - } - - return $result; - } - - public function sign($privateKeyFile, $privateKeyPass, $options = array()) { - if (! isset($options['version'])) { - $options['version'] = Version::latest(); - } - if (! isset($options['algorithm'])) { - $options['algorithm'] = 'RS256'; - } - if (! isset($options['display'])) { - $options['display'] = array( - 'en-US' => 'Statement Signature' - ); - } - if (! isset($options['signatureHeader'])) { - $options['signatureHeader'] = array(); - } - - if (! in_array($options['algorithm'], array('RS256', 'RS384', 'RS512'), true)) { - throw new \InvalidArgumentException("Invalid signing algorithm: '" . $options['algorithm'] . "'"); - } - - // serialize the statement - $serialization = $this->serializeForSignature($options['version']); - - // - // commands to generate required files: - // openssl genrsa -aes256 -out private.key 2048 - // openssl req -new -x509 -key private.key -out cacert.pem -days 1095 - // - $privateKey = openssl_pkey_get_private($privateKeyFile, $privateKeyPass); - if (! $privateKey) { - throw new \Exception('Unable to get private key: ' . openssl_error_string()); - } - - $jwsHeader = array( - 'alg' => $options['algorithm'], - 'cmi5PHP' => true - ); - if (isset($options['signatureHeader'])) { - array_replace($jwsHeader, $options['signatureHeader']); - } - - if (isset($options['x5c'])) { - $jwsHeader['x5c'] = array(); - - if (! is_array($options['x5c'])) { - $options['x5c'] = array($options['x5c']); - } - - foreach ($options['x5c'] as $cert) { - $cert = openssl_x509_read($cert); - if (! $cert) { - throw new \Exception('Unable to read certificate for x5c inclusion: ' . openssl_error_string()); - } - - if (! openssl_x509_export($cert, $x5c, true)) { - throw new \Exception('Unable to export certificate for x5c inclusion: ' . openssl_error_string()); - } - - $x5c = preg_replace( - array( - "/^-----BEGIN CERTIFICATE-----\r?\n/", - "/-----END CERTIFICATE-----\r?\n$/", - "/\r?\n/" - ), - '', - $x5c - ); - - array_push($jwsHeader['x5c'], $x5c); - } - } - $jws = new JWS($jwsHeader); - - $jws->setPayload($serialization, false); - $jws->sign($privateKey); - - $attachment = array( - 'contentType' => self::SIGNATURE_CONTENT_TYPE, - 'usageType' => self::SIGNATURE_USAGE_TYPE, - 'content' => $jws->getTokenString(), - 'display' => $options['display'], - ); - if (isset($options['description'])) { - $attachment['description'] = $options['description']; - } - $this->addAttachment($attachment); - } - - public function verify($options = array()) { - if (! isset($options['version'])) { - $options['version'] = Version::latest(); - } - - $signatureAttachment = null; - $signatureIndex = 0; - - foreach ($this->getAttachments() as $attachment) { - if ($attachment->getUsageType() === self::SIGNATURE_USAGE_TYPE) { - $signatureAttachment = $attachment; - break; - } - $signatureIndex++; - } - if ($signatureAttachment === null) { - return array('success' => false, 'reason' => "Unable to locate signature attachment (usage type)"); - } - - try { - $jws = JWS::load($signatureAttachment->getContent()); - } - catch (\InvalidArgumentException $e) { - return array('success' => false, 'reason' => 'Failed to load JWS: ' . $e); - } - - $header = $jws->getHeader(); - - // - // there is a JWS spec security issue with allowing non-RS algorithms - // to be specified and it is against the cmi5 spec anyways so we - // want to fail hard on non-RS algorithms - // - if (! in_array($header['alg'], array('RS256', 'RS384', 'RS512'), true)) { - throw new \InvalidArgumentException("Refusing to verify signature: Invalid signing algorithm ('" . $options['algorithm'] . "')"); - } - - if (isset($options['publicKey'])) { - $publicKeyFile = $options['publicKey']; - } - elseif (isset($header['x5c'])) { - $cert = "-----BEGIN CERTIFICATE-----\r\n" . chunk_split($header['x5c'][0], 64, "\r\n") . "-----END CERTIFICATE-----\r\n"; - $cert = openssl_x509_read($cert); - if (! $cert) { - return array('success' => false, 'reason' => 'failed to read cert in x5c: ' . openssl_error_string()); - } - $publicKeyFile = openssl_pkey_get_public($cert); - if (! $publicKeyFile) { - return array('success' => false, 'reason' => 'x5c failed to provide public key: ' . openssl_error_string()); - } - } - else { - return array('success' => false, 'reason' => 'No public key found or provided for verification'); - } - - if (! $jws->verify($publicKeyFile)) { - return array('success' => false, 'reason' => 'Failed to verify signature'); - } - - $payload = $jws->getPayload(); - - // - // serializing this statement as if it was going to be - // made into a signature should provide us with what we - // can expect in the payload, if the two don't match then - // the signature isn't valid, it also gives us a clone - // that we can then manipulate without affecting the - // original instance - // - // use the version from the payload as it indicates the - // version in use when the statement was serialized to - // begin with - // - $version = $payload['version'] ? $payload['version'] : Version::latest(); - $serialization = $this->serializeForSignature($version); - - // - // remove the signature attachment before comparing the - // serializations, if it was the only attachment and the - // signature doesn't include the 'attachments' property - // then unset it as well - // - unset($serialization['attachments'][$signatureIndex]); - if (count($serialization['attachments']) === 0 && ! isset($payload['attachments'])) { - unset($serialization['attachments']); - } - - // - // authority and stored are most often populated by the LRS, - // and presumably for signature purposes are *never* included - // in the signature so we are safe to remove them here - // - unset($serialization['stored']); - unset($serialization['authority']); - - // - // the payload 'version' is instructive of how to serialize the - // statement for comparison, that 'version' is not required and - // when not set we need to remove the 'version' in the serialization - // which will be the current latest supported by the library - // which shouldn't be compared against what is in the signature - // - if (! isset($payload['version'])) { - unset($serialization['version']); - } - - // - // a statement can be signed without having first provided an - // id, in that case the id is set by the receiving LRS, so if - // the serialization has one, presumably from retrieval from - // an LRS, remove it so that it is not compared - // - // if the statement did provide an id before signing then the - // LRS should have maintained that id, so they can be compared - // - if (! isset($payload['id'])) { - unset($serialization['id']); - } - - // - // the same applies to timestamp - // - if (! isset($payload['timestamp'])) { - unset($serialization['timestamp']); - } - - // - // now we can construct an object from both the payload and the - // serialization of this instance and compare the two for a match - // in meaning - // - $fromSerialization = new self($serialization); - $comparison = $fromSerialization->compareWithSignature(new self($payload)); - if (! $comparison['success']) { - return array('success' => false, 'reason' => 'Statement to signature comparison failed: ' . $comparison['reason']); - } - - return array('success' => true, 'jws' => $jws); - } - - public function setId($value) { - if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { - throw new \InvalidArgumentException('arg1 must be a UUID "' . $value . '"'); - } - $this->id = $value; - return $this; - } - public function getId() { return $this->id; } - public function hasId() { return isset($this->id); } - - public function setStored($value) { - if (isset($value)) { - if ($value instanceof \DateTime) { - // Use format('c') instead of format(\DateTime::ISO8601) due to bug in format(\DateTime::ISO8601) that generates an invalid timestamp. - $value = $value->format('c'); - } - elseif (is_string($value)) { - $value = $value; - } - else { - throw new \InvalidArgumentException('type of arg1 must be string or DateTime'); - } - } - - $this->stored = $value; - - return $this; - } - public function getStored() { return $this->stored; } - - public function setAuthority($value) { - if (! $value instanceof Agent && is_array($value)) { - $value = new Agent($value); - } - - $this->authority = $value; - - return $this; - } - public function getAuthority() { return $this->authority; } - - public function setVersion($value) { $this->version = $value; return $this; } - public function getVersion() { return $this->version; } - - public function setAttachments($value) { - foreach ($value as $k => $v) { - if (! $value[$k] instanceof Attachment) { - $value[$k] = new Attachment($value[$k]); - } - } - - $this->attachments = $value; - - return $this; - } - public function getAttachments() { return $this->attachments; } - public function hasAttachments() { return count($this->attachments) > 0; } - public function hasAttachmentsWithContent() { - if (! $this->hasAttachments()) { - return false; - } - - foreach ($this->attachments as $attachment) { - if ($attachment->hasContent()) { - return true; - } - } - - return false; - } - public function addAttachment($value) { - if (! $value instanceof Attachment) { - $value = new Attachment($value); - } - - array_push($this->attachments, $value); - - return $this; - } -} diff --git a/cmi5PHP/src/StatementBase.php b/cmi5PHP/src/StatementBase.php deleted file mode 100755 index bd65e4c..0000000 --- a/cmi5PHP/src/StatementBase.php +++ /dev/null @@ -1,218 +0,0 @@ -_fromArray($arg); - - // - // 'object' isn't in the list of properties so ._fromArray doesn't - // pick it up correctly, but 'target' and 'object' shouldn't be in - // the args at the same time, so handle 'object' here - // - if (isset($arg['object'])) { - $this->setObject($arg['object']); - } - } - } - - private function _asVersion(&$result, $version) { - if (isset($result['target'])) { - $result['object'] = $result['target']; - unset($result['target']); - } - } - - public function compareWithSignature($fromSig) { - foreach (array('actor', 'verb', 'target', 'context', 'result') as $property) { - if (! isset($this->$property) && ! isset($fromSig->$property)) { - continue; - } - if (isset($this->$property) && ! isset($fromSig->$property)) { - return array('success' => false, 'reason' => "Comparison of $property failed: value not in signature"); - } - if (isset($fromSig->$property) && ! isset($this->$property)) { - return array('success' => false, 'reason' => "Comparison of $property failed: value not in this"); - } - - $result = $this->$property->compareWithSignature($fromSig->$property); - if (! $result['success']) { - return array('success' => false, 'reason' => "Comparison of $property failed: " . $result['reason']); - } - } - - if (isset($this->timestamp) || isset($fromSig->timestamp)) { - if (isset($this->timestamp) && ! isset($fromSig->timestamp)) { - return array('success' => false, 'reason' => 'Comparison of timestamp failed: value not in signature'); - } - if (isset($fromSig->timestamp) && ! isset($this->timestamp)) { - return array('success' => false, 'reason' => 'Comparison of timestamp failed: value not in this'); - } - - $a = new \DateTime ($this->timestamp); - $b = new \DateTime ($fromSig->timestamp); - - if ($a != $b) { - return array('success' => false, 'reason' => 'Comparison of timestamp failed: value is not the same'); - } - - // - // DateTime's diff doesn't take into account subsecond precision - // even though it can store it, so manually check that - // - if ($a->format('u') !== $b->format('u')) { - return array('success' => false, 'reason' => 'Comparison of timestamp failed: value is not the same'); - } - } - - return array('success' => true, 'reason' => null); - } - - public function setActor($value) { - if ((! $value instanceof Agent && ! $value instanceof Group) && is_array($value)) { - if (isset($value['objectType']) && $value['objectType'] === 'Group') { - $value = new Group($value); - } - else { - $value = new Agent($value); - } - } - - $this->actor = $value; - - return $this; - } - public function getActor() { return $this->actor; } - - public function setVerb($value) { - if (! $value instanceof Verb) { - $value = new Verb($value); - } - - $this->verb = $value; - - return $this; - } - public function getVerb() { return $this->verb; } - - public function setTarget($value) { - if (! $value instanceof StatementTargetInterface && is_array($value)) { - if (isset($value['objectType'])) { - if ($value['objectType'] === 'Activity') { - $value = new Activity($value); - } - elseif ($value['objectType'] === 'Agent') { - $value = new Agent($value); - } - elseif ($value['objectType'] === 'Group') { - $value = new Group($value); - } - elseif ($value['objectType'] === 'StatementRef') { - $value = new StatementRef($value); - } - elseif ($value['objectType'] === 'SubStatement') { - $value = new SubStatement($value); - } - else { - throw new \InvalidArgumentException('arg1 must implement the StatementTargetInterface objectType not recognized:' . $value['objectType']); - } - } - else { - $value = new Activity($value); - } - } - - $this->target = $value; - - return $this; - } - public function getTarget() { return $this->target; } - - // sugar methods - public function setObject($value) { return $this->setTarget($value); } - public function getObject() { return $this->getTarget(); } - - public function setResult($value) { - if (! $value instanceof Result && is_array($value)) { - $value = new Result($value); - } - - $this->result = $value; - - return $this; - } - public function getResult() { return $this->result; } - - public function setContext($value) { - if (! $value instanceof Context && is_array($value)) { - $value = new Context($value); - } - - $this->context = $value; - - return $this; - } - public function getContext() { return $this->context; } - - public function setTimestamp($value) { - if (isset($value)) { - if ($value instanceof \DateTime) { - // Use format('c') instead of format(\DateTime::ISO8601) due to bug in format(\DateTime::ISO8601) that generates an invalid timestamp. - $value = $value->format('c'); - } - elseif (is_string($value)) { - $value = $value; - } - else { - throw new \InvalidArgumentException('type of arg1 must be string or DateTime'); - } - } - - $this->timestamp = $value; - - return $this; - } - public function getTimestamp() { return $this->timestamp; } -} diff --git a/cmi5PHP/src/StatementRef.php b/cmi5PHP/src/StatementRef.php deleted file mode 100755 index 7d68918..0000000 --- a/cmi5PHP/src/StatementRef.php +++ /dev/null @@ -1,48 +0,0 @@ -_fromArray($arg); - } - } - - public function getObjectType() { return $this->objectType; } - - public function setId($value) { - if (isset($value) && ! preg_match(Util::UUID_REGEX, $value)) { - throw new InvalidArgumentException('arg1 must be a UUID'); - } - $this->id = $value; - return $this; - } - public function getId() { return $this->id; } -} diff --git a/cmi5PHP/src/StatementTargetInterface.php b/cmi5PHP/src/StatementTargetInterface.php deleted file mode 100755 index bc68cb9..0000000 --- a/cmi5PHP/src/StatementTargetInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -_fromArray($arg); - } - } - - public function setStatements($value) { - foreach ($value as $k => $v) { - if (! $value[$k] instanceof Statement) { - $value[$k] = new Statement($v); - } - } - - $this->statements = $value; - - return $this; - } - public function getStatements() { return $this->statements; } - public function setMore($value) { $this->more = $value; return $this; } - public function getMore() { return $this->more; } -} diff --git a/cmi5PHP/src/SubStatement.php b/cmi5PHP/src/SubStatement.php deleted file mode 100755 index 8e7f16f..0000000 --- a/cmi5PHP/src/SubStatement.php +++ /dev/null @@ -1,25 +0,0 @@ -objectType; } -} diff --git a/cmi5PHP/src/Util.php b/cmi5PHP/src/Util.php deleted file mode 100755 index 17433fc..0000000 --- a/cmi5PHP/src/Util.php +++ /dev/null @@ -1,82 +0,0 @@ -> 4; - $time_hi_and_version = $time_hi_and_version | 0x4000; - - /** - * Set the two most significant bits (bits 6 and 7) of the - * clock_seq_hi_and_reserved to zero and one, respectively. - */ - $clock_seq_hi_and_reserved = hexdec($clock_seq_hi_and_reserved); - $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved >> 2; - $clock_seq_hi_and_reserved = $clock_seq_hi_and_reserved | 0x8000; - - return sprintf( - '%08s-%04s-%04x-%04x-%012s', - $time_low, - $time_mid, - $time_hi_and_version, - $clock_seq_hi_and_reserved, - $node - ); - } - - // - // Returns the current date+time in string format with - // sub-second precision - // - // Based on code from - // http://stackoverflow.com/a/4414060/1464957 - public static function getTimestamp() { - $time = microtime(true); - $microseconds = sprintf('%06d', ($time - floor($time)) * 1000000); - $millseconds = round($microseconds, -3)/1000; - $millsecondsStr = str_pad($millseconds, 3, '0', STR_PAD_LEFT); - $date = (new \DateTime(null, new \DateTimeZone("UTC")))->format('c'); - - $position = strrpos($date, '+'); - $date = substr($date,0,$position).'.'.$millsecondsStr.substr($date,$position); - - return $date; - } -} diff --git a/cmi5PHP/src/Verb.php b/cmi5PHP/src/Verb.php deleted file mode 100755 index f3ce8c2..0000000 --- a/cmi5PHP/src/Verb.php +++ /dev/null @@ -1,66 +0,0 @@ -_fromArray($arg); - } - - if (! isset($this->display)) { - $this->setDisplay(array()); - } - } - - // FEATURE: check IRI? - public function setId($value) { $this->id = $value; return $this; } - public function getId() { return $this->id; } - - public function setDisplay($value) { - if (! $value instanceof LanguageMap) { - $value = new LanguageMap($value); - } - - $this->display = $value; - - return $this; - } - public function getDisplay() { return $this->display; } - - static public function Voided() { - return new self( - [ - 'id' => 'http://adlnet.gov/expapi/verbs/voided', - 'display' => [ - 'en-US' => 'voided' - ] - ] - ); - } -} diff --git a/cmi5PHP/src/Version.php b/cmi5PHP/src/Version.php deleted file mode 100755 index 88814c0..0000000 --- a/cmi5PHP/src/Version.php +++ /dev/null @@ -1,159 +0,0 @@ - bool */ - private static $supported = [ - self::V101 => true, - self::V100 => true, - self::V095 => false - ]; - - /** @var self[] */ - private static $instances = []; - - /** @var string */ - private $value; - - /** - * Constructor - * - * @param string $aValue a version value - * @throws InvalidArgumentException when the value is not recognized - */ - private function __construct($aValue) { - if (!isset(static::$supported[$aValue])) { - throw new InvalidArgumentException("Invalid version [$aValue]"); - } - $this->value = $aValue; - } - - /** - * Does the value match? - * - * @param string $aValue a value to check - * @return bool - */ - public function hasValue($aValue) { - return $this->value === (string) $aValue; - } - - /** - * Is the value contained in a list of versions? - * - * @param string[] $aValueList a list of values to check - * @return bool - */ - public function hasAnyValue(array $aValueList) { - return in_array($this->value, $aValueList); - } - - /** - * Is this the latest version? - * - * @return bool - */ - public function isLatest() { - return $this->value === static::latest(); - } - - /** - * Is this a supported version? - * - * @return bool - */ - public function isSupported() { - return static::$supported[$this->value]; - } - - /** - * Convert the object to a string - * - * @return string - */ - public function __toString() { - return $this->value; - } - - /** - * Factory constructor - * - * @example $version = Version::V101(); - * @param string $aValue the called method as a version value - * @param array $arguments unused arguments passed to the method - * @return self - */ - public static function __callStatic($aValue, array $arguments = []) { - $aValue = trim(preg_replace("#v(\d)(95|\d)(\d)?#i", '$1.$2.$3', $aValue), "."); - if (!isset(static::$instances[$aValue])) { - static::$instances[$aValue] = new static($aValue); - } - return static::$instances[$aValue]; - } - - /** - * Convert a string into a Version instance - * - * @param string $aValue a version value - * @return self - */ - public static function fromString($aValue) { - $aValue = str_replace(".", "", $aValue); - return static::{"V$aValue"}(); - } - - /** - * List all supported versions - * - * @return string[] - */ - public static function supported() { - return array_keys(array_filter(static::$supported, function($supported) { - return $supported === true; - })); - } - - /** - * Retrieve the most recent version - * - * @return string - */ - public static function latest() { - return array_keys(static::$supported)[0]; - } -} diff --git a/cmi5PHP/src/VersionableInterface.php b/cmi5PHP/src/VersionableInterface.php deleted file mode 100755 index 0e31627..0000000 --- a/cmi5PHP/src/VersionableInterface.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ - function createAUs($auStatements) - { - //Needs to return our new AU objects - $newAus = array(); - - //for ($i = 0; $i < count($auStatements); $i++) { - foreach ($auStatements as $int => $info) { - - //The aus come back decoded from DB nestled in an array, so they are the first key, - //which is '0' - $statement = $info[0]; - - //Maybe just combine 45 and 48? TODO - $au = new au($statement); - - //assign the newly created au to the return array - $newAus[] = $au; - } - - //Return our new list of AU! - return $newAus; - } - - - /** - * Takes a list of AUs and record and saves to DB - * @param mixed $auObjectArray - * @return array - */ - function saveAUs($auObjectArray) - { - //Add userid to the record - global $DB, $USER; - //$record; - $table = "cmi5launch_aus"; - - //Lets make an array to hold the created ids - $auIDs = array(); - - //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) { - //Make a newRecord to save - $newRecord = new stdClass(); - $newRecord->userid = $USER->id; - $newRecord->auid = $auObject->id; - $newRecord->launchmethod = $auObject->launchMethod; - $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); - $newRecord->title = $title[0]['text']; - $newRecord->moveon = $auObject->moveOn; - $newRecord->auindex = $auObject->auIndex; - $newRecord->parents = json_encode($auObject->parents, true); - $newRecord->objectives = $auObject->objectives; - $desc = json_decode(json_encode($auObject->description), true); - $newRecord->description = $desc[0]['text']; - $newRecord->activitytype = $auObject->activityType; - $newRecord->masteryscore = $auObject->masteryscore; - $newRecord->completed = $auObject->completed; - $newRecord->passed = $auObject->passed; - $newRecord->inprogress = $auObject->inprogress; - $newRecord->noattempt = $auObject->noattempt; - $newRecord->satisfied = $auObject->satisfied; - - //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; - } - - return $auIDs; - } - - /** - * Retrieves a list of AU's from DB and makes them AU objects - * @param mixed $auID - * @return au - */ - function getFromDB($auID) - { - global $DB; - - $check = $DB->record_exists( 'cmi5launch_aus', ['id' => $auID], '*', IGNORE_MISSING); - - //If check is negative, the record doesnot exist. It should so throw error - if(!$check){ - - echo "

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

"; - echo "
";
-		   var_dump($auID);
-			echo "
"; - } - else{ - $auItem = $DB->get_record('cmi5launch_aus', array('id' => $auID)); - - $au = new au($auItem); - } - - //Return our new list of AU! - return $au; - } - -} -?> \ No newline at end of file diff --git a/cmi5PHP/src/cmi5Connector.php b/cmi5PHP/src/cmi5Connector.php deleted file mode 100755 index 7b6e58d..0000000 --- a/cmi5PHP/src/cmi5Connector.php +++ /dev/null @@ -1,471 +0,0 @@ -"; - var_dump($url); - echo "
"; - - //the body of the request must be made as array first - $data = $file; - - //sends the stream to the specified URL - $result = $this->sendRequest($data, $url, $token); - - if ($result === FALSE) { - - if ($CFG->debugdeveloper) { - echo "Something went wrong sending the request"; - echo "
"; - echo "Response from CMI5 player: "; - var_dump($result); - echo "
"; - } - } else { - - //Return an array with tenant name and info - return $result; - } - } - - ////// - //Function to create a tenant - // @param $urlToSend - URL retrieved from user in URL textbox - // @param $user - username - // @param $pass - password - // @param $newTenantName - the name the new tenant will be, retreived from Tenant Name textbox - ///// - public function createTenant($urlToSend, $user, $pass, $newTenantName){ - - global $CFG; - //retrieve and assign params - $url = $urlToSend; - $username = $user; - $password = $pass; - $tenant = $newTenantName; - - //the body of the request must be made as array first - $data = array( - 'code' => $tenant); - - //sends the stream to the specified URL - $result = $this->sendRequest($data, $url, $username, $password); - - if ($result === FALSE){ - if ($CFG->debugdeveloper) { - echo "Something went wrong!"; - echo "
"; - var_dump($_SESSION); - } - } - - //decode returned response into array - $returnedInfo = json_decode($result, true); - - //Return an array with tenant name and info - return $returnedInfo; - } - - - //Function to retreive registration from cmi5 player. This way uses - //the registration id - //Registration is "code" in returned json body - //@param $registration - registration UUID - // @param $id - launch id - function retrieveRegistrationGet($registration, $id) { - - $settings = cmi5launch_settings($id); - - $token = $settings['cmi5launchtenanttoken']; - $playerUrl = $settings['cmi5launchplayerurl']; - - global $CFG; - - //Build URL for launch URL request - //Okay it looks like the reurnurk is same level as - $url = $playerUrl . "/api/v1/registration/" . $registration ; - - /////////// - $options = array( - 'http' => array( - 'method' => 'GET', - 'header' => array('Authorization: Bearer ' . $token, - "Content-Type: application/json\r\n" . - "Accept: application/json\r\n") - ) - ); - //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) - $result = file_get_contents( $url, false, $context ); - - if ($result === FALSE){ - - if ($CFG->debugdeveloper) { - echo "Something went wrong!"; - echo "
"; - var_dump($_SESSION); - } - } - else{ - - $registrationInfo = json_decode($result, true); - //The returned 'registration info' is a large json - //code is the registration id we want - $registration = $registrationInfo["code"]; - - //Why would I return the code when the code is needed to fwetch? -// return $registration; - - return $registrationInfo; //much better! - } - } - - - //Function to retreive registration from cmi5 player. This way uses - //the course id and actor name - //As this is a POST request it returns a new code everytime it is called - //Registration is "code" in returned json body - //@param $courseID - // @param $id - function retrieveRegistrationPost($courseId, $id){ - - global $USER; - - $settings = cmi5launch_settings($id); - - //Switch this out for user not tenant -// $actor = $settings['cmi5launchtenantname']; - $actor = $USER->username; - $token = $settings['cmi5launchtenanttoken']; - $playerUrl = $settings['cmi5launchplayerurl']; - $homepage = $settings['cmi5launchcustomacchp']; - global $CFG; - - //Build URL for launch URL request - //Okay it looks like the return url is same level as - $url = $playerUrl . "/api/v1/registration" ; - - //the body of the request must be made as array first - $data = array( - 'courseId' => $courseId, - 'actor' => array( - 'account' => array( - "homePage" => $homepage, - "name" => $actor - ) - ) - ); - - $options = array( - 'http' => array( - 'method' => 'POST', - 'header' => array('Authorization: Bearer ' . $token, - "Content-Type: application/json\r\n" . - "Accept: application/json\r\n"), - 'content' => json_encode($data) - ) - ); - - //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) - $result = file_get_contents( $url, false, $context ); - - - if ($result === FALSE){ - - if ($CFG->debugdeveloper) { - echo "Something went wrong!"; - echo "
"; - var_dump($_SESSION); - } - } - else{ - - $registrationInfo = json_decode($result, true); - - //The returned 'registration info' is a large json - //code is the registration id we want - $registration = $registrationInfo["code"]; - - return $registration; - } - } - - //@param $urlToSend - URL to send request to - // @param $user - username - // @param $pass - password - // @param $audience - the name the of the audience using the token, - // @param #tenantId - the id of the tenant - function retrieveToken($urlToSend, $user, $pass, $audience, $tenantId){ - - global $CFG; - //retrieve and assign params - $url = $urlToSend; - $username = $user; - $password = $pass; - $tokenUser = $audience; - $id = $tenantId; - - //the body of the request must be made as array first - $data = array( - 'tenantId' => $id, - 'audience' => $tokenUser - ); - - //sends the stream to the specified URL - $token = $this->sendRequest($data, $url, $username, $password); - - if ($token === FALSE){ - - if ($CFG->debugdeveloper) { - echo "Something went wrong!"; - echo "
"; - var_dump($_SESSION); - } - } - else{ - return $token; - } - - } - - ///Function to retrieve a launch URL for an AU - //@param $id -Actor id to find correct info for url request - //@param $auID -AU id to pass to cmi5 for url request - //@return $url - The launch URL returned from cmi5 player - //////// - public function retrieveUrl($id, $auIndex){ - //TODO, this needs to be changed to have an if its one old call, if its not, new call - //MB - global $DB, $USER; - - //Retrieve actor record, this enables correct actor info for URL storage - $record = $DB->get_record("cmi5launch", array('id' => $id)); - - //Here's the trouble, still getting reggistration id from master RECORD - $settings = cmi5launch_settings($id); - - $usersCourse = $DB->get_record('cmi5launch_course', ['courseid' => $record->courseid, 'userid' => $USER->id]); - - $registrationID = $usersCourse->registrationid; - - $homepage = $settings['cmi5launchcustomacchp']; - $returnUrl =$usersCourse->returnurl; - //MB - //We need to change this to actor name, not tenant - $actor= $USER->username; - //$actor= $settings['cmi5launchtenantname']; - $token = $settings['cmi5launchtenanttoken']; - $playerUrl = $settings['cmi5launchplayerurl']; - $courseId = $usersCourse->courseid; - - //Build URL for launch URL request - $url = $playerUrl . "/api/v1/course/" . $courseId ."/launch-url/" . $auIndex; - - $data = array( - 'actor' => array( - 'account' => array( - "homePage" => $homepage, - "name" => $actor, - ), - ), - 'returnUrl' => $returnUrl, - 'reg' => $registrationID - ); - - // 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 - //JSON_UNESCAPED_SLASHES used so http addresses are displayed correctly - $options = array( - 'http' => array( - 'method' => 'POST', - 'ignore_errors' => true, - 'header' => array("Authorization: Bearer ". $token, - "Content-Type: application/json\r\n" . - "Accept: application/json\r\n"), - 'content' => json_encode($data, JSON_UNESCAPED_SLASHES) - - ) - ); - - //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) - $launchResponse = file_get_contents( $url, false, $context ); - - //here may be the problem, what is being sent back? - echo"
"; - echo" This is swhat is bein sent back>:"; - var_dump($launchResponse); - ECHO"
"; - //Only return the URL - $urlDecoded = json_decode($launchResponse, true); - - return $urlDecoded; - } - - - ///Function to construct, send an URL, and save result - //@param $dataBody - the data that will be used to construct the body of request as JSON - //@param $url - The URL the request will be sent to - //@param ...$tenantInfo is a variable length param. If one is passed, it is $token, if two it is $username and $password - ///@return - $result is the response from cmi5 player - ///// - public function sendRequest($dataBody, $urlDest, ...$tenantInfo) { - $data = $dataBody; - $url = $urlDest; - $tenantInformation = $tenantInfo; - - //If number of args is greater than one it is for retrieving tenant info and args are username and password - if(count($tenantInformation) > 1 ){ - - $username = $tenantInformation[0]; - $password = $tenantInformation[1]; - - // 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' => 'POST', - 'header' => array('Authorization: Basic '. base64_encode("$username:$password"), - "Content-Type: application/json\r\n" . - "Accept: application/json\r\n"), - 'content' => json_encode($data) - ) - ); - //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) - $result = file_get_contents( $url, false, $context ); - - //return response - return $result; - } - //Else the args are what we need for posting a course - else{ - - //First arg will be token - $token = $tenantInformation[0]; - $file_contents = $data->get_content(); - - // 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 - //JSON_UNESCAPED_SLASHES used so http addresses are displayed correctly - $options = array( - 'http' => array( - 'method' => 'POST', - 'ignore_errors' => true, - 'header' => array("Authorization: Bearer ". $token, - "Content-Type: application/zip\r\n"), - 'content' => $file_contents - ) - ); - - //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) - $result = file_get_contents( $url, false, $context ); - - return $result; - } - } - - - /** - *Retrieve session info from cmi5player - * @param mixed $sessionId - the session id to retrieve - * @param mixed $id - cmi5 id - * @return mixed - */ - public function retrieveSessionInfo($sessionId, $id){ - - global $DB; - - $settings = cmi5launch_settings($id); - - $token = $settings['cmi5launchtenanttoken']; - $playerUrl = $settings['cmi5launchplayerurl']; - - //Build URL for launch URL request - $url = $playerUrl . "/api/v1/session/" . $sessionId; - - // 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 - //JSON_UNESCAPED_SLASHES used so http addresses are displayed correctly - $options = array( - 'http' => array( - 'method' => 'GET', - 'ignore_errors' => true, - 'header' => array("Authorization: Bearer ". $token, - "Content-Type: application/json\r\n" . - "Accept: application/json\r\n") - ) - ); - - //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) - $launchResponse = file_get_contents( $url, false, $context ); - - $sessionDecoded = json_decode($launchResponse, true); - - return $sessionDecoded; - } - -} - - ?> diff --git a/cmi5PHP/src/course.php b/cmi5PHP/src/course.php deleted file mode 100644 index 2c194d8..0000000 --- a/cmi5PHP/src/course.php +++ /dev/null @@ -1,80 +0,0 @@ -. - -/** - *Class to handle invidual courses - *experimental - * - * @copyright 2023 Megan Bohland - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -class course { - // Properties - public $id, $url, $type, $lmsid, $grade, $scores, $title, $moveon, $auindex, $parents, $objectives, $description = [], $activitytype, $launchmethod, $masteryscore, $satisfied ; -public $courseid; //The id assigned by cmi5 -public $userid; //The user id who is taking the course - public $returnurl; //the users returnurl? needed? - public $registrationid; //the registration id assigned by the CMI5 player - public $aus = array(); //array of AUs in the course - - - //Do we needs 'sessions? todo - - - //Holds launch url, this may be the way to have separate sessions. - public $launchurl, $sessionid; - - //Maybe an array will hold info better - //I'm thinkig sessionId->launchurl (for that session) - public $sessions = array(); //Sessions still needed? - - - //These may be needed later if AU's become part of a block to keep track of - //being finished. - public $progress; - - public $noattempt = true|false; - - //Why did I ake these arrays? they should just be bools - public $completed, $passed, $inprogress = true | false; - //public $passed = array('finished'=> true|false, 'info' =>""); - //public $inProgress = array('finished'=> true|false, 'info' =>""); - - // Methods - function set_name($name) { - $this->name = $name; - } - function get_name() { - return $this->name; - } - -//Constructs courses. Is fed array and where array key matches property, sets the property. -function __construct($statement){ - - foreach($statement as $key => $value){ - - //If the key exists as a property, set it. - if(property_exists($this, $key) ){ - - $this->$key = ($value); - } - } - -} - -} -?> \ No newline at end of file diff --git a/cmi5PHP/src/sessionHelpers.php b/cmi5PHP/src/sessionHelpers.php deleted file mode 100644 index 8dfb438..0000000 --- a/cmi5PHP/src/sessionHelpers.php +++ /dev/null @@ -1,118 +0,0 @@ -dirroot/mod/cmi5launch/cmi5PHP/src/cmi5Connector.php"); - $connector = new cmi5Connectors; - $getSessionInfo = $connector->getSessions(); - - //Get the session from DB with session id - $session = $this->getFromDB($sessionID); - - //This is sessioninfo from CMI5 player - $sessionInfo = $getSessionInfo($sessionID, $cmi5Id); - - //Update session - foreach($sessionInfo as $key => $value){ - //We don't want to overwrite id - if (property_exists($session, $key ) && $key != 'id' ) { - //If it's an array encode it so it can be saved to DB - if (is_Array($value)) { - $value = json_encode($value); - } - $session->$key = $value; - } - } - - //Now update to table - $DB->update_record('cmi5launch_sessions', $session); - - return $session; - } - - - /** - * Creates a session record in DB - * @param mixed $sessId - the session id - * @param mixed $launchurl - the launch url - * @param mixed $launchMethod - the launch method - * @return void - */ - function createSession($sessId, $launchurl, $launchMethod) - { - global $DB, $CFG, $cmi5launch, $USER; - - //$record; - $table = "cmi5launch_sessions"; - - //Make a newRecord to save - $newRecord = new stdClass(); - //Because of many nested properties, needs to be done manually - $newRecord->sessionid = $sessId; - $newRecord->launchurl = $launchurl; - $newRecord->tenantname = $USER->username; - $newRecord->launchmethod = $launchMethod; - - //Save - $DB->insert_record($table, $newRecord, true); - } - - /** - * Retrieves session from DB - * @param mixed $sessionID - the session id - * @return session - */ - function getFromDB($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; - } -} - -?> \ No newline at end of file diff --git a/cmi5PHP/src/sessions.php b/cmi5PHP/src/sessions.php deleted file mode 100644 index b2b261b..0000000 --- a/cmi5PHP/src/sessions.php +++ /dev/null @@ -1,61 +0,0 @@ -. - -/** - *Class to handle Sessions. - * TODO The question is, to we want to build this exactly like the session table in cmi5??? - * - * @copyright 2023 Megan Bohland - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -class Session { - // Properties - //$id is session id - public $id, $tenantname, $tenantId, $registrationsCoursesAusId, $lmsid, $firstlaunch, $lastlaunch, - $progress = [], $auid, $aulaunchurl, $launchurl, - $completed_passed, $grade, $registrationid, $lrscode, - $createdAt, $updatedAt, $registrationCourseAusId, - $code, $lastRequestTime, $launchTokenId, $launchMode, $masteryScore, - $contextTemplate, $isLaunched, $isInitialized, $initializedAt, $isCompleted, - $isPassed, $isFailed, $isTerminated, $isAbandoned, $courseid; - - - //Why did I ake these arrays? they should just be bools - public $completed, $passed, $inprogress = true | false; - //public $passed = array('finished'=> true|false, 'info' =>""); - //public $inProgress = array('finished'=> true|false, 'info' =>""); - - // Methods - function set_name($name) { - $this->name = $name; - } - function get_name() { - return $this->name; - } - -//Constructs sessionss. Is fed array and where array key matches property, sets the property. -function __construct($statement){ - - foreach($statement as $key => $value){ - - $this->$key = ($value); - } - -} - -} -?> \ No newline at end of file diff --git a/cmi5PHP/tests/AboutTest.php b/cmi5PHP/tests/AboutTest.php deleted file mode 100755 index 1b29dbe..0000000 --- a/cmi5PHP/tests/AboutTest.php +++ /dev/null @@ -1,66 +0,0 @@ -assertInstanceOf('cmi5\About', $obj); - $this->assertAttributeEmpty('version', $obj, 'version empty'); - $this->assertAttributeNotEmpty('extensions', $obj, 'extenstions not empty'); - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\About')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\About')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\About')); - } - - public function testVersion() { - $obj = new About(); - $version = [self::VERSION_1]; - $this->assertSame($obj, $obj->setVersion($version)); - $this->assertSame($version, $obj->getVersion()); - } - - public function testExtensionsWithArray() { - $obj = new About(); - $this->assertSame($obj, $obj->setExtensions(['foo' => 'bar'])); - $this->assertInstanceOf('cmi5\Extensions', $obj->getExtensions()); - $this->assertFalse($obj->getExtensions()->isEmpty()); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = ['version' => [self::VERSION_1]]; - $obj = new About($args); - $versioned = $obj->asVersion(self::VERSION_1); - - $this->assertEquals($versioned, $args, "version only: 1.0.0"); - } -} diff --git a/cmi5PHP/tests/ActivityDefinitionTest.php b/cmi5PHP/tests/ActivityDefinitionTest.php deleted file mode 100755 index 3b493b1..0000000 --- a/cmi5PHP/tests/ActivityDefinitionTest.php +++ /dev/null @@ -1,119 +0,0 @@ -assertInstanceOf('cmi5\ActivityDefinition', $obj); - foreach ($this->emptyProperties as $property) { - $this->assertAttributeEmpty($property, $obj, "$property empty"); - } - foreach ($this->nonEmptyProperties as $property) { - $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\ActivityDefinition')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\ActivityDefinition')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\ActivityDefinition')); - } - - // TODO: need more robust test (happy-path) - public function testAsVersion() { - $args = [ - 'name' => ['en' => self::NAME], - 'extensions' => [] - ]; - $args['extensions'][COMMON_EXTENSION_ID_1] = 'test'; - - $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptyLanguageMap() { - $args = ['name' => []]; - - $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['name']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyExtensions() { - $args = ['extensions' => []]; - - $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['extensions']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyStringInLanguageMap() { - $args = ['name' => ['en' => '']]; - - $obj = ActivityDefinition::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } -} diff --git a/cmi5PHP/tests/ActivityProfileTest.php b/cmi5PHP/tests/ActivityProfileTest.php deleted file mode 100755 index 2c973e7..0000000 --- a/cmi5PHP/tests/ActivityProfileTest.php +++ /dev/null @@ -1,30 +0,0 @@ -setActivity(['id' => COMMON_ACTIVITY_ID]); - - $this->assertInstanceOf('cmi5\Activity', $profile->getActivity()); - } -} diff --git a/cmi5PHP/tests/ActivityTest.php b/cmi5PHP/tests/ActivityTest.php deleted file mode 100755 index f9fd4d9..0000000 --- a/cmi5PHP/tests/ActivityTest.php +++ /dev/null @@ -1,178 +0,0 @@ - 'http://id.cmi5api.com/activitytype/unit-test', - 'name' => [ - 'en-US' => 'test', - 'en-GB' => 'test', - 'es' => 'prueba' - ] - ]; - } - - public function testInstantiation() { - $obj = new Activity(); - $this->assertInstanceOf('cmi5\Activity', $obj); - $this->assertAttributeEmpty('id', $obj, 'id empty'); - $this->assertAttributeEmpty('definition', $obj, 'definition empty'); - } - - public function testFromJSONInvalidNull() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Activity::fromJSON(null); - } - - public function testFromJSONInvalidEmptyString() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Activity::fromJSON(''); - } - - public function testFromJSONInvalidMalformed() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Activity::fromJSON('{id:"some value"}'); - } - - public function testFromJSONIDOnly() { - $obj = Activity::fromJSON('{"id":"' . COMMON_ACTIVITY_ID . '"}'); - $this->assertInstanceOf('cmi5\Activity', $obj); - $this->assertAttributeEquals(COMMON_ACTIVITY_ID, 'id', $obj, 'id matches'); - $this->assertAttributeEmpty('definition', $obj, 'definition empty'); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'name' => [ - 'en' => 'test' - ] - ] - ]; - - $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionIdOnly() { - $args = [ 'id' => COMMON_ACTIVITY_ID ]; - - $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyDefinition() { - $args = [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [] - ]; - - $obj = Activity::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Activity'; - unset($args['definition']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testCompareWithSignature() { - $full = [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => self::$DEFINITION - ]; - $definition2 = array_replace(self::$DEFINITION, ['type' => 'http://id.cmi5api.com/activitytype/unit-test-suite']); - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'id', - 'objArgs' => ['id' => COMMON_ACTIVITY_ID] - ], - [ - 'description' => 'definition', - 'objArgs' => ['definition' => self::$DEFINITION] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - - // - // definitions are not matched for signature purposes because they - // are not supposed to affect the meaning of the statement and may - // be supplied in canonical format, etc. - // - [ - 'description' => 'definition only: mismatch (allowed)', - 'objArgs' => ['definition' => self::$DEFINITION ], - 'sigArgs' => ['definition' => $definition2 ] - ], - [ - 'description' => 'full: definition mismatch (allowed)', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['definition' => $definition2 ]) - ], - - [ - 'description' => 'id only: mismatch', - 'objArgs' => ['id' => COMMON_ACTIVITY_ID ], - 'sigArgs' => ['id' => COMMON_ACTIVITY_ID . '/invalid' ], - 'reason' => 'Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: id mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['id' => COMMON_ACTIVITY_ID . '/invalid']), - 'reason' => 'Comparison of id failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\Activity", $cases); - } -} diff --git a/cmi5PHP/tests/AgentAccountTest.php b/cmi5PHP/tests/AgentAccountTest.php deleted file mode 100755 index fb8ea47..0000000 --- a/cmi5PHP/tests/AgentAccountTest.php +++ /dev/null @@ -1,149 +0,0 @@ -assertInstanceOf('cmi5\AgentAccount', $obj); - $this->assertAttributeEmpty('homePage', $obj, 'homePage empty'); - $this->assertAttributeEmpty('name', $obj, 'name empty'); - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\AgentAccount')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\AgentAccount')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\AgentAccount')); - } - - public function testName() { - $obj = new AgentAccount(); - $name = COMMON_ACCT_NAME; - $this->assertSame($obj, $obj->setName($name)); - $this->assertSame($name, $obj->getName()); - } - - public function testHomePage() { - $obj = new AgentAccount(); - $homePage = COMMON_ACCT_HOMEPAGE; - $this->assertSame($obj, $obj->setHomePage($homePage)); - $this->assertSame($homePage, $obj->getHomePage()); - } - - public function testAsVersion() { - $args = [ - 'name' => COMMON_ACCT_NAME, - 'homePage' => COMMON_ACCT_HOMEPAGE - ]; - - $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptyStrings() { - $args = [ - 'name' => '', - 'homePage' => '' - ]; - - $obj = AgentAccount::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testCompareWithSignature() { - $full = [ - 'homePage' => COMMON_ACCT_HOMEPAGE, - 'name' => COMMON_ACCT_NAME - ]; - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'homePage', - 'objArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE] - ], - [ - 'description' => 'name', - 'objArgs' => ['name' => COMMON_ACCT_NAME] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - [ - 'description' => 'homepage only: mismatch', - 'objArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE], - 'sigArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid'], - 'reason' => 'Comparison of homePage failed: value is not the same' - ], - [ - 'description' => 'name only: mismatch', - 'objArgs' => ['name' => COMMON_ACCT_NAME], - 'sigArgs' => ['name' => 'diff'], - 'reason' => 'Comparison of name failed: value is not the same' - ], - [ - 'description' => 'full: homePage mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid']), - 'reason' => 'Comparison of homePage failed: value is not the same' - ], - [ - 'description' => 'full: name mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['name' => 'diff']), - 'reason' => 'Comparison of name failed: value is not the same' - ], - [ - 'description' => 'full: both mismatch', - 'objArgs' => $full, - 'sigArgs' => ['homePage' => COMMON_ACCT_HOMEPAGE . '/invalid', 'name' => 'diff'], - 'reason' => 'Comparison of name failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\AgentAccount", $cases); - } -} diff --git a/cmi5PHP/tests/AgentProfileTest.php b/cmi5PHP/tests/AgentProfileTest.php deleted file mode 100755 index 0c4bf65..0000000 --- a/cmi5PHP/tests/AgentProfileTest.php +++ /dev/null @@ -1,35 +0,0 @@ -setAgent(['mbox' => COMMON_MBOX]); - - $this->assertInstanceOf('cmi5\Agent', $profile->getAgent()); - - $profile->setAgent(['objectType' => 'Group']); - - $this->assertInstanceOf('cmi5\Group', $profile->getAgent()); - } -} diff --git a/cmi5PHP/tests/AgentTest.php b/cmi5PHP/tests/AgentTest.php deleted file mode 100755 index d34d23d..0000000 --- a/cmi5PHP/tests/AgentTest.php +++ /dev/null @@ -1,331 +0,0 @@ -assertInstanceOf('cmi5\Agent', $obj); - $this->assertAttributeEmpty('name', $obj, 'name empty'); - $this->assertAttributeEmpty('mbox', $obj, 'mbox empty'); - $this->assertAttributeEmpty('mbox_sha1sum', $obj, 'mbox_sha1sum empty'); - $this->assertAttributeEmpty('openid', $obj, 'openid empty'); - $this->assertAttributeEmpty('account', $obj, 'account empty'); - } - - public function testFromJSONInvalidNull() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Agent::fromJSON(null); - } - - public function testFromJSONInvalidEmptyString() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Agent::fromJSON(''); - } - - public function testFromJSONInvalidMalformed() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Agent::fromJSON('{name:"some value"}'); - } - - // TODO: need to loop possible configs - public function testFromJSONInstantiations() { - $obj = Agent::fromJSON('{"mbox":"' . COMMON_MBOX . '"}'); - $this->assertInstanceOf('cmi5\Agent', $obj); - $this->assertSame(COMMON_MBOX, $obj->getMbox(), 'mbox value'); - } - - // TODO: need to loop versions - public function testAsVersionMbox() { - $args = [ - 'mbox' => COMMON_MBOX - ]; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionMboxSha1() { - $args = [ - 'mbox_sha1sum' => COMMON_MBOX_SHA1 - ]; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionAccount() { - $args = [ - 'account' => [ - 'name' => COMMON_ACCT_NAME, - 'homePage' => COMMON_ACCT_HOMEPAGE - ] - ]; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionAccountEmptyStrings() { - $args = [ - 'account' => [ - 'name' => '', - 'homePage' => '' - ] - ]; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyAccount() { - $args = [ - 'account' => [] - ]; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - unset($args['account']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Agent::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Agent'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testIsIdentified() { - $identified = [ - [ - 'description' => 'mbox', - 'args' => ['mbox' => COMMON_MBOX] - ], - [ - 'description' => 'mbox_sha1sum', - 'args' => ['mbox_sha1sum' => COMMON_MBOX_SHA1] - ], - [ - 'description' => 'openid', - 'args' => ['openid' => COMMON_OPENID] - ], - [ - 'description' => 'account', - 'args' => ['account' => ['homePage' => COMMON_ACCT_HOMEPAGE, 'name' => COMMON_ACCT_NAME]] - ] - ]; - foreach ($identified as $case) { - $obj = new Agent ($case['args']); - $this->assertTrue($obj->isIdentified(), 'identified ' . $case['description']); - } - - $notIdentified = [ - [ - 'description' => 'empty', - 'args' => [] - ], - [ - 'description' => 'name only', - 'args' => ['name' => 'Test'] - ] - ]; - foreach ($notIdentified as $case) { - $obj = new Agent ($case['args']); - $this->assertFalse($obj->isIdentified(), 'not identified ' . $case['description']); - } - } - - public function testSetMbox() { - $obj = new Agent(); - - $obj->setMbox(COMMON_MBOX); - $this->assertSame(COMMON_MBOX, $obj->getMbox()); - - $obj->setMbox(COMMON_EMAIL); - $this->assertSame(COMMON_MBOX, $obj->getMbox()); - - // - // make sure it doesn't add mailto when null - // - $obj->setMbox(null); - $this->assertAttributeEmpty('mbox', $obj); - } - - public function testGetMbox_sha1sum() { - $obj = new Agent(['mbox_sha1sum' => COMMON_MBOX_SHA1]); - $this->assertSame($obj->getMbox_sha1sum(), COMMON_MBOX_SHA1, 'original sha1'); - - $obj = new Agent(['mbox' => COMMON_MBOX]); - $this->assertSame($obj->getMbox_sha1sum(), COMMON_MBOX_SHA1, 'sha1 from mbox'); - } - - public function testCompareWithSignature() { - $name = 'Test Name'; - $acct1 = new AgentAccount( - [ - 'homePage' => COMMON_ACCT_HOMEPAGE, - 'name' => COMMON_ACCT_NAME - ] - ); - $acct2 = new AgentAccount( - [ - 'homePage' => COMMON_ACCT_HOMEPAGE, - 'name' => COMMON_ACCT_NAME . '-diff' - ] - ); - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'mbox', - 'objArgs' => ['mbox' => COMMON_MBOX] - ], - [ - 'description' => 'mbox_sha1sum', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1] - ], - [ - 'description' => 'openid', - 'objArgs' => ['openid' => COMMON_OPENID] - ], - [ - 'description' => 'account', - 'objArgs' => ['account' => $acct1] - ], - [ - 'description' => 'mbox with name', - 'objArgs' => ['mbox' => COMMON_MBOX, 'name' => $name] - ], - [ - 'description' => 'mbox_sha1sum with name', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1, 'name' => $name] - ], - [ - 'description' => 'openid with name', - 'objArgs' => ['openid' => COMMON_OPENID, 'name' => $name] - ], - [ - 'description' => 'account with name', - 'objArgs' => ['account' => $acct1, 'name' => $name] - ], - [ - 'description' => 'mbox with mismatch name', - 'objArgs' => ['mbox' => COMMON_MBOX, 'name' => $name], - 'sigArgs' => ['mbox' => COMMON_MBOX, 'name' => $name . ' diff'] - ], - [ - 'description' => 'mbox_sha1sum with mismatch name', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1, 'name' => $name], - 'sigArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1, 'name' => $name . ' diff'] - ], - [ - 'description' => 'openid with mismatch name', - 'objArgs' => ['openid' => COMMON_OPENID, 'name' => $name], - 'sigArgs' => ['openid' => COMMON_OPENID, 'name' => $name . ' diff'] - ], - [ - 'description' => 'account with mismatch name', - 'objArgs' => ['account' => $acct1, 'name' => $name], - 'sigArgs' => ['account' => $acct1, 'name' => $name . ' diff'] - ], - [ - 'description' => 'mbox only: mismatch', - 'objArgs' => ['mbox' => COMMON_MBOX ], - 'sigArgs' => ['mbox' => 'diff-' . COMMON_MBOX ], - 'reason' => 'Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'mbox_sha1sum only: mismatch', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1 ], - 'sigArgs' => ['mbox_sha1sum' => 'diff-' . COMMON_MBOX_SHA1 ], - 'reason' => 'Comparison of mbox_sha1sum failed: value is not the same' - ], - [ - 'description' => 'openid only: mismatch', - 'objArgs' => ['openid' => COMMON_OPENID ], - 'sigArgs' => ['openid' => COMMON_OPENID . 'diff/' ], - 'reason' => 'Comparison of openid failed: value is not the same' - ], - [ - 'description' => 'account only: mismatch', - 'objArgs' => ['account' => $acct1 ], - 'sigArgs' => ['account' => $acct2 ], - 'reason' => 'Comparison of account failed: Comparison of name failed: value is not the same' - ], - - // - // special cases where we can try to equate an mbox and an mbox SHA1 sum - // - [ - 'description' => 'this.mbox to signature.mbox_sha1sum', - 'objArgs' => ['mbox' => COMMON_MBOX], - 'sigArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1] - ], - [ - 'description' => 'this.mbox_sha1sum to signature.mbox', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1], - 'sigArgs' => ['mbox' => COMMON_MBOX] - ], - [ - 'description' => 'this.mbox to signature.mbox_sha1sum non-matching', - 'objArgs' => ['mbox' => COMMON_MBOX], - 'sigArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1 . '-diff'], - 'reason' => 'Comparison of this.mbox to signature.mbox_sha1sum failed: no match' - ], - [ - 'description' => 'this.mbox_sha1sum to signature.mbox non-matching', - 'objArgs' => ['mbox_sha1sum' => COMMON_MBOX_SHA1 . '-diff'], - 'sigArgs' => ['mbox' => COMMON_MBOX], - 'reason' => 'Comparison of this.mbox_sha1sum to signature.mbox failed: no match' - ], - ]; - $this->runSignatureCases("cmi5\Agent", $cases); - } -} diff --git a/cmi5PHP/tests/AsVersionTraitTest.php b/cmi5PHP/tests/AsVersionTraitTest.php deleted file mode 100755 index 7b1f909..0000000 --- a/cmi5PHP/tests/AsVersionTraitTest.php +++ /dev/null @@ -1,36 +0,0 @@ -assertTrue(trait_exists('cmi5\AsVersionTrait')); - } - - public function testAsVersionReturnsArray() { - $trait = $this->getMockForTrait('cmi5\AsVersionTrait'); - $this->assertInternalType('array', $trait->asVersion('test')); - } - - public function testMagicSetThrowsException() { - $this->setExpectedException('DomainException'); - $trait = $this->getMockForTrait('cmi5\AsVersionTrait'); - $trait->foo = 'bar'; - } -} diff --git a/cmi5PHP/tests/AttachmentTest.php b/cmi5PHP/tests/AttachmentTest.php deleted file mode 100755 index a55bd2c..0000000 --- a/cmi5PHP/tests/AttachmentTest.php +++ /dev/null @@ -1,271 +0,0 @@ -assertInstanceOf('cmi5\Attachment', $obj); - foreach ($this->emptyProperties as $property) { - $this->assertAttributeEmpty($property, $obj, "$property empty"); - } - foreach ($this->nonEmptyProperties as $property) { - $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\Attachment')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\Attachment')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\Attachment')); - } - - public function testContent() { - $obj = new Attachment(); - $obj->setContent(self::CONTENT_STR); - - $this->assertSame($obj->getContent(), self::CONTENT_STR, 'content body'); - $this->assertSame($obj->getLength(), self::CONTENT_LENGTH, 'length'); - $this->assertSame($obj->getSha2(), self::CONTENT_SHA2, 'sha2'); - } - - public function testHasContent() { - $no_content = new Attachment(); - $this->assertFalse($no_content->hasContent()); - - $has_content = new Attachment( - [ - 'content' => self::CONTENT_STR - ] - ); - $this->assertTrue($has_content->hasContent()); - - $set_content = new Attachment(); - $set_content->setContent(self::CONTENT_STR); - $this->assertTrue($set_content->hasContent()); - } - - // TODO: need more robust test (happy-path) - public function testAsVersion() { - $args = [ - 'usageType' => self::USAGE_TYPE, - 'display' => ['en-US' => self::DISPLAY], - 'description' => ['en-US' => self::DESCRIPTION], - 'contentType' => self::CONTENT_TYPE, - 'length' => self::CONTENT_LENGTH, - 'sha2' => self::CONTENT_SHA2, - 'fileUrl' => self::FILE_URL - ]; - $obj = new Attachment($args); - $versioned = $obj->asVersion(Version::latest()); - $this->assertEquals($versioned, $args, '1.0.0'); - - $obj = new Attachment( - [ - 'content' => self::CONTENT_STR - ] - ); - $this->assertEquals( - $obj->asVersion(Version::latest()), - ['length' => self::CONTENT_LENGTH, 'sha2' => self::CONTENT_SHA2], - 'auto populated properties but content not returned' - ); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Attachment::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptyLanguageMap() { - $args = ['display' => []]; - - $obj = Attachment::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['display']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testCompareWithSignature() { - $full = [ - 'usageType' => self::USAGE_TYPE, - 'display' => ['en-US' => self::DISPLAY], - 'description' => ['en-US' => self::DESCRIPTION], - 'contentType' => self::CONTENT_TYPE, - 'length' => self::CONTENT_LENGTH, - 'sha2' => self::CONTENT_SHA2, - 'fileUrl' => self::FILE_URL - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'usageType', - 'objArgs' => ['usageType' => self::USAGE_TYPE] - ], - [ - 'description' => 'display', - 'objArgs' => ['display' => self::DISPLAY] - ], - [ - 'description' => 'description', - 'objArgs' => ['description' => self::DESCRIPTION] - ], - [ - 'description' => 'contentType', - 'objArgs' => ['contentType' => self::CONTENT_TYPE] - ], - [ - 'description' => 'length', - 'objArgs' => ['length' => self::CONTENT_LENGTH] - ], - [ - 'description' => 'sha2', - 'objArgs' => ['sha2' => self::CONTENT_SHA2] - ], - [ - 'description' => 'fileUrl', - 'objArgs' => ['fileUrl' => self::FILE_URL] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - - // - // display and description are language maps which we aren't - // checking for meaningful differences, so even though they - // differ they should not cause signature comparison to fail - // - [ - 'description' => 'display only with difference', - 'objArgs' => ['display' => [ 'en-US' => self::DISPLAY ]], - 'sigArgs' => ['display' => [ 'en-US' => self::DISPLAY . ' invalid' ]] - ], - [ - 'description' => 'description only with difference', - 'objArgs' => ['description' => [ 'en-US' => self::DESCRIPTION ]], - 'sigArgs' => ['description' => [ 'en-US' => self::DESCRIPTION . ' invalid' ]] - ], - - [ - 'description' => 'usageType only: mismatch', - 'objArgs' => ['usageType' => self::USAGE_TYPE ], - 'sigArgs' => ['usageType' => self::USAGE_TYPE . '/invalid' ], - 'reason' => 'Comparison of usageType failed: value is not the same' - ], - [ - 'description' => 'contentType only: mismatch', - 'objArgs' => ['contentType' => self::CONTENT_TYPE ], - 'sigArgs' => ['contentType' => 'application/octet-stream' ], - 'reason' => 'Comparison of contentType failed: value is not the same' - ], - [ - 'description' => 'length only: mismatch', - 'objArgs' => ['length' => self::CONTENT_LENGTH ], - 'sigArgs' => ['length' => self::CONTENT_LENGTH + 2 ], - 'reason' => 'Comparison of length failed: value is not the same' - ], - [ - 'description' => 'sha2 only: mismatch', - 'objArgs' => ['sha2' => self::CONTENT_SHA2], - 'sigArgs' => ['sha2' => self::CONTENT_SHA2 . self::CONTENT_SHA2], - 'reason' => 'Comparison of sha2 failed: value is not the same' - ], - [ - 'description' => 'fileUrl only: mismatch', - 'objArgs' => ['fileUrl' => self::FILE_URL], - 'sigArgs' => ['fileUrl' => self::FILE_URL . '/invalid'], - 'reason' => 'Comparison of fileUrl failed: value is not the same' - ], - [ - 'description' => 'full: usageType mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['usageType' => self::USAGE_TYPE . '/invalid']), - 'reason' => 'Comparison of usageType failed: value is not the same' - ], - [ - 'description' => 'full: contentType mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['contentType' => 'application/octet-stream']), - 'reason' => 'Comparison of contentType failed: value is not the same' - ], - [ - 'description' => 'full: length mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['length' => self::CONTENT_LENGTH + 2]), - 'reason' => 'Comparison of length failed: value is not the same' - ], - [ - 'description' => 'full: sha2 mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['sha2' => self::CONTENT_SHA2 . self::CONTENT_SHA2]), - 'reason' => 'Comparison of sha2 failed: value is not the same' - ], - [ - 'description' => 'full: fileUrl mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['fileUrl' => self::FILE_URL . '/invalid']), - 'reason' => 'Comparison of fileUrl failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\Attachment", $cases); - } -} diff --git a/cmi5PHP/tests/ContextActivitiesTest.php b/cmi5PHP/tests/ContextActivitiesTest.php deleted file mode 100755 index 82d4630..0000000 --- a/cmi5PHP/tests/ContextActivitiesTest.php +++ /dev/null @@ -1,301 +0,0 @@ - COMMON_ACTIVITY_ID - ]; - - public function testInstantiation() { - $obj = new ContextActivities(); - $this->assertInstanceOf('cmi5\ContextActivities', $obj); - foreach (self::$listProps as $k) { - $this->assertAttributeEquals([], $k, $obj, "$k empty array"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\ContextActivities')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\ContextActivities')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\ContextActivities')); - } - - public function testFromJSONInstantiations() { - $common_activity = new Activity(self::$common_activity_cfg); - - $all_json = array(); - foreach (self::$listProps as $k) { - $getMethod = 'get' . ucfirst($k); - - $prop_json = '"' . $k . '":[' . json_encode($common_activity->asVersion('1.0.0')) . ']'; - - array_push($all_json, $prop_json); - - $obj = ContextActivities::fromJSON('{' . $prop_json . '}'); - - $this->assertInstanceOf('cmi5\ContextActivities', $obj); - $this->assertEquals([$common_activity], $obj->$getMethod(), "$k list"); - } - - $obj = ContextActivities::fromJSON('{' . join(",", $all_json) . "}"); - - $this->assertInstanceOf('cmi5\ContextActivities', $obj); - $this->assertEquals([$common_activity], $obj->getCategory(), "all props: category list"); - $this->assertEquals([$common_activity], $obj->getParent(), "all props: parent list"); - $this->assertEquals([$common_activity], $obj->getGrouping(), "all props: grouping list"); - $this->assertEquals([$common_activity], $obj->getOther(), "all props: other list"); - } - - // TODO: need to loop versions - public function testAsVersionWithSingleList() { - $keys = ['category', 'parent', 'grouping', 'other']; - foreach ($keys as $k) { - $args = []; - $args[$k] = [ self::$common_activity_cfg ]; - - $obj = ContextActivities::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args[$k][0]['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches original"); - - unset($args[$k][0]['objectType']); - } - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = ContextActivities::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionWithEmptyList() { - $keys = ['category', 'parent', 'grouping', 'other']; - foreach ($keys as $k) { - $args = []; - $args[$k] = []; - - $obj = ContextActivities::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args[$k]); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - } - - public function testListSetters() { - $common_activity = new Activity(self::$common_activity_cfg); - - foreach (self::$listProps as $k) { - $setMethod = 'set' . ucfirst($k); - $getMethod = 'get' . ucfirst($k); - - $obj = new ContextActivities(); - - $obj->$setMethod($common_activity); - $this->assertEquals([$common_activity], $obj->$getMethod(), "$k: single Activity"); - - $obj->$setMethod([]); - $this->assertEquals([], $obj->$getMethod(), "$k: empty array"); - - $obj->$setMethod([$common_activity]); - $this->assertEquals([$common_activity], $obj->$getMethod(), "$k: array of single Activity"); - - $obj->$setMethod([]); - - $obj->$setMethod(self::$common_activity_cfg); - $this->assertEquals([$common_activity], $obj->$getMethod(), "$k: single Activity configuration"); - - $obj->$setMethod([]); - - $obj->$setMethod([self::$common_activity_cfg]); - $this->assertEquals([$common_activity], $obj->$getMethod(), "$k: array of single Activity configuration"); - } - } - - public function testCompareWithSignature() { - $acts = [ - new Activity( - ['id' => COMMON_ACTIVITY_ID . '/0'] - ), - new Activity( - ['id' => COMMON_ACTIVITY_ID . '/1'] - ), - new Activity( - ['id' => COMMON_ACTIVITY_ID . '/2'] - ), - new Activity( - ['id' => COMMON_ACTIVITY_ID . '/3'] - ) - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ] - ]; - foreach (self::$listProps as $k) { - array_push( - $cases, - [ - 'description' => "$k single", - 'objArgs' => [$k => [ $acts[0] ]], - ], - [ - 'description' => "$k multiple", - 'objArgs' => [$k => [ $acts[0], $acts[1] ]], - ], - [ - 'description' => "$k single empty sig (no $k set)", - 'objArgs' => [$k => [ $acts[0] ]], - 'sigArgs' => [], - 'reason' => "Comparison of $k failed: array lengths differ" - ], - [ - 'description' => "$k single empty sig", - 'objArgs' => [$k => [ $acts[0] ]], - 'sigArgs' => [$k => []], - 'reason' => "Comparison of $k failed: array lengths differ" - ], - [ - 'description' => "$k multiple single sig", - 'objArgs' => [$k => [ $acts[0], $acts[1] ]], - 'sigArgs' => [$k => [ $acts[1] ]], - 'reason' => "Comparison of $k failed: array lengths differ" - ], - [ - 'description' => "$k single multiple sig", - 'objArgs' => [$k => [ $acts[0] ]], - 'sigArgs' => [$k => [ $acts[0], $acts[1] ]], - 'reason' => "Comparison of $k failed: array lengths differ" - ], - [ - 'description' => "$k single diff sig", - 'objArgs' => [$k => [ $acts[0] ]], - 'sigArgs' => [$k => [ $acts[1] ]], - 'reason' => 'Comparison of ' . $k . '[0] failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => "$k multiple diff order", - 'objArgs' => [$k => [ $acts[0], $acts[1] ]], - 'sigArgs' => [$k => [ $acts[1], $acts[0] ]], - 'reason' => 'Comparison of ' . $k . '[0] failed: Comparison of id failed: value is not the same' - ] - ); - } - - foreach ([ - ['category', 'parent'], - ['category', 'other'], - ['category', 'grouping'], - ['parent', 'other'], - ['parent', 'grouping'], - ['grouping', 'other'], - ['category', 'parent', 'other'], - ['category', 'parent', 'grouping'], - ['category', 'other', 'grouping'], - ['parent', 'other', 'grouping'], - self::$listProps - ] as $set) { - $prefix = implode(', ', $set); - $new_cases = [ - [ - 'description' => $prefix, - 'objArgs' => [], - 'sigArgs' => [], - ], - [ - 'description' => "$prefix: empty sig", - 'objArgs' => [], - 'sigArgs' => [], - 'reason' => 'Comparison of ' . $set[0] . ' failed: array lengths differ' - ], - [ - 'description' => "$prefix: one missing this", - 'objArgs' => [], - 'sigArgs' => [], - 'reason' => 'Comparison of ' . $set[0] . ' failed: array lengths differ' - ], - [ - 'description' => "$prefix: one missing signature", - 'objArgs' => [], - 'sigArgs' => [], - 'reason' => 'Comparison of ' . $set[0] . ' failed: array lengths differ' - ] - ]; - - for ($i = 0; $i < count($set); $i++) { - $new_cases[0]['objArgs'][ $set[$i] ] = $acts[$i]; - $new_cases[0]['sigArgs'][ $set[$i] ] = $acts[$i]; - - $new_cases[1]['objArgs'][ $set[$i] ] = $acts[$i]; - - $new_cases[2]['objArgs'][ $set[$i] ] = $acts[$i]; - $new_cases[3]['objArgs'][ $set[$i] ] = $acts[$i]; - if ($i !== 0) { - $new_cases[2]['sigArgs'][ $set[$i] ] = $acts[$i]; - $new_cases[3]['sigArgs'][ $set[$i] ] = $acts[$i]; - } - } - - $cases = array_merge($cases, $new_cases); - } - $this->runSignatureCases("cmi5\ContextActivities", $cases); - } - - /** - * @dataProvider invalidListSetterDataProvider - */ - public function testListSetterThrowsInvalidArgumentException($publicMethodName, $invalidValue) { - $this->setExpectedException( - 'InvalidArgumentException', - 'type of arg1 must be Activity, array of Activity properties, or array of Activity/array of Activity properties' - ); - $obj = new ContextActivities(); - $obj->$publicMethodName($invalidValue); - } - - public function invalidListSetterDataProvider() { - $invalidValue = 1; - return [ - ["setCategory", $invalidValue], - ["setParent", $invalidValue], - ["setGrouping", $invalidValue], - ["setOther", $invalidValue] - ]; - } -} diff --git a/cmi5PHP/tests/ContextTest.php b/cmi5PHP/tests/ContextTest.php deleted file mode 100755 index ffabfaa..0000000 --- a/cmi5PHP/tests/ContextTest.php +++ /dev/null @@ -1,381 +0,0 @@ -assertInstanceOf('cmi5\Context', $obj); - foreach ($this->emptyProperties as $property) { - $this->assertAttributeEmpty($property, $obj, "$property empty"); - } - foreach ($this->nonEmptyProperties as $property) { - $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\Context')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\Context')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\Context')); - } - - /* - // TODO: need to loop possible configs - public function testFromJSONInstantiations() { - $obj = Context::fromJSON('{"mbox":"' . COMMON_GROUP_MBOX . '", "member":[{"mbox":"' . COMMON_MBOX . '"}]}'); - $this->assertInstanceOf('cmi5\Context', $obj); - $this->assertSame(COMMON_GROUP_MBOX, $obj->getMbox(), 'mbox value'); - $this->assertEquals([['mbox' => COMMON_MBOX]], $obj->getMember(), 'member list'); - } - */ - - public function testAsVersion() { - $args = [ - 'registration' => Util::getUUID(), - 'instructor' => [ - 'name' => 'test agent' - ], - 'team' => [ - 'name' => 'test group' - ], - 'contextActivities' => [ - 'category' => [ - [ - 'id' => 'test category' - ] - ] - ], - 'revision' => 'test revision', - 'platform' => 'test platform', - 'language' => 'test language', - 'statement' => [ - 'id' => Util::getUUID() - ], - 'extensions' => [], - ]; - $args['extensions'][COMMON_EXTENSION_ID_1] = "test"; - - $obj = Context::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['instructor']['objectType'] = 'Agent'; - $args['team']['objectType'] = 'Group'; - $args['contextActivities']['category'][0]['objectType'] = 'Activity'; - $args['statement']['objectType'] = 'StatementRef'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Context::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptyLists() { - $args = [ - 'contextActivities' => [ - 'category' => [] - ], - 'extensions' => [], - ]; - - $obj = Context::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['contextActivities']); - unset($args['extensions']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testSetInstructor() { - $common_agent_cfg = [ 'mbox' => COMMON_MBOX ]; - $common_agent = new Agent($common_agent_cfg); - $common_group_cfg = [ 'mbox' => COMMON_MBOX, 'objectType' => 'Group' ]; - $common_group = new Group($common_agent_cfg); - - $obj = new Context(); - - $obj->setInstructor($common_agent_cfg); - $this->assertEquals($common_agent, $obj->getInstructor(), "agent config"); - - $obj->setInstructor(null); - $this->assertEmpty($obj->getInstructor(), "empty"); - } - - public function testCompareWithSignature() { - $registration1 = Util::getUUID(); - $registration2 = Util::getUUID(); - $instructor1 = new Agent( - [ 'mbox' => COMMON_MBOX ] - ); - $instructor2 = new Agent( - [ 'account' => [ 'homePage' => COMMON_ACCT_HOMEPAGE, 'name' => COMMON_ACCT_NAME ]] - ); - $team1 = new Agent( - [ 'mbox' => COMMON_MBOX ] - ); - $team2 = new Agent( - [ 'account' => [ 'homePage' => COMMON_ACCT_HOMEPAGE, 'name' => COMMON_ACCT_NAME ]] - ); - $contextActivities1 = new ContextActivities( - [ 'parent' => [ COMMON_ACTIVITY_ID ]] - ); - $contextActivities2 = new ContextActivities( - [ 'parent' => [ COMMON_ACTIVITY_ID . '/parent' ]], - [ 'grouping' => [ COMMON_ACTIVITY_ID ]] - ); - $ref1 = new StatementRef( - [ 'id' => Util::getUUID() ] - ); - $ref2 = new StatementRef( - [ 'id' => Util::getUUID() ] - ); - $extensions1 = new Extensions( - [ - COMMON_EXTENSION_ID_1 => 'test1', - COMMON_EXTENSION_ID_2 => 'test2' - ] - ); - $extensions2 = new Extensions( - [ - COMMON_EXTENSION_ID_1 => 'test1' - ] - ); - - $full = [ - 'registration' => $registration1, - 'instructor' => $instructor1, - 'team' => $team1, - 'contextActivities' => $contextActivities1, - 'revision' => '1', - 'platform' => 'mobile', - 'language' => 'en-US', - 'statement' => $ref1, - 'extensions' => $extensions1 - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'registration', - 'objArgs' => ['registration' => $registration1] - ], - [ - 'description' => 'instructor', - 'objArgs' => ['instructor' => $instructor1] - ], - [ - 'description' => 'team', - 'objArgs' => ['team' => $team1] - ], - [ - 'description' => 'contextActivities', - 'objArgs' => ['contextActivities' => $contextActivities1] - ], - [ - 'description' => 'revision', - 'objArgs' => ['revision' => '1'] - ], - [ - 'description' => 'platform', - 'objArgs' => ['platform' => 'mobile'] - ], - [ - 'description' => 'language', - 'objArgs' => ['language' => 'en-US'] - ], - [ - 'description' => 'statement', - 'objArgs' => ['statement' => $ref1] - ], - [ - 'description' => 'extensions', - 'objArgs' => ['extensions' => $extensions1] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - [ - 'description' => 'registration only: mismatch', - 'objArgs' => ['registration' => $registration1 ], - 'sigArgs' => ['registration' => $registration2 ], - 'reason' => 'Comparison of registration failed: value is not the same' - ], - [ - 'description' => 'instructor only: mismatch', - 'objArgs' => ['instructor' => $instructor1 ], - 'sigArgs' => ['instructor' => $instructor2 ], - 'reason' => 'Comparison of instructor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'team only: mismatch', - 'objArgs' => ['team' => $team1 ], - 'sigArgs' => ['team' => $team2 ], - 'reason' => 'Comparison of team failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'contextActivities only: mismatch', - 'objArgs' => ['contextActivities' => $contextActivities1 ], - 'sigArgs' => ['contextActivities' => $contextActivities2 ], - 'reason' => 'Comparison of contextActivities failed: Comparison of parent failed: array lengths differ' - ], - [ - 'description' => 'revision only: mismatch', - 'objArgs' => ['revision' => '1' ], - 'sigArgs' => ['revision' => '2' ], - 'reason' => 'Comparison of revision failed: value is not the same' - ], - [ - 'description' => 'platform only: mismatch', - 'objArgs' => ['platform' => 'mobile' ], - 'sigArgs' => ['platform' => 'desktop' ], - 'reason' => 'Comparison of platform failed: value is not the same' - ], - [ - 'description' => 'language only: mismatch', - 'objArgs' => ['language' => 'en-US' ], - 'sigArgs' => ['language' => 'en-GB' ], - 'reason' => 'Comparison of language failed: value is not the same' - ], - [ - 'description' => 'statement only: mismatch', - 'objArgs' => ['statement' => $ref1 ], - 'sigArgs' => ['statement' => $ref2 ], - 'reason' => 'Comparison of statement failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'extensions only: mismatch', - 'objArgs' => ['extensions' => $extensions1 ], - 'sigArgs' => ['extensions' => $extensions2 ], - 'reason' => 'Comparison of extensions failed: http://id.cmi5api.com/extension/location not in signature' - ], - [ - 'description' => 'full: registration mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['registration' => $registration2]), - 'reason' => 'Comparison of registration failed: value is not the same' - ], - [ - 'description' => 'full: instructor mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['instructor' => $instructor2]), - 'reason' => 'Comparison of instructor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'full: team mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['team' => $team2]), - 'reason' => 'Comparison of team failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'full: contextActivities mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['contextActivities' => $contextActivities2]), - 'reason' => 'Comparison of contextActivities failed: Comparison of parent failed: array lengths differ' - ], - [ - 'description' => 'full: revision mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['revision' => '2']), - 'reason' => 'Comparison of revision failed: value is not the same' - ], - [ - 'description' => 'full: platform mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['platform' => 'desktop']), - 'reason' => 'Comparison of platform failed: value is not the same' - ], - [ - 'description' => 'full: language mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['language' => 'en-GB']), - 'reason' => 'Comparison of language failed: value is not the same' - ], - [ - 'description' => 'full: statement mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['statement' => $ref2]), - 'reason' => 'Comparison of statement failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: extensions mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['extensions' => $extensions2]), - 'reason' => 'Comparison of extensions failed: http://id.cmi5api.com/extension/location not in signature' - ] - ]; - $this->runSignatureCases("cmi5\Context", $cases); - } - - public function testSetInstructorConvertToGroup() { - $obj = new Context(); - $obj->setInstructor( - [ - 'objectType' => 'Group' - ] - ); - $this->assertInstanceOf('cmi5\Group', $obj->getInstructor()); - } - - public function testSetRegistrationInvalidArgumentException() { - $this->setExpectedException( - 'InvalidArgumentException', - 'arg1 must be a UUID' - ); - $obj = new Context(); - $obj->setRegistration('232....3.3..3./2/2/1m3m3m3'); - } -} diff --git a/cmi5PHP/tests/DocumentTest.php b/cmi5PHP/tests/DocumentTest.php deleted file mode 100755 index fcbbe03..0000000 --- a/cmi5PHP/tests/DocumentTest.php +++ /dev/null @@ -1,34 +0,0 @@ -setExpectedException( - "InvalidArgumentException", - 'type of arg1 must be string or DateTime' - ); - - $obj = new StubDocument(); - $obj->setTimestamp(1); - } -} diff --git a/cmi5PHP/tests/ExtensionsTest.php b/cmi5PHP/tests/ExtensionsTest.php deleted file mode 100755 index a015952..0000000 --- a/cmi5PHP/tests/ExtensionsTest.php +++ /dev/null @@ -1,112 +0,0 @@ -assertInstanceOf('cmi5\Extensions', $obj); - } - - public function testAsVersion() { - $args = []; - $args[COMMON_EXTENSION_ID_1] = 'test'; - $args[COMMON_EXTENSION_ID_2] = 'test2'; - - $obj = new Extensions($args); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "version: 1.0.0"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Extensions::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, null, "serialized version matches original"); - } - - public function testCompareWithSignature() { - $success = ['success' => true, 'reason' => null]; - - $content_1 = 'some value'; - $content_2 = 'some other value'; - - $extensions1 = [ COMMON_EXTENSION_ID_1 => 'some value' ]; - $extensions2 = [ COMMON_EXTENSION_ID_1 => 'some value', COMMON_EXTENSION_ID_2 => 'some other value' ]; - $extensions3 = [ COMMON_EXTENSION_ID_2 => 'some other value' ]; - - $cases = [ - [ - 'description' => 'empty', - 'objArgs' => [] - ], - [ - 'description' => 'single', - 'objArgs' => $extensions1 - ], - [ - 'description' => 'multiple', - 'objArgs' => $extensions2 - ], - [ - 'description' => 'empty sig: mismatch', - 'objArgs' => $extensions1, - 'sigArgs' => [], - 'reason' => 'http://id.cmi5api.com/extension/topic not in signature' - ], - [ - 'description' => 'empty this: mismatch', - 'objArgs' => [], - 'sigArgs' => $extensions1, - 'reason' => 'http://id.cmi5api.com/extension/topic not in this' - ], - [ - 'description' => 'missing in sig: mismatch', - 'objArgs' => $extensions2, - 'sigArgs' => $extensions3, - 'reason' => 'http://id.cmi5api.com/extension/topic not in signature' - ], - [ - 'description' => 'missing in this: mismatch', - 'objArgs' => $extensions3, - 'sigArgs' => $extensions2, - 'reason' => 'http://id.cmi5api.com/extension/topic not in this' - ], - [ - 'description' => 'single diff value in sig: mismatch', - 'objArgs' => $extensions2, - 'sigArgs' => array_replace($extensions2, [COMMON_EXTENSION_ID_1 => 'diff']), - 'reason' => 'http://id.cmi5api.com/extension/topic does not match' - ], - [ - 'description' => 'single diff value in this: mismatch', - 'objArgs' => array_replace($extensions2, [COMMON_EXTENSION_ID_1 => 'diff']), - 'sigArgs' => $extensions2, - 'reason' => 'http://id.cmi5api.com/extension/topic does not match' - ] - ]; - $this->runSignatureCases("cmi5\Extensions", $cases); - } -} diff --git a/cmi5PHP/tests/GroupTest.php b/cmi5PHP/tests/GroupTest.php deleted file mode 100755 index c10e3c4..0000000 --- a/cmi5PHP/tests/GroupTest.php +++ /dev/null @@ -1,333 +0,0 @@ -assertInstanceOf('cmi5\Agent', $obj); - $this->assertInstanceOf('cmi5\Group', $obj); - $this->assertAttributeEquals([], 'member', $obj, 'member empty array'); - } - - // TODO: need to loop possible configs - public function testFromJSONInstantiations() { - $obj = Group::fromJSON('{"mbox":"' . COMMON_GROUP_MBOX . '", "member":[{"mbox":"' . COMMON_MBOX . '"}]}'); - $this->assertInstanceOf('cmi5\Group', $obj); - $this->assertSame(COMMON_GROUP_MBOX, $obj->getMbox(), 'mbox value'); - $this->assertEquals([new Agent(['mbox' => COMMON_MBOX])], $obj->getMember(), 'member list'); - } - - // TODO: need to loop versions - public function testAsVersionMbox() { - $args = [ - 'mbox' => COMMON_GROUP_MBOX - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionMboxSha1() { - $args = [ - 'mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1 - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionAccount() { - $args = [ - 'account' => [ - 'name' => COMMON_ACCT_NAME, - 'homePage' => COMMON_ACCT_HOMEPAGE - ] - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionAccountEmptyStrings() { - $args = [ - 'account' => [ - 'name' => '', - 'homePage' => '' - ] - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyAccount() { - $args = [ - 'account' => [] - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - unset($args['account']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyMember() { - $args = [ - 'member' => [] - ]; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - unset($args['member']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Group::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'Group'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAddMember() { - $common_agent = new Agent(['mbox' => COMMON_MBOX]); - - $obj = new Group(); - - $obj->addMember([ 'mbox' => COMMON_MBOX ]); - $this->assertEquals([$common_agent], $obj->getMember(), 'member list create Agent'); - - $obj->setMember([]); - - $obj->addMember($common_agent); - $this->assertEquals([$common_agent], $obj->getMember(), 'member list existing Agent'); - - $versioned = $obj->asVersion('1.0.0'); - $this->assertSame($versioned['member'][0], $common_agent->asVersion('1.0.0')); - } - - public function testCompareWithSignature() { - $name = 'Test Group Name'; - $acct1 = new AgentAccount( - [ - 'homePage' => COMMON_ACCT_HOMEPAGE, - 'name' => COMMON_ACCT_NAME - ] - ); - $acct2 = new AgentAccount( - [ - 'homePage' => COMMON_ACCT_HOMEPAGE, - 'name' => COMMON_ACCT_NAME . '-diff' - ] - ); - - $member1 = new Agent( - [ 'mbox' => COMMON_MBOX ] - ); - $member2 = new Agent( - [ 'account' => $acct1 ] - ); - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'mbox', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX] - ], - [ - 'description' => 'mbox_sha1sum', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1] - ], - [ - 'description' => 'openid', - 'objArgs' => ['openid' => COMMON_OPENID] - ], - [ - 'description' => 'account', - 'objArgs' => ['account' => $acct1] - ], - [ - 'description' => 'mbox with name', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX, 'name' => $name] - ], - [ - 'description' => 'mbox_sha1sum with name', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1, 'name' => $name] - ], - [ - 'description' => 'openid with name', - 'objArgs' => ['openid' => COMMON_OPENID, 'name' => $name] - ], - [ - 'description' => 'account with name', - 'objArgs' => ['account' => $acct1, 'name' => $name] - ], - [ - 'description' => 'mbox with mismatch name', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX, 'name' => $name], - 'sigArgs' => ['mbox' => COMMON_GROUP_MBOX, 'name' => $name . ' diff'] - ], - [ - 'description' => 'mbox_sha1sum with mismatch name', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1, 'name' => $name], - 'sigArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1, 'name' => $name . ' diff'] - ], - [ - 'description' => 'openid with mismatch name', - 'objArgs' => ['openid' => COMMON_OPENID, 'name' => $name], - 'sigArgs' => ['openid' => COMMON_OPENID, 'name' => $name . ' diff'] - ], - [ - 'description' => 'account with mismatch name', - 'objArgs' => ['account' => $acct1, 'name' => $name], - 'sigArgs' => ['account' => $acct1, 'name' => $name . ' diff'] - ], - [ - 'description' => 'mbox only: mismatch', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX ], - 'sigArgs' => ['mbox' => 'diff-' . COMMON_GROUP_MBOX ], - 'reason' => 'Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'mbox_sha1sum only: mismatch', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1 ], - 'sigArgs' => ['mbox_sha1sum' => 'diff-' . COMMON_GROUP_MBOX_SHA1 ], - 'reason' => 'Comparison of mbox_sha1sum failed: value is not the same' - ], - [ - 'description' => 'openid only: mismatch', - 'objArgs' => ['openid' => COMMON_OPENID ], - 'sigArgs' => ['openid' => COMMON_OPENID . 'diff/' ], - 'reason' => 'Comparison of openid failed: value is not the same' - ], - [ - 'description' => 'account only: mismatch', - 'objArgs' => ['account' => $acct1 ], - 'sigArgs' => ['account' => $acct2 ], - 'reason' => 'Comparison of account failed: Comparison of name failed: value is not the same' - ], - - // - // special cases where we can try to equate an mbox and an mbox SHA1 sum - // - [ - 'description' => 'this.mbox to signature.mbox_sha1sum', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX], - 'sigArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1] - ], - [ - 'description' => 'this.mbox_sha1sum to signature.mbox', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1], - 'sigArgs' => ['mbox' => COMMON_GROUP_MBOX] - ], - [ - 'description' => 'this.mbox to signature.mbox_sha1sum non-matching', - 'objArgs' => ['mbox' => COMMON_GROUP_MBOX], - 'sigArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1 . '-diff'], - 'reason' => 'Comparison of this.mbox to signature.mbox_sha1sum failed: no match' - ], - [ - 'description' => 'this.mbox_sha1sum to signature.mbox non-matching', - 'objArgs' => ['mbox_sha1sum' => COMMON_GROUP_MBOX_SHA1 . '-diff'], - 'sigArgs' => ['mbox' => COMMON_GROUP_MBOX], - 'reason' => 'Comparison of this.mbox_sha1sum to signature.mbox failed: no match' - ], - - // special cases for unidentified groups, member list needs to match - [ - 'description' => 'anonymous match: empty member list', - 'objArgs' => ['member' => []], - ], - [ - 'description' => 'anonymous match: single member', - 'objArgs' => ['member' => [$member1]], - ], - [ - 'description' => 'anonymous match: multiple members', - 'objArgs' => ['member' => [$member1, $member2]], - ], - [ - 'description' => 'anonymous non-match: sig member missing (empty)', - 'objArgs' => ['member' => [$member1]], - 'sigArgs' => ['member' => []], - 'reason' => 'Comparison of member list failed: array lengths differ' - ], - [ - 'description' => 'anonymous non-match: this member missing (empty)', - 'objArgs' => ['member' => []], - 'sigArgs' => ['member' => [$member1]], - 'reason' => 'Comparison of member list failed: array lengths differ' - ], - [ - 'description' => 'anonymous non-match: sig member missing', - 'objArgs' => ['member' => [$member1, $member2]], - 'sigArgs' => ['member' => [$member1]], - 'reason' => 'Comparison of member list failed: array lengths differ' - ], - [ - 'description' => 'anonymous non-match: this member missing', - 'objArgs' => ['member' => [$member1]], - 'sigArgs' => ['member' => [$member1, $member2]], - 'reason' => 'Comparison of member list failed: array lengths differ' - ], - [ - 'description' => 'anonymous non-match: different order', - 'objArgs' => ['member' => [$member2, $member1]], - 'sigArgs' => ['member' => [$member1, $member2]], - 'reason' => 'Comparison of member 0 failed: Comparison of mbox failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\Group", $cases); - } -} diff --git a/cmi5PHP/tests/ISO8601Test.php b/cmi5PHP/tests/ISO8601Test.php deleted file mode 100755 index be29f81..0000000 --- a/cmi5PHP/tests/ISO8601Test.php +++ /dev/null @@ -1,54 +0,0 @@ -setDate(2014, 12, 15); - $datetime->setTime(19, 16, 05); - - $statement = new Statement(); - $statement->setStored($datetime); - $statement->setTimestamp($datetime); - - $document = new State(); - $document->setTimestamp($datetime); - - $this->assertEquals($statement->getStored(), $str_datetime, 'stored matches'); - $this->assertEquals($statement->getTimestamp(), $str_datetime, 'timestamp matches'); - $this->assertEquals($document->getTimestamp(), $str_datetime, 'document timestamp matches'); - - $datetime->setTimezone(new DateTimeZone('America/Chicago')); - $statement->setStored($datetime); - $statement->setTimestamp($datetime); - $document->setTimestamp($datetime); - - $this->assertEquals($statement->getStored(), $str_datetime_tz, 'stored matches'); - $this->assertEquals($statement->getTimestamp(), $str_datetime_tz, 'timestamp matches'); - $this->assertEquals($document->getTimestamp(), $str_datetime_tz, 'document timestamp matches'); - } -} diff --git a/cmi5PHP/tests/JSONParseErrorExceptionTest.php b/cmi5PHP/tests/JSONParseErrorExceptionTest.php deleted file mode 100755 index 5f7f4bc..0000000 --- a/cmi5PHP/tests/JSONParseErrorExceptionTest.php +++ /dev/null @@ -1,48 +0,0 @@ -jsonErrorNumber = JSON_ERROR_SYNTAX; - $this->jsonErrorMessage = 'Syntax error, malformed JSON'; - $this->exception = new JSONParseErrorException( - $this->malformedValue, - $this->jsonErrorNumber, - $this->jsonErrorMessage - ); - } - - public function testFetchErrorInformation() { - $this->assertEquals($this->malformedValue, $this->exception->malformedValue()); - $this->assertEquals($this->jsonErrorNumber, $this->exception->jsonErrorNumber()); - $this->assertEquals($this->jsonErrorMessage, $this->exception->jsonErrorMessage()); - } - - public function testGetMessage() { - $format = 'Invalid JSON "%s": %s (%d)'; - $expected = sprintf($format, $this->malformedValue, $this->jsonErrorMessage, $this->jsonErrorNumber); - $this->assertEquals($expected, $this->exception->getMessage()); - } -} diff --git a/cmi5PHP/tests/LRSResponseTest.php b/cmi5PHP/tests/LRSResponseTest.php deleted file mode 100755 index 0940d30..0000000 --- a/cmi5PHP/tests/LRSResponseTest.php +++ /dev/null @@ -1,29 +0,0 @@ -assertTrue($obj->success); - $this->assertEquals('', $obj->content); - $this->assertFalse($obj->httpResponse); - } -} diff --git a/cmi5PHP/tests/LanguageMapTest.php b/cmi5PHP/tests/LanguageMapTest.php deleted file mode 100755 index 499f385..0000000 --- a/cmi5PHP/tests/LanguageMapTest.php +++ /dev/null @@ -1,110 +0,0 @@ -assertInstanceOf('cmi5\LanguageMap', $obj); - } - - public function testAsVersion() { - $args = ['en' => [self::NAME]]; - - $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion(); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, null, "serialization returns null"); - } - - public function testAsVersionValueEmptyString() { - $args = ['en' => ['']]; - - $obj = LanguageMap::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion(); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testGetNegotiatedLanguageString() { - $langs = [ - 'en-GB' => 'petrol', - 'en-US' => 'gasoline' - ]; - $obj = new LanguageMap($langs); - - $usValue = $obj->getNegotiatedLanguageString('en-US;q=0.8, en-GB;q=0.6'); - $ukValue = $obj->getNegotiatedLanguageString('en-GB;q=0.8, en-US;q=0.6'); - - $this->assertEquals($usValue, $langs['en-US'], 'US name equal'); - $this->assertEquals($ukValue, $langs['en-GB'], 'UK name equal'); - - $nullValue = $obj->getNegotiatedLanguageString(); - $this->assertEquals($nullValue, $langs['en-GB'], 'from null: UK name equal'); - - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $restore = $_SERVER['HTTP_ACCEPT_LANGUAGE']; - } - $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US'; - - $nullAcceptValue = $obj->getNegotiatedLanguageString(); - $this->assertEquals($nullAcceptValue, $langs['en-US'], 'from server: US name equal'); - - if (isset($restore)) { - $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $restore; - } - else { - unset($_SERVER['HTTP_ACCEPT_LANGUAGE']); - } - - $langs = [ - 'en-US' => 'gasoline' - ]; - $obj = new LanguageMap($langs); - - $this->assertEquals($obj->getNegotiatedLanguageString('en'), $langs['en-US'], 'from prefix'); - - $langs = [ - 'fr-FR' => 'essence' - ]; - $obj = new LanguageMap($langs); - - $this->assertEquals($obj->getNegotiatedLanguageString('en, *'), $langs['fr-FR'], 'no matched'); - - $langs = [ - 'fr-FR' => 'essence', - 'und' => 'fuel', - ]; - $obj = new LanguageMap($langs); - - $this->assertEquals($obj->getNegotiatedLanguageString('en'), $langs['und'], 'no matched with und'); - } -} diff --git a/cmi5PHP/tests/MapTest.php b/cmi5PHP/tests/MapTest.php deleted file mode 100755 index 666bfbf..0000000 --- a/cmi5PHP/tests/MapTest.php +++ /dev/null @@ -1,60 +0,0 @@ -assertTrue($obj->isEmpty()); - } - - public function testSetUnset() { - $obj = new StubMap(); - - $code = 'code'; - $value = 'value'; - - $obj->set($code, $value); - - $this->assertEquals($value, $obj->asVersion()[$code]); - - $obj->unset($code); - - $this->assertFalse(isset($obj->asVersion()[$code])); - } - - public function testExceptionOnBadMethodCall() { - $badName ="dsadasdasdasdasdasdas"; - - $this->setExpectedException( - '\BadMethodCallException', - get_class(new StubMap) . "::$badName() does not exist" - ); - - $obj = new StubMap(); - $obj->$badName(); - } -} diff --git a/cmi5PHP/tests/PersonTest.php b/cmi5PHP/tests/PersonTest.php deleted file mode 100755 index 2d27de4..0000000 --- a/cmi5PHP/tests/PersonTest.php +++ /dev/null @@ -1,105 +0,0 @@ -assertInstanceOf('cmi5\Person', $obj); - $this->assertAttributeEmpty('name', $obj, 'name empty'); - $this->assertAttributeEmpty('mbox', $obj, 'mbox empty'); - $this->assertAttributeEmpty('mbox_sha1sum', $obj, 'mbox_sha1sum empty'); - $this->assertAttributeEmpty('openid', $obj, 'openid empty'); - $this->assertAttributeEmpty('account', $obj, 'account empty'); - } - - public function testFromJSONInvalidNull() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Person::fromJSON(null); - } - - public function testFromJSONInvalidEmptyString() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Person::fromJSON(''); - } - - public function testFromJSONInvalidMalformed() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Person::fromJSON('{name:"some value"}'); - } - - // TODO: need to loop possible configs - public function testFromJSONInstantiations() { - $obj = Person::fromJSON('{"mbox":["' . COMMON_MBOX . '","'.COMMON_MBOX.'"]}'); - $this->assertInstanceOf('cmi5\Person', $obj); - $this->assertSame(array(COMMON_MBOX, COMMON_MBOX), $obj->getMbox(), 'mbox value'); - } - - // TODO: need to loop versions - public function testAsVersion() { - $obj = new Person( - [ - 'mbox' => array(COMMON_MBOX), - 'account' => array( - array( - 'name' => COMMON_ACCT_NAME, - 'homePage' => COMMON_ACCT_HOMEPAGE - ) - ) - ] - ); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals( - [ - 'objectType' => 'Person', - 'mbox' => array(COMMON_MBOX), - 'account' => array( - array( - 'name' => COMMON_ACCT_NAME, - 'homePage' => COMMON_ACCT_HOMEPAGE - ) - ) - ], - $versioned, - "mbox only: 1.0.0" - ); - } - - public function testSetMbox() { - $obj = new Person(); - - $obj->setMbox(array(COMMON_MBOX)); - $this->assertSame(array(COMMON_MBOX), $obj->getMbox()); - - // - // make sure it doesn't add mailto when null - // - $obj->setMbox(null); - $this->assertAttributeEmpty('mbox', $obj); - } - - public function testGetMbox_sha1sum() { - $obj = new Person(['mbox_sha1sum' => array(COMMON_MBOX_SHA1)]); - $this->assertSame($obj->getMbox_sha1sum(), array(COMMON_MBOX_SHA1), 'original sha1'); - } -} diff --git a/cmi5PHP/tests/RemoteLRSTest.php b/cmi5PHP/tests/RemoteLRSTest.php deleted file mode 100755 index 7f2ac94..0000000 --- a/cmi5PHP/tests/RemoteLRSTest.php +++ /dev/null @@ -1,594 +0,0 @@ -assertInstanceOf('cmi5\RemoteLRS', $lrs); - $this->assertAttributeEmpty('endpoint', $lrs, 'endpoint empty'); - $this->assertAttributeEmpty('auth', $lrs, 'auth empty'); - $this->assertAttributeEmpty('extended', $lrs, 'extended empty'); - $this->assertSame(Version::latest(), $lrs->getVersion(), 'version set to latest'); - } - - public function testAbout() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->about(); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success); - } - - public function testSaveStatement() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]) - ] - ); - - $response = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertSame($response->content, $statement, 'content'); - } - - public function testSaveStatementStamped() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]) - ] - ); - $statement->stamp(); - - $response = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertSame($response->content, $statement, 'content'); - } - - public function testSaveStatements() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $statements = [ - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]) - ], - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/2' - ]) - ], - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/3' - ]) - ] - ]; - - $response = $lrs->saveStatements($statements); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertTrue(is_array($response->content), 'content is array'); - $this->assertSame(count($response->content), 3, 'content has 3 values'); - foreach ($response->content as $i => $st) { - $this->assertInstanceof('cmi5\Statement', $st, "$i: is statement"); - $id = $st->getId(); - $this->assertTrue(isset($id), "$i: id set"); - } - } - - public function testSaveStatementsWithAttachments() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $attachment1 = [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'RemoteLRSTest::testSaveStatements'], - 'contentType' => 'text/plain; charset=ascii', - 'content' => 'Attachment 1 content created at: ' . Util::getTimestamp() - ]; - $attachment2 = [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'RemoteLRSTest::testSaveStatements'], - 'contentType' => 'text/plain; charset=ascii', - 'content' => 'Attachment 2 content created at: ' . Util::getTimestamp() - ]; - $statements = [ - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]), - 'attachments' => [ - $attachment1 - ] - ], - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/2' - ]), - 'attachments' => [ - // provide a matching attachment to make sure only 2 in request - $attachment1, - $attachment2 - ] - ] - ]; - - $response = $lrs->saveStatements($statements); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertTrue(is_array($response->content), 'content is array'); - $this->assertSame(count($response->content), 2, 'content has 2 values'); - foreach ($response->content as $i => $st) { - $this->assertInstanceof('cmi5\Statement', $st, "$i: is statement"); - $id = $st->getId(); - $this->assertTrue(isset($id), "$i: id set"); - } - } - - public function testRetrieveStatement() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - - $saveResponse = $lrs->saveStatement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]) - ] - ); - if (! $saveResponse->success) { - $this->fail("save statement setup failed: " . $saveResponse->content); - } - - $response = $lrs->retrieveStatement($saveResponse->content->getId()); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success); - $this->assertInstanceOf('cmi5\Statement', $response->content); - } - - public function testRetrieveStatementWithAttachments() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $content = json_encode(['foo' => 'bar']); - - $saveResponse = $lrs->saveStatement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]), - 'attachments' => [ - new Attachment([ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'Test Display'], - 'contentType' => 'application/json', - 'content' => $content, - ]) - ] - ] - ); - $this->assertTrue($saveResponse->success, 'save succeeded'); - if (! $saveResponse->success) { - $this->fail("save statement setup failed: " . $saveResponse->content); - } - - $response = $lrs->retrieveStatement($saveResponse->content->getId(), [ 'attachments' => true ]); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success); - $this->assertInstanceOf('cmi5\Statement', $response->content); - $this->assertTrue(count($response->content->getAttachments()) === 1, 'attachment count'); - $this->assertSame($content, $response->content->getAttachments()[0]->getContent(), 'attachment content'); - } - - public function testRetrieveVoidedStatement() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - - $saveResponse = $lrs->saveStatement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID - ]) - ] - ); - if (! $saveResponse->success) { - $this->fail("save statement setup failed: " . $saveResponse->content); - } - - $voidResponse = $lrs->saveStatement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => Verb::Voided(), - 'object' => new StatementRef([ - 'id' => $saveResponse->content->getId() - ]) - ] - ); - if (! $voidResponse->success) { - $this->fail("void statement setup failed: " . $voidResponse->content); - } - - $retrieveResponse = $lrs->retrieveVoidedStatement($saveResponse->content->getId()); - - $this->assertInstanceOf('cmi5\LRSResponse', $retrieveResponse); - $this->assertTrue($retrieveResponse->success); - $this->assertInstanceOf('cmi5\Statement', $retrieveResponse->content); - } - - public function testQueryStatements() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->queryStatements(['limit' => 4]); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertInstanceOf('cmi5\StatementsResult', $response->content); - } - - public function testQueryStatementsWithAttachments() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->queryStatements(['limit' => 4, 'attachments' => true]); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertInstanceOf('cmi5\StatementsResult', $response->content); - } - - public function testMoreStatements() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $queryResponse = $lrs->queryStatements(['limit' => 1]); - if (! $queryResponse->success) { - $this->fail("query statements setup failed: " . $queryResponse->content); - } - - if (! $queryResponse->content->getMore()) { - $this->markTestSkipped('No more property in StatementsResult (not enough statements in endpoint?)'); - } - - $response = $lrs->moreStatements($queryResponse->content); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertInstanceOf('cmi5\StatementsResult', $response->content, 'content'); - } - - public function testMoreStatementsWithAttachments() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $queryResponse = $lrs->queryStatements(['limit' => 1, 'attachments' => true]); - if (! $queryResponse->success) { - $this->fail("query statements setup failed: " . $queryResponse->content); - } - - if (! $queryResponse->content->getMore()) { - $this->markTestSkipped('No more property in StatementsResult (not enough statements in endpoint?)'); - } - - $response = $lrs->moreStatements($queryResponse->content); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, 'success'); - $this->assertInstanceOf('cmi5\StatementsResult', $response->content, 'content'); - } - - public function testRetrieveStateIds() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->retrieveStateIds( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - array( - 'registration' => Util::getUUID(), - 'since' => '2014-01-07T08:24:30Z' - ) - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testDeleteState() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->deleteState( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - 'testKey', - [ - 'registration' => Util::getUUID() - ] - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testClearState() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->clearState( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - [ - 'registration' => Util::getUUID() - ] - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testRetrieveActivityProfileIds() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->retrieveActivityProfileIds( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - array( - 'since' => '2014-01-07T08:24:30Z' - ) - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testRetrieveActivityProfile() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->saveActivityProfile( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - 'testKey', - json_encode(['testProperty' => 'testValue']), - [ - 'contentType' => 'application/json', - ] - ); - if (! $response->success) { - $this->fail("save activity profile setup failed: " . $response->content); - } - - $response = $lrs->retrieveActivityProfile( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - 'testKey' - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testSaveActivityProfile() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->saveActivityProfile( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - 'testKey', - json_encode(['testProperty' => 'testValue']), - [ - 'contentType' => 'application/json', - ] - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testDeleteActivityProfile() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->deleteActivityProfile( - new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ), - 'testKey' - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testRetrieveActivity() { - $testActivity = new Activity([ - 'id' => COMMON_ACTIVITY_ID. '/testRetrieveActivity', - 'definition' => [ - 'name' => [ - 'en' => 'This is a test activity.' - ] - ] - ]); - - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => $testActivity - ] - ); - $response = $lrs->saveStatement($statement); - if (! $response->success) { - $this->fail("save statement setup failed: " . $response->content); - } - - $response = $lrs->retrieveActivity($testActivity->getId()); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertEquals($testActivity, $response->content, 'retrieved activity'); - } - - public function testRetrieveActivityWithHttpAcceptLanguageHeader() { - $_SERVER['HTTP_ACCEPT_LANGUAGE'] = 'en-US'; - $this->testRetrieveActivity(); - } - - public function testRetrieveAgentProfileIds() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->retrieveAgentProfileIds( - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - array( - 'since' => '2014-01-07T08:24:30Z' - ) - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testRetrieveAgentProfile() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->retrieveAgentProfile( - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - 'testKey' - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testDeleteAgentProfile() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - $response = $lrs->deleteAgentProfile( - new Agent( - [ 'mbox' => COMMON_MBOX ] - ), - 'testKey' - ); - - $this->assertInstanceOf('cmi5\LRSResponse', $response); - } - - public function testRetrievePerson() { - $lrs = new RemoteLRS(self::$endpoint, self::$version, self::$username, self::$password); - - $testAgent = new Agent( - [ - 'mbox' => COMMON_MBOX. '.testretrieveperson', - 'name' => COMMON_NAME - ] - ); - - $testPerson = new Person( - [ - 'mbox' => [ COMMON_MBOX. '.testretrieveperson' ], - 'name' => [ COMMON_NAME ] - ] - ); - - $response = $lrs->retrievePerson($testAgent); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertEquals($testPerson, $response->content, 'retrieved person'); - } -} diff --git a/cmi5PHP/tests/ResultTest.php b/cmi5PHP/tests/ResultTest.php deleted file mode 100755 index b4936f1..0000000 --- a/cmi5PHP/tests/ResultTest.php +++ /dev/null @@ -1,292 +0,0 @@ -assertInstanceOf('cmi5\Result', $obj); - foreach ($this->emptyProperties as $property) { - $this->assertAttributeEmpty($property, $obj, "$property empty"); - } - foreach ($this->nonEmptyProperties as $property) { - $this->assertAttributeNotEmpty($property, $obj, "$property not empty"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\Result')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\Result')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\Result')); - } - - // TODO: need more robust test (happy-path) - public function testAsVersion() { - $args = [ - 'success' => true, - 'completion' => true, - 'duration' => 'PT15S', - 'response' => 'a', - 'score' => [ - 'raw' => 100, - 'scaled' => 1, - 'min' => 0, - 'max' => 100 - ] - ]; - $obj = new Result($args); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionScoreEmpty() { - $args = [ - 'score' => [] - ]; - - $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['score']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionScoreZeroRaw() { - $args = [ - 'score' => [ - 'raw' => 0, - ] - ]; - - $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionResponseEmptyString() { - $args = [ - 'response' => '' - ]; - - $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionDurationEmptyString() { - $args = [ - 'duration' => '' - ]; - - $obj = Result::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['duration']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testCompareWithSignature() { - $score1 = new Score( - [ - 'raw' => 97, - 'scaled' => 0.97, - 'min' => 0, - 'max' => 100 - ] - ); - $score2 = new Score( - [ - 'raw' => 15, - 'scaled' => 0.50 - ] - ); - $extensions1 = new Extensions( - [ - COMMON_EXTENSION_ID_1 => 'test1', - COMMON_EXTENSION_ID_2 => 'test2' - ] - ); - $extensions2 = new Extensions( - [ - COMMON_EXTENSION_ID_1 => 'test1' - ] - ); - - $full = [ - 'success' => true, - 'completion' => true, - 'duration' => 'PT15S', - 'response' => 'a', - 'score' => $score1, - 'extensions' => $extensions1 - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'success', - 'objArgs' => ['success' => true] - ], - [ - 'description' => 'completion', - 'objArgs' => ['completion' => true] - ], - [ - 'description' => 'duration', - 'objArgs' => ['duration' => 'PT15S'] - ], - [ - 'description' => 'response', - 'objArgs' => ['response' => 'a'] - ], - [ - 'description' => 'score', - 'objArgs' => ['score' => $score1] - ], - [ - 'description' => 'extensions', - 'objArgs' => ['extensions' => $extensions1] - ], - [ - 'description' => 'all', - 'objArgs' => [ - 'success' => false, - 'completion' => false, - 'duration' => 'PT15S', - 'response' => 'b', - 'score' => $score2, - 'extensions' => $extensions2 - ] - ], - [ - 'description' => 'success only: mismatch', - 'objArgs' => ['success' => true ], - 'sigArgs' => ['success' => false ], - 'reason' => 'Comparison of success failed: value is not the same' - ], - [ - 'description' => 'completion only: mismatch', - 'objArgs' => ['completion' => true ], - 'sigArgs' => ['completion' => false ], - 'reason' => 'Comparison of completion failed: value is not the same' - ], - [ - 'description' => 'duration only: mismatch', - 'objArgs' => ['duration' => 'PT15S' ], - 'sigArgs' => ['duration' => 'PT180S' ], - 'reason' => 'Comparison of duration failed: value is not the same' - ], - [ - 'description' => 'response only: mismatch', - 'objArgs' => ['response' => 'a' ], - 'sigArgs' => ['response' => 'b' ], - 'reason' => 'Comparison of response failed: value is not the same' - ], - [ - 'description' => 'score only: mismatch', - 'objArgs' => ['score' => $score1 ], - 'sigArgs' => ['score' => $score2 ], - 'reason' => 'Comparison of score failed: Comparison of scaled failed: value is not the same' - ], - [ - 'description' => 'extensions only: mismatch', - 'objArgs' => ['extensions' => $extensions1 ], - 'sigArgs' => ['extensions' => $extensions2 ], - 'reason' => 'Comparison of extensions failed: http://id.cmi5api.com/extension/location not in signature' - ], - [ - 'description' => 'full: success mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['success' => false]), - 'reason' => 'Comparison of success failed: value is not the same' - ], - [ - 'description' => 'full: completion mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['completion' => false]), - 'reason' => 'Comparison of completion failed: value is not the same' - ], - [ - 'description' => 'full: duration mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['duration' => 'PT150S']), - 'reason' => 'Comparison of duration failed: value is not the same' - ], - [ - 'description' => 'full: response mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['response' => 'b']), - 'reason' => 'Comparison of response failed: value is not the same' - ], - [ - 'description' => 'full: score mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['score' => $score2]), - 'reason' => 'Comparison of score failed: Comparison of scaled failed: value is not the same' - ], - [ - 'description' => 'full: extensions mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['extensions' => $extensions2]), - 'reason' => 'Comparison of extensions failed: http://id.cmi5api.com/extension/location not in signature' - ] - ]; - $this->runSignatureCases("cmi5\Result", $cases); - } -} diff --git a/cmi5PHP/tests/ScoreTest.php b/cmi5PHP/tests/ScoreTest.php deleted file mode 100755 index 5af145b..0000000 --- a/cmi5PHP/tests/ScoreTest.php +++ /dev/null @@ -1,305 +0,0 @@ -assertInstanceOf('cmi5\Score', $obj); - foreach ($this->emptyProperties as $property) { - $this->assertAttributeEmpty($property, $obj, "$property empty"); - } - } - - public function testUsesArraySetterTrait() { - $this->assertContains('cmi5\ArraySetterTrait', class_uses('cmi5\Score')); - } - - public function testUsesFromJSONTrait() { - $this->assertContains('cmi5\FromJSONTrait', class_uses('cmi5\Score')); - } - - public function testUsesAsVersionTrait() { - $this->assertContains('cmi5\AsVersionTrait', class_uses('cmi5\Score')); - } - - public function testScaled() { - $score = new Score; - $score->setScaled(0.9); - - $this->assertEquals($score->getScaled(), 0.9); - $this->assertInternalType('float', $score->getScaled()); - } - - public function testSetScaledBelowMin() { - $this->setExpectedException( - 'InvalidArgumentException', - sprintf('Value must be greater than or equal to %s [-5]', Score::SCALE_MIN) - ); - $score = new Score; - $score->setScaled(-5); - } - - public function testSetScaledAboveMax() { - $this->setExpectedException( - 'InvalidArgumentException', - sprintf('Value must be less than or equal to %s [5]', Score::SCALE_MAX) - ); - $score = new Score; - $score->setScaled(5); - } - - public function testRaw() { - $score = new Score; - $score->setRaw(90); - - $this->assertEquals($score->getRaw(), 90); - $this->assertInternalType('float', $score->getRaw()); - - $score = new Score(['min' => 65, 'max' => 85]); - $score->setRaw(75); - $this->assertEquals($score->getRaw(), 75, 'between min and max'); - - $score = new Score(['min' => 65]); - $score->setRaw(65); - $this->assertEquals($score->getRaw(), 65, 'same as min'); - - $score = new Score(['max' => 65]); - $score->setRaw(65); - $this->assertEquals($score->getRaw(), 65, 'same as max'); - } - - public function testSetRawBelowMin() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be greater than or equal to \'min\' (60) [50]' - ); - $score = new Score(['min' => 60]); - $score->setRaw(50); - } - - public function testSetRawAboveMax() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be less than or equal to \'max\' (90) [95]' - ); - $score = new Score(['max' => 90]); - $score->setRaw(95); - } - - public function testMin() { - $score = new Score; - $score->setMin(9); - - $this->assertEquals($score->getMin(), 9); - $this->assertInternalType('float', $score->getMin()); - - $score = new Score(['raw' => 65, 'max' => 85]); - $score->setMin(35); - $this->assertEquals($score->getMin(), 35, 'below raw'); - - $score = new Score(['raw' => 35, 'max' => 85]); - $score->setMin(35); - $this->assertEquals($score->getMin(), 35, 'equal to raw'); - } - - public function testSetMinAboveRaw() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be less than or equal to \'raw\' (50) [60]' - ); - $score = new Score(['raw' => 50]); - $score->setMin(60); - } - - public function testSetMinAboveMax() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be less than \'max\' (90) [95]' - ); - $score = new Score(['max' => 90]); - $score->setMin(95); - } - - public function testMax() { - $score = new Score; - $score->setMax(96.3); - - $this->assertEquals($score->getMax(), 96.3); - $this->assertInternalType('float', $score->getMax()); - - $score = new Score(['raw' => 65, 'min' => 35]); - $score->setMax(85.4); - $this->assertEquals($score->getMax(), 85.4, 'above raw'); - - $score = new Score(['raw' => 35, 'min' => 15]); - $score->setMax(35); - $this->assertEquals($score->getMax(), 35, 'equal to raw'); - } - - public function testSetMaxBelowRaw() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be greater than or equal to \'raw\' (60) [50]' - ); - $score = new Score(['raw' => 60]); - $score->setMax(50); - } - - public function testSetMaxBelowMin() { - $this->setExpectedException( - 'InvalidArgumentException', - 'Value must be greater than \'min\' (10) [5]' - ); - $score = new Score(['min' => 10]); - $score->setMax(5); - } - - /** - * @dataProvider asVersionDataProvider - */ - public function testAsVersion($args) { - $obj = new Score($args); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "version: 1.0.0"); - } - - public function asVersionDataProvider() { - return [ - 'basic' => [ - [ - 'raw' => '1.5', - 'min' => '1.0', - 'max' => '2.0', - 'scaled' => '.95' - ] - ], - 'empty' => [[]], - 'zero raw' => [ - [ 'raw' => 0 ] - ], - 'zero scaled' => [ - [ 'scaled' => 0 ] - ], - 'multiple zeros' => [ - [ - 'raw' => '0', - 'min' => '-1.0', - 'max' => 2.0, - 'scaled' => 0 - ] - ] - ]; - } - - public function testCompareWithSignature() { - $full = [ - 'raw' => 97, - 'scaled' => 0.97, - 'min' => 0, - 'max' => 100 - ]; - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'raw', - 'objArgs' => ['raw' => 97] - ], - [ - 'description' => 'scaled', - 'objArgs' => ['scaled' => 0.97] - ], - [ - 'description' => 'min', - 'objArgs' => ['min' => 60] - ], - [ - 'description' => 'max', - 'objArgs' => ['max' => 99] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - [ - 'description' => 'raw only: mismatch', - 'objArgs' => ['raw' => 97 ], - 'sigArgs' => ['raw' => 87 ], - 'reason' => 'Comparison of raw failed: value is not the same' - ], - [ - 'description' => 'scaled only: mismatch', - 'objArgs' => ['scaled' => 0.97 ], - 'sigArgs' => ['scaled' => 0.87 ], - 'reason' => 'Comparison of scaled failed: value is not the same' - ], - [ - 'description' => 'min only: mismatch', - 'objArgs' => ['min' => 0 ], - 'sigArgs' => ['min' => 1 ], - 'reason' => 'Comparison of min failed: value is not the same' - ], - [ - 'description' => 'max only: mismatch', - 'objArgs' => ['max' => 97 ], - 'sigArgs' => ['max' => 100 ], - 'reason' => 'Comparison of max failed: value is not the same' - ], - [ - 'description' => 'full: raw mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['raw' => 79]), - 'reason' => 'Comparison of raw failed: value is not the same' - ], - [ - 'description' => 'full: scaled mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['scaled' => 0.96]), - 'reason' => 'Comparison of scaled failed: value is not the same' - ], - [ - 'description' => 'full: min mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['min' => 1]), - 'reason' => 'Comparison of min failed: value is not the same' - ], - [ - 'description' => 'full: max mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['max' => 98]), - 'reason' => 'Comparison of max failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\Score", $cases); - } -} diff --git a/cmi5PHP/tests/SignatureComparisonTraitTest.php b/cmi5PHP/tests/SignatureComparisonTraitTest.php deleted file mode 100755 index 1b1864b..0000000 --- a/cmi5PHP/tests/SignatureComparisonTraitTest.php +++ /dev/null @@ -1,43 +0,0 @@ -assertFalse($result['success']); - $this->assertEquals("Comparison of $description failed: not a " . get_class($a) . " value", $result['reason']); - - $result = SignatureComparisonStub::runDoMatch([], false, $description); - - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of $description failed: not an array in signature", $result['reason']); - } -} diff --git a/cmi5PHP/tests/StateTest.php b/cmi5PHP/tests/StateTest.php deleted file mode 100755 index 2c40877..0000000 --- a/cmi5PHP/tests/StateTest.php +++ /dev/null @@ -1,55 +0,0 @@ - COMMON_ACTIVITY_ID, - 'definition' => [] - ]; - - $state = new State(); - $state->setActivity($args); - } - - public function testSetAgent() { - $obj = new State(); - $obj->setAgent(['mbox' => COMMON_MBOX]); - - $this->assertInstanceOf('cmi5\Agent', $obj->getAgent()); - - $group = new Group(); - $obj->setAgent(['objectType' => 'Group']); - - $this->assertInstanceOf('cmi5\Group', $obj->getAgent()); - } - - public function testExceptionOnInvalidRegistrationUUID() { - $this->setExpectedException( - "InvalidArgumentException", - 'arg1 must be a UUID' - ); - - $obj = new State(); - $obj->setRegistration('232....3.3..3./2/2/1m3m3m3'); - } -} diff --git a/cmi5PHP/tests/StatementBaseTest.php b/cmi5PHP/tests/StatementBaseTest.php deleted file mode 100755 index 1385ddf..0000000 --- a/cmi5PHP/tests/StatementBaseTest.php +++ /dev/null @@ -1,173 +0,0 @@ - 'Agent' - ]; - $obj->setTarget($ss); - $this->assertInstanceOf('cmi5\Agent', $obj->getTarget()); - } - - public function testSetTargetAsGroup() { - $obj = new StubStatementBase(); - $ss = [ - 'objectType' => 'Group' - ]; - $obj->setTarget($ss); - $this->assertInstanceOf('cmi5\Group', $obj->getTarget()); - } - - public function testSetTargetAsSubStatement() { - $obj = new StubStatementBase(); - $ss = [ - 'objectType' => 'SubStatement' - ]; - $obj->setTarget($ss); - $this->assertInstanceOf('cmi5\SubStatement', $obj->getTarget()); - } - - public function testSetTargetInvalidArgumentException() { - $badObjectType = 'imABadObjectType'; - $this->setExpectedException( - "InvalidArgumentException", - "arg1 must implement the StatementTargetInterface objectType not recognized:$badObjectType" - ); - $obj = new StubStatementBase(); - $ss = [ - 'objectType' => $badObjectType - ]; - $obj->setTarget($ss); - } - - public function testSetActorAsGroup() { - $obj = new StubStatementBase(); - $ss = [ - 'objectType' => 'Group' - ]; - $obj->setActor($ss); - $this->assertInstanceOf('cmi5\Group', $obj->getActor()); - } - - public function testSetTimestampInvalidArgumentException() { - $this->setExpectedException( - "InvalidArgumentException", - 'type of arg1 must be string or DateTime' - ); - - $obj = new StubStatementBase(); - $obj->setTimestamp(1); - } - - /** - * @dataProvider statementPropertyValueProvider - */ - public function testCompareWithSignaturePropertyMissing($property, $value) { - $signature = new \stdClass; - $setMethodName = 'set' . ucfirst($property); - - $obj = new StubStatementBase(); - $obj->$setMethodName($value); - - $result = $obj->compareWithSignature($signature); - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of $property failed: value not in signature", $result['reason']); - - $obj = new StubStatementBase(); - $signature->$property = $value; - - $result = $obj->compareWithSignature($signature); - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of $property failed: value not in this", $result['reason']); - } - - public function statementPropertyValueProvider() { - return [ - ['actor', new Agent()], - ['verb', new Verb()], - ['target', new Agent()], - ['context', new Context()], - ['result', new Result()], - ]; - } - - public function testCompareWithSignatureTimestampMissing() { - $timestamp = "2004-02-12T15:19:21+00:00"; - $signature = new \stdClass; - - $obj = new StubStatementBase(); - $obj->setTimestamp($timestamp); - - $result = $obj->compareWithSignature($signature); - - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of timestamp failed: value not in signature", $result['reason']); - - $obj = new StubStatementBase(); - $signature->timestamp = $timestamp; - - $result = $obj->compareWithSignature($signature); - - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of timestamp failed: value not in this", $result['reason']); - } - - public function testCompareWithSignatureTimestampNotEqual() { - $timestamp = "2004-02-12T15:19:21+00:00"; - $signature = new \stdClass; - - $obj = new StubStatementBase(); - $obj->setTimestamp($timestamp); - $signature->timestamp = "2005-02-12T15:19:21+00:00"; - - $result = $obj->compareWithSignature($signature); - - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of timestamp failed: value is not the same", $result['reason']); - - //Now check with microseconds. - $timestamp = "2012-07-08 11:14:15.638276"; - $obj = new StubStatementBase(); - $obj->setTimestamp($timestamp); - $signature->timestamp = "2012-07-08 11:14:15.638286"; - - $dt = new \DateTime($timestamp); - $dt2 = new \DateTime($signature->timestamp); - - $result = $obj->compareWithSignature($signature); - - $this->assertFalse($result['success']); - $this->assertEquals("Comparison of timestamp failed: value is not the same", $result['reason']); - } -} diff --git a/cmi5PHP/tests/StatementRefTest.php b/cmi5PHP/tests/StatementRefTest.php deleted file mode 100755 index b65822d..0000000 --- a/cmi5PHP/tests/StatementRefTest.php +++ /dev/null @@ -1,91 +0,0 @@ -assertInstanceOf('cmi5\StatementRef', $obj); - $this->assertAttributeEmpty('id', $obj, 'id empty'); - $this->assertAttributeNotEmpty('objectType', $obj, 'objectType not empty'); - } - - public function testGetObjectType() { - $obj = new StatementRef(); - $this->assertSame('StatementRef', $obj->getObjectType()); - } - - public function testId() { - $obj = new StatementRef(); - $id = Util::getUUID(); - $this->assertSame($obj, $obj->setId($id)); - $this->assertSame($id, $obj->getId()); - } - - public function testSetIdThrowsException() { - $this->setExpectedException( - 'InvalidArgumentException', - 'arg1 must be a UUID' - ); - $obj = new StatementRef(['id' => 'foo']); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = [ - 'id' => Util::getUUID(), - ]; - - $obj = StatementRef::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'StatementRef'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = StatementRef::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'StatementRef'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testCompareWithSignature() { - $success = ['success' => true, 'reason' => null]; - $failure = ['success' => false, 'reason' => null]; - - $id = Util::getUUID(); - $obj = new StatementRef(['id' => $id]); - $sig = new StatementRef(['id' => $id]); - - $this->assertSame($success, $obj->compareWithSignature($sig), 'id only: match'); - - $sig->setId(Util::getUUID()); - $failure['reason'] = 'Comparison of id failed: value is not the same'; - - $this->assertSame($failure, $obj->compareWithSignature($sig), 'id only: mismatch'); - } -} diff --git a/cmi5PHP/tests/StatementTest.php b/cmi5PHP/tests/StatementTest.php deleted file mode 100755 index e39b376..0000000 --- a/cmi5PHP/tests/StatementTest.php +++ /dev/null @@ -1,1028 +0,0 @@ -assertInstanceOf('cmi5\Statement', $obj); - $this->assertAttributeEmpty('id', $obj, 'id empty'); - $this->assertAttributeEmpty('actor', $obj, 'actor empty'); - $this->assertAttributeEmpty('verb', $obj, 'verb empty'); - $this->assertAttributeEmpty('target', $obj, 'target empty'); - $this->assertAttributeEmpty('context', $obj, 'context empty'); - $this->assertAttributeEmpty('result', $obj, 'result empty'); - $this->assertAttributeEmpty('timestamp', $obj, 'timestamp empty'); - $this->assertAttributeEmpty('stored', $obj, 'stored empty'); - $this->assertAttributeEmpty('authority', $obj, 'authority empty'); - $this->assertAttributeEmpty('version', $obj, 'version empty'); - } - - public function testFromJSONInvalidNull() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Statement::fromJSON(null); - } - - public function testFromJSONInvalidEmptyString() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Statement::fromJSON(''); - } - - public function testFromJSONInvalidMalformed() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Statement::fromJSON('{id:"some value"}'); - } - - public function testConstructionFromArrayWithId() { - $id = Util::getUUID(); - $cfg = [ - 'id' => $id, - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - ], - ]; - $obj = new Statement($cfg); - - $this->assertSame($obj->getId(), $id, 'id'); - } - - public function testStamp() { - $obj = new Statement(); - $obj->stamp(); - - $this->assertAttributeInternalType('string', 'timestamp', $obj, 'timestamp is string'); - $this->assertRegExp(Util::UUID_REGEX, $obj->getId(), 'id is UUId'); - } - - public function testSetId() { - $this->setExpectedException( - 'InvalidArgumentException', - 'arg1 must be a UUID "some invalid id"' - ); - - $obj = new Statement(); - $obj->setId('some invalid id'); - } - - public function testSetStoredInvalidArgumentException() { - $this->setExpectedException( - 'InvalidArgumentException', - 'type of arg1 must be string or DateTime' - ); - - $obj = new Statement(); - $obj->setStored(1); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = [ - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - 'display' => [ - 'en-US' => 'experienced' - ] - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [ - 'en-US' => 'Test', - ], - 'description' => [ - 'en-US' => 'Test description', - ], - 'extensions' => [ - 'http://someuri' => 'some value' - ], - ] - ], - 'context' => [ - 'contextActivities' => [ - 'parent' => [ - [ - 'id' => COMMON_ACTIVITY_ID . '/1', - 'definition' => [ - 'name' => [ - 'en-US' => 'Test: 1', - ], - ], - ] - ], - ], - 'registration' => Util::getUUID(), - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [ - 'raw' => '97', - 'min' => '65', - 'max' => '100', - 'scaled' => '.97' - ] - ], - 'version' => '1.0.0', - 'attachments' => [ - [ - 'usageType' => 'http://test', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'text/plain; charset=ascii', - 'length' => 0, - 'sha2' => hash('sha256', json_encode(['foo', 'bar'])) - ] - ] - ]; - - $obj = Statement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $obj->stamp(); - - $versioned = $obj->asVersion('1.0.0'); - - $args['id'] = $obj->getId(); - $args['timestamp'] = $obj->getTimestamp(); - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - $args['context']['contextActivities']['parent'][0]['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Statement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptySubObjects() { - $args = [ - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - 'display' => [] - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [], - 'description' => [], - 'extensions' => [], - ] - ], - 'context' => [ - 'contextActivities' => [ - 'parent' => [], - ], - 'registration' => Util::getUUID(), - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [] - ], - 'version' => '1.0.0', - 'attachments' => [] - ]; - - $obj = Statement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - unset($args['verb']['display']); - unset($args['object']['definition']['name']); - unset($args['object']['definition']['description']); - unset($args['object']['definition']['extensions']); - unset($args['context']['contextActivities']); - unset($args['result']['score']); - unset($args['attachments']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionSubObjectWithEmptyValue() { - $args = [ - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [ - 'en-US' => '' - ], - ] - ], - 'context' => [ - 'contextActivities' => [], - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [ - 'raw' => 0 - ] - ] - ]; - - $obj = Statement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - unset($args['context']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testCompareWithSignature() { - $id1 = Util::getUUID(); - $id2 = Util::getUUID(); - $actor1 = new Agent( - [ 'mbox' => COMMON_MBOX ] - ); - $actor2 = new Agent( - [ 'account' => [ 'homePage' => COMMON_ACCT_HOMEPAGE, 'name' => COMMON_ACCT_NAME ]] - ); - $verb1 = new Verb( - [ 'id' => COMMON_VERB_ID ] - ); - $verb2 = new Verb( - [ 'id' => COMMON_VERB_ID . '/2' ] - ); - $activity1 = new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ); - $activity2 = new Activity( - [ 'id' => COMMON_ACTIVITY_ID . '/2' ] - ); - $context1 = new Context( - [ 'registration' => Util::getUUID() ] - ); - $context2 = new Context( - [ - 'contextActivities' => [ - [ 'parent' => [ COMMON_ACTIVITY_ID . '/parent' ]], - [ 'grouping' => [ COMMON_ACTIVITY_ID ]] - ] - ] - ); - $result1 = new Result( - [ 'raw' => 87 ] - ); - $result2 = new Result( - [ 'response' => 'a' ] - ); - $timestamp1 = '2015-01-28T14:23:37.159Z'; - $timestamp1_tz = '2015-01-28T08:23:37.159-06:00'; - $timestamp1_subsecond = '2015-01-28T14:23:37.348Z'; - $timestamp2 = '2015-01-28T15:49:11.089Z'; - - $attachments1 = new Attachment( - [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'Test Display'], - 'contentType' => 'application/json', - 'content' => json_encode(['foo', 'bar']), - ] - ); - $attachments2 = new Attachment( - [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'Test Display'], - 'contentType' => 'application/json', - 'content' => json_encode(['bar', 'foo']), - ] - ); - - $full = [ - 'id' => $id1, - 'actor' => $actor1, - 'verb' => $verb1, - 'target' => $activity1, - 'context' => $context1, - 'result' => $result1, - 'timestamp' => $timestamp1, - 'attachments' => [ $attachments1 ] - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'id', - 'objArgs' => ['id' => $id1] - ], - [ - 'description' => 'actor', - 'objArgs' => ['actor' => $actor1] - ], - [ - 'description' => 'verb', - 'objArgs' => ['verb' => $verb1] - ], - [ - 'description' => 'object', - 'objArgs' => ['target' => $activity1] - ], - [ - 'description' => 'result', - 'objArgs' => ['result' => $result1] - ], - [ - 'description' => 'context', - 'objArgs' => ['context' => $context1] - ], - [ - 'description' => 'timestamp', - 'objArgs' => ['timestamp' => $timestamp1] - ], - [ - 'description' => 'attachments', - 'objArgs' => ['attachments' => [ $attachments1 ]] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - - // - // special case where timestamp marks the same point in time but - // is provided in a different timezone - // - [ - 'description' => 'timestamp timezone difference', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp1_tz] - ], - - // - // special case where we make sure sub-second precision is handled - // - [ - 'description' => 'timestamp subsecond difference', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp1_subsecond], - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - - [ - 'description' => 'id this only: mismatch', - 'objArgs' => ['id' => $id1], - 'sigArgs' => [], - 'reason' => 'Comparison of id failed: value not in signature' - ], - [ - 'description' => 'id sig only: mismatch', - 'objArgs' => [], - 'sigArgs' => ['id' => $id1], - 'reason' => 'Comparison of id failed: value not in this' - ], - [ - 'description' => 'id only: mismatch', - 'objArgs' => ['id' => $id1], - 'sigArgs' => ['id' => $id2], - 'reason' => 'Comparison of id failed: value is not the same' - ], - [ - 'description' => 'actor only: mismatch', - 'objArgs' => ['actor' => $actor1], - 'sigArgs' => ['actor' => $actor2], - 'reason' => 'Comparison of actor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'verb only: mismatch', - 'objArgs' => ['verb' => $verb1], - 'sigArgs' => ['verb' => $verb2], - 'reason' => 'Comparison of verb failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'object only: mismatch', - 'objArgs' => ['target' => $activity1], - 'sigArgs' => ['target' => $activity2], - 'reason' => 'Comparison of target failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'result only: mismatch', - 'objArgs' => ['result' => $result1], - 'sigArgs' => ['result' => $result2], - 'reason' => 'Comparison of result failed: Comparison of response failed: value not present in this or signature' - ], - [ - 'description' => 'context only: mismatch', - 'objArgs' => ['context' => $context1], - 'sigArgs' => ['context' => $context2], - 'reason' => 'Comparison of context failed: Comparison of registration failed: value not present in this or signature' - ], - [ - 'description' => 'timestamp only: mismatch', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp2], - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - [ - 'description' => 'attachments this only: mismatch', - 'objArgs' => ['attachments' => [$attachments1]], - 'sigArgs' => ['attachments' => []], - 'reason' => 'Comparison of attachments list failed: array lengths differ' - ], - [ - 'description' => 'attachments sig only: mismatch', - 'objArgs' => ['attachments' => []], - 'sigArgs' => ['attachments' => [$attachments2]], - 'reason' => 'Comparison of attachments list failed: array lengths differ' - ], - [ - 'description' => 'attachments only: mismatch', - 'objArgs' => ['attachments' => [$attachments1]], - 'sigArgs' => ['attachments' => [$attachments2]], - 'reason' => 'Comparison of attachment 0 failed: Comparison of sha2 failed: value is not the same' - ], - [ - 'description' => 'full: id mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['id' => $id2]), - 'reason' => 'Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: actor mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['actor' => $actor2]), - 'reason' => 'Comparison of actor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'full: verb mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['verb' => $verb2]), - 'reason' => 'Comparison of verb failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: target mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['target' => $activity2]), - 'reason' => 'Comparison of target failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: result mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['result' => $result2]), - 'reason' => 'Comparison of result failed: Comparison of response failed: value not present in this or signature' - ], - [ - 'description' => 'full: context mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['context' => $context2]), - 'reason' => 'Comparison of context failed: Comparison of registration failed: value not present in this or signature' - ], - [ - 'description' => 'full: timestamp mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['timestamp' => $timestamp2]), - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - [ - 'description' => 'full: attachments mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['attachments' => [$attachments2]]), - 'reason' => 'Comparison of attachment 0 failed: Comparison of sha2 failed: value is not the same' - ], - ]; - $this->runSignatureCases("cmi5\Statement", $cases); - } - - public function testHasAttachments() { - $stNoAttachments = new Statement(); - $this->assertFalse($stNoAttachments->hasAttachments()); - - $stWithAttachments = new Statement( - [ - 'attachments' => [ - [ - 'usageType' => 'http://test', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'text/plain; charset=ascii', - 'length' => 0, - 'sha2' => hash('sha256', json_encode(['foo', 'bar'])) - ] - ] - ] - ); - $this->assertTrue($stWithAttachments->hasAttachments()); - } - - public function testHasAttachmentWithContent() { - $content = 'Just some test content'; - - $stNoAttachments = new Statement(); - $this->assertFalse($stNoAttachments->hasAttachmentsWithContent()); - - $stWithAttachmentNoContent = new Statement( - [ - 'attachments' => [ - [ - 'usageType' => 'http://test', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'text/plain; charset=ascii', - 'length' => strlen($content), - 'sha2' => hash('sha256', $content) - ] - ] - ] - ); - $this->assertFalse($stWithAttachmentNoContent->hasAttachmentsWithContent()); - - $stWithAttachmentWithContent = new Statement( - [ - 'attachments' => [ - [ - 'usageType' => 'http://test', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'text/plain; charset=ascii', - 'length' => strlen($content), - 'sha2' => hash('sha256', $content), - 'content' => $content - ] - ] - ] - ); - $this->assertTrue($stWithAttachmentWithContent->hasAttachmentsWithContent()); - } - - public function testSignNoArgs() { - $obj = new Statement(); - - $this->setExpectedException( - 'PHPUnit_Framework_Error_Warning', - (getenv('TRAVIS_PHP_VERSION') == "hhvm" ? 'sign() expects at least 2 parameters, 0 given' : 'Missing argument 1') - ); - $obj->sign(); - } - - public function testSignOneArg() { - $obj = new Statement(); - - $this->setExpectedException( - 'PHPUnit_Framework_Error_Warning', - (getenv('TRAVIS_PHP_VERSION') == "hhvm" ? 'sign() expects at least 2 parameters, 1 given' : 'Missing argument 2') - ); - $obj->sign('test'); - } - - public function testSignNoActor() { - $this->setExpectedException( - 'InvalidArgumentException', - 'actor must be present in signed statement' - ); - - $obj = new Statement(); - $obj->sign('test', 'test'); - } - - public function testSignNoVerb() { - $this->setExpectedException( - 'InvalidArgumentException', - 'verb must be present in signed statement' - ); - - $obj = new Statement( - [ - 'actor' => ['mbox' => COMMON_MBOX] - ] - ); - $obj->sign('test', 'test'); - } - - public function testSignNoObject() { - $this->setExpectedException( - 'InvalidArgumentException', - 'object must be present in signed statement' - ); - - $obj = new Statement( - [ - 'actor' => ['mbox' => COMMON_MBOX], - 'verb' => [ 'id' => COMMON_VERB_ID ] - ] - ); - $obj->sign('test', 'test'); - } - - public function testSignInvalidAlgorithm() { - $this->setExpectedException( - 'InvalidArgumentException', - "Invalid signing algorithm: 'not right'" - ); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], ['algorithm' => 'not right']); - } - - public function testSignEmptyPassword() { - $this->setExpectedException( - 'Exception', - 'Unable to get private key: error:0906A068:PEM routines:PEM_do_header:bad password read' - ); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], ''); - } - - public function testSignInvalidPassword() { - $this->setExpectedExceptionRegExp( - 'Exception', - '/Unable to get private key: error:.*:bad decrypt/' - ); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], 'notthecorrectpasswordhopefully'); - } - - public function testSignInvalidX5cErrorToException() { - $this->setExpectedExceptionRegExp( - 'PHPUnit_Framework_Error', - '/supplied parameter cannot be coerced into an X509 certificate!/' - ); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], [ 'x5c' => 'invalid' ]); - } - - public function testSignInvalidX5cNoError() { - $this->setExpectedExceptionRegExp( - 'Exception', - '/Unable to read certificate for x5c inclusion: .*/' - ); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]) - ] - ); - @$obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], [ 'x5c' => 'invalid' ]); - } - - public function testVerifyNoSignature() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignAndVerify' - ]) - ] - ); - $result = $obj->verify(); - - $this->assertFalse($result['success'], 'success'); - $this->assertSame($result['reason'], 'Unable to locate signature attachment (usage type)', 'reason'); - } - - public function testVerifyInvalidJWS() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignAndVerify' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://adlnet.gov/expapi/attachments/signature', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'application/octet-stream', - 'content' => 'not a signature' - ] - ] - ] - ); - $result = $obj->verify(); - - $this->assertFalse($result['success'], 'success'); - $this->assertStringStartsWith( - 'Failed to load JWS', - $result['reason'], - 'reason' - ); - } - - public function testVerifyInvalidX5cErrorToException() { - $this->setExpectedExceptionRegExp( - 'PHPUnit_Framework_Error', - '/supplied parameter cannot be coerced into an X509 certificate!/' - ); - - $content = new JWS( - [ - 'alg' => 'RS256', - 'x5c' => ['notAValidCertificate'] - ] - ); - $content->setPayload(['prop' => 'val'], false); - $content->sign(openssl_pkey_get_private('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'])); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://adlnet.gov/expapi/attachments/signature', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'application/octet-stream', - 'content' => $content->getTokenString() - ] - ] - ] - ); - $result = $obj->verify(); - } - - public function testVerifyInvalidX5cNoError() { - $content = new JWS( - [ - 'alg' => 'RS256', - 'x5c' => ['notAValidCertificate'] - ] - ); - $content->setPayload(['prop' => 'val'], false); - $content->sign(openssl_pkey_get_private('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'])); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://adlnet.gov/expapi/attachments/signature', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'application/octet-stream', - 'content' => $content->getTokenString() - ] - ] - ] - ); - @$result = $obj->verify(); - - $this->assertFalse($result['success'], 'success'); - $this->assertSame('failed to read cert in x5c: error:0906D06C:PEM routines:PEM_read_bio:no start line', $result['reason'], 'reason'); - } - - public function testVerifyNoPubKey() { - $content = new JWS( - [ - 'alg' => 'RS256' - ] - ); - $content->setPayload(['prop' => 'val'], false); - $content->sign(openssl_pkey_get_private('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'])); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://adlnet.gov/expapi/attachments/signature', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'application/octet-stream', - 'content' => $content->getTokenString() - ] - ] - ] - ); - $result = $obj->verify(); - - $this->assertFalse($result['success'], 'success'); - $this->assertSame($result['reason'], 'No public key found or provided for verification', 'reason'); - } - - public function testVerifyIncorrectPubKey() { - $content = new JWS( - [ - 'alg' => 'RS256' - ] - ); - $content->setPayload(['prop' => 'val'], false); - $content->sign(openssl_pkey_get_private('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'])); - - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignNoPassword' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://adlnet.gov/expapi/attachments/signature', - 'display' => ['en-US' => 'test display'], - 'contentType' => 'application/octet-stream', - 'content' => $content->getTokenString() - ] - ] - ] - ); - - $newKey = openssl_pkey_new( - [ - 'private_key_bits' => 2048, - 'private_key_type' => OPENSSL_KEYTYPE_RSA - ] - ); - $pubKey = openssl_pkey_get_details($newKey); - $pubKey = $pubKey["key"]; - - $result = $obj->verify(['publicKey' => $pubKey]); - - $this->assertFalse($result['success'], 'success'); - $this->assertSame($result['reason'], 'Failed to verify signature', 'reason'); - } - - public function testVerifyDiffStatement() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testVerifyDiffStatement' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], ['x5c' => 'file://' . $GLOBALS['KEYs']['public']]); - - $diff = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testVerifyDiffStatement-diff' - ]), - 'attachments' => $obj->getAttachments() - ] - ); - $result = $diff->verify(); - - $this->assertFalse($result['success'], 'success'); - $this->assertSame($result['reason'], 'Statement to signature comparison failed: Comparison of target failed: Comparison of id failed: value is not the same', 'reason'); - } - - public function testSignAndVerify() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignAndVerify' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password']); - - $attachment = $obj->getAttachments()[0]; - - $this->assertSame($attachment->getUsageType(), 'http://adlnet.gov/expapi/attachments/signature', 'usage type value'); - $this->assertSame($attachment->getContentType(), 'application/octet-stream', 'content type value'); - - $result = $obj->verify(['publicKey' => 'file://' . $GLOBALS['KEYs']['public']]); - if (! $result['success']) { - print $result['reason']; - } - $this->assertTrue($result['success'], 'success return value'); - } - - public function testSignAndVerifyFromEmbedded() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignAndVerifyFromEmbedded' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], [ 'x5c' => 'file://' . $GLOBALS['KEYs']['public'] ]); - - $attachment = $obj->getAttachments()[0]; - - $this->assertSame($attachment->getUsageType(), 'http://adlnet.gov/expapi/attachments/signature', 'usage type value'); - $this->assertSame($attachment->getContentType(), 'application/octet-stream', 'content type value'); - - $result = $obj->verify(); - if (! $result['success']) { - print $result['reason']; - } - $this->assertTrue($result['success'], 'success return value'); - } - - public function testSignAndVerifyNoPubKey() { - $obj = new Statement( - [ - 'actor' => [ 'mbox' => COMMON_MBOX ], - 'verb' => [ 'id' => COMMON_VERB_ID ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementTest/testSignAndVerify' - ]) - ] - ); - $obj->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password']); - - $result = $obj->verify(); - $this->assertFalse($result['success'], 'success return value'); - $this->assertSame($result['reason'], 'No public key found or provided for verification', 'reason'); - } -} diff --git a/cmi5PHP/tests/StatementVariationsTest.php b/cmi5PHP/tests/StatementVariationsTest.php deleted file mode 100755 index c3ccb66..0000000 --- a/cmi5PHP/tests/StatementVariationsTest.php +++ /dev/null @@ -1,227 +0,0 @@ - [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Basic' - ]) - ] - ); - - foreach (self::$lrss as $lrs) { - $response = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, "successful request"); - } - } - - public function testAttachmentsMetaOnly() { - $text_content = "Content created at: " . Util::getTimestamp(); - - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Basic' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsMetaOnly'], - 'contentType' => 'text/plain; charset=ascii', - 'length' => 25, - 'sha2' => hash('sha256', $text_content), - 'fileUrl' => 'http://cmi5api.com/cmi5PHP/Test/AttachmentFileUrl' - ] - ] - ] - ); - - foreach (self::$lrss as $lrs) { - $response = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, "successful request"); - } - } - - public function testAttachmentsString() { - $text_content = "Content created at: " . Util::getTimestamp(); - - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/AttachmentsString' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsString'], - 'contentType' => 'text/plain; charset=ascii', - 'content' => $text_content - ] - ] - ] - ); - - foreach (self::$lrss as $lrs) { - $response = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $response); - $this->assertTrue($response->success, "successful request"); - } - } - - public function testAttachmentsBinaryFile() { - $file = 'tests/files/image.jpg'; - - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/AttachmentsBinary' - ]), - 'attachments' => [ - [ - 'usageType' => 'http://id.cmi5api.com/attachment/supporting_media', - 'display' => ['en-US' => 'StatementVariantsTest::testAttachmentsString'], - 'contentType' => 'text/plain; charset=ascii', - 'content' => file_get_contents($file) - ] - ] - ] - ); - - foreach (self::$lrss as $lrs) { - $saveResponse = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $saveResponse); - $this->assertTrue($saveResponse->success, "successful request"); - - $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); - $this->assertInstanceOf('cmi5\LRSResponse', $retrieveResponse); - $this->assertTrue($retrieveResponse->success); - $this->assertInstanceOf('cmi5\Statement', $retrieveResponse->content); - - $this->assertSame($retrieveResponse->content->getAttachments()[0]->getSha2(), hash('sha256', file_get_contents($file)), 'verify content'); - } - } - - public function testSignedAndVerified() { - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Signed' - ]) - ] - ); - $statement->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password']); - - foreach (self::$lrss as $lrs) { - $saveResponse = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $saveResponse); - $this->assertTrue($saveResponse->success, "successful request"); - - $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); - $this->assertInstanceOf('cmi5\LRSResponse', $retrieveResponse); - $this->assertTrue($retrieveResponse->success); - $this->assertInstanceOf('cmi5\Statement', $retrieveResponse->content); - - $verificationResult = $retrieveResponse->content->verify(['publicKey' => 'file://' . $GLOBALS['KEYs']['public']]); - $this->assertTrue($verificationResult['success'], 'verify signature'); - } - } - - public function testSignedAndVerifiedX5c() { - $statement = new Statement( - [ - 'actor' => [ - 'mbox' => COMMON_MBOX - ], - 'verb' => [ - 'id' => COMMON_VERB_ID - ], - 'object' => new Activity([ - 'id' => COMMON_ACTIVITY_ID . '/StatementVariationsTest/Signed' - ]) - ] - ); - $statement->sign('file://' . $GLOBALS['KEYs']['private'], $GLOBALS['KEYs']['password'], ['x5c' => 'file://' . $GLOBALS['KEYs']['public']]); - - foreach (self::$lrss as $lrs) { - $saveResponse = $lrs->saveStatement($statement); - $this->assertInstanceOf('cmi5\LRSResponse', $saveResponse); - if (! $saveResponse->success) { - print_r($saveResponse); - } - $this->assertTrue($saveResponse->success, "successful request"); - - $retrieveResponse = $lrs->retrieveStatement($saveResponse->content->getId(), ['attachments' => true]); - $this->assertInstanceOf('cmi5\LRSResponse', $retrieveResponse); - $this->assertTrue($retrieveResponse->success); - $this->assertInstanceOf('cmi5\Statement', $retrieveResponse->content); - - $verificationResult = $retrieveResponse->content->verify(); - $this->assertTrue($verificationResult['success'], 'verify signature'); - } - } -} diff --git a/cmi5PHP/tests/SubStatementTest.php b/cmi5PHP/tests/SubStatementTest.php deleted file mode 100755 index 5614b43..0000000 --- a/cmi5PHP/tests/SubStatementTest.php +++ /dev/null @@ -1,387 +0,0 @@ -assertInstanceOf('cmi5\StatementBase', $obj); - } - - public function testGetObjectType() { - $obj = new SubStatement(); - $this->assertSame('SubStatement', $obj->getObjectType()); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = [ - 'timestamp' => '2015-01-28T14:23:37.159Z', - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - 'display' => [ - 'en-US' => 'experienced' - ] - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [ - 'en-US' => 'Test', - ], - 'description' => [ - 'en-US' => 'Test description', - ], - 'extensions' => [ - 'http://someuri' => 'some value' - ], - ] - ], - 'context' => [ - 'contextActivities' => [ - 'parent' => [ - [ - 'id' => COMMON_ACTIVITY_ID . '/1', - 'definition' => [ - 'name' => [ - 'en-US' => 'Test: 1', - ], - ], - ] - ], - ], - 'registration' => Util::getUUID(), - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [ - 'raw' => '97', - 'min' => '65', - 'max' => '100', - 'scaled' => '.97' - ] - ], - ]; - - $obj = SubStatement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'SubStatement'; - $args['timestamp'] = $obj->getTimestamp(); - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - $args['context']['contextActivities']['parent'][0]['objectType'] = 'Activity'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = SubStatement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'SubStatement'; - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptySubObjects() { - $args = [ - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - 'display' => [] - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [], - 'description' => [], - 'extensions' => [], - ] - ], - 'context' => [ - 'contextActivities' => [ - 'parent' => [], - ], - 'registration' => Util::getUUID(), - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [] - ], - ]; - - $obj = SubStatement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'SubStatement'; - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - unset($args['verb']['display']); - unset($args['object']['definition']['name']); - unset($args['object']['definition']['description']); - unset($args['object']['definition']['extensions']); - unset($args['context']['contextActivities']); - unset($args['result']['score']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionSubObjectWithEmptyValue() { - $args = [ - 'actor' => [ - 'mbox' => COMMON_MBOX, - ], - 'verb' => [ - 'id' => COMMON_VERB_ID, - ], - 'object' => [ - 'id' => COMMON_ACTIVITY_ID, - 'definition' => [ - 'type' => 'Invalid type', - 'name' => [ - 'en-US' => '' - ], - ] - ], - 'context' => [ - 'contextActivities' => [], - ], - 'result' => [ - 'completion' => true, - 'success' => false, - 'score' => [ - 'raw' => 0 - ] - ] - ]; - - $obj = SubStatement::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $args['objectType'] = 'SubStatement'; - $args['actor']['objectType'] = 'Agent'; - $args['object']['objectType'] = 'Activity'; - unset($args['context']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - - public function testCompareWithSignature() { - $actor1 = new Agent( - [ 'mbox' => COMMON_MBOX ] - ); - $actor2 = new Agent( - [ 'account' => [ 'homePage' => COMMON_ACCT_HOMEPAGE, 'name' => COMMON_ACCT_NAME ]] - ); - $verb1 = new Verb( - [ 'id' => COMMON_VERB_ID ] - ); - $verb2 = new Verb( - [ 'id' => COMMON_VERB_ID . '/2' ] - ); - $activity1 = new Activity( - [ 'id' => COMMON_ACTIVITY_ID ] - ); - $activity2 = new Activity( - [ 'id' => COMMON_ACTIVITY_ID . '/2' ] - ); - $context1 = new Context( - [ 'registration' => Util::getUUID() ] - ); - $context2 = new Context( - [ - 'contextActivities' => [ - [ 'parent' => [ COMMON_ACTIVITY_ID . '/parent' ]], - [ 'grouping' => [ COMMON_ACTIVITY_ID ]] - ] - ] - ); - $result1 = new Result( - [ 'raw' => 87 ] - ); - $result2 = new Result( - [ 'response' => 'a' ] - ); - $timestamp1 = '2015-01-28T14:23:37.159Z'; - $timestamp1_tz = '2015-01-28T08:23:37.159-06:00'; - $timestamp1_subsecond = '2015-01-28T14:23:37.348Z'; - $timestamp2 = '2015-01-28T15:49:11.089Z'; - - $full = [ - 'actor' => $actor1, - 'verb' => $verb1, - 'target' => $activity1, - 'context' => $context1, - 'result' => $result1, - 'timestamp' => $timestamp1 - ]; - - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'actor', - 'objArgs' => ['actor' => $actor1] - ], - [ - 'description' => 'verb', - 'objArgs' => ['verb' => $verb1] - ], - [ - 'description' => 'object', - 'objArgs' => ['target' => $activity1] - ], - [ - 'description' => 'result', - 'objArgs' => ['result' => $result1] - ], - [ - 'description' => 'context', - 'objArgs' => ['context' => $context1] - ], - [ - 'description' => 'timestamp', - 'objArgs' => ['timestamp' => $timestamp1] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - - // - // special case where timestamp marks the same point in time but - // is provided in a different timezone - // - [ - 'description' => 'timestamp timezone difference', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp1_tz] - ], - - // - // special case where we make sure sub-second precision is handled - // - [ - 'description' => 'timestamp subsecond difference', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp1_subsecond], - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - - [ - 'description' => 'actor only: mismatch', - 'objArgs' => ['actor' => $actor1], - 'sigArgs' => ['actor' => $actor2], - 'reason' => 'Comparison of actor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'verb only: mismatch', - 'objArgs' => ['verb' => $verb1], - 'sigArgs' => ['verb' => $verb2], - 'reason' => 'Comparison of verb failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'object only: mismatch', - 'objArgs' => ['target' => $activity1], - 'sigArgs' => ['target' => $activity2], - 'reason' => 'Comparison of target failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'result only: mismatch', - 'objArgs' => ['result' => $result1], - 'sigArgs' => ['result' => $result2], - 'reason' => 'Comparison of result failed: Comparison of response failed: value not present in this or signature' - ], - [ - 'description' => 'context only: mismatch', - 'objArgs' => ['context' => $context1], - 'sigArgs' => ['context' => $context2], - 'reason' => 'Comparison of context failed: Comparison of registration failed: value not present in this or signature' - ], - [ - 'description' => 'timestamp only: mismatch', - 'objArgs' => ['timestamp' => $timestamp1], - 'sigArgs' => ['timestamp' => $timestamp2], - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - [ - 'description' => 'full: actor mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['actor' => $actor2]), - 'reason' => 'Comparison of actor failed: Comparison of mbox failed: value is not the same' - ], - [ - 'description' => 'full: verb mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['verb' => $verb2]), - 'reason' => 'Comparison of verb failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: target mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['target' => $activity2]), - 'reason' => 'Comparison of target failed: Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: result mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['result' => $result2]), - 'reason' => 'Comparison of result failed: Comparison of response failed: value not present in this or signature' - ], - [ - 'description' => 'full: context mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['context' => $context2]), - 'reason' => 'Comparison of context failed: Comparison of registration failed: value not present in this or signature' - ], - [ - 'description' => 'full: timestamp mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['timestamp' => $timestamp2]), - 'reason' => 'Comparison of timestamp failed: value is not the same' - ], - ]; - $this->runSignatureCases("cmi5\SubStatement", $cases); - } -} diff --git a/cmi5PHP/tests/TestCompareWithSignatureTrait.php b/cmi5PHP/tests/TestCompareWithSignatureTrait.php deleted file mode 100755 index bf7b19e..0000000 --- a/cmi5PHP/tests/TestCompareWithSignatureTrait.php +++ /dev/null @@ -1,35 +0,0 @@ - true, 'reason' => null]; - - for ($i = 0; $i < count($cases); $i++) { - $obj = new $class($cases[$i]['objArgs']); - $sig = new $class(isset($cases[$i]['sigArgs']) ? $cases[$i]['sigArgs'] : $cases[$i]['objArgs']); - if (isset($cases[$i]['reason'])) { - $failure = ['success' => false, 'reason' => $cases[$i]['reason']]; - } - - $this->assertSame(isset($cases[$i]['reason']) ? $failure : $success, $obj->compareWithSignature($sig), $cases[$i]['description']); - } - } -} diff --git a/cmi5PHP/tests/UtilTest.php b/cmi5PHP/tests/UtilTest.php deleted file mode 100755 index c08164c..0000000 --- a/cmi5PHP/tests/UtilTest.php +++ /dev/null @@ -1,39 +0,0 @@ -assertRegExp(Util::UUID_REGEX, $result); - } - - public function testGetTimestamp() { - $result = Util::getTimestamp(); - - // - // this isn't intended to match all ISO8601 just *our* format of it, so it should - // catch regressions, at least more than will be accepted by an LRS which is really - // ultimately what we want in our tests - // - $this->assertRegExp('/\d\d\d\d-[01]\d-[0123]\dT[012]\d:[012345]\d:[012345]\d\.\d\d\d\+00:00/', $result); - } -} diff --git a/cmi5PHP/tests/VerbTest.php b/cmi5PHP/tests/VerbTest.php deleted file mode 100755 index 5141e71..0000000 --- a/cmi5PHP/tests/VerbTest.php +++ /dev/null @@ -1,162 +0,0 @@ - 'experienced', - 'en-GB' => 'experienced', - 'es' => 'experimentado', - 'fr' => 'expérimenté', - 'it' => 'esperto' - ]; - } - - public function testInstantiation() { - $obj = new Verb(); - $this->assertInstanceOf('cmi5\Verb', $obj); - $this->assertAttributeEmpty('id', $obj, 'id empty'); - $this->assertAttributeInstanceOf('cmi5\LanguageMap', 'display', $obj, 'display is LanguageMap'); - } - - public function testFromJSONInvalidNull() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Verb::fromJSON(null); - } - - public function testFromJSONInvalidEmptyString() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Verb::fromJSON(''); - } - - public function testFromJSONInvalidMalformed() { - $this->setExpectedException('cmi5\JSONParseErrorException'); - $obj = Verb::fromJSON('{id:"some value"}'); - } - - public function testFromJSONIDOnly() { - $obj = Verb::fromJSON('{"id":"' . COMMON_VERB_ID . '"}'); - $this->assertInstanceOf('cmi5\Verb', $obj); - $this->assertAttributeEquals(COMMON_VERB_ID, 'id', $obj, 'id matches'); - $this->assertTrue($obj->getDisplay()->isEmpty(), 'display empty'); - } - - // TODO: need to loop versions - public function testAsVersion() { - $args = [ - 'id' => COMMON_VERB_ID, - 'display' => self::$DISPLAY - ]; - - $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmpty() { - $args = []; - - $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testAsVersionEmptyLanguageMap() { - $args = ['display' => []]; - - $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - unset($args['display']); - - $this->assertEquals($versioned, $args, "serialized version matches corrected"); - } - - public function testAsVersionEmptyStringInLanguageMap() { - $args = ['display' => ['en' => '']]; - - $obj = Verb::fromJSON(json_encode($args, JSON_UNESCAPED_SLASHES)); - $versioned = $obj->asVersion('1.0.0'); - - $this->assertEquals($versioned, $args, "serialized version matches original"); - } - - public function testCompareWithSignature() { - $full = [ - 'id' => COMMON_VERB_ID, - 'display' => self::$DISPLAY - ]; - $display2 = array_replace(self::$DISPLAY, ['en-US' => 'not experienced']); - $cases = [ - [ - 'description' => 'all null', - 'objArgs' => [] - ], - [ - 'description' => 'id', - 'objArgs' => ['id' => COMMON_VERB_ID] - ], - [ - 'description' => 'display', - 'objArgs' => ['display' => self::$DISPLAY] - ], - [ - 'description' => 'all', - 'objArgs' => $full - ], - - // - // display is not matched for signature purposes because it - // is not supposed to affect the meaning of the statement - // - [ - 'description' => 'display only: mismatch (allowed)', - 'objArgs' => ['display' => self::$DISPLAY ], - 'sigArgs' => ['display' => $display2 ] - ], - [ - 'description' => 'full: display mismatch (allowed)', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['display' => $display2 ]) - ], - - [ - 'description' => 'id only: mismatch', - 'objArgs' => ['id' => COMMON_VERB_ID ], - 'sigArgs' => ['id' => COMMON_VERB_ID . '/invalid' ], - 'reason' => 'Comparison of id failed: value is not the same' - ], - [ - 'description' => 'full: id mismatch', - 'objArgs' => $full, - 'sigArgs' => array_replace($full, ['id' => COMMON_VERB_ID . '/invalid']), - 'reason' => 'Comparison of id failed: value is not the same' - ] - ]; - $this->runSignatureCases("cmi5\Verb", $cases); - } -} diff --git a/cmi5PHP/tests/VersionTest.php b/cmi5PHP/tests/VersionTest.php deleted file mode 100755 index c59c3ab..0000000 --- a/cmi5PHP/tests/VersionTest.php +++ /dev/null @@ -1,73 +0,0 @@ -assertInstanceOf("cmi5\Version", Version::v101(), "factory returns instance"); - } - - public function testToString() { - $this->assertInternalType("string", (string) Version::v101(), "object converts to string"); - } - - public function testHasValueReturnsBool() { - $this->assertTrue(Version::v101()->hasValue(Version::V101), "object has correct value"); - } - - public function testHasAnyValueReturnsBool() { - $this->assertFalse(Version::v101()->hasAnyValue([Version::V100, Version::V095]), "object does not have values"); - } - - public function testIsSupportedReturnsBool() { - $this->assertTrue(Version::v100()->isSupported(), "1.0.0 should be supported"); - $this->assertFalse(Version::v095()->isSupported(), "0.95 should not be supported"); - } - - public function testIsLatestReturnsBool() { - $this->assertTrue(Version::v101()->isLatest(), "1.0.1 should be the latest version"); - $this->assertFalse(Version::v095()->isLatest(), "0.95 should not be the latest version"); - } - - public function testSupported() { - $result = Version::supported(); - $this->assertNotContains(Version::V095, $result, "0.95 not included"); - } - - public function testLatest() { - $this->assertSame(Version::V101, Version::latest(), "match latest"); - } - - public function testVersionFromString() { - $number = '1.0.1'; - $version = Version::fromString($number); - - $this->assertTrue($version->hasValue($number)); - } - - public function testInvalidArgumentExceptionIsThrown() { - $number = '1.8.01'; - $this->setExpectedException( - 'InvalidArgumentException', - "Invalid version [$number]" - ); - $version = Version::fromString($number); - } -} diff --git a/cmi5PHP/tests/ausTest.php b/cmi5PHP/tests/auTest.php old mode 100644 new mode 100755 similarity index 80% rename from cmi5PHP/tests/ausTest.php rename to cmi5PHP/tests/auTest.php index 3f161f0..ea85501 --- a/cmi5PHP/tests/ausTest.php +++ b/cmi5PHP/tests/auTest.php @@ -2,7 +2,7 @@ namespace cmi5Test; use PHPUnit\Framework\TestCase; -use Au; +use mod_cmi5launch\local\au; /** * Class AuTest. @@ -10,16 +10,18 @@ * @copyright 2023 Megan Bohland * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * - * @covers \Au + * @covers \au */ -class AusTest extends TestCase +class auTest extends TestCase { private $auProperties, $emptyStatement, $mockStatementValues; protected function setUp(): void { + // All the properties in an AU object. $this->auProperties = array( 'id', + 'attempt', 'url', 'type', 'lmsid', @@ -42,14 +44,15 @@ protected function setUp(): void 'noattempt', 'completed', 'passed', - 'inprogress', + 'masteryScore', 'inprogress', 'launchMethod', 'lmsId', 'moveOn', 'auIndex', 'activityType' ); $this->emptyStatement = array(); - //Perhaps a good test would be to test the constructor with a statement that has all the properties set. + // Perhaps a good test would be to test the constructor with a statement that has all the properties set. $this->mockStatementValues = array( 'id' => 'id', + 'attempt' => 'attempt', 'url' => 'url', 'type' => 'type', 'lmsid' => 'lmsid', @@ -73,6 +76,7 @@ protected function setUp(): void 'completed' => 'completed', 'passed' => 'passed', 'inprogress' => 'inprogress', + 'masteryScore' => 'masteryScore', 'launchMethod' => 'launchMethod', 'lmsId' => 'lmsId', 'moveOn' => 'moveOn', 'auIndex' => 'auIndex', 'activityType' => 'activityType' ); } @@ -84,10 +88,11 @@ protected function tearDown(): void public function testInstantiationWithEmpty() { - $obj = new Au($this->emptyStatement); + $obj = new au($this->emptyStatement); + + // Is an AU object? + $this->assertInstanceOf(au::class, $obj); - //Is an AU object - $this->assertInstanceOf('Au', $obj); //It is saying AU is not transversable //Implementing traversable in AU is breaking the code, //Make sure the AU object does not have any 'extra' properties, only the amount passed in @@ -107,10 +112,11 @@ public function testInstantiationWithEmpty() public function testInstantiationWithValues() { - $obj = new Au($this->mockStatementValues); + $obj = new au($this->mockStatementValues); - //Is an AU object - $this->assertInstanceOf('Au', $obj); + // Is an AU object? + $this->assertInstanceOf(au::class, $obj); + //It is saying AU is not transversable //Implementing traversable in AU is breaking the code, //Make sure the AU object does not have any 'extra' properties, only the amount passed in diff --git a/cmi5PHP/tests/ausHelpersTest.php b/cmi5PHP/tests/ausHelpersTest.php old mode 100644 new mode 100755 index eda94ac..7d9ec90 --- a/cmi5PHP/tests/ausHelpersTest.php +++ b/cmi5PHP/tests/ausHelpersTest.php @@ -2,9 +2,8 @@ namespace cmi5Test; use PHPUnit\Framework\TestCase; -use Au; -use Au_Helpers; - +use mod_cmi5launch\local\au; +use mod_cmi5launch\local\au_helpers; /** * Tests for AuHelpers class. * @@ -14,14 +13,18 @@ * @covers \auHelpers * @covers \auHelpers::getAuProperties */ -class AusHelpersTest extends TestCase +class ausHelpersTest extends TestCase { - // private $auProperties, $emptyStatement, $mockStatementValues; + private $auProperties, $emptyStatement, $mockStatementValues, $mockStatement2, $returnedAUids; + + public $auidForTest; protected function setUp(): void { + // All the properties in an AU object. $this->auProperties = array( 'id', + 'attempt', 'url', 'type', 'lmsid', @@ -38,7 +41,6 @@ protected function setUp(): void 'masteryscore', 'satisfied', 'launchurl', - 'sessionid', 'sessions', 'progress', 'noattempt', @@ -49,9 +51,49 @@ protected function setUp(): void $this->emptyStatement = array(); - //Perhaps a good test would be to test the constructor with a statement that has all the properties set. + // Based on created AU in program, but with some values removed. + $this->mockStatement2 = array( + "id" => "https://exampleau", + "attempt" => NULL, + "url" => "example.html?pages=1&complete=launch", + "type" => "au example", + "lmsid" => NULL, + "grade" => NULL, + "scores" => NULL, + "title" => array( 0 => array( + "lang" => "en-US", + "text" => "Example AU") + ), + "moveOn"=> NULL, + "auIndex" => NULL, + "parents" => array(), + "objectives" => NULL, + "description" => array( 0 => array( + "lang" => "en-US", + "text" => "Example AU lesson description") + ), + 'activitytype' => NULL, + 'launchmethod' => NULL, + 'masteryscore' => NULL, + 'satisfied' => NULL, + 'launchurl' => NULL, + 'sessions' => NULL, + 'progress' => NULL, + 'noattempt' => NULL, + 'completed' => NULL, + 'passed' => NULL, + 'inprogress' => NULL, + 'launchMethod' => "AnyWindow", + 'lmsId' => "https://exampleau/ranomnum/au0", + 'moveOn' => "CompletedOrPassed", + 'auIndex' => 0, + 'activityType' => NULL, + 'masteryScore' => NULL + ); + // Perhaps a good test would be to test the constructor with a statement that has all the properties set. $this->mockStatementValues = array( 'id' => 'id', + 'attempt' => 'attempt', 'url' => 'url', 'type' => 'type', 'lmsid' => 'lmsid', @@ -68,7 +110,6 @@ protected function setUp(): void 'masteryscore' => 'masteryscore', 'satisfied' => 'satisfied', 'launchurl' => 'launchurl', - 'sessionid' => 'sessionid', 'sessions' => 'sessions', 'progress' => 'progress', 'noattempt' => 'noattempt', @@ -76,6 +117,7 @@ protected function setUp(): void 'passed' => 'passed', 'inprogress' => 'inprogress', ); + } protected function tearDown(): void @@ -84,10 +126,10 @@ protected function tearDown(): void } - //Retrieve Aus parses and returns AUs from large statements from the CMI5 player - //So to test, maybe make a statement and ensure the test value is returned? - //Arbitrarily pick a word and put in right place? See if it is returned? - public function testRetrieveAus() + // Retrieve Aus parses and returns AUs from large statements from the CMI5 player + // So to test, maybe make a statement and ensure the test value is returned? + // Arbitrarily pick a word and put in right place? See if it is returned? + public function testcmi5launch_retrieve_aus() { //It's not just returning it, it's splitting it into chuncks~! @@ -197,12 +239,12 @@ public function testRetrieveAus() ); - $helper = new Au_Helpers(); + $helper = new au_helpers(); //So now with this fake 'statement', lets ensure it pulls the correct value which is "correct Retrieval" - $retrieved = $helper->retrieveAus($mockStatement); + $retrieved = $helper->cmi5launch_retrieve_aus($mockStatement); - //It should retrieve the mock aus + // It should retrieve the mock aus $this->assertEquals($shouldBeReturned, $retrieved, "Expected retrieved statement to be equal to mock statement"); //This is being flaged as risky? //Is there a different way to test this? @@ -217,10 +259,10 @@ public function testRetrieveAus() //Those seem to pass, so take away line 206? } - //Test function that is fed an array of statments and returns an array of aus - public function testCreateAus() + //Test function that is fed an array of statments and returns an array of aus onjects + public function testcmi5launch_create_aus() { - //Should be enough to pass the mock statement values here, make an array of them first + // Should be enough to pass the mock statement values here, make an array of them first $testStatements = array(); //Lets create 4 aus statement @@ -228,15 +270,15 @@ public function testCreateAus() $testStatements[$i][] = $this->mockStatementValues; } - $helper = new Au_Helpers(); + $helper = new au_helpers(); //So now with this fake 'statement', lets ensure it pulls the correct value which is "correct Retrieval" - $auList = $helper->createAus($testStatements); + $auList = $helper->cmi5launch_create_aus($testStatements); //There should be a total of 4 Aus in this array $this->assertCount(4, $auList, "Expected retrieved statement to have four aus"); //And they should all be au objects foreach ($auList as $au) { - $this->assertInstanceOf(Au::class, $au, "Expected retrieved statement to be an array of aus"); + $this->assertInstanceOf(au::class, $au, "Expected retrieved statement to be an array of aus"); } } @@ -246,9 +288,65 @@ public function testCreateAus() //We just need tothat it saves the correct values and CALLS insert_record //Technically this function returns ids, so we can make a stub which just returns ids //This will test it is called without messing with the DB - public function testSaveAus() + public function testcmi5launch_save_aus() { + // The func should return auids created by the DB when AU's were saved in array format. + $helper = new au_helpers(); + + //Lets create 4 aus statement + for ($i = 0; $i < 3; $i++) { + $testAus[$i][] = $this->mockStatement2; + } + + //So now with this fake 'statement', lets ensure it pulls the correct value which is "correct Retrieval" + $returnedAUids = $helper->cmi5launch_save_aus($helper->cmi5launch_create_aus($testAus)); + + // First make sure array is returned + $this->assertIsArray($returnedAUids, "Expected retrieved statement to be an array"); + + // The array should have the same count of ids as AU's passed in + $this->assertCount(3, $returnedAUids, "Expected retrieved statement to have three aus"); + // Now iterate through the returned array and ensure ids were passed back, numeric ids + foreach ($returnedAUids as $auId) { + + // what is id? + // echo"auId: $auId"; + $this->assertIsNumeric($auId, "Expected array to have numeric values"); + } + global $auidForTest; + //Save to use in next test? + $auidForTest = $returnedAUids; + //Do I need to test fail? + + } + public function testcmi5launch_retrieve_aus_from_db() + { + // Access the global array of ids from above test + global $auidForTest; + + // global $auidForTest; + $helper = new au_helpers(); + + // It takes singular ids, so we will iterate through them + foreach ($auidForTest as $auId) { + + $returnedAu = $helper->cmi5launch_retrieve_aus_from_db($auId); + + // And the return should be an au object + $this->assertInstanceOf(au::class, $returnedAu, "Expected retrieved object to be an au object"); + } + + // And if it fails it should fail gracefully + $badid = 0; + $returnedAu = $helper->cmi5launch_retrieve_aus_from_db($badid); + + // And the return should be a false value + $this->assertNotTrue($returnedAu, "Expected retrieved object to be false"); + //And it should output this error message + $this->expectOutputString("

Error attempting to get AU data from DB. Check AU id. AU id is: " . $badid . "

"); + + } } \ No newline at end of file diff --git a/cmi5PHP/tests/cmi5ConnectorsTest.php b/cmi5PHP/tests/cmi5ConnectorsTest.php new file mode 100755 index 0000000..17bc9ab --- /dev/null +++ b/cmi5PHP/tests/cmi5ConnectorsTest.php @@ -0,0 +1,328 @@ +example = null; + } + + + // Create course sends data to the player. + // Now we dont actually want to talk to player or we will either make a bunch of unreal courses + // unless we then delete it? + // or have to make a fake player??? + // Does php have mocks or stubs? + // They do, and what is actually returned is a massive json encoded string. It is the return + // response when a course is created from player, but all we care about is a string return from stub? + //I understand what I worte here. IS it enouh to just retreive a strin? Or does it need to be in + // same shape it would be from the player? Like do I need to verify it has, these cpmonets? + // or is ti enouh to not.... Is my RPORTAM robusdt enouh to catch that? + // I don't think it i, it just throughs eneric errors, this may + // be a place to improve it. + + //Ok, so be that as it may THIS func gets a response and sends array back. If response is false it faisl + // robust enouh? + // I think since this job is just to take and pass on courseinfo, it doesn't have to validate the properties of returned array, that will fall into the testing of the functhat retrieves them? + + public function testcmi5launch_create_course_pass() + { + + // global $auidForTest; + global $auidForTest; + global $DB, $CFG, $filename; + + $id = 0; + $tenanttoken = "testtoken"; + + //If we make filename an object with it's own get_content method, we can stub it out + + + $filename = new class { + + public function get_content() { + return "testfilecontents"; + } + }; + // Wait this moiht work, we can just make this object a method, and stub it out ourselves, + // or would i be better to try and use their mocks + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other + // 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\cmi5_connectors') + ->onlyMethods(array('cmi5launch_send_request_to_cmi5_player_post')) + ->getMock(); + + + + //$setting = $this->getMockBuilder(\stdclass::class)->addMethods(array('cmi5launch_settings'))->getMock(); + // $setting = $this->getFunctionMod(_mod_cmi5launch\local__, 'cmi5launch_settings'); + // $setting = $this->getMockBuilder(\stdclass::class) + // ->addMethods(array('cmi5launch_settings')) + // ->getMock(); + + // $setting = $this->createMock(cmi5launch__settings::class) + //->addMethods(array('cmi5launch_settings')) + //->getMock(); + +// mod_cmi5launch\local\cmi5launch_settings() +// Can I mock lib? Then stuff like settings could be mocked? + + + // We will have the mock return a basic string, as it's not under test + // the string just needs to be returned as is. We do expect create_course to only call this once. + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_cmi5_player_post') + // IT will call '/api/v1/course' nd not a whole url because that is accessed through "Settings" not reachable under test conditions, so it + // will only use the second part of concantation + ->with('testfilecontents', '/api/v1/course', 'testtoken') + ->willReturn('Request sent to player'); + + + // $setting->expects($this->any()) + // ->method('cmi5launch_settings') + // ->willReturn(array('cmi5launchplayerurl' => 'http://localhost:8000/launch.php')) + // ->with('Request sent to player') + ; + + // I think I need to say expect to be called with these, + // because for some reason it says paraaam 0 is not matching? + //Call the method under test. + $result =$csc->cmi5launch_create_course($id, $tenanttoken, $filename); + + // And the return should be a string (the original method returns what the player sends back or FALSE) + $this->assertIsString($result); + $this->assertEquals('Request sent to player', $result); + + + } + + public function testcmi5launch_create_course_fail() + { + // global $auidForTest; + global $auidForTest; + global $DB, $CFG; + + $id = 0; + $tenanttoken = "testtoken"; + // $filename = array ("testfilename" => "testfilecontents"); + //If we make filename an object with it's own get_content method, we can stub it out + + + $filename = new class { + + public function get_content() { + return "testfilecontents"; + } +}; + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other + // 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\cmi5_connectors') + ->onlyMethods(array('cmi5launch_send_request_to_cmi5_player_post')) + ->getMock(); + + // We will have the mock return a FALSE value, this should enable us to test the + // method under failing conditions. We do expect create_course to only call this once. + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_cmi5_player_post') + ->with('testfilecontents', '/api/v1/course', 'testtoken') + ->willReturn(FALSE) + // ->with('Request sent to player') + ; + + //Call the method under test. + $result =$csc->cmi5launch_create_course($id, $tenanttoken, $filename); + + + // Result should be debug echo string and false + $this->assertNotTrue($result, "Expected retrieved object to be false"); + //And it should output this error message + $this->expectOutputString("
Something went wrong creating the course. CMI5 Player returned ". $result . "
"); + + + } + + //Here we will mock cmi5_send_request_to_player and have it return a string again, because thje func actually under test is + // cmi5launch_create_tenant, so to test it we want to make sure it calls the other func and retutrns what it does + // or throws the specified error + // (This is another place I have the error only if debug and wonder if it should be different. We always want that to show riht?) + public function testcmi5launch_create_tenant_pass() + { + + // global $auidForTest; + global $CFG; + + $urltosend = "playerwebaddress"; + $username = "testname"; + $password = "testpassword"; + $newtenantname = "testtenantname"; + + $returnvalue = array( + "code" => "testtenantname", + "id" => 9 + ); + + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other + // 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\cmi5_connectors') + ->onlyMethods(array('cmi5launch_send_request_to_cmi5_player_post')) + ->getMock(); + + // We will have the mock return a basic string, as it's not under test + // the string just needs to be returned as is. We do expect create_course to only call this once. + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_cmi5_player_post') + ->with(array ('code' => 'testtenantname'), 'playerwebaddress', 'testname', 'testpassword') + // IRL it returns something that needs to be json decoded, so lets pass somethin that is encoded> + ->willReturn('{ + "code": "testtenantname", + "id": 9 + }' ) + // ->with('Request sent to player') +; + +// I think I need to say expect to be called with these, +// because for some reason it says paraaam 0 is not matching? + //Call the method under test. + $result =$csc->cmi5launch_create_tenant($urltosend, $username, $password, $newtenantname); + + // And the return should be a string (the original method returns what the player sends back or FALSE) + $this->assertIsArray($result); + $this->assertEquals( $returnvalue, $result); + } + + public function testcmi5launch_create_tenant_fail() + { + + // global $auidForTest; + global $CFG; + + $urltosend = "playerwebaddress"; + $username = "testname"; + $password = "testpassword"; + $newtenantname = "testtenantname"; + + $returnvalue = array( + "code" => "testtenantname", + "id" => 9 + ); + + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other + // 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\cmi5_connectors') + ->onlyMethods(array('cmi5launch_send_request_to_cmi5_player_post')) + ->getMock(); + + // This time we will have ti 'fail' so return a fail response from player + $csc->expects($this->once()) + ->method('cmi5launch_send_request_to_cmi5_player_post') + ->with(array ('code' => 'testtenantname'), 'playerwebaddress', 'testname', 'testpassword') + // IRL it returns something that needs to be json decoded, so lets pass somethin that is encoded> + ->willReturn(false) + // ->with('Request sent to player') + ; + +// I think I need to say expect to be called with these, +// because for some reason it says paraaam 0 is not matching? + //Call the method under test. + $result =$csc->cmi5launch_create_tenant($urltosend, $username, $password, $newtenantname); + + // Result should be debug echo string and false + $this->assertNotTrue($result, "Expected retrieved object to be false"); + //And it should output this error message + $this->expectOutputString("
Something went wrong creating the tenant. CMI5 Player returned ". $result . "
"); + + + } + + + public function testcmi5launch_send_request_to__cmi5_player_post() + { + + // global $auidForTest; + global $CFG; + + $help = new cmi5TestHelpers(); + // $testHelp = new cmi5TestHelpers(); + + $databody = array ('code' => 'testtenantname'); + $urltosend = "playerwebaddress"; + $username = "testname"; + $password = "testpassword"; + $token = "testtoken"; + + $returnvalue = array( + "code" => "testtenantname", + "id" => 9 + ); + + // Mock a cmi5 connector object but only stub ONE method, as we want to test the other + // 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( __NAMESPACE__ . '\cmi5TestHelpers') + ->onlyMethods(array('file_get_contents')) + ->getMock(); + + // This time we will have ti 'fail' so return a fail response from player + $csc->expects($this->once()) + ->method('file_get_contents') + //->with(array ('code' => 'testtenantname'), 'playerwebaddress', 'testname', 'testpassword') + // IRL it returns something that needs to be json decoded, so lets pass somethin that is encoded> + ->willReturn("Test") + // ->with('Request sent to player') + ; + +// I think I need to say expect to be called with these, +// because for some reason it says paraaam 0 is not matching? + //Call the method under test. + $result =$csc->cmi5launch_send_request_to_cmi5_player_post($databody, $urltosend, $username, $password); + + // Result should be debug echo string and false + // $this->assertNotTrue($result, "Expected retrieved object to be false"); + //And it should output this error message + $this->expectOutputString("Test"); + + + } + + +} \ No newline at end of file diff --git a/cmi5PHP/tests/cmi5TestHelpers.php b/cmi5PHP/tests/cmi5TestHelpers.php new file mode 100755 index 0000000..bae3629 --- /dev/null +++ b/cmi5PHP/tests/cmi5TestHelpers.php @@ -0,0 +1,44 @@ +'https://cmi5launchplayerurl.com'); + } + } + + + + +} +?> diff --git a/cmi5PHP/tests/testHelpers.php b/cmi5PHP/tests/testHelpers.php new file mode 100755 index 0000000..d8c564d --- /dev/null +++ b/cmi5PHP/tests/testHelpers.php @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/complete.php b/complete.php index 3c3c7f4..e0d8f89 100755 --- a/complete.php +++ b/complete.php @@ -1,30 +1,32 @@ -. - -/** - * launches the experience with the requested registration - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once('completion_check.php'); - -// Return to the course. -header("Location: ". $CFG->wwwroot.'/course/view.php?id='.$cmi5launch->course); - -exit; +. + +/** + * launches the experience with the requested registration + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once('completion_check.php'); + +require_login($course, false, $cm); + +// Return to the course. +header("Location: ". $CFG->wwwroot.'/course/view.php?id='.$cmi5launch->course); + +exit; diff --git a/completion_check.php b/completion_check.php index f896d08..e9f25bc 100755 --- a/completion_check.php +++ b/completion_check.php @@ -1,50 +1,52 @@ -. - -/** - * launches the experience with the requested registration - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once('header.php'); - -$completion = new completion_info($course); - -$possibleresult = COMPLETION_COMPLETE; - -if ($cmi5launch->cmi5expiry > 0) { - $possibleresult = COMPLETION_UNKNOWN; -} - -if ($completion->is_enabled($cm) && $cmi5launch->cmi5verbid) { - $oldstate = $completion->get_data($cm, false, 0); - $completion->update_state($cm, $possibleresult); - $newstate = $completion->get_data($cm, false, 0); - - if ($oldstate->completionstate !== $newstate->completionstate) { - // Trigger Activity completed event. - $event = \mod_cmi5launch\event\activity_completed::create(array( - 'objectid' => $cmi5launch->id, - 'context' => $context, - )); - $event->add_record_snapshot('course_modules', $cm); - $event->add_record_snapshot('cmi5launch', $cmi5launch); - $event->trigger(); - } -} +. + +/** + * launches the experience with the requested registration + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once('header.php'); + +require_login($course, false, $cm); + +$completion = new completion_info($course); + +$possibleresult = COMPLETION_COMPLETE; + +if ($cmi5launch->cmi5expiry > 0) { + $possibleresult = COMPLETION_UNKNOWN; +} + +if ($completion->is_enabled($cm) && $cmi5launch->cmi5verbid) { + $oldstate = $completion->get_data($cm, false, 0); + $completion->update_state($cm, $possibleresult); + $newstate = $completion->get_data($cm, false, 0); + + if ($oldstate->completionstate !== $newstate->completionstate) { + // Trigger Activity completed event. + $event = \mod_cmi5launch\event\activity_completed::create(array( + 'objectid' => $cmi5launch->id, + 'context' => $context, + )); + $event->add_record_snapshot('course_modules', $cm); + $event->add_record_snapshot('cmi5launch', $cmi5launch); + $event->trigger(); + } +} diff --git a/db/access.php b/db/access.php index d71a067..444c115 100755 --- a/db/access.php +++ b/db/access.php @@ -1,71 +1,80 @@ -. - -/** - * Capability definitions for the cmi5launch module - * - * The capabilities are loaded into the database table when the module is - * installed or updated. Whenever the capability definitions are updated, - * the module version number should be bumped up. - * - * The system has four possible values for a capability: - * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set). - * - * It is important that capability names are unique. The naming convention - * for capabilities that are specific to modules and blocks is as follows: - * [mod/block]/: - * - * component_name should be the same as the directory name of the mod or block. - * - * Core moodle capabilities are defined thus: - * moodle/: - * - * Examples: mod/forum:viewpost - * block/recent_activity:view - * moodle/site:deleteuser - * - * The variable name for the capability definitions array is $capabilities - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$capabilities = array( - 'mod/cmi5launch:view' => array( - 'captype' => 'read', - 'contextlevel' => CONTEXT_MODULE, - 'archetypes' => array( - 'guest' => CAP_ALLOW, - 'user' => CAP_ALLOW, - ) - ), - - 'mod/cmi5launch:addinstance' => array( - 'riskbitmask' => RISK_XSS, - - 'captype' => 'write', - 'contextlevel' => CONTEXT_COURSE, - 'archetypes' => array( - 'editingteacher' => CAP_ALLOW, - 'manager' => CAP_ALLOW - ), - 'clonepermissionsfrom' => 'moodle/course:manageactivities' - ), -); - +. + +/** + * Capability definitions for the cmi5launch module + * + * The capabilities are loaded into the database table when the module is + * installed or updated. Whenever the capability definitions are updated, + * the module version number should be bumped up. + * + * The system has four possible values for a capability: + * CAP_ALLOW, CAP_PREVENT, CAP_PROHIBIT, and inherit (not set). + * + * It is important that capability names are unique. The naming convention + * for capabilities that are specific to modules and blocks is as follows: + * [mod/block]/: + * + * component_name should be the same as the directory name of the mod or block. + * + * Core moodle capabilities are defined thus: + * moodle/: + * + * Examples: mod/forum:viewpost + * block/recent_activity:view + * moodle/site:deleteuser + * + * The variable name for the capability definitions array is $capabilities + * + * @package mod_cmi5launch + * @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 + */ + +defined('MOODLE_INTERNAL') || die(); + +$capabilities = array( + 'mod/cmi5launch:view' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'guest' => CAP_ALLOW, + 'user' => CAP_ALLOW, + ), + ), + + 'mod/cmi5launch:viewgrades' => array( + 'captype' => 'read', + 'contextlevel' => CONTEXT_MODULE, + 'archetypes' => array( + 'coursecreator' => CAP_ALLOW, + 'teacher' => CAP_ALLOW, + ), + ), + + 'mod/cmi5launch:addinstance' => array( + 'riskbitmask' => RISK_XSS, + 'captype' => 'write', + 'contextlevel' => CONTEXT_COURSE, + 'archetypes' => array( + 'editingteacher' => CAP_ALLOW, + 'manager' => CAP_ALLOW, + ), + 'clonepermissionsfrom' => 'moodle/course:manageactivities', + ), +); + diff --git a/db/install.php b/db/install.php index 09f9834..83d5b22 100755 --- a/db/install.php +++ b/db/install.php @@ -1,42 +1,44 @@ -. - -/** - * This file replaces the legacy STATEMENTS section in db/install.xml, - * lib.php/modulename_install() post installation hook and partially defaults.php - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes mrdownes@hotmail.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); - -/** - * Post installation procedure - * - * @see upgrade_plugins_modules() - */ -function xmldb_cmi5launch_install() { -} - -/** - * Post installation recovery procedure - * - * @see upgrade_plugins_modules() - */ -function xmldb_cmi5launch_install_recovery() { -} +. + +/** + * This file replaces the legacy STATEMENTS section in db/install.xml, + * lib.php/modulename_install() post installation hook and partially defaults.php + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes mrdownes@hotmail.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); + +//require_login($course, false, $cm); + +/** + * Post installation procedure + * + * @see upgrade_plugins_modules() + */ +function xmldb_cmi5launch_install() { +} + +/** + * Post installation recovery procedure + * + * @see upgrade_plugins_modules() + */ +function xmldb_cmi5launch_install_recovery() { +} diff --git a/db/install.xml b/db/install.xml index 0ff70f2..d45cd4e 100755 --- a/db/install.xml +++ b/db/install.xml @@ -1,84 +1,59 @@ - - +
- + - + - - - - - + + + + - - - - - - - + + + + + + - -
- - - - - - - - - - - - - - - - - -
- +
+ - - - - - - - - - - - - - - + + + + + + + + + + - +
- + @@ -90,11 +65,8 @@ - - - - - + + @@ -103,44 +75,34 @@
- +
- - - - - - - - - - - - - + + + + + + + + + + + + - - - + - - + + - - + - - - - - - @@ -149,43 +111,38 @@
- +
- - - - - - + + + - - - - - + + + - + - - + + - - - - - - - - + + + + + + + + - +
diff --git a/db/log.php b/db/log.php index 5274485..8771800 100755 --- a/db/log.php +++ b/db/log.php @@ -1,35 +1,35 @@ -. - -/** - * Definition of log events - * - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes mrdownes@hotmail.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -global $DB; - -$logs = array( - array('module' => 'cmi5launch', 'action' => 'add', 'mtable' => 'cmi5launch', 'field' => 'name'), - array('module' => 'cmi5launch', 'action' => 'update', 'mtable' => 'cmi5launch', 'field' => 'name'), - array('module' => 'cmi5launch', 'action' => 'view', 'mtable' => 'cmi5launch', 'field' => 'name'), - array('module' => 'cmi5launch', 'action' => 'view all', 'mtable' => 'cmi5launch', 'field' => 'name') -); +. + +/** + * Definition of log events + * + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes mrdownes@hotmail.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +global $DB; + +$logs = array( + array('module' => 'cmi5launch', 'action' => 'add', 'mtable' => 'cmi5launch', 'field' => 'name'), + array('module' => 'cmi5launch', 'action' => 'update', 'mtable' => 'cmi5launch', 'field' => 'name'), + array('module' => 'cmi5launch', 'action' => 'view', 'mtable' => 'cmi5launch', 'field' => 'name'), + array('module' => 'cmi5launch', 'action' => 'view all', 'mtable' => 'cmi5launch', 'field' => 'name'), +); diff --git a/db/tasks.php b/db/tasks.php index 136030d..1aae467 100755 --- a/db/tasks.php +++ b/db/tasks.php @@ -1,33 +1,33 @@ -. - -/** - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes mrdownes@hotmail.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$tasks = array( - array( - 'classname' => 'mod_cmi5launch\task\check_completion', - 'blocking' => 0, - 'minute' => '01', - 'hour' => '23' - ) -); \ No newline at end of file +. + +/** + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes mrdownes@hotmail.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$tasks = array( + array( + 'classname' => 'mod_cmi5launch\task\check_completion', + 'blocking' => 0, + 'minute' => '01', + 'hour' => '23', + ), +); diff --git a/db/uninstall.php b/db/uninstall.php index dc03ae5..62c4fa0 100755 --- a/db/uninstall.php +++ b/db/uninstall.php @@ -1,31 +1,32 @@ -. - -/** - * @see uninstall_plugin() - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes mrdownes@hotmail.com - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); - -/** - * Custom uninstallation procedure - */ -function xmldb_cmi5launch_uninstall() { - return true; -} +. + +/** + * @see uninstall_plugin() + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes mrdownes@hotmail.com + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +require_once(dirname(dirname(dirname(dirname(__FILE__)))).'/config.php'); + +require_login($course, false, $cm); +/** + * Custom uninstallation procedure + */ +function xmldb_cmi5launch_uninstall() { + return true; +} diff --git a/db/upgrade.php b/db/upgrade.php index be13f8b..012e62a 100755 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -1,172 +1,925 @@ -. - -/** - * This file keeps track of upgrades to the cmi5launch module - * - * Sometimes, changes between versions involve alterations to database - * structures and other major things that may break installations. The upgrade - * function in this file will attempt to perform all the necessary actions to - * upgrade your older installation to the current version. If there's something - * it cannot do itself, it will tell you what you need to do. The commands in - * here will all be database-neutral, using the functions defined in DLL libraries. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -/** - * Execute cmi5launch upgrade from the given old version - * - * @param int $oldversion - * @return bool - */ -function xmldb_cmi5launch_upgrade($oldversion) { - global $DB; - - $dbman = $DB->get_manager(); - - if ($oldversion < 2013083100) { - // Define field cmi5activityid to be added to cmi5launch. - $table = new xmldb_table('cmi5launch'); - $field = new xmldb_field('cmi5activityid', XMLDB_TYPE_TEXT, '1333', null, XMLDB_NOTNULL, null, null, 'cmi5launchurl'); - - // Add field cmi5activityid. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - upgrade_mod_savepoint(true, 2013083100, 'cmi5launch'); - } - - if ($oldversion < 2013111600) { - // Define field cmi5verbid to be added to cmi5launch. - $table = new xmldb_table('cmi5launch'); - $field = new xmldb_field('cmi5verbid', XMLDB_TYPE_TEXT, '1333', null, XMLDB_NOTNULL, null, null, 'cmi5launchurl'); - - // Add field cmi5activityid. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - upgrade_mod_savepoint(true, 2013111600, 'cmi5launch'); - } - - if ($oldversion < 2015032500) { - - // Define field overridedefaults to be added to cmi5launch. - $table = new xmldb_table('cmi5launch'); - $field = new xmldb_field('overridedefaults', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'cmi5verbid'); - - // Conditionally launch add field overridedefaults. - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - // Define table cmi5launch_lrs to be created. - $table = new xmldb_table('cmi5launch_lrs'); - - // Adding fields to table cmi5launch_lrs. - $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); - $table->add_field('cmi5launchid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('lrsendpoint', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - $table->add_field('lrsauthentication', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - $table->add_field('lrslogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - $table->add_field('lrspass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - $table->add_field('lrsduration', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); - - // Adding keys to table cmi5launch_lrs. - $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); - - // Adding indexes to table cmi5launch_lrs. - $table->add_index('cmi5launchid', XMLDB_INDEX_NOTUNIQUE, array('cmi5launchid')); - - // Conditionally launch create table for cmi5launch_lrs. - if (!$dbman->table_exists($table)) { - $dbman->create_table($table); - } - - // cmi5launch savepoint reached. - upgrade_mod_savepoint(true, 2015032500, 'cmi5launch'); - } - - if ($oldversion < 2015033100) { - - unset_config('cmi5launchlrsversion', 'cmi5launch'); - unset_config('cmi5launchlrauthentication', 'cmi5launch'); - - upgrade_mod_savepoint(true, 2015033100, 'cmi5launch'); - } - - if ($oldversion < 2015112702) { - // Define field cmi5activityid to be added to cmi5launch. - $table = new xmldb_table('cmi5launch'); - $field = new xmldb_field('cmi5multipleregs', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'cmi5verbid'); - - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - $table = new xmldb_table('cmi5launch_lrs'); - $field = new xmldb_field('useactoremail', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); - - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - $table->add_field('customacchp', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - - upgrade_mod_savepoint(true, 2015112702, 'cmi5launch'); - } - - if ($oldversion < 2016121200) { - $table = new xmldb_table('cmi5launch'); - $field = new xmldb_field('cmi5expiry', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 365); - - if (!$dbman->field_exists($table, $field)) { - $dbman->add_field($table, $field); - } - } - - if ($oldversion < 2018103000) { - $table = new xmldb_table('cmi5launch_credentials'); - if ($dbman->table_exists($table)) { - $dbman->drop_table($table, $continue = true, $feedback = true); - } - - $table = new xmldb_table('cmi5launch_lrs'); - $field = new xmldb_field('watershedlogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - if ($dbman->field_exists($table, $field)) { - $dbman->drop_field($table, $field, $continue = true, $feedback = true); - } - - $field = new xmldb_field('watershedpass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); - if ($dbman->field_exists($table, $field)) { - $dbman->drop_field($table, $field, $continue = true, $feedback = true); - } - - upgrade_mod_savepoint(true, 2018103000, 'cmi5launch'); - } - - // Final return of upgrade result (true, all went good) to Moodle. - return true; -} +. + +/** + * This file keeps track of upgrades to the cmi5launch module + * + * Sometimes, changes between versions involve alterations to database + * structures and other major things that may break installations. The upgrade + * function in this file will attempt to perform all the necessary actions to + * upgrade your older installation to the current version. If there's something + * it cannot do itself, it will tell you what you need to do. The commands in + * here will all be database-neutral, using the functions defined in DLL libraries. + * + * @package mod_cmi5launch + * @copyright 2024 Megan Bohland - added onto + * @copyright original work by 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Execute cmi5launch upgrade from the given old version + * + * @param int $oldversion + * @return bool + */ +function xmldb_cmi5launch_upgrade($oldversion) { + global $DB; + $dbman = $DB->get_manager(); + + + if ($oldversion < 2024030615) { + + // Define field courseid to be dropped from cmi5launch_player. + $table = new xmldb_table('cmi5launch_player'); + $field = new xmldb_field('courseid'); + + // Conditionally launch drop field courseid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Add fields to help with data tracking and deletion. + // Define field courseid to be added to cmi5launch_player. + $tablestoadd = array(new xmldb_table('cmi5launch_usercourse'), new xmldb_table('cmi5launch_aus'),new xmldb_table('cmi5launch_sessions')); + $fieldmcid = new xmldb_field('moodlecourseid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0', 'id'); + + // Now cycle through array and remove fields. + foreach ($tablestoadd as $table) { + + // Conditionally launch add field moodlecourseid. + if (!$dbman->field_exists($table, $fieldmcid)) { + $dbman->add_field($table, $fieldmcid); + } + } + + + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024030615, 'cmi5launch'); + } + + if ($oldversion < 2024030425) { + + // Define field lmsid to be dropped from cmi5launch_sessions. + $table = new xmldb_table('cmi5launch_sessions'); + + // Define index lmsid (not unique) to be dropped from cmi5launch_sessions. + $index = new xmldb_index('lmsid', XMLDB_INDEX_NOTUNIQUE, ['lmsid']); + + $indexnew = new xmldb_index('sessionid', XMLDB_INDEX_NOTUNIQUE, ['lmsid']); + + // Conditionally launch add index lmsid. + if (!$dbman->index_exists($table, $indexnew)) { + + + $dbman->add_index($table, $indexnew); + } + + // Conditionally launch drop index lmsid. + if ($dbman->index_exists($table, $index)) { + $dbman->drop_index($table, $index); + } + + + $fieldstoremove = array($fieldlmsid = new xmldb_field('lmsid'), $fieldresponse = new xmldb_field('response'), $fieldcourseid = new xmldb_field('courseid')); + + // Now cycle through array and remove fields. + foreach ($fieldstoremove as $field) { + // Conditionally launch drop field registrationcourseausid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + } + + + // Define field id to be added to cmi5launch_aus. + $tableaus = new xmldb_table('cmi5launch_aus'); + $fielduserid = new xmldb_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, XMLDB_SEQUENCE, null, null); + + // Conditionally launch add field id. + if (!$dbman->field_exists($tableaus, $fielduserid)) { + $dbman->add_field($tableaus, $fielduserid); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024030425, 'cmi5launch'); + } + + + + if ($oldversion < 2024011612) { + + // Define field grade to be dropped from cmi5launch. + $tablecmi5launch = new xmldb_table('cmi5launch'); + $field1 = new xmldb_field('cmi5launchurl'); + + // Define field grade to be dropped from cmi5launch. + $tableusercourse = new xmldb_table('cmi5launch_usercourse'); + $field2 = new xmldb_field('cmi5launchurl'); + + // Define fields to be dropped from table cmi5launch_player. + $tableplayer = new xmldb_table('cmi5launch_player'); + $fieldstoremove = array (new xmldb_field('courseinfo'), new xmldb_field('firstlaunched'), new xmldb_field('lastlaunched') ); + + // Now cycle through array and remove fields. + foreach ($fieldstoremove as $field) { + // Conditionally launch drop field registrationcourseausid. + if ($dbman->field_exists($tableplayer, $field)) { + $dbman->drop_field($tableplayer, $field); + } + } + // Conditionally launch drop field grade. + if ($dbman->field_exists($tablecmi5launch, $field1)) { + $dbman->drop_field($tablecmi5launch, $field1); + } + + // Conditionally launch drop field grade. + if ($dbman->field_exists($tableusercourse, $field2)) { + $dbman->drop_field($tableusercourse, $field2); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024011612, 'cmi5launch'); + } + + if ($oldversion < 2024020915) { + + // Define field grade to be dropped from cmi5launch. + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('grade'); + + // Conditionally launch drop field grade. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024020915, 'cmi5launch'); + + } + + if ($oldversion < 2024020816) { + + // Conditionally rename table if it exists. + if ($dbman->table_exists('cmi5launch_course')) { + // Define table cmi5launch_lrs to be renamed to NEWNAMEGOESHERE. + $table = new xmldb_table('cmi5launch_course'); + + // Launch rename table for cmi5launch_lrs. + $dbman->rename_table($table, 'cmi5launch_usercourse'); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024020816, 'cmi5launch'); + } + if ($oldversion < 2024020717) { + + // This table is not used, dropping from future versions. + $tablelrs = new xmldb_table('cmi5launch_lrs'); + + // Define field registrationcourseausid to be dropped from cmi5launch_sessions. + $tablesessions = new xmldb_table('cmi5launch_sessions'); + + $fieldstoremove = array($fieldregcourse = new xmldb_field('registrationcourseausid'), + $fieldregid = new xmldb_field('registrationid'), $fieldlrscode = new xmldb_field('lrscode'), + $fieldauid = new xmldb_field('auid')); + + // Define field registrationcourseausid to be dropped from cmi5launch_sessions. + $tableaus = new xmldb_table('cmi5launch_aus'); + // This is an accidental duplicate field that does nothing, delete it. + $fieldsessionid = new xmldb_field('sessionid'); + + // Now cycle through array and remove fields. + foreach ($fieldstoremove as $field) { + // Conditionally launch drop field registrationcourseausid. + if ($dbman->field_exists($tablesessions, $field)) { + $dbman->drop_field($tablesessions, $field); + } + } + + // Conditionally launch drop field registrationid. + if ($dbman->field_exists($tableaus, $fieldsessionid)) { + $dbman->drop_field($tableaus, $fieldsessionid); + } + + // Conditionally drop table if exists. + if ($dbman->table_exists($tablelrs)) { + $dbman->drop_table($tablelrs); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024020717, 'cmi5launch'); + } + + if ($oldversion < 2024012516) { + + // Changing type of field ausgrades on table cmi5launch_course to text. + $table = new xmldb_table('cmi5launch_course'); + $field = new xmldb_field('ausgrades', XMLDB_TYPE_TEXT, 4000, null, null, null, null, 'aus'); + + // Launch change of type for field ausgrades. + $dbman->change_field_type($table, $field); + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024012516, 'cmi5launch'); + } + if ($oldversion < 2024010212) { + + // Changing type of field masteryscore on table cmi5launch_sessions to int. + $table = new xmldb_table('cmi5launch_sessions'); + $field = new xmldb_field('masteryscore', XMLDB_TYPE_NUMBER, '10', null, null, null, null, 'launchmode'); + + // Launch change of type for field masteryscore. + $dbman->change_field_type($table, $field); + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2024010212, 'cmi5launch'); + } + if ($oldversion < 2023121209) { + + // I need to remove 7 unused columns from the cmi5launch_aus table. + // Define field auid to be dropped from cmi5launch_aus. + $table = new xmldb_table('cmi5launch_aus'); + + $fieldauid = new xmldb_field('auid'); + $fielduserid = new xmldb_field('userid'); + $fieldtenantname = new xmldb_field('tenantname'); + $fieldcurrentgrade = new xmldb_field('currentgrade'); + $fieldregistrationid = new xmldb_field('registrationid'); + $fieldreturnurl = new xmldb_field('returnurl'); + + $arraytoremove = array($fieldauid, $fielduserid, $fieldtenantname, $fieldcurrentgrade, + $fieldregistrationid, $fieldreturnurl); + + // Now cycle through array and remove fields. + foreach ($arraytoremove as $field) { + // Conditionally launch drop field auid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2023121209, 'cmi5launch'); + } + + if ($oldversion < 2023112117) { + + // Changing type of field masteryscore on table cmi5launch_sessions to number. + $table = new xmldb_table('cmi5launch_sessions'); + $field = new xmldb_field('masteryscore', XMLDB_TYPE_NUMBER, '10', null, null, null, null, 'launchmode'); + + // Launch change of type for field masteryscore. + $dbman->change_field_type($table, $field); + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2023112117, 'cmi5launch'); + } + if ($oldversion < 2023112113) { + + // Changing the default of field grade on table cmi5launch_aus to drop it. + $table = new xmldb_table('cmi5launch_aus'); + $field = new xmldb_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'scores'); + + // Launch change of default for field grade. + $dbman->change_field_default($table, $field); + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2023112113, 'cmi5launch'); + } + + if ($oldversion < 2023111714) { + + // Changing type of field objectives on table cmi5launch_aus to text. + $table = new xmldb_table('cmi5launch_aus'); + $objectives = new xmldb_field('objectives', XMLDB_TYPE_TEXT, null, null, null, null, null, 'parents'); + $description = new xmldb_field('description', XMLDB_TYPE_TEXT, null, null, null, null, null, 'objectives'); + + // Launch change of type for field objectives. + $dbman->change_field_type($table, $objectives); + + // Launch change of type for field description. + $dbman->change_field_type($table, $description); + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2023111714, 'cmi5launch'); + + } + + if ($oldversion < 2023101217) { + + // Define table cmi5launch to be created. + $table = new xmldb_table('cmi5launch'); + + // Adding fields to table cmi5launch. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('intro', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('introformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('cmi5launchurl', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5activityid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('cmi5verbid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5expiry', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '365'); + $table->add_field('overridedefaults', XMLDB_TYPE_INTEGER, '1', null, null, null, null); + $table->add_field('cmi5multipleregs', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('courseinfo', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('aus', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch. + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, ['course']); + + // Conditionally launch create table for cmi5launch. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_course to be created. + $table = new xmldb_table('cmi5launch_course'); + + // Adding fields to table cmi5launch_course. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('cmi5launchurl', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5activityid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('aus', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('ausgrades', XMLDB_TYPE_CHAR, '1000', null, null, null, '0'); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch_course. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_course. + $table->add_index('courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + + // Conditionally launch create table for cmi5launch_course. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // If the table already exists, we just need to add the new field + // Define field ausgrades to be added to cmi5launch_course. + $table = new xmldb_table('cmi5launch_course'); + $field = new xmldb_field('ausgrades', XMLDB_TYPE_CHAR, '1000', null, null, null, '0', 'aus'); + + // Conditionally launch add field ausgrades. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define table cmi5launch_lrs to be created. + $table = new xmldb_table('cmi5launch_lrs'); + + // Adding fields to table cmi5launch_lrs. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('cmi5launchid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsendpoint', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsauthentication', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrslogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrspass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('customacchp', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('useactoremail', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); + $table->add_field('lrsduration', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantpass', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('tenanttoken', XMLDB_TYPE_CHAR, '350', null, null, null, null); + $table->add_field('playerport', XMLDB_TYPE_INTEGER, '5', null, null, null, '66398'); + $table->add_field('playerurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + + // Adding keys to table cmi5launch_lrs. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_lrs. + $table->add_index('cmi5launchid', XMLDB_INDEX_NOTUNIQUE, ['cmi5launchid']); + + // Conditionally launch create table for cmi5launch_lrs. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_player to be created. + $table = new xmldb_table('cmi5launch_player'); + + // Adding fields to table cmi5launch_player. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenanttoken', XMLDB_TYPE_CHAR, '350', null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('homepage', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchurl', XMLDB_TYPE_CHAR, '500', null, null, null, null); + $table->add_field('cmi5playerurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('cmi5playerport', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('courseinfo', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('firstlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('lastlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + + // Adding keys to table cmi5launch_player. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['registrationid']); + + // Adding indexes to table cmi5launch_player. + $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, ['name']); + + // Conditionally launch create table for cmi5launch_player. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_sessions to be created. + $table = new xmldb_table('cmi5launch_sessions'); + + // Adding fields to table cmi5launch_sessions. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, null, XMLDB_SEQUENCE, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('registrationscoursesausid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('lmsid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('createdat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('updatedat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('registrationcourseausid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('code', XMLDB_TYPE_CHAR, '550', null, null, null, null); + $table->add_field('launchtokenid', XMLDB_TYPE_CHAR, '550', null, null, null, null); + $table->add_field('lastrequesttime', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('launchmode', XMLDB_TYPE_CHAR, '25', null, null, null, null); + $table->add_field('masteryscore', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('score', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('response', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('islaunched', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isinitialized', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('initializedat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('duration', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('iscompleted', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('ispassed', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isfailed', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isterminated', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isabandoned', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('progress', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('lrscode', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('auid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchurl', XMLDB_TYPE_CHAR, '750', null, null, null, null); + + // Adding keys to table cmi5launch_sessions. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_sessions. + $table->add_index('lmsid', XMLDB_INDEX_NOTUNIQUE, ['lmsid']); + + // Conditionally launch create table for cmi5launch_sessions. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } else { + + // Define fields to be dropped from table cmi5launch_player. + $fieldstoremove = array ($firstlaunch = new xmldb_field('firstlaunch'), $lastlaunch = new xmldb_field('lastlaunch'), + $completedpassed = new xmldb_field('completed_passed'), $contexttemplate = new xmldb_field('contexttemplate') ); + // Define fields to be dropped from cmi5launch_sessions. + $table = new xmldb_table('cmi5launch_sessions'); + + // Now cycle through array and remove fields. + foreach ($fieldstoremove as $field) { + // Conditionally launch drop field registrationcourseausid. + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field); + } + } + } + + // Define table cmi5launch_aus to be created. + $table = new xmldb_table('cmi5launch_aus'); + + // Adding fields to table cmi5launch_aus. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, null, XMLDB_SEQUENCE, null); + $table->add_field('attempt', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('auid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('currentgrade', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('lmsid', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('url', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('type', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('title', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('moveon', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('auindex', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('parents', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('objectives', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('activitytype', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('masteryscore', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('completed', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('passed', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('inprogress', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('noattempt', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('satisfied', XMLDB_TYPE_CHAR, '5', null, null, null, '0'); + $table->add_field('sessions', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('scores', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch_aus. + $table->add_key('id', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_aus. + $table->add_index('courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + + // Conditionally launch create table for cmi5launch_aus. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Cmi5launch savepoint reached. + upgrade_mod_savepoint(true, 2023101217, 'cmi5launch'); + } + + if ($oldversion < 2023081516) { + + // Define table cmi5launch to be created. + $table = new xmldb_table('cmi5launch'); + + // Adding fields to table cmi5launch. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('intro', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('introformat', XMLDB_TYPE_INTEGER, '4', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('cmi5launchurl', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5activityid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('cmi5verbid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5expiry', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '365'); + $table->add_field('cmi5multipleregs', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); + $table->add_field('timecreated', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('timemodified', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('courseinfo', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('aus', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch. + $table->add_index('course', XMLDB_INDEX_NOTUNIQUE, ['course']); + + // Conditionally launch create table for cmi5launch. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_course to be created. + $table = new xmldb_table('cmi5launch_course'); + + // Adding fields to table cmi5launch_course. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('cmi5launchurl', XMLDB_TYPE_CHAR, '1333', null, XMLDB_NOTNULL, null, null); + $table->add_field('cmi5activityid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('aus', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch_course. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_course. + $table->add_index('courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + + // Conditionally launch create table for cmi5launch_course. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_lrs to be created. + $table = new xmldb_table('cmi5launch_lrs'); + + // Adding fields to table cmi5launch_lrs. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('cmi5launchid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsendpoint', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsauthentication', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrslogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrspass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('customacchp', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('useactoremail', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); + $table->add_field('lrsduration', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantpass', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('tenanttoken', XMLDB_TYPE_CHAR, '350', null, null, null, null); + $table->add_field('playerport', XMLDB_TYPE_INTEGER, '5', null, null, null, '66398'); + $table->add_field('playerurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + + // Adding keys to table cmi5launch_lrs. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_lrs. + $table->add_index('cmi5launchid', XMLDB_INDEX_NOTUNIQUE, ['cmi5launchid']); + + // Conditionally launch create table for cmi5launch_lrs. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_player to be created. + $table = new xmldb_table('cmi5launch_player'); + + // Adding fields to table cmi5launch_player. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('name', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenantid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('tenanttoken', XMLDB_TYPE_CHAR, '350', null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('homepage', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchurl', XMLDB_TYPE_CHAR, '500', null, null, null, null); + $table->add_field('cmi5playerurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('cmi5playerport', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('courseinfo', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('firstlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('lastlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + + // Adding keys to table cmi5launch_player. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['registrationid']); + + // Adding indexes to table cmi5launch_player. + $table->add_index('name', XMLDB_INDEX_NOTUNIQUE, ['name']); + + // Conditionally launch create table for cmi5launch_player. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_sessions to be created. + $table = new xmldb_table('cmi5launch_sessions'); + + // Adding fields to table cmi5launch_sessions. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, null, XMLDB_SEQUENCE, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('registrationscoursesausid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('lmsid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('createdat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('updatedat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('registrationcourseausid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('code', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('launchtokenid', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('lastrequesttime', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('launchmode', XMLDB_TYPE_CHAR, '25', null, null, null, null); + $table->add_field('masteryscore', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('score', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('response', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('contexttemplate', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('islaunched', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isinitialized', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('initializedat', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('duration', XMLDB_TYPE_CHAR, '30', null, null, null, null); + $table->add_field('iscompleted', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('ispassed', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isfailed', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isterminated', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('isabandoned', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('progress', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('lrscode', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('auid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchurl', XMLDB_TYPE_CHAR, '750', null, null, null, null); + $table->add_field('firstlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('lastlaunch', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('completed_passed', XMLDB_TYPE_CHAR, '10', null, null, null, null); + + // Adding keys to table cmi5launch_sessions. + $table->add_key('primary', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_sessions. + $table->add_index('lmsid', XMLDB_INDEX_NOTUNIQUE, ['lmsid']); + + // Conditionally launch create table for cmi5launch_sessions. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // Define table cmi5launch_aus to be created. + $table = new xmldb_table('cmi5launch_aus'); + + // Adding fields to table cmi5launch_aus. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, null, XMLDB_SEQUENCE, null); + $table->add_field('auid', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('courseid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0'); + $table->add_field('userid', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + $table->add_field('tenantname', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('currentgrade', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('launchmethod', XMLDB_TYPE_CHAR, '10', null, null, null, 'AnyWindow'); + $table->add_field('registrationid', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('sessionid', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('returnurl', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('lmsid', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('url', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('type', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('title', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('moveon', XMLDB_TYPE_CHAR, '50', null, null, null, null); + $table->add_field('auindex', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('parents', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('objectives', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('description', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('activitytype', XMLDB_TYPE_CHAR, '255', null, null, null, null); + $table->add_field('masteryscore', XMLDB_TYPE_INTEGER, '10', null, null, null, null); + $table->add_field('completed', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('passed', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('inprogress', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('noattempt', XMLDB_TYPE_INTEGER, '1', null, null, null, '0'); + $table->add_field('satisfied', XMLDB_TYPE_CHAR, '5', null, null, null, '0'); + $table->add_field('sessions', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('scores', XMLDB_TYPE_TEXT, null, null, null, null, null); + $table->add_field('grade', XMLDB_TYPE_INTEGER, '10', null, null, null, '0'); + + // Adding keys to table cmi5launch_aus. + $table->add_key('id', XMLDB_KEY_PRIMARY, ['id']); + + // Adding indexes to table cmi5launch_aus. + $table->add_index('courseid', XMLDB_INDEX_NOTUNIQUE, ['courseid']); + + // Conditionally launch create table for cmi5launch_aus. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + upgrade_mod_savepoint(true, 2023081516, 'cmi5launch'); + + } + + if ($oldversion < 2013083100) { + // Define field cmi5activityid to be added to cmi5launch. + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('cmi5activityid', XMLDB_TYPE_TEXT, '1333', null, XMLDB_NOTNULL, null, null, 'cmi5launchurl'); + + // Add field cmi5activityid. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + upgrade_mod_savepoint(true, 2013083100, 'cmi5launch'); + } + + if ($oldversion < 2013111600) { + // Define field cmi5verbid to be added to cmi5launch. + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('cmi5verbid', XMLDB_TYPE_TEXT, '1333', null, XMLDB_NOTNULL, null, null, 'cmi5launchurl'); + + // Add field cmi5activityid. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + upgrade_mod_savepoint(true, 2013111600, 'cmi5launch'); + } + + if ($oldversion < 2015032500) { + + // Define field overridedefaults to be added to cmi5launch. + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('overridedefaults', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '0', 'cmi5verbid'); + + // Conditionally launch add field overridedefaults. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + // Define table cmi5launch_lrs to be created. + $table = new xmldb_table('cmi5launch_lrs'); + + // Adding fields to table cmi5launch_lrs. + $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null); + $table->add_field('cmi5launchid', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsendpoint', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsauthentication', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrslogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrspass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + $table->add_field('lrsduration', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, null); + + // Adding keys to table cmi5launch_lrs. + $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id')); + + // Adding indexes to table cmi5launch_lrs. + $table->add_index('cmi5launchid', XMLDB_INDEX_NOTUNIQUE, array('cmi5launchid')); + + // Conditionally launch create table for cmi5launch_lrs. + if (!$dbman->table_exists($table)) { + $dbman->create_table($table); + } + + // CMI5launch savepoint reached. + upgrade_mod_savepoint(true, 2015032500, 'cmi5launch'); + } + + if ($oldversion < 2015033100) { + + unset_config('cmi5launchlrsversion', 'cmi5launch'); + unset_config('cmi5launchlrauthentication', 'cmi5launch'); + + upgrade_mod_savepoint(true, 2015033100, 'cmi5launch'); + } + + if ($oldversion < 2015112702) { + // Define field cmi5activityid to be added to cmi5launch. + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('cmi5multipleregs', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1', 'cmi5verbid'); + + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $table = new xmldb_table('cmi5launch_lrs'); + $field = new xmldb_field('useactoremail', XMLDB_TYPE_INTEGER, '1', null, XMLDB_NOTNULL, null, '1'); + + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $table->add_field('customacchp', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + upgrade_mod_savepoint(true, 2015112702, 'cmi5launch'); + } + + if ($oldversion < 2016121200) { + $table = new xmldb_table('cmi5launch'); + $field = new xmldb_field('cmi5expiry', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, 365); + + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + } + + if ($oldversion < 2018103000) { + $table = new xmldb_table('cmi5launch_credentials'); + if ($dbman->table_exists($table)) { + $dbman->drop_table($table, $continue = true, $feedback = true); + } + + $table = new xmldb_table('cmi5launch_lrs'); + $field = new xmldb_field('watershedlogin', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field, $continue = true, $feedback = true); + } + + $field = new xmldb_field('watershedpass', XMLDB_TYPE_CHAR, '255', null, XMLDB_NOTNULL, null, null); + if ($dbman->field_exists($table, $field)) { + $dbman->drop_field($table, $field, $continue = true, $feedback = true); + } + + upgrade_mod_savepoint(true, 2018103000, 'cmi5launch'); + } + + // Final return of upgrade result (true, all went good) to Moodle. + return true; +} diff --git a/grade.php b/grade.php old mode 100644 new mode 100755 index 66ef1e3..e8ef30e --- a/grade.php +++ b/grade.php @@ -1,5 +1,4 @@ - - . /** - * Redirect the user based on their capabilities to either a CMI5 activity or to CMI5 reports - * + * Redirect the user based on their capabilities to reporting page. * @package mod_cmi5 * @category grade - * @copyright 2023 M.Bohland + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2010 onwards Dan Marsden * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ require_once("../../config.php"); +require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +require('header.php'); -//MB TODO -//Maybe return later? Framework for Gradebook in Moodle integration +require_login($course, false, $cm); +// This page is the go-between from moodle grader (index.php) to our report.php. +// It's never visited itself, but is almost like an invisible page. +// So the params it holds or can retrieve, such as gradeid, userid, etc., can all be retrieved here and passed to report.php. // Course module ID +// This is what's needed if we only want the full course view, it is always included. $id = required_param('id', PARAM_INT); -// Item number, may be != 0 for activities that allow more than one grade per user -$itemnumber = optional_param('itemnumber', 0, PARAM_INT); - // Graded user ID (optional) + +// The following are optional parameters, they are what is needed if we want to zoom in on only ONE user's grades. + +// Item number, may be != 0 for activities that allow more than one grade per user. +// Itemnumber is from the moodle grade_items table, which holds info on the grade item itself such as course, mod type, etc. +$itemnumber = optional_param('itemnumber', 0, PARAM_INT); + +// Graded user ID (optional) (not the currently logged in user). $userid = optional_param('userid', 0, PARAM_INT); -if (! $cm = get_coursemodule_from_id('cmi5', $id)) { - throw new \moodle_exception('invalidcoursemodule'); -} +// The itemid is from the Moodle grade_grades table, it corresponds to a grade column. +$itemid = optional_param('itemid', 0, PARAM_INT); + +// This gradeid, which is also from the grade_grades table. It corresponds to a row entry, ie. a particular users info. +$gradeid = optional_param('gradeid', 0, PARAM_INT); +$contextmodule = context_module::instance($cm->id); -if (! $scorm = $DB->get_record('cmi5launch', array('id' => $cm->instance))) { +global $cmi5launch, $USER; + +// Get the course module. +if (! $cm = get_coursemodule_from_id('cmi5launch', $cm->id)) { throw new \moodle_exception('invalidcoursemodule'); } -if (! $course = $DB->get_record('cmi5launch', array('id' => $cm->id))) { +if (! $course = $DB->get_record('cmi5launch', array('course' => $cm->course, 'name' => $cm->name))) { + + $returned = $DB->get_record('cmi5launch', array('course' => $cm->course, 'name' => $cm->name)); + throw new \moodle_exception('coursemisconf'); } -require_login($course, false, $cm); +// Check the user has the capability to view grades. +if (has_capability('mod/cmi5launch:viewgrades', $context)) { + + // This is a teacher/manager/etc, they can see all grades, so we need to update all grades before they view. + // Get all enrolled users. + $users = get_enrolled_users($contextmodule); + + foreach ($users as $user) { + + // Call updategrades to ensure all grades are up to date before view. + cmi5launch_update_grades($cm, $user->id); + } + + // If the logged in user has an id pass that along, as they may have grades to view as well. + if ($userid != 0 || null) { + + redirect('report.php?id=' . $cm->id . '&userid=' . $userid . '&itemnumber=' . + $itemnumber . '&itemid=' . $itemid . '&gradeid=' . $gradeid); + + } else { + redirect('report.php?id=' . $cm->id . '&itemid=' . $itemid); + } -//How scorm did it -/* -if (has_capability('mod/scorm:viewreport', context_module::instance($cm->id))) { - redirect('report.php?id='.$cm->id); } else { - redirect('view.php?id='.$cm->id); + // This is student or other non-teacher role. + // If this is just the student we only need to worry about updating their grades, because thats all they'll see. + // Retrieve/update the users grades for this course. + cmi5launch_update_grades($cmi5launch, $USER->id); + + redirect('report.php?id='.$cm->id .'&userid=' . $userid ); } -*/ -//TODO -//We are currently using this capability, but we should make one for grading -if (has_capability('mod/cmi5launch:addinstance', $context)) { - //This is teacher/manger/non editing teacher; - redirect('report.php?id='.$cm->id); -}else{ - //This is student or other non-teacher role - redirect('view.php?id='.$cm->id); -} diff --git a/header.php b/header.php index 4ba2509..e14eed0 100755 --- a/header.php +++ b/header.php @@ -1,45 +1,45 @@ -. - -/** - * launches the experience with the requested registration - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once(dirname(__FILE__).'/lib.php'); -require_once('locallib.php'); - -$id = optional_param('id', 0, PARAM_INT); -$n = optional_param('n', 0, PARAM_INT); - -if ($id) { - $cm = get_coursemodule_from_id('cmi5launch', $id, 0, false, MUST_EXIST); - $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); - $cmi5launch = $DB->get_record('cmi5launch', array('id' => $cm->instance), '*', MUST_EXIST); -} else if ($n) { - $cmi5launch = $DB->get_record('cmi5launch', array('id' => $n), '*', MUST_EXIST); - $course = $DB->get_record('course', array('id' => $cmi5launch->course), '*', MUST_EXIST); - $cm = get_coursemodule_from_instance('cmi5launch', $cmi5launch->id, $course->id, false, MUST_EXIST); -} else { - error(get_string('idmissing', 'report_cmi5')); -} - -require_login($course, true, $cm); -$context = context_module::instance($cm->id); \ No newline at end of file +. + +/** + * launches the experience with the requested registration + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once(dirname(__FILE__).'/lib.php'); +require_once('locallib.php'); + +$id = optional_param('id', 0, PARAM_INT); +$n = optional_param('n', 0, PARAM_INT); + +if ($id) { + $cm = get_coursemodule_from_id('cmi5launch', $id, 0, false, MUST_EXIST); + $course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + $cmi5launch = $DB->get_record('cmi5launch', array('id' => $cm->instance), '*', MUST_EXIST); +} else if ($n) { + $cmi5launch = $DB->get_record('cmi5launch', array('id' => $n), '*', MUST_EXIST); + $course = $DB->get_record('course', array('id' => $cmi5launch->course), '*', MUST_EXIST); + $cm = get_coursemodule_from_instance('cmi5launch', $cmi5launch->id, $course->id, false, MUST_EXIST); +} else { + error(get_string('idmissing', 'report_cmi5')); +} + +require_login($course, true, $cm); +$context = context_module::instance($cm->id); diff --git a/index.php b/index.php index 199717e..777db63 100755 --- a/index.php +++ b/index.php @@ -1,89 +1,90 @@ -. - -/** - * This is a one-line short description of the file - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once(dirname(__FILE__).'/lib.php'); - -$id = required_param('id', PARAM_INT); - -$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); - -require_course_login($course); - -// Trigger instances list viewed event. -$event = \mod_cmi5launch\event\course_module_instance_list_viewed::create( - array('context' => context_course::instance($course->id)) -); -$event->add_record_snapshot('course', $course); -$event->trigger(); - -$coursecontext = context_course::instance($course->id); - -$PAGE->set_url('/mod/cmi5launch/index.php', array('id' => $id)); -$PAGE->set_title(format_string($course->fullname)); -$PAGE->set_heading(format_string($course->fullname)); -$PAGE->set_context($coursecontext); - -echo $OUTPUT->header(); - -if (! $cmi5launchs = get_all_instances_in_course('cmi5launch', $course)) { - notice(get_string('nocmi5launchs', 'cmi5launch'), new moodle_url('/course/view.php', array('id' => $course->id))); -} - -if ($course->format == 'weeks') { - $table->head = array(get_string('week'), get_string('name')); - $table->align = array('center', 'left'); -} else if ($course->format == 'topics') { - $table->head = array(get_string('topic'), get_string('name')); - $table->align = array('center', 'left', 'left', 'left'); -} else { - $table->head = array(get_string('name')); - $table->align = array('left', 'left', 'left'); -} - -foreach ($cmi5launchs as $cmi5launch) { - if (!$cmi5launch->visible) { - $link = html_writer::link( - new moodle_url('/mod/cmi5launch.php', array('id' => $cmi5launch->coursemodule)), - format_string($cmi5launch->name, true), - array('class' => 'dimmed')); - } else { - $link = html_writer::link( - new moodle_url('/mod/cmi5launch.php', array('id' => $cmi5launch->coursemodule)), - format_string($cmi5launch->name, true)); - } - - if ($course->format == 'weeks' or $course->format == 'topics') { - $table->data[] = array($cmi5launch->section, $link); - } else { - $table->data[] = array($link); - } - -} -echo $OUTPUT->heading(get_string('modulenameplural', 'cmi5launch'), 2); -echo html_writer::table($table); -echo $OUTPUT->footer(); +. + +/** + * This is a one-line short description of the file + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2024 Megan Bohland + * @copyright Based on work by 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); +require_once(dirname(__FILE__).'/lib.php'); + +$id = required_param('id', PARAM_INT); + +$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST); + +require_course_login($course); + +// Trigger instances list viewed event. +$event = \mod_cmi5launch\event\course_module_instance_list_viewed::create( + array('context' => context_course::instance($course->id)) +); +$event->add_record_snapshot('course', $course); +$event->trigger(); + +$coursecontext = context_course::instance($course->id); + +$PAGE->set_url('/mod/cmi5launch/index.php', array('id' => $id)); +$PAGE->set_title(format_string($course->fullname)); +$PAGE->set_heading(format_string($course->fullname)); +$PAGE->set_context($coursecontext); + +echo $OUTPUT->header(); + +if (! $cmi5launchs = get_all_instances_in_course('cmi5launch', $course)) { + notice(get_string('nocmi5launchs', 'cmi5launch'), new moodle_url('/course/view.php', array('id' => $course->id))); +} + +if ($course->format == 'weeks') { + $table->head = array(get_string('week'), get_string('name')); + $table->align = array('center', 'left'); +} else if ($course->format == 'topics') { + $table->head = array(get_string('topic'), get_string('name')); + $table->align = array('center', 'left', 'left', 'left'); +} else { + $table->head = array(get_string('name')); + $table->align = array('left', 'left', 'left'); +} + +foreach ($cmi5launchs as $cmi5launch) { + if (!$cmi5launch->visible) { + $link = html_writer::link( + new moodle_url('/mod/cmi5launch.php', array('id' => $cmi5launch->coursemodule)), + format_string($cmi5launch->name, true), + array('class' => 'dimmed')); + } else { + $link = html_writer::link( + new moodle_url('/mod/cmi5launch.php', array('id' => $cmi5launch->coursemodule)), + format_string($cmi5launch->name, true)); + } + + if ($course->format == 'weeks' || $course->format == 'topics') { + $table->data[] = array($cmi5launch->section, $link); + } else { + $table->data[] = array($link); + } + +} +echo $OUTPUT->heading(get_string('modulenameplural', 'cmi5launch'), 2); +echo html_writer::table($table); +echo $OUTPUT->footer(); diff --git a/lang/de/cmi5launch.php b/lang/de/cmi5launch.php index 115ab87..7b0faae 100755 --- a/lang/de/cmi5launch.php +++ b/lang/de/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * German strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = 'Zunächst startete'; -$string['cmi5launchviewlastlaunched'] = 'Letzte gestartet'; -$string['cmi5launchviewlaunchlinkheader'] = 'Start Link'; -$string['cmi5launchviewlaunchlink'] = 'Start'; - -$string['cmi5launch_completed'] = 'Erleben Sie komplett!'; -$string['cmi5launch_progress'] = 'Versuch im Gange'; -$string['cmi5launch_attempt'] = 'Neuer Versuch'; -$string['cmi5launch_notavailable'] = 'Das Learning Record Store ist nicht verfügbar. Bitte wenden Sie sich an den Systemadministrator.'; - +. + + +/** + * German strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = 'Zunächst startete'; +$string['cmi5launchviewlastlaunched'] = 'Letzte gestartet'; +$string['cmi5launchviewlaunchlinkheader'] = 'Start Link'; +$string['cmi5launchviewlaunchlink'] = 'Start'; + +$string['cmi5launch_completed'] = 'Erleben Sie komplett!'; +$string['cmi5launch_progress'] = 'Versuch im Gange'; +$string['cmi5launch_attempt'] = 'Neuer Versuch'; +$string['cmi5launch_notavailable'] = 'Das Learning Record Store ist nicht verfügbar. Bitte wenden Sie sich an den Systemadministrator.'; + diff --git a/lang/en/cmi5launch.php b/lang/en/cmi5launch.php index dc5fcee..3afdf97 100755 --- a/lang/en/cmi5launch.php +++ b/lang/en/cmi5launch.php @@ -1,258 +1,324 @@ -. - - -/** - * English strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -//$string['trialname'] = "Is this working? If so I rock!"; - -$string['modulename'] = 'cmi5 Launch Link'; -$string['modulenameplural'] = 'cmi5 Launch Links'; -$string['modulename_help'] = 'A plug in for Moodle that allows the launch of cmi5 (xAPI) content which is then tracked to a separate LRS.'; - -// Start Default LRS Admin Settings. -$string['cmi5launchlrsfieldset'] = 'Default values for cmi5 Launch Link activity settings'; -$string['cmi5launchlrsfieldset_help'] = 'These are site-wide, default values used when creating a new activity. Each activity has the ability to override and provide alternative values.'; - -$string['cmi5launchlrsendpoint'] = 'Endpoint'; -$string['cmi5launchlrsendpoint_help'] = 'The LRS endpoint (e.g. http://lrs.example.com/endpoint/). Must include trailing forward slash.'; -$string['cmi5launchlrsendpoint_default'] = ''; - -$string['cmi5launchlrslogin'] = 'LRS: Basic Username'; -$string['cmi5launchlrslogin_help'] = 'Your LRS login username.'; -$string['cmi5launchlrslogin_default'] = ''; - -$string['cmi5launchlrspass'] = 'LRS: Basic Password'; -$string['cmi5launchlrspass_help'] = 'Your LRS password (secret).'; -$string['cmi5launchlrspass_default'] = ''; - -$string['cmi5launchlrsduration'] = 'Duration'; -$string['cmi5launchlrsduration_help'] = 'Used with \'LRS integrated basic authentication\'. Requests the LRS to keep credentials valid for this number of minutes.'; -$string['cmi5launchlrsduration_default'] = '9000'; - -$string['cmi5launchlrsauthentication'] = 'LRS integration'; -$string['cmi5launchlrsauthentication_help'] = 'Use additional integration features to create new authentication credentials for each launch for supported LRSs.'; -$string['cmi5launchlrsauthentication_watershedhelp'] = 'Note: for Watershed integration, the Activity Provider does not require API access enabled.'; -$string['cmi5launchlrsauthentication_watershedhelp_label'] = 'Watershed integration'; -$string['cmi5launchlrsauthentication_option_0'] = 'None'; -$string['cmi5launchlrsauthentication_option_1'] = 'Watershed'; -$string['cmi5launchlrsauthentication_option_2'] = 'Learning Locker 1'; - -$string['cmi5launchuseactoremail'] = 'Identify by email'; -$string['cmi5launchuseactoremail_help'] = 'If selected, learners will be identified by their email address if they have one recorded in Moodle.'; - -$string['cmi5launchcustomacchp'] = 'Custom account homePage'; -$string['cmi5launchcustomacchp_help'] = 'If entered, Moodle will use this homePage in conjunction with the ID number user profile field to identify the learner. -If the ID number is not entered for a learner, they will instead be identified by email or Moodle ID number. -Note: If a learner\'s id changes, they will lose access to registrations associated with former ids and completion data may be reset. Reports in your LRS may also be affected.'; -$string['cmi5launchcustomacchp_default'] = 'https://moodle.com'; - -//Cmi5 grades admin -// Start Default LRS Admin Settings. -$string['cmi5launchgradesettings'] = 'Default values for cmi5 Launch Link activity settings'; -$string['cmi5launchgradesettings_help'] = 'These are site-wide, default values used when creating a new activity. Each activity has the ability to override and provide alternative values.'; - -/// -$string['othersettings'] = 'Additional settings'; - -//cmi5 player root location -$string['cmi5launchplayerurl'] = 'cmi5 Player URL'; -$string['cmi5launchplayerurl_help'] = 'The url to communicate with CMI5 player, can include port number(e.g. http://player.example.com or http://localhost:63398). Must NOT include a trailing forward slash.'; -$string['cmi5launchplayerurl_default'] = ''; - -//cmi5 player credentials -$string['cmi5launchtenantname'] = 'cmi5 Player: Basic Username'; -$string['cmi5launchtenantname_help'] = 'The cmi5 tenant username.'; -$string['cmi5launchtenantname_default'] = ''; - -$string['cmi5launchtenantpass'] = 'cmi5 Player: Basic Password'; -$string['cmi5launchtenantpass_help'] = 'The cmi5 tenant password (secret).'; -$string['cmi5launchtenantpass_default'] = ''; - -$string['cmi5launchtenanttoken'] = 'cmi5 Player: Bearer Token'; -$string['cmi5launchtenanttoken_help'] = 'The cmi5 tenant bearer token (should be a long string).'; -$string['cmi5launchtenanttoken_default'] = ''; - -//Grading info - MB - -//Headers -$string['defaultgradesettings'] = 'Default values for CMI5 Launch Link activity grades'; -$string['defaultothersettings'] = 'Default values for CMI5 Launch Link activity attempts and completion'; - -//other -$string['maximumgradedesc'] = 'The maximum grade for a CMI5 Launch Link activity'; - -$string['maximumattempts'] = 'Maxium Attempt Amount'; -$string['whatmaxdesc'] = 'The maximum amount of allowed attempts'; - -$string['maximumattempts'] = 'Maxium Attempt Amount'; -$string['whatmaxdesc'] = 'The maximum amount of allowed attempts'; - -$string['nolimit'] = 'No limit'; -$string['attempt1'] = '1 attempt'; -$string['attemptsx'] = '{$a} attempts'; - -$string['whatgrade'] = 'Attempts grading'; -$string['whatgradedesc'] = 'If multiple attempts are allowed, this setting specifies whether the highest, average (mean), first or last completed attempt is recorded in the gradebook. The last completed attempt option does not include attempts with a \'failed\' status.'; -$string['HIGHEST_ATTEMPT_CMI5'] = 'Highest attempt'; -$string['AVERAGE_ATTEMPT_CMI5'] = 'Average of attempts'; -$string['FIRST_ATTEMPT_CMI5'] = 'First attempt'; -$string['last_attempt_cmi5'] = 'Last attempt'; - -$string['lastattempt'] = 'Last completed attempt'; -$string['last_attempt_cmi5_lock'] = 'Lock after final attempt'; -$string['lastattemptlock_help'] = 'If enabled, a student is prevented from launching the CMI5 player after using up all their allocated attempts.'; -$string['last_attempt_cmi5_lockdesc'] = 'If enabled, a student is prevented from launching the CMI5 player after using up all their allocated attempts.'; - -//-MB - Not sure if we need ALL of these -//* No - If a previous attempt is completed, passed or failed, the student will be provided with the option to enter in review mode or start a new attempt. -//* When previous attempt completed, passed or failed - This relies on the SCORM package setting the status of \'completed\', \'passed\' or \'failed\'. -//* Always - Each re-entry to the SCORM activity will generate a new attempt and the student will not be returned to the same point they reached in their previous attempt.'; -/* -$string['forceattemptalways'] = 'Always'; -$string['forceattemptoncomplete'] = 'When previous attempt completed, passed or failed'; -$string['forcejavascript'] = 'Force users to enable JavaScript'; -$string['forcejavascript_desc'] = 'If enabled (recommended) this prevents access to SCORM objects when JavaScript is not supported/enabled in a users browser. If disabled the user may view the SCORM but API communication will fail and no grade information will be saved.'; -$string['forcejavascriptmessage'] = 'JavaScript is required to view this object, please enable JavaScript in your browser and try again.'; -$string['found'] = 'Manifest found'; -$string['frameheight'] = 'The height of the stage frame or window.'; -$string['framewidth'] = 'The width of the stage frame or window.'; -$string['fromleft'] = 'From left'; -$string['fromtop'] = 'From top'; -$string['fullscreen'] = 'Fill the whole screen';*/ - -//Not sure if we want to implement these? -/*$string['masteryoverride'] = 'Mastery score overrides status'; -$string['masteryoverride_help'] = 'If enabled and a mastery score is provided, when LMSFinish is called and a raw score has been set, status will be recalculated using the raw score and mastery score and any status provided by the SCORM (including "incomplete") will be overridden.'; -$string['masteryoverridedesc'] = 'This preference sets the default for the mastery score override setting'; -*/ - -$string['general'] = 'General data'; -$string['GRADE_AVERAGE_CMI5'] = 'Average grade'; -$string['gradeforattempt'] = 'Grade for attempt'; -$string['GRADE_HIGHEST_CMI5'] = 'Highest grade'; -$string['grademethod'] = 'Grading method'; -//TODO - Is this accurate? Does it define for only ONE attempt? -$string['grademethod_help'] = 'The grading method defines how the grade for a single attempt of the activity is determined. - -There are 4 grading methods: - -* Learning objects - The number of completed/passed learning objects -* Highest grade - The highest score obtained in all passed learning objects -* Average grade - The mean of all the scores -* Sum grade - The sum of all the scores'; -$string['grademethoddesc'] = 'The grading method defines how the grade for a single attempt of the activity is determined.'; -$string['gradereported'] = 'Grade reported'; -$string['gradesettings'] = 'Grade settings'; -$string['GRADE_CMI5_AUS'] = 'Learning objects'; -$string['GRADE_SUM_CMI5'] = 'Sum grade'; - - -// Start Activity Settings. -$string['cmi5launchname'] = 'Launch link name'; -$string['cmi5launchname_help'] = 'The name of the launch link as it will appear to the user.'; - -$string['cmi5launchurl'] = 'Launch URL'; -$string['cmi5launchurl_help'] = 'The base URL of the cmi5 activity you want to launch (e.g. http://example.com/content/index.html).'; - -$string['cmi5activityid'] = 'Activity ID'; -$string['cmi5activityid_help'] = 'The identifying IRI for the primary activity being launched.'; - -$string['cmi5package'] = 'Zip package'; -$string['cmi5package_help'] = 'If you have a packaged cmi5 course, you can upload it here. If you upload a package, the Launch URL and Activity ID fields above will be automatically populated when you save using data from the cmi5.xml file contained in the zip. You can edit these settings at any time, but should not change the Activity ID (either directly or by file upload) unless you understand the consequences.'; - -$string['cmi5packagetitle'] = 'Launch settings'; -$string['cmi5packagetext'] = 'You can populate the Launch URL and Activity ID settings directly, or by uploading a zip package containing a cmi5.xml file. The launch url defined in the cmi5.xml may point to other files in the zip package, or to an external URL. The Activity ID must always be a full URL (or other IRI).'; - -$string['lrsheading'] = 'LRS settings'; -$string['lrsdefaults'] = 'LRS Default Settings'; -$string['lrssettingdescription'] = 'By default, this activity uses the global LRS settings found in Site administration > Plugins > Activity modules > cmi5 Launch Link. To change the settings for this specific activity, select Unlock Defaults.'; -$string['overridedefaults'] = 'Unlock Defaults'; -$string['overridedefaults_help'] = 'Allows activity to have different LRS settings than the site-wide, default LRS settings.'; - -$string['behaviorheading'] = 'Module behavior'; - -$string['cmi5multipleregs'] = 'Allow multiple registrations.'; -$string['cmi5multipleregs_help'] = 'If selected, allow the learner to start more than one registration for the activity. Learners can always return to any registrations they have started, even if this setting is unchecked.'; - -$string['apCreationFailed'] = 'Failed to create Watershed Activity Provider.'; - -// Zip errors. -$string['badmanifest'] = 'Some manifest errors: see errors log'; -$string['badimsmanifestlocation'] = 'A cmi5.xml file was found but it was not in the root of your zip file, please re-package your course'; -$string['badarchive'] = 'You must provide a valid zip file'; -$string['nomanifest'] = 'Incorrect file package - missing cmi5.xml'; - -$string['cmi5launch'] = 'cmi5 Launch Link'; -$string['pluginadministration'] = 'cmi5 Launch Link administration'; -$string['pluginname'] = 'cmi5 Launch Link'; - -// Verb completion settings. -$string['completionverb'] = 'Verb'; -$string['completionverbgroup'] = 'Track completion by verb'; -$string['completionverbgroup_help'] = 'Moodle will look for statements where the actor is the current user, the object is the specified activity id and the verb is the one set here. If it finds a matching statement, the activity will be marked complete.'; - -// Expiry completion settings. -$string['completionexpiry'] = 'Expiry'; -$string['completionexpirygroup'] = 'Completion Expires After (days)'; -$string['completionexpirygroup_help'] = 'If checked, when looking for completion Moodle will only look at data stored in the LRS in the previous X days. It will unset completion for learners who had previously completed but whose completion has now expired.'; - -// AU View settings. -$string['cmi5launchviewfirstlaunched'] = 'First launched'; -$string['cmi5launchviewlastlaunched'] = 'Last launched'; -$string['cmi5launchviewlaunchlinkheader'] = 'Launch link'; -$string['cmi5launchviewlaunchlink'] = 'launch'; -$string['cmi5launchviewprogress'] = 'Progress'; -$string['cmi5launchviewgradeheader'] = 'Grade'; - -// View settings. -$string['cmi5launchviewAUname'] = 'Name'; -$string['cmi5launchviewstatus'] = 'Status'; -$string['cmi5launchviewregistrationheader'] = 'Sessions'; -$string['cmi5launchviewgradeheader'] = 'Grade'; -$string['cmi5launchviewlaunchlink'] = 'view'; -$string['AUtableheader'] = 'Assignable Units'; - - -$string['cmi5launch_completed'] = 'Experience complete!'; -$string['cmi5launch_progress'] = 'Attempt in progress.'; -$string['cmi5launch_attempt'] = 'Start New Session'; -$string['cmi5launch_notavailable'] = 'The Learning Record Store is not available. Please contact a system administrator. - -If you are the system administrator, go to Site admin / Development / Debugging and set Debug messages to DEVELOPER. Set it back to NONE or MINIMAL once the error details have been recorded.'; -$string['cmi5launch_regidempty'] = 'Registration id not found. Please close this window.'; - -$string['idmissing'] = 'You must specify a course_module ID or an instance ID'; - -// Events. -$string['eventactivitylaunched'] = 'Activity launched'; -$string['eventactivitycompleted'] = 'Activity completed'; - -$string['cmi5launch:addinstance'] = 'Add a new cmi5 (xAPI) activity to a course'; - -$string['expirecredentials'] = 'Expire credentials'; -$string['checkcompletion'] = 'Check Completion'; \ No newline at end of file +. + + +/** + * English strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2013 Andrew Downes + * @copyright and on work by 1999 onwards Martin Dougiamas {@link http://moodle.com} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['modulename'] = 'cmi5 launch link'; +$string['modulenameplural'] = 'cmi5 launch links'; +$string['modulename_help'] = 'A plug in for Moodle that allows the launch of cmi5 (xAPI) content which is then tracked to a separate LRS.'; + +// Start Default LRS Admin Settings. +$string['cmi5launchlrsfieldset'] = 'Default values for cmi5 launch link activity settings'; +$string['cmi5launchlrsfieldset_help'] = 'These are site-wide, default values used when creating a new activity. Each activity has the ability to override and provide alternative values.'; + +$string['cmi5launchlrsendpoint'] = 'Endpoint'; +$string['cmi5launchlrsendpoint_help'] = 'The LRS endpoint (e.g. http://lrs.example.com/endpoint/). Must include trailing forward slash.'; +$string['cmi5launchlrsendpoint_default'] = ''; + +$string['cmi5launchlrslogin'] = 'LRS: basic username'; +$string['cmi5launchlrslogin_help'] = 'Your LRS login username.'; +$string['cmi5launchlrslogin_default'] = ''; + +$string['cmi5launchlrspass'] = 'LRS: basic password'; +$string['cmi5launchlrspass_help'] = 'Your LRS password (secret).'; +$string['cmi5launchlrspass_default'] = ''; + +$string['cmi5launchlrsduration'] = 'Duration'; +$string['cmi5launchlrsduration_help'] = 'Used with \'LRS integrated basic authentication\'. Requests the LRS to keep credentials valid for this number of minutes.'; +$string['cmi5launchlrsduration_default'] = '9000'; + +$string['cmi5launchlrsauthentication'] = 'LRS integration'; +$string['cmi5launchlrsauthentication_help'] = 'Use additional integration features to create new authentication credentials for each launch for supported LRSs.'; +$string['cmi5launchlrsauthentication_watershedhelp'] = 'Note: for Watershed integration, the activity provider does not require API access enabled.'; +$string['cmi5launchlrsauthentication_watershedhelp_label'] = 'Watershed integration'; +$string['cmi5launchlrsauthentication_option_0'] = 'None'; +$string['cmi5launchlrsauthentication_option_1'] = 'Watershed'; +$string['cmi5launchlrsauthentication_option_2'] = 'Learning Locker 1'; + +$string['cmi5launchuseactoremail'] = 'Identify by email'; +$string['cmi5launchuseactoremail_help'] = 'If selected, learners will be identified by their email address if they have one recorded in Moodle.'; + +$string['cmi5launchcustomacchp'] = 'Custom account homepage'; +$string['cmi5launchcustomacchp_help'] = 'If entered, Moodle will use this homepage in conjunction with the ID number user profile field to identify the learner. +If the ID number is not entered for a learner, they will instead be identified by email or Moodle ID number. +Note: If a learner\'s id changes, they will lose access to registrations associated with former ids and completion data may be reset. Reports in your LRS may also be affected.'; +$string['cmi5launchcustomacchp_default'] = 'https://moodle.com'; + +// Cmi5 grades admin. +// Start Default LRS Admin Settings. +$string['cmi5launchgradesettings'] = 'Default values for cmi5 launch link activity settings'; +$string['cmi5launchgradesettings_help'] = 'These are site-wide, default values used when creating a new activity. Each activity has the ability to override and provide alternative values.'; + +$string['othersettings'] = 'Additional settings'; + +// Cmi5 player root location. +$string['cmi5launchplayerurl'] = 'cmi5 player URL'; +$string['cmi5launchplayerurl_help'] = 'The url to communicate with cmi5 player, can include port number(e.g. http://player.example.com or http://localhost:63398). Must NOT include a trailing forward slash.'; +$string['cmi5launchplayerurl_default'] = ''; + +// Cmi5 player credentials. +$string['cmi5launchtenantname'] = 'cmi5 player: basic username'; +$string['cmi5launchtenantname_help'] = 'The cmi5 tenant username.'; +$string['cmi5launchtenantname_default'] = ''; + +$string['cmi5launchtenantpass'] = 'cmi5 player: basic password'; +$string['cmi5launchtenantpass_help'] = 'The cmi5 tenant password (secret).'; +$string['cmi5launchtenantpass_default'] = ''; + +$string['cmi5launchtenanttoken'] = 'cmi5 player: bearer token'; +$string['cmi5launchtenanttoken_help'] = 'The cmi5 tenant bearer token (should be a long string).'; +$string['cmi5launchtenanttoken_default'] = ''; + +// Grading info - MB. +// Headers. +$string['defaultgradesettings'] = 'Default values for cmi5 launch link activity grades'; +$string['defaultothersettings'] = 'Default values for cmi5 launch link activity attempts and completion'; + +// Other. +$string['maximumgradedesc'] = 'The maximum grade for a cmi5 launch link activity'; + +$string['maximumattempts'] = 'Maxium attempt amount'; +$string['whatmaxdesc'] = 'The maximum amount of allowed attempts'; + +$string['nolimit'] = 'No limit'; +$string['attempt1'] = '1 attempt'; +$string['attemptsx'] = '{$a} attempts'; + +$string['whatgrade'] = 'Attempts grading'; +$string['whatgradedesc'] = 'If multiple attempts are allowed, this setting specifies whether the highest, average (mean), first or last completed attempt is recorded in the gradebook. The last completed attempt option does not include attempts with a \'failed\' status.'; +$string['mod_cmi5launch_highest_attempt'] = 'Highest attempt'; +$string['mod_cmi5launch_average_attempt'] = 'Average of attempts'; +$string['mod_cmi5launch_first_attempt'] = 'First attempt'; +$string['mod_cmi5launch_last_attempt'] = 'Last attempt'; + +$string['lastattempt'] = 'Last completed attempt'; +$string['mod_cmi5launch_last_attempt_lock'] = 'Lock after final attempt'; +$string['lastattemptlock_help'] = 'If enabled, a student is prevented from launching the cmi5 player after using up all their allocated attempts.'; +$string['mod_cmi5launch_last_attempt_lockdesc'] = 'If enabled, a student is prevented from launching the cmi5 player after using up all their allocated attempts.'; + +/* +MB - Not sure if we need ALL of these +No - If a previous attempt is completed, passed or failed, the student will be provided with the option to enter in review mode or start a new attempt. +When previous attempt completed, passed or failed - This relies on the SCORM package setting the status of \'completed\', \'passed\' or \'failed\'. +Always - Each re-entry to the SCORM activity will generate a new attempt and the student will not be returned to the same point they reached in their previous attempt.'; + +$string['forceattemptalways'] = 'Always'; +$string['forceattemptoncomplete'] = 'When previous attempt completed, passed or failed'; +$string['forcejavascript'] = 'Force users to enable JavaScript'; +$string['forcejavascript_desc'] = 'If enabled (recommended) this prevents access to SCORM objects when JavaScript is not supported/enabled in a users browser. If disabled the user may view the SCORM but API communication will fail and no grade information will be saved.'; +$string['forcejavascriptmessage'] = 'JavaScript is required to view this object, please enable JavaScript in your browser and try again.'; +$string['found'] = 'Manifest found'; +$string['frameheight'] = 'The height of the stage frame or window.'; +$string['framewidth'] = 'The width of the stage frame or window.'; +$string['fromleft'] = 'From left'; +$string['fromtop'] = 'From top'; +$string['fullscreen'] = 'Fill the whole screen'; + +Not sure if we want to implement these? +$string['masteryoverride'] = 'Mastery score overrides status'; +$string['masteryoverride_help'] = 'If enabled and a mastery score is provided, when LMSFinish is called and a raw score has been set, status will be recalculated using the raw score and mastery score and any status provided by the SCORM (including "incomplete") will be overridden.'; +$string['masteryoverridedesc'] = 'This preference sets the default for the mastery score override setting'; +*/ + +$string['general'] = 'General data'; +$string['mod_cmi5launch_grade_average'] = 'Average grade'; +$string['gradeforattempt'] = 'Grade for attempt'; +$string['mod_cmi5launch_grade_highest'] = 'Highest grade'; +$string['grademethod'] = 'Grading method'; +$string['grademethod_help'] = 'The grading method defines how the grade for a single attempt of the activity is determined.'; + +// There are 2 grading methods. +// Highest grade - The highest score obtained in all passed learning objects. +// Average grade - The mean of all the scores. +$string['grademethoddesc'] = 'The grading method defines how the grade for a single attempt of the activity is determined.'; +$string['gradereported'] = 'Grade reported'; +$string['gradesettings'] = 'Grade settings'; + +// Start Activity Settings. +$string['cmi5launchname'] = 'Launch link name'; +$string['cmi5launchname_help'] = 'The name of the launch link as it will appear to the user.'; + +$string['cmi5launchurl'] = 'Launch URL'; +$string['cmi5launchurl_help'] = 'The base URL of the cmi5 activity you want to launch (e.g. http://example.com/content/index.html).'; + +$string['cmi5activityid'] = 'Activity ID'; +$string['cmi5activityid_help'] = 'The identifying IRI for the primary activity being launched.'; + +$string['cmi5package'] = 'Zip package'; +$string['cmi5package_help'] = 'If you have a packaged cmi5 course, you can upload it here. If you upload a package, the launch URL and activity ID fields above will be automatically populated when you save using data from the cmi5.xml file contained in the zip. You can edit these settings at any time, but should not change the activity ID (either directly or by file upload) unless you understand the consequences.'; + +$string['cmi5packagetitle'] = 'cmi5 package upload'; +$string['cmi5packagetext'] = 'Here you upload a zip package containing a cmi5.xml file. The launch URL defined in the cmi5.xml may point to other files in the zip package, or to an external URL.'; + +$string['lrsheading'] = 'LRS settings'; +$string['lrsdefaults'] = 'LRS default settings'; +$string['lrssettingdescription'] = 'By default, this activity uses the global LRS settings found in Site administration > Plugins > Activity modules > cmi5 launch link. To change the settings for this specific activity, select \'unlock Defaults.\''; + +$string['behaviorheading'] = 'Module behavior'; + +$string['cmi5multipleregs'] = 'Allow multiple registrations.'; +$string['cmi5multipleregs_help'] = 'If selected, allow the learner to start more than one registration for the activity. Learners can always return to any registrations they have started, even if this setting is unchecked.'; + +$string['apCreationFailed'] = 'Failed to create Watershed activity provider.'; + +// Zip errors. +$string['badmanifest'] = 'Some manifest errors: see errors log'; +$string['badimsmanifestlocation'] = 'A cmi5.xml file was found but it was not in the root of your zip file, please re-package your course'; +$string['badarchive'] = 'You must provide a valid zip file'; +$string['nomanifest'] = 'Incorrect file package - missing cmi5.xml'; + +$string['cmi5launch'] = 'cmi5 launch link'; +$string['pluginadministration'] = 'cmi5 launch link administration'; +$string['pluginname'] = 'cmi5 launch link'; + +// Verb completion settings. +$string['completionverb'] = 'Verb'; +$string['completionverbgroup'] = 'Track completion by verb'; +$string['completionverbgroup_help'] = 'Moodle will look for statements where the actor is the current user, the object is the specified activity id and the verb is the one set here. If it finds a matching statement, the activity will be marked complete.'; + +// Expiry completion settings. +$string['completionexpiry'] = 'Expiry'; +$string['completionexpirygroup'] = 'Completion Expires After (days)'; +$string['completionexpirygroup_help'] = 'If checked, when looking for completion Moodle will only look at data stored in the LRS in the previous X days. It will unset completion for learners who had previously completed but whose completion has now expired.'; + +// AU View settings. +$string['cmi5launchviewfirstlaunched'] = 'First launched'; +$string['cmi5launchviewlastlaunched'] = 'Last launched'; +$string['cmi5launchviewlaunchlinkheader'] = 'Launch link'; +$string['cmi5launchviewlaunchlink'] = 'launch'; +$string['cmi5launchviewprogress'] = 'Progress'; +$string['cmi5launchviewgradeheader'] = 'Grade'; + +// View settings. +$string['cmi5launchviewAUname'] = 'Name'; +$string['cmi5launchviewstatus'] = 'AU Satisfied Status'; +$string['cmi5launchviewregistrationheader'] = 'Sessions'; +$string['cmi5launchviewgradeheader'] = 'Grade'; +$string['cmi5launchviewlaunchlink'] = 'view'; +$string['autableheader'] = 'Assignable Units'; + + +$string['cmi5launch_completed'] = 'Experience complete!'; +$string['cmi5launch_progress'] = 'Attempt in progress.'; +$string['cmi5launch_attempt'] = 'Start new session'; +$string['cmi5launch_notavailable'] = 'The Learning Record Store is not available. Please contact a system administrator. + +If you are the system administrator, go to Site admin / Development / Debugging and set Debug messages to DEVELOPER. Set it back to NONE or MINIMAL once the error details have been recorded.'; +$string['cmi5launch_regidempty'] = 'Registration id not found. Please close this window.'; + +$string['idmissing'] = 'You must specify a course_module ID or an instance ID'; + +// Events. +$string['eventactivitylaunched'] = 'Activity launched'; +$string['eventactivitycompleted'] = 'Activity completed'; + +$string['cmi5launch:addinstance'] = 'Add a new cmi5 (xAPI) activity to a course'; +$string['cmi5launch:view'] = 'View cmi5 (xAPI) activity'; +$string['cmi5launch:viewgrades'] = "Ability to view all of a course's grades. Often assigned to teachers."; + +$string['expirecredentials'] = 'Expire credentials'; +$string['checkcompletion'] = 'Check completion'; + +// For reports. +$string['report'] = 'Report'; +$string['attempt'] = 'Attempt'; +$string['started'] = 'Started'; +$string['last'] = 'Finished'; +$string['score'] = 'Score'; + +$string['autitle'] = 'AU Title'; +$string['attempt'] = 'Attempt'; +$string['started'] = 'Started'; +$string['last'] = 'Finished'; +$string['score'] = 'Score'; + +// For privacy module. + +// Usercourse table. +$string['privacy:metadata:cmi5launch_usercourse'] = 'The cmi5 launch link plugin stores a users particular instance of a cmi5 Activity. While some things, like the courseid are generic to all users of the course, others, like the grade are specific to user.'; +$string['privacy:metadata:cmi5launch_usercourse:id'] = 'The ID of the user course\'s particular instance assigned by Moodle.'; +$string['privacy:metadata:cmi5launch_usercourse:userid'] = 'The ID of the user'; +$string['privacy:metadata:cmi5launch_usercourse:registrationid'] = 'The registration ID is unique to each users particular cmi5 activity and is assigned by the cmi5 player.'; +$string['privacy:metadata:cmi5launch_usercourse:ausgrades'] = 'All the AUs and their grades (overall session grades) saved in this format: AU lmsid => [AU Title => [Scores from that title\'s sessions].'; +$string['privacy:metadata:cmi5launch_usercourse:grade'] = 'The current overall grade (based on grading type) for the cmi5 activity.'; + +// Sessions table. +$string['privacy:metadata:cmi5launch_sessions'] = 'The cmi5 launch link plugin stores each session of a users particular instance of a cmi5 Activity. While some things, like the masteryscore are generic to all users of the course, others, like the grade are specific to user.'; +$string['privacy:metadata:cmi5launch_sessions:id'] = 'The ID of the user\'s session\'s particular instance assigned by Moodle.'; +$string['privacy:metadata:cmi5launch_sessions:sessionid'] = 'The session id. This is created by the cmi5 player and returned with URL request. Each session has a unique ID.'; +$string['privacy:metadata:cmi5launch_sessions:userid'] = 'User id, which combined with course ID can be used to retrieve unique records.'; +$string['privacy:metadata:cmi5launch_sessions:registrationscoursesausid'] = 'ID assigned by the cmi5 player to AUs.'; +$string['privacy:metadata:cmi5launch_sessions:createdat'] = 'Time a session started (string that is returned by CMI5 player).'; +$string['privacy:metadata:cmi5launch_sessions:updatedat'] = 'Time a session was updated (string that is returned by CMI5 player).'; +$string['privacy:metadata:cmi5launch_sessions:code'] = 'Unique code for each session assigned by CMI5 plyer.'; +$string['privacy:metadata:cmi5launch_sessions:launchtokenid'] = 'Launchtoken assigned by CMI5 player.'; +$string['privacy:metadata:cmi5launch_sessions:lastrequesttime'] = 'Time of last request (string that is returned by CMI5 player).'; +$string['privacy:metadata:cmi5launch_sessions:score'] = 'The score of session (returned from "result" parameter in statements from LRS).'; +$string['privacy:metadata:cmi5launch_sessions:islaunched'] = 'Whether the session has been launched.'; +$string['privacy:metadata:cmi5launch_sessions:isinitialized'] = 'Whether the session has been initialized.'; +$string['privacy:metadata:cmi5launch_sessions:initializedat'] = 'The status of session (returned from "success" parameter in statements from LRS).'; +$string['privacy:metadata:cmi5launch_sessions:duration'] = 'Time a session lasted (from "result" parameter).'; +$string['privacy:metadata:cmi5launch_sessions:iscompleted'] = 'Whether the session has been completed.'; +$string['privacy:metadata:cmi5launch_sessions:ispassed'] = 'Whether the session has been passed.'; +$string['privacy:metadata:cmi5launch_sessions:isfailed'] = 'Whether the session has been failed.'; +$string['privacy:metadata:cmi5launch_sessions:isterminated'] = 'Whether the session has been terminated.'; +$string['privacy:metadata:cmi5launch_sessions:isabandoned'] = 'Whether the session has been abandoned.'; +$string['privacy:metadata:cmi5launch_sessions:progress'] = 'The full string of session progress reported from LRS".'; +$string['privacy:metadata:cmi5launch_sessions:launchurl'] = 'Returned launch url from cmi5 player.'; + +// AUs table. +$string['privacy:metadata:cmi5launch_aus'] = 'The cmi5 launch link plugin stores each AU of a users particular instance of a cmi5 Activity. While some things, like the masteryscore are generic to all users of the course, others, like the grade are specific to user.'; +$string['privacy:metadata:cmi5launch_aus:id'] = 'The ID of the user\'s AU\'s particular instance assigned by Moodle.'; +$string['privacy:metadata:cmi5launch_aus:userid'] = 'User id, which combined with course ID can be used to retrieve unique records.'; +$string['privacy:metadata:cmi5launch_aus:attempt'] = 'The attempt of the au, ie, first, second, third.'; +$string['privacy:metadata:cmi5launch_aus:lmsid'] = 'The lmsid id from the course packet. The singular CMI5 compliant id.'; +$string['privacy:metadata:cmi5launch_aus:completed'] = 'Whether an AU has met completed criteria (0 if false, 1 if true).'; +$string['privacy:metadata:cmi5launch_aus:passed'] = 'Whether an AU has met passed criteria (0 if false, 1 if true).'; +$string['privacy:metadata:cmi5launch_aus:inprogress'] = 'Whether an AU is in progress (0 if false, 1 if true).'; +$string['privacy:metadata:cmi5launch_aus:noattempt'] = 'Whether an AU has been attempted (0 if false, 1 if true).'; +$string['privacy:metadata:cmi5launch_aus:satisfied'] = 'Whether an AU has been satisfied (0 if false, 1 if true).'; +$string['privacy:metadata:cmi5launch_aus:sessions'] = 'The IDs of the AU\'s individual sessions, saved as array for retrieval.'; +$string['privacy:metadata:cmi5launch_aus:scores'] = 'The scores of the AU\'s individual sessions, saved as array for retrieval.'; +$string['privacy:metadata:cmi5launch_aus:grade'] = 'The overall grade of the AU (based on grading type).'; + +// For external systems. + +// LRS info. +$string['privacy:metadata:lrs'] = 'The cmi5 launch link requests xAPI statements from an LRS to dispaly progress reports to students.'; +$string['privacy:metadata:lrs:registrationid'] = 'There are several ways to request xAPI statements from an LRS, the only way compatible with Moodle information is the registration ID. This ID will request all statements from that instance of cmi5 launch link activity.'; +$string['privacy:metadata:lrs:createdat'] = 'By sending the \'created at\' time we can filter out irrelevant statements and only get those created after or on that time.'; + +// Cmi5 player info. +$string['privacy:metadata:cmi5_player'] = 'The cmi5 launch link communicates with the cmi5 player to upload cmi5 activities, request launch URLs for them, and track staus of the activity and it\'s AU\s status such as completed or not satisified. It can also assign registration ID\'s and return info on registrations and sessions.'; +$string['privacy:metadata:cmi5_player:registrationid'] = 'The cmi5 player assigns each user and their activity instance a registration id, and it can be used to query updated information from the cmi5 player.'; +$string['privacy:metadata:cmi5_player:actor'] = 'When retrieving a launch URL or new registration the cmi5 player requests the "actor" name, which is the username in Moodle.'; +$string['privacy:metadata:cmi5_player:courseid'] = 'The cmi5 player assigns each user and their activity instance a course ID, and it can is used to request a launch URL.'; +$string['privacy:metadata:cmi5_player:returnurl'] = 'The return URL is a parameter sent to the cmi5 player when requesting a launch URL. It is where the browser returns to upon closing the launched activity. It has a unique ID on the end directing back to the user\'s particular course instance.'; +$string['privacy:metadata:cmi5_player:sessionid'] = 'The cmi5 player assigns each user\'s session a unique ID, and this is sent to the cmi5 player when requesting updated session info.'; \ No newline at end of file diff --git a/lang/es/cmi5launch.php b/lang/es/cmi5launch.php index b97b253..9193fb5 100755 --- a/lang/es/cmi5launch.php +++ b/lang/es/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Spanish strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = 'Lanzado por primera vez'; -$string['cmi5launchviewlastlaunched'] = 'Último puesto en marcha'; -$string['cmi5launchviewlaunchlinkheader'] = 'Vínculo de Inicio'; -$string['cmi5launchviewlaunchlink'] = 'Lanzamiento'; - -$string['cmi5launch_completed'] = 'La experiencia completa!'; -$string['cmi5launch_progress'] = 'Intento en curso'; -$string['cmi5launch_attempt'] = 'Nuevo intento'; -$string['cmi5launch_notavailable'] = 'The Learning Record Store no está disponible. Por favor, póngase en contacto con el administrador del sistema.'; - +. + + +/** + * Spanish strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = 'Lanzado por primera vez'; +$string['cmi5launchviewlastlaunched'] = 'Último puesto en marcha'; +$string['cmi5launchviewlaunchlinkheader'] = 'Vínculo de Inicio'; +$string['cmi5launchviewlaunchlink'] = 'Lanzamiento'; + +$string['cmi5launch_completed'] = 'La experiencia completa!'; +$string['cmi5launch_progress'] = 'Intento en curso'; +$string['cmi5launch_attempt'] = 'Nuevo intento'; +$string['cmi5launch_notavailable'] = 'The Learning Record Store no está disponible. Por favor, póngase en contacto con el administrador del sistema.'; + diff --git a/lang/fr/cmi5launch.php b/lang/fr/cmi5launch.php index 74277cf..0b94549 100755 --- a/lang/fr/cmi5launch.php +++ b/lang/fr/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * French strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = "D'abord lancé"; -$string['cmi5launchviewlastlaunched'] = 'Dernier lancé'; -$string['cmi5launchviewlaunchlinkheader'] = 'lien de lancement'; -$string['cmi5launchviewlaunchlink'] = 'Lancement'; - -$string['cmi5launch_completed'] = 'Expérience complète!'; -$string['cmi5launch_progress'] = 'Tentative en cours'; -$string['cmi5launch_attempt'] = 'Nouvelle tentative'; -$string['cmi5launch_notavailable'] = "L'apprentissage Record Store n'est pas disponible. S'il vous plaît contactez l'administrateur du système."; - +. + + +/** + * French strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = "D'abord lancé"; +$string['cmi5launchviewlastlaunched'] = 'Dernier lancé'; +$string['cmi5launchviewlaunchlinkheader'] = 'lien de lancement'; +$string['cmi5launchviewlaunchlink'] = 'Lancement'; + +$string['cmi5launch_completed'] = 'Expérience complète!'; +$string['cmi5launch_progress'] = 'Tentative en cours'; +$string['cmi5launch_attempt'] = 'Nouvelle tentative'; +$string['cmi5launch_notavailable'] = "L'apprentissage Record Store n'est pas disponible. S'il vous plaît contactez l'administrateur du système."; + diff --git a/lang/it/cmi5launch.php b/lang/it/cmi5launch.php index 2703773..1e0e437 100755 --- a/lang/it/cmi5launch.php +++ b/lang/it/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Italian strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = 'Lanciato'; -$string['cmi5launchviewlastlaunched'] = 'Ultimo lanciato'; -$string['cmi5launchviewlaunchlinkheader'] = 'Collegamento di lancio'; -$string['cmi5launchviewlaunchlink'] = 'Lancio'; - -$string['cmi5launch_completed'] = 'Completa esperienza!'; -$string['cmi5launch_progress'] = 'Tentativo in corso'; -$string['cmi5launch_attempt'] = 'Nuovo tentativo'; -$string['cmi5launch_notavailable'] = 'The Learning Record Store non è disponibile. Si prega di contattare un amministratore di sistema.'; - +. + + +/** + * Italian strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = 'Lanciato'; +$string['cmi5launchviewlastlaunched'] = 'Ultimo lanciato'; +$string['cmi5launchviewlaunchlinkheader'] = 'Collegamento di lancio'; +$string['cmi5launchviewlaunchlink'] = 'Lancio'; + +$string['cmi5launch_completed'] = 'Completa esperienza!'; +$string['cmi5launch_progress'] = 'Tentativo in corso'; +$string['cmi5launch_attempt'] = 'Nuovo tentativo'; +$string['cmi5launch_notavailable'] = 'The Learning Record Store non è disponibile. Si prega di contattare un amministratore di sistema.'; + diff --git a/lang/ja/cmi5launch.php b/lang/ja/cmi5launch.php index 89fd950..fd66df6 100755 --- a/lang/ja/cmi5launch.php +++ b/lang/ja/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Japanese strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = '最初に起動'; -$string['cmi5launchviewlastlaunched'] = '最後 打ち上げ'; -$string['cmi5launchviewlaunchlinkheader'] = '起動リンク'; -$string['cmi5launchviewlaunchlink'] = '打ち上げ'; - -$string['cmi5launch_completed'] = '完全な体験!'; -$string['cmi5launch_progress'] = '進行中の試み'; -$string['cmi5launch_attempt'] = '新しい試み'; -$string['cmi5launch_notavailable'] = '学習の記録Storeは利用できません。システム管理者に連絡してください。'; - +. + + +/** + * Japanese strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = '最初に起動'; +$string['cmi5launchviewlastlaunched'] = '最後 打ち上げ'; +$string['cmi5launchviewlaunchlinkheader'] = '起動リンク'; +$string['cmi5launchviewlaunchlink'] = '打ち上げ'; + +$string['cmi5launch_completed'] = '完全な体験!'; +$string['cmi5launch_progress'] = '進行中の試み'; +$string['cmi5launch_attempt'] = '新しい試み'; +$string['cmi5launch_notavailable'] = '学習の記録Storeは利用できません。システム管理者に連絡してください。'; + diff --git a/lang/pt/cmi5launch.php b/lang/pt/cmi5launch.php index 5b89d73..934b021 100755 --- a/lang/pt/cmi5launch.php +++ b/lang/pt/cmi5launch.php @@ -1,132 +1,132 @@ -. - - -/** - * Strings em Português para plugin cmi5launch - * - * Traduzido por: Kélvin Santiago - kelvinsleonardo@gmail.com - * - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['modulename'] = 'cmi5 Launch Link'; -$string['modulenameplural'] = 'cmi5 Launch Links'; -$string['modulename_help'] = 'Um plugin para Moodle que permite lançar o cmi5 (xAPI) e o conteúdo então é rastreado pelo LRS.'; - -// Iniciar configuração padrão LRS. -$string['cmi5launchlrsfieldset'] = 'Configurações padrões para atividades Launch Link.'; -$string['cmi5launchlrsfieldset_help'] = 'Em todo site quando se cria uma nova atividade, valores padrões são utilizados. Cada atividade pode prover valores alternativos.'; - -$string['cmi5launchlrsendpoint'] = 'Endpoint'; -$string['cmi5launchlrsendpoint_help'] = 'O LRS endpoint (e.g. http://lrs.example.com/endpoint/). deve incluir a barra no final.'; -$string['cmi5launchlrsendpoint_default'] = ''; - -$string['cmi5launchlrslogin'] = 'Login Básico'; -$string['cmi5launchlrslogin_help'] = 'Sua chave de login LRS.'; -$string['cmi5launchlrslogin_default'] = ''; - -$string['cmi5launchlrspass'] = 'Senha Básica'; -$string['cmi5launchlrspass_help'] = 'Sua senha LRS (secret).'; -$string['cmi5launchlrspass_default'] = ''; - -$string['cmi5launchlrsduration'] = 'Duração'; -$string['cmi5launchlrsduration_help'] = 'Usado com integração basica de autenticação LRS. Solicita o LRS para manter as credenciais válidas nessa quantidade de minutos.'; -$string['cmi5launchlrsduration_default'] = '9000'; - -$string['cmi5launchlrsauthentication'] = 'Integração LRS'; -$string['cmi5launchlrsauthentication_help'] = 'Use os recursos de integração adicionais para criar novas credenciais de autenticação para cada lançamento que suporta os LRSS.'; -$string['cmi5launchlrsauthentication_watershedhelp'] = 'Para integrar com o Watershed, insira as credenciais de login e senha do Watershed nos campos abaixo. Esteja ciente que esses dados serão armazenados no banco de dados do moodle. Use uma conta criada que não utiliza uma senha de qualquer outra conta. Para outras configurações de integração, insira a autenticação básica Login/Password abaixo.'; -$string['cmi5launchlrsauthentication_watershedhelp_label'] = 'Integração com Watershed'; -$string['cmi5launchlrsauthentication_option_0'] = 'Nenhum'; -$string['cmi5launchlrsauthentication_option_1'] = 'Watershed'; -$string['cmi5launchlrsauthentication_option_2'] = 'Learning Locker 1'; - -$string['cmi5launchuseactoremail'] = 'Identificar por e-mail'; -$string['cmi5launchuseactoremail_help'] = 'Se marcar como selecionado, os alunos serão identificados pelo endereço de e-mail, se tiver registrado no moodle.'; - -$string['cmi5launchcustomacchp'] = 'Customizar conta homepage'; -$string['cmi5launchcustomacchp_help'] = 'Se for inserido o moodle irá utilizar essa home page com um número ID para identificar o aluno. Se o ID não for inserido pelo aluno, será identificado pelo e-mail ou pelo número do ID do moodle. Nota: Se o ID do aluno mudar eles irão perder o acesso aos registros associados aquele ID. Os relatórios no LRS poderão ser afetados também.'; -$string['cmi5launchcustomacchp_default'] = ''; - -// Configurações de Atividade. -$string['cmi5launchname'] = 'Lançar nome do link'; -$string['cmi5launchname_help'] = 'O nome do link como irá aparecer para o usuário.'; - -$string['cmi5launchurl'] = 'Lançar URL'; -$string['cmi5launchurl_help'] = 'A URL base da atividade do cmi5 que você deseja lançar/iniciar (e.g. http://example.com/content/index.html).'; - -$string['cmi5activityid'] = 'ID Atividade'; -$string['cmi5activityid_help'] = 'O IRI de de identificação para a atividade primária que está sendo lançada.'; - -$string['cmi5package'] = 'Pacote zip'; -$string['cmi5package_help'] = 'Se você tem um curso cmi5 compactado, você pode fazer o upload aqui. Se você carregar um pacote, a URL de lançamento e o campo ID de atividade será preenchido automaticamente, quando você salvar usando os dados do arquivo cmi5.xml contida no arquivo zip. Você pode editar essas configurações a qualquer momento, mas não deve alterar o ID da atividade, a não ser que você saiba o que está fazendo.'; - -$string['cmi5packagetitle'] = 'Configurações de lançamento'; -$string['cmi5packagetext'] = 'Você pode preencher as configurações de lançamento e ID da atividade diretamente, ou enviando um pacote zip onde deve conter o arquivo cmi5.xml. A URL de lançamento definida no arquivo cmi5.xml pode apontar para outros arquivos no pacote .zip ou para uma URL externa. O ID da atividade deve sempre ter um URL completo ou outro IRI.'; - -$string['lrsheading'] = 'Configurações de LRS'; -$string['lrsdefaults'] = 'Configuradões padrões de LRS'; -$string['lrssettingdescription'] = 'Por padrão, esta atividade usa a configuração global de LRS encontrada em Site administration > Plugins > Activity modules > cmi5 Launch Link. Para alterar a configuração para esta especifica atividade, selecione Desbloqueio Padrão.'; -$string['overridedefaults'] = 'Desbloqueio padrão'; -$string['overridedefaults_help'] = 'Permite a atividade ter diferentes configuradões de LRS.'; - -$string['behaviorheading'] = 'Comportamento do módulo'; - -$string['cmi5multipleregs'] = 'Permitir multiplos registros'; -$string['cmi5multipleregs_help'] = 'Se for selecionada, permite o aluno iniciar mais de um registro para atividade. Os alunos podem sempre voltar para os registros que já tenham sido iniciados, mesmo se esta configuração estiver desmarcada.'; -$string['apCreationFailed'] = 'Falha ao criar Watershed Activity Provider.'; - -// Zip erros. -$string['badmanifest'] = 'Alguns erros nos manifestos: veja os erros no log'; -$string['badimsmanifestlocation'] = 'Um arquivo cmi5.xml foi encontrado porém não está na raiz do seu arquivo zip, por favor recompactar o seu curso.'; -$string['badarchive'] = 'Você deve fornecer um arquivo zip válido'; -$string['nomanifest'] = 'Pacote de arquivo incorreto - está faltando cmi5.xml'; - -$string['cmi5launch'] = 'cmi5 Launch Link'; -$string['pluginadministration'] = 'cmi5 Launch Link administração'; -$string['pluginname'] = 'cmi5 Launch Link'; - -// Configuração de conclusão de verbos. -$string['completionverb'] = 'Verbo'; -$string['completionverbgroup'] = 'Rastreamento completo pelo verbo'; -$string['completionverbgroup_help'] = 'O moddle irá olhas para as demonstrações onde o ator é o usuário atual, o objeto ID da atividade especificado e o verbo será aquele definido aqui. Se ele encontrar uma de uma correspondência ao statement, a atividade será marcada como concluída.'; - -// Configuração de Visualização. -$string['cmi5launchviewfirstlaunched'] = 'Lançado pela primeira vez'; -$string['cmi5launchviewlastlaunched'] = 'Último lançado'; -$string['cmi5launchviewlaunchlinkheader'] = 'Link do Início'; -$string['cmi5launchviewlaunchlink'] = 'Lançamento'; - -$string['cmi5launch_completed'] = 'Experiência completa!'; -$string['cmi5launch_progress'] = 'Tentativa em andamento.'; -$string['cmi5launch_attempt'] = 'Nova tentativa'; -$string['cmi5launch_notavailable'] = 'The Learning Record Store não está disponível. Entre em contato com o administrador do sistema.'; - -$string['idmissing'] = 'Você deve especificar um ID do modulo do curso ou uma instância ID'; - -// Eventos. -$string['eventactivitylaunched'] = 'Atividade Lançada'; -$string['eventactivitycompleted'] = 'Atividade Completa'; - -$string['cmi5launch:addinstance'] = 'Adicionar uma nova atividade cmi5 (xAPI) para um curso'; - -$string['expirecredentials'] = 'Credenciais expiradas'; +. + + +/** + * Strings em Português para plugin cmi5launch + * + * Traduzido por: Kélvin Santiago - kelvinsleonardo@gmail.com + * + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['modulename'] = 'cmi5 Launch Link'; +$string['modulenameplural'] = 'cmi5 Launch Links'; +$string['modulename_help'] = 'Um plugin para Moodle que permite lançar o cmi5 (xAPI) e o conteúdo então é rastreado pelo LRS.'; + +// Iniciar configuração padrão LRS. +$string['cmi5launchlrsfieldset'] = 'Configurações padrões para atividades Launch Link.'; +$string['cmi5launchlrsfieldset_help'] = 'Em todo site quando se cria uma nova atividade, valores padrões são utilizados. Cada atividade pode prover valores alternativos.'; + +$string['cmi5launchlrsendpoint'] = 'Endpoint'; +$string['cmi5launchlrsendpoint_help'] = 'O LRS endpoint (e.g. http://lrs.example.com/endpoint/). deve incluir a barra no final.'; +$string['cmi5launchlrsendpoint_default'] = ''; + +$string['cmi5launchlrslogin'] = 'Login Básico'; +$string['cmi5launchlrslogin_help'] = 'Sua chave de login LRS.'; +$string['cmi5launchlrslogin_default'] = ''; + +$string['cmi5launchlrspass'] = 'Senha Básica'; +$string['cmi5launchlrspass_help'] = 'Sua senha LRS (secret).'; +$string['cmi5launchlrspass_default'] = ''; + +$string['cmi5launchlrsduration'] = 'Duração'; +$string['cmi5launchlrsduration_help'] = 'Usado com integração basica de autenticação LRS. Solicita o LRS para manter as credenciais válidas nessa quantidade de minutos.'; +$string['cmi5launchlrsduration_default'] = '9000'; + +$string['cmi5launchlrsauthentication'] = 'Integração LRS'; +$string['cmi5launchlrsauthentication_help'] = 'Use os recursos de integração adicionais para criar novas credenciais de autenticação para cada lançamento que suporta os LRSS.'; +$string['cmi5launchlrsauthentication_watershedhelp'] = 'Para integrar com o Watershed, insira as credenciais de login e senha do Watershed nos campos abaixo. Esteja ciente que esses dados serão armazenados no banco de dados do moodle. Use uma conta criada que não utiliza uma senha de qualquer outra conta. Para outras configurações de integração, insira a autenticação básica Login/Password abaixo.'; +$string['cmi5launchlrsauthentication_watershedhelp_label'] = 'Integração com Watershed'; +$string['cmi5launchlrsauthentication_option_0'] = 'Nenhum'; +$string['cmi5launchlrsauthentication_option_1'] = 'Watershed'; +$string['cmi5launchlrsauthentication_option_2'] = 'Learning Locker 1'; + +$string['cmi5launchuseactoremail'] = 'Identificar por e-mail'; +$string['cmi5launchuseactoremail_help'] = 'Se marcar como selecionado, os alunos serão identificados pelo endereço de e-mail, se tiver registrado no moodle.'; + +$string['cmi5launchcustomacchp'] = 'Customizar conta homepage'; +$string['cmi5launchcustomacchp_help'] = 'Se for inserido o moodle irá utilizar essa home page com um número ID para identificar o aluno. Se o ID não for inserido pelo aluno, será identificado pelo e-mail ou pelo número do ID do moodle. Nota: Se o ID do aluno mudar eles irão perder o acesso aos registros associados aquele ID. Os relatórios no LRS poderão ser afetados também.'; +$string['cmi5launchcustomacchp_default'] = ''; + +// Configurações de Atividade. +$string['cmi5launchname'] = 'Lançar nome do link'; +$string['cmi5launchname_help'] = 'O nome do link como irá aparecer para o usuário.'; + +$string['cmi5launchurl'] = 'Lançar URL'; +$string['cmi5launchurl_help'] = 'A URL base da atividade do cmi5 que você deseja lançar/iniciar (e.g. http://example.com/content/index.html).'; + +$string['cmi5activityid'] = 'ID Atividade'; +$string['cmi5activityid_help'] = 'O IRI de de identificação para a atividade primária que está sendo lançada.'; + +$string['cmi5package'] = 'Pacote zip'; +$string['cmi5package_help'] = 'Se você tem um curso cmi5 compactado, você pode fazer o upload aqui. Se você carregar um pacote, a URL de lançamento e o campo ID de atividade será preenchido automaticamente, quando você salvar usando os dados do arquivo cmi5.xml contida no arquivo zip. Você pode editar essas configurações a qualquer momento, mas não deve alterar o ID da atividade, a não ser que você saiba o que está fazendo.'; + +$string['cmi5packagetitle'] = 'Configurações de lançamento'; +$string['cmi5packagetext'] = 'Você pode preencher as configurações de lançamento e ID da atividade diretamente, ou enviando um pacote zip onde deve conter o arquivo cmi5.xml. A URL de lançamento definida no arquivo cmi5.xml pode apontar para outros arquivos no pacote .zip ou para uma URL externa. O ID da atividade deve sempre ter um URL completo ou outro IRI.'; + +$string['lrsheading'] = 'Configurações de LRS'; +$string['lrsdefaults'] = 'Configuradões padrões de LRS'; +$string['lrssettingdescription'] = 'Por padrão, esta atividade usa a configuração global de LRS encontrada em Site administration > Plugins > Activity modules > cmi5 Launch Link. Para alterar a configuração para esta especifica atividade, selecione Desbloqueio Padrão.'; +$string['overridedefaults'] = 'Desbloqueio padrão'; +$string['overridedefaults_help'] = 'Permite a atividade ter diferentes configuradões de LRS.'; + +$string['behaviorheading'] = 'Comportamento do módulo'; + +$string['cmi5multipleregs'] = 'Permitir multiplos registros'; +$string['cmi5multipleregs_help'] = 'Se for selecionada, permite o aluno iniciar mais de um registro para atividade. Os alunos podem sempre voltar para os registros que já tenham sido iniciados, mesmo se esta configuração estiver desmarcada.'; +$string['apCreationFailed'] = 'Falha ao criar Watershed Activity Provider.'; + +// Zip erros. +$string['badmanifest'] = 'Alguns erros nos manifestos: veja os erros no log'; +$string['badimsmanifestlocation'] = 'Um arquivo cmi5.xml foi encontrado porém não está na raiz do seu arquivo zip, por favor recompactar o seu curso.'; +$string['badarchive'] = 'Você deve fornecer um arquivo zip válido'; +$string['nomanifest'] = 'Pacote de arquivo incorreto - está faltando cmi5.xml'; + +$string['cmi5launch'] = 'cmi5 Launch Link'; +$string['pluginadministration'] = 'cmi5 Launch Link administração'; +$string['pluginname'] = 'cmi5 Launch Link'; + +// Configuração de conclusão de verbos. +$string['completionverb'] = 'Verbo'; +$string['completionverbgroup'] = 'Rastreamento completo pelo verbo'; +$string['completionverbgroup_help'] = 'O moddle irá olhas para as demonstrações onde o ator é o usuário atual, o objeto ID da atividade especificado e o verbo será aquele definido aqui. Se ele encontrar uma de uma correspondência ao statement, a atividade será marcada como concluída.'; + +// Configuração de Visualização. +$string['cmi5launchviewfirstlaunched'] = 'Lançado pela primeira vez'; +$string['cmi5launchviewlastlaunched'] = 'Último lançado'; +$string['cmi5launchviewlaunchlinkheader'] = 'Link do Início'; +$string['cmi5launchviewlaunchlink'] = 'Lançamento'; + +$string['cmi5launch_completed'] = 'Experiência completa!'; +$string['cmi5launch_progress'] = 'Tentativa em andamento.'; +$string['cmi5launch_attempt'] = 'Nova tentativa'; +$string['cmi5launch_notavailable'] = 'The Learning Record Store não está disponível. Entre em contato com o administrador do sistema.'; + +$string['idmissing'] = 'Você deve especificar um ID do modulo do curso ou uma instância ID'; + +// Eventos. +$string['eventactivitylaunched'] = 'Atividade Lançada'; +$string['eventactivitycompleted'] = 'Atividade Completa'; + +$string['cmi5launch:addinstance'] = 'Adicionar uma nova atividade cmi5 (xAPI) para um curso'; + +$string['expirecredentials'] = 'Credenciais expiradas'; diff --git a/lang/ru/cmi5launch.php b/lang/ru/cmi5launch.php index 96ec6ee..65d65dc 100755 --- a/lang/ru/cmi5launch.php +++ b/lang/ru/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Russian strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = 'Проект был начат'; -$string['cmi5launchviewlastlaunched'] = 'Последний запущенный'; -$string['cmi5launchviewlaunchlinkheader'] = 'Запуск ссылка'; -$string['cmi5launchviewlaunchlink'] = 'Запуск'; - -$string['cmi5launch_completed'] = 'Опыт завершена!'; -$string['cmi5launch_progress'] = 'Попытка продолжается.'; -$string['cmi5launch_attempt'] = 'Новый попытка'; -$string['cmi5launch_notavailable'] = 'Обучение Запись магазин не доступен. Пожалуйста, свяжитесь с системным администратором.'; - +. + + +/** + * Russian strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = 'Проект был начат'; +$string['cmi5launchviewlastlaunched'] = 'Последний запущенный'; +$string['cmi5launchviewlaunchlinkheader'] = 'Запуск ссылка'; +$string['cmi5launchviewlaunchlink'] = 'Запуск'; + +$string['cmi5launch_completed'] = 'Опыт завершена!'; +$string['cmi5launch_progress'] = 'Попытка продолжается.'; +$string['cmi5launch_attempt'] = 'Новый попытка'; +$string['cmi5launch_notavailable'] = 'Обучение Запись магазин не доступен. Пожалуйста, свяжитесь с системным администратором.'; + diff --git a/lang/tr/cmi5launch.php b/lang/tr/cmi5launch.php index fb90ab2..be310a4 100755 --- a/lang/tr/cmi5launch.php +++ b/lang/tr/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Turkish strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = 'İlk başlattı'; -$string['cmi5launchviewlastlaunched'] = 'Son başlattı'; -$string['cmi5launchviewlaunchlinkheader'] = 'Lansmanı bağlantı'; -$string['cmi5launchviewlaunchlink'] = 'Başlatmak'; - -$string['cmi5launch_completed'] = 'Deneyim tamamlandı!'; -$string['cmi5launch_progress'] = 'Devam Denemesi'; -$string['cmi5launch_attempt'] = 'Yeni girişim'; -$string['cmi5launch_notavailable'] = 'Öğrenme Record Store mevcut değildir. Bir sistem yöneticisine başvurun.'; - +. + + +/** + * Turkish strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = 'İlk başlattı'; +$string['cmi5launchviewlastlaunched'] = 'Son başlattı'; +$string['cmi5launchviewlaunchlinkheader'] = 'Lansmanı bağlantı'; +$string['cmi5launchviewlaunchlink'] = 'Başlatmak'; + +$string['cmi5launch_completed'] = 'Deneyim tamamlandı!'; +$string['cmi5launch_progress'] = 'Devam Denemesi'; +$string['cmi5launch_attempt'] = 'Yeni girişim'; +$string['cmi5launch_notavailable'] = 'Öğrenme Record Store mevcut değildir. Bir sistem yöneticisine başvurun.'; + diff --git a/lang/zh_cn/cmi5launch.php b/lang/zh_cn/cmi5launch.php index 060b33d..2ac4389 100755 --- a/lang/zh_cn/cmi5launch.php +++ b/lang/zh_cn/cmi5launch.php @@ -1,40 +1,40 @@ -. - - -/** - * Chinese strings for cmi5launch - * - * You can have a rather longer description of the file as well, - * if you like, and it can span multiple lines. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['cmi5launchviewfirstlaunched'] = '首次推出'; -$string['cmi5launchviewlastlaunched'] = '最後 推出'; -$string['cmi5launchviewlaunchlinkheader'] = '起動リンク'; -$string['cmi5launchviewlaunchlink'] = '發射'; - -$string['cmi5launch_completed'] = '體驗完整的!'; -$string['cmi5launch_progress'] = '嘗試進行中'; -$string['cmi5launch_attempt'] = '新的嘗試'; -$string['cmi5launch_notavailable'] = '學習記錄存儲不可用。請與系統管理員聯繫。'; - +. + + +/** + * Chinese strings for cmi5launch + * + * You can have a rather longer description of the file as well, + * if you like, and it can span multiple lines. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$string['cmi5launchviewfirstlaunched'] = '首次推出'; +$string['cmi5launchviewlastlaunched'] = '最後 推出'; +$string['cmi5launchviewlaunchlinkheader'] = '起動リンク'; +$string['cmi5launchviewlaunchlink'] = '發射'; + +$string['cmi5launch_completed'] = '體驗完整的!'; +$string['cmi5launch_progress'] = '嘗試進行中'; +$string['cmi5launch_attempt'] = '新的嘗試'; +$string['cmi5launch_notavailable'] = '學習記錄存儲不可用。請與系統管理員聯繫。'; + diff --git a/launch.php b/launch.php index 04604e7..e00846b 100755 --- a/launch.php +++ b/launch.php @@ -1,134 +1,137 @@ -. - -/** - * launches the experience with the requested registration - * - * @copyright 2023 Megan Bohland - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - - -require_once(dirname(dirname(dirname(__FILE__))).'/config.php'); -require_once('header.php'); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/sessionHelpers.php"); - -global $CFG, $cmi5launch, $USER, $DB; - -// Trigger Activity launched event. -$event = \mod_cmi5launch\event\activity_launched::create(array( - 'objectid' => $cmi5launch->id, - 'context' => $context, -)); -$event->add_record_snapshot('course_modules', $cm); -$event->add_record_snapshot('cmi5launch', $cmi5launch); -$event->trigger(); - -//External class and funcs to use -$aus_helpers = new Au_Helpers; -$connectors = new cmi5Connectors; -$ses_helpers = new Session_Helpers; - -$saveSession = $ses_helpers->getSaveSession(); -$retrieveUrl = $connectors->getRetrieveUrl(); -$getAUs = $aus_helpers->getAUsFromDB(); - -//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); - -// Reload cmi5 instance. -$record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); - -//Retrieve user's course record -$usersCourse = $DB->get_record('cmi5launch_course', ['courseid' => $record->courseid, 'userid' => $USER->id]); - - -//Retrieve registration id -$registrationid = $usersCourse->registrationid; - -if (empty($registrationid)) { - echo "
" . get_string('cmi5launch_regidempty', 'cmi5launch') . "
"; - - // Failed to connect to LRS. - if ($CFG->debug == 32767) { - - echo "

Error attempting to get registration id querystring parameter.

"; - - die(); - } -} -//To hold launch url or whether launch url is new! -$location = ""; - -//If true, this is a NEW launch -if ($idAndStatus[0] == "true") { - - //Retrieve AUs - $au = $getAUs($id); - - //Retrieve AU index - $auIndex = $au->auindex; - - //Pass in the au index to retrieve a launchurl and session id - $urlDecoded = $retrieveUrl($cmi5launch->id, $auIndex); - - //Retrieve and store session id in the aus table - $sessionID = intval($urlDecoded['id']); - - //Check if there are previous sessions - if(!$au->sessions == NULL){ - //We don't want to overwrite so retrieve the sessions - $sessionList = json_decode($au->sessions); - //now add the new session number - $sessionList[] = $sessionID; - - }else{ - //It is null so just start fresh - $sessionList = array(); - $sessionList[] = $sessionID; - } - - //Save sessions - $au->sessions =json_encode($sessionList ); - - //The record needs to updated in db - $updated = $DB->update_record('cmi5launch_aus', $au, true); - - //Retrieve the launch url - $location = $urlDecoded['url']; - //And launch method - $launchMethod = $urlDecoded['launchMethod']; - - //Create and save session object to session table - $saveSession($sessionID, $location, $launchMethod); - -} else { - //This is a new session, we want to get the launch url from the sessions - $session = $DB->get_record('cmi5launch_sessions', array('sessionid' => $id)); - - //Launch url isss in old session record - $location = $session->launchurl; -} - -header("Location: ". $location); - -exit; \ No newline at end of file +. + +/** + * Launches the experience with the requested registration + * + * @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\cmi5_connectors; +use mod_cmi5launch\local\au_helpers; +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'); + +require_login($course, false, $cm); + +global $CFG, $cmi5launch, $USER, $DB; + +// MB - currently not utilizing events, but may in future. +/* +// Trigger Activity launched event. +$event = \mod_cmi5launch\event\activity_launched::create(array( + 'objectid' => $cmi5launch->id, + 'context' => $context, +)); +$event->add_record_snapshot('course_modules', $cm); +$event->add_record_snapshot('cmi5launch', $cmi5launch); +$event->trigger(); +*/ + +// External class and funcs to use. +$auhelper = new au_helpers; +$connectors = new cmi5_connectors; +$sessionhelper = new session_helpers; + +$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); + +// 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; + +if (empty($registrationid)) { + echo "
" . get_string('cmi5launch_regidempty', 'cmi5launch') . "
"; + + // Failed to connect to LRS. + if ($CFG->debug == 32767) { + + echo "

Error attempting to get registration id querystring parameter.

"; + + die(); + } +} + +// To hold launch url. +$location = ""; + +// Retrieve AUs. +$au = $retrieveaus($id); + +// Retrieve the au index. +$auindex = $au->auindex; + +// 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']); + +// 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; + +} else { + // If it is null just start fresh. + $sessionlist = array(); + $sessionlist[] = $sessionid; +} + +// Save sessions. +$au->sessions = json_encode($sessionlist); + +// The record needs to updated in DB. +$updated = $DB->update_record('cmi5launch_aus', $au, true); + +// Retrieve the launch url. +$location = $urldecoded['url']; +// And launch method. +$launchmethod = $urldecoded['launchMethod']; + +// 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); + +exit; diff --git a/lib.php b/lib.php index 9242f09..3bd8ad1 100755 --- a/lib.php +++ b/lib.php @@ -1,1161 +1,1114 @@ -. - -/** - * Library of interface functions and constants for module cmi5launch - * - * All the core Moodle functions, neeeded to allow the module to work - * integrated in Moodle should be placed here. - * All the cmi5launch specific functions, needed to implement all the module - * logic, should go to locallib.php. This will help to save some memory when - * Moodle is performing actions across all modules. - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -// cmi5PHP - required for interacting with the LRS in cmi5launch_get_statements. -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/autoload.php"); - -// SCORM library from the SCORM module. Required for its xml2Array class by cmi5launch_process_new_package. -require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); -//Classes for connecting to CMI5 player -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/cmi5Connector.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/ausHelpers.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/aus.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/sessions.php"); - -global $cmi5launchsettings; -$cmi5launchsettings = null; - -// Moodle Core API. - -//TODO- this is where we add functions for grades we want suchs as FEATURE_GRADE_HAS_GRADE -//For now I added those SCORM had, we can whittle this down -/** - * Returns the information on whether the module supports a feature - * - * @see plugin_supports() in lib/moodlelib.php - * @param string $feature FEATURE_xx constant for requested feature - * @return mixed true if the feature is supported, null if unknown - */ -function cmi5launch_supports($feature) { - switch($feature) { - case FEATURE_MOD_INTRO: - return true; - case FEATURE_COMPLETION_TRACKS_VIEWS: - return true; - case FEATURE_COMPLETION_HAS_RULES: - return true; - case FEATURE_BACKUP_MOODLE2: - return true; - ///////////////// - case FEATURE_GROUPS: - return true; - case FEATURE_GROUPINGS: - return true; - case FEATURE_GRADE_HAS_GRADE: - return true; - case FEATURE_GRADE_OUTCOMES: - return true; - case FEATURE_SHOW_DESCRIPTION: - return true; - case FEATURE_MOD_PURPOSE: - return MOD_PURPOSE_CONTENT; - ////////////////////// - default: - return null; - } -} - -/** - * Saves a new instance of the cmi5launch into the database - * - * Given an object containing all the necessary data, - * (defined by the form in mod_form.php) this function - * will create a new instance and return the id number - * of the new instance. - * - * @param object $cmi5launch An object from the form in mod_form.php - * @param mod_cmi5launch_mod_form $mform - * @return int The id of the newly inserted cmi5launch record - */ - -function cmi5launch_add_instance(stdClass $cmi5launch, mod_cmi5launch_mod_form $mform = null) { - global $DB, $CFG; - - $cmi5launch->timecreated = time(); - - // Need the id of the newly created instance to return (and use if override defaults checkbox is checked). - $cmi5launch->id = $DB->insert_record('cmi5launch', $cmi5launch); - - $cmi5launchlrs = cmi5launch_build_lrs_settings($cmi5launch); - - // Determine if override defaults checkbox is checked or we need to save watershed creds. - if ($cmi5launch->overridedefaults == '1' || $cmi5launchlrs->lrsauthentication == '2') { - $cmi5launchlrs->cmi5launchid = $cmi5launch->id; - - // Insert data into cmi5launch_lrs table. - if (!$DB->insert_record('cmi5launch_lrs', $cmi5launchlrs)) { - return false; - } - } - - // Process uploaded file. - if (!empty($cmi5launch->packagefile)) { - cmi5launch_process_new_package($cmi5launch); - } - - return $cmi5launch->id; -} - -/** - * Updates an instance of the cmi5launch in the database - * - * Given an object containing all the necessary data, - * (defined by the form in mod_form.php) this function - * will update an existing instance with new data. - * - * @param object $cmi5launch An object from the form in mod_form.php - * @param mod_cmi5launch_mod_form $mform - * @return boolean Success/Fail - */ -function cmi5launch_update_instance(stdClass $cmi5launch, mod_cmi5launch_mod_form $mform = null) { - global $DB, $CFG; - - $cmi5launch->timemodified = time(); - $cmi5launch->id = $cmi5launch->instance; - - $cmi5launchlrs = cmi5launch_build_lrs_settings($cmi5launch); - - // Determine if override defaults checkbox is checked. - if ($cmi5launch->overridedefaults == '1') { - // Check to see if there is a record of this instance in the table. - $cmi5launchlrsid = $DB->get_field( - 'cmi5launch_lrs', - 'id', - array('cmi5launchid' => $cmi5launch->instance), - IGNORE_MISSING - ); - // If not, will need to insert_record. - if (!$cmi5launchlrsid) { - if (!$DB->insert_record('cmi5launch_lrs', $cmi5launchlrs)) { - return false; - } - } else { // If it does exist, update it. - $cmi5launchlrs->id = $cmi5launchlrsid; - - if (!$DB->update_record('cmi5launch_lrs', $cmi5launchlrs)) { - return false; - } - } - } - - if (!$DB->update_record('cmi5launch', $cmi5launch)) { - return false; - } - - // Process uploaded file. - if (!empty($cmi5launch->packagefile)) { - cmi5launch_process_new_package($cmi5launch); - } - - return true; -} - -function cmi5launch_build_lrs_settings(stdClass $cmi5launch) { - global $DB, $CFG; - - // Data for cmi5launch_lrs table. - $cmi5launchlrs = new stdClass(); - $cmi5launchlrs->lrsendpoint = $cmi5launch->cmi5launchlrsendpoint; - $cmi5launchlrs->lrsauthentication = $cmi5launch->cmi5launchlrsauthentication; - $cmi5launchlrs->customacchp = $cmi5launch->cmi5launchcustomacchp; - $cmi5launchlrs->useactoremail = $cmi5launch->cmi5launchuseactoremail; - $cmi5launchlrs->lrsduration = $cmi5launch->cmi5launchlrsduration; - $cmi5launchlrs->cmi5launchid = $cmi5launch->instance; - $cmi5launchlrs->lrslogin = $cmi5launch->cmi5launchlrslogin; - $cmi5launchlrs->lrspass = $cmi5launch->cmi5launchlrspass; - - return $cmi5launchlrs; -} - -/** - * Removes an instance of the cmi5launch from the database - * - * Given an ID of an instance of this module, - * this function will permanently delete the instance - * and any data that depends on it. - * - * @param int $id Id of the module instance - * @return boolean Success/Failure - */ -function cmi5launch_delete_instance($id) { - global $DB; - - if (! $cmi5launch = $DB->get_record('cmi5launch', array('id' => $id))) { - return false; - } - - // Determine if there is a record of this (ever) in the cmi5launch_lrs table. - $cmi5launchlrsid = $DB->get_field('cmi5launch_lrs', 'id', array('cmi5launchid' => $id), $strictness = IGNORE_MISSING); - if ($cmi5launchlrsid) { - // If there is, delete it. - $DB->delete_records('cmi5launch_lrs', array('id' => $cmi5launchlrsid)); - } - - $DB->delete_records('cmi5launch', array('id' => $cmi5launch->id)); - - return true; -} - -/** - * Returns a small object with summary information about what a - * user has done with a given particular instance of this module - * Used for user activity reports. - * $return->time = the time they did it - * $return->info = a short text description - * - * @return stdClass|null - */ -function cmi5launch_user_outline($course, $user, $mod, $cmi5launch) { - $return = new stdClass(); - $return->time = 0; - $return->info = ''; - return $return; -} - -/** - * Prints a detailed representation of what a user has done with - * a given particular instance of this module, for user activity reports. - * - * @param stdClass $course the current course record - * @param stdClass $user the record of the user we are generating report for - * @param cm_info $mod course module info - * @param stdClass $cmi5launch the module instance record - * @return void, is supposed to echp directly - */ -function cmi5launch_user_complete($course, $user, $mod, $cmi5launch) { -} - -/** - * Given a course and a time, this module should find recent activity - * that has occurred in cmi5launch activities and print it out. - * Return true if there was output, or false is there was none. - * - * @return boolean - */ -function cmi5launch_print_recent_activity($course, $viewfullnames, $timestart) { - return false; // True if anything was printed, otherwise false. -} - -/** - * Prepares the recent activity data - * - * This callback function is supposed to populate the passed array with - * custom activity records. These records are then rendered into HTML via - * {@link cmi5launch_print_recent_mod_activity()}. - * - * @param array $activities sequentially indexed array of objects with the 'cmid' property - * @param int $index the index in the $activities to use for the next record - * @param int $timestart append activity since this time - * @param int $courseid the id of the course we produce the report for - * @param int $cmid course module id - * @param int $userid check for a particular user's activity only, defaults to 0 (all users) - * @param int $groupid check for a particular group's activity only, defaults to 0 (all groups) - * @return void adds items into $activities and increases $index - */ -function cmi5launch_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { -} - -/** - * Prints single activity item prepared by {@see cmi5launch_get_recent_mod_activity()} - * @return void - */ -function cmi5launch_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { -} - -/** - * Function to be run periodically according to the moodle cron - * This function searches for things that need to be done, such - * as sending out mail, toggling flags etc ... - * - * @return boolean - * @todo Finish documenting this function - **/ -function cmi5launch_cron() { - return true; -} - -/** - * Returns all other caps used in the module - * - * @example return array('moodle/site:accessallgroups'); - * @return array - */ -function cmi5launch_get_extra_capabilities() { - return array(); -} - -// File API. - -/** - * Returns the lists of all browsable file areas within the given module context - * - * The file area 'intro' for the activity introduction field is added automatically - * by {@link file_browser::get_file_info_context_module()} - * - * @param stdClass $course - * @param stdClass $cm - * @param stdClass $context - * @return array of [(string)filearea] => (string)description - */ -function cmi5launch_get_file_areas($course, $cm, $context) { - $areas = array(); - $areas['content'] = get_string('areacontent', 'scorm'); - $areas['package'] = get_string('areapackage', 'scorm'); - return $areas; -} - -/** - * File browsing support for cmi5launch file areas - * - * @package mod_cmi5launch - * @category files - * - * @param file_browser $browser - * @param array $areas - * @param stdClass $course - * @param stdClass $cm - * @param stdClass $context - * @param string $filearea - * @param int $itemid - * @param string $filepath - * @param string $filename - * @return file_info instance or null if not found - */ -function cmi5launch_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { - global $CFG; - - if (!has_capability('moodle/course:managefiles', $context)) { - return null; - } - - $fs = get_file_storage(); - - if ($filearea === 'package') { - $filepath = is_null($filepath) ? '/' : $filepath; - $filename = is_null($filename) ? '.' : $filename; - - $urlbase = $CFG->wwwroot.'/pluginfile.php'; - if (!$storedfile = $fs->get_file($context->id, 'mod_cmi5launch', 'package', 0, $filepath, $filename)) { - if ($filepath === '/' and $filename === '.') { - $storedfile = new virtual_root_file($context->id, 'mod_cmi5launch', 'package', 0); - } else { - // Not found. - return null; - } - } - return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false); - } - - return false; -} - -/** - * Serves cmi5 content, introduction images and packages. Implements needed access control ;-) - * - * @package mod_cmi5launch - * @category files - * @param stdClass $course course object - * @param stdClass $cm course module object - * @param stdClass $context context object - * @param string $filearea file area - * @param array $args extra arguments - * @param bool $forcedownload whether or not force download - * @param array $options additional options affecting the file serving - * @return bool false if file not found, does not return if found - just send the file - */ -function cmi5launch_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { - global $CFG, $DB; - - if ($context->contextlevel != CONTEXT_MODULE) { - return false; - } - - require_login($course, true, $cm); - $canmanageactivity = has_capability('moodle/course:manageactivities', $context); - - $filename = array_pop($args); - $filepath = implode('/', $args); - if ($filearea === 'content') { - $lifetime = null; - } else if ($filearea === 'package') { - $lifetime = 0; // No caching here. - } else { - return false; - } - - $fs = get_file_storage(); - - if ( - !$file = $fs->get_file($context->id, 'mod_cmi5launch', $filearea, 0, '/'.$filepath.'/', $filename) - or $file->is_directory() - ) { - if ($filearea === 'content') { // Return file not found straight away to improve performance. - send_header_404(); - die; - } - return false; - } - - // Finally send the file. - send_stored_file($file, $lifetime, 0, false, $options); -} - -/** - * Export file resource contents for web service access. - * - * @param cm_info $cm Course module object. - * @param string $baseurl Base URL for Moodle. - * @return array array of file content - */ -function cmi5launch_export_contents($cm, $baseurl) { - global $CFG; - $contents = array(); - $context = context_module::instance($cm->id); - - $fs = get_file_storage(); - $files = $fs->get_area_files($context->id, 'mod_cmi5launch', 'package', 0, 'sortorder DESC, id ASC', false); - - foreach ($files as $fileinfo) { - $file = array(); - $file['type'] = 'file'; - $file['filename'] = $fileinfo->get_filename(); - $file['filepath'] = $fileinfo->get_filepath(); - $file['filesize'] = $fileinfo->get_filesize(); - $file['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_cmi5launch/package'. - $fileinfo->get_filepath().$fileinfo->get_filename(), true); - $file['timecreated'] = $fileinfo->get_timecreated(); - $file['timemodified'] = $fileinfo->get_timemodified(); - $file['sortorder'] = $fileinfo->get_sortorder(); - $file['userid'] = $fileinfo->get_userid(); - $file['author'] = $fileinfo->get_author(); - $file['license'] = $fileinfo->get_license(); - $contents[] = $file; - } - - return $contents; -} - -// Navigation API. - -/** - * Extends the global navigation tree by adding cmi5launch nodes if there is a relevant content - * - * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly. - * - * @param navigation_node $navref An object representing the navigation tree node of the cmi5launch module instance - * @param stdClass $course - * @param stdClass $module - * @param cm_info $cm - */ -function cmi5launch_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) { -} - -/** - * Extends the settings navigation with the cmi5launch settings - * - * This function is called when the context for the page is a cmi5launch module. This is not called by AJAX - * so it is safe to rely on the $PAGE. - * - * @param settings_navigation $settingsnav {@link settings_navigation} - * @param navigation_node $cmi5launchnode {@link navigation_node} - */ -function cmi5launch_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $cmi5launchnode = null) { -} - -// Called by Moodle core. -function cmi5launch_get_completion_state($course, $cm, $userid, $type) { - global $CFG, $DB; - $result = $type; // Default return value. - - // Get cmi5launch. - if (!$cmi5launch = $DB->get_record('cmi5launch', array('id' => $cm->instance))) { - throw new Exception("Can't find activity {$cm->instance}"); // TODO: localise this. - } - - $cmi5launchsettings = cmi5launch_settings($cm->instance); - - $expirydate = null; - $expirydays = $cmi5launch->cmi5expiry; - if ($expirydays > 0) { - $expirydatetime = new DateTime(); - $expirydatetime->sub(new DateInterval('P'.$expirydays.'D')); - $expirydate = $expirydatetime->format('c'); - } - - if (!empty($cmi5launch->cmi5verbid)) { - // Try to get a statement matching actor, verb and object specified in module settings. - $statementquery = cmi5launch_get_statements( - $cmi5launchsettings['cmi5launchlrsendpoint'], - $cmi5launchsettings['cmi5launchlrslogin'], - $cmi5launchsettings['cmi5launchlrspass'], - $cmi5launchsettings['cmi5launchlrsversion'], - $cmi5launch->cmi5activityid, - cmi5launch_getactor($cm->instance), - $cmi5launch->cmi5verbid, - $expirydate - ); - - // If the statement exists, return true else return false. - if (!empty($statementquery->content) && $statementquery->success) { - $result = true; - } else { - $result = false; - } - } - - return $result; -} - -// cmi5Launch specific functions. - -/* -The functions below should really be in locallib, however they are required for one -or more of the functions above so need to be here. -It looks like the standard Quiz module does that same thing, so I don't feel so bad. -*/ - -/** - * Handles uploaded zip packages when a module is added or updated. Unpacks the zip contents - * and extracts the launch url and activity id from the cmi5.xml file. - * Note: This takes the *first* activity from the cmi5.xml file to be the activity intended - * to be launched. It will not go hunting for launch URLs any activities listed below. - * Based closely on code from the SCORM and (to a lesser extent) Resource modules. - * @package mod_cmi5launch - * @category cmi5 - * @param object $cmi5launch An object from the form in mod_form.php - * @return array empty if no issue is found. Array of error message otherwise - */ - -function cmi5launch_process_new_package($cmi5launch) { - global $DB, $CFG; - $cmid = $cmi5launch->coursemodule; - $context = context_module::instance($cmid); - - //bring in functions from classes cmi5Connector/ - $connectors = new cmi5Connectors; - $aus_helpers = new Au_Helpers; - - //bring in functions from class cmi5_table_connectors and AU helpers - $createCourse = $connectors->getCreateCourse(); - $retrieveAus = $aus_helpers-> getRetrieveAus(); - $saveAUs = $aus_helpers->getSaveAUs(); - $createAUs = $aus_helpers->getCreateAUs(); - - // Reload cmi5 instance. - $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); - - $fs = get_file_storage(); - - $fs->delete_area_files($context->id, 'mod_cmi5launch', 'package'); - file_save_draft_area_files( - $cmi5launch->packagefile, - $context->id, - 'mod_cmi5launch', - 'package', - 0, - array('subdirs' => 0, 'maxfiles' => 1) - ); - - // Get filename of zip that was uploaded. - $files = $fs->get_area_files($context->id, 'mod_cmi5launch', 'package', 0, '', false); - if (count($files) < 1) { - return false; - } - - $zipfile = reset($files); - $zipfilename = $zipfile->get_filename(); - - $packagefile = false; - - $packagefile = $fs->get_file($context->id, 'mod_cmi5launch', 'package', 0, '/', $zipfilename); - - //Retrieve user settings to apply to newly created record - $settings = cmi5launch_settings($cmi5launch->id); - $token = $settings['cmi5launchtenanttoken']; - //Create the course and retrieve info for saving to DB - - //Lets wrap this in a try catch statement - $courseResults = $createCourse($context->id, $token, $packagefile ); - - //Take the results of created course and save new course id to table - $record->courseinfo = $courseResults; - - $returnedInfo = json_decode($courseResults, true); - - //Retrieve the lmsId of course - $lmsId = $returnedInfo["lmsId"]; - $record->cmi5activityid = $lmsId; - - $record->courseid = $returnedInfo["id"]; - - //MB - //Here, does the course need registration at all? - //retrieve reg - //$registration = $getRegistration($returnedInfo["id"], $cmi5launch->id); - //$record->registrationid = $registration; - - - //create url for sending to when requesting launch url for course - $playerUrl = $settings['cmi5launchplayerurl']; - - //TODO - another ref to tenant name! Maybe here is a good place to change user? - //MB - no, this needs to be tenant name cause this is course creation - $tenantname = $settings['cmi5launchtenantname']; - - //Build and save launchurl - $url = $playerUrl . "/api/v1/". $record->courseid. "/launch-url/"; - $record->launchurl = $url; - - //Retrieve the courses AUs and save to record - //Here it is saving to MASTER record, we need each student to have their own - - - //Maybe in view.php it does this same thing, saving to the student record instead. - //so these stay as a 'master' record and the students tweak their own - $aus = ($retrieveAus($returnedInfo)); - - //Maybe better to save AUs here and feed it the array returned by retreieveAUS - //$auIDs = $saveAUs($createAUs($aus)); - $record->aus = (json_encode($aus)); - - - //Populate player table with new course info - $DB->update_record('cmi5launch', $record, true); - - $fs->delete_area_files($context->id, 'mod_cmi5launch', 'content'); - - $packer = get_file_packer('application/zip'); - $packagefile->extract_to_storage($packer, $context->id, 'mod_cmi5launch', 'content', 0, '/'); - - // If the cmi5.xml file isn't there, don't do try to use it. - // This is unlikely as it should have been checked when the file was validated. - if ($manifestfile = $fs->get_file($context->id, 'mod_cmi5launch', 'content', 0, '/', 'cmi5.xml')) { - $xmltext = $manifestfile->get_content(); - - $defaultorgid = 0; - $firstinorg = 0; - - $pattern = '/&(?!\w{2,6};)/'; - $replacement = '&'; - $xmltext = preg_replace($pattern, $replacement, $xmltext); - - $objxml = new xml2Array(); - $manifest = $objxml->parse($xmltext); - - // Update activity id from the first activity in cmi5.xml, if it is found. - // Skip without error if not. (The Moodle admin will need to enter the id manually). - if (isset($manifest[0]["children"][0]["children"][0]["attrs"]["ID"])) { - $record->cmi5activityid = $manifest[0]["children"][0]["children"][0]["attrs"]["ID"]; - } - - // Update launch from the first activity in cmi5.xml, if it is found. - // Skip if not. (The Moodle admin will need to enter the url manually). - foreach ($manifest[0]["children"][0]["children"][0]["children"] as $property) { - if ($property["name"] === "LAUNCH") { - $record->cmi5launchurl = $CFG->wwwroot."/pluginfile.php/".$context->id."/mod_cmi5launch/" - .$manifestfile->get_filearea()."/".$property["tagData"]; - } - } - } - // Save reference. - ///turn off to trigger echo - return $DB->update_record('cmi5launch', $record); -} - -/** - * Check that a Zip file contains a cmi5.xml file in the right place. Used in mod_form.php. - * Heavily based on scorm_validate_package in /mod/scorm/lib.php - * @package mod_cmi5launch - * @category cmi5 - * @param stored_file $file a Zip file. - * @return array empty if no issue is found. Array of error message otherwise - */ -function cmi5launch_validate_package($file) { - $packer = get_file_packer('application/zip'); - $errors = array(); - $filelist = $file->list_files($packer); - if (!is_array($filelist)) { - $errors['packagefile'] = get_string('badarchive', 'cmi5launch'); - } else { - $badmanifestpresent = false; - foreach ($filelist as $info) { - if ($info->pathname == 'cmi5.xml') { - return array(); - } else if (strpos($info->pathname, 'cmi5.xml') !== false) { - // This package has cmi5 xml file inside a folder of the package. - $badmanifestpresent = true; - } - if (preg_match('/\.cst$/', $info->pathname)) { - return array(); - } - } - if ($badmanifestpresent) { - $errors['packagefile'] = get_string('badimsmanifestlocation', 'cmi5launch'); - } else { - $errors['packagefile'] = get_string('nomanifest', 'cmi5launch'); - } - } - return $errors; -} - -/** - * Fetches Statements from the LRS. This is used for completion tracking - - * we check for a statement matching certain criteria for each learner. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $url LRS endpoint URL - * @param string $basiclogin login/key for the LRS - * @param string $basicpass pass/secret for the LRS - * @param string $version version of xAPI to use - * @param string $activityid Activity Id to filter by - * @param cmi5 Agent $agent Agent to filter by - * @param string $verb Verb Id to filter by - * @param string $since Since date to filter by - * @return cmi5 LRS Response - */ -function cmi5launch_get_statements($url, $basiclogin, $basicpass, $version, $activityid, $agent, $verb, $since = null) { - - $lrs = new \cmi5\RemoteLRS($url, $version, $basiclogin, $basicpass); - - $statementsquery = array( - "agent" => $agent, - "verb" => new \cmi5\Verb(array("id" => trim($verb))), - "activity" => new \cmi5\Activity(array("id" => trim($activityid))), - "related_activities" => "false", - "format" => "ids" - ); - - if (!is_null($since)) { - $statementsquery["since"] = $since; - } - - // Get all the statements from the LRS. - $statementsresponse = $lrs->queryStatements($statementsquery); - - if ($statementsresponse->success == false) { - return $statementsresponse; - } - - $allthestatements = $statementsresponse->content->getStatements(); - $morestatementsurl = $statementsresponse->content->getMore(); - while (!empty($morestatementsurl)) { - $morestmtsresponse = $lrs->moreStatements($morestatementsurl); - if ($morestmtsresponse->success == false) { - return $morestmtsresponse; - } - $morestatements = $morestmtsresponse->content->getStatements(); - $morestatementsurl = $morestmtsresponse->content->getMore(); - // Note: due to the structure of the arrays, array_merge does not work as expected. - foreach ($morestatements as $morestatement) { - array_push($allthestatements, $morestatement); - } - } - - return new \cmi5\LRSResponse( - $statementsresponse->success, - $allthestatements, - $statementsresponse->httpResponse - ); -} - -/** - * Build a cmi5 Agent based on the current user - * - * @package mod_cmi5launch - * @category cmi5 - * @return cmi5 Agent $agent Agent - */ - -function cmi5launch_getactor($instance) { - global $USER, $CFG; - - $settings = cmi5launch_settings($instance); - - if ($USER->idnumber && $settings['cmi5launchcustomacchp']) { - $agent = array( - "name" => fullname($USER), - "account" => array( - "homePage" => $settings['cmi5launchcustomacchp'], - "name" => $USER->idnumber - ), - "objectType" => "Agent" - ); - } else if ($USER->email && $settings['cmi5launchuseactoremail']) { - $agent = array( - "name" => fullname($USER), - "mbox" => "mailto:".$USER->email, - "objectType" => "Agent" - ); - } else { - $agent = array( - "name" => fullname($USER), - "account" => array( - "homePage" => $CFG->wwwroot, - "name" => $USER->username - ), - "objectType" => "Agent" - ); - } - - return new \cmi5\Agent($agent); -} - - - -/** - * Returns the LRS settings relating to a cmi5 Launch module instance - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $instance The Moodle id for the cmi5 module instance. - * @return array LRS settings to use - */ - - function cmi5launch_settings($instance) { - global $DB, $CFG, $cmi5launchsettings; - - if (!is_null($cmi5launchsettings)) { - return $cmi5launchsettings; - } - - $expresult = array(); - $activitysettings = $DB->get_record( - 'cmi5launch_lrs', - array('cmi5launchid' => $instance), - $fields = '*', - $strictness = IGNORE_MISSING - ); - - - // If global settings are not used, retrieve activity settings. - if (!use_global_cmi5_lrs_settings($instance)) { - $expresult['cmi5launchlrsendpoint'] = $activitysettings->lrsendpoint; - $expresult['cmi5launchlrsauthentication'] = $activitysettings->lrsauthentication; - $expresult['cmi5launchlrslogin'] = $activitysettings->lrslogin; - $expresult['cmi5launchlrspass'] = $activitysettings->lrspass; - $expresult['cmi5launchcustomacchp'] = $activitysettings->customacchp; - $expresult['cmi5launchuseactoremail'] = $activitysettings->useactoremail; - $expresult['cmi5launchlrsduration'] = $activitysettings->lrsduration; - } else { // Use global lrs settings. - $result = $DB->get_records('config_plugins', array('plugin' => 'cmi5launch')); - foreach ($result as $value) { - $expresult[$value->name] = $value->value; - } - } - - $expresult['cmi5launchlrsversion'] = '1.0.0'; - - $cmi5launchsettings = $expresult; - - return $expresult; -} - - -/** - * Should the global LRS settings be used instead of the instance specific ones? - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $instance The Moodle id for the cmi5 module instance. - * @return bool - */ - -function use_global_cmi5_lrs_settings($instance) { - global $DB; - // Determine if there is a row in cmi5launch_lrs matching the current activity id. - $activitysettings = $DB->get_record('cmi5launch', array('id' => $instance)); - if ($activitysettings->overridedefaults == 1) { - return false; - } - return true; -} - -/* - -//Grade functions -//TODO - this is a copy of how scorm handles grades. I got these from SCORM's lib.php -//We will need to tweak -/** - * Return grade for given user or all users. - * - * @global stdClass - * @global object - * @param int $cmi5id id of scorm - * @param int $userid optional user id, 0 means all users - * @return array array of grades, false if none - */ - -function cmi5_get_user_grades($cmi5launch, $userid=0) { - global $CFG, $DB; - //What aer they pulling rom locallib? - //Do we need ot pul it as well? - // require_once($CFG->dirroot.'/mod/scorm/locallib.php'); - - $grades = array(); - - //They are pulling mutliple beacue this may be ALL grades! Like x student had this and y student had that - //Because if the userid is empty they want all as opposed to a particulr student - if (empty($userid)) { - - //We are going to need to make CMI5_tables - //They retrieve the list of users - $au_users = $DB->get_records_select('scorm_scoes_track', "cmi5id=? GROUP BY userid", - array($cmi5launch->id), "", "userid,null"); - - //if there is a list of users then they iterate through it and make user objects with the individual users - if ($au_users) { - foreach ($au_users as $au_user) { - $grades[$au_user->userid] = new stdClass(); - $grades[$au_user->userid]->id = $au_user->userid; - $grades[$au_user->userid]->userid = $au_user->userid; - $grades[$au_user->userid]->rawgrade = scorm_grade_user($cmi5launch, $au_user->userid); - } - } else { - return false; - } - - } else { - $preattempt = $DB->get_records_select('scorm_scoes_track', "cmi5id=? AND userid=? GROUP BY userid", - array($cmi5launch->id, $userid), "", "userid,null"); - if (!$preattempt) { - return false; // No attempt yet. - } - $grades[$userid] = new stdClass(); - $grades[$userid]->id = $userid; - $grades[$userid]->userid = $userid; - $grades[$userid]->rawgrade = scorm_grade_user($cmi5launch, $userid); - } - - return $grades; -} - - -/** - * For example, this callback for the assignment module is assignment_update_grades(). -*This callback should update the grade(s) for the supplied user. -* This may be as simple as retrieving the grades for the user from the activity module's own tables -* then calling {$modname}_grade_item_update(). - -*Its parameters are: - -*stdClass $modinstance the activity module settings. -*int $userid A user ID or 0 for all users. -*bool $nullifnone If a single user is specified, $nullifnone is true and the user has no grade then a grade item with a null rawgrade should be inserted - * Update grades in central gradebook - * - * @category grade - * @param object $cmi5launch - * @param int $userid specific user only, 0 mean all - * @param bool $nullifnone - */ -//This can call grade_item_update which helps interact with Moodle gradebook -//it can pull from DB and then call grade_item_update -//We could also later use this to change how the array is massaged -//in case they want highest or lowest -//TODO MB -function cmi5_update_grades($cmi5launch, $userid=0, $nullifnone=true) { - global $CFG, $DB; - //can our mod see these? - //I can make my own gradelib! to figuree stuff and things - - require_once($CFG->libdir.'/gradelib.php'); - require_once($CFG->libdir.'/completionlib.php'); - - // Reload cmi5 instance (to retrieve the aus). - $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); - - //Make array to hold aus grades - $auGrades = array(); - $courseGrade = 0;//to hold overall grade - - //Lets now retrieve our list of AUs from cmi5launch - $auIDs = json_decode($record->aus); - //Cycle through and get each au ID - foreach ($auIDs as $key => $auID) { - - //get the au - $au = $DB->get_record('cmi5launch_aus', array('id' => $auID)); - // Retrieve the aus scores - - $auGrades[] = $au->grade; - } - //Lets prevent his - if (!$auGrades == null) { - $grade = array_sum($auGrades) / count($auGrades); - //Ok what is grade here? - } - $record->grade = $grade; - - //Call update grade?? - cmi5_grade_item_update($record); - - //This is to get multiple users? - /* - if ($grades = cmi5_get_user_grades($cmi5launch, $userid)) { - cmi5_grade_item_update($scorm, $grades); - // Set complete. - scorm_set_completion($scorm, $userid, COMPLETION_COMPLETE, $grades); - } else if ($userid and $nullifnone) { - $grade = new stdClass(); - $grade->userid = $userid; - $grade->rawgrade = null; - cmi5_grade_item_update($scorm, $grade); - // Set incomplete. - scorm_set_completion($scorm, $userid, COMPLETION_INCOMPLETE); - } else { - cmi5_grade_item_update($scorm); - }*/ -} - - -/** - * Update/create grade item for given cmi5 - * calls grade_update from moodle gradelib.php - * @category grade - * @uses GRADE_TYPE_VALUE - * @uses GRADE_TYPE_NONE - * @param object $cmi5launch object with extra cmidnumber //now cmi5launch - * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook - * @return object grade_item - */ -//TODO MB -//Return to this for grades -/* -//Whereever 'scorm' is replace with 'cmi5launch' -function cmi5_grade_item_update($record, $grades=null) { - global $CFG, $DB, $cmi5launch; - //Note the SCORM version called it's locallib, we may need to get funcs from there as well and place into - //OUR locallib - //May be where those constants were kept - require_once($CFG->dirroot.'/mod/cmi5launch/locallib.php'); - - //gradelib?? We need to see wha this is - //I beleive they are referencing moodle/lib/gradelib - //In case they need their base grade_update? - if (!function_exists('grade_update')) { // Workaround for buggy PHP versions. - require_once($CFG->libdir.'/gradelib.php'); - } - - //so this is getting the scorm id right? Well cmi5 id should work to? - //But a cmi5 id isntusually gotten with cmidnumber I don't - //yeah the cmi5 launch table has a 'courseid' and just a plain 'id' - //I think in this context its 'id' they want? - //If not it has to be 'courseid' - $params = array('itemname' => $cmi5launch->name); - if (isset($cmi5launch->id)) { - $params['idnumber'] = $cmi5launch->id; - } - - //HEre scorm is getting the grading method. - //Scorm originally pulled from scorm_scoes - SCO being a Sharable Object Model - - //Here the grading method is checking if it is a SCO, and then pulls from table - - //BUT it also pulls from PARAM, where are these values set in param? - //From searching 'gradetype' it seems these may be admin settings (admin>settings>grades.php). Where can we set theeesE? - - //Ok, these are probably the plugin seettings we added. and looks like we need to add arow - //to cmi5launch main table to HOLD the gradetype? - //Which is better than constants right? - //Looks loike grade_tyope and others were from scoes tables, which means they should be in our au table?? - - //Ok this is passing as arrays the grades to be updated and the params to do so - //GRADE_TYPE_VALUE appears to be a moodle constant, so should be found when proggram, runs lovally -/* if ($cmi5launch->grademethod == GRADE_AUS_CMI5) { - $maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND '. - $DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id)); - if ($maxgrade) { - $params['gradetype'] = GRADE_TYPE_VALUE; - $params['grademax'] = $maxgrade; - $params['grademin'] = 0; - } else { - $params['gradetype'] = GRADE_TYPE_NONE; - } - } else { - $params['gradetype'] = GRADE_TYPE_VALUE; - $params['grademax'] = $cmi5launch->maxgrade; - $params['grademin'] = 0; - } - - if ($grades === 'reset') { - $params['reset'] = true; - $grades = null; - } - - - $grades = $record->grade; - //This is calling grade_update from lib>gradelib.php - /* - /** - * Submit new or update grade; update/create grade_item definition. Grade must have userid specified, - * rawgrade and feedback with format are optional. rawgrade NULL means 'Not graded'. - * Missing property or key means does not change the existing value. - * - * Only following grade item properties can be changed 'itemname', 'idnumber', 'gradetype', 'grademax', - * 'gra/demin', 'scaleid', 'multfactor', 'plusfactor', 'deleted' and 'hidden'. 'reset' means delete all current grades including locked ones. - * - * Manual, course or category items can not be updated by this function. - * - * @category grade - * @param string $source Source of the grade such as 'mod/assignment' - * @param int $courseid ID of course - * @param string $itemtype Type of grade item. For example, mod or block - * @param string $itemmodule More specific then $itemtype. For example, assignment or forum. May be NULL for some item types - * @param int $iteminstance Instance ID of graded item - * @param int $itemnumber Most probably 0. Modules can use other numbers when having more than one grade for each user - * @param mixed $grades Grade (object, array) or several grades (arrays of arrays or objects), NULL if updating grade_item definition only - * @param mixed $itemdetails Object or array describing the grading item, NULL if no change - * @param bool $isbulkupdate If bulk grade update is happening. - * @return int Returns GRADE_UPDATE_OK, GRADE_UPDATE_FAILED, GRADE_UPDATE_MULTIPLE or GRADE_UPDATE_ITEM_LOCKED - - return grade_update('mod/cmi5launch', $cmi5launch->courseid, 'mod', 'cmi5', $cmi5launch->id, 0, $grades, $params); -} -*/ - -/** - * Delete grade item for given scorm - * - * @category grade - * @param object $scorm object - * @return object grade_item - */ -/* - function scorm_grade_item_delete($scorm) { - global $CFG; - require_once($CFG->libdir.'/gradelib.php'); - - return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, null, array('deleted' => 1)); -} -*/ \ No newline at end of file +. + +/** + * Library of interface functions and constants for module cmi5launch + * + * All the core Moodle functions, neeeded to allow the module to work + * integrated in Moodle should be placed here. + * All the cmi5launch specific functions, needed to implement all the module + * logic, should go to locallib.php. This will help to save some memory when + * Moodle is performing actions across all modules. + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @copyright 2024 Megan Bohland - added functions for cmi5launch + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +// Cmi5PHP - required for interacting with the LRS in cmi5launch_get_statements. +require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/autoload.php"); + +// SCORM library from the SCORM module. Required for its xml2Array class by cmi5launch_process_new_package. +require_once("$CFG->dirroot/mod/scorm/datamodels/scormlib.php"); + +use mod_cmi5launch\local\cmi5_connectors; +use mod_cmi5launch\local\au_helpers; +use mod_cmi5launch\local\grade_helpers; + +global $cmi5launchsettings; +$cmi5launchsettings = null; + +// Moodle Core API. + +/** + * Returns the information on whether the module supports a feature + * + * @see plugin_supports() in lib/moodlelib.php + * @param string $feature FEATURE_xx constant for requested feature + * @return mixed true if the feature is supported, null if unknown + */ +function cmi5launch_supports($feature) { + switch($feature) { + // True if module supports intro editor. + case FEATURE_MOD_INTRO: + return true; + // MB - we do not currently support the next two, but leaving in, in case we start to. + // True if module has code to track whether somebody viewed it + // case FEATURE_COMPLETION_TRACKS_VIEWS: + // return true; + // True if module has custom completion rules + // case FEATURE_COMPLETION_HAS_RULES: + // return true; + // True if module supports backup/restore of moodle2 format. + case FEATURE_BACKUP_MOODLE2: + return true; + // True if module supports groups. + case FEATURE_GROUPS: + return true; + // True if module supports groupings. + case FEATURE_GROUPINGS: + return true; + // True if module can provide a grade. + case FEATURE_GRADE_HAS_GRADE: + return true; + // True if module supports outcomes. + case FEATURE_GRADE_OUTCOMES: + return true; + // True if module can show description on course main page. + case FEATURE_SHOW_DESCRIPTION: + return true; + // Type of module. + case FEATURE_MOD_PURPOSE: + return MOD_PURPOSE_CONTENT; + default: + return null; + } +} + +/** + * Saves a new instance of the cmi5launch into the database + * + * Given an object containing all the necessary data, + * (defined by the form in mod_form.php) this function + * will create a new instance and return the id number + * of the new instance. + * + * @param object $cmi5launch An object from the form in mod_form.php + * @param mod_cmi5launch_mod_form $mform + * @return int The id of the newly inserted cmi5launch record + */ +function cmi5launch_add_instance(stdClass $cmi5launch, mod_cmi5launch_mod_form $mform = null) { + + global $DB, $CFG; + + $cmi5launch->timecreated = time(); + + // Need the id of the newly created instance to return (and use if override defaults checkbox is checked). + $cmi5launch->id = $DB->insert_record('cmi5launch', $cmi5launch); + + // Process uploaded file. + if (!empty($cmi5launch->packagefile)) { + + cmi5launch_process_new_package($cmi5launch); + } + + return $cmi5launch->id; +} + +/** + * Updates an instance of the cmi5launch in the database + * + * Given an object containing all the necessary data, + * (defined by the form in mod_form.php) this function + * will update an existing instance with new data. + * + * @param object $cmi5launch An object from the form in mod_form.php + * @param mod_cmi5launch_mod_form $mform + * @return boolean Success/Fail + */ +function cmi5launch_update_instance(stdClass $cmi5launch, mod_cmi5launch_mod_form $mform = null) { + global $DB, $CFG; + + $cmi5launch->timemodified = time(); + $cmi5launch->id = $cmi5launch->instance; + + // We removed this part of lrs box -MB + // $cmi5launchlrs = cmi5launch_build_lrs_settings($cmi5launch); + /* + // Determine if override defaults checkbox is checked. + if ($cmi5launch->overridedefaults == '1') { + // Check to see if there is a record of this instance in the table. + $cmi5launchlrsid = $DB->get_field( + 'cmi5launch_lrs', + 'id', + array('cmi5launchid' => $cmi5launch->instance), + IGNORE_MISSING + ); + // If not, will need to insert_record. + if (!$cmi5launchlrsid) { + if (!$DB->insert_record('cmi5launch_lrs', $cmi5launchlrs)) { + return false; + } + } else { // If it does exist, update it. + $cmi5launchlrs->id = $cmi5launchlrsid; + + if (!$DB->update_record('cmi5launch_lrs', $cmi5launchlrs)) { + return false; + } + } + } + */ + + if (!$DB->update_record('cmi5launch', $cmi5launch)) { + return false; + } + + // Process uploaded file. + if (!empty($cmi5launch->packagefile)) { + cmi5launch_process_new_package($cmi5launch); + } + + return true; +} + +function cmi5launch_build_lrs_settings(stdClass $cmi5launch) { + global $DB, $CFG; + + // Data for cmi5launch_lrs table. + $cmi5launchlrs = new stdClass(); + $cmi5launchlrs->customacchp = $cmi5launch->cmi5launchcustomacchp; + $cmi5launchlrs->useactoremail = $cmi5launch->cmi5launchuseactoremail; + $cmi5launchlrs->cmi5launchid = $cmi5launch->instance; + + return $cmi5launchlrs; +} + +/** + * Removes an instance of the cmi5launch from the database + * + * Given an ID of an instance of this module, + * this function will permanently delete the instance + * and any data that depends on it. + * + * @param int $id Id of the module instance + * @return boolean Success/Failure + */ +function cmi5launch_delete_instance($id) { + global $DB; + + if (! $cmi5launch = $DB->get_record('cmi5launch', array('id' => $id))) { + return false; + } + + // Determine if there is a record of this (ever) in the cmi5launch_lrs table. + $cmi5launchlrsid = $DB->get_field('cmi5launch_lrs', 'id', array('cmi5launchid' => $id), $strictness = IGNORE_MISSING); + if ($cmi5launchlrsid) { + // If there is, delete it. + $DB->delete_records('cmi5launch_lrs', array('id' => $cmi5launchlrsid)); + } + + $DB->delete_records('cmi5launch', array('id' => $cmi5launch->id)); + + return true; +} + +/** + * Returns a small object with summary information about what a + * user has done with a given particular instance of this module + * Used for user activity reports. + * $return->time = the time they did it + * $return->info = a short text description + * + * @return stdClass|null + */ +function cmi5launch_user_outline($course, $user, $mod, $cmi5launch) { + $return = new stdClass(); + $return->time = 0; + $return->info = ''; + return $return; +} + +/** + * Prints a detailed representation of what a user has done with + * a given particular instance of this module, for user activity reports. + * + * @param stdClass $course the current course record + * @param stdClass $user the record of the user we are generating report for + * @param cm_info $mod course module info + * @param stdClass $cmi5launch the module instance record + * @return void, is supposed to echp directly + */ +function cmi5launch_user_complete($course, $user, $mod, $cmi5launch) { +} + +/** + * Given a course and a time, this module should find recent activity + * that has occurred in cmi5launch activities and print it out. + * Return true if there was output, or false is there was none. + * + * @return boolean + */ +function cmi5launch_print_recent_activity($course, $viewfullnames, $timestart) { + return false; // True if anything was printed, otherwise false. +} + +/** + * Prepares the recent activity data + * + * This callback function is supposed to populate the passed array with + * custom activity records. These records are then rendered into HTML via + * {@link cmi5launch_print_recent_mod_activity()}. + * + * @param array $activities sequentially indexed array of objects with the 'cmid' property + * @param int $index the index in the $activities to use for the next record + * @param int $timestart append activity since this time + * @param int $courseid the id of the course we produce the report for + * @param int $cmid course module id + * @param int $userid check for a particular user's activity only, defaults to 0 (all users) + * @param int $groupid check for a particular group's activity only, defaults to 0 (all groups) + * @return void adds items into $activities and increases $index + */ +function cmi5launch_get_recent_mod_activity(&$activities, &$index, $timestart, $courseid, $cmid, $userid = 0, $groupid = 0) { +} + +/** + * Prints single activity item prepared by {@see cmi5launch_get_recent_mod_activity()} + * @return void + */ +function cmi5launch_print_recent_mod_activity($activity, $courseid, $detail, $modnames, $viewfullnames) { +} + +/** + * Function to be run periodically according to the moodle cron + * This function searches for things that need to be done, such + * as sending out mail, toggling flags etc ... + * + * @return boolean + * @todo Finish documenting this function + **/ +function cmi5launch_cron() { + return true; +} + +/** + * Returns all other caps used in the module + * + * @example return array('moodle/site:accessallgroups'); + * @return array + */ +function cmi5launch_get_extra_capabilities() { + return array(); +} + +// File API. + +/** + * Returns the lists of all browsable file areas within the given module context + * + * The file area 'intro' for the activity introduction field is added automatically + * by {@link file_browser::get_file_info_context_module()} + * + * @param stdClass $course + * @param stdClass $cm + * @param stdClass $context + * @return array of [(string)filearea] => (string)description + */ +function cmi5launch_get_file_areas($course, $cm, $context) { + $areas = array(); + $areas['content'] = get_string('areacontent', 'scorm'); + $areas['package'] = get_string('areapackage', 'scorm'); + return $areas; +} + +/** + * File browsing support for cmi5launch file areas + * + * @package mod_cmi5launch + * @category files + * + * @param file_browser $browser + * @param array $areas + * @param stdClass $course + * @param stdClass $cm + * @param stdClass $context + * @param string $filearea + * @param int $itemid + * @param string $filepath + * @param string $filename + * @return file_info instance or null if not found + */ +function cmi5launch_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) { + global $CFG; + + if (!has_capability('moodle/course:managefiles', $context)) { + return null; + } + + $fs = get_file_storage(); + + if ($filearea === 'package') { + $filepath = is_null($filepath) ? '/' : $filepath; + $filename = is_null($filename) ? '.' : $filename; + + $urlbase = $CFG->wwwroot.'/pluginfile.php'; + if (!$storedfile = $fs->get_file($context->id, 'mod_cmi5launch', 'package', 0, $filepath, $filename)) { + if ($filepath === '/' && $filename === '.') { + $storedfile = new virtual_root_file($context->id, 'mod_cmi5launch', 'package', 0); + } else { + // Not found. + return null; + } + } + return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false); + } + + return false; +} + +/** + * Serves cmi5 content, introduction images and packages. Implements needed access control ;-) + * + * @package mod_cmi5launch + * @category files + * @param stdClass $course course object + * @param stdClass $cm course module object + * @param stdClass $context context object + * @param string $filearea file area + * @param array $args extra arguments + * @param bool $forcedownload whether or not force download + * @param array $options additional options affecting the file serving + * @return bool false if file not found, does not return if found - just send the file + */ +function cmi5launch_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options = array()) { + global $CFG, $DB; + + if ($context->contextlevel != CONTEXT_MODULE) { + return false; + } + + require_login($course, true, $cm); + $canmanageactivity = has_capability('moodle/course:manageactivities', $context); + + $filename = array_pop($args); + $filepath = implode('/', $args); + if ($filearea === 'content') { + $lifetime = null; + } else if ($filearea === 'package') { + $lifetime = 0; // No caching here. + } else { + return false; + } + + $fs = get_file_storage(); + + if ( + (!$file = $fs->get_file($context->id, 'mod_cmi5launch', $filearea, 0, '/'.$filepath.'/', $filename)) + || ($file->is_directory()) + ) { + if ($filearea === 'content') { // Return file not found straight away to improve performance. + send_header_404(); + die; + } + return false; + } + + // Finally send the file. + send_stored_file($file, $lifetime, 0, false, $options); +} + +/** + * Export file resource contents for web service access. + * + * @param cm_info $cm Course module object. + * @param string $baseurl Base URL for Moodle. + * @return array array of file content + */ +function cmi5launch_export_contents($cm, $baseurl) { + global $CFG; + $contents = array(); + $context = context_module::instance($cm->id); + + $fs = get_file_storage(); + $files = $fs->get_area_files($context->id, 'mod_cmi5launch', 'package', 0, 'sortorder DESC, id ASC', false); + + foreach ($files as $fileinfo) { + $file = array(); + $file['type'] = 'file'; + $file['filename'] = $fileinfo->get_filename(); + $file['filepath'] = $fileinfo->get_filepath(); + $file['filesize'] = $fileinfo->get_filesize(); + $file['fileurl'] = file_encode_url("$CFG->wwwroot/" . $baseurl, '/'.$context->id.'/mod_cmi5launch/package'. + $fileinfo->get_filepath().$fileinfo->get_filename(), true); + $file['timecreated'] = $fileinfo->get_timecreated(); + $file['timemodified'] = $fileinfo->get_timemodified(); + $file['sortorder'] = $fileinfo->get_sortorder(); + $file['userid'] = $fileinfo->get_userid(); + $file['author'] = $fileinfo->get_author(); + $file['license'] = $fileinfo->get_license(); + $contents[] = $file; + } + + return $contents; +} + +// Navigation API. + +/** + * Extends the global navigation tree by adding cmi5launch nodes if there is a relevant content + * + * This can be called by an AJAX request so do not rely on $PAGE as it might not be set up properly. + * + * @param navigation_node $navref An object representing the navigation tree node of the cmi5launch module instance + * @param stdClass $course + * @param stdClass $module + * @param cm_info $cm + */ +function cmi5launch_extend_navigation(navigation_node $navref, stdclass $course, stdclass $module, cm_info $cm) { +} + +/** + * Extends the settings navigation with the cmi5launch settings + * + * This function is called when the context for the page is a cmi5launch module. This is not called by AJAX + * so it is safe to rely on the $PAGE. + * + * @param settings_navigation $settingsnav {@link settings_navigation} + * @param navigation_node $cmi5launchnode {@link navigation_node} + */ +function cmi5launch_extend_settings_navigation(settings_navigation $settingsnav, navigation_node $cmi5launchnode = null) { +} + +// Called by Moodle core. +function cmi5launch_get_completion_state($course, $cm, $userid, $type) { + global $CFG, $DB; + $result = $type; // Default return value. + + // Get cmi5launch. + if (!$cmi5launch = $DB->get_record('cmi5launch', array('id' => $cm->instance))) { + throw new Exception("Can't find activity {$cm->instance}"); // TODO: localise this. + } + + $cmi5launchsettings = cmi5launch_settings($cm->instance); + + $expirydate = null; + $expirydays = $cmi5launch->cmi5expiry; + if ($expirydays > 0) { + $expirydatetime = new DateTime(); + $expirydatetime->sub(new DateInterval('P'.$expirydays.'D')); + $expirydate = $expirydatetime->format('c'); + } + + if (!empty($cmi5launch->cmi5verbid)) { + // Try to get a statement matching actor, verb and object specified in module settings. + $statementquery = cmi5launch_get_statements( + $cmi5launchsettings['cmi5launchlrsendpoint'], + $cmi5launchsettings['cmi5launchlrslogin'], + $cmi5launchsettings['cmi5launchlrspass'], + $cmi5launchsettings['cmi5launchlrsversion'], + $cmi5launch->cmi5activityid, + cmi5launch_getactor($cm->instance), + $cmi5launch->cmi5verbid, + $expirydate + ); + + // If the statement exists, return true else return false. + if (!empty($statementquery->content) && $statementquery->success) { + $result = true; + } else { + $result = false; + } + } + + return $result; +} + +// Cmi5Launch specific functions. +/* +The functions below should really be in locallib, however they are required for one +or more of the functions above so need to be here. +It looks like the standard Quiz module does that same thing, so I don't feel so bad. +*/ + +/** + * Handles uploaded zip packages when a module is added or updated. Unpacks the zip contents + * and extracts the launch url and activity id from the cmi5.xml file. + * Note: This takes the *first* activity from the cmi5.xml file to be the activity intended + * to be launched. It will not go hunting for launch URLs any activities listed below. + * Based closely on code from the SCORM and (to a lesser extent) Resource modules. + * @package mod_cmi5launch + * @category cmi5 + * @param object $cmi5launch An object from the form in mod_form.php + * @return array empty if no issue is found. Array of error message otherwise + */ + +function cmi5launch_process_new_package($cmi5launch) { + + global $DB, $CFG; + $cmid = $cmi5launch->coursemodule; + $context = context_module::instance($cmid); + + // Bring in functions from classes cmi5Connector and AU helpers. + $connectors = new cmi5_connectors; + $auhelper = new au_helpers; + + // Bring in functions from class cmi5_table_connectors and AU helpers. + $createcourse = $connectors->cmi5launch_get_create_course(); + $retrieveaus = $auhelper->get_cmi5launch_retrieve_aus(); + // Reload cmi5 instance. + $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); + + $fs = get_file_storage(); + + $fs->delete_area_files($context->id, 'mod_cmi5launch', 'package'); + file_save_draft_area_files( + $cmi5launch->packagefile, + $context->id, + 'mod_cmi5launch', + 'package', + 0, + array('subdirs' => 0, 'maxfiles' => 1) + ); + // Get filename of zip that was uploaded. + $files = $fs->get_area_files($context->id, 'mod_cmi5launch', 'package', 0, '', false); + if (count($files) < 1) { + return false; + } + + $zipfile = reset($files); + $zipfilename = $zipfile->get_filename(); + $packagefile = false; + + $packagefile = $fs->get_file($context->id, 'mod_cmi5launch', 'package', 0, '/', $zipfilename); + + // Retrieve user settings to apply to newly created record. + $settings = cmi5launch_settings($cmi5launch->id); + $token = $settings['cmi5launchtenanttoken']; + // Create the course and retrieve info for saving to DB. + $courseresults = $createcourse($context->id, $token, $packagefile); + + // Take the results of created course and save new course id to table. + $record->courseinfo = $courseresults; + + $returnedinfo = json_decode($courseresults, true); + // Retrieve the lmsId of course. + $lmsid = $returnedinfo["lmsId"]; + $record->cmi5activityid = $lmsid; + + $record->courseid = $returnedinfo["id"]; + + // Create url for sending to when requesting launch url for course. + $playerurl = $settings['cmi5launchplayerurl']; + + // Build and save launchurl. + $url = $playerurl . "/api/v1/". $record->courseid. "/launch-url/"; + $record->launchurl = $url; + + // Retrieve AUs and save to table. + $aus = ($retrieveaus($returnedinfo)); + $record->aus = (json_encode($aus)); + + $fs->delete_area_files($context->id, 'mod_cmi5launch', 'content'); + + $packer = get_file_packer('application/zip'); + $packagefile->extract_to_storage($packer, $context->id, 'mod_cmi5launch', 'content', 0, '/'); + + // If the cmi5.xml file isn't there, don't do try to use it. + // This is unlikely as it should have been checked when the file was validated. + if ($manifestfile = $fs->get_file($context->id, 'mod_cmi5launch', 'content', 0, '/', 'cmi5.xml')) { + $xmltext = $manifestfile->get_content(); + + $defaultorgid = 0; + $firstinorg = 0; + + $pattern = '/&(?!\w{2,6};)/'; + $replacement = '&'; + $xmltext = preg_replace($pattern, $replacement, $xmltext); + + $objxml = new xml2Array(); + $manifest = $objxml->parse($xmltext); + + // Update activity id from the first activity in cmi5.xml, if it is found. + // Skip without error if not. (The Moodle admin will need to enter the id manually). + if (isset($manifest[0]["children"][0]["children"][0]["attrs"]["ID"])) { + $record->cmi5activityid = $manifest[0]["children"][0]["children"][0]["attrs"]["ID"]; + } + + // Update launch from the first activity in cmi5.xml, if it is found. + // Skip if not. (The Moodle admin will need to enter the url manually). + foreach ($manifest[0]["children"][0]["children"][0]["children"] as $property) { + if ($property["name"] === "LAUNCH") { + $record->cmi5launchurl = $CFG->wwwroot."/pluginfile.php/".$context->id."/mod_cmi5launch/" + .$manifestfile->get_filearea()."/".$property["tagData"]; + } + } + } + // Save reference. + // Turn off to trigger echo. + return $DB->update_record('cmi5launch', $record); +} + +/** + * Check that a Zip file contains a cmi5.xml file in the right place. Used in mod_form.php. + * Heavily based on scorm_validate_package in /mod/scorm/lib.php + * @package mod_cmi5launch + * @category cmi5 + * @param stored_file $file a Zip file. + * @return array empty if no issue is found. Array of error message otherwise + */ +function cmi5launch_validate_package($file) { + $packer = get_file_packer('application/zip'); + $errors = array(); + $filelist = $file->list_files($packer); + if (!is_array($filelist)) { + $errors['packagefile'] = get_string('badarchive', 'cmi5launch'); + } else { + $badmanifestpresent = false; + foreach ($filelist as $info) { + if ($info->pathname == 'cmi5.xml') { + return array(); + } else if (strpos($info->pathname, 'cmi5.xml') !== false) { + // This package has cmi5 xml file inside a folder of the package. + $badmanifestpresent = true; + } + if (preg_match('/\.cst$/', $info->pathname)) { + return array(); + } + } + if ($badmanifestpresent) { + $errors['packagefile'] = get_string('badimsmanifestlocation', 'cmi5launch'); + } else { + $errors['packagefile'] = get_string('nomanifest', 'cmi5launch'); + } + } + return $errors; +} + +/** + * Check for AUs and their satisifed status in a block. Recursive to handle nested blocks. + * + * @package mod_cmi5launch + * @category cmi5 + * @param mixed bool|array - $auinfoin - the info containing a block, au, or true/false au satisfied value + * @param string $aulmsid - the lms id of the au we are looking for. + * @return mixed - returns an array of aus or au satisfied value. + */ + +function cmi5launch_find_au_satisfied($auinfoin, $aulmsid) { + + $ausatisfied = ""; + + // Check if auinfoin is a boolean or an array. + // It will be an array if an AU was found on recursive call. + if (is_bool($auinfoin)) { + + // Return value to func that called it. + return $auinfoin; + + // If it's an array it is either a block we still need to break down, or an AU we need to find satisfied value for. + } else if (is_array($auinfoin)) { + + // Check AU's satisifeid value and display accordingly. + foreach ($auinfoin as $key => $auinfo) { + + $ausatisfied = "false"; + + // If it's a block, we need to keep breaking it down. + if ($auinfo["type"] == "block" ) { + + // Grab its children, this is what other blocks or AU's will be nested in. + $auchildren = $auinfo["children"]; + + // Now recursively call function again. + $ausatisfied = cmi5launch_find_au_satisfied($auchildren, $aulmsid); + + // If it's an AU, we need to check if it's the one we are looking for. + } else if ($auinfo["type"] == "au") { + + // Search for the correct lms id and take only the AU that matches. + if ( $auinfo["lmsId"] == $aulmsid) { + // If it is, retrieve the satisfied value. + $ausatisfied = $auinfo["satisfied"]; + } else { + + // If no ids match we have a problem, and need to return. + $ausatisfied = "No ids match"; + } + } else { + // This shouldn't be reachable, but in case add error message. + echo "Type from statement does not equal either block or AU."; + } + } + } else { + + echo"Incorrect value passed to function cmi5launch_find_au_satisfied. Correct values are a boolean or array"; + + } + + return $ausatisfied; +} + +/** + * Fetches Statements from the LRS. This is used for completion tracking - + * we check for a statement matching certain criteria for each learner. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $url LRS endpoint URL + * @param string $basiclogin login/key for the LRS + * @param string $basicpass pass/secret for the LRS + * @param string $version version of xAPI to use + * @param string $activityid Activity Id to filter by + * @param cmi5 Agent $agent Agent to filter by + * @param string $verb Verb Id to filter by + * @param string $since Since date to filter by + * @return cmi5 LRS Response + */ +function cmi5launch_get_statements($url, $basiclogin, $basicpass, $version, $activityid, $agent, $verb, $since = null) { + + $lrs = new \cmi5\RemoteLRS($url, $version, $basiclogin, $basicpass); + + $statementsquery = array( + "agent" => $agent, + "verb" => new \cmi5\Verb(array("id" => trim($verb))), + "activity" => new \cmi5\Activity(array("id" => trim($activityid))), + "related_activities" => "false", + "format" => "ids", + ); + + if (!is_null($since)) { + $statementsquery["since"] = $since; + } + + // Get all the statements from the LRS. + $statementsresponse = $lrs->queryStatements($statementsquery); + + if ($statementsresponse->success == false) { + return $statementsresponse; + } + + $allthestatements = $statementsresponse->content->getStatements(); + $morestatementsurl = $statementsresponse->content->getMore(); + while (!empty($morestatementsurl)) { + $morestmtsresponse = $lrs->moreStatements($morestatementsurl); + if ($morestmtsresponse->success == false) { + return $morestmtsresponse; + } + $morestatements = $morestmtsresponse->content->getStatements(); + $morestatementsurl = $morestmtsresponse->content->getMore(); + // Note: due to the structure of the arrays, array_merge does not work as expected. + foreach ($morestatements as $morestatement) { + array_push($allthestatements, $morestatement); + } + } + + return new \cmi5\LRSResponse( + $statementsresponse->success, + $allthestatements, + $statementsresponse->httpResponse + ); +} + +/** + * Build a cmi5 Agent based on the current user + * + * @package mod_cmi5launch + * @category cmi5 + * @return cmi5 Agent $agent Agent + */ + +function cmi5launch_getactor($instance) { + global $USER, $CFG; + + $settings = cmi5launch_settings($instance); + + if ($USER->idnumber && $settings['cmi5launchcustomacchp']) { + $agent = array( + "name" => fullname($USER), + "account" => array( + "homePage" => $settings['cmi5launchcustomacchp'], + "name" => $USER->idnumber, + ), + "objectType" => "Agent", + ); + } else if ($USER->email && $settings['cmi5launchuseactoremail']) { + $agent = array( + "name" => fullname($USER), + "mbox" => "mailto:".$USER->email, + "objectType" => "Agent", + ); + } else { + $agent = array( + "name" => fullname($USER), + "account" => array( + "homePage" => $CFG->wwwroot, + "name" => $USER->username, + ), + "objectType" => "Agent", + ); + } + + return new \cmi5\Agent($agent); +} + + +/** + * Returns the LRS settings relating to a cmi5 Launch module instance + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $instance The Moodle id for the cmi5 module instance. + * @return array LRS settings to use + */ +function cmi5launch_settings($instance) { + + global $DB, $CFG, $cmi5launchsettings; + + if (!is_null($cmi5launchsettings)) { + return $cmi5launchsettings; + } + + $expresult = array(); + + $result = $DB->get_records('config_plugins', array('plugin' => 'cmi5launch')); + + foreach ($result as $value) { + $expresult[$value->name] = $value->value; + } + + $expresult['cmi5launchlrsversion'] = '1.0.0'; + + $cmi5launchsettings = $expresult; + + return $expresult; +} + + +/** + * Should the global LRS settings be used instead of the instance specific ones? + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $instance The Moodle id for the cmi5 module instance. + * @return bool + */ +function cmi5launch_use_global_cmi5_lrs_settings($instance) { + global $DB; + // Determine if there is a row in cmi5launch_lrs matching the current activity id. + $activitysettings = $DB->get_record('cmi5launch', array('id' => $instance)); + + /* Removed override defaults from db + if ($activitysettings->overridedefaults == 1) { + return false; + } + */ + return true; +} + +// Grade functions. + +/** + * Return grade for given user or all users. + * + * @global stdClass + * @global object + * @param int $cmi5id id of scorm + * @param int $userid optional user id, 0 means all users + * @return array array of grades, false if none + */ +function cmi5launch_get_user_grades($cmi5launch, $userid=0) { + + // External class and functions. + $gradehelpers = new grade_helpers; + + $updategrades = $gradehelpers->get_cmi5launch_check_user_grades_for_updates(); + + global $CFG, $DB; + + $id = required_param('id', PARAM_INT); + $contextmodule = context_module::instance($id); + + $grades = array(); + + // If userid is empty it means we want all users. + if (empty($userid)) { + + // If the userid is empty, use get_enrolled_users for this course then update all their grades. + $users = get_enrolled_users($contextmodule); + + // If there is a list of users then iterate through it and make grade objects with them and their updated grades. + if ($users) { + + foreach ($users as $user) { + + $grades[$user->id] = new stdClass(); + $grades[$user->id]->id = $user->id; + $grades[$user->id]->userid = $user->id; + $grades[$user->id]->rawgrade = $updategrades($user); + } + } else { + // Return false to avoid null values if no users. + return false; + } + } else { + + // This is if we have a specific user, so we need to retrieve their information. + $user = $DB->get_record('user', ['id' => $userid]); + + // Make grade objects with the individual user and their updated grades. + $grades[$userid] = new stdClass(); + $grades[$userid]->id = $userid; + $grades[$userid]->userid = $userid; + $grades[$userid]->rawgrade = $updategrades($user); + } + + return $grades; +} + +/** + * Update grades in central gradebook. + * @category grade + * @param object $cmi5launch - mod object + * @param int $userid - A user ID or 0 for all users. + * @param bool $nullifnone - If true and a single user is specified with no grade, a grade item with a null rawgrade is inserted. + */ +// This function is called automatically by moodle if it needs the users grades updated. +// It can also be called manually when you want to push a new grade to gradebook. +// This function should do whatever is needed to generate the relevant grades to push into gradebook +// then call 'myplugin_grade_item_update with the grades to write. + +function cmi5launch_update_grades($cmi5launch, $userid = 0, $nullifnone = true) { + + global $CFG, $DB; + + require_once($CFG->libdir . '/gradelib.php'); + require_once($CFG->libdir . '/completionlib.php'); + $id = required_param('id', PARAM_INT); + + // Reload cmi5 course instance. + $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); + $cm = get_coursemodule_from_id('cmi5launch', $id, 0, false, MUST_EXIST); + $contextmodule = context_module::instance($cm->id); + $users = get_enrolled_users($contextmodule); + + // Retrieve user grades and update gradebook. + if ($userid) { + + $grades = cmi5launch_get_user_grades($cmi5launch, $userid); + + // Grades come back nested in array, with keys being the user id. + $grades = $grades[$userid]; + + cmi5launch_grade_item_update($cmi5launch, $grades); + + } else if ($userid && $nullifnone) { + + // User has no grades so assign null. + $grade = new stdClass(); + $grade->userid = $userid; + $grade->rawgrade = null; + + cmi5launch_grade_item_update($cmi5launch, $grade); + + } else { + + cmi5launch_grade_item_update($cmi5launch); + } +} + +/** + * Update/create grade item for given cmi5 activity. + * Calls grade_update from moodle gradelib.php + * @category grade + * @param object $cmi5launch - mod object + * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook + * @return object grade_item + */ +// This is the only place that grade_update should be called. +// And this function should be called from cmi5launch_add_instance, cmi5launch_update_instance and cmi5launch_update_grades. +// It should look at settings in the activity $activitydbrecord to determine grading type, max and min +// values, etc. Then setup gradeinfo to pass to grade_update, it should also pass on the optional grades value. +// $gradeinfo is an array containing: ['itemname' => $activityname, 'idnumber' => $activityidnumber, +// 'gradetype' => GRADE_TYPE_VALUE, 'grademax' => 100, 'grademin' => 0]. +function cmi5launch_grade_item_update($cmi5launch, $grades = null) { + + global $CFG, $DB, $USER, $cmi5launchsettings; + + // Bring in grade helpers. + $gradehelpers = new grade_helpers; + + // Functions from other classes. + $highestgrade = $gradehelpers->get_cmi5launch_highest_grade(); + $averagegrade = $gradehelpers->get_cmi5launch_average_grade(); + $settings = cmi5launch_settings($cmi5launch->id); + + if (!function_exists('grade_update')) { // Workaround for buggy PHP versions. + require_once($CFG->libdir . '/gradelib.php'); + } + // Reload cmi5 course instance. + $record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); + + // Assign course instance info to grade params. + $params = array('itemname' => $cmi5launch->name); + if (isset($cmi5launch->id)) { + $params['idnumber'] = $cmi5launch->id; + } + + // Retrieve the settings for course grading. + $gradetype = $cmi5launchsettings["grademethod"]; + $maxgrade = $settings['maxgrade']; + + // Assign settings to grade item param. + $params['grademax'] = $maxgrade; + $params['grademin'] = 0; + + // If there's a max grade, set it. + if ($maxgrade) { + $params['gradetype'] = $gradetype; + $params['grademax'] = $maxgrade; + $params['grademin'] = 0; + } else { + + $params['gradetype'] = $gradetype; + } + + // Check if it's call to reset. + if ($grades === 'reset') { + $params['reset'] = true; + $grades = null; + } else { + + // Calculate grade based on grade type, and update rawgrade (a param of grade item). + switch($gradetype){ + + // 'MOD_CMI5LAUNCH_AUS_GRADE' = '0'. + // 'MOD_CMI5LAUNCH_GRADE_HIGHEST' = '1'. + // 'MOD_CMI5LAUNCH_GRADE_AVERAGE', = '2'. + // 'MOD_CMI5LAUNCH_GRADE_SUM', = '3'. + + case 1: + foreach ($grades as $key => $grade) { + + $grades->rawgrade = $highestgrade($grades->rawgrade); + } + break; + + case 2: + foreach ($grades as $key => $grade) { + + $grades->rawgrade = $averagegrade($grades->rawgrade); + + } + break; + } + } + + // Call grade_update to update gradebook. + return grade_update('mod/cmi5launch', $cmi5launch->course, 'mod', 'cmi5launch', $cmi5launch->id, 0, $grades, $params); +} + diff --git a/locallib.php b/locallib.php index 0a68c60..048afb8 100755 --- a/locallib.php +++ b/locallib.php @@ -1,599 +1,505 @@ -. - -/** - * Internal library of functions for module cmi5launch - * - * All the cmi5launch specific functions, needed to implement the module - * logic, should go here. Never include this file from your lib.php! - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); -require_once("$CFG->dirroot/mod/cmi5launch/lib.php"); - -//Classes for connecting to CMI5 player -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/cmi5Connector.php"); - - -/** - * Send a statement that the activity was launched. - * This is useful for debugging - if the 'launched' statement is present in the LRS, you know the activity was at least launched. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string/UUID $registrationid The cmi5 Registration UUID associated with the launch. - * @return cmi5 LRS Response - */ -function cmi5_launched_statement($registrationid) { - global $cmi5launch, $course, $CFG; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - - $version = $cmi5launchsettings['cmi5launchlrsversion']; - $url = $cmi5launchsettings['cmi5launchlrsendpoint']; - $basiclogin = $cmi5launchsettings['cmi5launchlrslogin']; - $basicpass = $cmi5launchsettings['cmi5launchlrspass']; - - $cmi5phputil = new \cmi5\Util(); - $statementid = $cmi5phputil->getUUID(); - - $lrs = new \cmi5\RemoteLRS($url, $version, $basiclogin, $basicpass); - - $parentdefinition = array(); - if (isset($course->summary) && $course->summary !== "") { - $parentdefinition["description"] = array( - "en-US" => $course->summary - ); - } - - if (isset($course->fullname) && $course->fullname !== "") { - $parentdefinition["name"] = array( - "en-US" => $course->fullname - ); - } - - $statement = new \cmi5\Statement( - array( - 'id' => $statementid, - 'actor' => cmi5launch_getactor($cmi5launch->id), - 'verb' => array( - 'id' => 'http://adlnet.gov/expapi/verbs/launched', - 'display' => array( - 'en-US' => 'launched' - ) - ), - - 'object' => array( - 'id' => $cmi5launch->cmi5activityid, - 'objectType' => "Activity" - ), - - "context" => array( - "registration" => $registrationid, - "contextActivities" => array( - "parent" => array( - array( - "id" => $CFG->wwwroot.'/course/view.php?id='. $course->id, - "objectType" => "Activity", - "definition" => $parentdefinition - ) - ), - "grouping" => array( - array( - "id" => $CFG->wwwroot, - "objectType" => "Activity" - ) - ), - "category" => array( - array( - "id" => "https://moodle.org", - "objectType" => "Activity", - "definition" => array ( - "type" => "http://id.cmi5api.com/activitytype/source" - ) - ) - ) - ), - "language" => cmi5launch_get_moodle_langauge() - ), - "timestamp" => date(DATE_ATOM) - ) - ); - - $response = $lrs->saveStatement($statement); - return $response; -} - -/** - * Builds a cmi5 launch link for the current module and a given registration - * - * @package mod_cmi5launch - * @category cmi5 - * @param string/UUID $registrationid The cmi5 Registration UUID associated with the launch. - * @return string launch link including querystring. - */ -function cmi5launch_get_launch_url($registrationuuid, $auID) { - global $cmi5launch, $CFG, $DB; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - $expiry = new DateTime('NOW'); - $xapiduration = $cmi5launchsettings['cmi5launchlrsduration']; - $expiry->add(new DateInterval('PT'.$xapiduration.'M')); - - $url = trim($cmi5launchsettings['cmi5launchlrsendpoint']); - - // Call the function to get the credentials from the LRS. - $basiclogin = trim($cmi5launchsettings['cmi5launchlrslogin']); - $basicpass = trim($cmi5launchsettings['cmi5launchlrspass']); - - switch ($cmi5launchsettings['cmi5launchlrsauthentication']) { - - // Learning Locker 1. - case "0": - $creds = cmi5launch_get_creds_learninglocker($cmi5launchsettings['cmi5launchlrslogin'], - $cmi5launchsettings['cmi5launchlrspass'], - $url, - $expiry, - $registrationuuid - ); - $basicauth = base64_encode($creds["contents"]["key"].":".$creds["contents"]["secret"]); - break; - - // Watershed. - case "2": - $creds = cmi5launch_get_creds_watershed ( - $basiclogin, - $basicpass, - $url, - $xapiduration * 60 - ); - $basicauth = base64_encode($creds["key"].":".$creds["secret"]); - break; - - default: - $basicauth = base64_encode($basiclogin.":".$basicpass); - break; - } -//to bring in functions from class cmi5Connector -$connectors = new cmi5Connectors; -//Get retrieve URL function -$retrieveUrl = $connectors->getRetrieveUrl(); -//See here we are passing the auid. If we have session ids will we pass those instead -//or is that a whole new func, I think it may be -//$rtnstring = $retrieveUrl($cmi5launch->id, $auID); - -//return $rtnstring; -} - -/** - * Used with Learning Locker integration to fetch credentials from the LRS. - * This process is not part of the xAPI specification or the cmi5 launch spec. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $basiclogin login/key for the LRS - * @param string $basicpass pass/secret for the LRS - * @param string $url LRS endpoint URL - * @return array the response of the LRS (Note: not a cmi5 LRS Response object) - */ -function cmi5launch_get_creds_learninglocker($basiclogin, $basicpass, $url, $expiry, $registrationuuid) { - global $cmi5launch; - $actor = cmi5launch_getactor($cmi5launch->id); - $data = array( - 'scope' => array ('all'), - 'expiry' => $expiry->format(DATE_ATOM), - 'historical' => false, - 'actors' => array( - "objectType" => 'Person', - "name" => array($actor->getName()) - ), - 'auth' => $actor, - 'activity' => array( - $cmi5launch->cmi5activityid, - ), - 'registration' => $registrationuuid - ); - - if (null !== $actor->getMbox()) { - $data['actors']['mbox'] = array($actor->getMbox()); - } else if (null !== $actor->getMbox_sha1sum()) { - $data['actors']['mbox_sha1sum'] = array($actor->getMbox_sha1sum()); - } else if (null !== $actor->getOpenid()) { - $data['actors']['openid'] = array($actor->getOpenid()); - } else if (null !== $actor->getAccount()) { - $data['actors']['account'] = array($actor->getAccount()); - } - - $streamopt = array( - 'ssl' => array( - 'verify-peer' => false, - ), - 'http' => array( - 'method' => 'POST', - 'ignore_errors' => false, - 'header' => array( - 'Authorization: Basic ' . base64_encode(trim($basiclogin) . ':' .trim($basicpass)), - 'Content-Type: application/json', - 'Accept: application/json, */*; q=0.01', - ), - 'content' => cmi5launch_myjson_encode($data), - ), - ); - - $streamparams = array(); - - $context = stream_context_create($streamopt); - - $stream = fopen(trim($url) . 'Basic/request'.'?'.http_build_query($streamparams, '', '&'), 'rb', false, $context); - - $returncode = explode(' ', $http_response_header[0]); - $returncode = (int)$returncode[1]; - - switch($returncode){ - case 200: - $ret = stream_get_contents($stream); - $meta = stream_get_meta_data($stream); - - if ($ret) { - $ret = json_decode($ret, true); - } - break; - default: // Error! - $ret = null; - $meta = $returncode; - break; - } - - return array( - 'contents' => $ret, - 'metadata' => $meta - ); -} - -/** - * By default, PHP escapes slashes when encoding into JSON. This cause problems for cmi5, - * so this function unescapes the slashes after encoding. - * - * @package mod_cmi5launch - * @category cmi5 - * @param object or array $obj object or array encode to JSON - * @return string/JSON JSON encoded object or array - */ -function cmi5launch_myjson_encode($obj) { - return str_replace('\\/', '/', json_encode($obj)); -} - -/** - * Save data to the state. Note: registration is not used as this is a general bucket of data against the activity/learner. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $data data to store as document - * @param string $key id to store the document against - * @param string $etag etag associated with the document last time it was fetched (may be Null if document is new) - * @return cmi5 LRS Response - */ -function cmi5launch_get_global_parameters_and_save_state($data, $key, $etag) { - global $cmi5launch; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - $lrs = new \cmi5\RemoteLRS( - $cmi5launchsettings['cmi5launchlrsendpoint'], - $cmi5launchsettings['cmi5launchlrsversion'], - $cmi5launchsettings['cmi5launchlrslogin'], - $cmi5launchsettings['cmi5launchlrspass'] - ); - - return $lrs->saveState( - new \cmi5\Activity(array("id" => trim($cmi5launch->cmi5activityid))), - cmi5launch_getactor($cmi5launch->id), - $key, - cmi5launch_myjson_encode($data), - array( - 'etag' => $etag, - 'contentType' => 'application/json' - ) - ); -} - -/** - * Save data to the agent profile. - * Note: registration is not used as this is a general bucket of data against the activity/learner. - * Note: fetches a new etag before storing. Will ALWAYS overwrite existing contents of the document. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $data data to store as document - * @param string $key id to store the document against - * @return cmi5 LRS Response - */ -function cmi5launch_get_global_parameters_and_save_agentprofile($data, $key) { - global $cmi5launch; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - - $lrs = new \cmi5\RemoteLRS( - $cmi5launchsettings['cmi5launchlrsendpoint'], - $cmi5launchsettings['cmi5launchlrsversion'], - $cmi5launchsettings['cmi5launchlrslogin'], - $cmi5launchsettings['cmi5launchlrspass'] - ); - - $getresponse = $lrs->retrieveAgentProfile(cmi5launch_getactor($cmi5launch->id), $key); - - $opts = array( - 'contentType' => 'application/json' - ); - if ($getresponse->success) { - $opts['etag'] = $getresponse->content->getEtag(); - } - - return $lrs->saveAgentProfile( - cmi5launch_getactor($cmi5launch->id), - $key, - cmi5launch_myjson_encode($data), - $opts - ); -} - -/** - * Get data from the state. Note: registration is not used as this is a general bucket of data against the activity/learner. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $key id to store the document against - * @return cmi5 LRS Response containing the response code and data or error message - */ -function cmi5launch_get_global_parameters_and_get_state($key) { - global $cmi5launch; - $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); - - $lrs = new \cmi5\RemoteLRS( - $cmi5launchsettings['cmi5launchlrsendpoint'], - $cmi5launchsettings['cmi5launchlrsversion'], - $cmi5launchsettings['cmi5launchlrslogin'], - $cmi5launchsettings['cmi5launchlrspass'] - ); - - - return $lrs->retrieveState( - new \cmi5\Activity(array("id" => trim($cmi5launch->cmi5activityid))), - cmi5launch_getactor($cmi5launch->id), - $key - ); -} - - -/** - * Get the current lanaguage of the current user and return it as an RFC 5646 language tag - * - * @package mod_cmi5launch - * @category cmi5 - * @return string RFC 5646 language tag - */ - -function cmi5launch_get_moodle_langauge() { - $lang = current_language(); - $langarr = explode('_', $lang); - if (count($langarr) == 2) { - return $langarr[0].'-'.strtoupper($langarr[1]); - } else { - return $lang; - } -} - - -/** - * Used with Watershed integration to fetch credentials from the LRS. - * This process is not part of the xAPI specification or the cmi5 launch spec. - * - * @package mod_cmi5launch - * @category cmi5 - * @param string $login login for Watershed - * @param string $pass pass for Watershed - * @param string $endpoint LRS endpoint URL - * @param int $expiry number of seconds the credentials are required for - * @return array the response of the LRS (Note: not a cmi5 LRS Response object) - */ -function cmi5launch_get_creds_watershed($login, $pass, $endpoint, $expiry) { - global $CFG, $DB; - - // Process input parameters. - $auth = 'Basic '.base64_encode($login.':'.$pass); - - $explodedendpoint = explode ('/', $endpoint); - $wsserver = $explodedendpoint[0].'//'.$explodedendpoint[2]; - $orgid = $explodedendpoint[5]; - - // Create a session. - $createsessionresponse = cmi5launch_send_api_request( - $auth, - "POST", - $wsserver . "/api/organizations/" . $orgid . "/activity-providers/self/sessions", - [ - "content" => json_encode([ - "expireSeconds" => $expiry, - "scope" => "xapi:all" - ]) - ] - ); - - if ($createsessionresponse["status"] === 200) { - return [ - "key" => $createsessionresponse["content"]->key, - "secret" => $createsessionresponse["content"]->secret - ]; - } else { - $reason = get_string('apCreationFailed', 'cmi5launch') - ." Status: ". $createsessionresponse["status"].". Response: ".$createsessionresponse["content"]->message; - throw new moodle_exception($reason, 'cmi5launch', ''); - } -} - -/* -@method sendAPIRequest Sends a request to the API. -@param {String} [$auth] Auth string -@param {String} [$method] Method of the request e.g. POST. -@param {String} [$url] URL to request -@param {Array} [$options] Array of optional properties. - @param {String} [content] Content of the request (should be JSON). -@return {Array} Details of the response - @return {String} [metadata] Raw metadata of the response - @return {String} [content] Raw content of the response - @return {Integer} [status] HTTP status code of the response e.g. 201 -*/ -function cmi5launch_send_api_request($auth, $method, $url) { - $options = func_num_args() === 4 ? func_get_arg(3) : array(); - - if (!isset($options['contentType'])) { - $options['contentType'] = 'application/json'; - } - - $http = array( - // We don't expect redirects. - 'max_redirects' => 0, - // This is here for some proxy handling. - 'request_fulluri' => 1, - // Switching this to false causes non-2xx/3xx status codes to throw exceptions. - // but we need to handle the "error" status codes ourselves in some cases. - 'ignore_errors' => true, - 'method' => $method, - 'header' => array() - ); - - array_push($http['header'], 'Authorization: ' . $auth); - - if (($method === 'PUT' || $method === 'POST') && isset($options['content'])) { - $http['content'] = $options['content']; - array_push($http['header'], 'Content-length: ' . strlen($options['content'])); - array_push($http['header'], 'Content-Type: ' . $options['contentType']); - } - - $context = stream_context_create(array( 'http' => $http )); - - $fp = fopen($url, 'rb', false, $context); - - $content = ""; - - if (! $fp) { - return array ( - "metadata" => null, - "content" => $content, - "status" => 0 - ); - } - $metadata = stream_get_meta_data($fp); - $content = stream_get_contents($fp); - $responsecode = (int)explode(' ', $metadata["wrapper_data"][0])[1]; - - fclose($fp); - - if ($options['contentType'] == 'application/json') { - $content = json_decode($content); - } - - return array ( - "metadata" => $metadata, - "content" => $content, - "status" => $responsecode - ); -} - -//Grade stuff from SCORM - -//Move these to top where they belong if they are what we need -define('GRADE_AUS_CMI5', '0'); -define('GRADE_HIGHEST_CMI5', '1'); -define('GRADE_AVERAGE_CMI5', '2'); -define('GRADE_SUM_CMI5', '3'); - -define('HIGHEST_ATTEMPT_CMI5', '0'); -define('AVERAGE_ATTEMPT_CMI5', '1'); -define('FIRST_ATTEMPT_CMI5', '2'); -define('LAST_ATTEMPT_CMI5', '3'); - -define('CMI5_FORCEATTEMPT_NO', 0); -define('CMI5_FORCEATTEMPT_ONCOMPLETE', 1); -define('CMI5_FORCEATTEMPT_ALWAYS', 2); - -define('CMI5_UPDATE_NEVER', '0'); -define('CMI5_UPDATE_EVERYDAY', '2'); -define('CMI5_UPDATE_EVERYTIME', '3'); - -/** - * Returns an array of the array of update frequency options - * - * @return array an array of update frequency options - */ -function cmi5_get_updatefreq_array() { - return array(CMI5_UPDATE_NEVER => get_string('never'), - CMI5_UPDATE_EVERYDAY => get_string('everyday', 'cmi5launch'), - CMI5_UPDATE_EVERYTIME => get_string('everytime', 'cmi5launch')); -} - -/** - * Returns an array of the array of what grade options - * - * @return array an array of what grade options - */ -function cmi5_get_grade_method_array() { - return array (GRADE_AUS_CMI5 => get_string('GRADE_CMI5_AUS', 'cmi5launch'), - GRADE_HIGHEST_CMI5 => get_string('GRADE_HIGHEST_CMI5', 'cmi5launch'), - GRADE_AVERAGE_CMI5 => get_string('GRADE_AVERAGE_CMI5', 'cmi5launch'), - GRADE_SUM_CMI5 => get_string('GRADE_SUM_CMI5', 'cmi5launch')); -} - - - -/** - * Returns an array of the array of attempt options - * - * @return array an array of attempt options - */ -function cmi5_get_attempts_array() { - $attempts = array(0 => get_string('nolimit', 'cmi5launch'), - 1 => get_string('attempt1', 'cmi5launch')); - - for ($i = 2; $i <= 6; $i++) { - $attempts[$i] = get_string('attemptsx', 'cmi5launch', $i); - } - - return $attempts; -} - -/** - * Returns an array of the array of what grade options - * - * @return array an array of what grade options - */ -function cmi5_get_what_grade_array() { - return array (HIGHEST_ATTEMPT_CMI5 => get_string('HIGHEST_ATTEMPT_CMI5', 'cmi5launch'), - AVERAGE_ATTEMPT_CMI5 => get_string('AVERAGE_ATTEMPT_CMI5', 'cmi5launch'), - FIRST_ATTEMPT_CMI5 => get_string('FIRST_ATTEMPT_CMI5', 'cmi5launch'), - LAST_ATTEMPT_CMI5 => get_string('last_attempt_cmi5', 'cmi5launch')); -} - -/** - * Returns an array of the force attempt options - * - * @return array an array of attempt options - */ -function cmi5_get_forceattempt_array() { - return array(CMI5_FORCEATTEMPT_NO => get_string('no'), - CMI5_FORCEATTEMPT_ONCOMPLETE => get_string('forceattemptoncomplete', 'cmi5launch'), - CMI5_FORCEATTEMPT_ALWAYS => get_string('forceattemptalways', 'cmi5launch')); -} - +. + +/** + * Internal library of functions for module cmi5launch + * + * All the cmi5launch specific functions, needed to implement the module + * logic, should go here. Never include this file from your lib.php! + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @copyright 2024 Megan Bohland - added functions + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); +require_once("$CFG->dirroot/mod/cmi5launch/lib.php"); + +// Class for connecting to CMI5 player. +use mod_cmi5launch\local\cmi5_connectors; + +// Grade stuff. +define('MOD_CMI5LAUNCH_AUS_GRADE', '0'); +define('MOD_CMI5LAUNCH_GRADE_HIGHEST', '1'); +define('MOD_CMI5LAUNCH_GRADE_AVERAGE', '2'); +define('MOD_CMI5LAUNCH_GRADE_SUM', '3'); + +define('MOD_CMI5LAUNCH_HIGHEST_ATTEMPT', '0'); +define('MOD_CMI5LAUNCH_AVERAGE_ATTEMPT', '1'); +define('MOD_CMI5LAUNCH_FIRST_ATTEMPT', '2'); +define('MOD_CMI5LAUNCH_LAST_ATTEMPT', '3'); + +define('MOD_CMI5LAUNCH_FORCEATTEMPT_NO', 0); +define('MOD_CMI5LAUNCH_FORCEATTEMPT_ONCOMPLETE', 1); +define('MOD_CMI5LAUNCH_FORCEATTEMPT_ALWAYS', 2); + +define('MOD_CMI5LAUNCH_UPDATE_NEVER', '0'); +define('MOD_CMI5LAUNCH_UPDATE_EVERYDAY', '2'); +define('MOD_CMI5LAUNCH_UPDATE_EVERYTIME', '3'); + + + +/** + * Builds a cmi5 launch link for the current module and a given registration + * + * @package mod_cmi5launch + * @category cmi5 + * @param string/UUID $registrationid The cmi5 Registration UUID associated with the launch. + * @return string launch link including querystring. + */ +function cmi5launch_get_launch_url($registrationuuid, $auid) { + + global $cmi5launch, $CFG, $DB; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + $expiry = new DateTime('NOW'); + $xapiduration = $cmi5launchsettings['cmi5launchlrsduration']; + $expiry->add(new DateInterval('PT'.$xapiduration.'M')); + + $url = trim($cmi5launchsettings['cmi5launchlrsendpoint']); + + // Call the function to get the credentials from the LRS. + $basiclogin = trim($cmi5launchsettings['cmi5launchlrslogin']); + $basicpass = trim($cmi5launchsettings['cmi5launchlrspass']); + + switch ($cmi5launchsettings['cmi5launchlrsauthentication']) { + + // Learning Locker 1. + case "0": + $creds = cmi5launch_get_creds_learninglocker($cmi5launchsettings['cmi5launchlrslogin'], + $cmi5launchsettings['cmi5launchlrspass'], + $url, + $expiry, + $registrationuuid + ); + $basicauth = base64_encode($creds["contents"]["key"].":".$creds["contents"]["secret"]); + break; + + // Watershed. + case "2": + $creds = cmi5launch_get_creds_watershed ( + $basiclogin, + $basicpass, + $url, + $xapiduration * 60 + ); + $basicauth = base64_encode($creds["key"].":".$creds["secret"]); + break; + + default: + $basicauth = base64_encode($basiclogin.":".$basicpass); + break; + } + // Bring in functions from class cmi5Connector. + $connectors = new cmi5_connectors; + // Get retrieve URL function. + $cmi5launchretrieveurl = $connectors->cmi5launch_get_retrieve_url(); + + // return $rtnstring; +} + +/** + * Used with Learning Locker integration to fetch credentials from the LRS. + * This process is not part of the xAPI specification or the cmi5 launch spec. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $basiclogin login/key for the LRS + * @param string $basicpass pass/secret for the LRS + * @param string $url LRS endpoint URL + * @return array the response of the LRS (Note: not a cmi5 LRS Response object) + */ +function cmi5launch_get_creds_learninglocker($basiclogin, $basicpass, $url, $expiry, $registrationuuid) { + global $cmi5launch; + $actor = cmi5launch_getactor($cmi5launch->id); + $data = array( + 'scope' => array ('all'), + 'expiry' => $expiry->format(DATE_ATOM), + 'historical' => false, + 'actors' => array( + "objectType" => 'Person', + "name" => array($actor->getName()), + ), + 'auth' => $actor, + 'activity' => array( + $cmi5launch->cmi5activityid, + ), + 'registration' => $registrationuuid, + ); + + if (null !== $actor->getMbox()) { + $data['actors']['mbox'] = array($actor->getMbox()); + } else if (null !== $actor->getMbox_sha1sum()) { + $data['actors']['mbox_sha1sum'] = array($actor->getMbox_sha1sum()); + } else if (null !== $actor->getOpenid()) { + $data['actors']['openid'] = array($actor->getOpenid()); + } else if (null !== $actor->getAccount()) { + $data['actors']['account'] = array($actor->getAccount()); + } + + $streamopt = array( + 'ssl' => array( + 'verify-peer' => false, + ), + 'http' => array( + 'method' => 'POST', + 'ignore_errors' => false, + 'header' => array( + 'Authorization: Basic ' . base64_encode(trim($basiclogin) . ':' .trim($basicpass)), + 'Content-Type: application/json', + 'Accept: application/json, */*; q=0.01', + ), + 'content' => cmi5launch_myjson_encode($data), + ), + ); + + $streamparams = array(); + + $context = stream_context_create($streamopt); + + $stream = fopen(trim($url) . 'Basic/request'.'?'.http_build_query($streamparams, '', '&'), 'rb', false, $context); + + $returncode = explode(' ', $http_response_header[0]); + $returncode = (int)$returncode[1]; + + switch($returncode){ + case 200: + $ret = stream_get_contents($stream); + $meta = stream_get_meta_data($stream); + + if ($ret) { + $ret = json_decode($ret, true); + } + break; + default: // Error! + $ret = null; + $meta = $returncode; + break; + } + + return array( + 'contents' => $ret, + 'metadata' => $meta, + ); +} + +/** + * By default, PHP escapes slashes when encoding into JSON. This cause problems for cmi5, + * so this function unescapes the slashes after encoding. + * + * @package mod_cmi5launch + * @category cmi5 + * @param object or array $obj object or array encode to JSON + * @return string/JSON JSON encoded object or array + */ +function cmi5launch_myjson_encode($obj) { + return str_replace('\\/', '/', json_encode($obj)); +} + +/** + * Save data to the state. Note: registration is not used as this is a general bucket of data against the activity/learner. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $data data to store as document + * @param string $key id to store the document against + * @param string $etag etag associated with the document last time it was fetched (may be Null if document is new) + * @return cmi5 LRS Response + */ +function cmi5launch_get_global_parameters_and_save_state($data, $key, $etag) { + global $cmi5launch; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + $lrs = new \cmi5\RemoteLRS( + $cmi5launchsettings['cmi5launchlrsendpoint'], + $cmi5launchsettings['cmi5launchlrsversion'], + $cmi5launchsettings['cmi5launchlrslogin'], + $cmi5launchsettings['cmi5launchlrspass'] + ); + + return $lrs->saveState( + new \cmi5\Activity(array("id" => trim($cmi5launch->cmi5activityid))), + cmi5launch_getactor($cmi5launch->id), + $key, + cmi5launch_myjson_encode($data), + array( + 'etag' => $etag, + 'contentType' => 'application/json', + ) + ); +} + +/** + * Save data to the agent profile. + * Note: registration is not used as this is a general bucket of data against the activity/learner. + * Note: fetches a new etag before storing. Will ALWAYS overwrite existing contents of the document. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $data data to store as document + * @param string $key id to store the document against + * @return cmi5 LRS Response + */ +function cmi5launch_get_global_parameters_and_save_agentprofile($data, $key) { + global $cmi5launch; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + $lrs = new \cmi5\RemoteLRS( + $cmi5launchsettings['cmi5launchlrsendpoint'], + $cmi5launchsettings['cmi5launchlrsversion'], + $cmi5launchsettings['cmi5launchlrslogin'], + $cmi5launchsettings['cmi5launchlrspass'] + ); + + $getresponse = $lrs->retrieveAgentProfile(cmi5launch_getactor($cmi5launch->id), $key); + + $opts = array( + 'contentType' => 'application/json', + ); + if ($getresponse->success) { + $opts['etag'] = $getresponse->content->getEtag(); + } + + return $lrs->saveAgentProfile( + cmi5launch_getactor($cmi5launch->id), + $key, + cmi5launch_myjson_encode($data), + $opts + ); +} + +/** + * Get data from the state. Note: registration is not used as this is a general bucket of data against the activity/learner. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $key id to store the document against + * @return cmi5 LRS Response containing the response code and data or error message + */ +function cmi5launch_get_global_parameters_and_get_state($key) { + global $cmi5launch; + $cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + + $lrs = new \cmi5\RemoteLRS( + $cmi5launchsettings['cmi5launchlrsendpoint'], + $cmi5launchsettings['cmi5launchlrsversion'], + $cmi5launchsettings['cmi5launchlrslogin'], + $cmi5launchsettings['cmi5launchlrspass'] + ); + + return $lrs->retrieveState( + new \cmi5\Activity(array("id" => trim($cmi5launch->cmi5activityid))), + cmi5launch_getactor($cmi5launch->id), + $key + ); +} + + +/** + * Get the current lanaguage of the current user and return it as an RFC 5646 language tag + * + * @package mod_cmi5launch + * @category cmi5 + * @return string RFC 5646 language tag + */ + +function cmi5launch_get_moodle_langauge() { + $lang = current_language(); + $langarr = explode('_', $lang); + if (count($langarr) == 2) { + return $langarr[0].'-'.strtoupper($langarr[1]); + } else { + return $lang; + } +} + + +/** + * Used with Watershed integration to fetch credentials from the LRS. + * This process is not part of the xAPI specification or the cmi5 launch spec. + * + * @package mod_cmi5launch + * @category cmi5 + * @param string $login login for Watershed + * @param string $pass pass for Watershed + * @param string $endpoint LRS endpoint URL + * @param int $expiry number of seconds the credentials are required for + * @return array the response of the LRS (Note: not a cmi5 LRS Response object) + */ +function cmi5launch_get_creds_watershed($login, $pass, $endpoint, $expiry) { + global $CFG, $DB; + + // Process input parameters. + $auth = 'Basic '.base64_encode($login.':'.$pass); + + $explodedendpoint = explode ('/', $endpoint); + $wsserver = $explodedendpoint[0].'//'.$explodedendpoint[2]; + $orgid = $explodedendpoint[5]; + + // Create a session. + $createsessionresponse = cmi5launch_send_api_request( + $auth, + "POST", + $wsserver . "/api/organizations/" . $orgid . "/activity-providers/self/sessions", + [ + "content" => json_encode([ + "expireSeconds" => $expiry, + "scope" => "xapi:all", + ]), + ] + ); + + if ($createsessionresponse["status"] === 200) { + return [ + "key" => $createsessionresponse["content"]->key, + "secret" => $createsessionresponse["content"]->secret, + ]; + } else { + $reason = get_string('apCreationFailed', 'cmi5launch') + ." Status: ". $createsessionresponse["status"].". Response: ".$createsessionresponse["content"]->message; + throw new moodle_exception($reason, 'cmi5launch', ''); + } +} + +/* +@method sendAPIRequest Sends a request to the API. +@param {String} [$auth] Auth string +@param {String} [$method] Method of the request e.g. POST. +@param {String} [$url] URL to request +@param {Array} [$options] Array of optional properties. + @param {String} [content] Content of the request (should be JSON). +@return {Array} Details of the response + @return {String} [metadata] Raw metadata of the response + @return {String} [content] Raw content of the response + @return {Integer} [status] HTTP status code of the response e.g. 201 +*/ +function cmi5launch_send_api_request($auth, $method, $url) { + $options = func_num_args() === 4 ? func_get_arg(3) : array(); + + if (!isset($options['contentType'])) { + $options['contentType'] = 'application/json'; + } + + $http = array( + // We don't expect redirects. + 'max_redirects' => 0, + // This is here for some proxy handling. + 'request_fulluri' => 1, + // Switching this to false causes non-2xx/3xx status codes to throw exceptions. + // but we need to handle the "error" status codes ourselves in some cases. + 'ignore_errors' => true, + 'method' => $method, + 'header' => array(), + ); + + array_push($http['header'], 'Authorization: ' . $auth); + + if (($method === 'PUT' || $method === 'POST') && isset($options['content'])) { + $http['content'] = $options['content']; + array_push($http['header'], 'Content-length: ' . strlen($options['content'])); + array_push($http['header'], 'Content-Type: ' . $options['contentType']); + } + + $context = stream_context_create(array( 'http' => $http )); + + $fp = fopen($url, 'rb', false, $context); + + $content = ""; + + if (! $fp) { + return array ( + "metadata" => null, + "content" => $content, + "status" => 0, + ); + } + $metadata = stream_get_meta_data($fp); + $content = stream_get_contents($fp); + $responsecode = (int)explode(' ', $metadata["wrapper_data"][0])[1]; + + fclose($fp); + + if ($options['contentType'] == 'application/json') { + $content = json_decode($content); + } + + return array ( + "metadata" => $metadata, + "content" => $content, + "status" => $responsecode, + ); +} + +/** + * Returns an array of the array of update frequency options + * + * @return array an array of update frequency options + */ +function cmi5launch_get_updatefreq_array() { + return array(MOD_CMI5LAUNCH_UPDATE_NEVER => get_string('never'), + MOD_CMI5LAUNCH_UPDATE_EVERYDAY => get_string('everyday', 'cmi5launch'), + MOD_CMI5LAUNCH_UPDATE_EVERYTIME => get_string('everytime', 'cmi5launch')); +} + +/** + * Returns an array of the array of what grade options + * + * @return array an array of what grade options + */ +function cmi5launch_get_grade_method_array() { + return array ( + MOD_CMI5LAUNCH_GRADE_HIGHEST => get_string('mod_cmi5launch_grade_highest', 'cmi5launch'), + MOD_CMI5LAUNCH_GRADE_AVERAGE => get_string('mod_cmi5launch_grade_average', 'cmi5launch'), + ); +} + +/** + * Returns an array of the array of attempt options + * + * @return array an array of attempt options + */ +function cmi5launch_get_attempts_array() { + $attempts = array(0 => get_string('nolimit', 'cmi5launch'), + 1 => get_string('attempt1', 'cmi5launch')); + + for ($i = 2; $i <= 6; $i++) { + $attempts[$i] = get_string('attemptsx', 'cmi5launch', $i); + } + + return $attempts; +} + +/** + * Returns an array of the array of what grade options + * + * @return array an array of what grade options + */ +function cmi5launch_get_what_grade_array() { + return array (MOD_CMI5LAUNCH_HIGHEST_ATTEMPT => get_string('mod_cmi5launch_highest_attempt', 'cmi5launch'), + MOD_CMI5LAUNCH_AVERAGE_ATTEMPT => get_string('mod_cmi5launch_average_attempt', 'cmi5launch'), + MOD_CMI5LAUNCH_FIRST_ATTEMPT => get_string('mod_cmi5launch_first_attempt', 'cmi5launch'), + MOD_CMI5LAUNCH_LAST_ATTEMPT => get_string('mod_cmi5launch_last_attempt', 'cmi5launch')); +} + +/** + * Returns an array of the force attempt options + * + * @return array an array of attempt options + */ +function cmi5launch_get_forceattempt_array() { + return array(MOD_CMI5LAUNCH_FORCEATTEMPT_NO => get_string('no'), + MOD_CMI5LAUNCH_FORCEATTEMPT_ONCOMPLETE => get_string('forceattemptoncomplete', 'cmi5launch'), + MOD_CMI5LAUNCH_FORCEATTEMPT_ALWAYS => get_string('forceattemptalways', 'cmi5launch')); +} diff --git a/mod_form.php b/mod_form.php index fb9cf80..91c8e40 100755 --- a/mod_form.php +++ b/mod_form.php @@ -1,403 +1,319 @@ -. - -/** - * The main cmi5launch configuration form - * - * It uses the standard core Moodle formslib. For more info about them, please - * visit: http://docs.moodle.org/en/Development:lib/formslib.php - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -require_once($CFG->dirroot.'/course/moodleform_mod.php'); - -/** - * Module instance settings form - */ -class mod_cmi5launch_mod_form extends moodleform_mod { - - /** - * Defines forms elements - */ - public function definition() { - - global $CFG; - $cfgcmi5launch = get_config('cmi5launch'); - - $mform = $this->_form; - - // Adding the "general" fieldset, where all the common settings are showed. - $mform->addElement('header', 'general', get_string('general', 'form')); - - // Adding the standard "name" field. - $mform->addElement('text', 'name', get_string('cmi5launchname', 'cmi5launch'), array('size' => '64')); - if (!empty($CFG->formatstringstriptags)) { - $mform->setType('name', PARAM_TEXT); - } else { - $mform->setType('name', PARAM_CLEANHTML); - } - $mform->addRule('name', null, 'required', null, 'client'); - $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('name', 'cmi5launchname', 'cmi5launch'); - // Adding the standard "intro" and "introformat" fields. - $this->standard_intro_elements(); - - $mform->addElement('header', 'packageheading', get_string('cmi5packagetitle', 'cmi5launch')); - $mform->addElement( - 'static', - 'packagesettingsdescription', - get_string('cmi5packagetitle', 'cmi5launch'), - get_string('cmi5packagetext', 'cmi5launch') - ); - - // Start required Fields for Activity. - $mform->addElement('text', 'cmi5launchurl', get_string('cmi5launchurl', 'cmi5launch'), array('size' => '64')); - $mform->setType('cmi5launchurl', PARAM_TEXT); - $mform->addRule('cmi5launchurl', null, 'required', null, 'client'); - $mform->addRule('cmi5launchurl', get_string('maximumchars', '', 1333), 'maxlength', 1333, 'client'); - $mform->addHelpButton('cmi5launchurl', 'cmi5launchurl', 'cmi5launch'); - $mform->setDefault('cmi5launchurl', 'https://example.com/example-activity/index.html'); - - - $mform->addElement('text', 'cmi5activityid', get_string('cmi5activityid', 'cmi5launch'), array('size' => '64')); - $mform->setType('cmi5activityid', PARAM_TEXT); - $mform->addRule('cmi5activityid', null, 'required', null, 'client'); - $mform->addRule('cmi5activityid', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('cmi5activityid', 'cmi5activityid', 'cmi5launch'); - $mform->setDefault('cmi5activityid', 'https://example.com/example-activity'); - // End required Fields for Activity. - - // New local package upload. - - //Ok, this is making an array of filemanager - $filemanageroptions = array(); - $filemanageroptions['accepted_types'] = array('.zip'); - $filemanageroptions['maxbytes'] = 0; - $filemanageroptions['maxfiles'] = 1; - $filemanageroptions['subdirs'] = 0; - $mform->addElement( - 'filemanager', - 'packagefile', - get_string('cmi5package', - 'cmi5launch'), - null, - $filemanageroptions - ); - $mform->addHelpButton('packagefile', 'cmi5package', 'cmi5launch'); - - // Start advanced settings. - $mform->addElement('header', 'lrsheading', get_string('lrsheading', 'cmi5launch')); - - $mform->addElement( - 'static', - 'description', - get_string('lrsdefaults', 'cmi5launch'), - get_string('lrssettingdescription', 'cmi5launch') - ); - - // Override default LRS settings. - $mform->addElement('advcheckbox', 'overridedefaults', get_string('overridedefaults', 'cmi5launch')); - $mform->addHelpButton('overridedefaults', 'overridedefaults', 'cmi5launch'); - - // Add LRS endpoint. - $mform->addElement( - 'text', - 'cmi5launchlrsendpoint', - get_string('cmi5launchlrsendpoint', 'cmi5launch'), - array('size' => '64') - ); - $mform->setType('cmi5launchlrsendpoint', PARAM_TEXT); - $mform->addRule('cmi5launchlrsendpoint', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('cmi5launchlrsendpoint', 'cmi5launchlrsendpoint', 'cmi5launch'); - $mform->setDefault('cmi5launchlrsendpoint', $cfgcmi5launch->cmi5launchlrsendpoint); - $mform->disabledIf('cmi5launchlrsendpoint', 'overridedefaults'); - - // Add LRS Authentication. - $authoptions = array( - 1 => get_string('cmi5launchlrsauthentication_option_0', 'cmi5launch'), - 2 => get_string('cmi5launchlrsauthentication_option_1', 'cmi5launch'), - 0 => get_string('cmi5launchlrsauthentication_option_2', 'cmi5launch') - ); - $mform->addElement( - 'select', - 'cmi5launchlrsauthentication', - get_string('cmi5launchlrsauthentication', 'cmi5launch'), - $authoptions - ); - $mform->disabledIf('cmi5launchlrsauthentication', 'overridedefaults'); - $mform->addHelpButton('cmi5launchlrsauthentication', 'cmi5launchlrsauthentication', 'cmi5launch'); - $mform->getElement('cmi5launchlrsauthentication')->setSelected($cfgcmi5launch->cmi5launchlrsauthentication); - - $mform->addElement( - 'static', - 'description', - get_string('cmi5launchlrsauthentication_watershedhelp_label', 'cmi5launch'), - get_string('cmi5launchlrsauthentication_watershedhelp', 'cmi5launch') - ); - - // Add basic authorisation login. - $mform->addElement( - 'text', - 'cmi5launchlrslogin', - get_string('cmi5launchlrslogin', 'cmi5launch'), - array('size' => '64') - ); - $mform->setType('cmi5launchlrslogin', PARAM_TEXT); - $mform->addRule('cmi5launchlrslogin', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('cmi5launchlrslogin', 'cmi5launchlrslogin', 'cmi5launch'); - $mform->setDefault('cmi5launchlrslogin', $cfgcmi5launch->cmi5launchlrslogin); - $mform->disabledIf('cmi5launchlrslogin', 'overridedefaults'); - - // Add basic authorisation pass. - $mform->addElement( - 'password', - 'cmi5launchlrspass', - get_string('cmi5launchlrspass', 'cmi5launch'), - array('size' => '64') - ); - $mform->setType('cmi5launchlrspass', PARAM_TEXT); - $mform->addRule('cmi5launchlrspass', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('cmi5launchlrspass', 'cmi5launchlrspass', 'cmi5launch'); - $mform->setDefault('cmi5launchlrspass', $cfgcmi5launch->cmi5launchlrspass); - $mform->disabledIf('cmi5launchlrspass', 'overridedefaults'); - - // Duration. - $mform->addElement( - 'text', - 'cmi5launchlrsduration', - get_string('cmi5launchlrsduration', 'cmi5launch'), - array('size' => '64') - ); - $mform->setType('cmi5launchlrsduration', PARAM_TEXT); - $mform->addRule('cmi5launchlrsduration', get_string('maximumchars', '', 5), 'maxlength', 5, 'client'); - $mform->addHelpButton('cmi5launchlrsduration', 'cmi5launchlrsduration', 'cmi5launch'); - $mform->setDefault('cmi5launchlrsduration', $cfgcmi5launch->cmi5launchlrsduration); - $mform->disabledIf('cmi5launchlrsduration', 'overridedefaults'); - - // Actor account homePage. - $mform->addElement( - 'text', - 'cmi5launchcustomacchp', - get_string('cmi5launchcustomacchp', 'cmi5launch'), - array('size' => '64') - ); - $mform->setType('cmi5launchcustomacchp', PARAM_TEXT); - $mform->addRule('cmi5launchcustomacchp', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); - $mform->addHelpButton('cmi5launchcustomacchp', 'cmi5launchcustomacchp', 'cmi5launch'); - $mform->setDefault('cmi5launchcustomacchp', $cfgcmi5launch->cmi5launchcustomacchp); - $mform->disabledIf('cmi5launchcustomacchp', 'overridedefaults'); - - // Don't use email. - $mform->addElement( - 'advcheckbox', - 'cmi5launchuseactoremail', - get_string('cmi5launchuseactoremail', 'cmi5launch') - ); - $mform->addHelpButton('cmi5launchuseactoremail', 'cmi5launchuseactoremail', 'cmi5launch'); - $mform->setDefault('cmi5launchuseactoremail', $cfgcmi5launch->cmi5launchuseactoremail); - $mform->disabledIf('cmi5launchuseactoremail', 'overridedefaults'); - // End advanced settings. - - // Behavior settings. - $mform->addElement('header', 'behaviorheading', get_string('behaviorheading', 'cmi5launch')); - - // Allow multiple ongoing registrations. - $mform->addElement('advcheckbox', 'cmi5multipleregs', get_string('cmi5multipleregs', 'cmi5launch')); - $mform->addHelpButton('cmi5multipleregs', 'cmi5multipleregs', 'cmi5launch'); - $mform->setDefault('cmi5multipleregs', 1); - - // Add standard elements, common to all modules. - $this->standard_coursemodule_elements(); - // Add standard buttons, common to all modules. - $this->add_action_buttons(); - } - - public function add_completion_rules() { - $mform =& $this->_form; - - // Add Verb Id setting. - $verbgroup = array(); - $verbgroup[] =& $mform->createElement( - 'checkbox', - 'completionverbenabled', - ' ', - get_string('completionverb', 'cmi5launch') - ); - $verbgroup[] =& $mform->createElement('text', 'cmi5verbid', ' ', array('size' => '64')); - $mform->setType('cmi5verbid', PARAM_TEXT); - - $mform->addGroup($verbgroup, 'completionverbgroup', get_string('completionverbgroup', 'cmi5launch'), array(' '), false); - $mform->addGroupRule( - 'completionverbgroup', array( - 'cmi5verbid' => array( - array(get_string('maximumchars', '', 255), 'maxlength', 255, 'client') - ) - ) - ); - - $mform->addHelpButton('completionverbgroup', 'completionverbgroup', 'cmi5launch'); - $mform->disabledIf('cmi5verbid', 'completionverbenabled', 'notchecked'); - $mform->setDefault('cmi5verbid', 'http://adlnet.gov/expapi/verbs/completed'); - - // Add Completion Expiry Date setting. - $completiongroup = array(); - $completiongroup[] =& $mform->createElement( - 'checkbox', - 'completionexpiryenabled', - ' ', - get_string('completionexpiry', 'cmi5launch') - ); - $completiongroup[] =& $mform->createElement('text', 'cmi5expiry', ' ', array('size' => '64')); - $mform->setType('cmi5expiry', PARAM_TEXT); - - $mform->addGroup( - $completiongroup, - 'completionexpirygroup', - get_string('completionexpirygroup', 'cmi5launch'), - array(' '), - false - ); - $mform->addGroupRule( - 'completionexpirygroup', array( - 'cmi5expiry' => array( - array(get_string('maximumchars', '', 10), 'maxlength', 10, 'client') - ) - ) - ); - - $mform->addHelpButton('completionexpirygroup', 'completionexpirygroup', 'cmi5launch'); - $mform->disabledIf('cmi5expiry', 'completionexpiryenabled', 'notchecked'); - $mform->setDefault('cmi5expiry', '365'); - - return array('completionverbgroup', 'completionexpirygroup'); - } - - public function completion_rule_enabled($data) { - if (!empty($data['completionverbenabled']) && !empty($data['cmi5verbid'])) { - return true; - } - if (!empty($data['completionexpiryenabled']) && !empty($data['cmi5expiry'])) { - return true; - } - return false; - } - - public function get_data() { - $data = parent::get_data(); - if (!$data) { - return $data; - } - if (!empty($data->completionunlocked)) { - // Turn off completion settings if the checkboxes aren't ticked. - $autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC; - if (empty($data->completionverbenabled) || !$autocompletion) { - $data->cmi5verbid = ''; - } - if (empty($data->completionexpiryenabled) || !$autocompletion) { - $data->cmi5expiry = ''; - } - } - return $data; - } - - public function data_preprocessing(&$defaultvalues) { - parent::data_preprocessing($defaultvalues); - - global $DB; - - // Determine if default lrs settings were overriden. - if (!empty($defaultvalues['overridedefaults'])) { - if ($defaultvalues['overridedefaults'] == '1') { - // Retrieve activity lrs settings from DB. - $cmi5launchlrs = $DB->get_record( - 'cmi5launch_lrs', - array('cmi5launchid' => $defaultvalues['instance']), - $fields = '*', - IGNORE_MISSING - ); - $defaultvalues['cmi5launchlrsendpoint'] = $cmi5launchlrs->lrsendpoint; - $defaultvalues['cmi5launchlrsauthentication'] = $cmi5launchlrs->lrsauthentication; - $defaultvalues['cmi5launchcustomacchp'] = $cmi5launchlrs->customacchp; - $defaultvalues['cmi5launchuseactoremail'] = $cmi5launchlrs->useactoremail; - $defaultvalues['cmi5launchlrsduration'] = $cmi5launchlrs->lrsduration; - $defaultvalues['cmi5launchlrslogin'] = $cmi5launchlrs->lrslogin; - $defaultvalues['cmi5launchlrspass'] = $cmi5launchlrs->lrspass; - - } - } - - $draftitemid = file_get_submitted_draft_itemid('packagefile'); - file_prepare_draft_area( - $draftitemid, - $this->context->id, - 'mod_cmi5launch', - 'package', - 0, - array('subdirs' => 0, 'maxfiles' => 1) - ); - $defaultvalues['packagefile'] = $draftitemid; - - // Set up the completion checkboxes which aren't part of standard data. - // We also make the default value (if you turn on the checkbox) for those - // numbers to be 1, this will not apply unless checkbox is ticked. - if (!empty($defaultvalues['cmi5verbid'])) { - $defaultvalues['completionverbenabled'] = 1; - } else { - $defaultvalues['cmi5verbid'] = 'http://adlnet.gov/expapi/verbs/completed'; - } - if (!empty($defaultvalues['cmi5expiry'])) { - $defaultvalues['completionexpiryenabled'] = 1; - } else { - $defaultvalues['cmi5expiry'] = 365; - } - - } - // Validate the form elements after submitting (server-side). - public function validation($data, $files) { - global $CFG, $USER; - $errors = parent::validation($data, $files); - if (!empty($data['packagefile'])) { - $draftitemid = file_get_submitted_draft_itemid('packagefile'); - - file_prepare_draft_area( - $draftitemid, - $this->context->id, - 'mod_cmi5launch', - 'packagefilecheck', - null, - array('subdirs' => 0, 'maxfiles' => 1) - ); - - // Get file from users draft area. - $usercontext = context_user::instance($USER->id); - $fs = get_file_storage(); - $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false); - - if (count($files) < 1) { - return $errors; - } - $file = reset($files); - // Validate this cmi5 package. - $errors = array_merge($errors, cmi5launch_validate_package($file)); - } - return $errors; - } -} +. + +/** + * The main cmi5launch configuration form + * + * It uses the standard core Moodle formslib. For more info about them, please + * visit: http://docs.moodle.org/en/Development:lib/formslib.php + * + * @package mod_cmi5launch + * @copyright 2023 Bohland + * @copyright Based on work by 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/course/moodleform_mod.php'); + +/** + * Module instance settings form + */ +class mod_cmi5launch_mod_form extends moodleform_mod { + + /** + * Defines forms elements + */ + public function definition() { + + global $CFG; + $cfgcmi5launch = get_config('cmi5launch'); + + $mform = $this->_form; + + // Adding the "general" fieldset, where all the common settings are showed. + $mform->addElement('header', 'general', get_string('general', 'form')); + + // Adding the standard "name" field. + $mform->addElement('text', 'name', get_string('cmi5launchname', 'cmi5launch'), array('size' => '64')); + if (!empty($CFG->formatstringstriptags)) { + $mform->setType('name', PARAM_TEXT); + } else { + $mform->setType('name', PARAM_CLEANHTML); + } + $mform->addRule('name', null, 'required', null, 'client'); + $mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addHelpButton('name', 'cmi5launchname', 'cmi5launch'); + // Adding the standard "intro" and "introformat" fields. + $this->standard_intro_elements(); + + $mform->addElement('header', 'packageheading', get_string('cmi5packagetitle', 'cmi5launch')); + $mform->addElement( + 'static', + 'packagesettingsdescription', + get_string('cmi5packagetitle', 'cmi5launch'), + get_string('cmi5packagetext', 'cmi5launch') + ); + + // Start required Fields for Activity. + /* + $mform->addElement('text', 'cmi5launchurl', get_string('cmi5launchurl', 'cmi5launch'), array('size' => '64')); + $mform->setType('cmi5launchurl', PARAM_TEXT); + $mform->addRule('cmi5launchurl', null, 'required', null, 'client'); + $mform->addRule('cmi5launchurl', get_string('maximumchars', '', 1333), 'maxlength', 1333, 'client'); + $mform->addHelpButton('cmi5launchurl', 'cmi5launchurl', 'cmi5launch'); + $mform->setDefault('cmi5launchurl', 'https://example.com/example-activity/index.html'); + + $mform->addElement('text', 'cmi5activityid', get_string('cmi5activityid', 'cmi5launch'), array('size' => '64')); + $mform->setType('cmi5activityid', PARAM_TEXT); + $mform->addRule('cmi5activityid', null, 'required', null, 'client'); + $mform->addRule('cmi5activityid', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addHelpButton('cmi5activityid', 'cmi5activityid', 'cmi5launch'); + $mform->setDefault('cmi5activityid', 'https://example.com/example-activity'); + */ + // End required Fields for Activity. + + // New local package upload. + + // This is making an array of filemanager. + $filemanageroptions = array(); + $filemanageroptions['accepted_types'] = array('.zip'); + $filemanageroptions['maxbytes'] = 0; + $filemanageroptions['maxfiles'] = 1; + $filemanageroptions['subdirs'] = 0; + $mform->addElement( + 'filemanager', + 'packagefile', + get_string('cmi5package', + 'cmi5launch'), + null, + $filemanageroptions + ); + $mform->addHelpButton('packagefile', 'cmi5package', 'cmi5launch'); + + // Start advanced settings. + $mform->addElement('header', 'lrsheading', get_string('lrsheading', 'cmi5launch')); + + // Actor account homePage. + $mform->addElement( + 'text', + 'cmi5launchcustomacchp', + get_string('cmi5launchcustomacchp', 'cmi5launch'), + array('size' => '64') + ); + $mform->setType('cmi5launchcustomacchp', PARAM_TEXT); + $mform->addRule('cmi5launchcustomacchp', get_string('maximumchars', '', 255), 'maxlength', 255, 'client'); + $mform->addHelpButton('cmi5launchcustomacchp', 'cmi5launchcustomacchp', 'cmi5launch'); + $mform->setDefault('cmi5launchcustomacchp', $cfgcmi5launch->cmi5launchcustomacchp); + $mform->disabledIf('cmi5launchcustomacchp', 'overridedefaults'); + + // Don't use email. + $mform->addElement( + 'advcheckbox', + 'cmi5launchuseactoremail', + get_string('cmi5launchuseactoremail', 'cmi5launch') + ); + $mform->addHelpButton('cmi5launchuseactoremail', 'cmi5launchuseactoremail', 'cmi5launch'); + $mform->setDefault('cmi5launchuseactoremail', $cfgcmi5launch->cmi5launchuseactoremail); + $mform->disabledIf('cmi5launchuseactoremail', 'overridedefaults'); + // End advanced settings. + + // Behavior settings. + $mform->addElement('header', 'behaviorheading', get_string('behaviorheading', 'cmi5launch')); + + // Allow multiple ongoing registrations. + $mform->addElement('advcheckbox', 'cmi5multipleregs', get_string('cmi5multipleregs', 'cmi5launch')); + $mform->addHelpButton('cmi5multipleregs', 'cmi5multipleregs', 'cmi5launch'); + $mform->setDefault('cmi5multipleregs', 1); + + // Add standard elements, common to all modules. + $this->standard_coursemodule_elements(); + // Add standard buttons, common to all modules. + $this->add_action_buttons(); + } + + public function add_completion_rules() { + $mform =& $this->_form; + + // Add Verb Id setting. + $verbgroup = array(); + $verbgroup[] =& $mform->createElement( + 'checkbox', + 'completionverbenabled', + ' ', + get_string('completionverb', 'cmi5launch') + ); + $verbgroup[] =& $mform->createElement('text', 'cmi5verbid', ' ', array('size' => '64')); + $mform->setType('cmi5verbid', PARAM_TEXT); + + $mform->addGroup($verbgroup, 'completionverbgroup', get_string('completionverbgroup', 'cmi5launch'), array(' '), false); + $mform->addGroupRule( + 'completionverbgroup', array( + 'cmi5verbid' => array( + array(get_string('maximumchars', '', 255), 'maxlength', 255, 'client'), + ), + ) + ); + + $mform->addHelpButton('completionverbgroup', 'completionverbgroup', 'cmi5launch'); + $mform->disabledIf('cmi5verbid', 'completionverbenabled', 'notchecked'); + $mform->setDefault('cmi5verbid', 'http://adlnet.gov/expapi/verbs/completed'); + + // Add Completion Expiry Date setting. + $completiongroup = array(); + $completiongroup[] =& $mform->createElement( + 'checkbox', + 'completionexpiryenabled', + ' ', + get_string('completionexpiry', 'cmi5launch') + ); + $completiongroup[] =& $mform->createElement('text', 'cmi5expiry', ' ', array('size' => '64')); + $mform->setType('cmi5expiry', PARAM_TEXT); + + $mform->addGroup( + $completiongroup, + 'completionexpirygroup', + get_string('completionexpirygroup', 'cmi5launch'), + array(' '), + false + ); + $mform->addGroupRule( + 'completionexpirygroup', array( + 'cmi5expiry' => array( + array(get_string('maximumchars', '', 10), 'maxlength', 10, 'client'), + ), + ) + ); + + $mform->addHelpButton('completionexpirygroup', 'completionexpirygroup', 'cmi5launch'); + $mform->disabledIf('cmi5expiry', 'completionexpiryenabled', 'notchecked'); + $mform->setDefault('cmi5expiry', '365'); + + return array('completionverbgroup', 'completionexpirygroup'); + } + + public function completion_rule_enabled($data) { + if (!empty($data['completionverbenabled']) && !empty($data['cmi5verbid'])) { + return true; + } + if (!empty($data['completionexpiryenabled']) && !empty($data['cmi5expiry'])) { + return true; + } + return false; + } + + public function get_data() { + $data = parent::get_data(); + if (!$data) { + return $data; + } + if (!empty($data->completionunlocked)) { + // Turn off completion settings if the checkboxes aren't ticked. + $autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC; + if (empty($data->completionverbenabled) || !$autocompletion) { + $data->cmi5verbid = ''; + } + if (empty($data->completionexpiryenabled) || !$autocompletion) { + $data->cmi5expiry = ''; + } + } + return $data; + } + + public function data_preprocessing(&$defaultvalues) { + parent::data_preprocessing($defaultvalues); + + global $DB; + + // Determine if default lrs settings were overriden. + if (!empty($defaultvalues['overridedefaults'])) { + if ($defaultvalues['overridedefaults'] == '1') { + // Retrieve activity lrs settings from DB. + $cmi5launchlrs = $DB->get_record( + 'cmi5launch_lrs', + array('cmi5launchid' => $defaultvalues['instance']), + $fields = '*', + IGNORE_MISSING + ); + $defaultvalues['cmi5launchlrsendpoint'] = $cmi5launchlrs->lrsendpoint; + $defaultvalues['cmi5launchlrsauthentication'] = $cmi5launchlrs->lrsauthentication; + $defaultvalues['cmi5launchcustomacchp'] = $cmi5launchlrs->customacchp; + $defaultvalues['cmi5launchuseactoremail'] = $cmi5launchlrs->useactoremail; + $defaultvalues['cmi5launchlrsduration'] = $cmi5launchlrs->lrsduration; + $defaultvalues['cmi5launchlrslogin'] = $cmi5launchlrs->lrslogin; + $defaultvalues['cmi5launchlrspass'] = $cmi5launchlrs->lrspass; + + } + } + + $draftitemid = file_get_submitted_draft_itemid('packagefile'); + file_prepare_draft_area( + $draftitemid, + $this->context->id, + 'mod_cmi5launch', + 'package', + 0, + array('subdirs' => 0, 'maxfiles' => 1) + ); + $defaultvalues['packagefile'] = $draftitemid; + + // Set up the completion checkboxes which aren't part of standard data. + // We also make the default value (if you turn on the checkbox) for those + // numbers to be 1, this will not apply unless checkbox is ticked. + if (!empty($defaultvalues['cmi5verbid'])) { + $defaultvalues['completionverbenabled'] = 1; + } else { + $defaultvalues['cmi5verbid'] = 'http://adlnet.gov/expapi/verbs/completed'; + } + if (!empty($defaultvalues['cmi5expiry'])) { + $defaultvalues['completionexpiryenabled'] = 1; + } else { + $defaultvalues['cmi5expiry'] = 365; + } + + } + // Validate the form elements after submitting (server-side). + public function validation($data, $files) { + global $CFG, $USER; + $errors = parent::validation($data, $files); + if (!empty($data['packagefile'])) { + $draftitemid = file_get_submitted_draft_itemid('packagefile'); + + file_prepare_draft_area( + $draftitemid, + $this->context->id, + 'mod_cmi5launch', + 'packagefilecheck', + null, + array('subdirs' => 0, 'maxfiles' => 1) + ); + + // Get file from users draft area. + $usercontext = context_user::instance($USER->id); + $fs = get_file_storage(); + $files = $fs->get_area_files($usercontext->id, 'user', 'draft', $draftitemid, 'id', false); + + if (count($files) < 1) { + return $errors; + } + $file = reset($files); + // Validate this cmi5 package. + $errors = array_merge($errors, cmi5launch_validate_package($file)); + } + return $errors; + } +} diff --git a/report.php b/report.php old mode 100644 new mode 100755 index 6f119a9..fc768cc --- a/report.php +++ b/report.php @@ -1,6 +1,346 @@ . -echo $OUTPUT->heading("This is the report page."); -?> \ No newline at end of file +/** + * The report page. Displays either the course grades for teacher, or user grades for student. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +use mod_cmi5launch\local\grade_helpers; + +require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +require('header.php'); +require_once($CFG->libdir.'/tablelib.php'); +require_once($CFG->dirroot.'/mod/cmi5launch/locallib.php'); +require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->dirroot. '/reportbuilder/classes/local/report/column.php'); + +require_login($course, false, $cm); + +define('CMI5LAUNCH_REPORT_DEFAULT_PAGE_SIZE', 20); +define('CMI5LAUNCH_REPORT_ATTEMPTS_ALL_STUDENTS', 0); +define('CMI5LAUNCH_REPORT_ATTEMPTS_STUDENTS_WITH', 1); +define('CMI5LAUNCH_REPORT_ATTEMPTS_STUDENTS_WITH_NO', 2); + +global $cmi5launch, $cmi5launchsettings, $USER, $DB; +// Reload course information. +$cm = get_coursemodule_from_id('cmi5launch', $id, 0, false, MUST_EXIST); +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); + +// Activity Module ID. +$id = required_param('id', PARAM_INT); +$download = optional_param('download', '', PARAM_RAW); +$mode = optional_param('mode', '', PARAM_ALPHA); // Report mode. +// Item number, may be != 0 for activities that allow more than one grade per user. +// Itemnumber is from the moodle grade_items table, which holds info on the grade item +// itself such as course, mod type, activity title, etc. +$itemnumber = optional_param('itemnumber', 0, PARAM_INT); +// Currently logged in user. +$userid = optional_param('userid', 0, PARAM_INT); +// The itemid is from the Moodle grade_grades table, corresponds to a grade column (such as +// one cmi5launch or other activity part of a course). +$itemid = optional_param('itemid', 0, PARAM_INT); +// This is the gradeid, which is the id in the same grade_grades table. A row entry, a particular users info. +$gradeid = optional_param('gradeid', 0, PARAM_INT); +// Active page. +$page = optional_param('page', 0, PARAM_INT); +$showall = optional_param('showall', null, PARAM_BOOL); +$cmid = optional_param('cmid', null, PARAM_INT); + +$url = new moodle_url('/mod/cmi5launch/report.php'); +if ($page !== 0) { + $url->param('page', $page); +} else if ($showall) { + $url->param('showall', $showall); +} +$requestbody = file_get_contents('php://input'); + +$contextmodule = context_module::instance($cm->id); + +// Setup page url. +$PAGE->set_url($url); +$PAGE->set_pagelayout('report'); +$PAGE->requires->jquery(); + +// Functions from other classes. +$gradehelpers = new grade_helpers; + +$updategrades = $gradehelpers->get_cmi5launch_check_user_grades_for_updates(); +$highestgrade = $gradehelpers->get_cmi5launch_highest_grade(); +$averagegrade = $gradehelpers->get_cmi5launch_average_grade(); + + +$cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + +// Activate the secondary nav tab. +navigation_node::override_active_url(new moodle_url('/mod/cmi5launch/report.php', ['id' => $id])); + +?> + + $contextmodule, + 'other' => array( + 'scormid' => $scorm->id, + 'mode' => $mode + ) +)); + +$event->add_record_snapshot('course_modules', $cm); +$event->add_record_snapshot('scorm', $scorm); +$event->trigger(); +*/ + +// Print the page header. +if (empty($noheader)) { + + $strreport = get_string('report', 'cmi5launch'); + $strattempt = get_string('attempt', 'cmi5launch'); + + // Setup the page + $PAGE->set_title("$course->shortname: ".format_string($course->id)); + $PAGE->set_heading($course->fullname); + $PAGE->activityheader->set_attrs([ + 'hidecompletion' => true, + 'description' => '', + ]); + $PAGE->navbar->add($strreport, new moodle_url('/mod/cmi5launch/report.php', array('id' => $cm->id))); + + echo $OUTPUT->header(); +} + +// Create table to display on page. +$reporttable = new \flexible_table('mod-cmi5launch-report'); + +$columns[] = 'AU Title'; +$headers[] = get_string('autitle', 'cmi5launch'); + +// The table is always the same, but the amount of users shown varies. +// If user has capability, they can see all users. +if (has_capability('mod/cmi5launch:viewgrades', $context)) { + + // Get enrolled users for this course. + $users = get_enrolled_users($contextmodule); + + + foreach ($users as $user) { + + // Call updategrades to ensure all grades are up to date before view. + $updategrades($user); + + // Each user needs their own column. + $headers[] = $user->username; + $columns[] = $user->username; + + // Backbutton goes to grader. + $backurl = $CFG->wwwroot . '/grade/report/grader/index.php' . '?id=' .$cmi5launch->course; + } +} else { + + // If the user does not have the correct capability then we are looking at a specific user, + // who is not a teacher and needs to see only their grades. + // Retrieve that user from DB. + $user = $DB->get_record('user', array('id' => $USER->id)); + + // Make sure their grades are up to date. + $updategrades($user); + // Add user to array for processing, the table build expects an array of users with their id as their index. + $users[$user->id] = $user; + + // Each user needs their own column. + $headers[] = $user->username; + $columns[] = $user->username; + + // Where back button goes. + $backurl = $CFG->wwwroot . '/grade/report/user/index.php' . '?id=' .$cmi5launch->course; +} + + // Create back button. + ?> +
+ + +
+ get_record('cmi5launch', array('id' => $cmi5launch->id)); + + // Retrieve AU ids for this course. + $aus = json_decode($record->aus, true); + + // Separate AUS into array on id. + $auschunked = array_chunk($aus, 13, true); + + // Add the columns and headers to the table. + $reporttable->define_columns($columns); + $reporttable->define_headers($headers); + $reporttable->define_baseurl($PAGE->url); + // Setup table (this needs to be done before data is added). + $reporttable->setup(); + + // Unfortunately, array_chunk nests our AU's, we need to use an index to grab them. + // I have not found a way to reliably separate AUs from DB without nesting -MB. + foreach ($auschunked[0] as $au) { + + // Array to hold data for rows. + $rowdata = array(); + + // For each AU, iterate through each user. + foreach ($users as $user) { + + // Array to hold info for next page, that will be placed into buttons for user to click. + $infofornextpage = array(); + + // Retrieve the current au id, this is always unique and will help with retrieving the + // student grades. It is the uniquie id cmi5 spec id. + $aulmsid = $au[0]['lmsId']; + $infofornextpage[] = $aulmsid; + // Grab the current title of the AU for the row header, also to be sent to next page. + $currenttitle = $au[0]['title'][0]['text']; + $infofornextpage[] = $currenttitle; + $rowdata["AU Title"] = ($currenttitle); + + $username = $user->username; + + // Retrieve users specific info for this course. + $userrecord = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $user->id]); + + // Retrieve grade type from settings. + $gradetype = $cmi5launchsettings["grademethod"]; + + // User record may be null if user has not participated in course yet. + if ($userrecord == null) { + + $userscore = " "; + $infofornextpage[] = null; + } else { + + // Retrieve the users grades for this course. + $usergrades = json_decode($userrecord->ausgrades, true); + + // These are the AUS we want to send on if clicked, the more specific ids (THIS users AU ids). + $currentauids = $userrecord->aus; + $infofornextpage[] = $currentauids; + + $userscore = ""; + + if (!$usergrades == null) { + + // Now compare the usergrades array keys to lmsid of current au, if + // it matches then we want to display, that's what userscore is. + if (array_key_exists($aulmsid, $usergrades)) { + + // If it is, we want it's info which should be title => grade(s). + $auinfo = array(); + $auinfo = $usergrades[$aulmsid]; + $augrades = $auinfo[$currenttitle]; + + // This is just to display, it calculates here so it doesn't effect the base array stored for AU. + switch($gradetype){ + + // MOD_CMI5LAUNCH_AUS_GRADE' = '0'. + // MOD_CMI5LAUNCH_GRADE_HIGHEST' = '1'. + // MOD_CMI5LAUNCH_GRADE_AVERAGE', = '2'. + // MOD_CMI5LAUNCH_GRADE_SUM', = '3'. + case 1: + $userscore = strval($highestgrade($augrades)); + break; + case 2: + // We need to update rawgrade not all of grades, that wipes out the array format it needs. + $userscore = strval($averagegrade($augrades)); + break; + } + + // Remove [] from userscore if they are there. + $toremove = array("[", "]"); + if ($userscore != null && str_contains($userscore, "[")) { + $userscore = str_replace($toremove, "", $userscore); + } + } + + } else { + $userscore = "N/A"; + } + + } + // Add the userid to info for next page. + $infofornextpage[] = $user->id; + + // Convert their grade to string to be passed into html button. + $userscoreasstring = strval($userscore); + + // Encode to send to next page, because it has to go as a string and pass through the Javascript function. + $sendtopage = base64_encode(json_encode($infofornextpage, JSON_HEX_QUOT)); + + // Build the button to be displayed. It appears as the users score, but is also a link + // to session_report if user wants to break down the score. + $button = "" + . $userscoreasstring . ""; + + // Add the button to the row data under the correct user. + $rowdata[$username] = ($button); + } + + // Add the row data to the table. + $reporttable->add_data_keyed($rowdata); + } + + // Finish building table now that all data is passed in. + $reporttable->get_page_start(); + $reporttable->get_page_size(); + $reporttable->finish_output(); +?> + +
+ + +
+footer(); +} diff --git a/session_report.php b/session_report.php new file mode 100755 index 0000000..738afdd --- /dev/null +++ b/session_report.php @@ -0,0 +1,287 @@ +. + +/** + * Class to report on sessions grades. + * + * @copyright 2023 Megan Bohland + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use mod_cmi5launch\local\au_helpers; +use mod_cmi5launch\local\session_helpers; + +require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +require('header.php'); +require_once($CFG->libdir.'/tablelib.php'); +require_once($CFG->dirroot.'/mod/cmi5launch/locallib.php'); +require_once($CFG->libdir.'/formslib.php'); +require_once($CFG->dirroot. '/reportbuilder/classes/local/report/column.php'); + +require_login($course, false, $cm); + +define('CMI5LAUNCH_REPORT_DEFAULT_PAGE_SIZE', 20); +define('CMI5LAUNCH_REPORT_ATTEMPTS_ALL_STUDENTS', 0); +define('CMI5LAUNCH_REPORT_ATTEMPTS_STUDENTS_WITH', 1); +define('CMI5LAUNCH_REPORT_ATTEMPTS_STUDENTS_WITH_NO', 2); +$PAGE->requires->jquery(); + +global $cmi5launch, $CFG; + +$cmi5launchsettings = cmi5launch_settings($cmi5launch->id); + +// Retrieve the grade type to use to calculate the overall score. +$gradetype = $cmi5launchsettings["grademethod"]; + +// External classes and functions. +$sessionhelper = new session_helpers; +$aushelpers = new au_helpers; + +$updatesession = $sessionhelper->cmi5launch_get_update_session(); +$getaus = $aushelpers->get_cmi5launch_retrieve_aus_from_db(); + +// Activity Module ID. +$id = required_param('id', PARAM_INT); + +// Retrieve the user and AU specific info from previous page. +$fromreportpage = base64_decode(required_param('session_report', PARAM_TEXT) ); +// Break it into array. +$fromreportpage = json_decode($fromreportpage, true); + +// The args from the previous page come through in this order: +// 0: cmi5 unique AU ID. +// 1: AU title. +// 2: AU IDs to retrieve AUs from DB for this user. +// 3: The user id, the one whose grades we need. +$cmi5idprevpage = $fromreportpage[0]; +$currenttitle = $fromreportpage[1]; +$auidprevpage = $fromreportpage[2]; +$userid = $fromreportpage[3]; + +// Retrieve the course module. +$cm = get_coursemodule_from_id('cmi5launch', $id, 0, false, MUST_EXIST); +$course = $DB->get_record('course', array('id' => $cm->course), '*', MUST_EXIST); +$contextmodule = context_module::instance($cm->id); + +// Set page url. +$url = new moodle_url('/mod/cmi5launch/session_report.php'); +$url->param('id', $id); + +// Activate the secondary nav tab. +navigation_node::override_active_url(new moodle_url('/mod/cmi5launch/classes/local/session_report.php', ['id' => $id])); + +if (!empty($download)) { + $noheader = true; +} + +// Print the page header. +if (empty($noheader)) { + + $strreport = get_string('report', 'cmi5launch'); + + // Setup the page. + $PAGE->set_url($url); + $PAGE->set_pagelayout('report'); + $PAGE->set_title("$course->shortname: ".format_string($cm->name)); + $PAGE->set_heading($course->fullname); + $PAGE->activityheader->set_attrs([ + 'hidecompletion' => true, + 'description' => '', + ]); + $PAGE->navbar->add($strreport, new moodle_url('/mod/cmi5launch/report.php', array('id' => $cm->id))); + + echo $OUTPUT->header(); +} + +// Back button. +?> +
+ + +
+get_record('user', array('id' => $userid)); + +// Create tables to display on page. +// This is the main table with session info. +$table = new \flexible_table('mod-cmi5launch-report'); +// This table holds the overall score, showing the grading type. +$scoretable = new \flexible_table('mod-cmi5launch-report'); + +$columns[] = 'Attempt'; +$headers[] = get_string('attempt', 'cmi5launch'); +$columns[] = 'Started'; +$headers[] = get_string('started', 'cmi5launch'); +$columns[] = 'Finished'; +$headers[] = get_string('last', 'cmi5launch'); +$columns[] = 'Status'; +$headers[] = "AU Satisfied Status"; +$columns[] = 'Score'; +$headers[] = get_string('score', 'cmi5launch'); + +$scorecolumns = array(); +$scoreheaders = array(); + +// Add the columns and headers to the table. +$table->define_columns($columns); +$table->define_headers($headers); +$table->define_baseurl($PAGE->url); + +// Decode and put AU ids in array. +$auids = (json_decode($auidprevpage, true) ); + +// For each AU id, find the one that matches our auid from previous page, this is the record we want. +foreach ($auids as $key => $auid) { + + // Retrieve record from table. + $au = $DB->get_record('cmi5launch_aus', ['id' => $auid]); + + if ($au->lmsid == $cmi5idprevpage) { + + // If the id matches it is the record we want. + $aurecord = $au; + } +} + +if ($aurecord->sessions != null || false) { + // Retrieve session ids for this course. + $sessions = json_decode($aurecord->sessions, true); + + // Start Attempts at one. + $attempt = 1; + + // Arrays to hold row info. + $rowdata = array(); + $scorerow = array(); + + // An array to hold grades for max or mean scoring. + $sessionscores = array(); + // Set table up, this needs to be done before rows added. + $table->setup(); + $austatus = ""; + + // There may be more than one session. + foreach ($sessions as $sessionid) { + + $session = $updatesession($sessionid, $cmi5launch->id, $user); + // Add score to array for AU. + $sessionscores[] = $session->score; + + // Retrieve createdAt and format. + $date = new DateTime($session->createdat, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + $datestart = $date->format('D d M Y H:i:s'); + + // Retrieve lastRequestTime and format. + $date = new DateTime($session->lastrequesttime, new DateTimeZone('US/Eastern')); + $date->setTimezone(new DateTimeZone('America/New_York')); + $datefinish = $date->format('D d M Y H:i:s'); + + // The users sessions. + $usersession = $DB->get_record('cmi5launch_sessions', array('sessionid' => $sessionid)); + + // Add row data. + $rowdata["Attempt"] = "Attempt " . $attempt; + $rowdata["Started"] = $datestart; + $rowdata["Finished"] = $datefinish; + + // AUs moveon specification. + $aumoveon = $aurecord->moveon; + + // 0 is no 1 is yes, these are from CMI5 player + $iscompleted = $session->iscompleted; + $ispassed = $session->ispassed; + $isfailed = $session->isfailed; + $isterminated = $session->isterminated; + $isabandoned = $session->isabandoned; + + // If it's been attempted but no moveon value. + if ($iscompleted == 1) { + + $austatus = "Completed"; + + if ($ispassed == 1) { + $austatus = "Completed and Passed"; + } + if ($isfailed == 1) { + $austatus = "Completed and Failed"; + } + } + // Update table. + $scorecolumns[] = "Attempt " . $attempt; + $scoreheaders[] = "Attempt " . $attempt; + $scorerow["Attempt " . $attempt] = $usersession->score; + + switch ($gradetype) { + + // 'MOD_CMI5LAUNCH_AUS_GRADE' = '0'). + // 'MOD_CMI5LAUNCH_GRADE_HIGHEST' = '1'. + // 'MOD_CMI5LAUNCH_GRADE_AVERAGE', = '2'. + // 'MOD_CMI5LAUNCH_GRADE_SUM', = '3'. + + case 1: + $grade = "Highest"; + $overall = max($sessionscores); + break; + case 2: + $grade = "Average"; + $overall = (array_sum($sessionscores) / count($sessionscores)); + break; + } + + $scorerow["Grading type"] = $grade; + + $attempt++; + + $rowdata["Status"] = $austatus; + + $rowdata["Score"] = $usersession->score; + + $table->add_data_keyed($rowdata); + } +} + +// Display the grading type, highest, avg, etc. +$scorecolumns[] = 'Grading type'; +$scoreheaders[] = 'Gradingtype'; +$scorecolumns[] = 'Overall Score'; +$scoreheaders[] = 'Overall Score'; + +// Session score may be null or empty. +if (!empty($sessionscores)) { + + $scorerow["Overall Score"] = $overall; +} + +// Setup score table. +$scoretable->define_columns($scorecolumns); +$scoretable->define_headers($scoreheaders); +$scoretable->define_baseurl($PAGE->url); +$scoretable->setup(); +$scoretable->add_data_keyed($scorerow); +$scoretable->add_data_keyed("SCORE"); + +$table->get_page_start(); +$table->get_page_size(); + +$scoretable->get_page_start(); +$scoretable->get_page_size(); + +$table->add_separator(); +$scoretable->finish_output(); +$table->finish_output(); diff --git a/settings.php b/settings.php index 93d70c0..8718ee5 100755 --- a/settings.php +++ b/settings.php @@ -1,139 +1,152 @@ -. - -/* For global cmi5 settings */ - -defined('MOODLE_INTERNAL') || die; - -if ($ADMIN->fulltree) { - require_once($CFG->dirroot . '/mod/cmi5launch/locallib.php'); - require_once($CFG->dirroot . '/mod/cmi5launch/settingslib.php'); - - //MB - //From scorm grading stuff - $yesno = array(0 => get_string('no'), - 1 => get_string('yes')); - - // Default display settings. - $settings->add(new admin_setting_heading('cmi5launch/cmi5launchlrsfieldset', - get_string('cmi5launchlrsfieldset', 'cmi5launch'), - get_string('cmi5launchlrsfieldset_help', 'cmi5launch'))); - - $settings->add(new admin_setting_configtext_mod_cmi5launch('cmi5launch/cmi5launchlrsendpoint', - get_string('cmi5launchlrsendpoint', 'cmi5launch'), - get_string('cmi5launchlrsendpoint_help', 'cmi5launch'), - get_string('cmi5launchlrsendpoint_default', 'cmi5launch'), PARAM_URL)); - - $options = array( - 1 => get_string('cmi5launchlrsauthentication_option_0', 'cmi5launch'), - 2 => get_string('cmi5launchlrsauthentication_option_1', 'cmi5launch'), - 0 => get_string('cmi5launchlrsauthentication_option_2', 'cmi5launch') - ); - // Note the numbers above are deliberately mis-ordered for reasons of backwards compatibility with older settings. - - $setting = new admin_setting_configselect('cmi5launch/cmi5launchlrsauthentication', - get_string('cmi5launchlrsauthentication', 'cmi5launch'), - get_string('cmi5launchlrsauthentication_help', 'cmi5launch').'
' - .get_string('cmi5launchlrsauthentication_watershedhelp', 'cmi5launch') - , 1, $options); - $settings->add($setting); - - $setting = new admin_setting_configtext('cmi5launch/cmi5launchlrslogin', - get_string('cmi5launchlrslogin', 'cmi5launch'), - get_string('cmi5launchlrslogin_help', 'cmi5launch'), - get_string('cmi5launchlrslogin_default', 'cmi5launch')); - $settings->add($setting); - - $setting = new admin_setting_configtext('cmi5launch/cmi5launchlrspass', - get_string('cmi5launchlrspass', 'cmi5launch'), - get_string('cmi5launchlrspass_help', 'cmi5launch'), - get_string('cmi5launchlrspass_default', 'cmi5launch')); - $settings->add($setting); - - $settings->add(new admin_setting_configtext('cmi5launch/cmi5launchlrsduration', - get_string('cmi5launchlrsduration', 'cmi5launch'), - get_string('cmi5launchlrsduration_help', 'cmi5launch'), - get_string('cmi5launchlrsduration_default', 'cmi5launch'))); - - $settings->add(new admin_setting_configtext('cmi5launch/cmi5launchcustomacchp', - get_string('cmi5launchcustomacchp', 'cmi5launch'), - get_string('cmi5launchcustomacchp_help', 'cmi5launch'), - get_string('cmi5launchcustomacchp_default', 'cmi5launch'))); - - $settings->add(new admin_setting_configcheckbox('cmi5launch/cmi5launchuseactoremail', - get_string('cmi5launchuseactoremail', 'cmi5launch'), - get_string('cmi5launchuseactoremail_help', 'cmi5launch'), - 1)); - - $settings->add(new admin_setting_configtext_mod_cmi5launch('cmi5launch/cmi5launchplayerurl', - get_string('cmi5launchplayerurl', 'cmi5launch'), - get_string('cmi5launchplayerurl_help', 'cmi5launch'), - get_string('cmi5launchplayerurl_default', 'cmi5launch'), PARAM_URL)); - - $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenantname', - get_string('cmi5launchtenantname', 'cmi5launch'), - get_string('cmi5launchtenantname_help', 'cmi5launch'), - get_string('cmi5launchtenantname_default', 'cmi5launch')); - $settings->add($setting); - - $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenantpass', - get_string('cmi5launchtenantpass', 'cmi5launch'), - get_string('cmi5launchtenantpass_help', 'cmi5launch'), - get_string('cmi5launchtenantpass_default', 'cmi5launch')); - $settings->add($setting); - - $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenanttoken', - get_string('cmi5launchtenanttoken', 'cmi5launch'), - get_string('cmi5launchtenanttoken_help', 'cmi5launch'), - get_string('cmi5launchtenanttoken_default', 'cmi5launch')); - $settings->add($setting); - - //MB - //Grade stuff I'm bringing over - // Default grade settings. - $settings->add(new admin_setting_heading('cmi5launch/gradesettings', get_string('defaultgradesettings', 'cmi5launch'), '')); - $settings->add(new admin_setting_configselect('cmi5launch/grademethod', - get_string('grademethod', 'cmi5launch'), get_string('grademethoddesc', 'cmi5launch'), - GRADE_HIGHEST_CMI5, cmi5_get_grade_method_array())); - - for ($i = 0; $i <= 100; $i++) { - $grades[$i] = "$i"; - } - - $settings->add(new admin_setting_configselect('cmi5launch/maxgrade', - get_string('maximumgrade'), get_string('maximumgradedesc', 'cmi5launch'), 100, $grades)); - - $settings->add(new admin_setting_heading('cmi5launch/othersettings', get_string('defaultothersettings', 'cmi5launch'), '')); - - // Default attempts settings. - $settings->add(new admin_setting_configselect('cmi5launch/maxattempt', - get_string('maximumattempts', 'cmi5launch'), '', '0', cmi5_get_attempts_array()), get_string('whatmaxdesc', 'cmi5launch'),); - - $settings->add(new admin_setting_configselect('cmi5launch/whatgrade', - get_string('whatgrade', 'cmi5launch'), get_string('whatgradedesc', 'cmi5launch'), HIGHEST_ATTEMPT_CMI5, cmi5_get_what_grade_array())); - - //Not sure if we wan to implement mastery override? -MB - /* - $settings->add(new admin_setting_configselect('cmi5launch/masteryoverride', - get_string('masteryoverride', 'cmi5launch'), get_string('masteryoverridedesc', 'cmi5launch'), 1, $yesno)); - */ - - $settings->add(new admin_setting_configselect('cmi5launch/last_attempt_cmi5lock', - get_string('last_attempt_cmi5_lock', 'cmi5launch'), get_string('last_attempt_cmi5_lockdesc', 'cmi5launch'), 0, $yesno)); - - - -} +. + +/* For global cmi5 settings */ + + +/** + * Defines the version of cmi5launch + * + * This code fragment is called by moodle_needs_upgrading() and + * /admin/index.php + * + * @package mod_cmi5launch + * @copyright 2023 Megan Bohland + * @copyright Based on work by 2013 Andrew Downes as well as some code from the scorm module (Source code was uncredited). + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + + +defined('MOODLE_INTERNAL') || die; + +if ($ADMIN->fulltree) { + require_once($CFG->dirroot . '/mod/cmi5launch/locallib.php'); + require_once($CFG->dirroot . '/mod/cmi5launch/settingslib.php'); + + // MB + // From scorm grading stuff. + $yesno = array(0 => get_string('no'), + 1 => get_string('yes')); + + // Default display settings. + $settings->add(new admin_setting_heading('cmi5launch/cmi5launchlrsfieldset', + get_string('cmi5launchlrsfieldset', 'cmi5launch'), + get_string('cmi5launchlrsfieldset_help', 'cmi5launch'))); + + $settings->add(new admin_setting_configtext_mod_cmi5launch('cmi5launch/cmi5launchlrsendpoint', + get_string('cmi5launchlrsendpoint', 'cmi5launch'), + get_string('cmi5launchlrsendpoint_help', 'cmi5launch'), + get_string('cmi5launchlrsendpoint_default', 'cmi5launch'), PARAM_URL)); + + $options = array( + 1 => get_string('cmi5launchlrsauthentication_option_0', 'cmi5launch'), + 2 => get_string('cmi5launchlrsauthentication_option_1', 'cmi5launch'), + 0 => get_string('cmi5launchlrsauthentication_option_2', 'cmi5launch'), + ); + // Note the numbers above are deliberately mis-ordered for reasons of backwards compatibility with older settings. + + $setting = new admin_setting_configselect('cmi5launch/cmi5launchlrsauthentication', + get_string('cmi5launchlrsauthentication', 'cmi5launch'), + get_string('cmi5launchlrsauthentication_help', 'cmi5launch').'
' + .get_string('cmi5launchlrsauthentication_watershedhelp', 'cmi5launch') + , 1, $options); + $settings->add($setting); + + $setting = new admin_setting_configtext('cmi5launch/cmi5launchlrslogin', + get_string('cmi5launchlrslogin', 'cmi5launch'), + get_string('cmi5launchlrslogin_help', 'cmi5launch'), + get_string('cmi5launchlrslogin_default', 'cmi5launch')); + $settings->add($setting); + + $setting = new admin_setting_configtext('cmi5launch/cmi5launchlrspass', + get_string('cmi5launchlrspass', 'cmi5launch'), + get_string('cmi5launchlrspass_help', 'cmi5launch'), + get_string('cmi5launchlrspass_default', 'cmi5launch')); + $settings->add($setting); + + $settings->add(new admin_setting_configtext('cmi5launch/cmi5launchlrsduration', + get_string('cmi5launchlrsduration', 'cmi5launch'), + get_string('cmi5launchlrsduration_help', 'cmi5launch'), + get_string('cmi5launchlrsduration_default', 'cmi5launch'))); + + $settings->add(new admin_setting_configtext('cmi5launch/cmi5launchcustomacchp', + get_string('cmi5launchcustomacchp', 'cmi5launch'), + get_string('cmi5launchcustomacchp_help', 'cmi5launch'), + get_string('cmi5launchcustomacchp_default', 'cmi5launch'))); + + $settings->add(new admin_setting_configcheckbox('cmi5launch/cmi5launchuseactoremail', + get_string('cmi5launchuseactoremail', 'cmi5launch'), + get_string('cmi5launchuseactoremail_help', 'cmi5launch'), + 1)); + + $settings->add(new admin_setting_configtext_mod_cmi5launch('cmi5launch/cmi5launchplayerurl', + get_string('cmi5launchplayerurl', 'cmi5launch'), + get_string('cmi5launchplayerurl_help', 'cmi5launch'), + get_string('cmi5launchplayerurl_default', 'cmi5launch'), PARAM_URL)); + + $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenantname', + get_string('cmi5launchtenantname', 'cmi5launch'), + get_string('cmi5launchtenantname_help', 'cmi5launch'), + get_string('cmi5launchtenantname_default', 'cmi5launch')); + $settings->add($setting); + + $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenantpass', + get_string('cmi5launchtenantpass', 'cmi5launch'), + get_string('cmi5launchtenantpass_help', 'cmi5launch'), + get_string('cmi5launchtenantpass_default', 'cmi5launch')); + $settings->add($setting); + + $setting = new admin_setting_configtext('cmi5launch/cmi5launchtenanttoken', + get_string('cmi5launchtenanttoken', 'cmi5launch'), + get_string('cmi5launchtenanttoken_help', 'cmi5launch'), + get_string('cmi5launchtenanttoken_default', 'cmi5launch')); + $settings->add($setting); + + // MB. + // Grade stuff I'm bringing over. + // Default grade settings. + $settings->add(new admin_setting_heading('cmi5launch/gradesettings', get_string('defaultgradesettings', 'cmi5launch'), '')); + $settings->add(new admin_setting_configselect('cmi5launch/grademethod', + get_string('grademethod', 'cmi5launch'), get_string('grademethoddesc', 'cmi5launch'), + MOD_CMI5LAUNCH_GRADE_HIGHEST, cmi5launch_get_grade_method_array())); + + for ($i = 0; $i <= 100; $i++) { + $grades[$i] = "$i"; + } + + $settings->add(new admin_setting_configselect('cmi5launch/maxgrade', + get_string('maximumgrade'), get_string('maximumgradedesc', 'cmi5launch'), 100, $grades)); + + $settings->add(new admin_setting_heading('cmi5launch/othersettings', get_string('defaultothersettings', 'cmi5launch'), '')); + + // Default attempts settings. + $settings->add(new admin_setting_configselect('cmi5launch/maxattempt', + get_string('maximumattempts', 'cmi5launch'), '', '0', cmi5launch_get_attempts_array()), + get_string('whatmaxdesc', 'cmi5launch'), ); + + $settings->add(new admin_setting_configselect('cmi5launch/whatgrade', + get_string('whatgrade', 'cmi5launch'), get_string('whatgradedesc', 'cmi5launch'), + MOD_CMI5LAUNCH_HIGHEST_ATTEMPT, cmi5launch_get_what_grade_array())); + + // Not sure if we want to implement mastery override at this time -MB. + /* + $settings->add(new admin_setting_configselect('cmi5launch/masteryoverride', + get_string('masteryoverride', 'cmi5launch'), get_string('masteryoverridedesc', 'cmi5launch'), 1, $yesno)); + */ + + $settings->add(new admin_setting_configselect('cmi5launch/MOD_CMI5LAUNCH_LAST_ATTEMPTlock', + get_string('mod_cmi5launch_last_attempt_lock', 'cmi5launch'), get_string('mod_cmi5launch_last_attempt_lockdesc', 'cmi5launch'), 0, $yesno)); +} diff --git a/settingslib.php b/settingslib.php index e06da08..fcfdf61 100755 --- a/settingslib.php +++ b/settingslib.php @@ -1,51 +1,51 @@ -. - -/** - * Extend admin_setting_configtext to validate form data in cmi5launch global settings - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -class admin_setting_configtext_mod_cmi5launch extends admin_setting_configtext { - /** - * Saves the setting(s) provided in $data - * - * @param array $data An array of data, if not array returns empty str - * @return mixed empty string on useless data or success, error string if failed - */ - public function write_setting($data) { - if ($this->paramtype === PARAM_INT and $data === '') { - // Do not complain if '' used instead of 0. - $data = 0; - } - - $validated = $this->validate($data); - if ($validated !== true) { - return $validated; - } - - // Make sure there is always a trailing slash on endpoint URLs. - if ($this->name == 'cmi5launchlrsendpoint') { - $data = rtrim($data, '/') . '/'; - } - return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); - } -} \ No newline at end of file +. + +/** + * Extend admin_setting_configtext to validate form data in cmi5launch global settings + * + * @package mod_cmi5launch + * @copyright 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +class admin_setting_configtext_mod_cmi5launch extends admin_setting_configtext { + /** + * Saves the setting(s) provided in $data + * + * @param array $data An array of data, if not array returns empty str + * @return mixed empty string on useless data or success, error string if failed + */ + public function write_setting($data) { + if ($this->paramtype === PARAM_INT && $data === '') { + // Do not complain if '' used instead of 0. + $data = 0; + } + + $validated = $this->validate($data); + if ($validated !== true) { + return $validated; + } + + // Make sure there is always a trailing slash on endpoint URLs. + if ($this->name == 'cmi5launchlrsendpoint') { + $data = rtrim($data, '/') . '/'; + } + return ($this->config_write($this->name, $data) ? '' : get_string('errorsetting', 'admin')); + } +} diff --git a/version.php b/version.php index 94c1b37..e85fb63 100755 --- a/version.php +++ b/version.php @@ -1,36 +1,38 @@ -. - - -/** - * Defines the version of cmi5launch - * - * This code fragment is called by moodle_needs_upgrading() and - * /admin/index.php - * - * @package mod_cmi5launch - * @copyright 2013 Andrew Downes - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$plugin->version = 2018110100; // 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). -$plugin->component = 'mod_cmi5launch'; // To check on upgrade, that module sits in correct place. -$plugin->maturity = MATURITY_STABLE; -$plugin->release = '1.3RC2 (Build: 2016060300)'; +. + + +/** + * Defines the version of cmi5launch + * + * This code fragment is called by moodle_needs_upgrading() and + * /admin/index.php + * + * @package mod_cmi5launch + * @copyright 2024 Megan Bohland + * @copyright Based on work by 2013 Andrew Downes + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2024030615; // 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). +$plugin->component = 'mod_cmi5launch'; // To check on upgrade, that module sits in correct place. +$plugin->maturity = MATURITY_STABLE; +$plugin->release = '1.3RC2 (Build: 2016060300)'; diff --git a/view.php b/view.php index 2b03734..5419137 100755 --- a/view.php +++ b/view.php @@ -1,373 +1,411 @@ -. - -/** - * Displays the AU's of a course and their progress - * @copyright 2023 Megan Bohland - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -// namespace cmi5; - //For some reason using the namespace cmi5; here breaks the code. -//It cannot find html_table class. - -require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); -require('header.php'); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/Progress.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/course.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/cmi5Connector.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/ausHelpers.php"); -require_once("$CFG->dirroot/mod/cmi5launch/cmi5PHP/src/sessionHelpers.php"); -require_once("$CFG->dirroot/lib/outputcomponents.php"); - -//bring in functions from class Progress and AU helpers, Connectors -$progress = new progress; -$aus_helpers = new Au_Helpers; -$connectors = new cmi5Connectors; -$ses_helpers = new Session_Helpers; - -//Functions from other classes -$saveAUs = $aus_helpers->getSaveAUs(); -$createAUs = $aus_helpers->getCreateAUs(); -$getAUs = $aus_helpers->getAUsFromDB(); -$getRegistration = $connectors->getRegistrationPost(); -$getRegistrationInfo = $connectors->getRegistrationGet(); -$getProgress = $progress->getRetrieveStatement(); -$updateSession = $ses_helpers->getUpdateSession(); - -global $cmi5launch, $USER, $mod; - -// Trigger module viewed event. -$event = \mod_cmi5launch\event\course_module_viewed::create(array( - 'objectid' => $cmi5launch->id, - 'context' => $context, -)); -$event->add_record_snapshot('course', $course); -$event->add_record_snapshot('cmi5launch', $cmi5launch); -$event->add_record_snapshot('course_modules', $cm); -$event->trigger(); - -$PAGE->set_url('/mod/cmi5launch/view.php', array('id' => $cm->id)); -$PAGE->set_title(format_string($cmi5launch->name)); -$PAGE->set_heading(format_string($course->fullname)); -$PAGE->set_context($context); - -$PAGE->requires->jquery(); - -// Output starts here. -echo $OUTPUT->header(); - -// Reload cmi5 course instance. -$record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); - -if ($cmi5launch->intro) { - // Conditions to show the intro can change to look for own settings or whatever. - echo $OUTPUT->box( - format_module_intro('cmi5launch', $cmi5launch, $cm->id), - 'generalbox mod_introbox', - 'cmi5launchintro' - ); -} - -// TODO: Put all the php inserted data as parameters on the functions and put the functions in a separate JS file. -?> - - -record_exists('cmi5launch_course', ['courseid' => $record->courseid, 'userid' => $USER->id]); - -//If it does not exist, create it -if($exists == false){ - - $usersCourse = new course($record); - - $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($record->courseid, $cmi5launch->id); - $usersCourse->registrationid = $registrationID; - - //Retrieve AU ids for this user/course - $aus = json_decode($record->aus); - $auIDs = $saveAUs($createAUs($aus)); - $usersCourse->aus = (json_encode($auIDs)); - //Save new record to DB - $DB->insert_record('cmi5launch_course', $usersCourse); - -}else{ - - //Then we have a record, so we need to retrieve it - $usersCourse = $DB->get_record('cmi5launch_course', ['courseid' => $record->courseid, 'userid' => $USER->id]); - - //Retrieve registration id - $registrationID = $usersCourse->registrationid; - - //Retrieve AU ids - $auIDs = (json_decode($usersCourse->aus) ); -} - -//Array to hold info for table population -$tableData = array(); - -//We need id to get progress -$cmid = $cmi5launch->id; - -//Create table to display on page -$table = new html_table(); -$table->id = 'cmi5launch_autable'; -$table->caption = get_string('AUtableheader', 'cmi5launch'); -$table->head = array( - get_string('cmi5launchviewAUname', 'cmi5launch'), - get_string('cmi5launchviewstatus', 'cmi5launch'), - get_string('cmi5launchviewgradeheader', 'cmi5launch'), - get_string('cmi5launchviewregistrationheader', 'cmi5launch'), - -); -//TODO MB -//Return to for grades -//cmi5_update_grades($cmi5launch, 0); - -//Cycle through AU IDs -foreach($auIDs as $key => $auID){ - - $au = $getAUs($auID); - - //Verify object is an au object - if (!is_a($au, 'Au')) { - - $reason = "Excepted AU, found "; - var_dump($au); - throw new moodle_exception($reason, 'cmi5launch', '', $warnings[$reason]); - } - - //Retrieve AU's lmsID - $auLmsId = $au->lmsid; - - //Query CMI5 player for updated registration info - $registrationInfoFromCMI5 = $getRegistrationInfo($registrationID, $cmi5launch->id); - //Take only info about AUs out of registrationInfoFromCMI5 - $ausFromCMI5 = array_chunk($registrationInfoFromCMI5["metadata"]["moveOn"]["children"], 1, true); - - //TODO now we can get the AU's satisifed FROM the CMI5 player - //TODO (for that matter couldn't we make it, notattempetd, satisifed, not satisfied??) - - foreach($ausFromCMI5 as $key => $auInfo){ - - //Array to hold scores for AU - $sessionScores = array(); - - if ($auInfo[$key]["lmsId"] == $auLmsId){ - //Grab it's 'satisfied' info - $auSatisfied = $auInfo[$key]["satisfied"]; - } - } - - //If the 'sessions' in this AU are null we know this hasn't even been attempted - if($au->sessions == null ){ - - $auStatus = "Not attempted"; - - }else{ - - //Retrieve AUs moveon specification - $auMoveon = $au->moveon; - - //If it's been attempted but no moveon value - if ($auMoveon == "NotApplicable") { - $auStatus = "viewed"; - } - //IF it DOES have a moveon value - else { - - //If satisifed is returned true, I - 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"; - } - }; - //Ensure sessions are up to date - //Retrieve session ids - $sessionIDs = json_decode($au->sessions); - - - //Iterate through each session by id - foreach ($sessionIDs as $key => $sessionID) { - - - //Retrieve new info (if any) from CMI5 player on session - $session = $updateSession($sessionID, $cmi5launch->id); - - //Get progress from LRS - $session = $getProgress($registrationID, $cmi5launch->id, $session); - - //Add score to array for AU - $sessionScores[] = $session->score; - - //Update session in DB - $DB->update_record('cmi5launch_sessions', $session); - } - - //Save the session scores to AU, it is ok to overwrite - $au->scores = json_encode($sessionScores); - }; - - //Create array of info to place in table - $auInfo = array(); - - //Assign au name, progress, and index - $auInfo[] = $au->title; - $auInfo[] = ($auStatus); - - - //Ok, now we need to retreive the sessions and find the average score - $grade = 0; - //what is au moveon is not set? - // var_dump($au->moveon); - // echo "
"; - if ($au->moveon == "CompletedOrPassed" || "Passed") { - // echo"are we in here?"; - //what is au moveon is not set? - //TODO M - //Currently it takes the highest grade out of sessions for grade. Later this can be changed by linking it to plugin options - //However, since CMI5 player does not count any sessions after the first for scoring, by averaging we are adding unnessary - //0', and artificailly lowering the grade. - //Also, should we query for 'passed' or 'completed'? statements here? - //Or can we have the cmi5player update our AU's moveon to 'passed' or 'completed'? - - if (!$sessionScores == null) { - //if the grade is empty, we need to pass a null or NA - $grade = max($sessionScores); - $au->grade = $grade; - if ($grade == 0) { - $auInfo[] = ("Passed"); - } else { - $auInfo[] = ($grade);; - } - - } else { - $auInfo[] = ("Not Applicable"); - } - } else { - // echo"why not here?"; - if (!$sessionScores == null) { - //if the grade is empty, we need to pass a null or NA - $grade = max($sessionScores); - $au->grade = $grade; - $auInfo[] = ($grade); - - } else { - $auInfo[] = ("Not Attempted"); - } - } - $auIndex = $au->auindex; - - //AU id for next page (to be loaded) - $infoForNextPage = $auID; - - //Assign au link to auviews - $auInfo[] = "" - . get_string('cmi5launchviewlaunchlink', 'cmi5launch') . ""; - - //add to be fed to table - $tableData[] = $auInfo; - - //update the au in DB - $DB->update_record("cmi5launch_aus", $au); - } - -//Lastly, update our course table -$updated = $DB->update_record("cmi5launch_course", $usersCourse); - -//This feeds the table -$table->data = $tableData; - -echo html_writer::table($table); - -// Add a form to be posted based on the attempt selected. -?> -
- - - - -
-footer(); \ No newline at end of file +. + +/** + * Displays the AU's of a course and their progress. + * @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\progress; +use mod_cmi5launch\local\course; +use mod_cmi5launch\local\cmi5_connectors; +use mod_cmi5launch\local\au_helpers; +use mod_cmi5launch\local\session_helpers; + +require_once("../../config.php"); +require_once(dirname(dirname(dirname(__FILE__))) . '/config.php'); +require('header.php'); +require_login($course, false, $cm); + +// Bring in functions and classes. +$progress = new progress; +$aushelpers = new au_helpers; +$connectors = new cmi5_connectors; + +// Functions from other classes. +$saveaus = $aushelpers->get_cmi5launch_save_aus(); +$createaus = $aushelpers->get_cmi5launch_create_aus(); +$getaus = $aushelpers->get_cmi5launch_retrieve_aus_from_db(); +$getregistration = $connectors->cmi5launch_get_registration_with_post(); +$getregistrationinfo = $connectors->cmi5launch_get_registration_with_get(); + +global $cmi5launch, $USER, $mod; + +// MB - Not currently using events, but may in future. +/* +// Trigger module viewed event. +$event = \mod_cmi5launch\event\course_module_viewed::create(array( + 'objectid' => $cmi5launch->id, + 'context' => $context, +)); + +$event->add_record_snapshot('course', $course); +$event->add_record_snapshot('cmi5launch', $cmi5launch); +$event->add_record_snapshot('course_modules', $cm); +$event->trigger(); +*/ + +// Print the page header. +$PAGE->set_url('/mod/cmi5launch/view.php', array('id' => $cm->id)); +$PAGE->set_title(format_string($cmi5launch->name)); +$PAGE->set_heading(format_string($course->fullname)); +$PAGE->set_context($context); +$PAGE->requires->jquery(); + +// Output starts here. +echo $OUTPUT->header(); + +// Reload cmi5 course instance. +$record = $DB->get_record('cmi5launch', array('id' => $cmi5launch->id)); + +// TODO: Put all the php inserted data as parameters on the functions and put the functions in a separate JS file. +?> + + +id); + +// Check if a course record exists for this user yet. +$exists = $DB->record_exists('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); + +// If it does not exist, create it. +if ($exists == false) { + + // Make a new course record. + $userscourse = new course($record); + + // 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; + + // Retreive the Moodle course id + + $userscourse->moodlecourseid = $cm->instance; + + // Retrieve AU ids for this user/course. + $aus = json_decode($record->aus); + + $auids = $saveaus($createaus($aus)); + $userscourse->aus = (json_encode($auids)); + + // Save new record to DB. + $newid = $DB->insert_record('cmi5launch_usercourse', $userscourse); + + // Now assign id created by DB. + $userscourse->id = $newid; + +} else { // Record exists. + echo" cm is $cm->instance"; + echo" cmi5launch id is $cmi5launch->id"; + // We have a record, so we need to retrieve it. + $userscourse = $DB->get_record('cmi5launch_usercourse', ['courseid' => $record->courseid, 'userid' => $USER->id]); + + // Retrieve registration id. + $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) ); +} + +// Array to hold info for table population. +$tabledata = array(); + +// We need id to get progress. +$cmid = $cmi5launch->id; + +// Create table to display on page. +$table = new html_table(); +$table->id = 'cmi5launch_autable'; +$table->caption = get_string('autableheader', 'cmi5launch'); +$table->head = array( + get_string('cmi5launchviewAUname', 'cmi5launch'), + get_string('cmi5launchviewstatus', 'cmi5launch'), + get_string('cmi5launchviewgradeheader', 'cmi5launch'), + get_string('cmi5launchviewregistrationheader', 'cmi5launch'), +); + +// Array to hold Au scores. +$auscores = array(); + +// Query CMI5 player for updated registration info. +$registrationinfofromcmi5 = $getregistrationinfo($registrationid, $cmi5launch->id); + +// 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) { + + // 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)) { + + $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; + + // 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) { + + $ausession = $getsessioninfo($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 the 'sessions' in this AU are null we know this hasn't even been attempted. + if ($au->sessions == null) { + + $austatus = "Not attempted"; + + } else { + + // Retrieve AUs moveon specification. + $aumoveon = $au->moveon; + + // 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(); + + // Assign au name, progress, and index. + $auinfo[] = $au->title; + $auinfo[] = ($austatus); + + $grade = 0; + + // Retrieve grade. + if (!$au->grade == 0 || $au->grade == null) { + + $grade = $au->grade; + + $auinfo[] = ($grade); + } else if ($au->grade == 0) { + + // Display the 0. + $auinfo[] = ($grade); + } else { + // There is no grade, leave blank. + $auinfo[] = (" "); + } + + $auindex = $au->auindex; + + // AU id for next page (to be loaded). + $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); + + // 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); + +// Lastly, update our course table. +$updated = $DB->update_record("cmi5launch_usercourse", $userscourse); + +// This feeds the table. +$table->data = $tabledata; + +echo html_writer::table($table); + +// Add a form to be posted based on the attempt selected. +?> +
+ + + + +
+footer();