Learning path: Add option to use score as progress in single-SCO SCORM packages

Implement feature request #3069
pull/3090/head
Yannick Warnier 6 years ago committed by GitHub
parent 4f090d4c92
commit 5643beaace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      main/install/configuration.dist.php
  2. 13
      main/lang/english/trad4all.inc.php
  3. 136
      main/lp/learnpath.class.php
  4. 15
      main/lp/lp_ajax_save_item.php
  5. 20
      main/lp/lp_ajax_switch_item_toc.php
  6. 25
      main/lp/lp_edit.php
  7. 4
      main/lp/lp_upload.php
  8. 12
      main/lp/scorm_api.php

@ -1378,6 +1378,10 @@ ALTER TABLE notification_event ADD COLUMN event_id INT NULL;
// Catalog search settings visibility
//$_configuration['catalog_settings'] = ['sessions' => ['by_title' => true, 'by_date' => true, 'by_tag' => true, 'show_session_info' => true, 'show_session_date' => true]];
// Enable learning paths with only one SCO item to use the score returned by
// the SCO as an indicator of progress of the whole learning path
// $_configuration['lp_score_as_progress_enable'] = false;
// Use this link as the "Forgot password?" link instead of the default. This setting should be transformed into a hook for plugins at a later time
//$_configuration['pass_reminder_custom_link'] = '';

@ -751,7 +751,7 @@ $ToExportLearnpathWithQuizYouHaveToSelectQuiz = "If you want to export a course
$ArchivesDirectoryNotWriteableContactAdmin = "The app/cache/ directory, used by this tool, is not writeable. Please contact your platform administrator.";
$DestinationCourse = "Target course";
$ConvertToMultipleAnswer = "Convert to multiple answer";
$CasMainActivateComment = "Enabling CAS authentication will allow users to authenticate with their CAS credentials.<br/>Go to <a href='settings.php?category=CAS'>Plugin</a> to add a configurable 'CAS Login' button for your Chamilo campus. Or you can force CAS authentication by setting cas[force_redirect] in app/config/auth.conf.php.";
$CasMainActivateComment = "Enabling CAS authentication will allow users to authenticate with their CAS credentials.<br/>Go to <a href='settings.php?category=CAS'>Plugin</a> to add a configurable 'CAS Login' button for your Chamilo campus.";
$UsersRegisteredInAnyGroup = "Users registered in any group";
$Camera = "Camera";
$Microphone = "Microphone";
@ -6818,7 +6818,6 @@ $CasMainPortComment = "The port on which to connect to the main CAS server";
$CasMainProtocolTitle = "Main CAS server protocol";
$CAS1Text = "CAS 1";
$CAS2Text = "CAS 2";
$CAS3Text = "CAS 3";
$SAMLText = "SAML";
$CasMainProtocolComment = "The protocol with which we connect to the CAS server";
$CasUserAddActivateTitle = "Enable CAS user addition";
@ -7162,10 +7161,10 @@ $SessionadminAutosubscribeTitle = "Session admin autosubscribe";
$SessionadminAutosubscribeComment = "Session administrator autosubscribe - not available yet";
$ToolVisibleByDefaultAtCreationTitle = "Tool visible at course creation";
$ToolVisibleByDefaultAtCreationComment = "Select the tools that will be visible when creating the courses - not yet available";
$casAddUserActivatePlatform = "Create a user account for any new CAS-authenticated user, from scratch";
$casAddUserActivateLDAP = "Create a user account for any new CAS-authenticated user, from LDAP";
$UpdateUserInfoCasWithLdapTitle = "Update CAS-authenticated user account information from LDAP";
$UpdateUserInfoCasWithLdapComment = "Makes sure the user firstname, lastname and email address are the same as current values in the LDAP directory";
$casAddUserActivatePlatform = "CAS internal setting";
$casAddUserActivateLDAP = "CAS internal setting";
$UpdateUserInfoCasWithLdapTitle = "CAS internal setting";
$UpdateUserInfoCasWithLdapComment = "CAS internal setting";
$InstallExecution = "Installation process execution";
$UpdateExecution = "Update process execution";
$PleaseWaitThisCouldTakeAWhile = "Please wait. This could take a while...";
@ -8449,4 +8448,6 @@ $CompilatioComunicationAjaxImpossible = "AJAX communication with the Compilatio
$UserClassExplanation = "Information: The list of classes below contains the list of classes you have already registered in your course. If this list is empty, use the + green above to add classes.";
$InsertTwoNames = "Insert your two names";
$AddRightLogo = "Add right logo";
$LearnpathUseScoreAsProgress = "Use score as progress";
$LearnpathUseScoreAsProgressComment = "Use the score returned, by the only SCO in this learning path, as the progress indicator in the progress bar. This modifies the SCORM behaviour in the strict sense, but improves visual feedback to the learner.";
?>

