From 7606ba2133939a24e854caf4e64088d54a8116db Mon Sep 17 00:00:00 2001 From: Dan Marsden Date: Fri, 23 Dec 2016 12:04:41 +1300 Subject: [PATCH] MDL-49557 mod_scorm: fix AICC pre-requisite handling. --- mod/scorm/datamodels/aicclib.php | 5 +- mod/scorm/datamodels/scorm_12lib.php | 103 -------------------------- mod/scorm/locallib.php | 105 +++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 104 deletions(-) diff --git a/mod/scorm/datamodels/aicclib.php b/mod/scorm/datamodels/aicclib.php index dfd4e4e9fb4..354018c78f3 100644 --- a/mod/scorm/datamodels/aicclib.php +++ b/mod/scorm/datamodels/aicclib.php @@ -83,6 +83,7 @@ function scorm_get_aicc_columns($row, $mastername='system_id') { $tok = strtok(strtolower($row), "\",\n\r"); $result = new stdClass(); $result->columns = array(); + $result->mastercol = 0; $i = 0; while ($tok) { if ($tok != '') { @@ -250,7 +251,9 @@ function scorm_parse_aicc(&$scorm) { $regexp = scorm_forge_cols_regexp($columns->columns, '(.+),'); for ($i = 1; $i < count($rows); $i++) { if (preg_match($regexp, $rows[$i], $matches)) { - $courses[$courseid]->elements[$columns->mastercol + 1]->prerequisites = substr(trim($matches[2 - $columns->mastercol]), 1, -1); + $elementid = substr(trim($matches[$columns->mastercol + 1]), 1, -1); + $prereq = substr(trim($matches[2 - $columns->mastercol]), 1, -1); + $courses[$courseid]->elements[$elementid]->prerequisites = $prereq; } } } diff --git a/mod/scorm/datamodels/scorm_12lib.php b/mod/scorm/datamodels/scorm_12lib.php index bc7791a11da..306b9ac54d2 100644 --- a/mod/scorm/datamodels/scorm_12lib.php +++ b/mod/scorm/datamodels/scorm_12lib.php @@ -14,109 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * This is really a little language parser for AICC_SCRIPT - * evaluates the expression and returns a boolean answer - * see 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec (CAM). - * - * @param string $prerequisites the aicc_script prerequisites expression - * @param array $usertracks the tracked user data of each SCO visited - * @return boolean - */ -function scorm_eval_prerequisites($prerequisites, $usertracks) { - - // This is really a little language parser - AICC_SCRIPT is the reference - // see 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec. - $element = ''; - $stack = array(); - $statuses = array( - 'passed' => 'passed', - 'completed' => 'completed', - 'failed' => 'failed', - 'incomplete' => 'incomplete', - 'browsed' => 'browsed', - 'not attempted' => 'notattempted', - 'p' => 'passed', - 'c' => 'completed', - 'f' => 'failed', - 'i' => 'incomplete', - 'b' => 'browsed', - 'n' => 'notattempted' - ); - $i = 0; - - // Expand the amp entities. - $prerequisites = preg_replace('/&/', '&', $prerequisites); - // Find all my parsable tokens. - $prerequisites = preg_replace('/(&|\||\(|\)|\~)/', '\t$1\t', $prerequisites); - // Expand operators. - $prerequisites = preg_replace('/&/', '&&', $prerequisites); - $prerequisites = preg_replace('/\|/', '||', $prerequisites); - // Now - grab all the tokens. - $elements = explode('\t', trim($prerequisites)); - - // Process each token to build an expression to be evaluated. - $stack = array(); - foreach ($elements as $element) { - $element = trim($element); - if (empty($element)) { - continue; - } - if (!preg_match('/^(&&|\|\||\(|\))$/', $element)) { - // Create each individual expression. - // Search for ~ = <> X*{} . - - // Sets like 3*{S34, S36, S37, S39}. - if (preg_match('/^(\d+)\*\{(.+)\}$/', $element, $matches)) { - $repeat = $matches[1]; - $set = explode(',', $matches[2]); - $count = 0; - foreach ($set as $setelement) { - if (isset($usertracks[$setelement]) && - ($usertracks[$setelement]->status == 'completed' || $usertracks[$setelement]->status == 'passed')) { - $count++; - } - } - if ($count >= $repeat) { - $element = 'true'; - } else { - $element = 'false'; - } - } else if ($element == '~') { - // Not maps ~. - $element = '!'; - } else if (preg_match('/^(.+)(\=|\<\>)(.+)$/', $element, $matches)) { - // Other symbols = | <> . - $element = trim($matches[1]); - if (isset($usertracks[$element])) { - $value = trim(preg_replace('/(\'|\")/', '', $matches[3])); - if (isset($statuses[$value])) { - $value = $statuses[$value]; - } - if ($matches[2] == '<>') { - $oper = '!='; - } else { - $oper = '=='; - } - $element = '(\''.$usertracks[$element]->status.'\' '.$oper.' \''.$value.'\')'; - } else { - $element = 'false'; - } - } else { - // Everything else must be an element defined like S45 ... - if (isset($usertracks[$element]) && - ($usertracks[$element]->status == 'completed' || $usertracks[$element]->status == 'passed')) { - $element = 'true'; - } else { - $element = 'false'; - } - } - - } - $stack[] = ' '.$element.' '; - } - return eval('return '.implode($stack).';'); -} /** * Sets up $userdata array and default values for SCORM 1.2 . diff --git a/mod/scorm/locallib.php b/mod/scorm/locallib.php index 09ebae8919d..c713e723dc1 100644 --- a/mod/scorm/locallib.php +++ b/mod/scorm/locallib.php @@ -2239,3 +2239,108 @@ function scorm_launch_sco($scorm, $sco, $cm, $context, $scourl) { $event->add_record_snapshot('scorm_scoes', $sco); $event->trigger(); } + +/** + * This is really a little language parser for AICC_SCRIPT + * evaluates the expression and returns a boolean answer + * see 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec (CAM). + * Also used by AICC packages. + * + * @param string $prerequisites the aicc_script prerequisites expression + * @param array $usertracks the tracked user data of each SCO visited + * @return boolean + */ +function scorm_eval_prerequisites($prerequisites, $usertracks) { + + // This is really a little language parser - AICC_SCRIPT is the reference + // see 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec. + $element = ''; + $stack = array(); + $statuses = array( + 'passed' => 'passed', + 'completed' => 'completed', + 'failed' => 'failed', + 'incomplete' => 'incomplete', + 'browsed' => 'browsed', + 'not attempted' => 'notattempted', + 'p' => 'passed', + 'c' => 'completed', + 'f' => 'failed', + 'i' => 'incomplete', + 'b' => 'browsed', + 'n' => 'notattempted' + ); + $i = 0; + + // Expand the amp entities. + $prerequisites = preg_replace('/&/', '&', $prerequisites); + // Find all my parsable tokens. + $prerequisites = preg_replace('/(&|\||\(|\)|\~)/', '\t$1\t', $prerequisites); + // Expand operators. + $prerequisites = preg_replace('/&/', '&&', $prerequisites); + $prerequisites = preg_replace('/\|/', '||', $prerequisites); + // Now - grab all the tokens. + $elements = explode('\t', trim($prerequisites)); + + // Process each token to build an expression to be evaluated. + $stack = array(); + foreach ($elements as $element) { + $element = trim($element); + if (empty($element)) { + continue; + } + if (!preg_match('/^(&&|\|\||\(|\))$/', $element)) { + // Create each individual expression. + // Search for ~ = <> X*{} . + + // Sets like 3*{S34, S36, S37, S39}. + if (preg_match('/^(\d+)\*\{(.+)\}$/', $element, $matches)) { + $repeat = $matches[1]; + $set = explode(',', $matches[2]); + $count = 0; + foreach ($set as $setelement) { + if (isset($usertracks[$setelement]) && + ($usertracks[$setelement]->status == 'completed' || $usertracks[$setelement]->status == 'passed')) { + $count++; + } + } + if ($count >= $repeat) { + $element = 'true'; + } else { + $element = 'false'; + } + } else if ($element == '~') { + // Not maps ~. + $element = '!'; + } else if (preg_match('/^(.+)(\=|\<\>)(.+)$/', $element, $matches)) { + // Other symbols = | <> . + $element = trim($matches[1]); + if (isset($usertracks[$element])) { + $value = trim(preg_replace('/(\'|\")/', '', $matches[3])); + if (isset($statuses[$value])) { + $value = $statuses[$value]; + } + if ($matches[2] == '<>') { + $oper = '!='; + } else { + $oper = '=='; + } + $element = '(\''.$usertracks[$element]->status.'\' '.$oper.' \''.$value.'\')'; + } else { + $element = 'false'; + } + } else { + // Everything else must be an element defined like S45 ... + if (isset($usertracks[$element]) && + ($usertracks[$element]->status == 'completed' || $usertracks[$element]->status == 'passed')) { + $element = 'true'; + } else { + $element = 'false'; + } + } + + } + $stack[] = ' '.$element.' '; + } + return eval('return '.implode($stack).';'); +}