@ -1965,9 +1965,9 @@ class learnpath
* @param string $file_path the path to the file
* @param string $file_name the original name of the file
*
* @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized
* @return string 'scorm','aicc','scorm2004','dokeos', 'error-empty-package' if the package is empty, or '' if the package cannot be recognized
*/
public static function get_package_type($file_path, $file_name)
public static function getPackageType($file_path, $file_name)
{
// Get name of the zip file without the extension.
$file_info = pathinfo($file_name);
@ -1994,43 +1994,47 @@ class learnpath
$aicc_match_au = 0;
$aicc_match_des = 0;
$aicc_match_cst = 0;
$countItems = 0;
// The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?).
if (is_array($zipContentArray) && count($zipContentArray) > 0) {
foreach ($zipContentArray as $thisContent) {
if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
// New behaviour: Don't do anything. These files will be removed in scorm::import_package.
} elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
$manifest = $thisContent['filename']; // Just the relative directory inside scorm/
$package_type = 'scorm';
break; // Exit the foreach loop.
} elseif (
preg_match('/aicc\//i', $thisContent['filename']) ||
in_array(
strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
['crs', 'au', 'des', 'cst']
)
) {
$ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
switch ($ext) {
case 'crs':
$aicc_match_crs = 1;
break;
case 'au':
$aicc_match_au = 1;
break;
case 'des':
$aicc_match_des = 1;
break;
case 'cst':
$aicc_match_cst = 1;
break;
default:
break;
if (is_array($zipContentArray)) {
$countItems = count($zipContentArray);
if ($countItems > 0) {
foreach ($zipContentArray as $thisContent) {
if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) {
// New behaviour: Don't do anything. These files will be removed in scorm::import_package.
} elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) {
$manifest = $thisContent['filename']; // Just the relative directory inside scorm/
$package_type = 'scorm';
break; // Exit the foreach loop.
} elseif (
preg_match('/aicc\//i', $thisContent['filename']) ||
in_array(
strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION)),
['crs', 'au', 'des', 'cst']
)
) {
$ext = strtolower(pathinfo($thisContent['filename'], PATHINFO_EXTENSION));
switch ($ext) {
case 'crs':
$aicc_match_crs = 1;
break;
case 'au':
$aicc_match_au = 1;
break;
case 'des':
$aicc_match_des = 1;
break;
case 'cst':
$aicc_match_cst = 1;
break;
default:
break;
}
//break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
} else {
$package_type = '';
}
//break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC.
} else {
$package_type = '';
}
}
}
@ -2042,6 +2046,13 @@ class learnpath
// Try with chamilo course builder
if (empty($package_type)) {
// Sometimes users will try to upload an empty zip, or a zip with
// only a folder. Catch that and make the calling function aware.
// If the single file was the imsmanifest.xml, then $package_type
// would be 'scorm' and we wouldn't be here.
if ($countItems < 2) {
return 'error-empty-package';
}
$package_type = 'chamilo';
}
@ -2538,16 +2549,40 @@ class learnpath
if (empty($mode)) {
$mode = $this->progress_bar_mode;
}
$text = '';
$percentage = 0;
// If the option to use the score as progress is set for this learning
// path, then the rules are completely different: we assume only one
// item exists and the progress of the LP depends on the score
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
if ($scoreAsProgressSetting === true) {
$scoreAsProgress = $this->getUseScoreAsProgress();
if ($scoreAsProgress) {
// Get single item's score
$itemId = $this->get_current_item_id();
$item = $this->getItem($itemId);
$score = $item->get_score();
$maxScore = $item->get_max();
if ($mode = '%') {
$percentage = ((float) $score / (float) $maxScore) * 100;
$percentage = number_format($percentage, 0);
$text = '%';
} else {
$percentage = $score;
$text = '/'.$maxScore;
}
return [$percentage, $text];
}
}
// otherwise just continue the normal processing of progress
$total_items = $this->getTotalItemsCountWithoutDirs();
$completeItems = $this->get_complete_items_count();
if ($add != 0) {
$completeItems += $add;
}
$text = '';
if ($completeItems > $total_items) {
$completeItems = $total_items;
}
$percentage = 0;
if ($mode == '%') {
if ($total_items > 0) {
$percentage = ((float) $completeItems / (float) $total_items) * 100;
@ -13740,4 +13775,29 @@ EOD;
return '';
}
/**
* Gets whether this SCORM learning path has been marked to use the score
* as progress. Takes into account whether the learnpath matches (SCORM
* content + less than 2 items).
* @return bool True if the score should be used as progress, false otherwise
*/
public function getUseScoreAsProgress()
{
// If not a SCORM, we don't care about the setting
if ($this->get_type() != 2) {
return false;
}
// If more than one step in the SCORM, we don't care about the setting
if ($this->get_total_items_count() > 1) {
return false;
}
$extraFieldValue = new ExtraFieldValue('lp');
$doUseScore = false;
$useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress');
if (!empty($useScore) && isset($useScore['value'])) {
$doUseScore = $useScore['value'];
}
return $doUseScore;
}
}

@ -474,7 +474,20 @@ function save_item(
$return .= "update_toc('".$my_upd_status."','".$my_upd_id."');";
}
}
$return .= "update_progress_bar('$myComplete', '$myTotal', '$myProgressMode');";
$progressBarSpecial = false;
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
if ($scoreAsProgressSetting === true) {
$scoreAsProgress = $myLP->getUseScoreAsProgress();
if ($scoreAsProgress) {
$score = $myLPI->get_score();
$maxScore = $myLPI->get_max();
$return .= "update_progress_bar('$score', '$maxScore', '$myProgressMode');";
$progressBarSpecial = true;
}
}
if (!$progressBarSpecial) {
$return .= "update_progress_bar('$myComplete', '$myTotal', '$myProgressMode');";
}
if (!Session::read('login_as')) {
// If $_SESSION['login_as'] is set, then the user is an admin logged as the user.

@ -151,8 +151,24 @@ function switch_item_toc($lpId, $userId, $viewId, $currentItem, $nextItem)
$return .= "update_toc('unhighlight','".$currentItem."');".
"update_toc('highlight','".$newItemId."');".
"update_toc('$lessonStatus','".$newItemId."');".
"update_progress_bar('$completedItems','$totalItems','$progressMode');";
"update_toc('$lessonStatus','".$newItemId."');";
$progressBarSpecial = false;
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
if ($scoreAsProgressSetting === true) {
$scoreAsProgress = $myLP->getUseScoreAsProgress();
if ($scoreAsProgress) {
$score = $myLPI->get_score();
$maxScore = $myLPI->get_max();
$return .= "update_progress_bar('$score', '$maxScore', '$progressMode');";
$progressBarSpecial = true;
}
}
if (!$progressBarSpecial) {
$return .= "update_progress_bar('$completedItems','$totalItems','$progressMode');";
}
$myLP->set_error_msg('');
$myLP->prerequisites_match(); // Check the prerequisites are all complete.

@ -235,6 +235,24 @@ $form->addElement(
get_lang('AccumulateScormTime')
);
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
$countItems = $learnPath->get_total_items_count();
$lpType = $learnPath->get_type();
// This option is only usable for SCORM, if there is only 1 item, otherwise
// using the score as progress would not work anymore (we would have to divide
// between the two without knowing if the second has any score at all)
// TODO: automatically cancel this setting if items >= 2
if ($scoreAsProgressSetting && $countItems < 2 && $lpType == 2) {
$scoreAsProgress = $learnPath->getUseScoreAsProgress();
$form->addElement(
'checkbox',
'extra_use_score_as_progress',
[null, get_lang('LearnpathUseScoreAsProgressComment')],
get_lang('LearnpathUseScoreAsProgress')
);
$defaults['extra_use_score_as_progress'] = $scoreAsProgress;
}
$options = learnpath::getIconSelect();
if (!empty($options)) {
@ -247,10 +265,15 @@ if (!empty($options)) {
}
$extraField = new ExtraField('lp');
$extra = $extraField->addElements($form, $lpId, ['lp_icon']);
$extra = $extraField->addElements(
$form,
$lpId,
['lp_icon', 'use_score_as_progress']
);
$skillList = Skill::addSkillsToForm($form, ITEM_TYPE_LEARNPATH, $lpId);
// Submit button
$form->addButtonSave(get_lang('SaveLPSettings'));

@ -55,7 +55,7 @@ if (isset($_POST) && $is_error) {
$file_base_name = str_replace('.'.$extension, '', $filename);
$new_dir = api_replace_dangerous_char(trim($file_base_name));
$type = learnpath::get_package_type(
$type = learnpath::getPackageType(
$_FILES['user_file']['tmp_name'],
$_FILES['user_file']['name']
);
@ -162,7 +162,7 @@ if (isset($_POST) && $is_error) {
Display::return_message(get_lang('UplFileTooBig'))
);
}
$type = learnpath::get_package_type($s, basename($s));
$type = learnpath::getPackageType($s, basename($s));
switch ($type) {
case 'scorm':

@ -35,6 +35,10 @@ $oLP = UnserializeApi::unserialize(
'lp',
Session::read('lpobject')
);
if (!is_object($oLP)) {
error_log('New LP - scorm_api - Could not load oLP object', 0);
exit;
}
/** @var learnpathItem $oItem */
$oItem = isset($oLP->items[$oLP->current]) ? $oLP->items[$oLP->current] : null;
@ -50,6 +54,7 @@ header('Content-type: text/javascript');
?>var scorm_logs=<?php echo (empty($oLP->scorm_debug) or (!api_is_course_admin() && !api_is_platform_admin())) ? '0' : '3'; ?>; //debug log level for SCORM. 0 = none, 1=light, 2=a lot, 3=all - displays logs in log frame
var lms_logs = 0; //debug log level for LMS actions. 0=none, 1=light, 2=a lot, 3=all
var score_as_progress = <?php echo (empty($oLP->getUseScoreAsProgress())? 'false':'true'); ?>;
// API Object initialization (eases access later on)
function APIobject() {
@ -650,6 +655,9 @@ function LMSSetValue(param, val) {
if (param == "cmi.core.score.raw") {
olms.score= val;
olms.updatable_vars_list['cmi.core.score.raw']=true;
if (score_as_progress) {
update_progress_bar(val, olms.max, '%');
}
return_value='true';
} else if ( param == "cmi.core.score.max") {
olms.max = val;
@ -2410,9 +2418,9 @@ function attach_glossary_into_scorm(type) {
*/
function update_time_bar(nbr_complete, nbr_total, mode)
{
logit_lms('update_progress_bar('+nbr_complete+', '+nbr_total+', '+mode+')',3);
logit_lms('update_time_bar('+nbr_complete+', '+nbr_total+', '+mode+')',3);
logit_lms(
'update_progress_bar with params: lms_lp_id= ' + olms.lms_lp_id +
'update_time_bar with params: lms_lp_id= ' + olms.lms_lp_id +
', lms_view_id= '+ olms.lms_view_id + ' lms_user_id= '+ olms.lms_user_id,
3
);

Loading…
Cancel
Save