This commit is contained in:
Xes
2025-08-14 22:37:50 +02:00
parent fb6d5d5926
commit 3641e93527
9156 changed files with 1813532 additions and 0 deletions

112
main/lp/LpAiHelper.php Normal file
View File

@@ -0,0 +1,112 @@
<?php
/* For licensing terms, see /license.txt */
class LpAiHelper
{
/**
* AiHelper constructor.
* Requires plugin ai_helper to connect to the api.
*/
public function __construct()
{
if (!('true' === api_get_plugin_setting('ai_helper', 'tool_enable') && 'true' === api_get_plugin_setting('ai_helper', 'tool_lp_enable'))) {
return false;
}
}
/**
* Get the form to generate Lp items using Ai Helper.
*/
public function aiHelperForm()
{
$plugin = AiHelperPlugin::create();
$availableApis = $plugin->getApiList();
$configuredApi = $plugin->get('api_name');
$hasSingleApi = count($availableApis) === 1 || isset($availableApis[$configuredApi]);
$form = new FormValidator(
'lp_ai_generate',
'post',
api_get_self()."?".api_get_cidreq(),
null
);
$form->addElement('header', get_lang('LpAiGenerator'));
if ($hasSingleApi) {
$apiName = $availableApis[$configuredApi] ?? $configuredApi;
$form->addHtml('<div style="margin-bottom: 10px; font-size: 14px; color: #555;">'
.sprintf(get_lang('UsingAIProviderX'), '<strong>'.htmlspecialchars($apiName).'</strong>').'</div>');
}
$form->addElement('text', 'lp_name', [get_lang('LpAiTopic'), get_lang('LpAiTopicHelp')]);
$form->addRule('lp_name', get_lang('ThisFieldIsRequired'), 'required');
$form->addElement('number', 'nro_items', [get_lang('LpAiNumberOfItems'), get_lang('LpAiNumberOfItemsHelper')]);
$form->addRule('nro_items', get_lang('ThisFieldIsRequired'), 'required');
$form->addElement('number', 'words_count', [get_lang('LpAiWordsCount'), get_lang('LpAiWordsCountHelper')]);
$form->addRule('words_count', get_lang('ThisFieldIsRequired'), 'required');
$form->addElement('checkbox', 'add_lp_quiz', null, get_lang('AddTestAfterEachPage'), ['id' => 'add-lp-quiz']);
$form->addHtml('<div id="lp-quiz-area">');
$form->addElement('number', 'nro_questions', [get_lang('NumberOfQuestions'), get_lang('AIQuestionsGeneratorNumberHelper')]);
$form->addRule('nro_questions', get_lang('ThisFieldIsRequired'), 'required');
$form->addHtml('</div>');
$form->setDefaults(['nro_questions' => 2]);
$generateUrl = api_get_path(WEB_PLUGIN_PATH).'ai_helper/tool/learnpath.php';
$language = api_get_interface_language();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$redirectSuccess = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&isStudentView=false&lp_id=';
$form->addHtml('<script>
$(function () {
$("#lp-quiz-area").hide();
$("#add-lp-quiz").change(function() {
$("#lp-quiz-area").toggle(this.checked);
});
$("#create-lp-ai").on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var btnGenerate = $(this);
var lpName = $("[name=\'lp_name\']").val();
var nroItems = parseInt($("[name=\'nro_items\']").val());
var wordsCount = parseInt($("[name=\'words_count\']").val());
var addTests = $("#add-lp-quiz").is(":checked");
var nroQuestions = parseInt($("[name=\'nro_questions\']").val());
var provider = "'.$configuredApi.'";
if (lpName && nroItems > 0 && wordsCount > 0) {
if (addTests && (nroQuestions <= 0 || nroQuestions > 5)) {
alert("'.sprintf(get_lang('NumberOfQuestionsLimitedFromXToY'), 1, 5).'");
return false;
}
btnGenerate.attr("disabled", true).text("'.get_lang('PleaseWaitThisCouldTakeAWhile').'");
$.getJSON("'.$generateUrl.'", {
"lp_name": lpName,
"nro_items": nroItems,
"words_count": wordsCount,
"language": "'.$language.'",
"course_code": "'.$courseCode.'",
"session_id": "'.$sessionId.'",
"add_tests": addTests,
"nro_questions": nroQuestions,
"ai_provider": provider
}).done(function (data) {
btnGenerate.attr("disabled", false).text("'.get_lang('Generate').'");
if (data.success) {
location.href = "'.$redirectSuccess.'" + data.lp_id;
} else {
alert(data.text || "'.get_lang('NoSearchResults').'. '.get_lang('PleaseTryAgain').'");
}
});
}
});
});
</script>');
$form->addButton('create_lp_button', get_lang('LearnpathAddLearnpath'), '', 'default', 'default', null, ['id' => 'create-lp-ai']);
echo $form->returnForm();
}
}

View File

@@ -0,0 +1,159 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class LpIndexGenerator.
*/
class LpIndexGenerator
{
/**
* @var learnpath
*/
private $lp;
/**
* @var array
*/
private $courseInfo;
/**
* @var DOMDocument
*/
private $domDocument;
public function __construct(learnpath $lp)
{
$this->lp = $lp;
$this->courseInfo = api_get_course_info();
$this->domDocument = new DOMDocument();
$this->generateHtml();
}
public function generate(): string
{
$this->generateToc();
$indexHtml = @$this->domDocument->saveHTML();
return api_utf8_decode_xml($indexHtml);
}
private function generateHtml()
{
$iso = api_get_language_isocode();
$title = api_utf8_encode($this->lp->get_name());
$this->domDocument->loadHTML(
'<!doctype html>
<html lang="'.$iso.'">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width,user-scalable=no,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>'.$title.'</title>
<link rel="stylesheet" href="assets/bootstrap/bootstrap.min.css">
<style>
.page-header { margin-top: 0; padding-top: 10px; }
#toc__ul { height: calc(100vh - 80px - 15px); overflow: auto; }
</style>
</head>
<body>
<div class="container-fluid">
<h1 class="page-header">'.$title.'</h1>
<div class="row">
<div class="col-md-3">
<ul id="toc__ul"></ul>
</div>
<div class="col-md-9">
<div class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" id="content__iframe" name="content-frame"
src="" frameborder="0"></iframe>
</div>
</div>
</div>
</div>
</body>
</html>'
);
}
private function generateToc()
{
$ulNode = $this->domDocument->getElementById('toc__ul');
$folderName = 'document';
$pathToRemove = '';
$pathToReplace = '';
$result = $this->lp->generate_lp_folder($this->courseInfo);
if (isset($result['dir']) && strpos($result['dir'], 'learning_path')) {
$pathToRemove = 'document'.$result['dir'];
$pathToReplace = $folderName;
}
if ($this->lp->ref === 'chamilo_scorm_export') {
$pathToRemove = 'scorm/'.$this->lp->path.'/document/';
}
foreach ($this->lp->ordered_items as $itemId) {
$item = $this->lp->items[$itemId];
if (!in_array($item->type, [TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION])) {
$myFilePath = $item->get_file_path('scorm/'.$this->lp->path.'/');
$itemFilePath = $myFilePath;
if (!empty($pathToRemove)) {
$itemFilePath = str_replace($pathToRemove, $pathToReplace, $myFilePath);
if ($this->lp->ref === 'chamilo_scorm_export') {
$pathToRemove = 'scorm/'.$this->lp->path.'/';
$itemFilePath = 'document/'.str_replace($pathToRemove, '', $myFilePath);
}
}
} elseif (TOOL_LINK === $item->type) {
$itemFilePath = "link_{$item->get_id()}.html";
} elseif (TOOL_QUIZ === $item->type) {
$itemFilePath = "quiz_{$item->get_id()}.html";
} else {
continue;
}
$itemText = htmlspecialchars(api_utf8_encode($item->get_title()), ENT_QUOTES);
$liNode = $this->domDocument->createElement('li');
$liNode->setAttribute('id', "item_{$item->get_id()}");
if (!empty($item->parent) && $item->parent != 0) {
$possibleItemParent = $this->lp->get_scorm_xml_node(
$ulNode->childNodes,
"item_$item->parent",
'li',
'id'
);
if ($possibleItemParent instanceof DOMElement) {
$innerUlNode = $possibleItemParent->getElementsByTagName('ul')->item(0)
?: $this->domDocument->createElement('ul');
$innerUlNode->appendChild($liNode);
$possibleItemParent->appendChild($innerUlNode);
}
} else {
$ulNode->appendChild($liNode);
}
if (!empty($itemFilePath) && $itemFilePath !== 'document/') {
$aNode = $this->domDocument->createElement('a', $itemText);
$aNode->setAttribute('href', $itemFilePath);
$aNode->setAttribute('target', 'content-frame');
$liNode->appendChild($aNode);
} else {
$liNode->appendChild(
$this->domDocument->createTextNode($itemText)
);
}
}
}
}

View File

@@ -0,0 +1,75 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class LpItemOrderList.
*
* Classes to create a special data structure to manipulate LP Items used only in this file.
*/
class LpItemOrderList
{
public $list = [];
/**
* LpItemOrderList constructor.
*/
public function __construct()
{
$this->list = [];
}
/**
* @param int $parentId
*
* @return LpItemOrderList
*/
public function getItemWithSameParent($parentId)
{
$list = new LpItemOrderList();
for ($i = 0; $i < count($this->list); $i++) {
if ($this->list[$i]->parent_item_id == $parentId) {
$list->add($this->list[$i]);
}
}
return $list;
}
/**
* @param array $list
*/
public function add($list)
{
$this->list[] = $list;
}
/**
* @return array
*/
public function getListOfParents()
{
$result = [];
foreach ($this->list as $item) {
if (!in_array($item->parent_item_id, $result)) {
$result[] = $item->parent_item_id;
}
}
return $result;
}
/**
* @param int $id
* @param int $value
* @param string $parameter
*/
public function setParametersForId($id, $value, $parameter)
{
for ($i = 0; $i < count($this->list); $i++) {
if ($this->list[$i]->id == $id) {
$this->list[$i]->$parameter = $value;
break;
}
}
}
}

26
main/lp/LpOrderItem.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class LpOrderItem.
*/
class LpOrderItem
{
public $id = 0;
public $parent_item_id = 0;
public $previous_item_id = 0;
public $next_item_id = 0;
public $display_order = 0;
/**
* LpOrderItem constructor.
*
* @param int $id
* @param int $parentId
*/
public function __construct($id = 0, $parentId = 0)
{
$this->id = $id;
$this->parent_item_id = $parentId;
}
}

15
main/lp/README.txt Normal file
View File

@@ -0,0 +1,15 @@
To use the new SCORM tool, first run the scorm_update_db.php from where it is in the (new)scorm directory.
This will update all courses databases by adding 4 tables (lp, lp_item, lp_view and lp_item_view) and add an lp_type table in the main database.
If you had installed a development version of the newscorm tool, it is a good idea to also run scorm_update_db_alter.php to make sure the latest changes are in.
Then go to the database and modify the 'tool' table in each course to add the 'newscorm' tool (copy the tool row from 'learnpath' and change the name). Make sure to select newscorm/lp_list.php as a starting point for this tool (although index.php should work as well).
You can do this by using the scorm_migrate_hometools.php script now.
If you are using one of these scripts, it is vital you check by yourself that all the courses have been migrated accurately. For example, you will need to check that all links to learning paths in the course homepage are pointing towards the newscorm/ directory, not scorm/ or learnpath/.
Go to one of your courses' homepage and try the new SCORM tool as you would normally do with the old SCORM tool.
Please note this DOES NOT manage the Dokeos learnpaths yet.
For more detailed development information, please read the corresponding Wiki page: http://www.dokeos.com/wiki/index.php/SCORM_tool_redesign
For any problem contact me directly at <yannick.warnier@beeznest.com>

1074
main/lp/aicc.class.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class aiccBlock
* Class defining the Block elements in an AICC Course Structure file.
*
* Container for the aiccResource class that deals with elemens from AICC Course Structure file
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @license GNU/GPL
*/
class aiccBlock extends learnpathItem
{
public $identifier = '';
public $members = [];
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormResource
* object from database records or from the array given as second param.
*
* @param string $type Type of construction needed ('db' or 'config', default = 'config')
* @param mixed $params Depending on the type given, DB id for the lp_item or parameters array
*/
public function __construct($type = 'config', $params)
{
if (isset($params)) {
switch ($type) {
case 'db':
//TODO: Implement this way of object creation.
break;
case 'config': // Do the same as the default.
default:
foreach ($params as $a => $value) {
switch ($a) {
case 'system_id':
$this->identifier = strtolower($value);
break;
case 'member':
if (false !== strstr($value, ',')) {
$temp = explode(',', $value);
foreach ($temp as $val) {
if (!empty($val)) {
$this->members[] = $val;
}
}
}
break;
}
}
}
}
}
}

147
main/lp/aiccItem.class.php Normal file
View File

@@ -0,0 +1,147 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class aiccItem
* This class handles the elements from an AICC Descriptor file.
* Container for the aiccItem class that deals with AICC Assignable Units (AUs).
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @license GNU/GPL
*/
class aiccItem extends learnpathItem
{
public $identifier = ''; // AICC AU's system_id
public $identifierref = '';
public $parameters = ''; // AICC AU's web_launch
public $title = ''; // no AICC equivalent
public $sub_items = []; // AICC elements (des)
//public $prerequisites = ''; // defined in learnpathItem.class.php
//public $max_score = ''; // defined in learnpathItem
//public $path = ''; // defined in learnpathItem
public $maxtimeallowed = '00:00:00'; // AICC AU's max_time_allowed
public $timelimitaction = ''; // AICC AU's time_limit_action
public $masteryscore = ''; // AICC AU's mastery_score
public $core_vendor = ''; // AICC AU's core_vendor
public $system_vendor = ''; // AICC AU's system_vendor
public $au_type = ''; // AICC AU's type
public $command_line = ''; // AICC AU's command_line
public $debug = 0;
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormItem
* object from database records or from the array given as second parameter.
*
* @param string Type of construction needed ('db' or 'config', default = 'config')
* @param mixed Depending on the type given, DB id for the lp_item or parameters array
*/
public function __construct($type = 'config', $params = [], $course_id = null)
{
if (isset($params)) {
switch ($type) {
case 'db':
parent::__construct($params, api_get_user_id(), $course_id);
$this->aicc_contact = false;
//TODO: Implement this way of metadata object creation.
// no break
case 'config': // Do the same as the default.
default:
foreach ($params as $a => $value) {
switch ($a) {
case 'system_id':
$this->identifier = Database::escape_string(
strtolower($value)
);
break;
case 'type':
$this->au_type = Database::escape_string($value);
break;
case 'command_line':
$this->command_line = Database::escape_string(
$value
);
break;
case 'max_time_allowed':
$this->maxtimeallowed = Database::escape_string(
$value
);
break;
case 'time_limit_action':
$this->timelimitaction = Database::escape_string(
$value
);
break;
case 'max_score':
$this->max_score = Database::escape_string($value);
break;
case 'core_vendor':
$this->core_vendor = Database::escape_string(
$value
);
break;
case 'system_vendor':
$this->system_vendor = Database::escape_string(
$value
);
break;
case 'file_name':
$this->path = Database::escape_string($value);
break;
case 'mastery_score':
$this->masteryscore = Database::escape_string(
$value
);
break;
case 'web_launch':
$this->parameters = Database::escape_string($value);
break;
}
}
}
}
}
/**
* Builds a flat list with the current item and calls itself recursively on all children.
*
* @param array Reference to the array to complete with the current item
* @param int Optional absolute order (pointer) of the item in this learning path
* @param int Optional relative order of the item at this level
* @param int Optional level. If not given, assumes it's level 0
*/
public function get_flat_list(&$list, &$abs_order, $rel_order = 1, $level = 0)
{
$list[] = [
'au_type' => $this->au_type,
'command_line' => $this->command_line,
'core_vendor' => $this->core_vendor,
'identifier' => $this->identifier,
'identifierref' => $this->identifierref,
'masteryscore' => $this->masteryscore,
'maxtimeallowed' => $this->maxtimeallowed,
'level' => $level,
'parameters' => $this->parameters,
'prerequisites' => (!empty($this->prereq_string) ? $this->prereq_string : ''),
'timelimitaction' => $this->timelimitaction,
];
$abs_order++;
$i = 1;
foreach ($this->sub_items as $id => $dummy) {
$oSubitem = &$this->sub_items[$id];
$oSubitem->get_flat_list($list, $abs_order, $i, $level + 1);
$i++;
}
}
/**
* Save function. Uses the parent save function and adds a layer for AICC.
*
* @param bool Save from URL params (1) or from object attributes (0)
*/
public function save($from_outside = true, $prereqs_complete = false)
{
parent::save($from_outside, $prereqs_complete = false);
// Under certain conditions, the scorm_contact should not be set, because no scorm signal was sent.
$this->aicc_contact = true;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class aiccObjective
* Class defining the Block elements in an AICC Course Structure file.
* Container for the aiccResource class that deals with elemens from AICC Objectives file.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @license GNU/GPL
*/
class aiccObjective extends learnpathItem
{
public $identifier = '';
public $members = [];
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormResource
* object from database records or from the array given as second param.
*
* @param string Type of construction needed ('db' or 'config', default = 'config')
* @param mixed Depending on the type given, DB id for the lp_item or parameters array
*/
public function __construct($type = 'config', $params)
{
if (isset($params)) {
switch ($type) {
case 'db':
// TODO: Implement this way of object creation.
break;
case 'config': // Do the same as the default.
default:
foreach ($params as $a => $value) {
switch ($a) {
case 'system_id':
$this->identifier = strtolower($value);
break;
case 'member':
if (false !== strstr($value, ',')) {
$temp = explode(',', $value);
foreach ($temp as $val) {
if (!empty($val)) {
$this->members[] = $val;
}
}
}
break;
}
}
}
}
}
}

View File

@@ -0,0 +1,55 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class defining the elements from an AICC Descriptor file.
* Container for the aiccResource class that deals with elemens from AICC Descriptor file.
*
* @package chamilo.learnpath
*
* @author Yannick Warnier <ywarnier@beeznest.org>
* @license GNU/GPL
*/
class aiccResource
{
public $identifier = '';
public $title = '';
public $description = '';
public $developer_id = '';
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormResource
* object from database records or from the array given as second param.
*
* @param string $type Type of construction needed ('db' or 'config', default = 'config')
* @param mixed $params Depending on the type given, DB id for the lp_item or parameters array
*/
public function __construct($type = 'config', $params)
{
if (isset($params)) {
switch ($type) {
case 'db':
// TODO: Implement this way of object creation.
break;
case 'config': // Do the same as the default.
default:
foreach ($params as $a => $value) {
switch ($a) {
case 'system_id':
$this->identifier = strtolower($value);
break;
case 'title':
$this->title = $value;
// no break - @todo check this, not sure the intention is to have description=title
case 'description':
$this->description = $value;
break;
case 'developer_id':
$this->developer_id = $value;
break;
}
}
}
}
}
}

785
main/lp/aicc_api.php Normal file
View File

@@ -0,0 +1,785 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* API event handler functions for AICC / CMIv4 in API communication mode.
*
* @author Denes Nagy <darkden@freemail.hu>
* @author Yannick Warnier <ywarnier@beeznest.org>
*
* @version v 1.0
*
* @package chamilo.learnpath
*
* @license GNU/GPL
*/
/**
* This script is divided into three sections.
* The first section (below) is the initialisation part.
* The second section is the AICC object part
* The third section defines the event handlers for Chamilo's internal messaging
* and frames refresh.
*
* This script implements the API messaging for AICC. The HACP messaging is
* made by another set of scripts.
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
// Load common libraries using a compatibility script to bridge between 1.6 and 1.8.
require_once __DIR__.'/../inc/global.inc.php';
// Is this needed? This is probabaly done in the header file.
$file = Session::read('file');
/** @var learnpath $oLP */
$oLP = UnserializeApi::unserialize('lp', Session::read('lpobject'));
$oItem = $oLP->items[$oLP->current];
if (!is_object($oItem)) {
error_log('New LP - scorm_api - Could not load oItem item', 0);
exit;
}
$autocomplete_when_80pct = 0;
/* JavaScript Functions */
?>var scorm_logs=<?php echo empty($oLP->scorm_debug) ? '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
//logit_lms('scormfunctions.php included',0);
function APIobject() {
this.LMSInitialize=LMSInitialize;
this.LMSGetValue=LMSGetValue;
this.LMSSetValue=LMSSetValue;
this.LMSCommit=LMSCommit;
this.LMSFinish=LMSFinish;
this.LMSGetLastError=LMSGetLastError;
this.LMSGetErrorString=LMSGetErrorString;
this.LMSGetDiagnostic=LMSGetDiagnostic;
}
// It is not sure that the scos use the above declarations.
API = new APIobject(); //for scorm 1.2
var G_NoError = 0;
var G_GeneralException = 101;
var G_ServerBusy = 102;
var G_InvalidArgumentError = 201;
var G_ElementCannotHaveChildren = 202;
var G_ElementIsNotAnArray = 203;
var G_NotInitialized = 301;
var G_NotImplementedError = 401;
var G_InvalidSetValue = 402;
var G_ElementIsReadOnly = 403;
var G_ElementIsWriteOnly = 404;
var G_IncorrectDataType = 405;
var G_LastError = G_NoError ;
var commit = false ;
// Strictly SCORM variables.
var score=<?php echo $oItem->get_score(); ?>;
var max=<?php echo $oItem->get_max(); ?>;
var min=<?php echo $oItem->get_min(); ?>;
var lesson_status='<?php echo $oItem->get_status(); ?>';
var session_time='<?php echo $oItem->get_scorm_time('js'); ?>';
var suspend_data = '<?php echo $oItem->get_suspend_data(); ?>';
var lesson_location = '<?php echo $oItem->get_lesson_location(); ?>';
var total_time = '<?php echo $oItem->get_scorm_time('js'); ?>';
// Chamilo internal variables.
var saved_lesson_status = 'not attempted';
var lms_lp_id = <?php echo $oLP->get_id(); ?>;
var lms_item_id = <?php echo $oItem->get_id(); ?>;
//var lms_new_item_id = 0; //temporary value (only there between a load_item() and a LMSInitialize())
var lms_been_synchronized = 0;
var lms_initialized = 0;
var lms_total_lessons = <?php echo $oLP->get_total_items_count(); ?>;
var lms_complete_lessons = <?php echo $oLP->get_complete_items_count(); ?>;
var lms_progress_bar_mode = '<?php echo $oLP->progress_bar_mode; ?>';
if(lms_progress_bar_mode == ''){lms_progress_bar_mode='%';}
var lms_view_id = '<?php echo $oLP->get_view(null, $_user['user_id']); ?>';
if(lms_view_id == ''){ lms_view_id = 1;}
var lms_user_id = '<?php echo $_user['user_id']; ?>';
var lms_next_item = '<?php echo $oLP->get_next_item_id(); ?>';
var lms_previous_item = '<?php echo $oLP->get_previous_item_id(); ?>';
var lms_lp_type = '<?php echo $oLP->get_type(); ?>';
var lms_item_type = '<?php echo $oItem->get_type(); ?>';
// Backup for old values.
var old_score = 0;
var old_max = 0;
var old_min = 0;
var old_lesson_status = '';
var old_session_time = '';
var old_suspend_data = '';
var lms_old_item_id = 0;
function LMSInitialize() { //this is the initialize function of all APIobjects
logit_scorm('LMSInitialise()',0);
lms_initialized=1;
return('true');
}
function LMSGetValue(param) {
//logit_scorm("LMSGetValue('"+param+"')",1);
var result='';
if(param=='cmi.core._children' || param=='cmi.core_children'){
result='entry, exit, lesson_status, student_id, student_name, lesson_location, total_time, credit, lesson_mode, score, session_time';
}else if(param == 'cmi.core.entry'){
result='';
}else if(param == 'cmi.core.exit'){
result='';
}else if(param == 'cmi.core.lesson_status'){
if(lesson_status != '') {
result=lesson_status;
}
else{
result='not attempted';
}
}else if(param == 'cmi.core.student_id'){
result='<?php echo $_user['user_id']; ?>';
}else if(param == 'cmi.core.student_name'){
<?php
$who = addslashes(api_get_person_name($_user['firstName'], $_user['lastName']));
echo "result='$who';";
?>
}else if(param == 'cmi.core.lesson_location'){
result=lesson_location;
}else if(param == 'cmi.core.total_time'){
result=total_time;
}else if(param == 'cmi.core.score._children'){
result='raw,min,max';
}else if(param == 'cmi.core.score.raw'){
result=score;
}else if(param == 'cmi.core.score.max'){
result=max;
}else if(param == 'cmi.core.score.min'){
result=min;
}else if(param == 'cmi.core.score'){
result=score;
}else if(param == 'cmi.core.credit'){
result='no-credit';
}else if(param == 'cmi.core.lesson_mode'){
result='normal';
}else if(param == 'cmi.suspend_data'){
result='<?php echo $oItem->get_suspend_data(); ?>';
}else if(param == 'cmi.launch_data'){
result='';
}else if(param == 'cmi.objectives._count'){
result='<?php echo $oItem->get_view_count(); ?>';
}
/*
// Switch not working??? WTF???
switch(param) {
case 'cmi.core._children':
result='entry, exit, lesson_status, student_id, student_name, lesson_location, total_time, credit, lesson_mode, score, session_time';
break;
case 'cmi.core_children':
result='entry, exit, lesson_status, student_id, student_name, lesson_location, total_time, credit, lesson_mode, score, session_time';
break;
case 'cmi.core.entry':
result='';
break;
case 'cmi.core.exit':
result='';
break;
case 'cmi.core.lesson_status':
if(lesson_status != '') {
result=lesson_status;
}
else{
result='not attempted';
}
break;
case 'cmi.core.student_id':
result='<?php echo $_user['user_id']; ?>';
break;
case 'cmi.core.student_name':
<?php
$who = addslashes(api_get_person_name($_user['firstName'], $_user['lastName']));
echo "result='$who';";
?> break;
case 'cmi.core.lesson_location':
result='';
break;
case 'cmi.core.total_time':
result=total_time;
break;
case 'cmi.core.score._children':
result='raw,min,max';
break;
case 'cmi.core.score.raw':
result=score;
break;
case 'cmi.core.score.max':
result=max;
break;
case 'cmi.core.score.min':
result=min;
break;
case 'cmi.core.score':
result=score;
break;
case 'cmi.core.credit':
result='no-credit';
break;
case 'cmi.core.lesson_mode':
result='normal';
break;
case 'cmi.suspend_data':
result='<?php echo $oItem->get_suspend_data(); ?>';
break;
case 'cmi.launch_data':
result='';
break;
case 'cmi.objectives._count':
result='<?php echo $oItem->get_view_count(); ?>';
break;
default:
result='';
break;
}
*/
logit_scorm("LMSGetValue('"+param+"') returned '"+result+"'",1);
return result;
}
function LMSSetValue(param, val) {
logit_scorm("LMSSetValue('"+param+"','"+val+"')",0);
switch(param) {
case 'cmi.core.score.raw' : score= val ; break;
case 'cmi.core.score.max' : max = val; break;
case 'cmi.core.score.min' : min = val; break;
case 'cmi.core.lesson_location' : lesson_location = val;break;
case 'cmi.core.lesson_status' :
saved_lesson_status = lesson_status;
lesson_status = val;
<?php if ($oLP->mode != 'fullscreen') {
?>
//var update = update_toc(lesson_status,lms_item_id);
<?php
} ?>
break;
case 'cmi.completion_status' : lesson_status = val; break; //1.3
case 'cmi.core.session_time' : session_time = val; break;
case 'cmi.score.scaled' : score = val ; break; //1.3
case 'cmi.success_status' : success_status = val; break; //1.3
case 'cmi.suspend_data' : suspend_data = val; break;
}
//var update = update_toc();
//var update_progress = update_progress_bar();
<?php
if ($oLP->force_commit == 1) {
echo " var mycommit = LMSCommit('force');";
}
?>
return(true);
}
function savedata(origin) { //origin can be 'commit', 'finish' or 'terminate'
<?php if ($autocomplete_when_80pct) {
?>
if( ( lesson_status == 'incomplete') && (score >= (0.8*max) ) ){
lesson_status = 'completed';
}
<?php
}?>
param = 'id='+lms_item_id+'&origin='+origin+'&score='+score+'&max='+max+'&min='+min+'&lesson_status='+lesson_status+'&time='+session_time+'&suspend_data='+suspend_data;
url="http://<?php
$self = api_get_self();
$url = $_SERVER['HTTP_HOST'].$self;
$url = substr($url, 0, -14); // 14 is the length of this file's name (/scorm_api.php).
echo $url;
?>/lp_controller.php?<?php echo api_get_cidreq(); ?>&action=save&lp_id=<?php echo $oLP->get_id(); ?>&" + param + "";
logit_lms('saving data (status='+lesson_status+')',1);
xajax_save_item(lms_lp_id, lms_user_id, lms_view_id, lms_item_id, score, max, min, lesson_status, session_time, suspend_data, lesson_location);
//xajax_update_pgs();
//xajax_update_toc();
}
function LMSCommit(val) {
logit_scorm('LMSCommit()',0);
commit = true ;
savedata('commit');
return('true');
}
function LMSFinish(val) {
if ( !commit ) {
logit_scorm('LMSFinish() (no LMSCommit())',1);
}
if ( commit ) {
logit_scorm('LMSFinish() called',1);
savedata('finish');
}
return('true');
}
function LMSGetLastError() {
logit_scorm('LMSGetLastError()',1);
return(G_LastError);
}
function LMSGetErrorString(errCode){
logit_scorm('LMSGetErrorString()',1);
return('No error !');
}
function LMSGetDiagnostic(errCode){
logit_scorm('LMSGetDiagnostic()',1);
return(API.LMSGetLastError());
}
<?php
/**
* Chamilo-specific code that deals with event handling and inter-frames
* messaging/refreshing.
* Note that from now on, the Chamilo JS code in this library will act as
* a controller, of the MVC pattern, and receive all requests for frame
* updates, then redispatch to any frame concerned.
*/
?>
/**
* Defining the AJAX-object class to be made available from other frames.
*/
function XAJAXobject() {
this.xajax_switch_item_details=xajax_switch_item_details;
this.switch_item=switch_item;
}
//it is not sure that the scos use the above declarations
oXAJAX = new XAJAXobject();
oxajax = new XAJAXobject();
/**
* Cross-browser event handling by Scott Andrew
* @param element Element that needs an event attached
* @param string Event type (load, unload, click, keyDown, ...)
* @param string Function name (the event handler)
* @param string used in addEventListener
*/
function addEvent(elm, evType, fn, useCapture){
if(elm.addEventListener){
elm.addEventListener(evType, fn, useCapture);
return true;
}else if (elm.attachEvent){
var r = elm.attachEvent('on' + evType, fn);
}else{
elm['on'+evType] = fn;
}
}
/**
* Add listeners to the page objects. This has to be defined for
* the current context as it acts on objects that should exist
* on the page
*/
function addListeners(){
//exit if the browser doesn't support ID or tag retrieval
logit_lms('Entering addListeners()',2);
if(!document.getElementsByTagName){
logit_lms("getElementsByTagName not available",2);
return;
}
if(!document.getElementById){
logit_lms("getElementById not available",2);
return;
}
//assign event handlers to objects
if(lms_lp_type==1 || lms_item_type=='asset'){
logit_lms('Chamilo LP or asset',2);
// If this path is a Chamilo learnpath, then start manual save
// when something is loaded in there.
var myelem = document.getElementById('content_id');
if(!myelem){logit_lms("Impossible to find content_id element in document",2);}
addEvent(myelem,'unload',chamilo_save_asset,false);
logit_lms('Added event listener on content_id for unload',2);
}
logit_lms('Quitting addListeners()',2);
}
/**
* Load an item into the content frame:
* - making sure the previous item status have been saved
* - first updating the current item ID (to save the right item)
* - updating the frame src
*/
function load_item(item_id,url){
if(document.getElementById('content_id'))
{
logit_lms('Loading item '+item_id,2);
var cont_f = document.getElementById('content_id');
if(cont_f.src){
lms_old_item_id = lms_item_id;
var lms_new_item_id = item_id;
//load new content page into content frame
if(lms_lp_type==1 || lms_item_type=='asset'){
chamilo_save_asset();
}
cont_f.src = url;
update_toc('unhighlight',lms_old_item_id);
update_toc('highlight',item_id);
/* legacy code
lms_been_synchronized = 0;
lms_initialized = 0;
if(lms_lp_type==1 || lms_item_type=='asset'){
lms_item_id = lms_new_item_id;
}*/
return true;
}
logit_lms('cont_f.src has no properties',0);
}
logit_lms('content_id has no properties',0);
return false;
}
/**
* Save a Chamilo learnpath item's time and mark as completed upon
* leaving it
*/
function chamilo_save_asset(){
//var linkparams = 'id='+lms_item_id+'&score='+score+'&max='+max+'&min='+min+'&lesson_status='+lesson_status+'&time='+session_time+'&suspend_data='+suspend_data;
//var url = "<?php echo api_get_path(WEB_CODE_PATH).'lp/lp_controller.php'; ?>?action=save&" + linkparams + "";
logit_lms('chamilo_save_asset: '+url,0);
//frames["message_name"].src = url;
xajax_save_item(lms_lp_id, lms_user_id, lms_view_id, lms_item_id, score, max, min, lesson_status, session_time, suspend_data, lesson_location);
}
/**
* Logs information about SCORM messages into the log frame
* @param string Message to log
* @param integer Priority (0 for top priority, 3 for lowest)
*/
function logit_scorm(message,priority) {
if (scorm_logs) {
log_in_log("SCORM: " + message);
}
return false;
/*if(frames["lp_log_name"] && scorm_logs>priority){
frames["lp_log_name"].document.getElementById("log_content").innerHTML += "AICC: " + message + "<br/>";
}*/
}
function log_in_log(message) {
var ua = $.browser;
if (ua.mozilla) {
console.log(message);
} else {
if (window.console) {
window.console.log(message);
}
}
}
/**
* Logs information about LMS activity into the log frame
* @param string Message to log
* @param integer Priority (0 for top priority, 3 for lowest)
*/
function logit_lms(message,priority) {
/*
if(frames["lp_log_name"] && lms_logs>priority){
frames["lp_log_name"].document.getElementById("log_content").innerHTML += "LMS: " + message + "<br/>";
}*/
if (scorm_logs) {
log_in_log("LMS: " + message);
}
}
/**
* update the Table Of Contents frame, by changing CSS styles, mostly
* @param string Action to be taken
* @param integer Item id to update
*/
function update_toc(update_action,update_id)
{
<?php if ($oLP->mode != 'fullscreen') {
?>
var myframe = frames["toc_name"];
var myelem = myframe.document.getElementById("toc_"+update_id);
var myelemimg = myframe.document.getElementById("toc_img_"+update_id);
logit_lms('update_toc('+update_action+','+update_id+')',2);
if(update_id != 0){
switch(update_action){
case 'unhighlight':
myelem.className = "scorm_item";
break;
case 'highlight':
myelem.className = "scorm_item_highlight";
break;
case 'not attempted':
if(myelemimg.src != '../img/notattempted.gif'){
myelemimg.src = "../img/notattempted.gif";
myelemimg.alt = "not attempted";
}
break;
case 'incomplete':
if(myelemimg.src != '../img/incomplete.png'){
myelemimg.src = "../img/incomplete.png";
myelemimg.alt = "incomplete";
}
break;
case 'completed':
if(myelemimg.src != '../img/completed.png'){
myelemimg.src = "../img/completed.png";
myelemimg.alt = "completed";
}
break;
case 'failed':
if(myelemimg.src != '../img/failed.png'){
myelemimg.src = "../img/failed.png";
myelemimg.alt = "failed";
}
break;
case 'passed':
if(myelemimg.src != '../img/completed.png' && myelemimg.alt != 'passed'){
myelemimg.src = "../img/completed.png";
myelemimg.alt = "passed";
}
break;
case 'browsed':
if(myelemimg.src != '../img/completed.png' && myelemimg.alt != 'browsed'){
myelemimg.src = "../img/completed.png";
myelemimg.alt = "browsed";
}
break;
default:
logit_lms('Update action unknown',2);
break;
}
}
return true;
<?php
} ?>
return true;
}
/**
* Updates the progress bar with the new status. Prevents the need of a page refresh and flickering
* @param integer Number of completed items
* @param integer Number of items in total
* @param string Display mode (absolute 'abs' or percentage '%').Defaults to %
*/
function update_progress_bar(nbr_complete, nbr_total, mode)
{
logit_lms('update_progress_bar('+nbr_complete+','+nbr_total+','+mode+')',2);
logit_lms('could update with data: '+lms_lp_id+','+lms_view_id+','+lms_user_id,2);
var myframe = frames["nav_name"];
if(myframe){
if(mode == ''){mode='%';}
if(nbr_total == 0){nbr_total=1;}
var percentage = (nbr_complete/nbr_total)*100;
percentage = Math.round(percentage);
var progress_bar = $("#progress_bar_value");
progress_bar.css('width', percentage +"%");
/*
var pr_text = myframe.document.getElementById('progress_text');
var pr_full = myframe.document.getElementById('progress_img_full');
var pr_empty = myframe.document.getElementById('progress_img_empty');
pr_full.width = percentage;
pr_empty.width = 100-percentage;
*/
var mytext = '';
switch(mode){
case 'abs':
mytext = nbr_complete + '/' + nbr_total;
break;
case '%':
default:
mytext = percentage + '%';
break;
}
pr_text.innerHTML = mytext;
}
return true;
}
/**
* Updates the message frame with the given string
*/
function update_message_frame(msg_msg)
{
if(msg_msg==null){msg_msg='';}
var msg_f = frames["message_name"];
if(!msg_f.document || !msg_f.document.getElementById('msg_div_id')){
logit_lms('In update_message_frame() - message frame has no document property',0);
}else{
logit_lms('In update_message_frame() - updating frame',0);
msg_f.document.getElementById('msg_div_id').innerHTML= msg_msg;
}
}
/**
* Function that handles the saving of an item and switching from an item to another.
* Once called, this function should be able to do the whole process of (1) saving the
* current item, (2) refresh all the values inside the SCORM API object, (3) open the
* new item into the content_id frame, (4) refresh the table of contents, (5) refresh
* the progress bar (completion), (6) refresh the message frame
* @param integer Chamilo ID for the current item
* @param string This parameter can be a string specifying the next
* item (like 'next', 'previous', 'first' or 'last') or the id to the next item
*/
function switch_item(current_item, next_item){
/*
if(!current_item){
logit_lms('In switch - no current_item defined',0);
}
if(!next_item){
logit_lms('In switch - no next_item defined',0);
}
*/
if(lms_item_id == next_item){
return; //nothing to switch
}
//(1) save the current item
logit_lms('Called switch_item with params '+lms_item_id+' and '+next_item+'',0);
xajax_save_item(lms_lp_id, lms_user_id, lms_view_id, lms_item_id, score, max, min, lesson_status, session_time, suspend_data, lesson_location);
//(2) Refresh all the values inside this SCORM API object - use AJAX
xajax_switch_item_details(lms_lp_id,lms_user_id,lms_view_id,lms_item_id,next_item);
//(3) open the new item in the content_id frame
var cont_f = document.getElementById('content_id');
if(!cont_f){logit_lms('In switch - content frame not found',0);return false;}
switch(next_item){
case 'next':
next_item = lms_next_item;
break;
case 'previous':
next_item = lms_previous_item;
break;
default:
break;
}
cont_f.src = 'lp_controller.php?action=content&lp_id='+lms_lp_id+'&item_id='+next_item;
//(4) refresh table of contents
/*
var toc_f = document.getElementById('toc_id');
if(!toc_f){logit_lms('In switch - toc frame not found',0);return false;}
var myrefresh = toc_f.src;
toc_f.src = myrefresh;
*/
//(5) refresh the progress bar
/*
var prg_f = document.getElementById('nav_id');
if(!prg_f){logit_lms('In switch - navigation frame not found',0);return false;}
var myrefresh = prg_f.src;
prg_f.src = myrefresh;
*/
//(6) refresh the message box (included in switch_item_details)
return true;
}
/**
* Save a specific item (with its interactions, if any) into the LMS through
* an AJAX call. Originally, we used the xajax library. Now we use jQuery.
* Because of the need to pass an array, we have to build the parameters
* manually into GET[]
*/
function xajax_save_item(lms_lp_id, lms_user_id, lms_view_id, lms_item_id, score, max, min, lesson_status, session_time, suspend_data, lesson_location, interactions, lms_item_core_exit) {
params='';
params += 'lid='+lms_lp_id+'&uid='+lms_user_id+'&vid='+lms_view_id;
params += '&iid='+lms_item_id+'&s='+score+'&max='+max+'&min='+min;
params += '&status='+lesson_status+'&t='+session_time;
params += '&suspend='+suspend_data+'&loc='+lesson_location;
params += '&core_exit='+lms_item_core_exit;
interact_string = '';
for (i in interactions){
interact_string += '&interact['+i+']=';
interact_temp = '[';
for (j in interactions[i]) {
interact_temp += interactions[i][j]+',';
}
interact_temp = interact_temp.substr(0,(interact_temp.length-2)) + ']';
interact_string += encodeURIComponent(interact_temp);
}
//interact_string = encodeURIComponent(interact_string.substr(0,(interact_string.length-1)));
params += interact_string;
/*params = {
'lid': lms_lp_id,
'uid': lms_user_id,
'vid': lms_view_id,
'iid': lms_item_id,
's': score,
'max': max,
'min': min,
'status': lesson_status,
't': session_time,
'suspend': suspend_data,
'loc': lesson_location,
'interact': interac_string,
'core_exit': lms_item_core_exit
}
*/
$.ajax({
type:"POST",
data: params,
url: "lp_ajax_save_item.php",
dataType: "script",
async: false
}
);
}
/**
* Starts the timer with the server clock time.
* Originally, we used the xajax library. Now we use jQuery
*/
function xajax_start_timer() {
$.ajax({
type: "GET",
url: "lp_ajax_start_timer.php",
dataType: "script",
async: false
});
}
/**
* Save a specific item's objectives into the LMS through
* an AJAX call. Originally, we used the xajax library. Now we use jQuery
*/
function xajax_save_objectives(lms_lp_id,lms_user_id,lms_view_id,lms_item_id,item_objectives) {
params='';
params += 'lid='+lms_lp_id+'&uid='+lms_user_id+'&vid='+lms_view_id;
params += '&iid='+lms_item_id;
obj_string = '';
for (i in item_objectives){
obj_string += '&objectives['+i+']=';
obj_temp = '[';
for (j in item_objectives[i]) {
obj_temp += item_objectives[i][j]+',';
}
obj_temp = obj_temp.substr(0,(obj_temp.length-2)) + ']';
obj_string += encodeURIComponent(obj_temp);
}
params += obj_string;
$.ajax({
type: "POST",
data: params,
url: "lp_ajax_save_objectives.php",
dataType: "script",
async: false
});
}
/**
* Switch between two items through
* an AJAX call. Originally, we used the xajax library. Now we use jQuery
*/
function xajax_switch_item_details(lms_lp_id,lms_user_id,lms_view_id,lms_item_id,next_item) {
params = {
'lid': lms_lp_id,
'uid': lms_user_id,
'vid': lms_view_id,
'iid': lms_item_id,
'next': next_item
}
$.ajax({
type: "POST",
data: params,
url: "lp_ajax_switch_item.php",
dataType: "script",
async: false
});
}
addEvent(window,'load',addListeners,false);

273
main/lp/aicc_hacp.php Normal file
View File

@@ -0,0 +1,273 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* API event handler functions for AICC / CMIv4 in HACP communication mode.
*
* @author Denes Nagy <darkden@freemail.hu>
* @author Yannick Warnier <ywarnier@beeznest.org>
*
* @version v 1.0
*
* @package chamilo.learnpath
*
* @license GNU/GPL
*/
/**
* This script is divided into three sections.
* The first section (below) is the initialisation part.
* The second section is the AICC object part
* The third section defines the event handlers for Chamilo's internal messaging
* and frames refresh.
*
* This script implements the HACP messaging for AICC. The API messaging is
* made by another set of scripts.
*
* Rules for HACP processing of one AU
* Rule #1 The first HACP message issued must be a GetParam
* Rule #2 The last HACP message issued must be an ExitAU
* Rule #3 At least one PutParam message must be issued prior to an ExitAU message
* Rule #4 No HACP messages can be issued after a successfully issued ExitAU message
*
* Only suspend_data and core.lesson_location should be sent updated to a late GetParam
* request. All other params should be as when the AU was launched.
*/
/* INIT SECTION */
$debug = 0;
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
// Use session ID as provided by the request.
if (!empty($_REQUEST['aicc_sid'])) {
session_id($_REQUEST['aicc_sid']);
if ($debug > 1) {
error_log('New LP - '.__FILE__.','.__LINE__.' - reusing session ID '.$_REQUEST['aicc_sid']);
}
} elseif (!empty($_REQUEST['session_id'])) {
session_id($_REQUEST['session_id']);
if ($debug > 1) {
error_log('New LP - '.__FILE__.','.__LINE__.' - reusing session ID '.$_REQUEST['session_id']);
}
}
//Load common libraries using a compatibility script to bridge between 1.6 and 1.8.
require_once __DIR__.'/../inc/global.inc.php';
if ($debug > 2) {
error_log('New LP - '.__FILE__.','.__LINE__.' - Current session ID: '.session_id());
}
// Is this needed? This is probabaly done in the header file.
$file = Session::read('file');
/** @var learnpath $oLP */
$oLP = UnserializeApi::unserialize(
'not_allowed_classes',
Session::read('lpobject')
);
$oItem = &$oLP->items[$oLP->current];
if (!is_object($oItem)) {
error_log('New LP - aicc_hacp - Could not load oItem item', 0);
exit;
}
$autocomplete_when_80pct = 0;
$result = [
'core' => [],
'core_lesson' => [],
'core_vendor' => [],
'evaluation' => [],
'student_data' => [],
];
$convert_enc = ['%25', '%0D', '%0A', '%09', '%20', '%2D', '%2F', '%3B', '%3F', '%7B', '%7D', '%7C', '%5C', '%5E', '%7E', '%5B', '%5D', '%60', '%23', '%3E', '%3C', '%22'];
$convert_dec = ['%', "\r", "\n", "\t", ' ', '-', '/', ';', '?', '{', '}', '|', '\\', '^', '~', '[', ']', '`', '#', '>', '<', '"'];
$crlf = "\r\n";
//$tab = "\t";
$tab = "";
$s_ec = 'error='; //string for error code
$s_et = 'error_text='; //string for error text
$s_ad = 'aicc_data='; //string for aicc_data
$errors = [0 => 'Successful', 1 => 'Invalid Command', 2 => 'Invalid AU password', 3 => 'Invalid Session ID'];
$error_code = 0;
$error_text = '';
$aicc_data = '';
$result = '';
// Get REQUEST
if (!empty($_REQUEST['command'])) {
//error_log('In '.__FILE__.', '.__LINE__.' - request is '.$_REQUEST['command'], 0);
switch (strtolower($_REQUEST['command'])) {
case 'getparam':
// Request for all available data to be printed out in the answer.
if (!empty($_REQUEST['version'])) {
$hacp_version = Database::escape_string($_REQUEST['version']);
}
if (!empty($_REQUEST['session_id'])) {
$hacp_session_id = Database::escape_string($_REQUEST['session_id']);
}
$error_code = 0;
$error_text = $errors[$error_code];
//$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad;
$result .= '[Core]'.$crlf;
$result .= $tab.'Student_ID='.$_user['user_id'].$crlf;
$result .= $tab.'Student_Name='.api_get_person_name($_user['firstName'], $_user['lastName']).$crlf;
$result .= $tab.'Lesson_Location='.$oItem->get_lesson_location().$crlf;
$result .= $tab.'Credit='.$oItem->get_credit().$crlf;
$result .= $tab.'Lesson_Status='.$oItem->get_status().$crlf;
$result .= $tab.'Score='.$oItem->get_score().$crlf;
$result .= $tab.'Time='.$oItem->get_scorm_time('js').$crlf;
$result .= $tab.'Lesson_Mode='.$oItem->get_lesson_mode().$crlf;
$result .= '[Core_Lesson]'.$crlf;
$result .= $oItem->get_suspend_data().$crlf;
$result .= '[Core_Vendor]'.$crlf;
$result .= $oItem->get_launch_data.$crlf;
$result .= '[Comments]'.$crlf;
$result .= $crlf;
$result .= '[Evaluation]'.$crlf;
$result .= $tab.'Course_ID={'.api_get_course_id().'}'.$crlf;
//$result .= '[Objectives_Status]'.$crlf;
$result .= '[Student_Data]'.$crlf;
$result .= $tab.'Mastery_Score='.$oItem->masteryscore.$crlf;
//$result .= '[Student_Demographics]'.$crlf;
//$result .= '[Student_Preferences]'.$crlf;
//error_log('Returning message: '.$result,0);
//$result = str_replace($convert_dec, $convert_enc, $result);
//error_log('Returning message (encoded): '.$result,0);
break;
case 'putparam':
$hacp_version = '';
$hacp_session_id = '';
$hacp_aicc_data = '';
foreach ($_REQUEST as $name => $value) {
//escape the value as described in the AICC documentation p170
switch (strtolower($name)) {
case 'version':
$hacp_version = $value;
break;
case 'session_id':
$hacp_session_id = $value;
break;
case 'aicc_data':
//error_log('In '.__FILE__.', '.__LINE__.' - aicc data before translation is '.$value, 0);
$value = str_replace('+', ' ', $value);
$value = str_replace($convert_enc, $convert_dec, $value);
$hacp_aicc_data = $value;
break;
}
}
// Treat the incoming request:
$aicc = new aicc();
$msg_array = $aicc->parse_ini_string_quotes_safe($hacp_aicc_data, ['core_lesson', 'core_vendor']);
foreach ($msg_array as $key => $dummy) {
switch (strtolower($key)) {
case 'core':
foreach ($msg_array[$key] as $subkey => $value) {
switch (strtolower($subkey)) {
case 'lesson_location':
$oItem->set_lesson_location($value);
break;
case 'lesson_status':
// Sometimes values are sent abbreviated
switch ($value) {
case 'C':
$value = 'completed';
break;
case 'I':
$value = 'incomplete';
break;
case 'N':
case 'NA':
$value = 'not attempted';
break;
case 'P':
$value = 'passed';
break;
case 'B':
$value = 'browsed';
break;
default:
break;
}
$oItem->set_status($value);
break;
case 'score':
$oItem->set_score($value);
break;
case 'time':
if (strpos($value, ':') !== false) {
$oItem->set_time($value, 'scorm');
} else {
$oItem->set_time($value);
}
break;
}
}
break;
case 'core_lesson':
$oItem->current_data = $msg_array[$key];
break;
case 'comments':
break;
case 'objectives_status':
break;
case 'student_data':
break;
case 'student_preferences':
break;
}
}
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
$oItem->save(false);
break;
case 'putcomments':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
case 'putobjectives':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
case 'putpath':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
case 'putinteractions':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
case 'putperformance':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
case 'exitau':
$error_code = 0;
$error_text = $errors[$error_code];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf.$s_ad.$crlf;
break;
default:
$error_code = 1;
$error_text = $errors[1];
$result = $s_ec.$error_code.$crlf.$s_et.$error_text.$crlf;
}
}
Session::write('lpobject', serialize($oLP));
Session::write('oLP', $oLP);
session_write_close();
// Content type must be text/plain.
header('Content-type: text/plain');
echo $result;

79
main/lp/blank.php Normal file
View File

@@ -0,0 +1,79 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Script that displays a blank page (with later a message saying why).
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
$htmlHeadXtra[] = "
<style>
body { background: none;}
</style>
";
$message = '';
if (isset($_GET['error'])) {
switch ($_GET['error']) {
case 'document_protected':
$message = Display::return_message(get_lang('ProtectedDocument'), 'warning');
break;
case 'document_deleted':
$message = Display::return_message(get_lang('DocumentHasBeenDeleted'), 'error');
break;
case 'prerequisites':
$prerequisiteMessage = isset($_GET['prerequisite_message']) ? $_GET['prerequisite_message'] : '';
$message = Display::return_message(get_lang('LearnpathPrereqNotCompleted'), 'warning');
if (!empty($prerequisiteMessage)) {
$message = Display::return_message(Security::remove_XSS($prerequisiteMessage), 'warning');
}
// Validates and display error message for prerequisite dates.
/** @var learnpath $lp */
$lp = Session::read('oLP');
$itemId = $lp->get_current_item_id();
$datesMatch = $lp->prerequistesDatesMatch($itemId);
if (!$datesMatch) {
$currentItem = $lp->getItem($itemId);
if (!empty($currentItem->prereq_alert)) {
$message = Display::return_message($currentItem->prereq_alert, 'warning');
}
}
break;
case 'document_not_found':
$message = Display::return_message(get_lang('FileNotFound'), 'warning');
break;
case 'reached_one_attempt':
$message = Display::return_message(get_lang('ReachedOneAttempt'), 'warning');
break;
case 'x_frames_options':
$src = Session::read('x_frame_source');
if (!empty($src)) {
$icon = '<em class="icon-play-sign icon-2x" aria-hidden="true"></em>';
$message = Display::return_message(
Display::url(
$icon.$src,
$src,
['class' => 'btn generated', 'target' => '_blank']
),
'normal',
false
);
Session::erase('x_frame_source');
}
break;
default:
break;
}
} elseif (isset($_GET['msg']) && $_GET['msg'] === 'exerciseFinished') {
$message = Display::return_message(get_lang('ExerciseFinished'));
}
$template = new Template();
$template->assign('content', $message);
$template->display_blank_template();

View File

@@ -0,0 +1,53 @@
<?php
/* For licensing terms, see /license.txt */
/**
* The only usage of this script is to have a portable way of keeping the list of content makers.
*
* @package chamilo.learnpath
*
* @author Yannick Warnier <yannick.warnier@beeznest.com>
*/
$content_origins = [
'--'.get_lang('GenericScorm').'--',
'--'.get_lang('Other').'--',
'Accent',
'Accenture',
'ADLNet',
'Articulate',
'ATutor',
'Blackboard',
'Calfat',
'Captivate',
'Chamilo',
'Chamilo 2',
'Claroline',
'Commest',
'Coursebuilder',
'Docent',
'Dokeos',
'Dreamweaver',
'Easyquiz',
'e-doceo',
'ENI Editions',
'Explio',
'Flash',
'HTML',
'HotPotatoes',
'Hyperoffice',
'Ingenatic',
'Instruxion',
'iProgress',
'Lectora',
'Microsoft',
'Onlineformapro',
'Opikanoba',
'Plantyn',
'Saba',
'Skillsoft',
'Speechi',
'Thomson-NETg',
'U&I Learning',
'Udutu',
'WebCT',
];

59
main/lp/download.php Normal file
View File

@@ -0,0 +1,59 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This file is responsible for passing requested documents to the browser.
*
* @package chamilo.document
*/
session_cache_limiter('none');
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_COURSES;
// Protection
api_protect_course_script();
$_course = api_get_course_info();
if (!isset($_course)) {
api_not_allowed(true);
}
$doc_url = $_GET['doc_url'];
// Change the '&' that got rewritten to '///' by mod_rewrite back to '&'
$doc_url = str_replace('///', '&', $doc_url);
// Still a space present? it must be a '+' (that got replaced by mod_rewrite)
$doc_url = str_replace(' ', '+', $doc_url);
$doc_url = str_replace(['../', '\\..', '\\0', '..\\'], ['', '', '', ''], $doc_url); //echo $doc_url;
if (strpos($doc_url, '../') || strpos($doc_url, '/..')) {
$doc_url = '';
}
$sys_course_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/scorm';
$user_id = api_get_user_id();
/** @var learnpath $lp */
$lp = Session::read('oLP');
if ($lp) {
$lp_id = $lp->get_id();
$lp_item_id = $lp->current;
$lp_item_info = new learnpathItem($lp_item_id);
if (!empty($lp_item_info)) {
$visible = learnpath::is_lp_visible_for_student($lp_id, $user_id);
if ($visible) {
Event::event_download($doc_url);
if (Security::check_abs_path($sys_course_path.$doc_url, $sys_course_path.'/')) {
$full_file_name = $sys_course_path.$doc_url;
DocumentManager::file_send_for_download($full_file_name);
exit;
}
}
//}
}
}
echo Display::return_message(get_lang('ProtectedDocument'), 'error');
//api_not_allowed backbutton won't work.
exit;

72
main/lp/embed.php Normal file
View File

@@ -0,0 +1,72 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script(true);
$type = $_REQUEST['type'] ?? '';
$src = $_REQUEST['source'] ?? '';
if (empty($type) || empty($src)) {
api_not_allowed();
}
$iframe = '';
switch ($type) {
case 'download':
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$itemId = isset($_GET['lp_item_id']) ? $_GET['lp_item_id'] : '';
if (!$learnPath || empty($itemId)) {
api_not_allowed();
}
$file = learnpath::rl_get_resource_link_for_learnpath(
api_get_course_int_id(),
$learnPath->get_id(),
$itemId,
$learnPath->get_view_id()
);
$iframe = Display::return_message(
Display::url(get_lang('Download'), $file, ['class' => 'btn btn-primary']),
'info',
false
);
break;
case 'youtube':
$src = "src ='//www.youtube.com/embed/$src'";
$src = Security::remove_XSS($src);
$iframe .= '<div id="content" style="width: 700px ;margin-left:auto; margin-right:auto;"><br />';
$iframe .= '<iframe class="youtube-player" type="text/html" width="640" height="385" '.$src.' frameborder="0"></iframe>';
$iframe .= '</div>';
break;
case 'vimeo':
$src = "src ='//player.vimeo.com/video/$src'";
$src = Security::remove_XSS($src);
$iframe .= '<div id="content" style="width: 700px ;margin-left:auto; margin-right:auto;"><br />';
$iframe .= '<iframe '.$src.' width="640" height="385" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe>';
$iframe .= '</div>';
break;
case 'nonhttps':
$icon = '&nbsp;<em class="icon-external-link icon-2x"></em>';
$iframe = Security::remove_XSS(Display::return_message(
Display::url($src.$icon, $src, ['class' => 'btn', 'target' => '_blank']),
'normal',
false
));
break;
}
$htmlHeadXtra[] = "
<style>
body { background: none;}
</style>
";
Display::display_reduced_header();
echo $iframe;
Display::display_footer();

View File

@@ -0,0 +1,3 @@
<div>
Congratulations! You have finished this learning path
</div>

14
main/lp/index.php Normal file
View File

@@ -0,0 +1,14 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Redirection script.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
header('location: lp_controller.php?'.api_get_cidreq().'&action=list');
exit;

View File

@@ -0,0 +1,43 @@
HOWTO use Storage API in your content.
Include the following JavaScript function in your content:
--------------------javascript code--------------------
function FindSTAPI(win) {
var g_nFindAPITries = 0;
while ((win.STAPI == null) && (win.parent != null) && (win.parent != win)) {
g_nFindAPITries ++;
if (g_nFindAPITries > 500) {
return null;
}
win = win.parent;
}
return win.STAPI;
}
--------------------javascript code--------------------
This function returns a facade object which has the following methods :
- testCall(message) : outputs message in an alertbox, you can use it to make sure the API is reachable
- getAllUsers : get a list of users info (user ids, usernames, firstnames, lastnames)
Basic storage:
- setValue(sv_key, sv_value): set the given value for the given key for the current user
- getValue(sv_key): get the value stored for the key sv_key for the current user
- getAll(): get all stored key/values pair for the current user
Stack storage (you can store a stack of values instead of a single value for each key):
- stack_push(sv_key, sv_value): push a new value in the stack for sv_key for the current user
- stack_pop(sv_key): pop the most recently pushed value from the sv_key stack for the current user
- stack_length(sv_key): get the number of stacked values for the key sv_key for the current user
- stack_clear(sv_key): erase all values from the storage stack for the key sv_key for the current user
- stack_getAll(sv_key): get all values from the storage stack for the key sv_key for the current user
These storage values have superuser counterparts which allow platform administrators to alter values for any user :
- setValue_user(sv_key, sv_value, user_id)
- getValue(sv_key, user_id)
- getAll(user_id)
- stack_push(sv_key, sv_value, user_id)
- stack_pop(sv_key, sv_value, user_id)
- stack_length(sv_key, user_id)
- stack_clear(sv_key, user_id)
- stack_getAll(sv_key, user_id)

28
main/lp/js/documentapi.js Normal file
View File

@@ -0,0 +1,28 @@
// JS interface enabling scorm content to use main/document/remote.php easily
// CBlue SPRL, Arnaud Ligot <arnaud@cblue.be>
lms_documents_list = function(path) {
var result;
$.ajax({
async: false,
type: "POST",
datatype: "json",
url: "../document/remote.php",
data: {
action: "list",
cwd: path,
cidReq: chamilo_courseCode,
},
success: function(data) {
result = eval("("+data+")");
}
});
return result;
}
// Accessor object
function DOCUMENTAPIobject() {
this.list = lms_documents_list;
}
var DOCUMENTAPI = new DOCUMENTAPIobject();

302
main/lp/js/storageapi.js Normal file
View File

@@ -0,0 +1,302 @@
// Storage API
// JavaScript API
// CBlue SPRL, Jean-Karim Bockstael <jeankarim@cblue.be>
lms_storage_testCall = function(content) {
alert(content);
}
lms_storage_setValue_user = function(sv_key, sv_value, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "set",
svkey: sv_key,
svvalue: sv_value,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = (data != '0');
}
});
return result;
}
lms_storage_getValue_user = function(sv_key, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "get",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = data;
}
});
return result;
}
lms_storage_getPosition_user = function(sv_key, sv_user, sv_asc) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "getposition",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco,
svasc: sv_asc
},
success: function(data) {
result = data;
}
});
return result;
}
lms_storage_getLeaders_user = function(sv_key, sv_user, sv_asc, sv_length) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "getleaders",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco,
svasc: sv_asc,
svlength: sv_length
},
success: function(data) {
result = eval("("+data+")");
}
});
return result;
}
lms_storage_getAll_user = function(sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "getall",
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = eval("("+data+")");
}
});
return result;
}
lms_storage_stack_push_user = function(sv_key, sv_value, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "stackpush",
svvalue: sv_value,
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = (data != '0');
}
});
return result;
}
lms_storage_stack_pop_user = function(sv_key, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "stackpop",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = data;
}
});
return result;
}
lms_storage_stack_length_user = function(sv_key, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "stacklength",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = data;
}
});
return result;
}
lms_storage_stack_clear_user = function(sv_key, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "stackclear",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = data;
}
});
return result;
}
lms_storage_stack_getAll_user = function(sv_key, sv_user) {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "stackgetall",
svkey: sv_key,
svuser: sv_user,
svcourse: sv_course,
svsco: sv_sco
},
success: function(data) {
result = eval("("+data+")");
}
});
return result;
}
lms_storage_getAllUsers = function() {
var result;
$.ajax({
async: false,
type: "POST",
url: "storageapi.php",
data: {
action: "usersgetall"
},
success: function(data) {
result = eval("("+data+")");
}
});
return result;
}
lms_storage_setValue = function(sv_key, sv_value) {
return lms_storage_setValue_user(sv_key, sv_value, sv_user);
}
lms_storage_getValue = function(sv_key) {
return lms_storage_getValue_user(sv_key, sv_user);
}
lms_storage_getPosition = function(sv_key, sv_asc) {
return lms_storage_getPosition_user(sv_key, sv_user, sv_asc);
}
lms_storage_getLeaders = function(sv_key, sv_asc, sv_length) {
return lms_storage_getLeaders_user(sv_key, sv_user, sv_asc, sv_length);
}
lms_storage_getAll = function() {
return lms_storage_getAll_user(sv_user);
}
lms_storage_stack_push = function(sv_key, sv_value) {
return lms_storage_stack_push_user(sv_key, sv_value, sv_user);
}
lms_storage_stack_pop = function(sv_key) {
return lms_storage_stack_pop(sv_key, sv_user);
}
lms_storage_stack_length = function(sv_key) {
return lms_storage_stack_length_user(sv_key, sv_user);
}
lms_storage_stack_clear = function(sv_key) {
return lms_storage_stack_clear_user(sv_key, sv_user);
}
lms_storage_stack_getAll = function(sv_key) {
return lms_storage_stack_getAll_user(sv_key, sv_user);
}
// Accessor object
function STORAGEAPIobject() {
this.testCall = lms_storage_testCall;
this.setValue = lms_storage_setValue;
this.setValue_user = lms_storage_setValue_user;
this.getValue = lms_storage_getValue;
this.getValue_user = lms_storage_getValue_user;
this.getAll = lms_storage_getAll;
this.getAll_user = lms_storage_getAll_user;
this.getPosition_user = lms_storage_getPosition_user;
this.getPosition = lms_storage_getPosition;
this.getLeaders_user = lms_storage_getLeaders_user;
this.getLeaders = lms_storage_getLeaders;
this.stack_push = lms_storage_stack_push;
this.stack_push_user = lms_storage_stack_push_user;
this.stack_pop = lms_storage_stack_pop;
this.stack_pop_user = lms_storage_stack_pop_user;
this.stack_length = lms_storage_stack_length;
this.stack_length_user = lms_storage_stack_length_user;
this.stack_clear = lms_storage_stack_clear;
this.stack_clear_user = lms_storage_stack_clear_user;
this.stack_getAll = lms_storage_stack_getAll;
this.stack_getAll_user = lms_storage_stack_getAll_user;
this.getAllUsers = lms_storage_getAllUsers;
this.sv_user = sv_user;
this.sv_course = sv_course;
this.sv_sco = sv_sco;
}
var STORAGEAPI = new STORAGEAPIobject();
var STAPI = STORAGEAPI;

14990
main/lp/learnpath.class.php Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,269 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CLp;
/**
* Class LearnpathList
* This class is only a learning path list container with several practical methods for sorting the list and
* provide links to specific paths.
*
* @uses \Database.lib.php to use the database
* @uses \learnpath.class.php to generate learnpath objects to get in the list
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
class LearnpathList
{
// Holds a flat list of learnpaths data from the database.
public $list = [];
// Holds a flat list of learnpaths sorted by alphabetical name order.
public $alpha_list = [];
public $course_code;
public $user_id;
/**
* This method is the constructor for the learnpathList. It gets a list of available learning paths from
* the database and creates the learnpath objects. This list depends on the user that is connected
* (only displays) items if he has enough permissions to view them.
*
* @param int $user_id
* @param array $courseInfo Optional course info array (otherwise we use api_get_course_info())
* @param int $session_id Optional session id (otherwise we use api_get_session_id())
* @param string $order_by
* @param bool $check_publication_dates
* @param int $categoryId
* @param bool $ignoreCategoryFilter
* @param bool $ignoreLpVisibility get the list of LPs for reports
*/
public function __construct(
$user_id,
$courseInfo = [],
$session_id = 0,
$order_by = null,
$check_publication_dates = false,
$categoryId = null,
$ignoreCategoryFilter = false,
$ignoreLpVisibility = false,
bool $includeSubscribedLp = true,
bool $showOnlyVisibleToStudent = false
) {
if (empty($courseInfo)) {
$courseInfo = api_get_course_info();
}
$this->course_code = $courseInfo['code'];
$course_id = $courseInfo['real_id'];
$this->user_id = $user_id;
// Condition for the session.
$session_id = empty($session_id) ? api_get_session_id() : (int) $session_id;
$condition_session = api_get_session_condition(
$session_id,
true,
true,
'lp.sessionId'
);
$tbl_tool = Database::get_course_table(TABLE_TOOL_LIST);
$order = ' ORDER BY lp.displayOrder ASC, lp.name ASC';
if (!empty($order_by)) {
// @todo Replace with criteria order by
$order = ' ORDER BY '.Database::escape_string($order_by);
}
$now = api_get_utc_datetime();
$time_conditions = '';
if ($check_publication_dates) {
$time_conditions = " AND (
(lp.publicatedOn IS NOT NULL AND lp.publicatedOn < '$now' AND lp.expiredOn IS NOT NULL AND lp.expiredOn > '$now') OR
(lp.publicatedOn IS NOT NULL AND lp.publicatedOn < '$now' AND lp.expiredOn IS NULL) OR
(lp.publicatedOn IS NULL AND lp.expiredOn IS NOT NULL AND lp.expiredOn > '$now') OR
(lp.publicatedOn IS NULL AND lp.expiredOn IS NULL ))
";
}
$categoryFilter = '';
if ($ignoreCategoryFilter == false) {
if (!empty($categoryId)) {
$categoryId = (int) $categoryId;
$categoryFilter = " AND lp.categoryId = $categoryId";
} else {
$categoryFilter = ' AND (lp.categoryId = 0 OR lp.categoryId IS NULL) ';
}
}
$dql = "SELECT lp FROM ChamiloCourseBundle:CLp as lp
WHERE
lp.cId = $course_id
$time_conditions
$condition_session
$categoryFilter
$order
";
$learningPaths = Database::getManager()->createQuery($dql)->getResult();
$showBlockedPrerequisite = api_get_configuration_value('show_prerequisite_as_blocked');
$names = [];
$isAllowToEdit = api_is_allowed_to_edit();
$toolSessionCondition = api_get_session_condition($session_id);
/** @var CLp $row */
foreach ($learningPaths as $row) {
$link = 'lp/lp_controller.php?action=view&lp_id='.$row->getId().'&id_session='.$session_id;
$oldLink = 'newscorm/lp_controller.php?action=view&lp_id='.$row->getId().'&id_session='.$session_id;
$extraCondition = '';
if (!empty($session_id)) {
$extraLink = 'lp/lp_controller.php?action=view&lp_id='.$row->getId().'&id_session=0';
$extraCondition = " OR link LIKE '$extraLink' ";
}
$sql2 = "SELECT visibility FROM $tbl_tool
WHERE
c_id = $course_id AND
image = 'scormbuilder.gif' AND
(
link LIKE '$link%' OR
link LIKE '$oldLink%'
$extraCondition
)
$toolSessionCondition
";
$res2 = Database::query($sql2);
$pub = 'i';
if (Database::num_rows($res2) > 0) {
$row2 = Database::fetch_array($res2);
$pub = (int) $row2['visibility'];
if (!empty($session_id)) {
$pub = 'v';
// Check exact value in session:
/*$sql3 = "SELECT visibility FROM $tbl_tool
WHERE
c_id = $course_id AND
image = 'scormbuilder.gif' AND
( link LIKE '$link'
)
$toolSessionCondition
";
$res3 = Database::query($sql3);
if (Database::num_rows($res3)) {
$pub = 'v';
}*/
//$pub = 0 === $pub ? 'i' : 'v';
}
}
// Check if visible.
$visibility = api_get_item_visibility(
$courseInfo,
'learnpath',
$row->getId(),
$session_id
);
// If option is not true then don't show invisible LP to user
if ($ignoreLpVisibility === false) {
if ($showBlockedPrerequisite !== true && (!$isAllowToEdit || $showOnlyVisibleToStudent)) {
$lpVisibility = learnpath::is_lp_visible_for_student(
$row->getId(),
$user_id,
$courseInfo,
$session_id,
$includeSubscribedLp
);
if ($lpVisibility === false) {
continue;
}
}
}
if (!$includeSubscribedLp && $row->getSubscribeUsers() && $isAllowToEdit) {
$isSubscribedToLp = learnpath::isUserSubscribedToLp(
['subscribe_users' => $row->getSubscribeUsers(), 'id' => $row->getIid()],
(int) $this->user_id,
$courseInfo,
(int) $session_id
);
if (!$isSubscribedToLp) {
continue;
}
}
$this->list[$row->getIid()] = [
'lp_type' => $row->getLpType(),
'lp_session' => $row->getSessionId(),
'lp_name' => stripslashes($row->getName()),
'lp_desc' => stripslashes($row->getDescription()),
'lp_path' => $row->getPath(),
'lp_view_mode' => $row->getDefaultViewMod(),
'lp_force_commit' => $row->getForceCommit(),
'lp_maker' => stripslashes($row->getContentMaker()),
'lp_proximity' => $row->getContentLocal(),
'lp_encoding' => api_get_system_encoding(),
'lp_visibility' => $visibility,
'lp_published' => $pub,
'lp_prevent_reinit' => $row->getPreventReinit(),
'seriousgame_mode' => $row->getSeriousgameMode(),
'lp_scorm_debug' => $row->getDebug(),
'lp_display_order' => $row->getDisplayOrder(),
'lp_preview_image' => stripslashes($row->getPreviewImage()),
'autolaunch' => $row->getAutolaunch(),
'session_id' => $row->getSessionId(),
'created_on' => $row->getCreatedOn() ? $row->getCreatedOn()->format('Y-m-d H:i:s') : null,
'modified_on' => $row->getModifiedOn() ? $row->getModifiedOn()->format('Y-m-d H:i:s') : null,
'publicated_on' => $row->getPublicatedOn() ? $row->getPublicatedOn()->format('Y-m-d H:i:s') : null,
'expired_on' => $row->getExpiredOn() ? $row->getExpiredOn()->format('Y-m-d H:i:s') : null,
//'category_id' => $row['category_id'],
'subscribe_users' => $row->getSubscribeUsers(),
'lp_old_id' => $row->getId(),
'iid' => $row->getIid(),
'prerequisite' => $row->getPrerequisite(),
'category_id' => $row->getCategoryId(),
];
$names[$row->getName()] = $row->getIid();
}
asort($names);
$this->alpha_list = $names;
}
/**
* Gets a table of the different learnpaths we have at the moment.
*
* @return array Learnpath info as [lp_id] => ([lp_type]=> ..., [lp_name]=>...,[lp_desc]=>...,[lp_path]=>...)
*/
public function get_flat_list()
{
return $this->list;
}
/**
* Gets a list of lessons of the given course_code and session_id
* This functions doesn't need user_id.
*
* @param string $course_code Text code of the course
* @param int $session_id Id of session
*
* @return array List of lessons with lessons id as keys
*/
public static function get_course_lessons($course_code, $session_id)
{
$table = Database::get_course_table(TABLE_LP_MAIN);
$course = api_get_course_info($course_code);
// @todo AND session_id = %s ?
$sql = "SELECT * FROM $table WHERE c_id = %s ";
$sql_query = sprintf($sql, $course['real_id']);
$result = Database::query($sql_query);
$lessons = [];
while ($row = Database::fetch_array($result)) {
if (api_get_item_visibility($course, 'learnpath', $row['id'], $session_id)) {
$lessons[$row['id']] = $row;
}
}
return $lessons;
}
}

192
main/lp/lp_add.php Normal file
View File

@@ -0,0 +1,192 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This is a learning path creation and player tool in Chamilo.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
* @author Julio Montoya <gugli100@gmail.com> Adding formvalidator support
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$currentstyle = api_get_setting('stylesheets');
$htmlHeadXtra[] = '<script>
function activate_start_date() {
if(document.getElementById(\'start_date_div\').style.display == \'none\') {
document.getElementById(\'start_date_div\').style.display = \'block\';
} else {
document.getElementById(\'start_date_div\').style.display = \'none\';
}
}
function activate_end_date() {
if(document.getElementById(\'end_date_div\').style.display == \'none\') {
document.getElementById(\'end_date_div\').style.display = \'block\';
} else {
document.getElementById(\'end_date_div\').style.display = \'none\';
}
}
</script>';
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = isset($_REQUEST['isStudentView']) ? $_REQUEST['isStudentView'] : null;
$learnpath_id = isset($_REQUEST['lp_id']) ? $_REQUEST['lp_id'] : null;
$sessionId = api_get_session_id();
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
exit;
}
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
Display::display_header(get_lang('LearnpathAddLearnpath'), 'Path');
echo '<div class="actions">';
echo '<a href="lp_controller.php?'.api_get_cidreq().'">'.
Display::return_icon(
'back.png',
get_lang('ReturnToLearningPaths'),
'',
ICON_SIZE_MEDIUM
).'</a>';
echo '</div>';
echo Display::return_message(get_lang('AddLpIntro'), 'normal', false);
if ($_POST && empty($_REQUEST['lp_name'])) {
echo Display::return_message(
get_lang('FormHasErrorsPleaseComplete'),
'error',
false
);
}
$form = new FormValidator(
'lp_add',
'post',
api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq()
);
$form->addHeader(get_lang('AddLpToStart'));
// Title
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor(
'lp_name',
get_lang('LPName'),
true,
false,
['ToolbarSet' => 'TitleAsHtml']
);
} else {
$form->addElement(
'text',
'lp_name',
api_ucfirst(get_lang('LPName')),
['autofocus' => 'autofocus']
);
}
$form->applyFilter('lp_name', 'html_filter');
$form->addRule('lp_name', get_lang('ThisFieldIsRequired'), 'required');
$allowCategory = true;
if (!empty($sessionId)) {
$allowCategory = false;
if (api_get_configuration_value('allow_session_lp_category')) {
$allowCategory = true;
}
}
if ($allowCategory) {
$items = learnpath::getCategoryFromCourseIntoSelect(
api_get_course_int_id(),
true
);
$form->addElement('select', 'category_id', get_lang('Category'), $items);
}
$form->addElement('hidden', 'post_time', time());
$form->addElement('hidden', 'action', 'add_lp');
$form->addButtonAdvancedSettings('advanced_params');
$form->addHtml('<div id="advanced_params_options" style="display:none">');
// accumulate_scorm_time
$form->addElement(
'checkbox',
'accumulate_scorm_time',
[null, get_lang('AccumulateScormTimeInfo')],
get_lang('AccumulateScormTime')
);
// Start date.
$form->addElement(
'checkbox',
'activate_start_date_check',
null,
get_lang('EnableStartTime'),
['onclick' => 'activate_start_date()']
);
$form->addElement('html', '<div id="start_date_div" style="display:block;">');
$form->addDateTimePicker('publicated_on', get_lang('PublicationDate'));
$form->addElement('html', '</div>');
// End date.
$form->addElement(
'checkbox',
'activate_end_date_check',
null,
get_lang('EnableEndTime'),
['onclick' => 'activate_end_date()']
);
$form->addElement('html', '<div id="end_date_div" style="display:none;">');
$form->addDateTimePicker('expired_on', get_lang('ExpirationDate'));
$form->addElement('html', '</div>');
$subscriptionSettings = learnpath::getSubscriptionSettings();
if ($subscriptionSettings['allow_add_users_to_lp']) {
$form->addElement(
'checkbox',
'subscribe_users',
null,
get_lang('SubscribeUsersToLp')
);
}
$extraField = new ExtraField('lp');
$extra = $extraField->addElements($form, 0, ['lp_icon']);
Skill::addSkillsToForm($form, api_get_course_int_id(), api_get_session_id(), ITEM_TYPE_LEARNPATH, 0);
$form->addElement('html', '</div>');
$defaults['activate_start_date_check'] = 1;
$defaults['accumulate_scorm_time'] = 0;
if (api_get_setting('scorm_cumulative_session_time') === 'true') {
$defaults['accumulate_scorm_time'] = 1;
}
$defaults['publicated_on'] = api_get_local_time();
$defaults['expired_on'] = api_get_local_time(time() + 86400);
$form->setDefaults($defaults);
$form->addButtonCreate(get_lang('CreateLearningPath'));
$form->display();
Display::display_footer();

View File

@@ -0,0 +1,28 @@
<?php
/* For licensing terms, see /license.txt */
$this_section = SECTION_COURSES;
api_protect_course_script();
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
Display::display_header(get_lang('LpAiGenerator'), 'Path');
echo '<div class="actions">';
echo '<a href="lp_controller.php?'.api_get_cidreq().'">'.
Display::return_icon(
'back.png',
get_lang('ReturnToLearningPaths'),
'',
ICON_SIZE_MEDIUM
).'</a>';
echo '</div>';
$aiHelper = new LpAiHelper();
$aiHelper->aiHelperForm();
Display::display_footer();

238
main/lp/lp_add_audio.php Normal file
View File

@@ -0,0 +1,238 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously.
*
* @author Julio Montoya - Improving the list of templates
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = api_is_student_view_active();
$learnpath_id = (int) $_REQUEST['lp_id'];
$lp_item_id = isset($_GET['id']) ? (int) $_GET['id'] : null;
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
$type = isset($_GET['type']) ? $_GET['type'] : null;
$action = isset($_GET['action']) ? $_GET['action'] : null;
$courseInfo = api_get_course_info();
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
exit;
}
if (empty($lp_item_id)) {
api_not_allowed();
}
/** @var learnpath $lp */
$lp = Session::read('oLP');
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => $lp->getNameNoTags(),
];
$audioPreview = DocumentManager::generateAudioJavascript([]);
$htmlHeadXtra[] = "<script>
$(function() {
$audioPreview
});
</script>";
switch ($type) {
case 'dir':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$lp->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewChapter')];
break;
case 'document':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$lp->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
break;
default:
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
break;
}
if ($action === 'add_item' && $type === 'document') {
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewDocumentCreated')];
}
// Theme calls.
$show_learn_path = true;
$lp_item = new learnpathItem($lp_item_id);
$form = new FormValidator(
'add_audio',
'post',
api_get_self().'?action=add_audio&id='.$lp_item_id.'&'.api_get_cidreq().'&lp_id='.$learnpath_id,
null,
['enctype' => 'multipart/form-data']
);
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
$lpPathInfo = $lp->generate_lp_folder($courseInfo);
DocumentManager::createDefaultAudioFolder($courseInfo);
$currentDir = '/audio';
$audioFolderId = DocumentManager::get_document_id($courseInfo, $currentDir);
if (isset($_REQUEST['folder_id'])) {
$folderIdFromRequest = isset($_REQUEST['folder_id']) ? (int) $_REQUEST['folder_id'] : 0;
$documentData = DocumentManager::get_document_data_by_id($folderIdFromRequest, $courseInfo['code']);
if ($documentData) {
$audioFolderId = $folderIdFromRequest;
$currentDir = $documentData['path'];
} else {
$currentDir = '/';
$audioFolderId = false;
}
}
$file = null;
if (!empty($lp_item->audio)) {
$file = api_get_path(SYS_COURSE_PATH).$courseInfo['path'].'/document/'.$lp_item->audio;
$urlFile = api_get_path(WEB_COURSE_PATH).$courseInfo['path'].'/document/'.$lp_item->audio.'?'.api_get_cidreq();
}
$page = $lp->build_action_menu(
true,
true,
false,
true,
$action
);
$page .= '<div class="row" style="overflow:hidden">';
$page .= '<div id="lp_sidebar" class="col-md-4">';
$page .= $lp->return_new_tree(null, true);
// Show the template list.
$page .= '</div>';
$recordVoiceForm = '<h3 class="page-header">'.get_lang('RecordYourVoice').'</h3>';
$page .= '<div id="doc_form" class="col-md-8">';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'rtc/RecordRTC.js"></script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'wami-recorder/recorder.js"></script>';
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'wami-recorder/gui.js"></script>';
$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'swfobject/swfobject.js"></script>';
$tpl = new Template(get_lang('Add'));
$tpl->assign('unique_file_id', api_get_unique_id());
$tpl->assign('course_code', api_get_course_id());
$tpl->assign('filename', $lp_item->get_title().'_nano.wav');
$tpl->assign('enable_record_audio', api_get_setting('enable_record_audio') === 'true');
$tpl->assign('cur_dir_path', '/audio');
$tpl->assign('lp_item_id', $lp_item_id);
$tpl->assign('lp_dir', api_remove_trailing_slash($lpPathInfo['dir']));
$template = $tpl->get_template('learnpath/record_voice.tpl');
$recordVoiceForm .= $tpl->fetch($template);
$form->addElement('header', '<small class="text-muted">'.get_lang('Or').'</small> '.get_lang('AudioFile'));
$audioLabel = '';
if (!empty($lp_item->audio)) {
$audioLabel = '<br />'.get_lang('FileName').': <b>'.$lp_item->audio.'<b/>';
}
$form->addLabel(null, sprintf(get_lang('AudioFileForItemX'), $lp_item->get_title()).$audioLabel);
if (!empty($file)) {
$audioPlayer = '<div id="preview">'.
Display::getMediaPlayer($file, ['url' => $urlFile]).
"</div>";
$form->addElement('label', get_lang('Listen'), $audioPlayer);
$url = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?lp_id='.$lp->get_id().'&action=add_audio&id='.$lp_item_id.'&delete_file=1&'.api_get_cidreq();
$form->addElement(
'label',
null,
Display::url(
get_lang('RemoveAudio'),
$url,
['class' => 'btn btn-danger']
)
);
}
$form->addElement('file', 'file');
$form->addElement('hidden', 'id', $lp_item_id);
$form->addButtonSave(get_lang('SaveRecordedAudio'));
$documentTree = DocumentManager::get_document_preview(
$courseInfo,
$lp->get_id(),
null,
api_get_session_id(),
false,
'',
api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=add_audio&lp_id='.$lp->get_id().'&id='.$lp_item_id,
false,
true,
$audioFolderId,
true,
true,
['mp3', 'ogg', 'wav']
);
$page .= $recordVoiceForm;
$page .= '<br>';
$page .= $form->returnForm();
$page .= '<h3 class="page-header">
<small>'.get_lang('Or').'</small> '.get_lang('SelectAnAudioFileFromDocuments').'</h3>';
$folders = DocumentManager::get_all_document_folders(
$courseInfo,
null,
true,
false,
$currentDir
);
$form = new FormValidator(
'selector',
'POST',
api_get_self().'?view=build&id='.$lp_item_id.'&lp_id='.$learnpath_id.'&action=add_audio&'.api_get_cidreq()
);
$attributes = ['onchange' => 'javascript: document.selector.submit();'];
$selector = DocumentManager::build_directory_selector(
$folders,
$audioFolderId,
null,
false,
$form,
'folder_id',
$attributes
);
$page .= $selector;
$page .= '<ul class="lp_resource">';
$page .= '<li class="doc_folder" style="margin-left: 36px;">'.get_lang('Audio').'</li>';
$page .= '<li class="doc_folder">';
$page .= '<ul class="lp_resource">'.$documentTree.'</ul>';
$page .= '</div>';
$page .= '</ul>';
$page .= '</div>';
$page .= '</div>';
$tpl->assign('content', $page);
$tpl->display_one_col_template();

341
main/lp/lp_add_author.php Normal file
View File

@@ -0,0 +1,341 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path editor author.
*
* @author Carlos Alvarado
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update
* @author Julio Montoya - Improving the list of templates
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
api_protect_admin_script(true);
$isStudentView = isset($_REQUEST['isStudentView']) ? $_REQUEST['isStudentView'] : null;
$lpId = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : 0;
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
$type = isset($_GET['type']) ? $_GET['type'] : null;
$action = isset($_GET['action']) ? $_GET['action'] : null;
$is_allowed_to_edit = api_is_allowed_to_edit(null, false);
$listUrl = api_get_path(WEB_CODE_PATH).
'lp/lp_controller.php?action=view&lp_id='.$lpId.'&'.api_get_cidreq().'&isStudentView=true';
if (!$is_allowed_to_edit) {
header("Location: $listUrl");
exit;
}
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
if (empty($learnPath)) {
api_not_allowed();
}
if ($learnPath->get_lp_session_id() != api_get_session_id()) {
// You cannot edit an LP from a base course.
header("Location: $listUrl");
exit;
}
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$htmlHeadXtra[] = api_get_jquery_libraries_js(['jquery-ui', 'jquery-upload']);
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$lpId&".api_get_cidreq(),
'name' => $learnPath->getNameNoTags(),
];
switch ($type) {
case 'dir':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$learnPath->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewChapter')];
break;
case 'document':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$learnPath->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
break;
default:
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewStep')];
break;
}
if ($action === 'add_item' && $type === 'document') {
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewDocumentCreated')];
}
// Theme calls.
$show_learn_path = true;
$lp_theme_css = $learnPath->get_theme();
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str = str.replace(/\\'/g, '\'');
str = str.replace(/\\"/g, '"');
str = str.replace(/\\\\/g, '\\');
str = str.replace(/\\0/g, '\0');
return str;
}
function confirmation(name) {
name = stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
$(function () {
jQuery('.scrollbar-inner').scrollbar();
$('#subtab ').on('click', 'a:first', function () {
window.location.reload();
});
expandColumnToogle('#hide_bar_template', {
selector: '#lp_sidebar'
}, {
selector: '#doc_form'
});
$('.lp-btn-associate-forum').on('click', function (e) {
var associate = confirm('<?php echo get_lang('ConfirmAssociateForumToLPItem'); ?>');
if (!associate) {
e.preventDefault();
}
});
$('.lp-btn-dissociate-forum').on('click', function (e) {
var dissociate = confirm('<?php echo get_lang('ConfirmDissociateForumToLPItem'); ?>');
if (!dissociate) {
e.preventDefault();
}
});
// hide the current template list for new documment until it tab clicked
$('#frmModel').hide();
});
// document template for new document tab handler
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
var id = e.target.id;
if (id == 'subtab2') {
$('#frmModel').show();
} else {
$('#frmModel').hide();
}
})
</script>
<?php
$extraField = [];
$form = new FormValidator(
'configure_homepage_'.$action,
'post',
api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=author_view&sub_action=author_view',
'',
['style' => 'margin: 0px;']
);
$extraField['backTo'] = api_get_self().'?action=add_item&type=step&lp_id='.$lpId.'&'.api_get_cidreq();
$form->addHtml('<div id="doc_form" class="col-md-12 row">');
$extraFieldValue = new ExtraFieldValue('lp_item');
$form->addHeader(get_lang('LpByAuthor'));
$default = [];
$form->addHtml('<div class="col-xs-12 row" >');
$defaultAuthor = [];
foreach ($_SESSION['oLP']->items as $item) {
$itemName = $item->name;
$itemId = $item->iId;
$extraFieldValues = $extraFieldValue->get_values_by_handler_and_field_variable($itemId, 'authorlpitem');
$priceItem = $extraFieldValue->get_values_by_handler_and_field_variable($itemId, 'price');
$authorName = [];
if (!empty($extraFieldValues)) {
if ($extraFieldValues != false) {
$authors = explode(';', $extraFieldValues['value']);
if (!empty($authors)) {
foreach ($authors as $author) {
if ($author != 0) {
$defaultAuthor[$author] = $author;
$teacher = api_get_user_info($author);
$authorName[] = $teacher['complete_name'];
}
}
}
}
}
if (count($authorName) != 0) {
$authorName = " (".implode(', ', $authorName).")";
} else {
$authorName = '';
}
if (isset($priceItem['value']) && !empty($priceItem['value'])) {
$authorName .= "<br /><small>".get_lang('Price')." (".$priceItem['value'].")</small>";
}
$form->addCheckBox(
"itemSelected[$itemId]",
null,
Display::return_icon('lp_document.png', $itemName).$itemName.$authorName
);
$default['itemSelected'][$itemId] = false;
}
$options = [0 => get_lang('RemoveSelected')];
$default['authorItemSelect'] = [];
$form->addHtml('</div>');
$teachers = [];
$field = new ExtraField('user');
$authorLp = $field->get_handler_field_info_by_field_variable('authorlp');
$extraFieldId = isset($authorLp['id']) ? (int) $authorLp['id'] : 0;
if ($extraFieldId != 0) {
$extraFieldValueUser = new ExtraFieldValue('user');
$arrayExtraFieldValueUser = $extraFieldValueUser->get_item_id_from_field_variable_and_field_value(
'authorlp',
1,
true,
false,
true
);
if (!empty($arrayExtraFieldValueUser)) {
foreach ($arrayExtraFieldValueUser as $item) {
$teacher = api_get_user_info($item['item_id']);
$teachers[] = $teacher;
}
}
}
foreach ($teachers as $key => $value) {
$authorId = $value['id'];
$authorName = $value['complete_name'];
if (!empty($authorName)) {
$options[$authorId] = $authorName;
}
}
$form->addSelect('authorItemSelect', get_lang('Authors'), $options, ['multiple' => 'multiple']);
$form->addFloat('price', get_lang('Price'));
$form->addHtml('</div>');
$form->addButtonCreate(get_lang('Send'));
$form->setDefaults($default);
if ($form->validate()) {
if (isset($_GET['sub_action']) && ($_GET['sub_action'] === 'author_view')) {
$authors = isset($_POST['authorItemSelect']) ? $_POST['authorItemSelect'] : [];
$items = isset($_POST['itemSelected']) ? $_POST['itemSelected'] : [];
$price = api_float_val($_POST['price']);
unset($author);
$saveExtraFieldItem = [];
$saveAuthor = [];
$removeExist = 0;
foreach ($_SESSION['oLP']->items as $item) {
$itemName = $item->name;
$itemId = $item->iId;
if (isset($items[$itemId])) {
foreach ($authors as $author) {
if ($author == 0 || $removeExist == 1) {
$saveExtraFieldItem[$itemId][0] = 0;
$removeExist = 1;
} else {
$saveExtraFieldItem[$itemId][$author] = $author;
}
}
if ($price > 0) {
$extraFieldValues = $extraFieldValue->get_values_by_handler_and_field_variable(
$itemId,
'price'
);
$extraFieldValue->save([
'variable' => 'price',
'value' => $price,
'item_id' => $itemId,
]);
}
}
}
if (count($saveExtraFieldItem) > 0 || $price > 0) {
$lastEdited = [];
foreach ($saveExtraFieldItem as $saveItemId => $values) {
$extraFieldValues = $extraFieldValue->get_values_by_handler_and_field_variable(
$saveItemId,
'authorlpitem'
);
$extraFieldValue->save([
'variable' => 'authorlpitem',
'value' => $values,
'item_id' => $saveItemId,
]);
$lastEdited = $values;
if (isset($options[$author])) {
$saveAuthor[] = $options[$author];
}
}
$saveAuthor = array_unique($saveAuthor);
$messages = implode(' / ', $saveAuthor);
$currentUrl = api_request_uri();
$redirect = false;
if ($removeExist) {
Display::addFlash(Display::return_message(get_lang('DeletedAuthors')));
$redirect = true;
} elseif ($price > 0) {
Display::addFlash(Display::return_message(get_lang('PriceUpdated')));
$redirect = true;
} elseif (!empty($messages)) {
Display::addFlash(Display::return_message(get_lang('RegisteredAuthors').' '.$messages));
$redirect = true;
}
if ($redirect) {
api_location($currentUrl);
}
}
}
}
Display::display_header(null, 'Path');
echo $learnPath->build_action_menu(
false,
true,
false,
true,
'',
$extraField
);
echo '<div class="row">';
echo '<div id="lp_sidebar" class="col-md-4">';
echo $learnPath->return_new_tree(null, false);
// Show the template list.
if (($type === 'document' || $type === 'step') && !isset($_GET['file'])) {
// Show the template list.
echo '<div id="frmModel" class="scrollbar-inner lp-add-item">';
echo '</div>';
}
echo '</div>';
$form->display();
echo '</div>';
echo '</div>';
Display::display_footer();

View File

@@ -0,0 +1,94 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @author Julio Montoya <gugli100@gmail.com> Adding formvalidator support
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
if (!$is_allowed_to_edit) {
header('location:lp_controller.php?action=list&'.api_get_cidreq());
exit;
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$form = new FormValidator(
'lp_add_category',
'post',
'lp_controller.php?'.api_get_cidreq()
);
// Form title
$form->addElement('header', null, get_lang('AddLPCategory'));
// Title
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor(
'name',
get_lang('Name'),
true,
false,
['ToolbarSet' => 'TitleAsHtml']
);
} else {
$form->addText('name', get_lang('Name'), true);
}
$form->addElement('hidden', 'action', 'add_lp_category');
$form->addElement('hidden', 'c_id', api_get_course_int_id());
$form->addElement('hidden', 'id', 0);
$form->addButtonSave(get_lang('Save'));
if ($form->validate()) {
$values = $form->getSubmitValues();
if (!empty($values['id'])) {
learnpath::updateCategory($values);
$url = api_get_self().'?action=list&'.api_get_cidreq();
Display::addFlash(Display::return_message(get_lang('Updated')));
header('Location: '.$url);
exit;
} else {
learnpath::createCategory($values);
Display::addFlash(Display::return_message(get_lang('Added')));
$url = api_get_self().'?action=list&'.api_get_cidreq();
header('Location: '.$url);
exit;
}
} else {
$id = isset($_REQUEST['id']) ? $_REQUEST['id'] : null;
if ($id) {
$item = learnpath::getCategory($id);
$defaults = [
'id' => $item->getId(),
'name' => $item->getName(),
];
$form->setDefaults($defaults);
}
}
Display::display_header(get_lang('LearnpathAddLearnpath'), 'Path');
echo '<div class="actions">';
echo '<a href="lp_controller.php?'.api_get_cidreq().'">'.
Display::return_icon(
'back.png',
get_lang('ReturnToLearningPaths'),
'',
ICON_SIZE_MEDIUM
).
'</a>';
echo '</div>';
$form->display();
Display::display_footer();

334
main/lp/lp_add_item.php Normal file
View File

@@ -0,0 +1,334 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update
* @author Julio Montoya - Improving the list of templates
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$isStudentView = isset($_REQUEST['isStudentView']) ? $_REQUEST['isStudentView'] : null;
$lpId = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : 0;
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
$type = isset($_GET['type']) ? $_GET['type'] : null;
$action = isset($_GET['action']) ? $_GET['action'] : null;
$is_allowed_to_edit = api_is_allowed_to_edit(null, false);
$listUrl = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=view&lp_id='.$lpId.'&'.api_get_cidreq().'&isStudentView=true';
if (!$is_allowed_to_edit) {
header("Location: $listUrl");
exit;
}
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
if (empty($learnPath)) {
api_not_allowed();
}
if ($learnPath->get_lp_session_id() != api_get_session_id()) {
// You cannot edit an LP from a base course.
header("Location: $listUrl");
exit;
}
$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array()."
function load_cbo(id, previousId) {
if (!id) {
return false;
}
previousId = previousId || 'previous';
var cbo = document.getElementById(previousId);
for (var i = cbo.length - 1; i > 0; i--) {
cbo.options[i] = null;
}
var k=0;
for (var i = 1; i <= child_name[id].length; i++){
var option = new Option(child_name[id][i - 1], child_value[id][i - 1]);
option.style.paddingLeft = '40px';
cbo.options[i] = option;
k = i;
}
cbo.options[k].selected = true;
$('#' + previousId).selectpicker('refresh');
}
$(function() {
if ($('#previous')) {
if('parent is'+$('#idParent').val()) {
load_cbo($('#idParent').val());
}
}
$('.lp_resource_element').click(function() {
window.location.href = $('a', this).attr('href');
});
CKEDITOR.on('instanceReady', function (e) {
showTemplates('content_lp');
});
});
</script>";
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$htmlHeadXtra[] = api_get_jquery_libraries_js(['jquery-ui', 'jquery-upload']);
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$lpId&".api_get_cidreq(),
'name' => $learnPath->getNameNoTags(),
];
switch ($type) {
case 'dir':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$learnPath->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewChapter')];
break;
case 'document':
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=add_item&type=step&lp_id='.$learnPath->get_id().'&'.api_get_cidreq(),
'name' => get_lang('NewStep'),
];
break;
default:
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewStep')];
break;
}
if ($action === 'add_item' && $type === 'document') {
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('NewDocumentCreated')];
}
// Theme calls.
$show_learn_path = true;
$lp_theme_css = $learnPath->get_theme();
Display::display_header(null, 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
$(function() {
jQuery('.scrollbar-inner').scrollbar();
$('#subtab ').on('click', 'a:first', function() {
window.location.reload();
});
expandColumnToogle('#hide_bar_template', {
selector: '#lp_sidebar'
}, {
selector: '#doc_form'
});
$('.lp-btn-associate-forum').on('click', function (e) {
var associate = confirm('<?php echo get_lang('ConfirmAssociateForumToLPItem'); ?>');
if (!associate) {
e.preventDefault();
}
});
$('.lp-btn-dissociate-forum').on('click', function (e) {
var dissociate = confirm('<?php echo get_lang('ConfirmDissociateForumToLPItem'); ?>');
if (!dissociate) {
e.preventDefault();
}
});
// hide the current template list for new documment until it tab clicked
$('#frmModel').hide();
});
// document template for new document tab handler
$(document).on('shown.bs.tab', 'a[data-toggle="tab"]', function (e) {
var id = e.target.id;
if (id == 'subtab2') {
$('#frmModel').show();
} else {
$('#frmModel').hide();
}
})
</script>
<?php
$extraField = [];
$field = new ExtraField('user');
$authorLpField = $field->get_handler_field_info_by_field_variable('authorlp');
if ($authorLpField != null) {
$extraField['authorlp'] = $authorLpField;
}
echo $learnPath->build_action_menu(false,
true,
false,
true,
'',
$extraField
);
echo '<div class="row">';
echo '<div id="lp_sidebar" class="col-md-4">';
echo $learnPath->return_new_tree(null, true);
$message = isset($_REQUEST['message']) ? $_REQUEST['message'] : null;
// Show the template list.
if (($type == 'document' || $type == 'step') && !isset($_GET['file'])) {
// Show the template list.
echo '<div id="frmModel" class="scrollbar-inner lp-add-item">';
echo '</div>';
}
echo '</div>';
echo '<div id="doc_form" class="col-md-8">';
//@todo use session flash messages
if (in_array($message, ['ItemUpdated'])) {
echo Display::return_message(get_lang($message));
}
if (isset($new_item_id) && is_numeric($new_item_id)) {
switch ($type) {
case 'dir':
echo $learnPath->display_manipulate($new_item_id, $_POST['type']);
echo Display::return_message(
get_lang('NewChapterCreated'),
'confirmation'
);
break;
case TOOL_LINK:
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('NewLinksCreated'),
'confirmation'
);
break;
case TOOL_STUDENTPUBLICATION:
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('NewStudentPublicationCreated'),
'confirmation'
);
break;
case TOOL_QUIZ:
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('NewExerciseCreated'),
'confirmation'
);
break;
case TOOL_DOCUMENT:
echo Display::return_message(
get_lang('NewDocumentCreated'),
'confirmation'
);
echo $learnPath->display_item($new_item_id);
break;
case TOOL_FORUM:
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('NewForumCreated'),
'confirmation'
);
break;
case 'thread':
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('NewThreadCreated'),
'confirmation'
);
break;
case TOOL_SURVEY:
echo $learnPath->display_manipulate($new_item_id, $type);
echo Display::return_message(
get_lang('SurveyAdded'),
'confirmation'
);
break;
}
} else {
switch ($type) {
case 'dir':
echo $learnPath->display_item_form(
$type,
get_lang('EnterDataNewChapter')
);
break;
case TOOL_DOCUMENT:
if (isset($_GET['file']) && is_numeric($_GET['file'])) {
echo $learnPath->display_document_form('add', 0, $_GET['file']);
} else {
echo $learnPath->display_document_form('add', 0);
}
break;
case 'hotpotatoes':
echo $learnPath->display_hotpotatoes_form('add', 0, $_GET['file']);
break;
case TOOL_QUIZ:
echo Display::return_message(
get_lang('ExerciseCantBeEditedAfterAddingToTheLP'),
'warning'
);
echo $learnPath->display_quiz_form('add', 0, $_GET['file']);
break;
case TOOL_FORUM:
echo $learnPath->display_forum_form('add', 0, $_GET['forum_id']);
break;
case TOOL_SURVEY:
echo $learnPath->displaySurveyForm('add', 0, $_GET['survey_id']);
break;
case 'thread':
echo $learnPath->display_thread_form('add', 0, $_GET['thread_id']);
break;
case TOOL_LINK:
echo $learnPath->display_link_form('add', 0, $_GET['file']);
break;
case TOOL_STUDENTPUBLICATION:
$extra = isset($_GET['file']) ? $_GET['file'] : null;
echo $learnPath->display_student_publication_form('add', 0, $extra);
break;
case 'step':
$learnPath->display_resources();
break;
}
}
echo '</div>';
echo '</div>';
Display::display_footer();

308
main/lp/lp_admin_view.php Normal file
View File

@@ -0,0 +1,308 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
$isStudentView = isset($_REQUEST['isStudentView']) ? (int) $_REQUEST['isStudentView'] : null;
$learnpath_id = (int) $_REQUEST['lp_id'];
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
$_course = api_get_course_info();
$excludeExtraFields = [
'authors',
'authorlp',
'authorlpitem',
'price',
];
if (api_is_platform_admin()) {
// Only admins can edit this items
$excludeExtraFields = [];
}
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
exit;
}
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
"name" => Security::remove_XSS($learnPath->getNameNoTags()),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
if (isset($_REQUEST['updateaudio'])) {
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('UpdateAllAudioFragments')];
} else {
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('BasicOverview')];
}
$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array().'</script>';
// Theme calls.
$show_learn_path = true;
$lp_theme_css = $learnPath->get_theme();
// POST action handling (uploading mp3, deleting mp3)
if (isset($_POST['save_audio'])) {
// Updating the lp.modified_on
$learnPath->set_modified_on();
$lp_items_to_remove_audio = [];
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
// Deleting the audio fragments.
foreach ($_POST as $key => $value) {
if (substr($key, 0, 9) === 'removemp3') {
$lp_items_to_remove_audio[] = str_ireplace('removemp3', '', $key);
// Removing the audio from the learning path item.
$in = implode(',', $lp_items_to_remove_audio);
}
}
if (count($lp_items_to_remove_audio) > 0) {
$sql = "UPDATE $tbl_lp_item SET audio = ''
WHERE iid IN (".$in.")";
Database::query($sql);
}
// Create the audio folder if it does not exist yet.
DocumentManager::createDefaultAudioFolder($_course);
// Uploading the audio files.
foreach ($_FILES as $key => $value) {
if (substr($key, 0, 7) === 'mp3file' &&
!empty($_FILES[$key]['tmp_name'])
) {
// The id of the learning path item.
$lp_item_id = str_ireplace('mp3file', '', $key);
// Check if file already exits into document/audio/
$file_name = $_FILES[$key]['name'];
$file_name = stripslashes($file_name);
// Add extension to files without one (if possible).
$file_name = add_ext_on_mime($file_name, $_FILES[$key]['type']);
$clean_name = api_replace_dangerous_char($file_name);
// No "dangerous" files.
$clean_name = disable_dangerous_file($clean_name);
$check_file_path = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document/audio/'.$clean_name;
// If the file exists we generate a new name.
if (file_exists($check_file_path)) {
$filename_components = explode('.', $clean_name);
// Gettting the extension of the file.
$file_extension = $filename_components[count($filename_components) - 1];
// Adding something random to prevent overwriting.
$filename_components[count($filename_components) - 1] = time();
// Reconstructing the new filename.
$clean_name = implode($filename_components).'.'.$file_extension;
// Using the new name in the $_FILES superglobal.
$_FILES[$key]['name'] = $clean_name;
}
// Upload the file in the documents tool.
$filePath = handle_uploaded_document(
$_course,
$_FILES[$key],
api_get_path(SYS_COURSE_PATH).$_course['path'].'/document',
'/audio',
api_get_user_id(),
'',
'',
'',
'',
false
);
// Store the mp3 file in the lp_item table.
$sql = "UPDATE $tbl_lp_item
SET audio = '".Database::escape_string($filePath)."'
WHERE iid = ".(int) $lp_item_id;
Database::query($sql);
}
}
Display::addFlash(Display::return_message(get_lang('ItemUpdated'), 'confirm'));
$url = api_get_self().'?action=add_item&type=step&lp_id='.$learnPath->get_id().'&'.api_get_cidreq();
header('Location: '.$url);
exit;
}
Display::display_header(null, 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
var newOrderData= "";
//source code found in http://www.swartzfager.org/blog/dspNestedList.cfm
$(function() {
<?php
if (!isset($_REQUEST['updateaudio'])) {
?>
$("#lp_item_list").sortable({
items: "li",
handle: ".moved", //only the class "moved"
cursor: "move",
placeholder: "ui-state-highlight" //defines the yellow highlight
});
$("#listSubmit").click(function () {
//Disable the submit button to prevent a double-click
$(this).attr("disabled","disabled");
//Initialize the variable that will contain the data to submit to the form
newOrderData= "";
//All direct descendants of the lp_item_list will have a parentId of 0
var parentId= 0;
//Walk through the direct descendants of the lp_item_list <ul>
$("#lp_item_list").children().each(function () {
/*Only process elements with an id attribute (in order to skip the blank,
unmovable <li> elements.*/
if ($(this).attr("id")) {
/*Build a string of data with the child's ID and parent ID,
using the "|" as a delimiter between the two IDs and the "^"
as a record delimiter (these delimiters were chosen in case the data
involved includes more common delimiters like commas within the content)
*/
newOrderData= newOrderData + $(this).attr("id") + "|" + "0" + "^";
//Determine if this child is a containter
if ($(this).is(".li_container")) {
//Process the child elements of the container
processChildren($(this).attr("id"));
}
}
}); //end of lp_item_list children loop
//Write the newOrderData string out to the listResults form element
//$("#listResults").val(newOrderData);
var order = "new_order="+ newOrderData + "&a=update_lp_item_order";
$.post("<?php echo api_get_path(WEB_AJAX_PATH); ?>lp.ajax.php", order, function(reponse) {
$("#message").html(reponse);
});
setTimeout(function() {
$("#message").html('');
}, 3000);
return false;
}); //end of lp_item_list event assignment
<?php
} ?>
function processChildren(parentId) {
//Loop through the children of the UL element defined by the parentId
var ulParentID= "UL_" + parentId;
$("#" + ulParentID).children().each(function () {
/*Only process elements with an id attribute (in order to skip the blank,
unmovable <li> elements.*/
if ($(this).attr("id")) {
/*Build a string of data with the child's ID and parent ID,
using the "|" as a delimiter between the two IDs and the "^"
as a record delimiter (these delimiters were chosen in case the data
involved includes more common delimiters like commas within the content)
*/
newOrderData= newOrderData + $(this).attr("id") + "|" + parentId + "^";
//Determine if this child is a containter
if ($(this).is(".container")) {
//Process the child elements of the container
processChildren($(this).attr("id"));
}
}
}); //end of children loop
} //end of processChildren function
});
/* <![CDATA[ */
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
</script>
<?php
echo $learnPath->build_action_menu();
echo '<div class="row">';
echo '<div class="col-md-4">';
echo $learnPath->return_new_tree(null, true);
echo '</div>';
echo '<div class="col-md-8">';
switch ($_GET['action']) {
case 'edit_item':
if (isset($is_success) && $is_success === true) {
echo Display::return_message(
get_lang('LearnpathItemEdited'),
'confirm'
);
} else {
echo $learnPath->display_edit_item(
$_GET['id'],
$excludeExtraFields
);
}
break;
case 'delete_item':
if (isset($is_success) && $is_success === true) {
echo Display::return_message(
get_lang('LearnpathItemDeleted'),
'confirm'
);
}
break;
}
if (!empty($_GET['updateaudio'])) {
// list of items to add audio files
echo $learnPath->overview();
}
echo '</div>';
echo '</div>';
Display::display_footer();

View File

@@ -0,0 +1,192 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script contains the server part of the xajax interaction process.
* This script, in particular, enables the process of SCO's initialization. It
* resets the JavaScript values for each SCO to the current LMS status.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
/**
* Get one item's details.
*
* @param int LP ID
* @param int user ID
* @param int View ID
* @param int Current item ID
* @param int New item ID
*
* @return string
*/
function initialize_item($lp_id, $user_id, $view_id, $next_item)
{
$debug = 0;
$return = '';
if ($debug) {
error_log('In initialize_item('.$lp_id.','.$user_id.','.$view_id.','.$next_item.')');
}
/*$item_id may be one of:
* -'next'
* -'previous'
* -'first'
* -'last'
* - a real item ID
*/
$mylp = learnpath::getLpFromSession(api_get_course_id(), $lp_id, $user_id);
$mylp->set_current_item($next_item);
if ($debug) {
error_log('In initialize_item() - new item is '.$next_item);
}
$mylp->start_current_item(true);
if (is_object($mylp->items[$next_item])) {
if ($debug) {
error_log('In initialize_item - recovering existing item object '.$next_item, 0);
}
$mylpi = $mylp->items[$next_item];
} else {
if ($debug) {
error_log('In initialize_item - generating new item object '.$next_item, 0);
}
$mylpi = new learnpathItem($next_item, $user_id);
}
if ($mylpi) {
$mylpi->set_lp_view($view_id);
}
/*
* now get what's needed by the SCORM API:
* -score
* -max
* -min
* -lesson_status
* -session_time
* -suspend_data
*/
$myscore = $mylpi->get_score();
$mymax = $mylpi->get_max();
if ('' === $mymax) {
$mymax = "''";
}
$mymin = $mylpi->get_min();
$mylesson_status = $mylpi->get_status();
$mytotal_time = $mylpi->get_scorm_time('js', null, true);
$mymastery_score = $mylpi->get_mastery_score();
$mymax_time_allowed = $mylpi->get_max_time_allowed();
$mylaunch_data = $mylpi->get_launch_data();
$mysession_time = $mylpi->get_total_time();
$mysuspend_data = $mylpi->get_suspend_data();
$mylesson_location = $mylpi->get_lesson_location();
$myic = $mylpi->get_interactions_count();
$myistring = '';
for ($i = 0; $i < $myic; $i++) {
$myistring .= ",[".$i.",'','','','','','','']";
}
if (!empty($myistring)) {
$myistring = substr($myistring, 1);
}
// Obtention des donnees d'objectifs
$mycoursedb = Database::get_course_table(TABLE_LP_IV_OBJECTIVE);
$course_id = api_get_course_int_id();
$mylp_iv_id = $mylpi->db_item_view_id;
$phpobjectives = [];
if (!empty($mylp_iv_id)) {
$sql = "SELECT objective_id, status, score_raw, score_max, score_min
FROM $mycoursedb
WHERE lp_iv_id = $mylp_iv_id AND c_id = $course_id
ORDER BY id ASC;";
$res = Database::query($sql);
while ($row = Database::fetch_row($res)) {
$phpobjectives[] = $row;
}
}
$myobjectives = json_encode($phpobjectives);
$return .=
"olms.score=".$myscore.";".
"olms.max=".$mymax.";".
"olms.min=".$mymin.";".
"olms.lesson_status='".$mylesson_status."';".
"olms.lesson_location='".$mylesson_location."';".
"olms.session_time='".$mysession_time."';".
"olms.suspend_data='".$mysuspend_data."';".
"olms.total_time = '".$mytotal_time."';".
"olms.mastery_score = '".$mymastery_score."';".
"olms.max_time_allowed = '".$mymax_time_allowed."';".
"olms.launch_data = '".$mylaunch_data."';".
"olms.interactions = new Array(".$myistring.");".
//"olms.item_objectives = new Array();" .
"olms.item_objectives = ".$myobjectives.";".
"olms.G_lastError = 0;".
"olms.G_LastErrorMessage = 'No error';".
"olms.finishSignalReceived = 0;";
/*
* and re-initialise the rest (proper to the LMS)
* -lms_lp_id
* -lms_item_id
* -lms_old_item_id
* -lms_new_item_id
* -lms_initialized
* -lms_progress_bar_mode
* -lms_view_id
* -lms_user_id
*/
$mynext = $mylp->get_next_item_id();
$myprevious = $mylp->get_previous_item_id();
$myitemtype = $mylpi->get_type();
$mylesson_mode = $mylpi->get_lesson_mode();
$mycredit = $mylpi->get_credit();
$mylaunch_data = $mylpi->get_launch_data();
$myinteractions_count = $mylpi->get_interactions_count();
$mycore_exit = $mylpi->get_core_exit();
$return .=
"olms.lms_lp_id=".$lp_id.";".
"olms.lms_item_id=".$next_item.";".
"olms.lms_old_item_id=0;".
"olms.lms_initialized=0;".
"olms.lms_view_id=".$view_id.";".
"olms.lms_user_id=".$user_id.";".
"olms.next_item=".$next_item.";".// This one is very important to replace possible literal strings.
"olms.lms_next_item=".$mynext.";".
"olms.lms_previous_item=".$myprevious.";".
"olms.lms_item_type = '".$myitemtype."';".
"olms.lms_item_credit = '".$mycredit."';".
"olms.lms_item_lesson_mode = '".$mylesson_mode."';".
"olms.lms_item_launch_data = '".$mylaunch_data."';".
"olms.lms_item_interactions_count = '".$myinteractions_count."';".
"olms.lms_item_objectives_count = '".$myinteractions_count."';".
"olms.lms_item_core_exit = '".$mycore_exit."';".
"olms.asset_timer = 0;";
$mylp->set_error_msg('');
$mylp->prerequisites_match(); // Check the prerequisites are all complete.
if ($debug) {
error_log('Prereq_match() returned '.htmlentities($mylp->error), 0);
error_log("return = $return ");
error_log("mylp->lp_view_session_id: ".$mylp->lp_view_session_id);
}
if (isset($mylp->lti_launch_id)) {
$ltiLaunchId = $mylp->lti_launch_id;
$return .= "sendLtiLaunch('$ltiLaunchId', '$lp_id');";
}
return $return;
}
echo initialize_item(
$_POST['lid'],
$_POST['uid'],
$_POST['vid'],
$_POST['iid']
);

View File

@@ -0,0 +1,634 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This script contains the server part of the AJAX interaction process.
* The client part is located * in lp_api.php or other api's.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
/**
* Writes an item's new values into the database and returns the operation result.
*
* @param int $lp_id Learnpath ID
* @param int $user_id User ID
* @param int $view_id View ID
* @param int $item_id Item ID
* @param float $score Current score
* @param float $max Maximum score
* @param float $min Minimum score
* @param string $status Lesson status
* @param int $time Session time
* @param string $suspend Suspend data
* @param string $location Lesson location
* @param array $interactions Interactions array
* @param string $core_exit Core exit SCORM string
* @param int $sessionId Session ID
* @param int $courseId Course ID
* @param int $lmsFinish Whether the call was issued from SCORM's LMSFinish()
* @param int $userNavigatesAway Whether the user is moving to another item
* @param int $statusSignalReceived Whether the SCO called SetValue(lesson_status)
*
* @return bool|string|null The resulting JS string
*/
function save_item(
$lp_id,
$user_id,
$view_id,
$item_id,
$score = -1.0,
$max = -1.0,
$min = -1.0,
$status = '',
$time = 0,
$suspend = '',
$location = '',
$interactions = [],
$core_exit = 'none',
$sessionId = null,
$courseId = null,
$lmsFinish = 0,
$userNavigatesAway = 0,
$statusSignalReceived = 0,
$forceIframeSave = 0
) {
$debug = 0;
$return = null;
$courseCode = api_get_course_id();
if (!empty($courseId)) {
$courseInfo = api_get_course_info_by_id($courseId);
if ($courseInfo) {
$courseCode = $courseInfo['code'];
}
}
if ($debug > 0) {
error_log('--------------------------------------');
error_log('SAVE ITEM - lp_ajax_save_item.php');
error_log('--------------------------------------');
error_log("item_id: $item_id - lp_id: $lp_id - user_id: - $user_id - view_id: $view_id - item_id: $item_id");
error_log("SCORE: $score - max:$max - min: $min - status:$status");
error_log("TIME: $time - suspend: $suspend - location: $location - core_exit: $core_exit");
error_log("finish: $lmsFinish - navigatesAway: $userNavigatesAway");
error_log("courseCode: $courseCode");
}
$myLP = learnpath::getLpFromSession($courseCode, $lp_id, $user_id);
if (!is_a($myLP, 'learnpath')) {
if ($debug) {
error_log('mylp variable is not an learnpath object');
}
return null;
}
$prerequisitesCheck = $myLP->prerequisites_match($item_id);
/** @var learnpathItem $myLPI */
if ($myLP->items && isset($myLP->items[$item_id])) {
$myLPI = $myLP->items[$item_id];
}
if (empty($myLPI)) {
if ($debug > 0) {
error_log("item #$item_id not found in the items array: ".print_r($myLP->items, 1));
}
return null;
}
// This functions sets the $this->db_item_view_id variable needed in get_status() see BT#5069
$myLPI->set_lp_view($view_id);
$my_type = $myLPI->get_type();
$saveStatus = true;
if ('document' === $my_type) {
$saveStatus = learnpathItem::isLpItemAutoComplete($myLPI->getIid());
if ($forceIframeSave) {
$saveStatus = true;
}
}
// Launch the prerequisites check and set error if needed
if (true !== $prerequisitesCheck) {
// If prerequisites were not matched, don't update any item info
if ($debug) {
error_log("prereq_check failed: ".intval($prerequisitesCheck));
}
return null;
} else {
if ($debug > 1) {
error_log('Prerequisites are OK');
}
$logInfo = [
'tool' => TOOL_LEARNPATH,
'tool_id' => $lp_id,
'tool_id_detail' => $item_id,
'action' => 'view',
'action_details' => $myLP->getCurrentAttempt(),
];
Event::registerLog($logInfo);
/*$logInfo = [
'tool' => TOOL_LEARNPATH,
'tool_id' => $lp_id,
'tool_id_detail' => $item_id,
'action' => 'set_status_score',
'action_details' => $status.':'.$score,
];
Event::registerLog($logInfo);*/
if (isset($max) && $max != -1) {
$myLPI->max_score = $max;
$myLPI->set_max_score($max);
if ($debug > 1) {
error_log("Setting max_score: $max");
}
}
if (isset($min) && $min != -1 && $min != 'undefined') {
$myLPI->min_score = $min;
if ($debug > 1) {
error_log("Setting min_score: $min");
}
}
// set_score function used to save the status, but this is not the case anymore
if (isset($score) && $score != -1) {
if ($debug > 1) {
error_log('Calling set_score('.$score.')');
error_log('set_score changes the status to failed/passed if mastery score is provided');
}
$myLPI->set_score($score);
if ($debug > 1) {
error_log('Done calling set_score '.$myLPI->get_score());
}
} else {
if ($debug > 1) {
error_log('Score not updated');
}
}
$statusIsSet = false;
if ($saveStatus) {
// Default behaviour.
if (isset($status) && $status != '' && $status != 'undefined') {
if ($debug > 1) {
error_log('Calling set_status('.$status.')');
}
$myLPI->set_status($status);
$statusIsSet = true;
if ($debug > 1) {
error_log('Done calling set_status: checking from memory: '.$myLPI->get_status(false));
}
} else {
if ($debug > 1) {
error_log('Status not updated');
}
}
} else {
if ($debug > 1) {
error_log('Status not updated');
}
}
// Set status to completed for hotpotatoes if score > 80%.
if ($my_type === 'hotpotatoes') {
if ((empty($status) || $status == 'undefined' || $status == 'not attempted') && $max > 0) {
if (($score / $max) > 0.8) {
$myStatus = 'completed';
if ($debug > 1) {
error_log('Calling set_status('.$myStatus.') for hotpotatoes');
}
$myLPI->set_status($myStatus);
$statusIsSet = true;
if ($debug > 1) {
error_log('Done calling set_status for hotpotatoes - now '.$myLPI->get_status(false));
}
}
} elseif ($status == 'completed' && $max > 0 && ($score / $max) < 0.8) {
$myStatus = 'failed';
if ($debug > 1) {
error_log('Calling set_status('.$myStatus.') for hotpotatoes');
}
$myLPI->set_status($myStatus);
$statusIsSet = true;
if ($debug > 1) {
error_log('Done calling set_status for hotpotatoes - now '.$myLPI->get_status(false));
}
}
} elseif ($my_type === 'sco') {
/*
* This is a specific implementation for SCORM 1.2, matching page 26 of SCORM 1.2's RTE
* "Normally the SCO determines its own status and passes it to the LMS.
* 1) If cmi.core.credit is set to "credit" and there is a mastery
* score in the manifest (adlcp:masteryscore), the LMS can change
* the status to either passed or failed depending on the
* student's score compared to the mastery score.
* 2) If there is no mastery score in the manifest
* (adlcp:masteryscore), the LMS cannot override SCO
* determined status.
* 3) If the student is taking the SCO for no-credit, there is no
* change to the lesson_status, with one exception. If the
* lesson_mode is "browse", the lesson_status may change to
* "browsed" even if the cmi.core.credit is set to no-credit.
* "
* Additionally, the LMS behaviour should be:
* If a SCO sets the cmi.core.lesson_status then there is no problem.
* However, the SCORM does not force the SCO to set the cmi.core.lesson_status.
* There is some additional requirements that must be adhered to
* successfully handle these cases:
* Upon initial launch
* the LMS should set the cmi.core.lesson_status to "not attempted".
* Upon receiving the LMSFinish() call or the user navigates away,
* the LMS should set the cmi.core.lesson_status for the SCO to "completed".
* After setting the cmi.core.lesson_status to "completed",
* the LMS should now check to see if a Mastery Score has been
* specified in the cmi.student_data.mastery_score, if supported,
* or the manifest that the SCO is a member of.
* If a Mastery Score is provided and the SCO did set the
* cmi.core.score.raw, the LMS shall compare the cmi.core.score.raw
* to the Mastery Score and set the cmi.core.lesson_status to
* either "passed" or "failed". If no Mastery Score is provided,
* the LMS will leave the cmi.core.lesson_status as "completed"
*/
$masteryScore = $myLPI->get_mastery_score();
if ($masteryScore == -1 || empty($masteryScore)) {
$masteryScore = false;
}
$credit = $myLPI->get_credit();
/**
* 1) If cmi.core.credit is set to "credit" and there is a mastery
* score in the manifest (adlcp:masteryscore), the LMS can change
* the status to either passed or failed depending on the
* student's score compared to the mastery score.
*/
if ($credit === 'credit' &&
$masteryScore &&
(isset($score) && $score != -1) &&
!$statusIsSet && !$statusSignalReceived
) {
if ($score >= $masteryScore) {
$myLPI->set_status('passed');
if ($debug) {
error_log('Set status: passed');
}
} else {
$myLPI->set_status('failed');
if ($debug) {
error_log('Set status: failed');
}
}
$statusIsSet = true;
}
/**
* 2) If there is no mastery score in the manifest
* (adlcp:masteryscore), the LMS cannot override SCO
* determined status.
*/
if (!$statusIsSet && !$masteryScore && !$statusSignalReceived) {
if (!empty($status)) {
if ($debug) {
error_log("Set status: $status because: statusSignalReceived ");
}
$myLPI->set_status($status);
$statusIsSet = true;
}
//if no status was set directly, we keep the previous one
}
/**
* 3) If the student is taking the SCO for no-credit, there is no
* change to the lesson_status, with one exception. If the
* lesson_mode is "browse", the lesson_status may change to
* "browsed" even if the cmi.core.credit is set to no-credit.
*/
if (!$statusIsSet && $credit == 'no-credit' && !$statusSignalReceived) {
$mode = $myLPI->get_lesson_mode();
if ($mode == 'browse' && $status == 'browsed') {
if ($debug) {
error_log("Set status: $status because mode browse");
}
$myLPI->set_status($status);
$statusIsSet = true;
}
//if no status was set directly, we keep the previous one
}
/**
* If a SCO sets the cmi.core.lesson_status then there is no problem.
* However, the SCORM does not force the SCO to set the
* cmi.core.lesson_status. There is some additional requirements
* that must be adhered to successfully handle these cases:.
*/
$LMSUpdateStatus = true;
if (!api_get_configuration_value('scorm_lms_update_status_all_time') && $myLPI->get_status() !== "not attempted") {
$LMSUpdateStatus = false;
}
if (!$statusIsSet && empty($status) && !$statusSignalReceived && $LMSUpdateStatus) {
/**
* Upon initial launch the LMS should set the
* cmi.core.lesson_status to "not attempted".
*/
// this case should be handled by LMSInitialize() and xajax_switch_item()
/**
* Upon receiving the LMSFinish() call or the user navigates
* away, the LMS should set the cmi.core.lesson_status for the
* SCO to "completed".
*/
if ($lmsFinish || $userNavigatesAway) {
$myStatus = 'completed';
$updateStatus = true;
// Do not update status if "score as progress" and $userNavigatesAway
// The progress will be saved by the scorm BT#16766.
if ($userNavigatesAway && !$lmsFinish && $myLP->getUseScoreAsProgress()) {
$updateStatus = false;
}
if ($updateStatus) {
/**
* After setting the cmi.core.lesson_status to "completed",
* the LMS should now check to see if a Mastery Score has been
* specified in the cmi.student_data.mastery_score, if supported,
* or the manifest that the SCO is a member of.
* If a Mastery Score is provided and the SCO did set the
* cmi.core.score.raw, the LMS shall compare the cmi.core.score.raw
* to the Mastery Score and set the cmi.core.lesson_status to
* either "passed" or "failed". If no Mastery Score is provided,
* the LMS will leave the cmi.core.lesson_status as "completed”.
*/
if ($masteryScore && (isset($score) && $score != -1)) {
if ($score >= $masteryScore) {
$myStatus = 'passed';
} else {
$myStatus = 'failed';
}
}
if ($debug) {
error_log("Set status: $myStatus because lmsFinish || userNavigatesAway");
}
$myLPI->set_status($myStatus);
$statusIsSet = true;
}
}
}
// End of type=='sco'
}
// If no previous condition changed the SCO status, proceed with a generic behaviour
if ($saveStatus) {
if (!$statusIsSet && !$statusSignalReceived) {
// Default behaviour
if (isset($status) && $status != '' && $status != 'undefined') {
if ($debug > 1) {
error_log('Calling set_status('.$status.')');
}
$myLPI->set_status($status);
if ($debug > 1) {
error_log('Done calling set_status: checking from memory: '.$myLPI->get_status(false));
}
} else {
if ($debug > 1) {
error_log("Status not updated");
}
}
}
} else {
if ($debug > 1) {
error_log("Status not updated");
}
}
if (isset($time) && $time != '' && $time != 'undefined') {
// If big integer, then it's a timestamp, otherwise it's normal scorm time.
if ($debug > 1) {
error_log('Calling set_time('.$time.') ');
}
if ($time == intval(strval($time)) && $time > 1000000) {
if ($debug > 1) {
error_log("Time is INT");
}
$real_time = time() - $time;
if ($debug > 1) {
error_log('Calling $real_time '.$real_time.' ');
}
$myLPI->set_time($real_time, 'int');
} else {
if ($debug > 1) {
error_log("Time is in SCORM format");
}
if ($debug > 1) {
error_log('Calling $time '.$time.' ');
}
$myLPI->set_time($time, 'scorm');
}
} else {
$myLPI->current_stop_time = time();
}
if (isset($suspend) && $suspend != '' && $suspend != 'undefined') {
$myLPI->current_data = $suspend;
}
if (isset($location) && $location != '' && $location != 'undefined') {
$myLPI->set_lesson_location($location);
}
// Deal with interactions provided in arrays in the following format:
// id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7)
if (is_array($interactions) && count($interactions) > 0) {
foreach ($interactions as $index => $interaction) {
//$mylpi->add_interaction($index,$interactions[$index]);
//fix DT#4444
$clean_interaction = str_replace('@.|@', ',', $interactions[$index]);
$myLPI->add_interaction($index, $clean_interaction);
}
}
if ($core_exit != 'undefined') {
$myLPI->set_core_exit($core_exit);
}
if ($saveStatus) {
$myLP->save_item($item_id, false);
}
}
$myStatusInDB = $myLPI->get_status(true);
if ($debug) {
error_log("Status in DB: $myStatusInDB");
}
if ($myStatusInDB != 'completed' &&
$myStatusInDB != 'passed' &&
$myStatusInDB != 'browsed' &&
$myStatusInDB != 'failed'
) {
$myStatusInMemory = $myLPI->get_status(false);
if ($debug) {
error_log("myStatusInMemory: $myStatusInMemory");
}
if ($myStatusInMemory != $myStatusInDB) {
$myStatus = $myStatusInMemory;
} else {
$myStatus = $myStatusInDB;
}
} else {
$myStatus = $myStatusInDB;
}
$myTotal = $myLP->getTotalItemsCountWithoutDirs();
$myComplete = $myLP->get_complete_items_count();
$myProgressMode = $myLP->get_progress_bar_mode();
$myProgressMode = $myProgressMode == '' ? '%' : $myProgressMode;
if ($debug > 1) {
error_log("mystatus: $myStatus");
error_log("myprogress_mode: $myProgressMode");
error_log("progress: $myComplete / $myTotal");
}
if ($saveStatus) {
if ($myLPI->get_type() !== 'sco') {
// If this object's JS status has not been updated by the SCORM API, update now.
$return .= "olms.lesson_status='".$myStatus."';";
}
$return .= "update_toc('".$myStatus."','".$item_id."');";
$update_list = $myLP->get_update_queue();
foreach ($update_list as $my_upd_id => $my_upd_status) {
if ($my_upd_id != $item_id) {
/* Only update the status from other items (i.e. parents and brothers),
do not update current as we just did it already. */
$return .= "update_toc('".$my_upd_status."','".$my_upd_id."');";
}
}
$progressBarSpecial = false;
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
if ($scoreAsProgressSetting === true) {
$scoreAsProgress = $myLP->getUseScoreAsProgress();
if ($scoreAsProgress) {
// Only update score if it was set by scorm.
if (isset($score) && $score != -1) {
$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 (isset($myLP->lti_launch_id)) {
$ltiLaunchId = $myLP->lti_launch_id;
$return .= "sendLtiLaunch('$ltiLaunchId', '$lp_id');";
}
}
if (!Session::read('login_as')) {
// If $_SESSION['login_as'] is set, then the user is an admin logged as the user.
$tbl_track_login = Database::get_main_table(TABLE_STATISTIC_TRACK_E_LOGIN);
$sql = "SELECT login_id, login_date
FROM $tbl_track_login
WHERE login_user_id= ".api_get_user_id()."
ORDER BY login_date DESC
LIMIT 0,1";
$q_last_connection = Database::query($sql);
if (Database::num_rows($q_last_connection) > 0) {
$current_time = api_get_utc_datetime();
$row = Database::fetch_array($q_last_connection);
$i_id_last_connection = $row['login_id'];
$sql = "UPDATE $tbl_track_login
SET logout_date='".$current_time."'
WHERE login_id = $i_id_last_connection";
Database::query($sql);
}
}
if ($myLP->get_type() == 2) {
$return .= 'update_stats();';
}
if ($saveStatus) {
// To be sure progress is updated.
$myLP->save_last($score);
HookLearningPathItemViewed::create()
->setEventData(['item_view_id' => $myLPI->db_item_view_id])
->notifyLearningPathItemViewed();
Session::write('lpobject', serialize($myLP));
Session::write('oLP', $myLP);
}
if ($debug > 0) {
error_log("lp_view_session_id :".$myLP->lp_view_session_id);
error_log('---------------- lp_ajax_save_item.php : save_item end ----- ');
}
$logInfo = [
'tool' => TOOL_LEARNPATH,
'tool_id' => $myLP->get_id(),
'action_details' => $myLP->getCurrentAttempt(),
'tool_id_detail' => $myLP->get_current_item_id(),
'action' => 'save_item',
];
Event::registerLog($logInfo);
return $return;
}
$interactions = [];
if (isset($_REQUEST['interact'])) {
if (is_array($_REQUEST['interact'])) {
foreach ($_REQUEST['interact'] as $idx => $interac) {
$interactions[$idx] = preg_split('/,/', substr($interac, 1, -1));
if (!isset($interactions[$idx][7])) { // Make sure there are 7 elements.
$interactions[$idx][7] = '';
}
}
}
}
echo save_item(
(!empty($_REQUEST['lid']) ? $_REQUEST['lid'] : null),
(!empty($_REQUEST['uid']) ? $_REQUEST['uid'] : null),
(!empty($_REQUEST['vid']) ? $_REQUEST['vid'] : null),
(!empty($_REQUEST['iid']) ? $_REQUEST['iid'] : null),
(!empty($_REQUEST['s']) ? $_REQUEST['s'] : null),
(!empty($_REQUEST['max']) ? $_REQUEST['max'] : null),
(!empty($_REQUEST['min']) ? $_REQUEST['min'] : null),
(!empty($_REQUEST['status']) ? $_REQUEST['status'] : null),
(!empty($_REQUEST['t']) ? $_REQUEST['t'] : null),
(!empty($_REQUEST['suspend']) ? $_REQUEST['suspend'] : null),
(!empty($_REQUEST['loc']) ? $_REQUEST['loc'] : null),
$interactions,
(!empty($_REQUEST['core_exit']) ? $_REQUEST['core_exit'] : ''),
(!empty($_REQUEST['session_id']) ? $_REQUEST['session_id'] : ''),
(!empty($_REQUEST['course_id']) ? $_REQUEST['course_id'] : ''),
(empty($_REQUEST['finish']) ? 0 : 1),
(empty($_REQUEST['userNavigatesAway']) ? 0 : 1),
(empty($_REQUEST['statusSignalReceived']) ? 0 : 1),
isset($_REQUEST['forceIframeSave']) ? (int) $_REQUEST['forceIframeSave'] : 0
);

View File

@@ -0,0 +1,62 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script contains the server part of the xajax interaction process.
* The client part is located in lp_api.php or other api's.
* This is a first attempt at using xajax and AJAX in general,
* so the code might be a bit unsettling.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
/**
* Writes an item's new values into the database and returns the operation result.
*
* @param int Learnpath ID
* @param int User ID
* @param int View ID
* @param int Item ID
* @param array Objectives array
*/
function save_objectives($lp_id, $user_id, $view_id, $item_id, $objectives = [])
{
$debug = 0;
$return = '';
if ($debug > 0) {
error_log('In xajax_save_objectives('.$lp_id.','.$user_id.','.$view_id.','.$item_id.',"'.(count($objectives) > 0 ? count($objectives) : '').'")', 0);
}
$mylp = learnpath::getLpFromSession(api_get_course_id(), $lp_id, $user_id);
$mylpi = &$mylp->items[$item_id];
if (is_array($objectives) && count($objectives) > 0) {
foreach ($objectives as $index => $objective) {
$mylpi->add_objective($index, $objectives[$index]);
}
$mylpi->write_objectives_to_db();
}
return $return;
}
$objectives = [];
if (isset($_REQUEST['objectives'])) {
if (is_array($_REQUEST['objectives'])) {
foreach ($_REQUEST['objectives'] as $idx => $ob) {
$objectives[$idx] = explode(',', substr($ob, 1, -1));
if (!isset($objectives[$idx][4])) {
// Make sure there are 7 elements.
$objectives[$idx][4] = '';
}
}
}
}
echo save_objectives(
$_REQUEST['lid'],
$_REQUEST['uid'],
$_REQUEST['vid'],
$_REQUEST['iid'],
$objectives
);

View File

@@ -0,0 +1,7 @@
<?php
/* For licensing terms, see /license.txt */
/**
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
echo time();

View File

@@ -0,0 +1,293 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This script contains the server part of the xajax interaction process. The client part is located
* in lp_api.php or other api's.
* This is a first attempt at using xajax and AJAX in general, so the code might be a bit unsettling.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
/**
* Get one item's details.
*
* @param int LP ID
* @param int user ID
* @param int View ID
* @param int Current item ID
* @param int New item ID
*/
function switch_item_details($lp_id, $user_id, $view_id, $current_item, $next_item)
{
$debug = 0;
$return = '';
if ($debug > 0) {
error_log('--------------------------------------');
error_log('SWITCH');
error_log('Params('.$lp_id.','.$user_id.','.$view_id.','.$current_item.','.$next_item.')');
}
//$objResponse = new xajaxResponse();
/*$item_id may be one of:
* -'next'
* -'previous'
* -'first'
* -'last'
* - a real item ID
*/
$mylp = learnpath::getLpFromSession(api_get_course_id(), $lp_id, $user_id);
$new_item_id = 0;
$saveStatus = learnpathItem::isLpItemAutoComplete($current_item);
switch ($next_item) {
case 'next':
$mylp->set_current_item($current_item);
$mylp->next();
$new_item_id = $mylp->get_current_item_id();
if ($debug > 1) {
error_log('In {next} - next item is '.$new_item_id.'(current: '.$current_item.')');
}
break;
case 'previous':
$mylp->set_current_item($current_item);
$mylp->previous();
$new_item_id = $mylp->get_current_item_id();
if ($debug > 1) {
error_log('In {previous} - next item is '.$new_item_id.'(current: '.$current_item.')');
}
break;
case 'first':
$mylp->set_current_item($current_item);
$mylp->first();
$new_item_id = $mylp->get_current_item_id();
if ($debug > 1) {
error_log('In {first} - next item is '.$new_item_id.'(current: '.$current_item.')');
}
break;
case 'last':
break;
default:
// Should be filtered to check it's not hacked.
if ($next_item == $current_item) {
// If we're opening the same item again.
$mylp->items[$current_item]->restart();
}
$new_item_id = $next_item;
$mylp->set_current_item($new_item_id);
if ($debug > 1) {
error_log('In {default} - next item is '.$new_item_id.'(current: '.$current_item.')');
}
break;
}
if (WhispeakAuthPlugin::isLpItemMarked($new_item_id)) {
ChamiloSession::write(
WhispeakAuthPlugin::SESSION_LP_ITEM,
['lp' => $lp_id, 'lp_item' => $new_item_id, 'src' => '']
);
}
$mylp->start_current_item(true);
if ($saveStatus) {
if ($mylp->force_commit) {
$mylp->save_current();
}
}
if (is_object($mylp->items[$new_item_id])) {
$mylpi = $mylp->items[$new_item_id];
} else {
if ($debug > 1) {
error_log('In switch_item_details - generating new item object', 0);
}
$mylpi = new learnpathItem($new_item_id, $user_id);
$mylpi->set_lp_view($view_id);
}
/*
* now get what's needed by the SCORM API:
* -score
* -max
* -min
* -lesson_status
* -session_time
* -suspend_data
*/
$myscore = $mylpi->get_score();
$mymax = $mylpi->get_max();
if ('' === $mymax) {
$mymax = "''";
}
$mymin = $mylpi->get_min();
$mylesson_status = $mylpi->get_status();
$mylesson_location = $mylpi->get_lesson_location();
$mytotal_time = $mylpi->get_scorm_time('js');
$mymastery_score = $mylpi->get_mastery_score();
$mymax_time_allowed = $mylpi->get_max_time_allowed();
$mylaunch_data = $mylpi->get_launch_data();
/*
if ($mylpi->get_type() == 'asset') {
// Temporary measure to save completion of an asset. Later on,
// Chamilo should trigger something on unload, maybe...
// (even though that would mean the last item cannot be completed)
$mylesson_status = 'completed';
$mylpi->set_status('completed');
$mylpi->save();
}
*/
$mysession_time = $mylpi->get_total_time();
$mysuspend_data = $mylpi->get_suspend_data();
$mylesson_location = $mylpi->get_lesson_location();
$myic = $mylpi->get_interactions_count();
$myistring = '';
for ($i = 0; $i < $myic; $i++) {
$myistring .= ",[".$i.",'','','','','','','']";
}
if (!empty($myistring)) {
$myistring = substr($myistring, 1);
}
/*
* The following lines should reinitialize the values for the SCO
* However, due to many complications, we are now relying more on the
* LMSInitialize() call and its underlying lp_ajax_initialize.php call
* so this code is technically deprecated (but the change of item_id should
* remain). However, due to numerous technical issues with SCORM, we prefer
* leaving it as a double-lock security. If removing, please test carefully
* with both SCORM and proper learning path tracking.
*/
$return .=
"olms.score=".$myscore.";".
"olms.max=".$mymax.";".
"olms.min=".$mymin.";".
"olms.lesson_status='".$mylesson_status."';".
"olms.lesson_location='".$mylesson_location."';".
"olms.session_time='".$mysession_time."';".
"olms.suspend_data='".$mysuspend_data."';".
"olms.total_time = '".$mytotal_time."';".
"olms.mastery_score = '".$mymastery_score."';".
"olms.max_time_allowed = '".$mymax_time_allowed."';".
"olms.launch_data = '".$mylaunch_data."';".
"olms.interactions = new Array(".$myistring.");".
"olms.item_objectives = new Array();".
"olms.G_lastError = 0;".
"olms.G_LastErrorMessage = 'No error';".
"olms.finishSignalReceived = 0;";
/*
* and re-initialise the rest
* -lms_lp_id
* -lms_item_id
* -lms_old_item_id
* -lms_new_item_id
* -lms_initialized
* -lms_progress_bar_mode
* -lms_view_id
* -lms_user_id
*/
$mytotal = $mylp->getTotalItemsCountWithoutDirs();
$mycomplete = $mylp->get_complete_items_count();
$myprogress_mode = $mylp->get_progress_bar_mode();
$myprogress_mode = ('' == $myprogress_mode ? '%' : $myprogress_mode);
$mynext = $mylp->get_next_item_id();
$myprevious = $mylp->get_previous_item_id();
$myitemtype = $mylpi->get_type();
$mylesson_mode = $mylpi->get_lesson_mode();
$mycredit = $mylpi->get_credit();
$mylaunch_data = $mylpi->get_launch_data();
$myinteractions_count = $mylpi->get_interactions_count();
//$myobjectives_count = $mylpi->get_objectives_count();
$mycore_exit = $mylpi->get_core_exit();
$return .=
//"saved_lesson_status='not attempted';" .
"olms.lms_lp_id=".$lp_id.";".
"olms.lms_item_id=".$new_item_id.";".
"olms.lms_old_item_id=0;".
//"lms_been_synchronized=0;" .
"olms.lms_initialized=0;".
//"lms_total_lessons=".$mytotal.";" .
//"lms_complete_lessons=".$mycomplete.";" .
//"lms_progress_bar_mode='".$myprogress_mode."';" .
"olms.lms_view_id=".$view_id.";".
"olms.lms_user_id=".$user_id.";".
"olms.next_item=".$new_item_id.";".// This one is very important to replace possible literal strings.
"olms.lms_next_item=".$mynext.";".
"olms.lms_previous_item=".$myprevious.";".
"olms.lms_item_type = '".$myitemtype."';".
"olms.lms_item_credit = '".$mycredit."';".
"olms.lms_item_lesson_mode = '".$mylesson_mode."';".
"olms.lms_item_launch_data = '".$mylaunch_data."';".
"olms.lms_item_interactions_count = '".$myinteractions_count."';".
"olms.lms_item_objectives_count = '".$myinteractions_count."';".
"olms.lms_item_core_exit = '".$mycore_exit."';".
"olms.asset_timer = 0;";
$sessionId = api_get_session_id();
$updateMinTime = '';
if (Tracking::minimumTimeAvailable($sessionId, api_get_course_int_id())) {
$timeLp = $mylp->getAccumulateWorkTime();
$timeTotalCourse = $mylp->getAccumulateWorkTimeTotalCourse();
// Minimum connection percentage
$perc = 100;
// Time from the course
$tc = $timeTotalCourse;
// Percentage of the learning paths
$pl = 0;
if (!empty($timeTotalCourse)) {
$pl = $timeLp / $timeTotalCourse;
}
// Minimum time for each learning path
$time_total = intval($pl * $tc * $perc / 100) * 60;
$lpTimeList = Tracking::getCalculateTime($user_id, api_get_course_int_id(), $sessionId);
$lpTime = isset($lpTimeList[TOOL_LEARNPATH][$lp_id]) ? $lpTimeList[TOOL_LEARNPATH][$lp_id] : 0;
if ($lpTime >= $time_total) {
$time_spent = $time_total;
} else {
$time_spent = $lpTime;
}
$hour = (intval($lpTime / 3600)) < 10 ? '0'.intval($lpTime / 3600) : intval($lpTime / 3600);
$minute = date('i', $lpTime);
$second = date('s', $lpTime);
$updateMinTime = "update_time_bar('$time_spent','$time_total','%');".
"update_chronometer('$hour','$minute','$second');";
}
$return .=
"update_toc('unhighlight','".$current_item."');".
"update_toc('highlight','".$new_item_id."');".
"update_toc('$mylesson_status','".$new_item_id."');".
"update_progress_bar('$mycomplete','$mytotal','$myprogress_mode');".
$updateMinTime
;
$return .= 'updateGamificationValues(); ';
$mylp->set_error_msg('');
$mylp->prerequisites_match(); // Check the prerequisites are all complete.
if ($debug > 1) {
error_log($return);
error_log('Prereq_match() returned '.htmlentities($mylp->error), 0);
}
// Save the new item ID for the exercise tool to use.
Session::write('scorm_item_id', $new_item_id);
Session::write('lpobject', serialize($mylp));
Session::write('oLP', $mylp);
return $return;
}
echo switch_item_details(
$_REQUEST['lid'],
$_REQUEST['uid'],
$_REQUEST['vid'],
$_REQUEST['iid'],
$_REQUEST['next']
);

View File

@@ -0,0 +1,190 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This script contains the server part of the ajax interaction process. The client part is located
* in lp_api.php or other api's.
* This script updated the TOC of the SCORM without updating the SCO's attributes.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
/**
* Get one item's details.
*
* @param int $lpId LP ID
* @param int $userId user ID
* @param int $viewId View ID
* @param int $currentItem Current item ID
* @param int $nextItem New item ID
*
* @return string JavaScript commands to be executed in scorm_api.php
*/
function switch_item_toc($lpId, $userId, $viewId, $currentItem, $nextItem)
{
$debug = 0;
$return = '';
if ($debug > 0) {
error_log('In switch_item_toc('.$lpId.','.$userId.','.$viewId.','.$currentItem.','.$nextItem.')', 0);
}
$myLP = learnpath::getLpFromSession(api_get_course_id(), $lpId, $userId);
$saveStatus = learnpathItem::isLpItemAutoComplete($currentItem);
$newItemId = 0;
$oldItemId = 0;
switch ($nextItem) {
case 'next':
$myLP->set_current_item($currentItem);
$myLP->next();
$newItemId = $myLP->get_current_item_id();
if ($debug > 1) {
error_log('In {next} - next item is '.$newItemId.'(current: '.$currentItem.')', 0);
}
break;
case 'previous':
$myLP->set_current_item($currentItem);
$myLP->previous();
$newItemId = $myLP->get_current_item_id();
if ($debug > 1) {
error_log('In {previous} - next item is '.$newItemId.'(current: '.$currentItem.')', 0);
}
break;
case 'first':
$myLP->set_current_item($currentItem);
$myLP->first();
$newItemId = $myLP->get_current_item_id();
if ($debug > 1) {
error_log('In {first} - next item is '.$newItemId.'(current: '.$currentItem.')', 0);
}
break;
case 'last':
break;
default:
// Should be filtered to check it's not hacked
if ($nextItem == $currentItem) {
// If we're opening the same item again.
$myLP->items[$currentItem]->restart();
} else {
$oldItemId = $currentItem;
}
$newItemId = $nextItem;
$myLP->set_current_item($newItemId);
if ($debug > 1) {
error_log('In {default} - next item is '.$newItemId.'(current: '.$currentItem.')', 0);
}
break;
}
$myLP->start_current_item(true);
if ($myLP->force_commit && $saveStatus) {
$myLP->save_current();
}
if (is_object($myLP->items[$newItemId])) {
$myLPI = $myLP->items[$newItemId];
} else {
if ($debug > 1) {
error_log('In switch_item_details - generating new item object', 0);
}
$myLPI = new learnpathItem($newItemId, $userId);
$myLPI->set_lp_view($viewId);
}
/*
* now get what's needed by the SCORM API:
* -score
* -max
* -min
* -lesson_status
* -session_time
* -suspend_data
*/
$lessonStatus = $myLPI->get_status();
$interactionsCount = $myLPI->get_interactions_count();
/**
* Interactions are not returned by switch_item at the moment, but please
* leave commented code to allow for the addition of these in the future.
*/
/*
$interactionsString = '';
for ($i = 0; $i < $interactionsCount; $i++) {
$interactionsString .= ",[".$i.",'','','','','','','']";
}
if (!empty($interactionsString)) {
$interactionsString = substr($interactionsString, 1);
}
*/
$totalItems = $myLP->getTotalItemsCountWithoutDirs();
$completedItems = $myLP->get_complete_items_count();
$progressMode = $myLP->get_progress_bar_mode();
$progressMode = ('' == $progressMode ? '%' : $progressMode);
$nextItemId = $myLP->get_next_item_id();
$previousItemId = $myLP->get_previous_item_id();
$itemType = $myLPI->get_type();
$lessonMode = $myLPI->get_lesson_mode();
$credit = $myLPI->get_credit();
$launchData = $myLPI->get_launch_data();
$objectivesCount = $myLPI->get_objectives_count();
$coreExit = $myLPI->get_core_exit();
$return .=
"olms.lms_lp_id=".$lpId.";".
"olms.lms_item_id=".$newItemId.";".
"olms.lms_old_item_id=".$oldItemId.";".
"olms.lms_initialized=0;".
"olms.lms_view_id=".$viewId.";".
"olms.lms_user_id=".$userId.";".
"olms.next_item=".$newItemId.";".// This one is very important to replace possible literal strings.
"olms.lms_next_item=".$nextItemId.";".
"olms.lms_previous_item=".$previousItemId.";".
"olms.lms_item_type = '".$itemType."';".
"olms.lms_item_credit = '".$credit."';".
"olms.lms_item_lesson_mode = '".$lessonMode."';".
"olms.lms_item_launch_data = '".$launchData."';".
"olms.lms_item_interactions_count = '".$interactionsCount."';".
"olms.lms_item_objectives_count = '".$objectivesCount."';".
"olms.lms_item_core_exit = '".$coreExit."';".
"olms.asset_timer = 0;";
$return .= "update_toc('unhighlight','".$currentItem."');".
"update_toc('highlight','".$newItemId."');".
"update_toc('$lessonStatus','".$newItemId."');";
$progressBarSpecial = false;
$scoreAsProgressSetting = api_get_configuration_value('lp_score_as_progress_enable');
if (true === $scoreAsProgressSetting) {
$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.
if ($debug > 1) {
error_log('prerequisites_match() returned '.htmlentities($myLP->error), 0);
}
Session::write('scorm_item_id', $newItemId);
Session::write('lpobject', serialize($myLP));
Session::write('oLP', $myLP);
return $return;
}
echo switch_item_toc(
$_POST['lid'],
$_POST['uid'],
$_POST['vid'],
$_POST['iid'],
$_POST['next']
);

125
main/lp/lp_build.php Normal file
View File

@@ -0,0 +1,125 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
/* Constants and variables */
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$isStudentView = (int) $_REQUEST['isStudentView'];
$learnpath_id = (int) $_REQUEST['lp_id'];
$submit = $_POST['submit_button'];
/* MAIN CODE */
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
exit;
}
/* The learnpath has been just created, go get the last id. */
$is_new = false;
if ($learnpath_id == 0) {
$is_new = true;
}
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = ['url' => 'lp_controller.php?action=list&'.api_get_cidreq(), 'name' => get_lang('LearningPaths')];
$interbreadcrumb[] = ['url' => '#', 'name' => $learnPath->getNameNoTags()];
// Theme calls.
$lp_theme_css = $learnPath->get_theme();
$show_learn_path = true;
Display::display_header('', 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
/* <![CDATA[ */
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
</script>
<?php
/* DISPLAY SECTION */
echo $learnPath->build_action_menu();
echo '<div class="row">';
echo '<div class="col-md-4">';
// Build the tree with the menu items in it.
echo $learnPath->return_new_tree();
echo '</div>';
echo '<div class="col-md-8">';
if (isset($is_success) && $is_success === true) {
echo Display::return_message(get_lang('ItemRemoved'), 'confirmation');
} else {
if ($is_new) {
echo Display::return_message(get_lang('LearnpathAdded'), 'normal', false);
}
echo Display::page_subheader(get_lang('LearnPathAddedTitle'));
echo '<ul id="lp_overview" class="thumbnails">';
echo show_block(
'lp_controller.php?'.api_get_cidreq().'&action=add_item&type=step&lp_id='.$learnPath->get_id(),
get_lang("NewStep"),
get_lang('NewStepComment'),
'tools.png'
);
echo show_block(
'lp_controller.php?'.api_get_cidreq().'&action=view&lp_id='.$learnPath->get_id(),
get_lang("Display"),
get_lang('DisplayComment'),
'view.png'
);
echo '</ul>';
}
echo '</div>';
echo '</div>';
function show_block($link, $title, $subtitle, $icon)
{
$html = '<li class="col-md-4">';
$html .= '<div class="thumbnail">';
$html .= '<a href="'.$link.'" title="'.$title.'">';
$html .= Display::return_icon($icon, $title, [], ICON_SIZE_BIG);
$html .= '</a>';
$html .= '<div class="caption">';
$html .= '<strong>'.$title.'</strong></a> '.$subtitle;
$html .= '</div>';
$html .= '</div>';
$html .= '</li>';
return $html;
}
Display::display_footer();

133
main/lp/lp_content.php Normal file
View File

@@ -0,0 +1,133 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Script that displays an error message when no content could be loaded.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
require_once __DIR__.'/../inc/global.inc.php';
$debug = 0;
if ($debug > 0) {
error_log('New lp - In lp_content.php');
}
if (empty($lp_controller_touched)) {
if ($debug > 0) {
error_log('New lp - In lp_content.php - Redirecting to lp_controller');
}
header('Location: lp_controller.php?action=content&lp_id='.intval($_REQUEST['lp_id']).'&item_id='.intval($_REQUEST['item_id']).'&'.api_get_cidreq());
exit;
}
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$learnPath->error = '';
$lpType = $learnPath->get_type();
$lpItemId = $learnPath->get_current_item_id();
/**
* Get a link to the corresponding document.
*/
$src = '';
if ($debug > 0) {
error_log('New lp - In lp_content.php - Looking for file url');
error_log("lp_type $lpType");
error_log("lp_item_id $lpItemId");
}
$list = $learnPath->get_toc();
$dir = false;
foreach ($list as $toc) {
if ($toc['id'] == $lpItemId && $toc['type'] === 'dir') {
$dir = true;
}
}
if ($dir) {
$src = 'blank.php';
} else {
switch ($lpType) {
case 1:
$learnPath->stop_previous_item();
$prerequisiteCheck = $learnPath->prerequisites_match($lpItemId);
if ($prerequisiteCheck === true) {
$src = $learnPath->get_link('http', $lpItemId);
if (empty($src)) {
$src = 'blank.php?'.api_get_cidreq().'&error=document_protected';
break;
}
$learnPath->start_current_item(); // starts time counter manually if asset
$src = $learnPath->fixBlockedLinks($src);
if (WhispeakAuthPlugin::isLpItemMarked($lpItemId)) {
ChamiloSession::write(
WhispeakAuthPlugin::SESSION_LP_ITEM,
['lp' => $learnPath->lp_id, 'lp_item' => $lpItemId, 'src' => $src]
);
$src = api_get_path(WEB_PLUGIN_PATH).'whispeakauth/authentify.php';
}
break;
}
$src = 'blank.php?'.api_get_cidreq().'&error=prerequisites&prerequisite_message='.Security::remove_XSS($learnPath->error);
break;
case 2:
$learnPath->stop_previous_item();
$prerequisiteCheck = $learnPath->prerequisites_match($lpItemId);
if ($prerequisiteCheck === true) {
$src = $learnPath->get_link('http', $lpItemId);
$learnPath->start_current_item(); // starts time counter manually if asset
} else {
$src = 'blank.php?'.api_get_cidreq().'&error=prerequisites&prerequisite_message='.Security::remove_XSS($learnPath->error);
}
break;
case 3:
// save old if asset
$learnPath->stop_previous_item(); // save status manually if asset
$prerequisiteCheck = $learnPath->prerequisites_match($lpItemId);
if ($prerequisiteCheck === true) {
$src = $learnPath->get_link('http', $lpItemId);
$learnPath->start_current_item(); // starts time counter manually if asset
} else {
$src = 'blank.php';
}
break;
case 4:
break;
}
}
if ($debug > 0) {
error_log('New lp - In lp_content.php - File url is '.$src);
}
$learnPath->set_previous_item($lpItemId);
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
// Define the 'doc.inc.php' as language file.
$nameTools = $learnPath->getNameNoTags();
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'lp/lp_list.php?'.api_get_cidreq(),
'name' => get_lang('Doc'),
];
// Update global setting to avoid displaying right menu.
$save_setting = api_get_setting('show_navigation_menu');
global $_setting;
$_setting['show_navigation_menu'] = false;
if ($debug > 0) {
error_log('New LP - In lp_content.php - Loading '.$src);
}
Session::write('oLP', $learnPath);
header('Location: '.urldecode($src));
exit;

1750
main/lp/lp_controller.php Normal file

File diff suppressed because it is too large Load Diff

364
main/lp/lp_edit.php Normal file
View File

@@ -0,0 +1,364 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Script allowing simple edition of learnpath information (title, description, etc).
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
api_protect_course_script();
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$nameTools = get_lang('Doc');
$this_section = SECTION_COURSES;
Event::event_access_tool(TOOL_LEARNPATH);
$lpId = $learnPath->get_id();
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=".$lpId.'&'.api_get_cidreq(),
'name' => $learnPath->getNameNoTags(),
];
$htmlHeadXtra[] = '<script>
function activate_start_date() {
if(document.getElementById(\'start_date_div\').style.display == \'none\') {
document.getElementById(\'start_date_div\').style.display = \'block\';
} else {
document.getElementById(\'start_date_div\').style.display = \'none\';
}
}
function activate_end_date() {
if(document.getElementById(\'end_date_div\').style.display == \'none\') {
document.getElementById(\'end_date_div\').style.display = \'block\';
} else {
document.getElementById(\'end_date_div\').style.display = \'none\';
}
}
</script>';
$defaults = [];
$form = new FormValidator(
'form1',
'post',
'lp_controller.php?'.api_get_cidreq()
);
// Form title
$form->addElement('header', get_lang('Edit'));
// Title
if (api_get_configuration_value('save_titles_as_html')) {
$form->addHtmlEditor(
'lp_name',
get_lang('LPName'),
true,
false,
['ToolbarSet' => 'TitleAsHtml']
);
} else {
$form->addElement('text', 'lp_name', api_ucfirst(get_lang('LearnpathTitle')), ['size' => 43]);
}
$form->applyFilter('lp_name', 'html_filter');
$form->addRule('lp_name', get_lang('ThisFieldIsRequired'), 'required');
$form->addElement('hidden', 'lp_encoding');
$items = learnpath::getCategoryFromCourseIntoSelect(api_get_course_int_id(), true);
$form->addElement('select', 'category_id', get_lang('Category'), $items);
// Hide toc frame
$form->addElement(
'checkbox',
'hide_toc_frame',
null,
get_lang('HideTocFrame')
);
if (api_get_setting('allow_course_theme') === 'true') {
$mycourselptheme = api_get_course_setting('allow_learning_path_theme');
if (!empty($mycourselptheme) && $mycourselptheme != -1 && $mycourselptheme == 1) {
//LP theme picker
$theme_select = $form->addElement('SelectTheme', 'lp_theme', get_lang('Theme'));
$form->applyFilter('lp_theme', 'trim');
$s_theme = $learnPath->get_theme();
$theme_select->setSelected($s_theme); //default
}
}
// Author
$form->addHtmlEditor(
'lp_author',
get_lang('Author'),
false,
false,
['ToolbarSet' => 'LearningPathAuthor', 'Width' => '100%', 'Height' => '200px']
);
$form->applyFilter('lp_author', 'html_filter');
// LP image
if (strlen($learnPath->get_preview_image()) > 0) {
$show_preview_image = '<img src='.api_get_path(WEB_COURSE_PATH).api_get_course_path()
.'/upload/learning_path/images/'.$learnPath->get_preview_image().'>';
$form->addElement('label', get_lang('ImagePreview'), $show_preview_image);
$form->addElement('checkbox', 'remove_picture', null, get_lang('DelImage'));
}
$label = $learnPath->get_preview_image() != '' ? get_lang('UpdateImage') : get_lang('AddImage');
$form->addElement('file', 'lp_preview_image', [$label, get_lang('ImageWillResizeMsg')]);
$form->addRule('lp_preview_image', get_lang('OnlyImagesAllowed'), 'filetype', ['jpg', 'jpeg', 'png', 'gif']);
// Search terms (only if search is activated).
if (api_get_setting('search_enabled') === 'true') {
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
$form->addElement('text', $specific_field['code'], $specific_field['name']);
$filter = [
'c_id' => "'".api_get_course_int_id()."'",
'field_id' => $specific_field['id'],
'ref_id' => $learnPath->lp_id,
'tool_id' => '\''.TOOL_LEARNPATH.'\'',
];
$values = get_specific_field_values_list($filter, ['value']);
if (!empty($values)) {
$arr_str_values = [];
foreach ($values as $value) {
$arr_str_values[] = $value['value'];
}
$defaults[$specific_field['code']] = implode(', ', $arr_str_values);
}
}
}
$hideTableOfContents = $learnPath->getHideTableOfContents();
$defaults['lp_encoding'] = Security::remove_XSS($learnPath->encoding);
$defaults['lp_name'] = Security::remove_XSS($learnPath->get_name());
$defaults['lp_author'] = Security::remove_XSS($learnPath->get_author());
$defaults['hide_toc_frame'] = $hideTableOfContents;
$defaults['category_id'] = $learnPath->getCategoryId();
$defaults['accumulate_scorm_time'] = $learnPath->getAccumulateScormTime();
$expired_on = $learnPath->expired_on;
$publicated_on = $learnPath->publicated_on;
// Prerequisites
$form->addElement('html', '<div class="form-group">');
$items = $learnPath->display_lp_prerequisites_list();
$form->addElement('html', '<label class="col-md-2">'.get_lang('LearnpathPrerequisites').'</label>');
$form->addElement('html', '<div class="col-md-8">');
$form->addElement('html', $items);
$form->addElement('html', '<div class="help-block">'.get_lang('LpPrerequisiteDescription').'</div>');
$form->addElement('html', '</div>');
$form->addElement('html', '<div class="col-md-2"></div>');
$form->addElement('html', '</div>');
// Time Control
if (Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
$accumulateTime = $_SESSION['oLP']->getAccumulateWorkTime();
$form->addText('accumulate_work_time', [get_lang('LpMinTime'), get_lang('LpMinTimeDescription')]);
$defaults['accumulate_work_time'] = $accumulateTime;
}
// Start date
$form->addElement(
'checkbox',
'activate_start_date_check',
null,
get_lang('EnableStartTime'),
['onclick' => 'activate_start_date()']
);
$display_date = 'none';
if (!empty($publicated_on) && $publicated_on !== '0000-00-00 00:00:00') {
$display_date = 'block';
$defaults['activate_start_date_check'] = 1;
}
$form->addElement('html', '<div id="start_date_div" style="display:'.$display_date.';">');
$form->addDateTimePicker('publicated_on', get_lang('PublicationDate'));
$form->addElement('html', '</div>');
//End date
$form->addElement(
'checkbox',
'activate_end_date_check',
null,
get_lang('EnableEndTime'),
['onclick' => 'activate_end_date()']
);
$display_date = 'none';
if (!empty($expired_on)) {
$display_date = 'block';
$defaults['activate_end_date_check'] = 1;
}
$form->addElement('html', '<div id="end_date_div" style="display:'.$display_date.';">');
$form->addDateTimePicker('expired_on', get_lang('ExpirationDate'));
$form->addElement('html', '</div>');
if (api_is_platform_admin()) {
$form->addElement('checkbox', 'use_max_score', null, get_lang('UseMaxScore100'));
$defaults['use_max_score'] = $learnPath->use_max_score;
}
$subscriptionSettings = learnpath::getSubscriptionSettings();
if ($subscriptionSettings['allow_add_users_to_lp']) {
$form->addElement(
'checkbox',
'subscribe_users',
null,
get_lang('SubscribeUsersToLp')
);
}
// accumulate_scorm_time
$form->addElement(
'checkbox',
'accumulate_scorm_time',
[null, get_lang('AccumulateScormTimeInfo')],
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)) {
$form->addSelect(
'extra_lp_icon',
get_lang('Icon'),
$options
);
$defaults['extra_lp_icon'] = learnpath::getSelectedIcon($lpId);
}
$extraField = new ExtraField('lp');
$extra = $extraField->addElements(
$form,
$lpId,
['lp_icon', 'use_score_as_progress']
);
if ($form->hasElement('extra_authors')) {
/** @var HTML_QuickForm_select $author */
$author = $form->getElement('extra_authors');
$conditions = [
'enabled' => 1,
'status' => COURSEMANAGER,
];
$teachers = UserManager::get_user_list($conditions);
$options = [];
foreach ($teachers as $teacher) {
$options[$teacher['id']] = $teacher['complete_name'];
}
$author->setOptions($options);
}
Skill::addSkillsToForm($form, api_get_course_int_id(), api_get_session_id(), ITEM_TYPE_LEARNPATH, $lpId);
// select the next lp
if (true === api_get_configuration_value('lp_enable_flow')) {
$nextLpsOptions = learnpath::getNextLpsAvailable(api_get_course_int_id(), $lpId);
$nextLpId = learnpath::getFlowNextLpId($lpId, api_get_course_int_id());
if (!empty($nextLpId)) {
$nextLpsOptions[$nextLpId] = learnPath::getLpNameById($nextLpId);
}
if (!empty($nextLpsOptions)) {
$form->addSelect(
'next_lp_id',
get_lang('SelectTheNextLp'),
$nextLpsOptions
);
$defaults['next_lp_id'] = $nextLpId;
}
}
// Submit button
$form->addButtonSave(get_lang('SaveLPSettings'));
// Hidden fields
$form->addElement('hidden', 'action', 'update_lp');
$form->addElement('hidden', 'lp_id', $lpId);
$htmlHeadXtra[] = '<script>
$(function() {
'.$extra['jquery_ready_content'].'
});
</script>';
$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array().'</script>';
$defaults['publicated_on'] = !empty($publicated_on) && $publicated_on !== '0000-00-00 00:00:00'
? api_get_local_time($publicated_on)
: null;
$defaults['expired_on'] = (!empty($expired_on))
? api_get_local_time($expired_on)
: date('Y-m-d 12:00:00', time() + 84600);
$defaults['subscribe_users'] = $learnPath->getSubscribeUsers();
$display = api_get_configuration_value('lp_view_settings')['display'] ?? [];
if (!empty($display)) {
$addExtraQuitToHomeIcon = $display['add_extra_quit_to_home_icon'] ?? false;
$value = (new ExtraFieldValue('lp'))->get_values_by_handler_and_field_variable($lpId, 'add_extra_quit_button');
if (!is_array($value) && $addExtraQuitToHomeIcon) {
$defaults['extra_add_extra_quit_button[extra_add_extra_quit_button]'] = true;
}
}
$form->setDefaults($defaults);
Display::display_header(get_lang('CourseSettings'), 'Path');
echo $learnPath->build_action_menu(false, false, true, false);
echo '<div class="row">';
echo '<div class="'.($hideTableOfContents ? 'col-md-12' : 'col-md-8').'" id="pnl-frm">';
$form->display();
echo '</div>';
echo '<div class="'.($hideTableOfContents ? 'hide' : 'col-md-4').' text-right" id="pnl-toc">';
echo Display::return_icon('course_setting_layout.png');
echo '</div>';
echo '</div>';
echo "
<script>
$(function() {
$('[name=\'hide_toc_frame\']').on('change', function() {
$('#pnl-frm').toggleClass('col-md-8').toggleClass('col-sm-12');
$('#pnl-toc').toggleClass('col-md-4').toggleClass('hide');
});
});
</script>
";
Display::display_footer();

196
main/lp/lp_edit_item.php Normal file
View File

@@ -0,0 +1,196 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
* @author Julio Montoya - Improving the list of templates
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
/* Header and action code */
$htmlHeadXtra[] = '<script>'.$learnPath->get_js_dropdown_array().'
$(function() {
CKEDITOR.on("instanceReady", function (e) {
showTemplates("content_lp");
});
});
</script>';
/* Constants and variables */
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$tbl_lp = Database::get_course_table(TABLE_LP_MAIN);
$isStudentView = isset($_REQUEST['isStudentView']) ? intval($_REQUEST['isStudentView']) : null;
$learnpath_id = (int) $_REQUEST['lp_id'];
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
exit;
}
// From here on, we are admin because of the previous condition, so don't check anymore.
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
$course_id = api_get_course_int_id();
/*
Course admin section
- all the functions not available for students - always available in this case (page only shown to admin)
*/
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => $learnPath->getNameNoTags(),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
// Theme calls.
$show_learn_path = true;
$lp_theme_css = $learnPath->get_theme();
Display::display_header(get_lang('Edit'), 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
$(function() {
jQuery('.scrollbar-inner').scrollbar();
expandColumnToogle('#hide_bar_template', {
selector: '#lp_sidebar'
}, {
selector: '#doc_form'
});
$('.lp-btn-associate-forum').on('click', function (e) {
var associate = confirm('<?php echo get_lang('ConfirmAssociateForumToLPItem'); ?>');
if (!associate) {
e.preventDefault();
}
});
$('.lp-btn-dissociate-forum').on('click', function (e) {
var dissociate = confirm('<?php echo get_lang('ConfirmDissociateForumToLPItem'); ?>');
if (!dissociate) {
e.preventDefault();
}
});
});
</script>
<?php
$extraField = [];
$field = new ExtraField('lp_item');
$authorLpField = $field->get_handler_field_info_by_field_variable('authorlpitem');
if ($authorLpField != null) {
$extraField['authorlp'] = $authorLpField;
}
echo $learnPath->build_action_menu(
false,
true,
false,
true,
'',
$extraField
);
echo '<div class="row">';
echo '<div id="lp_sidebar" class="col-md-4">';
$documentId = isset($_GET['path_item']) ? (int) $_GET['path_item'] : 0;
$documentInfo = DocumentManager::get_document_data_by_id($documentId, api_get_course_id(), false, null, true);
if (empty($documentInfo)) {
// Try with iid
$table = Database::get_course_table(TABLE_DOCUMENT);
$sql = "SELECT path FROM $table
WHERE c_id = $course_id AND iid = $documentId AND path NOT LIKE '%_DELETED_%'";
$res_doc = Database::query($sql);
$path_file = Database::result($res_doc, 0, 0);
} else {
$path_file = $documentInfo['path'];
}
$path_parts = pathinfo($path_file);
if (!empty($path_file) && isset($path_parts['extension']) && $path_parts['extension'] === 'html') {
echo $learnPath->return_new_tree();
// Show the template list
echo '<div id="frmModel" class="scrollbar-inner lp-add-item"></div>';
} else {
echo $learnPath->return_new_tree();
}
echo '</div>';
echo '<div id="doc_form" class="col-md-8">';
$excludeExtraFields = [
'authors',
'authorlp',
'authorlpitem',
'price',
];
if (api_is_platform_admin()) {
// Only admins can edit this items
$excludeExtraFields = [];
}
if (isset($is_success) && $is_success === true) {
$msg = '<div class="lp_message" style="margin-bottom:10px;">';
$msg .= 'The item has been edited.';
$msg .= '</div>';
echo $learnPath->display_item($_GET['id'], $msg);
} else {
$item = $learnPath->getItem($_GET['id']);
if ('document' !== $item->get_type()) {
$excludeExtraFields[] = 'no_automatic_validation';
}
echo $learnPath->display_edit_item($item->getIid(), $excludeExtraFields);
$finalItem = Session::read('finalItem');
if ($finalItem) {
echo '<script>$("#frmModel").remove()</script>';
}
Session::erase('finalItem');
}
echo '</div>';
echo '</div>';
Display::display_footer();

View File

@@ -0,0 +1,95 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = isset($_REQUEST['isStudentView']) ? (int) $_REQUEST['isStudentView'] : null;
$learnpath_id = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : null;
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
if (!$is_allowed_to_edit || $isStudentView) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
exit;
}
// Theme calls.
$show_learn_path = true;
/** @var learnpath $lp */
$lp = Session::read('oLP');
$lp_theme_css = $lp->get_theme();
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list',
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id",
'name' => $lp->getNameNoTags(),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
Display::display_header(get_lang('LearnpathPrerequisites'), 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
</script>
<?php
echo $lp->build_action_menu();
echo '<div class="row">';
echo '<div class="col-md-3">';
echo $lp->return_new_tree();
echo '</div>';
echo '<div class="col-md-9">';
echo '<div class="prerequisites">';
$lpItem = new learnpathItem($_GET['id']);
if (isset($is_success) && $is_success == true) {
echo $lp->display_manipulate($_GET['id'], $lpItem->get_type());
echo Display::return_message(get_lang('PrerequisitesAdded'));
} else {
echo $lp->display_manipulate($_GET['id'], $lpItem->get_type());
echo $lp->display_item_prerequisites_form($_GET['id']);
}
echo '</div>';
echo '</div>';
Display::display_footer();

292
main/lp/lp_final_item.php Normal file
View File

@@ -0,0 +1,292 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Print a learning path finish page with details.
*
* @author Jose Loguercio <jose.loguercio@beeznest.com>
*
* @package chamilo.learnpath
*/
$_in_course = true;
require_once __DIR__.'/../inc/global.inc.php';
$current_course_tool = TOOL_GRADEBOOK;
// Make sure no anonymous user gets here without permission
api_protect_course_script(true);
// Get environment variables
$courseCode = api_get_course_id();
$courseId = api_get_course_int_id();
$userId = api_get_user_id();
$sessionId = api_get_session_id();
$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
$lpId = isset($_GET['lp_id']) ? intval($_GET['lp_id']) : 0;
// This page can only be shown from inside a learning path
if (!$id && !$lpId) {
Display::return_message(get_lang('FileNotFound'), 'warning');
exit;
}
// Certificate and Skills Premium with Service check
$plugin = BuyCoursesPlugin::create();
$checker = $plugin->isEnabled() && $plugin->get('include_services');
if ($checker) {
$userServiceSale = $plugin->getServiceSales(
$userId,
BuyCoursesPlugin::SERVICE_STATUS_COMPLETED,
BuyCoursesPlugin::SERVICE_TYPE_LP_FINAL_ITEM,
$lpId
);
if (empty($userServiceSale)) {
// Instance a new template : No page tittle, No header, No footer
$tpl = new Template(null, false, false);
$url = api_get_path(WEB_PLUGIN_PATH).'buycourses/src/service_catalog.php';
$content = sprintf(
Display::return_message(
get_lang('IfYouWantToGetTheCertificateAndOrSkillsAsociatedToThisCourseYouNeedToBuyTheCertificateServiceYouCanGoToServiceCatalogClickingHere'),
'normal',
false
),
'<a href="'.$url.'">'.$url.'</a>'
);
$tpl->assign('content', $content);
$tpl->display_blank_template();
exit;
}
}
// Initialize variables required for the template
$downloadCertificateLink = '';
$viewCertificateLink = '';
$badgeLink = '';
$finalItemTemplate = '';
// Check prerequisites and total completion of the learning path
$lp = new Learnpath($courseCode, $lpId, $userId);
$count = $lp->getTotalItemsCountWithoutDirs();
$excludeFailedStatus = !(true === api_get_configuration_value('lp_prerequisit_on_quiz_unblock_if_max_attempt_reached'));
$completed = $lp->get_complete_items_count($excludeFailedStatus);
$currentItemId = $lp->get_current_item_id();
$currentItem = $lp->items[$currentItemId];
$currentItemStatus = $currentItem->get_status();
$accessGranted = false;
if (($count - $completed == 0) ||
($count - $completed == 1 && ($currentItemStatus == 'incomplete') || ($currentItemStatus == 'not attempted'))
) {
if ($lp->prerequisites_match($currentItemId)) {
$accessGranted = true;
}
}
// Update the progress in DB from the items completed
$lp->save_last();
// unset the (heavy) lp object to free memory - we don't need it anymore
unset($lp);
unset($currentItem);
// If for some reason we consider the requirements haven't been completed yet,
// show a prerequisites warning
if ($accessGranted == false) {
echo Display::return_message(
get_lang('LearnpathPrereqNotCompleted'),
'warning'
);
$finalItemTemplate = '';
} else {
$catLoad = Category::load(
null,
null,
$courseCode,
null,
null,
$sessionId,
'ORDER By id'
);
// If not gradebook has been defined
if (empty($catLoad)) {
$finalItemTemplate = generateLPFinalItemTemplate(
$id,
$courseCode,
$sessionId,
$downloadCertificateLink,
$badgeLink
);
} else {
// A gradebook was found, proceed...
/** @var Category $category */
$category = $catLoad[0];
$categoryId = $category->get_id();
$link = LinkFactory::load(
null,
null,
$lpId,
null,
$courseCode,
$categoryId
);
if ($link) {
$cat = new Category();
$catCourseCode = CourseManager::get_course_by_category($categoryId);
$show_message = $cat->show_message_resource_delete($catCourseCode);
if (false === $show_message && !api_is_allowed_to_edit() && !api_is_excluded_user_type()) {
$certificate = Category::generateUserCertificate(
$categoryId,
$userId
);
if (!empty($certificate['pdf_url']) ||
!empty($certificate['badge_link'])
) {
if (is_array($certificate)) {
$downloadCertificateLink = Category::getDownloadCertificateBlock($certificate);
}
if (is_array($certificate) &&
isset($certificate['badge_link'])
) {
$courseId = api_get_course_int_id();
$badgeLink = generateLPFinalItemTemplateBadgeLinks(
$userId,
$courseId,
$sessionId
);
}
}
$currentScore = Category::getCurrentScore(
$userId,
$category,
true
);
Category::registerCurrentScore(
$currentScore,
$userId,
$categoryId
);
}
}
$finalItemTemplate = generateLPFinalItemTemplate(
$id,
$courseCode,
$sessionId,
$downloadCertificateLink,
$badgeLink
);
if (!$finalItemTemplate) {
echo Display::return_message(get_lang('FileNotFound'), 'warning');
}
}
}
// Instance a new template : No page tittle, No header, No footer
$tpl = new Template(null, false, false);
$tpl->assign('content', $finalItemTemplate);
$tpl->display_blank_template();
// A few functions used only here...
/**
* Return a HTML string to show as final document in learning path.
*
* @param int $lpItemId
* @param string $courseCode
* @param int $sessionId
* @param string $downloadCertificateLink
* @param string $badgeLink
*
* @return mixed|string
*/
function generateLPFinalItemTemplate(
$lpItemId,
$courseCode,
$sessionId = 0,
$downloadCertificateLink = '',
$badgeLink = ''
) {
$documentInfo = DocumentManager::get_document_data_by_id(
$lpItemId,
$courseCode,
true,
$sessionId
);
$finalItemTemplate = file_get_contents($documentInfo['absolute_path']);
$finalItemTemplate = str_replace('((certificate))', $downloadCertificateLink, $finalItemTemplate);
$finalItemTemplate = str_replace('((skill))', $badgeLink, $finalItemTemplate);
return $finalItemTemplate;
}
/**
* Return HTML string with badges list.
*
* @param int $userId
* @param int $courseId
* @param int $sessionId
*
* @return string HTML string for badges
*/
function generateLPFinalItemTemplateBadgeLinks($userId, $courseId, $sessionId = 0)
{
$em = Database::getManager();
$skillRelUser = new SkillRelUser();
$userSkills = $skillRelUser->getUserSkills($userId, $courseId, $sessionId);
$skillList = '';
$badgeLink = '';
if ($userSkills) {
foreach ($userSkills as $userSkill) {
$skill = $em->find('ChamiloCoreBundle:Skill', $userSkill['skill_id']);
if (!$skill) {
continue;
}
$skillList .= "
<div class='row'>
<div class='col-md-2 col-xs-4'>
<div class='thumbnail'>
<img class='skill-badge-img' src='".Skill::getWebIconPath($skill)."' >
</div>
</div>
<div class='col-md-8 col-xs-8'>
<h5><b>".$skill->getName()."</b></h5>
".$skill->getDescription()."
</div>
<div class='col-md-2 col-xs-12'>
<h5><b>".get_lang('ShareWithYourFriends')."</b></h5>
<a href='http://www.facebook.com/sharer.php?u=".api_get_path(WEB_PATH)."badge/".$skill->getId()."/user/".$userId."' target='_new'>
<em class='fa fa-facebook-square fa-3x text-info' aria-hidden='true'></em>
</a>
<a
href='https://twitter.com/home?status=".sprintf(get_lang('IHaveObtainedSkillXOnY'), '"'.$skill->getName().'"', api_get_setting('siteName')).' - '.api_get_path(WEB_PATH).'badge/'.$skill->getId().'/user/'.$userId."' target='_new'>
<em class='fa fa-twitter-square fa-3x text-light' aria-hidden='true'></em>
</a>
</div>
</div>
";
}
if (!empty($skillList)) {
$badgeLink .= "
<div class='panel panel-default'>
<div class='panel-body'>
<h3 class='text-center'>".get_lang('AdditionallyYouHaveObtainedTheFollowingSkills')."</h3>
$skillList
</div>
</div>
";
}
}
return $badgeLink;
}

99
main/lp/lp_impress.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* @package chamilo.learnpath
*/
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_COURSES;
//To prevent the template class
$show_learnpath = true;
api_protect_course_script();
$lp_id = intval($_GET['lp_id']);
// Check if the learning path is visible for student - (LP requisites)
if (!api_is_allowed_to_edit(null, true) &&
!learnpath::is_lp_visible_for_student($lp_id, api_get_user_id(), api_get_course_info())
) {
api_not_allowed();
}
//Checking visibility (eye icon)
$visibility = api_get_item_visibility(
api_get_course_info(),
TOOL_LEARNPATH,
$lp_id,
api_get_session_id(),
api_get_user_id(),
null,
api_get_group_id()
);
if (!api_is_allowed_to_edit(null, true) && intval($visibility) == 0) {
api_not_allowed();
}
/** @var learnpath $lp */
$lp = Session::read('oLP');
if (!$lp) {
api_not_allowed(true);
}
$debug = 0;
$course_code = api_get_course_id();
$course_id = api_get_course_int_id();
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/impress/impress-demo.css');
$list = $lp->get_toc();
$content = '';
$is_allowed_to_edit = api_is_allowed_to_edit(null, true, false, false);
if ($is_allowed_to_edit) {
$content .= '<div style="position: fixed; top: 0px; left: 0px; pointer-events: auto;width:100%">';
global $interbreadcrumb;
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&isStudentView=false&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=".$lp->lp_id."&isStudentView=false&".api_get_cidreq(),
'name' => $lp->getNameNoTags(),
];
$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Preview')];
$content .= return_breadcrumb($interbreadcrumb, null, null);
$content .= '</div>';
}
$html = '';
$step = 1;
foreach ($list as $toc) {
$stepId = "$step-".api_replace_dangerous_char($toc['title']);
$x = 1000 * $step;
$html .= '<div id="'.strtolower($stepId).'" title="'.$toc['title'].'" class="step slide" data-x="'.$x.'" data-y="-1500" >';
$html .= '<div class="impress-content">';
$src = $lp->get_link('http', $toc['id']);
if ($toc['type'] !== 'dir') {
//just showing the src in a iframe ...
$html .= '<h2>'.$toc['title'].'</h2>';
$html .= '<iframe border="0" frameborder="0" src="'.$src.'"></iframe>';
} else {
$html .= "<div class='impress-title'>";
$html .= '<h1>'.$toc['title'].'</h1>';
$html .= "</div>";
}
$html .= "</div>";
$html .= "</div>";
$step++;
}
//Setting the template
$tool_name = get_lang('ViewModeImpress');
$tpl = new Template($tool_name, false, false, true);
$tpl->assign('html', $html);
$templateName = $tpl->get_template('learnpath/impress.tpl');
$content .= $tpl->fetch($templateName);
$tpl->assign('content', $content);
$tpl->display_no_layout_template();

1056
main/lp/lp_list.php Normal file

File diff suppressed because it is too large Load Diff

202
main/lp/lp_list_search.php Normal file
View File

@@ -0,0 +1,202 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Script to draw the results from a query.
*
* @author Diego Escalante Urrelo <diegoe@gmail.com>
* @author Marco Antonio Villegas Vega <marvil07@gmail.com>
* @author Julio Montoya <gugli100@gmail.com> bug fixing
*/
require api_get_path(LIBRARY_PATH).'search/search_widget.php';
require api_get_path(LIBRARY_PATH).'search/ChamiloQuery.php';
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
Event::event_access_tool(TOOL_SEARCH);
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = ['url' => './index.php', 'name' => get_lang(ucfirst(TOOL_SEARCH))];
search_widget_prepare($htmlHeadXtra);
Display::display_header(null, 'Path');
if (api_get_setting('search_enabled') !== 'true') {
echo Display::return_message(get_lang('SearchFeatureNotEnabledComment'), 'error');
} else {
if (!empty($_GET['action'])) {
search_widget_show($_GET['action']);
} else {
search_widget_show();
}
}
// Initialize.
$op = 'or';
if (!empty($_REQUEST['operator']) && in_array($op, ['or', 'and'])) {
$op = $_REQUEST['operator'];
}
$query = null;
if (isset($_REQUEST['query'])) {
$query = stripslashes(htmlspecialchars_decode($_REQUEST['query'], ENT_QUOTES));
}
$mode = 'default';
if (isset($_GET['mode']) && in_array($_GET['mode'], ['gallery', 'default'])) {
$mode = $_GET['mode'];
}
$term_array = [];
$specific_fields = get_specific_field_list();
foreach ($specific_fields as $specific_field) {
if (!empty($_REQUEST['sf_'.$specific_field['code']])) {
$values = $_REQUEST['sf_'.$specific_field['code']];
if (in_array('__all__', $values)) {
$sf_terms_for_code = xapian_get_all_terms(
1000,
$specific_field['code']
);
foreach ($sf_terms_for_code as $term) {
if (!empty($term)) {
$term_array[] = chamilo_get_boolean_query($term['name']); // Here name includes prefix.
}
}
} else {
foreach ($values as $term) {
if (!empty($term)) {
$prefix = $specific_field['code'];
$term_array[] = chamilo_get_boolean_query($prefix.$term);
}
}
}
} else {
$sf_terms_for_code = xapian_get_all_terms(1000, $specific_field['code']);
foreach ($sf_terms_for_code as $term) {
if (!empty($term)) {
// Here name includes prefix.
$term_array[] = chamilo_get_boolean_query($term['name']);
}
}
}
}
// Get right group of terms to show on multiple select.
$fixed_queries = [];
$course_filter = null;
if (($cid = api_get_course_id()) != -1) {
// Results only from actual course.
$course_filter = chamilo_get_boolean_query(XAPIAN_PREFIX_COURSEID.$cid);
}
if (count($term_array)) {
$fixed_queries = chamilo_join_queries($term_array, null, $op);
if ($course_filter != null) {
$fixed_queries = chamilo_join_queries(
$fixed_queries,
$course_filter,
'and'
);
}
} else {
if (!empty($query)) {
$fixed_queries = [$course_filter];
}
}
if ($query) {
list($count, $results) = chamilo_query_query(
api_convert_encoding($query, 'UTF-8', $charset),
0,
1000,
$fixed_queries
);
} else {
$count = 0;
$results = [];
}
// Prepare blocks to show.
$blocks = [];
if ($count > 0) {
foreach ($results as $result) {
// Fill the result array.
if (empty($result['thumbnail'])) {
$result['thumbnail'] = Display::returnIconPath('no_document_thumb.jpg');
}
if (!empty($result['url'])) {
$a_prefix = '<a href="'.$result['url'].'">';
$a_suffix = '</a>';
} else {
$a_prefix = '';
$a_suffix = '';
}
if ($mode == 'gallery') {
$title = $a_prefix.str_replace('_', ' ', $result['title']).$a_suffix;
$blocks[] = [1 => $a_prefix.'<img src="'.$result['thumbnail'].'" />'.$a_suffix.'<br />'.$title.'<br />'.$result['author'],
];
} else {
$title = '<div style="text-align:left;">'.$a_prefix.$result['title'].$a_suffix.(!empty($result['author']) ? ' '.$result['author'] : '').'<div>';
$blocks[] = [1 => $title];
}
}
}
// Show results.
if (count($blocks) > 0) {
$s = new SortableTableFromArray($blocks);
$s->display_mode = $mode; // default
$s->display_mode_params = 3;
$s->per_page = 9;
$additional_parameters = [
'mode' => $mode,
'action' => 'search',
'query' => Security::remove_XSS($_REQUEST['query']),
];
$get_params = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST['sf_'.$specific_field['code']])) {
$values = $_REQUEST['sf_'.$specific_field['code']];
//Sortable additional_parameters doesn't accept multi dimensional arrays
//$additional_parameters[ 'sf_'. $specific_field['code'] ] = $values;
foreach ($values as $value) {
$get_params .= '&sf_'.$specific_field['code'].'[]='.$value;
}
$get_params .= '&';
}
}
$additional_parameters['operator'] = $op;
$s->additional_parameters = $additional_parameters;
if ('default' == $mode) {
$s->set_header(0, get_lang(ucfirst(TOOL_SEARCH)), false);
}
$search_link = '<a href="%ssearch/index.php?mode=%s&action=search&query=%s%s">';
$iconGallery = (($mode == 'gallery') ? 'ButtonGallOn' : 'ButtonGallOff').'.png';
$iconDefault = (($mode == 'default') ? 'ButtonListOn' : 'ButtonListOff').'.png';
$mode_selector = '<div id="mode-selector">';
$mode_selector .= sprintf($search_link, api_get_path(WEB_CODE_PATH), 'gallery', $_REQUEST['query'], $get_params);
$mode_selector .= Display::return_icon($iconGallery).'</a>';
$mode_selector .= sprintf($search_link, api_get_path(WEB_CODE_PATH), 'default', $_REQUEST['query'], $get_params);
$mode_selector .= Display::return_icon($iconDefault).'</a>';
$mode_selector .= '</div>';
echo '<div id="search-results-container">';
echo $mode_selector;
$s->display();
echo '</div>';
}
Display::display_footer();

119
main/lp/lp_move_item.php Normal file
View File

@@ -0,0 +1,119 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
*/
$this_section = SECTION_COURSES;
api_protect_course_script();
/** @var learnpath $learnPath */
$learnPath = Session::read('oLP');
/* Header and action code */
$htmlHeadXtra[] = '<script>'.
$learnPath->get_js_dropdown_array().'
$(function() {
if ($(\'#previous\')) {
if(\'parent is\'+$(\'#idParent\').val()) {
load_cbo($(\'#idParent\').val());
}
}
});
</script>';
/* Constants and variables */
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = isset($_REQUEST['isStudentView']) ? (int) $_REQUEST['isStudentView'] : '';
$learnpath_id = (int) $_REQUEST['lp_id'];
$submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : '';
/* MAIN CODE */
if ((!$is_allowed_to_edit) || ($isStudentView)) {
header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
exit;
}
// From here on, we are admin because of the previous condition, so don't check anymore.
$course_id = api_get_course_int_id();
/*
Course admin section
- all the functions not available for students - always available in this case (page only shown to admin)
*/
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => $learnPath->getNameNoTags(),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
// Theme calls
$show_learn_path = true;
$lp_theme_css = $learnPath->get_theme();
Display::display_header(get_lang('Move'), 'Path');
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
</script>
<?php
echo $learnPath->build_action_menu();
echo '<div class="row">';
echo '<div class="col-md-3">';
echo $learnPath->return_new_tree();
echo '</div>';
echo '<div class="col-md-9">';
if (isset($is_success) && true === $is_success) {
$msg = '<div class="lp_message" style="margin-bottom:10px;">';
$msg .= 'The item has been moved.';
$msg .= '</div>';
echo $learnPath->display_item($_GET['id'], $msg);
} else {
$item = new learnpathItem($_GET['id']);
echo $learnPath->display_move_item($item);
}
echo '</div>';
echo '</div>';
Display::display_footer();

61
main/lp/lp_nav.php Normal file
View File

@@ -0,0 +1,61 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Script opened in an iframe and containing the
* learning path's navigation and progress bar.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
// Flag to allow for anonymous user - needs to be set before global.inc.php.
$use_anonymous = true;
require_once __DIR__.'/../inc/global.inc.php';
$htmlHeadXtra[] = '<script>
var chamilo_xajax_handler = window.parent.oxajax;
</script>';
$lpItemId = isset($_REQUEST['lp_item']) ? (int) $_REQUEST['lp_item'] : 0;
$lpId = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : 0;
if (!$lpItemId) {
echo '';
exit;
}
$progress_bar = '';
$navigation_bar = '';
$autostart = 'true';
$myLP = learnpath::getLpFromSession(api_get_course_id(), $lpId, api_get_user_id());
if ($myLP) {
$lp_theme_css = $myLP->get_theme();
$my_style = api_get_visual_theme();
// Setting up the CSS theme if exists
$myCourseLpTheme = null;
if ('true' === api_get_setting('allow_course_theme')) {
$myCourseLpTheme = api_get_course_setting('allow_learning_path_theme');
}
if (!empty($lp_theme_css) && !empty($myCourseLpTheme) && -1 != $myCourseLpTheme && 1 == $myCourseLpTheme) {
global $lp_theme_css;
} else {
$lp_theme_css = $my_style;
}
$progress_bar = $myLP->getProgressBar();
$navigation_bar = $myLP->get_navigation_bar();
$mediaplayer = $myLP->get_mediaplayer($lpItemId, $autostart);
if ($mediaplayer) {
echo $mediaplayer;
echo "<script>
$(function() {
jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer();
});
</script>";
}
}
session_write_close();

528
main/lp/lp_report.php Normal file
View File

@@ -0,0 +1,528 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
use Chamilo\CourseBundle\Entity\CLpCategory;
/**
* Report from students for learning path.
*/
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script(true);
$isAllowedToEdit = api_is_allowed_to_edit(null, true);
if (!$isAllowedToEdit) {
api_not_allowed(true);
}
$lpTable = Database::get_course_table(TABLE_LP_MAIN);
$courseInfo = api_get_course_info();
$sessionId = api_get_session_id();
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$lpId = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : 0;
$studentId = isset($_REQUEST['student_id']) ? (int) $_REQUEST['student_id'] : 0;
$groupFilter = isset($_REQUEST['group_filter']) ? Security::remove_XSS($_REQUEST['group_filter']) : '';
$deleteExercisesAttempts = isset($_REQUEST['delete_exercise_attempts']) && 1 === (int) $_REQUEST['delete_exercise_attempts'] ? true : false;
$groupFilterType = '';
$groupFilterId = 0;
$groupFilterParts = explode(':', $groupFilter);
if (!empty($groupFilterParts) && isset($groupFilterParts[1])) {
$groupFilterType = $groupFilterParts[0];
$groupFilterId = (int) $groupFilterParts[1];
}
$export = isset($_REQUEST['export']);
$reset = isset($_REQUEST['reset']) ? $_REQUEST['reset'] : '';
$lp = new learnpath($courseCode, $lpId, api_get_user_id());
if (empty($lp)) {
api_not_allowed(true);
}
$urlBase = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq().'&action=report&lp_id='.$lpId;
$url = $urlBase.'&group_filter='.$groupFilter;
$allowUserGroups = api_get_configuration_value('allow_lp_subscription_to_usergroups');
$course = api_get_course_entity($courseId);
$session = api_get_session_entity($sessionId);
$em = Database::getManager();
// Check LP subscribers
if ('1' === $lp->getSubscribeUsers()) {
/** @var ItemPropertyRepository $itemRepo */
$itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
$subscribedUsersInLp = $itemRepo->getUsersSubscribedToItem(
'learnpath',
$lpId,
$course,
$session
);
// Subscribed groups to a LP
$subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
'learnpath',
$lpId,
$course,
$session
);
$groups = [];
/** @var CItemProperty $itemProperty */
if (!empty($subscribedGroupsInLp)) {
foreach ($subscribedGroupsInLp as $itemProperty) {
if (!empty($itemProperty)) {
$getGroup = $itemProperty->getGroup();
if (!empty($getGroup)) {
$groups[] = $itemProperty->getGroup()->getId();
}
}
}
}
$users = [];
if (!empty($groups)) {
foreach ($groups as $groupId) {
$students = GroupManager::getStudents($groupId);
if (!empty($students)) {
foreach ($students as $studentInfo) {
$users[]['user_id'] = $studentInfo['user_id'];
}
}
}
}
if (!empty($subscribedUsersInLp)) {
foreach ($subscribedUsersInLp as $itemProperty) {
$user = $itemProperty->getToUser();
if ($user) {
$users[]['user_id'] = $itemProperty->getToUser()->getId();
}
}
}
} else {
$categoryId = $lp->getCategoryId();
$users = [];
/** @var ItemPropertyRepository $itemRepo */
$itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
if (!empty($categoryId)) {
// Check users subscribed in category.
/** @var CLpCategory $category */
$category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($categoryId);
$subscribedUsersInCategory = $category->getUsers();
if (!empty($subscribedUsersInCategory)) {
foreach ($subscribedUsersInCategory as $item) {
$user = $item->getUser();
if ($user) {
$users[]['user_id'] = $item->getUser()->getId();
}
}
}
// Check groups subscribed to category.
$subscribedGroupsInLpCategory = $itemRepo->getGroupsSubscribedToItem(
'learnpath_category',
$categoryId,
$course,
$session
);
$groups = [];
if (!empty($subscribedGroupsInLpCategory)) {
foreach ($subscribedGroupsInLpCategory as $itemProperty) {
if ($itemProperty->getGroup()) {
$groups[] = $itemProperty->getGroup()->getId();
}
}
}
if (!empty($groups)) {
foreach ($groups as $groupId) {
$students = GroupManager::getStudents($groupId);
if (!empty($students)) {
foreach ($students as $studentInfo) {
$users[]['user_id'] = $studentInfo['user_id'];
}
}
}
}
if ($allowUserGroups) {
$userGroup = new UserGroup();
$items = $userGroup->getGroupsByLpCategory($categoryId, $courseId, $sessionId);
$selectedUserGroupChoices = [];
if (!empty($items)) {
foreach ($items as $data) {
$userList = $userGroup->get_users_by_usergroup($data['usergroup_id']);
if (!empty($userList)) {
foreach ($userList as $userId) {
$users[]['user_id'] = $userId;
}
}
}
}
}
}
if (empty($categoryId) || empty($users)) {
if (empty($sessionId)) {
$users = CourseManager::get_user_list_from_course_code(
$courseCode,
0,
null,
null,
STUDENT
);
} else {
$users = CourseManager::get_user_list_from_course_code(
$courseCode,
$sessionId,
null,
null,
0
);
}
}
}
$lpInfo = Database::select(
'*',
$lpTable,
[
'where' => [
'c_id = ? AND ' => $courseId,
'id = ?' => $lpId,
],
],
'first'
);
$groups = GroupManager::get_group_list(null, $courseInfo, null, $sessionId);
$label = get_lang('Groups');
$classes = [];
if ($allowUserGroups) {
$label = get_lang('Groups').' / '.get_lang('Classes');
$userGroup = new UserGroup();
$conditions = [];
$conditions['where'] = [' usergroup.course_id = ? ' => $courseId];
$classes = $userGroup->getUserGroupInCourse($conditions);
}
$groupFilterForm = '';
if (!empty($groups)) {
$form = new FormValidator('group', 'GET', $url);
$form->addHidden('action', 'report');
$form->addHidden('lp_id', $lpId);
$form->addCourseHiddenParams();
$courseGroups = [];
foreach ($groups as $group) {
$option = [
'text' => $group['name'],
'value' => "group:".$group['iid'],
];
$courseGroups[] = $option;
}
$select = $form->addSelect(
'group_filter',
$label,
[],
[
'id' => 'group_filter',
'placeholder' => get_lang('All'),
]
);
$select->addOptGroup($courseGroups, get_lang('Groups'));
if ($allowUserGroups) {
$options = [];
foreach ($classes as $group) {
$option = [
'text' => $group['name'],
'value' => "class:".$group['id'],
];
$options[] = $option;
}
$select->addOptGroup($options, get_lang('Classes'));
}
if (!empty($groupFilter)) {
switch ($groupFilterType) {
case 'group':
$users = GroupManager::getStudents($groupFilterId, true);
break;
case 'class':
if ($allowUserGroups) {
$users = $userGroup->getUserListByUserGroup($groupFilterId);
}
break;
}
$form->setDefaults(['group_filter' => $groupFilter]);
}
$groupFilterForm = $form->returnForm();
}
if ($reset) {
switch ($reset) {
case 'student':
if ($studentId) {
$studentInfo = api_get_user_info($studentId);
if ($studentInfo) {
Event::delete_student_lp_events(
$studentId,
$lpId,
$courseInfo,
$sessionId,
false === $deleteExercisesAttempts
);
Display::addFlash(
Display::return_message(
get_lang('LPWasReset').': '.$studentInfo['complete_name_with_username'],
'success'
)
);
}
}
break;
case 'all':
$result = [];
foreach ($users as $user) {
$userId = $user['user_id'];
$studentInfo = api_get_user_info($userId);
if ($studentInfo) {
Event::delete_student_lp_events(
$userId,
$lpId,
$courseInfo,
$sessionId,
false === $deleteExercisesAttempts
);
$result[] = $studentInfo['complete_name_with_username'];
}
}
if (!empty($result)) {
Display::addFlash(
Display::return_message(
get_lang('LPWasReset').': '.implode(', ', $result),
'success'
)
);
}
break;
}
api_location($url);
}
$userList = [];
$showEmail = api_get_setting('show_email_addresses');
if (!empty($users)) {
$added = [];
foreach ($users as $user) {
$userId = $user['user_id'];
if (in_array($userId, $added)) {
continue;
}
$userInfo = api_get_user_info($userId);
$lpTime = Tracking::get_time_spent_in_lp(
$userId,
$courseCode,
[$lpId],
$sessionId
);
$lpScore = Tracking::get_avg_student_score(
$userId,
$courseCode,
[$lpId],
$sessionId
);
$lpProgress = Tracking::get_avg_student_progress(
$userId,
$courseCode,
[$lpId],
$sessionId
);
$lpLastConnection = Tracking::get_last_connection_time_in_lp(
$userId,
$courseCode,
$lpId,
$sessionId
);
$lpLastConnection = empty($lpLastConnection) ? '-' : api_convert_and_format_date(
$lpLastConnection,
DATE_TIME_FORMAT_LONG
);
$userGroupList = '';
if (!empty($groups)) {
$groupsByUser = GroupManager::getAllGroupPerUserSubscription($userId, $courseId, $sessionId);
$icon = Display::return_icon('group.png', get_lang('Group'));
if (!empty($groupsByUser)) {
$groupUrl = api_get_path(WEB_CODE_PATH).'group/group_space.php?'.api_get_cidreq(true, false);
foreach ($groupsByUser as $group) {
$userGroupList .= Display::url($icon.$group['name'], $groupUrl.'&gidReq='.$group['iid']).'&nbsp;';
}
}
}
$classesToString = '';
if ($allowUserGroups) {
$classes = $userGroup->getUserGroupListByUser($userId, UserGroup::NORMAL_CLASS);
$icon = Display::return_icon('class.png', get_lang('Class'));
if (!empty($classes)) {
$classUrl = api_get_path(WEB_CODE_PATH).'user/class.php?'.api_get_cidreq(true, false);
foreach ($classes as $class) {
$classesToString .= Display::url(
$icon.$class['name'],
$classUrl.'&class_id='.$class['id']
).'&nbsp;';
}
}
}
$trackingUrl = api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?details=true&'.
api_get_cidreq().'&course='.$courseCode.'&origin=tracking_course&student='.$userId;
$row = [];
$row[] = Display::url($userInfo['firstname'], $trackingUrl);
$row[] = Display::url($userInfo['lastname'], $trackingUrl);
if ('true' === $showEmail) {
$row[] = $userInfo['email'];
}
$row[] = $userGroupList.$classesToString;
$row[] = api_time_to_hms($lpTime);
$row[] = "$lpProgress %";
$row[] = is_numeric($lpScore) ? "$lpScore%" : $lpScore;
$row[] = $lpLastConnection;
$actions = Display::url(Display::return_icon('statistics.png', get_lang('Reporting')), $trackingUrl).'&nbsp;';
$actions .= Display::url(
Display::return_icon('2rightarrow.png', get_lang('Details')),
'javascript:void(0);',
['data-id' => $userId, 'class' => 'details']
).'&nbsp;';
$actions .= Display::url(
Display::return_icon('clean.png', get_lang('Reset')),
'javascript:void(0);',
['data-id' => $userId, 'data-username' => $userInfo['username'], 'class' => 'delete_attempt']
);
$row[] = $actions;
$row['username'] = $userInfo['username'];
$userList[] = $row;
$added[] = $userId;
}
} else {
Display::addFlash(Display::return_message(get_lang('NoUserAdded'), 'warning'));
}
$parameters = [
'action' => 'report',
'group_filter' => $groupFilter,
'cidReq' => $courseCode,
'id_session' => $sessionId,
'lp_id' => $lpId,
];
$table = new SortableTableFromArrayConfig($userList, 1, 20, 'lp');
$table->set_additional_parameters($parameters);
$column = 0;
$table->set_header($column++, get_lang('FirstName'));
$table->set_header($column++, get_lang('LastName'));
if ('true' === $showEmail) {
$table->set_header($column++, get_lang('Email'));
}
$table->set_header($column++, $label, false);
$table->set_header($column++, get_lang('ScormTime'));
$table->set_header($column++, get_lang('Progress'));
$table->set_header($column++, get_lang('ScormScore'));
$table->set_header($column++, get_lang('LastConnection'), false);
if (false === $export) {
$table->set_header($column++, get_lang('Actions'), false);
}
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$actions = Display::url(
Display::return_icon(
'back.png',
get_lang('Back'),
[],
ICON_SIZE_MEDIUM
),
api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq()
);
if (!empty($users)) {
$actions .= Display::url(
Display::return_icon(
'pdf.png',
get_lang('ExportToPdf'),
[],
ICON_SIZE_MEDIUM
),
$url.'&export=pdf'
);
$userListToString = array_column($userList, 'username');
$userListToString = implode(', ', $userListToString);
$actions .= Display::url(
Display::return_icon(
'clean.png',
get_lang('Clean'),
[],
ICON_SIZE_MEDIUM
),
'javascript:void(0);',
['data-users' => $userListToString, 'class' => 'delete_all']
);
}
$template = new Template(get_lang('StudentScore'));
$template->assign('group_class_label', $label);
$template->assign('user_list', $userList);
$template->assign('session_id', api_get_session_id());
$template->assign('course_code', api_get_course_id());
$template->assign('lp_id', $lpId);
$template->assign('show_email', 'true' === $showEmail);
$template->assign('table', $table->return_table());
$template->assign('export', (int) $export);
$template->assign('group_form', $groupFilterForm);
$template->assign('url', $url);
$template->assign('url_base', $urlBase);
$layout = $template->get_template('learnpath/report.tpl');
$template->assign('header', $lpInfo['name']);
$template->assign('actions', Display::toolbarAction('lp_actions', [$actions]));
$result = $template->fetch($layout);
$template->assign('content', $result);
if ($export) {
$pdfParams = [
'filename' => get_lang('StudentScore').'_'.api_get_local_time(),
];
$pdf = new PDF('A4', 'P', $pdfParams);
$pdf->html_to_pdf_with_template(
$result,
false,
false,
true
);
exit;
}
$template->display_one_col_template();

34
main/lp/lp_save.php Normal file
View File

@@ -0,0 +1,34 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* Script that handles the saving of item status.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
/**
* Initialization is to be done by lp_controller.php.
* Switching within the field to update.
*/
?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php echo api_get_language_isocode(); ?>" lang="<?php echo api_get_language_isocode(); ?>">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=<?php echo api_get_system_encoding(); ?>" />
<script language='javascript'>
<?php
/** @var learnpath $lp */
$lp = Session::read('oLP');
if ('fullscreen' != $lp->mode) {
}
?>
</script>
</head>
<body dir="<?php echo api_get_text_direction(); ?>">
</body></html>

58
main/lp/lp_stats.php Normal file
View File

@@ -0,0 +1,58 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This script displays statistics on the current learning path (scorm)
* This script must be included by lp_controller.php to get basic initialisation.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
require_once __DIR__.'/../inc/global.inc.php';
// When origin is not set that means that the lp_stats are viewed from the "man running" icon
if (!isset($origin)) {
$origin = 'learnpath';
}
$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : api_get_session_id();
$courseCode = isset($_GET['course']) ? $_GET['course'] : api_get_course_id();
$userId = isset($_GET['student_id']) ? (int) $_GET['student_id'] : api_get_user_id();
$lpId = isset($_GET['lp_id']) ? $_GET['lp_id'] : null;
$lpItemId = isset($_GET['lp_item_id']) ? $_GET['lp_item_id'] : null;
$extendId = isset($_GET['extend_id']) ? $_GET['extend_id'] : null;
$extendAttemptId = isset($_GET['extend_attempt_id']) ? $_GET['extend_attempt_id'] : null;
$extendedAttempt = isset($_GET['extend_attempt']) ? $_GET['extend_attempt'] : null;
$extendedAll = isset($_GET['extend_all']) ? $_GET['extend_all'] : null;
$export = isset($_GET['export']) && 'csv' === $_GET['export'];
$allowExtend = isset($_GET['allow_extend']) ? $_GET['allow_extend'] : 1;
$lpReportType = api_get_setting('lp_show_reduced_report');
$type = 'classic';
if ('true' === $lpReportType) {
$type = 'simple';
}
$courseInfo = api_get_course_info($courseCode);
$output = Tracking::getLpStats(
$userId,
$courseInfo,
$sessionId,
$origin,
$export,
$lpId,
$lpItemId,
$extendId,
$extendAttemptId,
$extendedAttempt,
$extendedAll,
$type,
$allowExtend
);
// Origin = tracking means that teachers see that info in the Reporting tool
if ($origin !== 'tracking') {
Display::display_reduced_header();
$output .= '</body></html>';
}
return $output;

View File

@@ -0,0 +1,378 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
use Chamilo\CoreBundle\Entity\Session;
use Chamilo\CoreBundle\Entity\SessionRelCourseRelUser;
use Chamilo\CourseBundle\Entity\CItemProperty;
use Chamilo\UserBundle\Entity\User;
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
$subscriptionSettings = learnpath::getSubscriptionSettings();
if ($subscriptionSettings['allow_add_users_to_lp'] == false) {
api_not_allowed(true);
}
$is_allowed_to_edit = api_is_allowed_to_edit(false, true, false, false);
if (!$is_allowed_to_edit) {
api_not_allowed(true);
}
$lpId = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
if (empty($lpId)) {
api_not_allowed(true);
}
$allowUserGroups = api_get_configuration_value('allow_lp_subscription_to_usergroups');
$currentUser = api_get_user_entity(api_get_user_id());
$oLP = new learnpath(api_get_course_id(), $lpId, api_get_user_id());
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self().'?action=build&lp_id='.$oLP->get_id().'&'.api_get_cidreq(),
'name' => $oLP->getNameNoTags(),
];
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$url = api_get_self().'?'.api_get_cidreq().'&lp_id='.$lpId;
$lp = new learnpath($courseCode, $lpId, api_get_user_id());
$em = Database::getManager();
/** @var CourseRepository $courseRepo */
$courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
/** @var ItemPropertyRepository $itemRepo */
$itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
/** @var Session $session */
$session = null;
if (!empty($sessionId)) {
$session = $em->getRepository('ChamiloCoreBundle:Session')->find($sessionId);
}
$course = $courseRepo->find($courseId);
$subscribedUsers = [];
// Getting subscribe users to the course.
if (!$session) {
$subscribedUsers = $courseRepo->getSubscribedStudents($course)
->getQuery()
->getResult();
} else {
$list = $session->getUserCourseSubscriptionsByStatus($course, Session::STUDENT);
if ($list) {
/** @var SessionRelCourseRelUser $sessionCourseUser */
foreach ($list as $sessionCourseUser) {
$subscribedUsers[$sessionCourseUser->getUser()->getId()] = $sessionCourseUser->getUser();
}
}
}
// Getting all users in a nice format.
$choices = [];
/** @var User $user */
foreach ($subscribedUsers as $user) {
$choices[$user->getId()] = $user->getCompleteNameWithClasses();
}
// Getting subscribed users to a LP.
$subscribedUsersInLp = $itemRepo->getUsersSubscribedToItem(
'learnpath',
$lpId,
$course,
$session
);
$selectedChoices = [];
if (!empty($subscribedUsersInLp)) {
foreach ($subscribedUsersInLp as $itemProperty) {
if (!empty($itemProperty)) {
$getToUser = $itemProperty->getToUser();
if (!empty($getToUser)) {
$selectedChoices[] = $getToUser->getId();
}
}
}
}
// User form.
$formUsers = new FormValidator('lp_edit', 'post', $url);
$formUsers->addElement('hidden', 'user_form', 1);
$userMultiSelect = $formUsers->addElement(
'advmultiselect',
'users',
null,
$choices
);
$userMultiSelect->setLabel([
get_lang('Users'),
get_lang('UserNotSubscribed'),
get_lang('Subscribed'),
]);
$formUsers->addButtonSave(get_lang('Save'));
$defaults = [];
if (!empty($selectedChoices)) {
$defaults['users'] = $selectedChoices;
}
$formUsers->setDefaults($defaults);
// Group form.
$form = new FormValidator('lp_edit', 'post', $url);
$form->addElement('hidden', 'group_form', 1);
// Group list
$groupList = \CourseManager::get_group_list_of_course(
$courseCode,
$sessionId,
1
);
$groupChoices = array_column($groupList, 'name', 'id');
// Subscribed groups to a LP
$subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
'learnpath',
$lpId,
$course,
$session
);
$selectedGroupChoices = [];
/** @var CItemProperty $itemProperty */
if (!empty($subscribedGroupsInLp)) {
foreach ($subscribedGroupsInLp as $itemProperty) {
if (!empty($itemProperty)) {
$getGroup = $itemProperty->getGroup();
if (!empty($getGroup)) {
$selectedGroupChoices[] = $itemProperty->getGroup()->getId();
}
}
}
}
$groupMultiSelect = $form->addElement(
'advmultiselect',
'groups',
null,
$groupChoices
);
$groupMultiSelect->setLabel([
get_lang('Groups'),
get_lang('UserNotSubscribed'),
get_lang('Subscribed'),
]);
$form->addButtonSave(get_lang('Save'));
// UserGroup
if ($allowUserGroups) {
$formUserGroup = new FormValidator('usergroup_form', 'post', $url);
$formUserGroup->addHidden('usergroup_form', 1);
$userGroup = new UserGroup();
$conditions = [];
$conditions['where'] = [' usergroup.course_id = ? ' => $courseId];
$groups = $userGroup->getUserGroupInCourse($conditions);
$allOptions = array_column($groups, 'name', 'id');
$items = $userGroup->getGroupsByLp($lpId, $courseId, $sessionId);
$selectedUserGroupChoices = [];
if (!empty($items)) {
foreach ($items as $data) {
if (isset($allOptions[$data['usergroup_id']])) {
$selectedUserGroupChoices[] = $data['usergroup_id'];
}
}
}
$userGroupMultiSelect = $formUserGroup->addElement(
'advmultiselect',
'usergroups',
get_lang('Classes'),
$allOptions
);
$formUserGroup->setDefaults(['usergroups' => $selectedUserGroupChoices]);
$formUserGroup->addButtonSave(get_lang('Save'));
$sessionCondition = api_get_session_condition($sessionId, true);
if ($formUserGroup->validate()) {
$values = $formUserGroup->getSubmitValues();
$table = Database::get_course_table(TABLE_LP_REL_USERGROUP);
if (isset($values['usergroups'])) {
$userGroups = $values['usergroups'];
foreach ($selectedUserGroupChoices as $userGroupId) {
$userGroupId = (int) $userGroupId;
if (!in_array($userGroupId, $userGroups)) {
$sql = "DELETE FROM $table
WHERE
c_id = $courseId AND
lp_id = $lpId AND
usergroup_id = $userGroupId
$sessionCondition
";
Database::query($sql);
$userList = $userGroup->get_users_by_usergroup($userGroupId);
$itemRepo->unsubcribeUsersToItem(
'learnpath',
$course,
$session,
$lpId,
$userList
);
}
}
foreach ($userGroups as $userGroupId) {
$userGroupId = (int) $userGroupId;
$sql = "SELECT id FROM $table
WHERE
c_id = $courseId AND
lp_id = $lpId AND
usergroup_id = $userGroupId
$sessionCondition
";
$result = Database::query($sql);
if (0 == Database::num_rows($result)) {
$params = [
'lp_id' => $lpId,
'c_id' => $courseId,
'usergroup_id' => $userGroupId,
'created_at' => api_get_utc_datetime(),
];
if (!empty($sessionId)) {
$params['session_id'] = $sessionId;
}
Database::insert($table, $params);
}
}
$groups = $userGroup->getGroupsByLp($lpId, $courseId, $sessionId);
$userList = [];
foreach ($groups as $groupId) {
$userList = $userGroup->get_users_by_usergroup($groupId);
$itemRepo->subscribeUsersToItem(
$currentUser,
'learnpath',
$course,
$session,
$lpId,
$userList,
false
);
}
Display::addFlash(Display::return_message(get_lang('Updated')));
} else {
foreach ($groups as $group) {
$userList = $userGroup->get_users_by_usergroup($group['id']);
$itemRepo->unsubcribeUsersToItem(
'learnpath',
$course,
$session,
$lpId,
$userList
);
}
// Clean all
$sql = "DELETE FROM $table
WHERE
c_id = $courseId AND
lp_id = $lpId
$sessionCondition
";
Database::query($sql);
}
header("Location: $url");
exit;
}
}
$defaults = [];
if (!empty($selectedChoices)) {
$defaults['users'] = $selectedChoices;
}
$formUsers->setDefaults($defaults);
$defaults = [];
if (!empty($selectedGroupChoices)) {
$defaults['groups'] = $selectedGroupChoices;
}
$form->setDefaults($defaults);
// Group.
if ($form->validate()) {
$values = $form->getSubmitValues();
// Subscribing users
$users = isset($values['users']) ? $values['users'] : [];
$userForm = isset($values['user_form']) ? $values['user_form'] : [];
if (!empty($userForm)) {
$itemRepo->subscribeUsersToItem(
$currentUser,
'learnpath',
$course,
$session,
$lpId,
$users
);
Display::addFlash(Display::return_message(get_lang('Updated')));
}
// Subscribing groups
$groups = isset($values['groups']) ? $values['groups'] : [];
$groupForm = isset($values['group_form']) ? $values['group_form'] : [];
if (!empty($groupForm)) {
$itemRepo->subscribeGroupsToItem(
$currentUser,
'learnpath',
$course,
$session,
$lpId,
$groups
);
Display::addFlash(Display::return_message(get_lang('Updated')));
}
header("Location: $url");
exit;
}
$message = Display::return_message(get_lang('UserLpSubscriptionDescription'));
$headers = [
get_lang('SubscribeUsersToLp'),
get_lang('SubscribeGroupsToLp'),
];
$items = [$formUsers->toHtml(), $form->toHtml()];
if ($allowUserGroups) {
$headers[] = get_lang('SubscribeUserGroupsToLp');
$items[] = $formUserGroup->toHtml();
}
$menu = $oLP->build_action_menu(true, false, true, false);
$tpl = new Template();
$tabs = Display::tabs($headers, $items);
$tpl->assign('content', $menu.$message.$tabs);
$tpl->display_one_col_template();

View File

@@ -0,0 +1,354 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CoreBundle\Entity\Repository\CourseRepository;
use Chamilo\CoreBundle\Entity\Repository\ItemPropertyRepository;
use Chamilo\CourseBundle\Entity\CLpCategory;
use Chamilo\CourseBundle\Entity\CLpCategoryUser;
use Chamilo\UserBundle\Entity\User;
use Doctrine\Common\Collections\Criteria;
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
$is_allowed_to_edit = api_is_allowed_to_edit(false, true, false, false);
if (!$is_allowed_to_edit) {
api_not_allowed(true);
}
$categoryId = isset($_GET['id']) ? (int) $_GET['id'] : 0;
if (empty($categoryId)) {
api_not_allowed(true);
}
$subscriptionSettings = learnpath::getSubscriptionSettings();
if (false == $subscriptionSettings['allow_add_users_to_lp_category']) {
api_not_allowed(true);
}
$allowUserGroups = api_get_configuration_value('allow_lp_subscription_to_usergroups');
$courseId = api_get_course_int_id();
$courseCode = api_get_course_id();
$sessionId = api_get_session_id();
$currentUser = api_get_user_entity(api_get_user_id());
$em = Database::getManager();
/** @var CLpCategory $category */
$category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($categoryId);
if (!$category) {
api_not_allowed(true);
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = ['url' => '#', 'name' => strip_tags($category->getName())];
$url = api_get_self().'?'.api_get_cidreq().'&action=add_users_to_category&id='.$categoryId;
$message = Display::return_message(get_lang('UserLpCategorySubscriptionDescription'));
// Building the form for Groups
$form = new FormValidator('lp_edit', 'post', $url);
$form->addElement('hidden', 'group_form', 1);
$form->addLabel('', $message);
// Group list
$groupList = \CourseManager::get_group_list_of_course(
api_get_course_id(),
$sessionId,
1
);
$groupChoices = array_column($groupList, 'name', 'id');
$session = api_get_session_entity($sessionId);
/** @var CourseRepository $courseRepo */
$courseRepo = $em->getRepository('ChamiloCoreBundle:Course');
/** @var ItemPropertyRepository $itemRepo */
$itemRepo = $em->getRepository('ChamiloCourseBundle:CItemProperty');
$course = api_get_course_entity($courseId);
// Subscribed groups to a LP
$subscribedGroupsInLp = $itemRepo->getGroupsSubscribedToItem(
'learnpath_category',
$categoryId,
$course,
$session
);
$selectedGroupChoices = [];
/** @var \Chamilo\CourseBundle\Entity\CItemProperty $itemProperty */
foreach ($subscribedGroupsInLp as $itemProperty) {
$selectedGroupChoices[] = $itemProperty->getGroup()->getId();
}
$groupMultiSelect = $form->addElement(
'advmultiselect',
'groups',
get_lang('Groups'),
$groupChoices
);
// submit button
$form->addButtonSave(get_lang('Save'));
// UserGroup
if ($allowUserGroups) {
$formUserGroup = new FormValidator('lp_edit_usergroup', 'post', $url);
$formUserGroup->addHidden('usergroup_form', 1);
$userGroup = new UserGroup();
$conditions = [];
$conditions['where'] = [' usergroup.course_id = ? ' => $courseId];
$groups = $userGroup->getUserGroupInCourse($conditions);
$allOptions = array_column($groups, 'name', 'id');
$items = $userGroup->getGroupsByLpCategory($categoryId, $courseId, $sessionId);
$selectedUserGroupChoices = [];
if (!empty($items)) {
foreach ($items as $data) {
if (isset($allOptions[$data['usergroup_id']])) {
$selectedUserGroupChoices[] = $data['usergroup_id'];
}
}
}
$userGroupMultiSelect = $formUserGroup->addElement(
'advmultiselect',
'usergroups',
get_lang('Classes'),
$allOptions
);
$formUserGroup->setDefaults(['usergroups' => $selectedUserGroupChoices]);
$formUserGroup->addButtonSave(get_lang('Save'));
$sessionCondition = api_get_session_condition($sessionId, true);
if ($formUserGroup->validate()) {
$values = $formUserGroup->getSubmitValues();
$table = Database::get_course_table(TABLE_LP_CATEGORY_REL_USERGROUP);
if (isset($values['usergroups'])) {
$userGroups = $values['usergroups'];
foreach ($selectedUserGroupChoices as $userGroupId) {
$userGroupId = (int) $userGroupId;
if (!in_array($userGroupId, $userGroups)) {
$sql = "DELETE FROM $table
WHERE
c_id = $courseId AND
lp_category_id = $categoryId AND
usergroup_id = $userGroupId
$sessionCondition
";
Database::query($sql);
$userList = $userGroup->get_users_by_usergroup($userGroupId);
foreach ($userList as $userId) {
$user = api_get_user_entity($userId);
$criteria = Criteria::create()->where(
Criteria::expr()->eq('user', $user)
);
$userCategory = $category->getUsers()->matching($criteria)->first();
if ($userCategory) {
$category->removeUsers($userCategory);
}
}
}
}
foreach ($userGroups as $userGroupId) {
$userGroupId = (int) $userGroupId;
$sql = "SELECT id FROM $table
WHERE
c_id = $courseId AND
lp_category_id = $categoryId AND
usergroup_id = $userGroupId
$sessionCondition
";
$result = Database::query($sql);
if (0 == Database::num_rows($result)) {
$params = [
'lp_category_id' => $categoryId,
'c_id' => $courseId,
'usergroup_id' => $userGroupId,
'created_at' => api_get_utc_datetime(),
];
if (!empty($sessionId)) {
$params['session_id'] = $sessionId;
}
Database::insert($table, $params);
}
}
$groups = $userGroup->getGroupsByLpCategory($categoryId, $courseId, $sessionId);
$userList = [];
foreach ($groups as $groupId) {
$userList = $userGroup->get_users_by_usergroup($groupId);
foreach ($userList as $userId) {
$user = api_get_user_entity($userId);
if ($user) {
$categoryUser = new CLpCategoryUser();
$categoryUser->setUser($user);
$category->addUser($categoryUser);
}
}
}
$em->persist($category);
$em->flush();
Display::addFlash(Display::return_message(get_lang('Updated')));
} else {
foreach ($category->getUsers() as $userCategory) {
$category->removeUsers($userCategory);
}
// Clean all
$sql = "DELETE FROM $table
WHERE
c_id = $courseId AND
lp_category_id = $categoryId
$sessionCondition
";
Database::query($sql);
$em->persist($category);
$em->flush();
}
header("Location: $url");
exit;
}
}
$defaults = [];
if (!empty($selectedGroupChoices)) {
$defaults['groups'] = $selectedGroupChoices;
}
$form->setDefaults($defaults);
// Getting subscribe users to the course.
$choices = [];
if (empty($sessionId)) {
$subscribedUsers = $courseRepo->getSubscribedStudents($course);
$subscribedUsers = $subscribedUsers->getQuery();
$subscribedUsers = $subscribedUsers->execute();
// Getting all users in a nice format.
/** @var User $user */
foreach ($subscribedUsers as $user) {
$choices[$user->getId()] = $user->getCompleteNameWithClasses();
}
} else {
$users = CourseManager::get_user_list_from_course_code($course->getCode(), $sessionId);
foreach ($users as $user) {
$choices[$user['user_id']] = api_get_person_name($user['firstname'], $user['lastname']);
}
}
// Getting subscribed users to a category.
$subscribedUsersInCategory = $category->getUsers();
$selectedChoices = [];
foreach ($subscribedUsersInCategory as $item) {
$selectedChoices[] = $item->getUser()->getId();
}
// Building the form for Users
$formUsers = new FormValidator('lp_edit_users', 'post', $url);
$formUsers->addElement('hidden', 'user_form', 1);
$formUsers->addLabel('', $message);
$userMultiSelect = $formUsers->addElement(
'advmultiselect',
'users',
get_lang('Users'),
$choices
);
$formUsers->addButtonSave(get_lang('Save'));
$defaults = [];
if (!empty($selectedChoices)) {
$defaults['users'] = $selectedChoices;
}
$formUsers->setDefaults($defaults);
$tpl = new Template();
if ($formUsers->validate()) {
$values = $formUsers->getSubmitValues();
// Subscribing users
$users = isset($values['users']) ? $values['users'] : [];
$userForm = isset($values['user_form']) ? $values['user_form'] : [];
if (!empty($userForm)) {
$deleteUsers = [];
if ($subscribedUsersInCategory) {
/** @var CLpCategoryUser $user */
foreach ($subscribedUsersInCategory as $user) {
$userId = $user->getUser()->getId();
if (!in_array($userId, $users)) {
$category->removeUsers($user);
}
}
}
foreach ($users as $userId) {
$categoryUser = new CLpCategoryUser();
$user = UserManager::getRepository()->find($userId);
if ($user) {
$categoryUser->setUser($user);
$category->addUser($categoryUser);
}
}
$em->persist($category);
$em->flush();
Display::addFlash(Display::return_message(get_lang('Updated')));
}
header("Location: $url");
exit;
}
if ($form->validate()) {
$values = $form->getSubmitValues();
// Subscribing groups
$groups = isset($values['groups']) ? $values['groups'] : [];
$groupForm = isset($values['group_form']) ? $values['group_form'] : [];
if (!empty($groupForm)) {
$itemRepo->subscribeGroupsToItem(
$currentUser,
'learnpath_category',
$course,
$session,
$categoryId,
$groups
);
Display::addFlash(Display::return_message(get_lang('Updated')));
}
header("Location: $url");
exit;
}
$headers = [
get_lang('SubscribeUsersToLpCategory'),
get_lang('SubscribeGroupsToLpCategory'),
];
$items = [$formUsers->toHtml(), $form->toHtml()];
if ($allowUserGroups) {
$headers[] = get_lang('SubscribeClassesToLpCategory');
$items[] = $formUserGroup->toHtml();
}
$tabs = Display::tabs($headers, $items);
$tpl->assign('content', $tabs);
$tpl->display_one_col_template();

View File

@@ -0,0 +1,81 @@
<?php
/* For licensing terms, see /license.txt */
/**
* This is a learning path creation and player tool in Chamilo - previously.
*
* @author Julio Montoya - Improving the list of templates
*/
require_once __DIR__.'/../inc/global.inc.php';
$this_section = SECTION_COURSES;
api_protect_course_script();
$allow = api_is_allowed_to_edit(null, true);
$lpId = !empty($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
if (!$allow || empty($lpId)) {
api_not_allowed(true);
}
$lp = new learnpath(api_get_course_id(), $lpId, api_get_user_id());
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => Category::getUrl(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => 'lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$lpId&".api_get_cidreq(),
'name' => $lp->getNameNoTags(),
];
$form = new FormValidator(
'',
'POST',
api_get_self().'?'.api_get_cidreq().'&lp_id='.$lpId,
'',
[
'id' => 'upload_form',
'enctype' => 'multipart/form-data',
]
);
$firstPart = explode("/", $lp->path, 2);
$zipFileName = $firstPart[0].".zip";
$form->addHeader(get_lang('UpdateFile'));
$form->addHtml(Display::return_message(get_lang('TheScormPackageWillBeUpdatedYouMustUploadTheFileWithTheSameName')." ".get_lang('FileName')." : ".$zipFileName));
$form->addLabel(null, Display::return_icon('scorm_logo.jpg', null, ['style' => 'width:230px;height:100px']));
$form->addElement('hidden', 'curdirpath', '');
$form->addElement('file', 'user_file', get_lang('FileToUpload'));
$form->addRule('user_file', get_lang('ThisFieldIsRequired'), 'required');
$form->addButtonUpload(get_lang('Upload'));
if ($form->validate()) {
$oScorm = new scorm();
$manifest = $oScorm->import_package(
$_FILES['user_file'],
'',
api_get_course_info(),
true,
$lp
);
if ($manifest) {
Display::addFlash(Display::return_message(get_lang('Updated')));
}
header('Location: '.api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?'.api_get_cidreq());
exit;
}
$content = $form->returnForm();
$tpl = new Template(null);
$tpl->assign('content', $content);
$tpl->display_one_col_template();

229
main/lp/lp_upload.php Normal file
View File

@@ -0,0 +1,229 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Component\CourseCopy\CourseArchiver;
use Chamilo\CourseBundle\Component\CourseCopy\CourseRestorer;
/**
* Script managing the learnpath upload. To best treat the uploaded file, make sure we can identify it.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
$course_dir = api_get_course_path().'/scorm';
$course_sys_dir = api_get_path(SYS_COURSE_PATH).$course_dir;
if (empty($_POST['current_dir'])) {
$current_dir = '';
} else {
$current_dir = api_replace_dangerous_char(trim($_POST['current_dir']));
}
$uncompress = 1;
$allowHtaccess = false;
if (api_get_configuration_value('allow_htaccess_import_from_scorm') && isset($_POST['allow_htaccess'])) {
$allowHtaccess = true;
}
/*
* Check the request method in place of a variable from POST
* because if the file size exceed the maximum file upload
* size set in php.ini, all variables from POST are cleared !
*/
$user_file = isset($_GET['user_file']) ? $_GET['user_file'] : [];
$user_file = $user_file ? $user_file : [];
$is_error = isset($user_file['error']) ? $user_file['error'] : false;
if (isset($_POST) && $is_error) {
Display::addFlash(
Display::return_message(get_lang('UplFileTooBig'))
);
return false;
unset($_FILES['user_file']);
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST' && count($_FILES) > 0 && !empty($_FILES['user_file']['name'])) {
// A file upload has been detected, now deal with the file...
// Directory creation.
$stopping_error = false;
$s = $_FILES['user_file']['name'];
// Get name of the zip file without the extension.
$info = pathinfo($s);
$filename = $info['basename'];
$extension = $info['extension'];
$file_base_name = str_replace('.'.$extension, '', $filename);
$new_dir = api_replace_dangerous_char(trim($file_base_name));
$type = learnpath::getPackageType($_FILES['user_file']['tmp_name'], $_FILES['user_file']['name']);
$proximity = 'local';
if (!empty($_REQUEST['content_proximity'])) {
$proximity = Database::escape_string($_REQUEST['content_proximity']);
}
$maker = 'Scorm';
if (!empty($_REQUEST['content_maker'])) {
$maker = Database::escape_string($_REQUEST['content_maker']);
}
switch ($type) {
case 'chamilo':
$filename = CourseArchiver::importUploadedFile($_FILES['user_file']['tmp_name']);
if ($filename) {
$course = CourseArchiver::readCourse($filename, false);
$courseRestorer = new CourseRestorer($course);
// FILE_SKIP, FILE_RENAME or FILE_OVERWRITE
$courseRestorer->set_file_option(FILE_OVERWRITE);
$courseRestorer->restore('', api_get_session_id());
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
}
break;
case 'scorm':
$oScorm = new scorm();
$manifest = $oScorm->import_package(
$_FILES['user_file'],
$current_dir,
[],
false,
null,
$allowHtaccess
);
if (!empty($manifest)) {
$oScorm->parse_manifest($manifest);
$oScorm->import_manifest(api_get_course_id(), $_REQUEST['use_max_score']);
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
}
$oScorm->set_proximity($proximity);
$oScorm->set_maker($maker);
$oScorm->set_jslib('scorm_api.php');
break;
case 'aicc':
$oAICC = new aicc();
$config_dir = $oAICC->import_package($_FILES['user_file']);
if (!empty($config_dir)) {
$oAICC->parse_config_files($config_dir);
$oAICC->import_aicc(api_get_course_id());
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
}
$oAICC->set_proximity($proximity);
$oAICC->set_maker($maker);
$oAICC->set_jslib('aicc_api.php');
break;
case 'oogie':
require_once 'openoffice_presentation.class.php';
$take_slide_name = empty($_POST['take_slide_name']) ? false : true;
$o_ppt = new OpenofficePresentation($take_slide_name);
$first_item_id = $o_ppt->convert_document($_FILES['user_file'], 'make_lp', $_POST['slide_size']);
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
break;
case 'woogie':
require_once 'openoffice_text.class.php';
$split_steps = (empty($_POST['split_steps']) || $_POST['split_steps'] == 'per_page') ? 'per_page' : 'per_chapter';
$o_doc = new OpenofficeText($split_steps);
$first_item_id = $o_doc->convert_document($_FILES['user_file']);
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
break;
case '':
default:
Display::addFlash(Display::return_message(get_lang('ScormUnknownPackageFormat'), 'warning'));
return false;
break;
}
} elseif ($_SERVER['REQUEST_METHOD'] == 'POST' || ('bigUpload' === $_REQUEST['from'] && !empty($_REQUEST['name']))) {
// end if is_uploaded_file
// If file name given to get in /upload/, try importing this way.
// A file upload has been detected, now deal with the file...
// Directory creation.
$stopping_error = false;
// When it is used from bigupload input
if ('bigUpload' === $_REQUEST['from']) {
if (empty($_REQUEST['name'])) {
return false;
}
$tempName = $_REQUEST['name'];
} else {
if (!isset($_POST['file_name'])) {
return false;
}
$tempName = $_POST['file_name'];
}
// Escape path with basename so it can only be directly into the archive/ directory.
$s = api_get_path(SYS_ARCHIVE_PATH).basename($tempName);
// Get name of the zip file without the extension
$info = pathinfo($s);
$filename = $info['basename'];
$extension = $info['extension'];
$file_base_name = str_replace('.'.$extension, '', $filename);
$new_dir = api_replace_dangerous_char(trim($file_base_name));
$result = learnpath::verify_document_size($s);
if ($result) {
Display::addFlash(
Display::return_message(get_lang('UplFileTooBig'))
);
}
$type = learnpath::getPackageType($s, basename($s));
switch ($type) {
case 'scorm':
$oScorm = new scorm();
$manifest = $oScorm->import_local_package($s, $current_dir);
// The file was treated, it can now be cleaned from the temp dir
unlink($s);
if (!empty($manifest)) {
$oScorm->parse_manifest($manifest);
$oScorm->import_manifest(api_get_course_id(), $_REQUEST['use_max_score']);
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
}
$proximity = '';
if (!empty($_REQUEST['content_proximity'])) {
$proximity = Database::escape_string($_REQUEST['content_proximity']);
}
$maker = '';
if (!empty($_REQUEST['content_maker'])) {
$maker = Database::escape_string($_REQUEST['content_maker']);
}
$oScorm->set_proximity($proximity);
$oScorm->set_maker($maker);
$oScorm->set_jslib('scorm_api.php');
break;
case 'aicc':
$oAICC = new aicc();
$config_dir = $oAICC->import_local_package($s, $current_dir);
// The file was treated, it can now be cleaned from the temp dir
unlink($s);
if (!empty($config_dir)) {
$oAICC->parse_config_files($config_dir);
$oAICC->import_aicc(api_get_course_id());
Display::addFlash(Display::return_message(get_lang('UplUploadSucceeded')));
}
$proximity = '';
if (!empty($_REQUEST['content_proximity'])) {
$proximity = Database::escape_string($_REQUEST['content_proximity']);
}
$maker = '';
if (!empty($_REQUEST['content_maker'])) {
$maker = Database::escape_string($_REQUEST['content_maker']);
}
$oAICC->set_proximity($proximity);
$oAICC->set_maker($maker);
$oAICC->set_jslib('aicc_api.php');
break;
case '':
default:
// There was an error, clean the file from the temp dir
if (is_file($s)) {
unlink($s);
}
Display::addFlash(
Display::return_message(get_lang('ScormUnknownPackageFormat'), 'warning')
);
return false;
break;
}
}

62
main/lp/lp_view.lib.js Normal file
View File

@@ -0,0 +1,62 @@
/***********************************************
* IFrame SSI script II- Dynamic Drive DHTML code library (http://www.dynamicdrive.com)
* Visit DynamicDrive.com for hundreds of original DHTML scripts
* This notice must stay intact for legal use
***********************************************/
//Input the IDs of the IFRAMES you wish to dynamically resize to match its content height:
//Separate each ID with a comma. Examples: ["myframe1", "myframe2"] or ["myframe"] or [] for none:
var iframeid = "lp_content_frame";
//Should script hide iframe from browsers that don't support this script (non IE5+/NS6+ browsers. Recommended):
var iframehide = "no";
//var getFFVersion=navigator.userAgent.substring(navigator.userAgent.indexOf("Firefox")).split("/")[1];
var FFextraHeight = 32;
//parseFloat(getFFVersion)>=0.1? 16 : 0; //extra height in px to add to iframe in FireFox 1.0+ browsers
function resizeIframe(frameid) {
var currentfr=document.getElementById(frameid);
if (currentfr && !window.opera){
currentfr.style.display="block";
if (currentfr.contentDocument && currentfr.contentDocument.body.offsetHeight){ //ns6 syntax
currentfr.height = currentfr.contentDocument.body.offsetHeight+FFextraHeight;
}
else if (currentfr.Document && currentfr.Document.body.scrollHeight) //ie5+ syntax
currentfr.height = currentfr.Document.body.scrollHeight;
if(currentfr.height < 580){
currentfr.height = 580;
}
if (currentfr.addEventListener)
currentfr.addEventListener("load", readjustIframe, false);
else if (currentfr.attachEvent){
currentfr.detachEvent("onload", readjustIframe); // Bug fix line
currentfr.attachEvent("onload", readjustIframe);
}
}
}
function resizeCaller() {
if (document.getElementById)
resizeIframe(iframeid);
//reveal iframe for lower end browsers? (see var above):
if ((document.all || document.getElementById) && iframehide=="no"){
var tempobj=document.all? document.all[iframeid] : document.getElementById(iframeid);
tempobj.style.display="block";
}
}
function readjustIframe(loadevt) {
var crossevt=(window.event)? event : loadevt;
var iframeroot=(crossevt.currentTarget)? crossevt.currentTarget : crossevt.srcElement;
if (iframeroot)
resizeIframe(iframeroot.id);
}
if (window.addEventListener)
window.addEventListener("load", resizeCaller, false);
else if (window.attachEvent)
window.attachEvent("onload", resizeCaller);
else
window.onload = resizeCaller;

690
main/lp/lp_view.php Normal file
View File

@@ -0,0 +1,690 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CLpCategory;
use ChamiloSession as Session;
/**
* This file was originally the copy of document.php, but many modifications happened since then ;
* the direct file view is not needed anymore, if the user uploads a scorm zip file, a directory
* will be automatically created for it, and the files will be uncompressed there for example ;.
*
* @author Yannick Warnier <ywarnier@beeznest.org> - redesign
* @author Denes Nagy, principal author
* @author Isthvan Mandak, several new features
* @author Roan Embrechts, code improvements and refactoring
*/
$use_anonymous = true;
$this_section = SECTION_COURSES;
if ($lp_controller_touched != 1) {
header('Location: lp_controller.php?action=view&item_id='.intval($_REQUEST['item_id']));
exit;
}
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
if (isset($_REQUEST['origin']) && $_REQUEST['origin'] === 'learnpath') {
$_REQUEST['origin'] = '';
}
// To prevent the template class
$lp_id = !empty($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
$sessionId = api_get_session_id();
$course_code = api_get_course_id();
$course_id = api_get_course_int_id();
$user_id = api_get_user_id();
// Check if the learning path is visible for student - (LP requisites)
if (!api_is_platform_admin()) {
if (!api_is_allowed_to_edit(null, true, false, false) &&
!learnpath::is_lp_visible_for_student($lp_id, api_get_user_id())
) {
api_not_allowed(true);
}
}
// Checking visibility (eye icon)
$visibility = api_get_item_visibility(
api_get_course_info(),
TOOL_LEARNPATH,
$lp_id,
$action,
api_get_user_id(),
$sessionId
);
if ($visibility === 0 &&
!api_is_allowed_to_edit(false, true, false, false)
) {
api_not_allowed(true);
}
/** @var learnpath $lp */
$lp = Session::read('oLP');
if (empty($lp)) {
api_not_allowed(true);
}
$debug = 0;
if ($debug) {
error_log('------ Entering lp_view.php -------');
}
$lp_item_id = $lp->get_current_item_id();
$lpType = $lp->get_type();
if (!$is_allowed_to_edit) {
$categoryId = $lp->getCategoryId();
$em = Database::getManager();
if (!empty($categoryId)) {
/** @var CLpCategory $category */
$category = $em->getRepository('ChamiloCourseBundle:CLpCategory')->find($categoryId);
$block = false;
if ($category) {
$user = UserManager::getRepository()->find($user_id);
$users = $category->getUsers();
if (!empty($users) && $users->count() > 0) {
if ($user && !$category->hasUserAdded($user)) {
$block = true;
}
}
$isVisible = learnpath::categoryIsVisibleForStudent(
$category,
$user
);
if ($isVisible) {
$block = false;
}
if ($block) {
api_not_allowed(true);
}
}
}
}
$platform_theme = api_get_setting('stylesheets');
$my_style = $platform_theme;
$extraParams = '';
if (isset($_REQUEST['lti_launch_id'])) {
$extraParams .= '&lti_launch_id='.Security::remove_XSS($_REQUEST['lti_launch_id']);
}
$ajaxUrl = api_get_path(WEB_AJAX_PATH).'lp.ajax.php?a=get_item_prerequisites&'.api_get_cidreq().$extraParams;
$htmlHeadXtra[] = '<script>
<!--
var jQueryFrameReadyConfigPath = \''.api_get_jquery_web_path().'\';
-->
</script>';
$htmlHeadXtra[] = api_get_css_asset('qtip2/jquery.qtip.min.css');
$htmlHeadXtra[] = api_get_asset('qtip2/jquery.qtip.min.js');
$htmlHeadXtra[] = '<script type="text/javascript" src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.frameready.js"></script>';
$htmlHeadXtra[] = '<script>
$(function() {
$("div#log_content_cleaner").bind("click", function() {
$("div#log_content").empty();
});
});
var chamilo_xajax_handler = window.oxajax;
</script>';
if (!empty($lp->lti_launch_id)) {
if (isset($_REQUEST['from']) && 'lti_provider' == $_REQUEST['from']) {
$logout = api_get_path(WEB_PATH).'plugin/lti_provider/tool/logout.php?uid='.api_get_user_id();
$htmlHeadXtra[] = '<script>
$(function() {
if ($("#btn-menu-float").length > 0) {
$("#btn-menu-float").find("#home-course").show();
$("#btn-menu-float").find("#home-course").attr("href", "'.$logout.'");
}
});
</script>';
} else {
$htmlHeadXtra[] = '<script>
$(function() {
if ($("#btn-menu-float").length > 0) {
$("#btn-menu-float").find("#home-course").hide();
}
});
</script>';
}
}
$zoomOptions = api_get_configuration_value('quiz_image_zoom');
if (isset($zoomOptions['options']) && !in_array($origin, ['embeddable', 'noheader'])) {
$options = $zoomOptions['options'];
$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_JS_PATH).'jquery.elevatezoom.js"></script>';
$htmlHeadXtra[] = '<script>
$(function() {
$("img").each(function() {
var attr = $(this).attr("data-zoom-image");
// For some browsers, `attr` is undefined; for others,
// `attr` is false. Check for both.
if (typeof attr !== typeof undefined && attr !== false) {
$(this).elevateZoom({
scrollZoom : true,
cursor: "crosshair",
tint:true,
tintColour:\'#CCC\',
tintOpacity:0.5,
zoomWindowWidth:'.$options['zoomWindowWidth'].',
zoomWindowHeight:'.$options['zoomWindowHeight'].'
});
}
});
});
</script>';
}
$allowLpItemTip = api_get_configuration_value('hide_accessibility_label_on_lp_item') === false;
if ($allowLpItemTip) {
$htmlHeadXtra[] = '<script>
$(function() {
$(".scorm_item_normal").qtip({
content: {
text: function(event, api) {
var item = $(this);
var itemId = $(this).attr("id");
itemId = itemId.replace("toc_", "");
var textToShow = "";
$.ajax({
type: "GET",
url: "'.$ajaxUrl.'&item_id="+ itemId,
async: false
})
.then(function(content) {
if (content == 1) {
textToShow = "'.addslashes(get_lang('LPItemCanBeAccessed')).'";
api.set("style.classes", "qtip-green qtip-shadow");
} else {
textToShow = content;
api.set("style.classes", "qtip-red qtip-shadow");
}
return textToShow;
});
return textToShow;
}
}
});
});
</script>';
}
// Impress js
if ($lp->mode === 'impress') {
$lp_id = $lp->get_id();
$url = api_get_path(WEB_CODE_PATH)."lp/lp_impress.php?lp_id=$lp_id&".api_get_cidreq();
header("Location: $url");
exit;
}
// Prepare variables for the test tool (just in case) - honestly, this should disappear later on.
Session::write('scorm_view_id', $lp->get_view_id());
Session::write('scorm_item_id', $lp_item_id);
// Reinit exercises variables to avoid spacename clashes (see exercise tool)
if (isset($exerciseResult) || isset($_SESSION['exerciseResult'])) {
Session::erase('exerciseResult');
Session::erase('objExercise');
Session::erase('questionList');
Session::erase('duration_time_previous');
Session::erase('duration_time');
}
// additional APIs
$htmlHeadXtra[] = '<script>
chamilo_courseCode = "'.$course_code.'";
</script>';
// Document API
$htmlHeadXtra[] = '<script src="js/documentapi.js" type="text/javascript" language="javascript"></script>';
// Storage API
$htmlHeadXtra[] = '<script>
var sv_user = \''.api_get_user_id().'\';
var sv_course = chamilo_courseCode;
var sv_sco = \''.$lp_id.'\';
</script>'; // FIXME fetch sco and userid from a more reliable source directly in storageapi.js
$htmlHeadXtra[] = '<script type="text/javascript" src="js/storageapi.js"></script>';
/**
* Get a link to the corresponding document.
*/
if ($debug) {
error_log(" src: $src ");
error_log(" lp_type: $lpType ");
}
$get_toc_list = $lp->get_toc();
$get_teacher_buttons = $lp->get_teacher_toc_buttons();
$flowButtons = $lp->getFlowLpbuttons();
$itemType = '';
foreach ($get_toc_list as $toc) {
if ($toc['id'] == $lp_item_id) {
$itemType = $toc['type'];
}
}
if (!isset($src)) {
$src = null;
switch ($lpType) {
case 1:
$lp->stop_previous_item();
$htmlHeadXtra[] = '<script src="scorm_api.php" type="text/javascript" language="javascript"></script>';
$preReqCheck = $lp->prerequisites_match($lp_item_id);
if ($preReqCheck === true) {
$src = $lp->get_link(
'http',
$lp_item_id,
$get_toc_list
);
if (empty($src)) {
$src = 'blank.php?'.api_get_cidreq().'&error=document_protected';
break;
}
// Prevents FF 3.6 + Adobe Reader 9 bug see BT#794 when calling a pdf file in a LP.
$file_info = parse_url($src);
if (isset($file_info['path'])) {
$file_info = pathinfo($file_info['path']);
}
if (isset($file_info['extension']) &&
api_strtolower(substr($file_info['extension'], 0, 3)) == 'pdf'
) {
$src = api_get_path(WEB_CODE_PATH).'lp/lp_view_item.php?lp_item_id='.$lp_item_id.'&'.api_get_cidreq();
}
$src = $lp->fixBlockedLinks($src);
if (WhispeakAuthPlugin::isLpItemMarked($lp_item_id)) {
ChamiloSession::write(
WhispeakAuthPlugin::SESSION_LP_ITEM,
['lp' => $lp->lp_id, 'lp_item' => $lp_item_id, 'src' => $src]
);
$src = api_get_path(WEB_PLUGIN_PATH).'whispeakauth/authentify.php';
break;
}
// This change the status to complete in Chamilo LP.
$lp->start_current_item(); // starts time counter manually if asset
} else {
$src = 'blank.php?error=prerequisites';
}
break;
case 2:
// save old if asset
$lp->stop_previous_item(); // save status manually if asset
$htmlHeadXtra[] = '<script src="scorm_api.php" type="text/javascript" language="javascript"></script>';
$preReqCheck = $lp->prerequisites_match($lp_item_id);
if ($preReqCheck === true) {
$src = $lp->get_link('http', $lp_item_id, $get_toc_list);
$lp->start_current_item(); // starts time counter manually if asset
} else {
$src = 'blank.php?error=prerequisites';
}
break;
case 3:
// aicc
$lp->stop_previous_item(); // save status manually if asset
$htmlHeadXtra[] = '<script src="'.$lp->get_js_lib().'" type="text/javascript" language="javascript"></script>';
$preReqCheck = $lp->prerequisites_match($lp_item_id);
if ($preReqCheck === true) {
$src = $lp->get_link(
'http',
$lp_item_id,
$get_toc_list
);
$lp->start_current_item(); // starts time counter manually if asset
} else {
$src = 'blank.php';
}
break;
case 4:
break;
}
}
$autostart = 'true';
// Update status, total_time from lp_item_view table when you finish the exercises in learning path.
if ($debug) {
error_log('$_REQUEST[exeId]: '.intval($_REQUEST['exeId']));
error_log('$lp_id: '.$lp_id);
error_log('$_REQUEST[lp_item_id]: '.intval($_REQUEST['lp_item_id']));
}
if (!empty($_REQUEST['exeId']) &&
isset($lp_id) &&
isset($_REQUEST['lp_item_id'])
) {
global $src;
$lp->items[$lp->current]->write_to_db();
$TBL_TRACK_EXERCICES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
$TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
$TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM);
$safe_item_id = (int) $_REQUEST['lp_item_id'];
$safe_id = $lp_id;
$safe_exe_id = (int) $_REQUEST['exeId'];
if (!empty($safe_id) && !empty($safe_item_id)) {
Exercise::saveExerciseInLp($safe_item_id, $safe_exe_id);
}
/*if (intval($_GET['fb_type']) != EXERCISE_FEEDBACK_TYPE_END) {
$src = 'blank.php?msg=exerciseFinished&'.api_get_cidreq(true, true, 'learnpath');
} else {*/
$src = api_get_path(WEB_CODE_PATH).'exercise/result.php?id='.$safe_exe_id.'&'.api_get_cidreq(true, true, 'learnpath');
if ($debug) {
error_log('Calling URL: '.$src);
}
$autostart = 'false';
}
$lp->set_previous_item($lp_item_id);
$nameTools = Security::remove_XSS($lp->get_name());
$save_setting = api_get_setting('show_navigation_menu');
global $_setting;
$_setting['show_navigation_menu'] = 'false';
$scorm_css_header = true;
$lp_theme_css = $lp->get_theme();
// Sets the css theme of the LP this call is also use at the frames (toc, nav, message).
if ($lp->mode === 'fullscreen') {
$htmlHeadXtra[] = "<script>
window.open('$src','content_id','toolbar=0,location=0,status=0,scrollbars=1,resizable=1');
</script>";
}
// Set flag to ensure lp_header.php is loaded by this script (flag is unset in lp_header.php).
Session::write('loaded_lp_view', true);
$display_none = '';
$margin_left = '340px';
// Media player code
$display_mode = $lp->mode;
$scorm_css_header = true;
$lp_theme_css = $lp->get_theme();
// Setting up the CSS theme if exists.
if (!empty($lp_theme_css) && !empty($mycourselptheme) && $mycourselptheme != -1 && $mycourselptheme == 1) {
global $lp_theme_css;
} else {
$lp_theme_css = $my_style;
}
$progress_bar = '';
if (!api_is_invitee()) {
$progress_bar = $lp->getProgressBar();
}
$navigation_bar = $lp->get_navigation_bar();
$navigation_bar_bottom = $lp->get_navigation_bar('control-bottom');
$mediaplayer = $lp->get_mediaplayer($lp->current, $autostart);
$tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
// Getting all the information about the item.
$sql = "SELECT audio FROM $tbl_lp_item
WHERE c_id = $course_id AND lp_id = ".$lp->lp_id;
$res_media = Database::query($sql);
$show_audioplayer = false;
if (Database::num_rows($res_media) > 0) {
while ($row_media = Database::fetch_array($res_media)) {
if (!empty($row_media['audio'])) {
$show_audioplayer = true;
break;
}
}
}
$is_allowed_to_edit = api_is_allowed_to_edit(false, true, true, false);
global $interbreadcrumb;
if ($is_allowed_to_edit) {
$interbreadcrumb[] = [
'url' => api_get_self().'?action=list&isStudentView=false&'.api_get_cidreq(true, true, 'course'),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id={$lp->lp_id}&isStudentView=false&".api_get_cidreq(true, true, 'course'),
'name' => $lp->getNameNoTags(),
];
$interbreadcrumb[] = [
'url' => '#',
'name' => get_lang('Preview'),
];
$buttonHomeUrl = 'lp_controller.php?'.api_get_cidreq(true, true, 'course').'&'.http_build_query([
'isStudentView' => 'false',
'action' => 'return_to_course_homepage',
]);
} else {
$buttonHomeUrl = 'lp_controller.php?'.api_get_cidreq(true, true, 'course').'&'.http_build_query([
'action' => 'return_to_course_homepage',
]);
}
$buttonHomeText = get_lang('CourseHomepageLink');
$returnLink = api_get_course_setting('lp_return_link');
switch ($returnLink) {
case 1: // lp list
$buttonHomeUrl .= '&redirectTo=lp_list';
$buttonHomeText = get_lang('LearningPathList');
break;
case 2: // Course home
$buttonHomeUrl .= '&redirectTo=my_courses';
$buttonHomeText = get_lang('MyCourses');
break;
case 3: // Portal home
$buttonHomeUrl .= '&redirectTo=portal_home';
$buttonHomeText = get_lang('Home');
break;
}
$lpPreviewImagePath = Display::returnIconPath('unknown.png', ICON_SIZE_BIG);
if ($lp->get_preview_image()) {
$lpPreviewImagePath = $lp->get_preview_image_path();
}
if ($lp->current == $lp->get_last()) {
$categories = Category::load(
null,
null,
$course_code,
null,
null,
$sessionId
);
if (!empty($categories)) {
$gradebookEvaluations = $categories[0]->get_evaluations();
$gradebookLinks = $categories[0]->get_links();
if (count($gradebookEvaluations) === 0 &&
count($gradebookLinks) === 1 &&
$gradebookLinks[0]->get_type() == LINK_LEARNPATH &&
$gradebookLinks[0]->get_ref_id() == $lp->lp_id
) {
$gradebookMinScore = $categories[0]->getCertificateMinScore();
$userScore = $gradebookLinks[0]->calc_score($user_id, 'best');
if ($userScore[0] >= $gradebookMinScore) {
Category::generateUserCertificate($categories[0]->get_id(), $user_id);
}
}
}
}
$template = new Template('', false, false, true, true, false);
$fixLinkSetting = api_get_configuration_value('lp_fix_embed_content');
$fixLink = '';
if ($fixLinkSetting) {
$fixLink = '{type:"script", id:"_fr10", src:"'.api_get_path(WEB_LIBRARY_PATH).'fixlinks.js"}';
}
$template->assign('fix_link', $fixLink);
$template->assign('glossary_tool_available_list', ['true', 'lp', 'exercise_and_lp']);
// If the global gamification mode is enabled...
$gamificationMode = api_get_setting('gamification_mode');
// ...AND this learning path is set in gamification mode, then change the display
$gamificationMode = $gamificationMode && $lp->seriousgame_mode;
$template->assign('gamification_mode', $gamificationMode);
$template->assign('glossary_extra_tools', api_get_setting('show_glossary_in_extra_tools'));
$template->assign('show_glossary_in_documents', api_get_setting('show_glossary_in_documents'));
$template->assign('jquery_web_path', api_get_jquery_web_path());
$template->assign('jquery_ui_js_web_path', api_get_jquery_ui_js_web_path());
$template->assign('jquery_ui_css_web_path', api_get_jquery_ui_css_web_path());
$template->assign('is_allowed_to_edit', $is_allowed_to_edit);
$template->assign('button_home_url', $buttonHomeUrl);
$template->assign('button_home_text', $buttonHomeText);
$template->assign('navigation_bar', $navigation_bar);
$template->assign('progress_bar', $progress_bar);
$template->assign('show_audio_player', $show_audioplayer);
$template->assign('media_player', $mediaplayer);
$template->assign('toc_list', $get_toc_list);
$template->assign('teacher_toc_buttons', $get_teacher_buttons);
$template->assign('flow_buttons', $flowButtons);
$template->assign('iframe_src', $src);
$template->assign('navigation_bar_bottom', $navigation_bar_bottom);
$template->assign('show_left_column', $lp->getHideTableOfContents() == 0);
$showMenu = 0;
$settings = api_get_configuration_value('lp_view_settings');
$display = isset($settings['display']) ? $settings['display'] : false;
$navigationInTheMiddle = false;
$addExtraQuitToHomeIcon = false;
if (!empty($display)) {
$showMenu = isset($display['show_toolbar_by_default']) && $display['show_toolbar_by_default'] ? 1 : 0;
$navigationInTheMiddle = isset($display['navigation_in_the_middle']) && $display['navigation_in_the_middle'] ? 1 : 0;
$addExtraQuitToHomeIcon = $display['add_extra_quit_to_home_icon'] ?? false;
$value = (new ExtraFieldValue('lp'))->get_values_by_handler_and_field_variable($lp_id, 'add_extra_quit_button');
if (is_array($value)) {
$addExtraQuitToHomeIcon = $value['value'] !== '0';
}
}
$template->assign('show_toolbar_by_default', $showMenu);
$template->assign('navigation_in_the_middle', $navigationInTheMiddle);
$template->assign('add_extra_quit_to_home_icon', $addExtraQuitToHomeIcon);
if ($gamificationMode == 1) {
$template->assign('gamification_stars', $lp->getCalculateStars($sessionId));
$template->assign('gamification_points', $lp->getCalculateScore($sessionId));
}
$template->assign('lp_author', $lp->get_author());
$lpMinTime = '';
if (Tracking::minimumTimeAvailable(api_get_session_id(), api_get_course_int_id())) {
// Calulate minimum and accumulated time
$timeLp = $_SESSION['oLP']->getAccumulateWorkTime();
$timeTotalCourse = $_SESSION['oLP']->getAccumulateWorkTimeTotalCourse();
// Minimum connection percentage
$perc = 100;
// Time from the course
$tc = $timeTotalCourse;
// Percentage of the learning paths
$pl = 0;
if (!empty($timeTotalCourse)) {
$pl = $timeLp / $timeTotalCourse;
}
// Minimum time for each learning path
$time_min = (int) ($pl * $tc * $perc / 100);
if ($_SESSION['oLP']->getAccumulateWorkTime() > 0) {
$lpMinTime = '('.$time_min.' min)';
}
$lpTimeList = Tracking::getCalculateTime($user_id, api_get_course_int_id(), api_get_session_id());
$lpTime = isset($lpTimeList[TOOL_LEARNPATH][$lp_id]) ? (int) $lpTimeList[TOOL_LEARNPATH][$lp_id] : 0;
if ($lpTime >= ($time_min * 60)) {
$time_progress_perc = '100%';
$time_progress_value = 100;
} else {
$time_progress_value = intval(($lpTime * 100) / ($time_min * 60));
$time_progress_perc = $time_progress_value.'%';
}
$template->assign('time_progress_perc', $time_progress_perc);
$template->assign('time_progress_value', $time_progress_value);
// Cronometro
$hour = (intval($lpTime / 3600)) < 10 ? '0'.intval($lpTime / 3600) : intval($lpTime / 3600);
$template->assign('hour', $hour);
$template->assign('minute', date('i', $lpTime));
$template->assign('second', date('s', $lpTime));
$template->assign('hour_min', api_time_to_hms($timeLp * 60, '</div><div class="divider">:</div><div>'));
}
$template->assign('lp_accumulate_work_time', $lpMinTime);
$template->assign('lp_mode', $lp->mode);
$template->assign('lp_title_scorm', $lp->get_name());
if (api_get_configuration_value('lp_view_accordion') === true && $lpType == 1) {
$template->assign('data_panel', $lp->getTOCTree());
} else {
$template->assign('data_list', $lp->getListArrayToc($get_toc_list));
}
$template->assign('lp_id', $lp->lp_id);
$template->assign('lp_current_item_id', $lp->get_current_item_id());
$menuLocation = 'left';
if (!empty(api_get_configuration_value('lp_menu_location'))) {
$menuLocation = api_get_configuration_value('lp_menu_location');
}
$template->assign('menu_location', $menuLocation);
$template->assign('disable_js_in_lp_view', (int) api_get_configuration_value('disable_js_in_lp_view'));
$template->assign(
'lp_preview_image',
Display::img(
$lpPreviewImagePath,
$lp->getNameNoTags(),
[],
ICON_SIZE_BIG
)
);
// Check if the 'Open in new window' button for IOs hosts must be hidden
$iosHideOpenInNewWindow = false;
if (api_get_configuration_value('lp_ios_hide_open_in_new_window_button') === true) {
$iosHideOpenInNewWindow = api_get_configuration_value('lp_ios_hide_open_in_new_window_button');
}
$template->assign('ios_hide_open_in_new_window', $iosHideOpenInNewWindow);
$frameReady = Display::getFrameReadyBlock(
'#content_id, #content_id_blank',
$itemType,
'function () {
var arr = ["link", "sco", "xapi", "quiz", "h5p"];
return $.inArray(olms.lms_item_type, arr) !== -1;
}'
);
$template->assign('frame_ready', $frameReady);
$view = $template->get_template('learnpath/view.tpl');
$content = $template->fetch($view);
$template->assign('content', $content);
$template->display_no_layout_template();
// Restore a global setting.
$_setting['show_navigation_menu'] = $save_setting;
Session::write('oLP', $lp);
if ($debug) {
error_log(' ------- end lp_view.php ------');
}

134
main/lp/lp_view_item.php Normal file
View File

@@ -0,0 +1,134 @@
<?php
/* For licensing terms, see /license.txt */
use ChamiloSession as Session;
/**
* This is a learning path creation and player tool in Chamilo - previously learnpath_handler.php.
*
* @author Patrick Cool
* @author Denes Nagy
* @author Roan Embrechts, refactoring and code cleaning
* @author Yannick Warnier <ywarnier@beeznest.org> - cleaning and update for new SCORM tool
*/
// Prevents FF 3.6 + Adobe Reader 9 bug see BT#794 when calling a pdf file in a LP
require_once __DIR__.'/../inc/global.inc.php';
api_protect_course_script();
/** @var learnpath $lp */
$lp = Session::read('oLP');
if (isset($_GET['lp_item_id'])) {
// Get parameter only came from lp_view.php.
$lp_item_id = (int) $_GET['lp_item_id'];
if (is_object($lp)) {
$src = $lp->get_link('http', $lp_item_id);
}
$url_info = parse_url($src);
$real_url_info = parse_url(api_get_path(WEB_PATH));
// The host must be the same.
if ($url_info['host'] == $real_url_info['host']) {
$url = Security::remove_XSS($src);
header("Location: ".$url);
exit;
} else {
header("Location: blank.php?error=document_not_found");
exit;
}
}
if (empty($lp)) {
api_not_allowed();
}
$mode = isset($_REQUEST['mode']) ? $_REQUEST['mode'] : 'fullpage';
if (isset($_SESSION['oLP']) && isset($_GET['id'])) {
$_SESSION['oLP']->current = (int) $_GET['id'];
}
$this_section = SECTION_COURSES;
/* Header and action code */
/* Constants and variables */
$is_allowed_to_edit = api_is_allowed_to_edit(null, true);
$isStudentView = (empty($_REQUEST['isStudentView']) ? 0 : (int) $_REQUEST['isStudentView']);
$learnpath_id = (int) $_REQUEST['lp_id'];
if (!$is_allowed_to_edit || $isStudentView) {
header('Location: '.api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
exit;
}
// From here on, we are admin because of the previous condition, so don't check anymore.
$course_id = api_get_course_int_id();
/* SHOWING THE ADMIN TOOLS */
if (api_is_in_gradebook()) {
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'gradebook/index.php?'.api_get_cidreq(),
'name' => get_lang('ToolGradebook'),
];
}
$interbreadcrumb[] = [
'url' => api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=list&'.api_get_cidreq(),
'name' => get_lang('LearningPaths'),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=build&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => $lp->getNameNoTags(),
];
$interbreadcrumb[] = [
'url' => api_get_self()."?action=add_item&type=step&lp_id=$learnpath_id&".api_get_cidreq(),
'name' => get_lang('NewStep'),
];
// Theme calls
$show_learn_path = true;
$lp_theme_css = $lp->get_theme();
if ('fullpage' === $mode) {
Display::display_header(get_lang('Item'), 'Path');
}
$suredel = trim(get_lang('AreYouSureToDeleteJS'));
?>
<script>
function stripslashes(str) {
str=str.replace(/\\'/g,'\'');
str=str.replace(/\\"/g,'"');
str=str.replace(/\\\\/g,'\\');
str=str.replace(/\\0/g,'\0');
return str;
}
function confirmation(name) {
name=stripslashes(name);
if (confirm("<?php echo $suredel; ?> " + name + " ?")) {
return true;
} else {
return false;
}
}
</script>
<?php
$id = isset($new_item_id) ? $new_item_id : $_GET['id'];
if (is_object($lp)) {
switch ($mode) {
case 'fullpage':
echo $lp->build_action_menu();
echo '<div class="row">';
echo '<div class="col-md-3">';
echo $lp->return_new_tree();
echo '</div>';
echo '<div class="col-md-9">';
echo $lp->display_item($id);
echo '</div>';
echo '</div>';
Display::display_footer();
break;
case 'preview_document':
echo $lp->display_item($id, null, false);
break;
}
}

98
main/lp/my_list.php Normal file
View File

@@ -0,0 +1,98 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CLp;
$cidReset = true;
require_once __DIR__.'/../inc/global.inc.php';
if (api_get_configuration_value('disable_my_lps_page')) {
api_not_allowed(true);
}
api_block_anonymous_users();
$userId = api_get_user_id();
$courses = CourseManager::get_courses_list_by_user_id($userId, true);
$lps = [];
if (!empty($courses)) {
$courseIdList = array_column($courses, 'real_id');
$courseWithSession = [];
$courseIteration = 0;
foreach ($courses as $course) {
if (isset($course['session_id'])) {
$sessionVisibility = api_get_session_visibility($course['session_id']);
if (SESSION_VISIBLE === $sessionVisibility || SESSION_AVAILABLE === $sessionVisibility) {
$courseWithSession[$course['real_id']] = $course['session_id'];
} else {
unset($courseIdList[$courseIteration]);
}
$courseIteration++;
}
}
$courseCondition = " AND lp.cId IN ('".implode("', '", $courseIdList)."') ";
$order = ' ORDER BY lp.createdOn ASC, lp.name ASC';
$now = api_get_utc_datetime();
$conditions = " (
(lp.publicatedOn IS NOT NULL AND lp.publicatedOn < '$now' AND lp.expiredOn IS NOT NULL AND lp.expiredOn > '$now') OR
(lp.publicatedOn IS NOT NULL AND lp.publicatedOn < '$now' AND lp.expiredOn IS NULL) OR
(lp.publicatedOn IS NULL AND lp.expiredOn IS NOT NULL AND lp.expiredOn > '$now') OR
(lp.publicatedOn IS NULL AND lp.expiredOn IS NULL )
)
";
$dql = "SELECT lp FROM ChamiloCourseBundle:CLp as lp
WHERE
$conditions
$courseCondition
ORDER BY lp.createdOn ASC, lp.name ASC
";
$learningPaths = Database::getManager()->createQuery($dql)->getResult();
/** @var CLp $lp */
foreach ($learningPaths as $lp) {
$id = $lp->getIid();
$courseId = $lp->getCId();
$courseInfo = api_get_course_info_by_id($courseId);
$lpVisibility = learnpath::is_lp_visible_for_student($id, $userId, $courseInfo);
if (false === $lpVisibility) {
continue;
}
$sessionId = 0;
if (isset($courseWithSession[$courseId])) {
$sessionId = $courseWithSession[$courseId];
}
$lpSessionId = $lp->getSessionId();
if (!empty($lpSessionId)) {
$sessionId = $lpSessionId;
}
$params = '&cidReq='.$courseInfo['code'].'&id_session='.$sessionId;
$link = api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=view'.$params.'&lp_id='.$id;
$icon = Display::url(
Display::return_icon(
'learnpath.png',
get_lang('Lp')
),
$link
);
$name = trim(strip_tags(Security::remove_XSS($lp->getName())));
$lps[] = [
'name' => $name,
'link' => $link,
'icon' => $icon,
//'creation_date' => api_get_local_time($lp->getCreatedOn()),
];
}
}
$template = new Template(get_lang('MyLps'));
$template->assign('lps', $lps);
$content = $template->fetch($template->get_template('learnpath/my_list.tpl'));
$template->assign('content', $content);
$template->display_one_col_template();

View File

@@ -0,0 +1,371 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines the OpenofficeDocument class, which is meant as a mother class
* to help in the conversion of Office documents to learning paths.
*
* @package chamilo.learnpath
*
* @author Eric Marguin <eric.marguin@dokeos.com>
* @author Julio Montoya
* @license GNU/GPL
*/
/**
* Defines the "OpenofficeDocument" child of class "learnpath".
*/
abstract class OpenofficeDocument extends learnpath
{
public $first_item = 0;
public $original_charset = 'utf-8';
public $original_locale = 'en_US.UTF-8';
public $slide_width;
public $slide_height;
/**
* Class constructor. Based on the parent constructor.
*
* @param string Course code
* @param int Learnpath ID in DB
* @param int User ID
*/
public function __construct($course_code = null, $resource_id = null, $user_id = null)
{
if (!empty($course_code) && !empty($resource_id) && !empty($user_id)) {
parent::__construct($course_code, $resource_id, $user_id);
}
}
/**
* Calls the LibreOffice server to convert the PPTs to a set of HTML + png files in a learning path.
*
* @param string $file
* @param string $action_after_conversion
* @param string $size The size to which we want the slides to be generated
*
* @return bool|int
*/
public function convert_document($file, $action_after_conversion = 'make_lp', $size = null)
{
$_course = api_get_course_info();
$this->file_name = pathinfo($file['name'], PATHINFO_FILENAME);
// Create the directory
$result = $this->generate_lp_folder($_course, $this->file_name);
// Create the directory
$this->base_work_dir = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document';
///learning_path/ppt_dirname directory
$this->created_dir = $result['dir'];
if (substr($this->created_dir, -1, 1) == '/') {
$this->file_path = $this->created_dir.api_replace_dangerous_char($file['name']);
} else {
$this->file_path = $this->created_dir.'/'.api_replace_dangerous_char($file['name']);
}
$dirMode = api_get_permissions_for_new_directories();
$fileMode = api_get_permissions_for_new_files();
if (!empty($size)) {
list($w, $h) = explode('x', $size);
if (!empty($w) && !empty($h)) {
$this->slide_width = (int) $w;
$this->slide_height = (int) $h;
}
}
$ppt2lp_host = api_get_setting('service_ppt2lp', 'host');
if ($ppt2lp_host == 'localhost') {
move_uploaded_file($file['tmp_name'], $this->base_work_dir.'/'.$this->file_path);
$perm = api_get_setting('permissions_for_new_files');
$changeDirectoryCmd = '';
if (IS_WINDOWS_OS) { // IS_WINDOWS_OS has been defined in main_api.lib.php
$converter_path = str_replace('/', '\\', api_get_path(SYS_PATH).'main/inc/lib/ppt2png');
$class_path = $converter_path.';'.$converter_path.'/jodconverter-2.2.2.jar;'.$converter_path.'/jodconverter-cli-2.2.2.jar';
//$cmd = 'java -cp "'.$class_path.'" DokeosConverter';
$cmd = 'java -Dfile.encoding=UTF-8 -cp "'.$class_path.'" DokeosConverter';
} else {
$converter_path = api_get_path(SYS_PATH).'main/inc/lib/ppt2png';
//$class_path = '-cp .:jodconverter-2.2.1.jar:jodconverter-cli-2.2.1.jar';
$class_path = ' -Dfile.encoding=UTF-8 -cp .:jodconverter-2.2.2.jar:jodconverter-cli-2.2.2.jar';
$changeDirectoryCmd = "cd $converter_path && ";
$cmd = 'java '.$class_path.' DokeosConverter';
}
$cmd .= ' -p '.api_get_setting('service_ppt2lp', 'port');
// Call to the function implemented by child.
$cmd .= $this->add_command_parameters();
// To allow openoffice to manipulate docs.
@chmod($this->base_work_dir, $dirMode);
@chmod($this->base_work_dir.$this->created_dir, $dirMode);
@chmod($this->base_work_dir.$this->file_path, $fileMode);
$locale = $this->original_locale; // TODO: Improve it because we're not sure this locale is present everywhere.
putenv('LC_ALL='.$locale);
$files = [];
$return = 0;
$cmd = $changeDirectoryCmd.escapeshellcmd($cmd);
$shell = exec($cmd, $files, $return);
if ($return != 0) { // If the java application returns an error code.
switch ($return) {
case 1:
// Can't connect to openoffice.
$this->error = get_lang('CannotConnectToOpenOffice');
break;
case 2:
// Conversion failed in openoffice.
$this->error = get_lang('OogieConversionFailed');
break;
case 255:
// Conversion can't be launch because command failed.
$this->error = get_lang('OogieUnknownError');
break;
}
DocumentManager::delete_document($_course, $this->created_dir, $this->base_work_dir);
return false;
}
} else {
// get result from webservices
$result = $this->_get_remote_ppt2lp_files($file, $size);
$result = unserialize($result);
// Save remote images to server
chmod($this->base_work_dir.$this->created_dir, api_get_permissions_for_new_directories());
if (!empty($result['images'])) {
foreach ($result['images'] as $image => $img_data) {
$image_path = $this->base_work_dir.$this->created_dir;
@file_put_contents($image_path.'/'.$image, base64_decode($img_data));
@chmod($image_path.'/'.$image, $fileMode);
}
}
// files info
$files = $result['files'];
}
if (!empty($files)) {
// Create lp
$this->lp_id = learnpath::add_lp($_course['id'], $this->file_name, '', 'guess', 'manual');
// make sure we have a course code available for later
$this->cc = $_course['id'];
$this->course_info = $_course;
// Call to the function implemented by child following action_after_conversion parameter.
switch ($action_after_conversion) {
case 'make_lp':
$this->make_lp($files);
break;
case 'add_docs_to_visio':
$this->add_docs_to_visio($files);
break;
}
@chmod($this->base_work_dir, api_get_permissions_for_new_directories());
}
return $this->first_item;
}
abstract public function make_lp();
abstract public function add_docs_to_visio();
abstract public function add_command_parameters();
/**
* Used to convert copied from document.
*
* @param string $originalPath
* @param string $convertedPath
* @param string $convertedTitle
*
* @return bool
*/
public function convertCopyDocument($originalPath, $convertedPath, $convertedTitle)
{
$_course = api_get_course_info();
$ids = [];
$originalPathInfo = pathinfo($originalPath);
$convertedPathInfo = pathinfo($convertedPath);
$this->base_work_dir = $originalPathInfo['dirname'];
$this->file_path = $originalPathInfo['basename'];
$this->created_dir = $convertedPathInfo['basename'];
$ppt2lpHost = api_get_setting('service_ppt2lp', 'host');
$permissionFile = api_get_permissions_for_new_files();
$permissionFolder = api_get_permissions_for_new_directories();
if (file_exists($this->base_work_dir.'/'.$this->created_dir)) {
return $ids;
}
if ($ppt2lpHost == 'localhost') {
$changeDirectoryCmd = '';
if (IS_WINDOWS_OS) { // IS_WINDOWS_OS has been defined in main_api.lib.php
$converterPath = str_replace('/', '\\', api_get_path(SYS_PATH).'main/inc/lib/ppt2png');
$classPath = $converterPath.';'.$converterPath.'/jodconverter-2.2.2.jar;'.$converterPath.'/jodconverter-cli-2.2.2.jar';
$cmd = 'java -Dfile.encoding=UTF-8 -jar "'.$classPath.'/jodconverter-2.2.2.jar"';
} else {
$converterPath = api_get_path(SYS_PATH).'main/inc/lib/ppt2png';
$classPath = ' -Dfile.encoding=UTF-8 -jar jodconverter-cli-2.2.2.jar';
$changeDirectoryCmd = "cd $converterPath && ";
$cmd = 'java '.$classPath.' ';
}
$cmd .= ' -p '.api_get_setting('service_ppt2lp', 'port');
// Call to the function implemented by child.
$cmd .= ' "'.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.'" "'
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->created_dir).'"';
// To allow openoffice to manipulate docs.
@chmod($this->base_work_dir, $permissionFolder);
@chmod($this->base_work_dir.'/'.$this->file_path, $permissionFile);
$locale = $this->original_locale; // TODO: Improve it because we're not sure this locale is present everywhere.
putenv('LC_ALL='.$locale);
$files = [];
$return = 0;
$cmd = $changeDirectoryCmd.escapeshellcmd($cmd);
$shell = exec($cmd, $files, $return);
// TODO: Chown is not working, root keep user privileges, should be www-data
@chown($this->base_work_dir.'/'.$this->created_dir, 'www-data');
@chmod($this->base_work_dir.'/'.$this->created_dir, $permissionFile);
if ($return != 0) { // If the java application returns an error code.
switch ($return) {
case 1:
// Can't connect to openoffice.
$this->error = get_lang('CannotConnectToOpenOffice');
break;
case 2:
// Conversion failed in openoffice.
$this->error = get_lang('OogieConversionFailed');
break;
case 255:
// Conversion can't be launch because command failed.
$this->error = get_lang('OogieUnknownError');
break;
}
DocumentManager::delete_document($_course, $this->created_dir, $this->base_work_dir);
return false;
}
} else {
/*
* @TODO Create method to use webservice
// get result from webservices
$result = $this->_get_remote_ppt2lp_files($file);
$result = unserialize(base64_decode($result));
// Save remote images to server
chmod($this->base_work_dir.$this->created_dir, api_get_permissions_for_new_directories());
if (!empty($result['images'])) {
foreach ($result['images'] as $image => $img_data) {
$image_path = $this->base_work_dir.$this->created_dir;
@file_put_contents($image_path . '/' . $image, base64_decode($img_data));
@chmod($image_path . '/' . $image, 0777);
}
}
// files info
$files = $result['files'];
*/
}
if (file_exists($this->base_work_dir.'/'.$this->created_dir)) {
// Register Files to Document tool
$ids[] = add_document(
$_course,
'/'.$this->created_dir,
'file',
filesize($this->base_work_dir.'/'.$this->created_dir),
$convertedTitle,
sprintf(
get_lang('FileConvertedFromXToY'),
strtoupper($originalPathInfo['extension']),
strtoupper($convertedPathInfo['extension'])
),
0,
true,
null,
api_get_session_id()
);
chmod($this->base_work_dir, $permissionFolder);
}
return $ids;
}
/**
* Get images files from remote host (with webservices).
*
* @param array $file current ppt file details
* @param string $size The expected final size of the rendered slides
*
* @return array images files
*/
private function _get_remote_ppt2lp_files($file, $size = null)
{
// host
$ppt2lp_host = api_get_setting('service_ppt2lp', 'host');
// SOAP URI (just the host)
$matches = [];
$uri = '';
$result = preg_match('/^([a-zA-Z0-9]*):\/\/([^\/]*)\//', $ppt2lp_host, $matches);
if ($result) {
$uri = $matches[1].'://'.$matches[2].'/';
} else {
$uri = $ppt2lp_host;
}
// secret key
$secret_key = sha1(api_get_setting('service_ppt2lp', 'ftp_password'));
// client
$options = [
'location' => $ppt2lp_host,
'uri' => $uri,
'trace' => 1,
'exceptions' => true,
'cache_wsdl' => WSDL_CACHE_NONE,
'keep_alive' => false,
'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
'compression' => SOAP_COMPRESSION_ACCEPT | SOAP_COMPRESSION_GZIP | 9,
];
if (substr($ppt2lp_host, 0, 5) === 'https') {
$options['ssl_method'] = SOAP_SSL_METHOD_TLS;
// If using SSL, please note that *not* supporting the SSLv2
// (broken in terms of security), the server tends to generate
// the following issue:
// SoapClient::__doRequest(): SSL: Connection reset by peer
}
$client = new SoapClient(null, $options);
$result = '';
$file_data = base64_encode(file_get_contents($file['tmp_name']));
$file_name = $file['name'];
if (empty($size)) {
$size = api_get_setting('service_ppt2lp', 'size');
}
$params = [
'secret_key' => $secret_key,
'file_data' => $file_data,
'file_name' => $file_name,
'service_ppt2lp_size' => $size,
];
try {
$result = $client->wsConvertPpt($params);
} catch (Exception $e) {
error_log('['.time().'] Chamilo SOAP call error: '.$e->getMessage());
}
// Make sure we destroy the SOAP client as it may generate SSL connection
// binding issue (if using SSL)
unset($client);
return $result;
}
}

View File

@@ -0,0 +1,301 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines the OpenofficeDocument class, which is meant as a conversion
* tool from Office presentations (.ppt, .sxi, .odp, .pptx) to
* learning paths.
*
* @package chamilo.learnpath
*
* @author Eric Marguin <eric.marguin@dokeos.com>
* @license GNU/GPL
*/
/**
* Defines the "OpenofficePresentation" child of class "OpenofficeDocument".
*/
require_once 'openoffice_document.class.php';
if (api_get_setting('search_enabled') == 'true') {
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
}
class OpenofficePresentation extends OpenofficeDocument
{
public $take_slide_name;
public function __construct($take_slide_name = false, $course_code = null, $resource_id = null, $user_id = null)
{
$this->take_slide_name = $take_slide_name;
parent::__construct($course_code, $resource_id, $user_id);
}
public function make_lp($files = [])
{
$_course = api_get_course_info();
$previous = 0;
$i = 0;
if (!is_dir($this->base_work_dir.$this->created_dir)) {
return false;
}
$dir = $this->created_dir;
if (substr($dir, -1, 1) !== '/') {
$dir .= '/';
}
foreach ($files as $file) {
/* '||' is used as separator between fields:
slide name (with accents) || file name (without accents) || all slide text (to be indexed).
*/
list($slide_name, $file_name, $slide_body) = explode('||', $file);
// Filename is utf8 encoded, but when we decode, some chars are not translated (like quote &rsquo;).
// so we remove these chars by translating it in htmlentities and the reconvert it in want charset.
$slide_name = api_htmlentities($slide_name, ENT_COMPAT, $this->original_charset);
$slide_name = str_replace('&rsquo;', '\'', $slide_name);
$slide_name = api_convert_encoding($slide_name, api_get_system_encoding(), $this->original_charset);
$slide_name = api_html_entity_decode($slide_name, ENT_COMPAT, api_get_system_encoding());
if ($this->take_slide_name === true) {
$slide_name = str_replace('_', ' ', $slide_name);
$slide_name = api_ucfirst($slide_name);
} else {
$slide_name = 'slide'.str_repeat('0', 2 - strlen($i)).$i;
}
if (!is_file($this->base_work_dir.$dir.$file_name) or filesize($this->base_work_dir.$dir.$file_name) == 0) {
continue;
}
$i++;
if (substr($file_name, -1, 1) == '/') {
$file_name = substr($file_name, 0, -1);
}
// Add the png to documents.
$document_id = add_document(
$_course,
$dir.urlencode($file_name),
'file',
filesize($this->base_work_dir.$dir.$file_name),
$slide_name
);
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
// Generating the thumbnail.
$image = $this->base_work_dir.$dir.$file_name;
$pattern = '/(\w+)\.png$/';
$replacement = '${1}_thumb.png';
$thumb_name = preg_replace($pattern, $replacement, $file_name);
// Calculate thumbnail size.
$image_size = api_getimagesize($image);
$width = $image_size['width'];
$height = $image_size['height'];
$thumb_width = 300;
$thumb_height = floor($height * ($thumb_width / $width));
$my_new_image = new Image($image);
$my_new_image->resize($thumb_width, $thumb_height);
$my_new_image->send_image($this->base_work_dir.$dir.$thumb_name, -1, 'png');
// Adding the thumbnail to documents.
$document_id_thumb = add_document(
$_course,
$dir.urlencode($thumb_name),
'file',
filesize($this->base_work_dir.$dir.$thumb_name),
$slide_name
);
api_item_property_update(
$_course,
TOOL_THUMBNAIL,
$document_id_thumb,
'DocumentAdded',
api_get_user_id(),
0,
0
);
// Create an html file.
$html_file = $file_name.'.html';
$fp = fopen($this->base_work_dir.$dir.$html_file, 'w+');
if (substr($dir, 0, 1) != '/') {
$dir = '/'.$dir;
}
$slide_src = api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$dir.utf8_encode($file_name);
$slide_src = str_replace('\/\/', '/', $slide_src);
fwrite(
$fp,
'<html>
<head>
<title>'.$slide_name.'</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="'.api_get_path(WEB_PUBLIC_PATH).'assets/bootstrap/dist/css/bootstrap.min.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<img class="img-responsive" alt="'.htmlspecialchars($slide_name.'"', ENT_QUOTES).'" src="'.$slide_src.'" />
</body>
</html>'
); // This indentation is to make the generated html files to look well.
fclose($fp);
$document_id = add_document(
$_course,
$dir.urlencode($html_file),
'file',
filesize($this->base_work_dir.$dir.$html_file),
$slide_name
);
if ($document_id) {
// Put the document in item_property update.
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
$previous = $this->add_item(
0,
$previous,
'document',
$document_id,
$slide_name,
''
);
if ($this->first_item == 0) {
$this->first_item = intval($previous);
}
}
// Code for text indexing.
if (api_get_setting('search_enabled') == 'true') {
if (isset($_POST['index_document']) && $_POST['index_document']) {
$di = new ChamiloIndexer();
isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
$di->connectDb(null, null, $lang);
$ic_slide = new IndexableChunk();
$ic_slide->addValue('title', $slide_name);
$specific_fields = get_specific_field_list();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '.$sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
}
$slide_body = $all_specific_terms.' '.$slide_body;
$ic_slide->addValue('content', $slide_body);
/* FIXME: cidReq:lp_id:doc_id al indexar */
// Add a comment to say terms separated by commas.
$courseid = api_get_course_id();
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id;
$xapian_data = [
SE_COURSE_ID => $courseid,
SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => ['lp_id' => $lp_id, 'lp_item' => $previous, 'document_id' => $document_id],
SE_USER => (int) api_get_user_id(),
];
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide);
// Index and return search engine document id.
$did = $di->index();
if ($did) {
// Save it to db.
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
$sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $lp_id, $previous, $did);
Database::query($sql);
}
}
}
}
}
public function add_command_parameters()
{
if (empty($this->slide_width) || empty($this->slide_height)) {
list($w, $h) = explode('x', api_get_setting('service_ppt2lp', 'size'));
$this->slide_width = (int) $w;
$this->slide_height = (int) $h;
}
return ' -w '.$this->slide_width.' -h '.$this->slide_height.' -d oogie '
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.' '
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'.html');
}
public function set_slide_size($width, $height)
{
$this->slide_width = (int) $width;
$this->slide_height = (int) $height;
}
public function add_docs_to_visio($files = [])
{
$_course = api_get_course_info();
foreach ($files as $file) {
// '||' is used as separator between slide name (with accents) and file name (without accents).
list($slide_name, $file_name) = explode('||', $file);
$slide_name = api_htmlentities($slide_name, ENT_COMPAT, $this->original_charset);
$slide_name = str_replace('&rsquo;', '\'', $slide_name);
$slide_name = api_convert_encoding($slide_name, api_get_system_encoding(), $this->original_charset);
$slide_name = api_html_entity_decode($slide_name, ENT_COMPAT, api_get_system_encoding());
$did = add_document(
$_course,
$this->created_dir.'/'.urlencode($file_name),
'file',
filesize($this->base_work_dir.$this->created_dir.'/'.$file_name),
$slide_name
);
if ($did) {
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$did,
'DocumentAdded',
api_get_user_id(),
0,
null,
null,
null,
api_get_session_id()
);
}
}
}
}

View File

@@ -0,0 +1,396 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines the OpenofficeDocument class, which is meant as a conversion
* tool from Office text documents (.doc, .sxw, .odt, .docx) to
* learning paths.
*
* @package chamilo.learnpath
*
* @author Eric Marguin <eric.marguin@dokeos.com>
* @license GNU/GPL
*/
/**
* Defines the "OpenofficeText" child of class "learnpath".
*/
require_once 'openoffice_document.class.php';
if (api_get_setting('search_enabled') == 'true') {
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
}
/**
* @package chamilo.learnpath.OpenofficeDocument
*/
class OpenofficeText extends OpenofficeDocument
{
public $split_steps;
/**
* Class constructor. Calls the parent class and initialises the local attribute split_steps.
*
* @param bool Whether to split steps (true) or make one large page (false)
* @param string Course code
* @param int Resource ID
* @param int Creator user id
*/
public function __construct(
$split_steps = false,
$course_code = null,
$resource_id = null,
$user_id = null
) {
$this->split_steps = $split_steps;
parent::__construct($course_code, $resource_id, $user_id);
}
/**
* Gets html pages and compose them into a learning path.
*
* @param array The files that will compose the generated learning path. Unused so far.
*
* @return bool False if file does not exit. Nothing otherwise.
*/
public function make_lp($files = [])
{
$_course = api_get_course_info();
// We get a content where ||page_break|| indicates where the page is broken.
if (!file_exists($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html')) {
return false;
}
$content = file_get_contents($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html');
unlink($this->base_work_dir.'/'.$this->file_path);
unlink($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html');
// The file is utf8 encoded and it seems to make problems with special quotes.
// Then we htmlentities that, we replace these quotes and html_entity_decode that in good charset.
$charset = api_get_system_encoding();
$content = api_htmlentities($content, ENT_COMPAT, $this->original_charset);
$content = str_replace('&rsquo;', '\'', $content);
$content = api_convert_encoding($content, $charset, $this->original_charset);
$content = str_replace($this->original_charset, $charset, $content);
$content = api_html_entity_decode($content, ENT_COMPAT, $charset);
// Set the path to pictures to absolute (so that it can be modified in fckeditor).
$content = preg_replace(
"|src=\"([^\"]*)|i",
"src=\"".api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$this->created_dir."/\\1",
$content
);
list($header, $body) = explode('<BODY', $content);
$body = '<BODY'.$body;
// Remove font-family styles.
$header = preg_replace("|font\-family[^;]*;|i", '', $header);
// Chamilo styles.
$my_style = api_get_setting('stylesheets');
if (empty($my_style)) {
$my_style = 'chamilo';
}
$style_to_import = "<style type=\"text/css\">\r\n";
$style_to_import .= '@import "'.api_get_path(WEB_CODE_PATH).'css/'.$my_style.'/default.css";'."\n";
$style_to_import .= "</style>\r\n";
$header = preg_replace("|</head>|i", "\r\n$style_to_import\r\n\\0", $header);
// Line break before and after picture.
$header = str_replace('p {', 'p {clear:both;', $header);
$header = str_replace('absolute', 'relative', $header);
switch ($this->split_steps) {
case 'per_page':
$this->dealPerPage($header, $body);
break;
case 'per_chapter':
$this->dealPerChapter($header, $body);
break;
}
}
/**
* Manages dir/chapter splitting.
*
* @param string Chapter header
* @param string Content
*/
public function dealPerChapter($header, $content)
{
$_course = api_get_course_info();
$content = str_replace('||page_break||', '', $content);
// Get all the h1.
preg_match_all("|<h1[^>]*>([^(h1)+]*)</h1>|is", $content, $matches_temp);
// Empty the fake chapters.
$new_index = 0;
for ($i = 0; $i < count($matches_temp[0]); $i++) {
if (trim($matches_temp[1][$i]) !== '') {
$matches[0][$new_index] = $matches_temp[0][$i];
$matches[1][$new_index] = $matches_temp[1][$i];
$new_index++;
}
}
// Add intro item.
$intro_content = substr($content, 0, strpos($content, $matches[0][0]));
$items_to_create[get_lang('Introduction')] = $intro_content;
for ($i = 0; $i < count($matches[0]); $i++) {
if (empty($matches[1][$i])) {
continue;
}
$content = strstr($content, $matches[0][$i]);
if ($i + 1 !== count($matches[0])) {
$dir_content = substr($content, 0, strpos($content, $matches[0][$i + 1]));
} else {
$dir_content = $content;
}
$items_to_create[$matches[1][$i]] = $dir_content;
}
$i = 0;
foreach ($items_to_create as $item_title => $item_content) {
$i++;
$page_content = $this->format_page_content($header, $item_content);
$html_file = $this->created_dir.'-'.$i.'.html';
$handle = fopen($this->base_work_dir.$this->created_dir.'/'.$html_file, 'w+');
fwrite($handle, $page_content);
fclose($handle);
$document_id = add_document(
$_course,
$this->created_dir.'/'.$html_file,
'file',
filesize(
$this->base_work_dir.$this->created_dir.'/'.$html_file
),
$html_file
);
if ($document_id) {
// Put the document in item_property update.
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
$infos = pathinfo($this->filepath);
$slide_name = strip_tags(nl2br($item_title));
$slide_name = str_replace(["\r\n", "\r", "\n"], '', $slide_name);
$slide_name = html_entity_decode($slide_name);
$previous = learnpath::add_item(
0,
$previous,
'document',
$document_id,
$slide_name,
''
);
if ($this->first_item == 0) {
$this->first_item = $previous;
}
}
}
}
/**
* Manages page splitting.
*
* @param string Page header
* @param string Page body
*/
public function dealPerPage($header, $body)
{
$_course = api_get_course_info();
// Split document to pages.
$pages = explode('||page_break||', $body);
$first_item = 0;
foreach ($pages as $key => $page_content) {
// For every pages, we create a new file.
$key++;
$page_content = $this->format_page_content($header, $page_content, $this->base_work_dir.$this->created_dir);
$html_file = $this->created_dir.'-'.$key.'.html';
$handle = fopen($this->base_work_dir.$this->created_dir.'/'.$html_file, 'w+');
fwrite($handle, $page_content);
fclose($handle);
$document_id = add_document(
$_course,
$this->created_dir.$html_file,
'file',
filesize($this->base_work_dir.$this->created_dir.$html_file),
$html_file
);
$slide_name = '';
if ($document_id) {
// Put the document in item_property update.
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
$infos = pathinfo($this->filepath);
$slide_name = 'Page '.str_repeat('0', 2 - strlen($key)).$key;
$previous = learnpath::add_item(0, $previous, 'document', $document_id, $slide_name, '');
if ($this->first_item == 0) {
$this->first_item = $previous;
}
// Code for text indexing.
if (api_get_setting('search_enabled') == 'true') {
if (isset($_POST['index_document']) && $_POST['index_document']) {
//echo Display::return_message(print_r($_POST));
$di = new ChamiloIndexer();
isset($_POST['language']) ? $lang = Database::escape_string(
$_POST['language']
) : $lang = 'english';
$di->connectDb(null, null, $lang);
$ic_slide = new IndexableChunk();
$ic_slide->addValue('title', $slide_name);
$specific_fields = get_specific_field_list();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '.$sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
}
$page_content = $all_specific_terms.' '.$page_content;
$ic_slide->addValue('content', $page_content);
// Add a comment to say terms separated by commas.
$courseid = api_get_course_id();
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id;
$xapian_data = [
SE_COURSE_ID => $courseid,
SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => ['lp_id' => $lp_id, 'lp_item' => $previous, 'document_id' => $document_id],
SE_USER => (int) api_get_user_id(),
];
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide);
// Index and return search engine document id.
$did = $di->index();
if ($did) {
// Save it to db.
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
$sql = sprintf(
$sql,
$tbl_se_ref,
api_get_course_id(),
TOOL_LEARNPATH,
$lp_id,
$previous,
$did
);
Database::query($sql);
}
}
}
}
}
}
/**
* Returns additional Java command parameters.
*
* @return string The additional parameters to be used in the Java call
*/
public function add_command_parameters()
{
return ' -d woogie '
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.' '
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html');
}
/**
* Formats a page content by reorganising the HTML code a little.
*
* @param string Page header
* @param string Page content
*
* @return string Formatted page content
*/
public function format_page_content($header, $content)
{
// Limit the width of the doc.
list($max_width, $max_height) = explode('x', api_get_setting('service_ppt2lp', 'size'));
$content = preg_replace("|<body[^>]*>|i", "\\0\r\n<div style=\"width:".$max_width."\">", $content, -1, $count);
if ($count < 1) {
$content = '<body><div style="width:'.$max_width.'">'.$content;
}
$content = preg_replace('|</body>|i', '</div>\\0', $content, -1, $count);
if ($count < 1) {
$content = $content.'</div></body>';
}
// Add the headers.
$content = $header.$content;
// Resize all the picture to the max_width-10
preg_match_all("|<img[^src]*src=\"([^\"]*)\"[^>]*>|i", $content, $images);
foreach ($images[1] as $key => $image) {
// Check if the <img tag soon has a width attribute.
$defined_width = preg_match("|width=([^\s]*)|i", $images[0][$key], $img_width);
$img_width = $img_width[1];
if (!$defined_width) {
list($img_width, $img_height, $type) = getimagesize($this->base_work_dir.$this->created_dir.'/'.$image);
$new_width = $max_width - 10;
if ($img_width > $new_width) {
$picture_resized = str_ireplace('<img', '<img width="'.$new_width.'" ', $images[0][$key]);
$content = str_replace($images[0][$key], $picture_resized, $content);
}
} elseif ($img_width > $max_width - 10) {
$picture_resized = str_ireplace('width='.$img_width, 'width="'.($max_width - 10).'"', $images[0][$key]);
$content = str_replace($images[0][$key], $picture_resized, $content);
}
}
return $content;
}
/**
* Add documents to the visioconference (to be implemented).
*/
public function add_docs_to_visio()
{
}
}

View File

@@ -0,0 +1,408 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Defines the OpenOfficeDocument class, which is meant as a conversion
* tool from Office text documents (.doc, .sxw, .odt, .docx) to
* learning paths.
*
* @package chamilo.learnpath
*
* @author Eric Marguin <eric.marguin@dokeos.com>
* @license GNU/GPL
*/
/**
* Defines the "OpenOfficeTextDocument" child of class "learnpath".
*/
require_once 'openoffice_document.class.php';
require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php';
/**
* @package chamilo.learnpath.openofficedocument
*/
class OpenOfficeTextDocument extends OpenofficeDocument
{
public $split_steps;
/**
* Class constructor. Calls the parent class and initialises the local attribute split_steps.
*
* @param bool Whether to split steps (true) or make one large page (false)
* @param string Course code
* @param int Resource ID
* @param int Creator user id
*/
public function __construct(
$split_steps = false,
$course_code = null,
$resource_id = null,
$user_id = null
) {
$this->split_steps = $split_steps;
parent::__construct($course_code, $resource_id, $user_id);
}
/**
* Gets html pages and compose them into a learning path.
*
* @param array The files that will compose the generated learning path. Unused so far.
*
* @return bool False if file does not exit. Nothing otherwise.
*/
public function make_lp($files = [])
{
$_course = api_get_course_info();
// We get a content where ||page_break|| indicates where the page is broken.
if (!file_exists($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html')) {
return false;
}
$content = file_get_contents($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html');
unlink($this->base_work_dir.'/'.$this->file_path);
unlink($this->base_work_dir.'/'.$this->created_dir.'/'.$this->file_name.'.html');
// The file is utf8 encoded and it seems to make problems with special quotes.
// then we htmlentities that, we replace these quotes and html_entity_decode that in good charset.
$charset = api_get_system_encoding();
$content = api_htmlentities($content, ENT_COMPAT, $this->original_charset);
$content = str_replace('&rsquo;', '\'', $content);
$content = api_convert_encoding($content, $charset, $this->original_charset);
$content = str_replace($this->original_charset, $charset, $content);
$content = api_html_entity_decode($content, ENT_COMPAT, $charset);
// Set the path to pictures to absolute (so that it can be modified in fckeditor).
$content = preg_replace("|src=\"([^\"]*)|i", "src=\"".api_get_path(REL_COURSE_PATH).$_course['path'].'/document'.$this->created_dir."/\\1", $content);
list($header, $body) = explode('<BODY', $content);
$body = '<BODY'.$body;
// Remove font-family styles.
$header = preg_replace("|font\-family[^;]*;|i", '', $header);
// Chamilo styles.
$my_style = api_get_setting('stylesheets');
if (empty($my_style)) {
$my_style = 'chamilo';
}
$style_to_import = "<style type=\"text/css\">\r\n";
$style_to_import .= '@import "'.api_get_path(WEB_CODE_PATH).'css/'.$my_style.'/default.css";'."\n";
$style_to_import .= "</style>\r\n";
$header = preg_replace("|</head>|i", "\r\n$style_to_import\r\n\\0", $header);
// Line break before and after picture.
$header = str_replace('p {', 'p {clear:both;', $header);
$header = str_replace('absolute', 'relative', $header);
switch ($this->split_steps) {
case 'per_page':
$this->dealPerPage($header, $body);
break;
case 'per_chapter':
$this->dealPerChapter($header, $body);
break;
}
}
/**
* Manages dir/chapter splitting.
*
* @param string Chapter header
* @param string Content
*/
public function dealPerChapter($header, $content)
{
$_course = api_get_course_info();
$content = str_replace('||page_break||', '', $content);
// Get all the h1.
preg_match_all("|<h1[^>]*>([^(h1)+]*)</h1>|is", $content, $matches_temp);
// Empty the fake dir/chapters.
$new_index = 0;
for ($i = 0; $i < count($matches_temp[0]); $i++) {
if (trim($matches_temp[1][$i]) !== '') {
$matches[0][$new_index] = $matches_temp[0][$i];
$matches[1][$new_index] = $matches_temp[1][$i];
$new_index++;
}
}
// Add intro item.
$intro_content = api_substr($content, 0, api_strpos($content, $matches[0][0]));
$items_to_create[get_lang('Introduction')] = $intro_content;
for ($i = 0; $i < count($matches[0]); $i++) {
if (empty($matches[1][$i])) {
continue;
}
$content = api_strstr($content, $matches[0][$i]);
if ($i + 1 !== count($matches[0])) {
$dir_content = api_substr($content, 0, api_strpos($content, $matches[0][$i + 1]));
} else {
$dir_content = $content;
}
$items_to_create[$matches[1][$i]] = $dir_content;
}
$i = 0;
$previous = 0; // @todo define this variable properly
foreach ($items_to_create as $item_title => $item_content) {
$i++;
$page_content = $this->format_page_content($header, $item_content);
$html_file = $this->created_dir.'-'.$i.'.html';
$handle = fopen($this->base_work_dir.$this->created_dir.'/'.$html_file, 'w+');
fwrite($handle, $page_content);
fclose($handle);
$document_id = add_document(
$_course,
$this->created_dir.'/'.$html_file,
'file',
filesize($this->base_work_dir.$this->created_dir.'/'.$html_file),
$html_file
);
if ($document_id) {
// Put the document in item_property update.
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
$infos = pathinfo($this->filepath);
$slide_name = strip_tags(nl2br($item_title));
$slide_name = str_replace(["\r\n", "\r", "\n"], '', $slide_name);
$slide_name = api_html_entity_decode($slide_name, ENT_COMPAT, api_get_system_encoding());
$previous = learnpath::add_item(
0,
$previous,
'document',
$document_id,
$slide_name,
''
);
if ($this->first_item == 0) {
$this->first_item = (int) $previous;
}
}
}
}
/**
* Manages page splitting.
*
* @param string Page header
* @param string Page body
*/
public function dealPerPage($header, $body)
{
$_course = api_get_course_info();
// Split document to pages.
$pages = explode('||page_break||', $body);
foreach ($pages as $key => $page_content) {
// For every pages, we create a new file.
$key++;
$page_content = $this->format_page_content(
$header,
$page_content,
$this->base_work_dir.$this->created_dir
);
$html_file = $this->created_dir.'-'.$key.'.html';
$handle = fopen($this->base_work_dir.$this->created_dir.'/'.$html_file, 'w+');
fwrite($handle, $page_content);
fclose($handle);
$document_id = add_document(
$_course,
$this->created_dir.$html_file,
'file',
filesize($this->base_work_dir.$this->created_dir.$html_file),
$html_file
);
$slide_name = '';
$previous = 0; // @todo define this variable properly
if ($document_id) {
// Put the document in item_property update.
api_item_property_update(
$_course,
TOOL_DOCUMENT,
$document_id,
'DocumentAdded',
api_get_user_id(),
0,
0,
null,
null,
api_get_session_id()
);
$infos = pathinfo($this->filepath);
$slide_name = 'Page '.str_repeat('0', 2 - strlen($key)).$key;
$previous = learnpath::add_item(
0,
$previous,
'document',
$document_id,
$slide_name,
''
);
if ($this->first_item == 0) {
$this->first_item = (int) $previous;
}
// Code for text indexing.
if (isset($_POST['index_document']) && $_POST['index_document']) {
//echo Display::return_message(print_r($_POST));
$di = new ChamiloIndexer();
isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english';
$di->connectDb(null, null, $lang);
$ic_slide = new IndexableChunk();
$ic_slide->addValue('title', $slide_name);
$specific_fields = get_specific_field_list();
$all_specific_terms = '';
foreach ($specific_fields as $specific_field) {
if (isset($_REQUEST[$specific_field['code']])) {
$sterms = trim($_REQUEST[$specific_field['code']]);
$all_specific_terms .= ' '.$sterms;
if (!empty($sterms)) {
$sterms = explode(',', $sterms);
foreach ($sterms as $sterm) {
$ic_slide->addTerm(trim($sterm), $specific_field['code']);
}
}
}
}
$page_content = $all_specific_terms.' '.$page_content;
$ic_slide->addValue('content', $page_content);
// Add a comment to say terms separated by commas.
$courseid = api_get_course_id();
$ic_slide->addCourseId($courseid);
$ic_slide->addToolId(TOOL_LEARNPATH);
$lp_id = $this->lp_id;
$xapian_data = [
SE_COURSE_ID => $courseid,
SE_TOOL_ID => TOOL_LEARNPATH,
SE_DATA => [
'lp_id' => $lp_id,
'lp_item' => $previous,
'document_id' => $document_id,
],
SE_USER => (int) api_get_user_id(),
];
$ic_slide->xapian_data = serialize($xapian_data);
$di->addChunk($ic_slide);
// Index and return search engine document id.
$did = $di->index();
if ($did) {
// Save it to db.
$tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF);
$sql = 'INSERT INTO %s (id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did)
VALUES (NULL , \'%s\', \'%s\', %s, %s, %s)';
$sql = sprintf(
$sql,
$tbl_se_ref,
api_get_course_id(),
TOOL_LEARNPATH,
$lp_id,
$previous,
$did
);
Database::query($sql);
}
}
}
}
}
/**
* Returns additional Java command parameters.
*
* @return string The additional parameters to be used in the Java call
*/
public function add_command_parameters()
{
return ' -d woogie '
.Security::sanitizeExecParam($this->base_work_dir.'/'.$this->file_path)
.' '
.Security::sanitizeExecParam($this->base_work_dir.$this->created_dir.'/'.$this->file_name.'.html');
}
/**
* Formats a page content by reorganising the HTML code a little.
*
* @param string Page header
* @param string Page content
*
* @return string Formatted page content
*/
public function format_page_content($header, $content)
{
// Limit the width of the doc.
list($max_width, $max_height) = explode('x', api_get_setting('service_ppt2lp', 'size'));
$content = preg_replace("|<body[^>]*>|i", "\\0\r\n<div style=\"width:".$max_width."\">", $content, -1, $count);
if ($count < 1) {
$content = '<body><div style="width:'.$max_width.'">'.$content;
}
$content = preg_replace('|</body>|i', '</div>\\0', $content, -1, $count);
if ($count < 1) {
$content = $content.'</div></body>';
}
// Add the headers.
$content = $header.$content;
// Resize all the picture to the max_width-10
preg_match_all("|<img[^src]*src=\"([^\"]*)\"[^>]*>|i", $content, $images);
foreach ($images[1] as $key => $image) {
// Check if the <img tag soon has a width attribute.
$defined_width = preg_match("|width=([^\s]*)|i", $images[0][$key], $img_width);
$img_width = $img_width[1];
if (!$defined_width) {
$image_size = api_getimagesize($this->base_work_dir.$this->created_dir.'/'.$image);
$img_width = $image_size['width'];
$img_height = $image_size['height'];
$new_width = $max_width - 10;
if ($img_width > $new_width) {
$picture_resized = str_ireplace('<img', '<img width="'.$new_width.'" ', $images[0][$key]);
$content = str_replace($images[0][$key], $picture_resized, $content);
}
} elseif ($img_width > $max_width - 10) {
$picture_resized = str_ireplace(
'width='.$img_width,
'width="'.($max_width - 10).'"',
$images[0][$key]
);
$content = str_replace(
$images[0][$key],
$picture_resized,
$content
);
}
}
return $content;
}
/**
* Add documents to the visioconference (to be implemented).
*/
public function add_docs_to_visio()
{
}
}

View File

@@ -0,0 +1,110 @@
<?xml version="1.0"?>
<!-- filename=adlcp_rootv1p2.xsd -->
<!-- Conforms to w3c http://www.w3.org/TR/xmlschema-1/ 2000-10-24-->
<xsd:schema xmlns="http://www.adlnet.org/xsd/adlcp_rootv1p2"
targetNamespace="http://www.adlnet.org/xsd/adlcp_rootv1p2"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:imscp="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
version="ADL Version 1.2">
<xsd:import namespace="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
schemaLocation="imscp_rootv1p1p2.xsd"/>
<xsd:element name="location" type="locationType"/>
<xsd:element name="prerequisites" type="prerequisitesType"/>
<xsd:element name="maxtimeallowed" type="maxtimeallowedType"/>
<xsd:element name="timelimitaction" type="timelimitactionType"/>
<xsd:element name="datafromlms" type="datafromlmsType"/>
<xsd:element name="masteryscore" type="masteryscoreType"/>
<xsd:element name="schema" type="newSchemaType"/>
<xsd:simpleType name="newSchemaType">
<xsd:restriction base="imscp:schemaType">
<xsd:enumeration value="ADL SCORM"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:element name="schemaversion" type="newSchemaversionType"/>
<xsd:simpleType name="newSchemaversionType">
<xsd:restriction base="imscp:schemaversionType">
<xsd:enumeration value="1.2"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:attribute name="scormtype">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="asset"/>
<xsd:enumeration value="sco"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:simpleType name="locationType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="2000"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:complexType name="prerequisitesType">
<xsd:simpleContent>
<xsd:extension base="prerequisiteStringType">
<xsd:attributeGroup ref="attr.prerequisitetype"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:attributeGroup name="attr.prerequisitetype">
<xsd:attribute name="type" use="required">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="aicc_script"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:simpleType name="maxtimeallowedType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="13"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="timelimitactionType">
<xsd:restriction base="stringType">
<xsd:enumeration value="exit,no message"/>
<xsd:enumeration value="exit,message"/>
<xsd:enumeration value="continue,no message"/>
<xsd:enumeration value="continue,message"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="datafromlmsType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="255"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="masteryscoreType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="200"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="stringType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="prerequisiteStringType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="200"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,714 @@
/**
* Wrapper to the SCORM API provided by Chamilo
* The complete set of functions and variables are in this file to avoid unnecessary file
* accesses.
* Only event triggers and answer data are inserted into the final document.
* @author Yannick Warnier - inspired by the ADLNet documentation on SCORM content-side API
* @package scorm.js
*/
/**
* Initialisation of the SCORM API section.
* Find the SCO functions (startTimer, computeTime, etc in the second section)
* Find the Chamilo-proper functions (checkAnswers, etc in the third section)
*/
var _debug = true;
var findAPITries = 0;
var _apiHandle = null; //private variable
var errMsgLocate = "Unable to locate the LMS's API implementation";
var _NoError = 0;
var _GeneralException = 101;
var _ServerBusy = 102;
var _InvalidArgumentError = 201;
var _ElementCannotHaveChildren = 202;
var _ElementIsNotAnArray = 203;
var _NotInitialized = 301;
var _NotImplementedError = 401;
var _InvalidSetValue = 402;
var _ElementIsReadOnly = 403;
var _ElementIsWriteOnly = 404;
var _IncorrectDataType = 405;
var startTime;
var exitPageStatus;
/**
* Gets the API handle right into the local API object and ensure there is only one.
* Using the singleton pattern to ensure there's only one API object.
* @return object The API object as given by the LMS
*/
var API = new function()
{
if (_apiHandle == null) {
_apiHandle = getAPI();
}
return _apiHandle;
}
/**
* Finds the API on the LMS side or gives up giving an error message
* @param object The window/frame object in which we are searching for the SCORM API
* @return object The API object recovered from the LMS's implementation of the SCORM API
*/
function findAPI(win)
{
while((win.API == null) && (win.parent != null) && (win.parent != win)) {
findAPITries++;
if (findAPITries>10) {
alert("Error finding API - too deeply nested");
return null;
}
win = win.parent
}
return win.API;
}
/**
* Gets the API from the current window/frame or from parent objects if not found
* @return object The API object recovered from the LMS's implementation of the SCORM API
*/
function getAPI()
{
//window is the global/root object of the current window/frame
var MyAPI = findAPI(window);
//look through parents if any
if ((MyAPI == null) && (window.opener != null) && (typeof(window.opener) != "undefined")) {
MyAPI = findAPI(window.opener);
}
//still not found? error message
if (MyAPI == null) {
alert("Unable to find SCORM API adapter.\nPlease check your LMS is considering this page as SCORM and providing the right JavaScript interface.")
}
return MyAPI;
}
/**
* Handles error codes (prints the error if it has a description)
* @return int Error code from LMS's API
*/
function errorHandler()
{
if (API == null) {
alert("Unable to locate the LMS's API. Cannot determine LMS error code");
return;
}
var errCode = API.LMSGetLastError().toString();
if (errCode != _NoError) {
if (errCode == _NotImplementedError) {
var errDescription = "The LMS doesn't support this feature";
if (_debug) {
errDescription += "\n";
errDescription += api.LMSGetDiagnostic(null);
}
addDebug(errDescription);
} else {
var errDescription = API.LMSGetErrorString(errCode);
if (_debug) {
errDescription += "\n";
errDescription += api.LMSGetDiagnostic(null);
}
addDebug(errDescription);
}
}
return errCode;
}
function addDebug(message) {
if (_debug && window.console) {
console.log(message);
}
}
function addDebugTable(message) {
if (_debug && window.console) {
console.table(message);
}
}
/**
* Calls the LMSInitialize method of the LMS's API object
* @return string The string value of the LMS returned value or false if error (should be "true" otherwise)
*/
function doLMSInitialize()
{
if (API == null) {
alert(errMsgLocate + "\nLMSInitialize failed");
return false;
}
var result = API.LMSInitialize("");
if (result.toString() != "true") {
var err = errorHandler();
}
return result.toString();
}
/**
* Calls the LMSFinish method of the LMS's API object
* @return string The string value of the LMS return value, or false if error (should be "true" otherwise)
*/
function doLMSFinish()
{
if (API == null) {
alert(errMsgLocate + "\nLMSFinish failed");
return false;
} else {
var result = API.LMSFinish('');
if (result.toString() != "true") {
var err = errorHandler();
}
}
return result.toString();
}
/**
* Calls the LMSGetValue method
* @param string The name of the SCORM parameter to get
* @return string The value returned by the LMS
*/
function doLMSGetValue(name)
{
if (API == null) {
alert(errMsgLocate + "\nLMSGetValue was not successful.");
return '';
} else {
var value = API.LMSGetValue(name);
var errCode = API.LMSGetLastError().toString();
if (errCode != _NoError) {
// an error was encountered so display the error description
var errDescription = API.LMSGetErrorString(errCode);
addDebug("LMSGetValue(" + name + ") failed. \n" + errDescription)
return '';
}
return value.toString();
}
}
/**
* Calls the LMSSetValue method of the API object
* @param string The name of the SCORM parameter to set
* @param string The value to set the parameter to
* @return void
*/
function doLMSSetValue(name, value)
{
if (API == null) {
alert("Unable to locate the LMS's API Implementation.\nLMSSetValue was not successful.");
return;
} else {
var result = API.LMSSetValue(name, value);
if (result.toString() != "true") {
var err = errorHandler();
}
}
return;
}
/**
* Calls the LMSCommit method
*/
function doLMSCommit()
{
if (API == null) {
alert(errMsgLocate + "\nLMSCommit was not successful.");
return "false";
} else {
var result = API.LMSCommit("");
if (result != "true") {
var err = errorHandler();
}
}
return result.toString();
}
/**
* Calls GetLastError()
*/
function doLMSGetLastError()
{
if (API == null) {
alert(errMsgLocate + "\nLMSGetLastError was not successful.");
//since we can't get the error code from the LMS, return a general error
return _GeneralError;
}
return API.LMSGetLastError().toString();
}
/**
* Calls LMSGetErrorString()
*/
function doLMSGetErrorString(errorCode)
{
if (API == null) {
alert(errMsgLocate + "\nLMSGetErrorString was not successful.");
}
return API.LMSGetErrorString(errorCode).toString();
}
/**
* Calls LMSGetDiagnostic()
*/
function doLMSGetDiagnostic(errorCode)
{
if (API == null) {
alert(errMsgLocate + "\nLMSGetDiagnostic was not successful.");
}
return API.LMSGetDiagnostic(errorCode).toString();
}
/**
* Initialise page values
*/
function loadPage()
{
var result = doLMSInitialize();
if (result) {
var status = doLMSGetValue("cmi.core.lesson_status");
if (status == "not attempted") {
doLMSSetValue("cmi.core.lesson_status", "incomplete");
}
exitPageStatus = false;
startTimer();
}
}
/**
* Starts the local timer
*/
function startTimer()
{
startTime = new Date().getTime();
}
/**
* Calculates the total time and sends the result to the LMS
*/
function computeTime()
{
if (startTime != 0) {
var currentDate = new Date().getTime();
var elapsedSeconds = ( (currentDate - startTime) / 1000 );
var formattedTime = convertTotalSeconds(elapsedSeconds);
} else {
formattedTime = "00:00:00.0";
}
doLMSSetValue( "cmi.core.session_time", formattedTime );
}
/**
* Formats the time in a SCORM time format
*/
function convertTotalSeconds(ts)
{
var sec = (ts % 60);
ts -= sec;
var tmp = (ts % 3600); //# of seconds in the total # of minutes
ts -= tmp; //# of seconds in the total # of hours
// convert seconds to conform to CMITimespan type (e.g. SS.00)
sec = Math.round(sec*100)/100;
var strSec = new String(sec);
var strWholeSec = strSec;
var strFractionSec = "";
if (strSec.indexOf(".") != -1) {
strWholeSec = strSec.substring(0, strSec.indexOf("."));
strFractionSec = strSec.substring(strSec.indexOf(".") + 1, strSec.length);
}
if (strWholeSec.length < 2) {
strWholeSec = "0" + strWholeSec;
}
strSec = strWholeSec;
if (strFractionSec.length) {
strSec = strSec + "." + strFractionSec;
}
if ((ts % 3600) != 0)
var hour = 0;
else var hour = (ts / 3600);
if ((tmp % 60) != 0)
var min = 0;
else var min = (tmp / 60);
if ((new String(hour)).length < 2)
hour = "0" + hour;
if ((new String(min)).length < 2)
min = "0" + min;
var rtnVal = hour + ":" + min + ":" + strSec;
return rtnVal
}
/**
* Handles the use of the back button (saves data and closes SCO)
*/
function doBack()
{
checkAnswers(true);
doLMSSetValue( "cmi.core.exit", "suspend" );
computeTime();
exitPageStatus = true;
var result;
result = doLMSCommit();
result = doLMSFinish();
}
/**
* Handles the closure of the current SCO before an interruption. This is only useful if the LMS
* deals with the cmi.core.exit, cmi.core.lesson_status and cmi.core.lesson_mode *and* the SCO
* sends some kind of value for cmi.core.exit, which is not the case here (yet).
*/
function doContinue(status)
{
// Reinitialize Exit to blank
doLMSSetValue( "cmi.core.exit", "" );
var mode = doLMSGetValue( "cmi.core.lesson_mode" );
if ( mode != "review" && mode != "browse" )
{
doLMSSetValue( "cmi.core.lesson_status", status );
}
computeTime();
exitPageStatus = true;
var result;
result = doLMSCommit();
result = doLMSFinish();
}
/**
* handles the recording of everything on a normal shutdown
*/
function doQuit()
{
checkAnswers();
computeTime();
exitPageStatus = true;
var result;
result = doLMSCommit();
result = doLMSFinish();
}
/**
* Called upon unload event from body element
*/
function unloadPage(status)
{
if (!exitPageStatus)
{
// doQuit( status );
}
}
/**
* Checks the answers on the test formular page
*/
function checkAnswers(interrupted)
{
var tmpScore = 0;
var status = 'not attempted';
var scoreMax = 0;
addDebug('Number of questions: '+ questions.length);
for (var i=0; i < questions.length; i++) {
if (questions[i] != undefined && questions[i] != null) {
var idQuestion = questions[i];
var type = questions_types[idQuestion];
var interactionScore = 0;
var interactionAnswers = '';
var interactionCorrectResponses = '';
var interactionType = '';
addDebug('idQuestion: ' +idQuestion + ', Type: ' +type);
addDebug('questions_answers: ');
addDebugTable(questions_answers[idQuestion]);
addDebug('questions_answers_ponderation: ');
addDebugTable(questions_answers_ponderation[idQuestion]);
addDebug('questions_answers_correct: ');
addDebugTable(questions_answers_correct[idQuestion]);
switch (type) {
case 'mcma':
interactionType = 'choice';
var myScore = 0;
for(var j = 0; j< questions_answers[idQuestion].length;j++) {
var idAnswer = questions_answers[idQuestion][j];
var answer = document.getElementById('question_'+(idQuestion)+'_multiple_'+(idAnswer));
if (answer.checked) {
interactionAnswers += idAnswer+'__|';// changed by isaac flores
myScore += questions_answers_ponderation[idQuestion][idAnswer];
}
}
interactionScore = myScore;
scoreMax += questions_score_max[idQuestion];
addDebug("Score: "+myScore);
break;
case 'mcua':
interactionType = 'choice';
var myScore = 0;
for (var j = 0; j<questions_answers[idQuestion].length; j++) {
var idAnswer = questions_answers[idQuestion][j];
var elementId = 'question_'+(idQuestion)+'_unique_'+(idAnswer);
var answer = document.getElementById(elementId);
if (answer.checked) {
addDebug('Element id # "'+ elementId +'" was checked');
interactionAnswers += idAnswer;
addDebug("List of correct answers: "+questions_answers_correct[idQuestion]);
addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
addDebug("idAnswer: "+idAnswer);
addDebug("Option selected: "+questions_answers_correct[idQuestion][idAnswer]);
if (questions_answers_correct[idQuestion][idAnswer] == 1) {
if (questions_answers_ponderation[idQuestion][idAnswer]) {
myScore += questions_answers_ponderation[idQuestion][idAnswer];
} else {
myScore++;
}
}
}
}
addDebug("Score: "+myScore);
interactionScore = myScore;
scoreMax += questions_score_max[idQuestion];
break;
case 'tf':
interactionType = 'true-false';
var myScore = 0;
for (var j = 0; j < questions_answers[idQuestion].length; j++) {
var idAnswer = questions_answers[idQuestion][j];
var answer = document.getElementById('question_' + idQuestion + '_tf_' + (idAnswer));
if (answer.checked.value) {
interactionAnswers += idAnswer;
for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
if (questions_answers_correct[idQuestion][k] == idAnswer) {
if (questions_answers_ponderation[idQuestion][idAnswer]) {
myScore += questions_answers_ponderation[idQuestion][idAnswer];
} else {
myScore++;
}
}
}
}
}
addDebug("Score: "+ myScore);
interactionScore = myScore;
scoreMax += questions_score_max[idQuestion];
break;
case 'fib':
interactionType = 'fill-in';
var myScore = 0;
for (var j = 0; j < questions_answers[idQuestion].length; j++) {
var idAnswer = questions_answers[idQuestion][j];
var answer = document.getElementById('question_'+(idQuestion)+'_fib_'+(idAnswer));
if (answer.value) {
interactionAnswers += answer.value + '__|';//changed by isaac flores
for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
if (questions_answers_correct[idQuestion][k] == answer.value) {
if (questions_answers_ponderation[idQuestion][idAnswer]) {
myScore += questions_answers_ponderation[idQuestion][idAnswer];
} else {
myScore++;
}
}
}
}
}
addDebug("Score: "+myScore);
interactionScore = myScore;
scoreMax += questions_score_max[idQuestion];
break;
case 'matching':
interactionType = 'matching';
var myScore = 0;
addDebug("List of correct answers: ");
console.log(questions_answers_correct[idQuestion]);
for (var j = 0; j < questions_answers[idQuestion].length; j++) {
var idAnswer = questions_answers[idQuestion][j];
var elementId = 'question_' + (idQuestion) + '_matching_' + (idAnswer);
addDebug("---------idAnswer #"+idAnswer+'------------------');
addDebug("Checking element #"+elementId);
var answer = document.getElementById(elementId);
if (answer && answer.value) {
interactionAnswers += answer.value + '__|';//changed by isaac flores
for (k = 0; k < questions_answers_correct[idQuestion].length; k++) {
var left = questions_answers_correct[idQuestion][k][0];
var right = questions_answers_correct[idQuestion][k][1];
addDebug('Left ' + left);
addDebug('Right ' + right);
addDebug('answer.value ' + answer.value);
if (right == idAnswer && left == answer.value) {
addDebug('Score for this answer: ' + questions_answers_ponderation[idQuestion][idAnswer]);
if (questions_answers_ponderation[idQuestion][idAnswer]) {
myScore += questions_answers_ponderation[idQuestion][idAnswer];
} else {
// myScore++;
}
}
}
}
addDebug("Partial score: "+myScore);
addDebug("--------- end --- idAnswer #"+idAnswer+'------------------');
}
addDebug("Score: "+myScore);
interactionScore = myScore;
scoreMax += questions_score_max[idQuestion];
break;
case 'free':
//ignore for now as a score cannot be given
interactionType = 'free';
var answer = document.getElementById('question_'+(idQuestion)+'_free');
if (answer && answer.value) {
interactionAnswers += answer.value
}
//interactionScore = questions_score_max[idQuestion];
interactionScore = 0;
scoreMax += questions_score_max[idQuestion];
//interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').value;
//correct responses work by pattern, see SCORM Runtime Env Doc
//interactionCorrectResponses += questions_answers_correct[idQuestion].toString();
break;
case 'hotspot':
interactionType = 'sequencing';
interactionScore = 0;
//if(question_score && question_score[idQuestion]){
// interactionScore = question_score[idQuestion];
//} //else, 0
//interactionAnswers = document.getElementById('question_'+(idQuestion)+'_free').innerHTML;
//correct responses work by pattern, see SCORM Runtime Env Doc
//for(k=0;k<questions_answers_correct[idQuestion].length;k++)
//{
// interactionCorrectResponses += questions_answers_correct[idQuestion][k].toString()+',';
//}
break;
case 'exact':
interactionType = 'exact';
interactionScore = 0;
var real_answers = new Array();
for (var j = 0; j < questions_answers[idQuestion].length; j++) {
var idAnswer = questions_answers[idQuestion][j];
var answer = document.getElementById('question_' + (idQuestion) + '_exact_' + (idAnswer));
if (answer.checked == true) {
interactionAnswers += idAnswer+', ';
if (questions_answers_correct[idQuestion][idAnswer] != 0) {
real_answers[j] = true;
} else {
real_answers[j] = false;
}
} else {
if (questions_answers_correct[idQuestion][idAnswer] != 0) {
real_answers[j] = false;
} else {
real_answers[j] = true;
}
}
}
var final_answer = true;
for (var z = 0; z < real_answers.length; z++) {
if (real_answers[z] == false) {
final_answer = false;
}
}
interactionScore = 0;
addDebug(real_answers);
if (final_answer) {
//getting only the first score where we save the weight of all the question
interactionScore = questions_answers_ponderation[idQuestion][1];
}
addDebug("Score: "+interactionScore);
scoreMax += questions_score_max[idQuestion];
break;
}
tmpScore += interactionScore;
doLMSSetValue('cmi.interactions.'+idQuestion+'.id', 'Q'+idQuestion);
doLMSSetValue('cmi.interactions.'+idQuestion+'.type', interactionType);
doLMSSetValue('cmi.interactions.'+idQuestion+'.student_response', interactionAnswers);
doLMSSetValue('cmi.interactions.'+idQuestion+'.result', interactionScore);
}
}
doLMSSetValue('cmi.core.score.min', 0);
doLMSSetValue('cmi.core.score.max', scoreMax);
doLMSSetValue('cmi.core.score.raw', tmpScore);
// Get status
var mastery_score = doLMSGetValue('cmi.student_data.mastery_score');
if (mastery_score <= 0) {
mastery_score = (scoreMax * 0.80);
}
if (tmpScore >= mastery_score) {
status = 'passed';
} else {
status = 'failed';
}
addDebug('student_score: ' + tmpScore);
addDebug('mastery_score: ' + mastery_score);
addDebug('cmi.core.score.max: ' + scoreMax);
addDebug('cmi.core.lesson_status: ' + status);
doLMSSetValue('cmi.core.lesson_status', status);
if (interrupted && (status != 'completed') && (status != 'passed')) {
doLMSSetValue('cmi.core.exit', 'suspended');
}
return false; //do not submit the form
}
(function($){
//Shuffle all rows, while keeping the first column
//Requires: Shuffle
$.fn.shuffleRows = function(){
return this.each(function(){
var main = $(/table/i.test(this.tagName) ? this.tBodies[0] : this);
var firstElem = [], counter=0;
main.children().each(function(){
firstElem.push(this.firstChild);
});
main.shuffle();
main.children().each(function(){
this.insertBefore(firstElem[counter++], this.firstChild);
});
});
}
/* Shuffle is required */
$.fn.shuffle = function() {
return this.each(function(){
var items = $(this).children();
return (items.length)
? $(this).html($.shuffle(items))
: this;
});
}
$.shuffle = function(arr) {
for(
var j, x, i = arr.length; i;
j = parseInt(Math.random() * i),
x = arr[--i], arr[i] = arr[j], arr[j] = x
);
return arr;
}
})(jQuery);
/*
* Assigns any event handler to any element
* @param object Element on which the event is added
* @param string Name of event
* @param string Function to trigger on event
* @param boolean Capture the event and prevent
*/
function addEvent(elm, evType, fn, useCapture)
{
if (elm.addEventListener) {
elm.addEventListener(evType, fn, useCapture);
return true;
} else if(elm.attachEvent) {
var r = elm.attachEvent('on' + evType, fn);
return r;
} else {
elm['on' + evType] = fn;
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- filename=ims_xml.xsd -->
<xsd:schema xmlns="http://www.w3.org/XML/1998/namespace"
targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified">
<!-- 2001-02-22 edited by Thomas Wason IMS Global Learning Consortium, Inc. -->
<xsd:annotation>
<xsd:documentation>In namespace-aware XML processors, the &quot;xml&quot; prefix is bound to the namespace name http://www.w3.org/XML/1998/namespace.</xsd:documentation>
<xsd:documentation>Do not reference this file in XML instances</xsd:documentation>
<xsd:documentation>Schawn Thropp: Changed the uriReference type to string type</xsd:documentation>
</xsd:annotation>
<xsd:attribute name="lang" type="xsd:language">
<xsd:annotation>
<xsd:documentation>Refers to universal XML 1.0 lang attribute</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="base" type="xsd:string">
<xsd:annotation>
<xsd:documentation>Refers to XML Base: http://www.w3.org/TR/xmlbase</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="link" type="xsd:string"/>
</xsd:schema>

View File

@@ -0,0 +1,345 @@
<?xml version="1.0"?>
<!-- edited with XML Spy v3.5 (http://www.xmlspy.com) by Thomas Wason (private) -->
<!-- filename=ims_cp_rootv1p1p2.xsd -->
<!-- Copyright (2) 2001 IMS Global Learning Consortium, Inc. -->
<!-- edited by Thomas Wason -->
<!-- Conforms to w3c http://www.w3.org/TR/xmlschema-1/ 2000-10-24-->
<xsd:schema xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
targetNamespace="http://www.imsproject.org/xsd/imscp_rootv1p1p2"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified" version="IMS CP 1.1.2">
<!-- ******************** -->
<!-- ** Change History ** -->
<!-- ******************** -->
<xsd:annotation>
<xsd:documentation xml:lang="en">DRAFT XSD for IMS Content Packaging version 1.1 DRAFT</xsd:documentation>
<xsd:documentation> Copyright (c) 2001 IMS GLC, Inc. </xsd:documentation>
<xsd:documentation>2000-04-21, Adjustments by T.D. Wason from CP 1.0.</xsd:documentation>
<xsd:documentation>2001-02-22, T.D.Wason: Modify for 2000-10-24 XML-Schema version. Modified to support extension.</xsd:documentation>
<xsd:documentation>2001-03-12, T.D.Wason: Change filename, target and meta-data namespaces and meta-data fielname. Add meta-data to itemType, fileType and organizationType.</xsd:documentation>
<xsd:documentation>Do not define namespaces for xml in XML instances generated from this xsd.</xsd:documentation>
<xsd:documentation>Imports IMS meta-data xsd, lower case element names. </xsd:documentation>
<xsd:documentation>This XSD provides a reference to the IMS meta-data root element as imsmd:record</xsd:documentation>
<xsd:documentation>If the IMS meta-data is to be used in the XML instance then the instance must define an IMS meta-data prefix with a namespace. The meta-data targetNamespace should be used. </xsd:documentation>
<xsd:documentation>2001-03-20, Thor Anderson: Remove manifestref, change resourceref back to identifierref, change manifest back to contained by manifest. --Tom Wason: manifest may contain _none_ or more manifests.</xsd:documentation>
<xsd:documentation>2001-04-13 Tom Wason: corrected attirbute name structure. Was misnamed type. </xsd:documentation>
<xsd:documentation>2001-05-14 Schawn Thropp: Made all complexType extensible with the group.any</xsd:documentation>
<xsd:documentation>Added the anyAttribute to all complexTypes. Changed the href attribute on the fileType and resourceType to xsd:string</xsd:documentation>
<xsd:documentation>Changed the maxLength of the href, identifierref, parameters, structure attributes to match the Information model.</xsd:documentation>
<xsd:documentation>2001-07-25 Schawn Thropp: Changed the namespace for the Schema of Schemas to the 5/2/2001 W3C XML Schema</xsd:documentation>
<xsd:documentation>Recommendation. attributeGroup attr.imsmd deleted, was not used anywhere. Any attribute declarations that have</xsd:documentation>
<xsd:documentation>use = "default" changed to use="optional" - attr.structure.req.</xsd:documentation>
<xsd:documentation>Any attribute declarations that have value="somevalue" changed to default="somevalue",</xsd:documentation>
<xsd:documentation>attr.structure.req (hierarchical). Removed references to IMS MD Version 1.1.</xsd:documentation>
<xsd:documentation>Modified attribute group "attr.resourcetype.req" to change use from optional</xsd:documentation>
<xsd:documentation>to required to match the information model. As a result the default value also needed to be removed</xsd:documentation>
<xsd:documentation>Name change for XSD. Changed to match version of CP Spec </xsd:documentation>
</xsd:annotation>
<xsd:annotation>
<xsd:documentation>Inclusions and Imports</xsd:documentation>
</xsd:annotation>
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="ims_xml.xsd"/>
<xsd:annotation>
<xsd:documentation>Attribute Declarations</xsd:documentation>
</xsd:annotation>
<!-- **************************** -->
<!-- ** Attribute Declarations ** -->
<!-- **************************** -->
<xsd:attributeGroup name="attr.base">
<xsd:attribute ref="xml:base" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.default">
<xsd:attribute name="default" type="xsd:IDREF" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.href">
<xsd:attribute name="href" use="optional">
<xsd:simpleType>
<xsd:restriction base="xsd:anyURI">
<xsd:maxLength value="2000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.href.req">
<xsd:attribute name="href" use="required">
<xsd:simpleType>
<xsd:restriction base="xsd:anyURI">
<xsd:maxLength value="2000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.identifier.req">
<xsd:attribute name="identifier" type="xsd:ID" use="required"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.identifier">
<xsd:attribute name="identifier" type="xsd:ID" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.isvisible">
<xsd:attribute name="isvisible" type="xsd:boolean" use="optional"/>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.parameters">
<xsd:attribute name="parameters" use="optional">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="1000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.identifierref">
<xsd:attribute name="identifierref" use="optional">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="2000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.identifierref.req">
<xsd:attribute name="identifierref" use="required">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="2000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.resourcetype.req">
<xsd:attribute name="type" use="required">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="1000"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.structure.req">
<xsd:attribute name="structure" use="optional" default="hierarchical">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="200"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:attributeGroup name="attr.version">
<xsd:attribute name="version" use="optional">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:maxLength value="20"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:annotation>
<xsd:documentation>element groups</xsd:documentation>
</xsd:annotation>
<xsd:group name="grp.any">
<xsd:annotation>
<xsd:documentation>Any namespaced element from any namespace may be included within an &quot;any&quot; element. The namespace for the imported element must be defined in the instance, and the schema must be imported. </xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:group>
<!-- ************************** -->
<!-- ** Element Declarations ** -->
<!-- ************************** -->
<xsd:element name="dependency" type="dependencyType"/>
<xsd:element name="file" type="fileType"/>
<xsd:element name="item" type="itemType"/>
<xsd:element name="manifest" type="manifestType"/>
<xsd:element name="metadata" type="metadataType"/>
<xsd:element name="organization" type="organizationType"/>
<xsd:element name="organizations" type="organizationsType"/>
<xsd:element name="resource" type="resourceType"/>
<xsd:element name="resources" type="resourcesType"/>
<xsd:element name="schema" type="schemaType"/>
<xsd:element name="schemaversion" type="schemaversionType"/>
<xsd:element name="title" type="titleType"/>
<!-- ******************* -->
<!-- ** Complex Types ** -->
<!-- ******************* -->
<!-- **************** -->
<!-- ** dependency ** -->
<!-- **************** -->
<xsd:complexType name="dependencyType">
<xsd:sequence>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.identifierref.req"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ********** -->
<!-- ** file ** -->
<!-- ********** -->
<xsd:complexType name="fileType">
<xsd:sequence>
<xsd:element ref="metadata" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.href.req"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ********** -->
<!-- ** item ** -->
<!-- ********** -->
<xsd:complexType name="itemType">
<xsd:sequence>
<xsd:element ref="title" minOccurs="0"/>
<xsd:element ref="item" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="metadata" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.identifier.req"/>
<xsd:attributeGroup ref="attr.identifierref"/>
<xsd:attributeGroup ref="attr.isvisible"/>
<xsd:attributeGroup ref="attr.parameters"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ************** -->
<!-- ** manifest ** -->
<!-- ************** -->
<xsd:complexType name="manifestType">
<xsd:sequence>
<xsd:element ref="metadata" minOccurs="0"/>
<xsd:element ref="organizations"/>
<xsd:element ref="resources"/>
<xsd:element ref="manifest" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.identifier.req"/>
<xsd:attributeGroup ref="attr.version"/>
<xsd:attribute ref="xml:base"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ************** -->
<!-- ** metadata ** -->
<!-- ************** -->
<xsd:complexType name="metadataType">
<xsd:sequence>
<xsd:element ref="schema" minOccurs="0"/>
<xsd:element ref="schemaversion" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<!-- ******************* -->
<!-- ** organizations ** -->
<!-- ******************* -->
<xsd:complexType name="organizationsType">
<xsd:sequence>
<xsd:element ref="organization" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.default"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ****************** -->
<!-- ** organization ** -->
<!-- ****************** -->
<xsd:complexType name="organizationType">
<xsd:sequence>
<xsd:element ref="title" minOccurs="0"/>
<xsd:element ref="item" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="metadata" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.identifier.req"/>
<xsd:attributeGroup ref="attr.structure.req"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- *************** -->
<!-- ** resources ** -->
<!-- *************** -->
<xsd:complexType name="resourcesType">
<xsd:sequence>
<xsd:element ref="resource" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.base"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ************** -->
<!-- ** resource ** -->
<!-- ************** -->
<xsd:complexType name="resourceType">
<xsd:sequence>
<xsd:element ref="metadata" minOccurs="0"/>
<xsd:element ref="file" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="dependency" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
<xsd:attributeGroup ref="attr.identifier.req"/>
<xsd:attributeGroup ref="attr.resourcetype.req"/>
<xsd:attributeGroup ref="attr.base"/>
<xsd:attributeGroup ref="attr.href"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- ****************** -->
<!-- ** Simple Types ** -->
<!-- ****************** -->
<!-- ************ -->
<!-- ** schema ** -->
<!-- ************ -->
<xsd:simpleType name="schemaType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="100"/>
</xsd:restriction>
</xsd:simpleType>
<!-- ******************* -->
<!-- ** schemaversion ** -->
<!-- ******************* -->
<xsd:simpleType name="schemaversionType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="20"/>
</xsd:restriction>
</xsd:simpleType>
<!-- *********** -->
<!-- ** title ** -->
<!-- *********** -->
<xsd:simpleType name="titleType">
<xsd:restriction base="xsd:string">
<xsd:maxLength value="200"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:schema>

View File

@@ -0,0 +1,573 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- edited by Thomas Wason -->
<xsd:schema targetNamespace="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1"
xmlns:xml="http://www.w3.org/XML/1998/namespace"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.imsglobal.org/xsd/imsmd_rootv1p2p1"
elementFormDefault="qualified"
version="1.2:1.1 IMS:MD1.2">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="ims_xml.xsd"/>
<!-- ******************** -->
<!-- ** Change History ** -->
<!-- ******************** -->
<xsd:annotation>
<xsd:documentation>2001-04-26 T.D.Wason. IMS meta-data 1.2 XML-Schema. </xsd:documentation>
<xsd:documentation>2001-06-07 S.E.Thropp. Changed the multiplicity on all elements to match the </xsd:documentation>
<xsd:documentation>Final 1.2 Binding Specification. </xsd:documentation>
<xsd:documentation>Changed all elements that use the langstringType to a multiplicy of 1 or more </xsd:documentation>
<xsd:documentation>Changed centity in the contribute element to have a multiplicity of 0 or more. </xsd:documentation>
<xsd:documentation>Changed the requirement element to have a multiplicity of 0 or more. </xsd:documentation>
<xsd:documentation> 2001-07-25 Schawn Thropp. Updates to bring the XSD up to speed with the W3C </xsd:documentation>
<xsd:documentation> XML Schema Recommendation. The following changes were made: Change the </xsd:documentation>
<xsd:documentation> namespace to reference the 5/2/2001 W3C XML Schema Recommendation,the base </xsd:documentation>
<xsd:documentation> type for the durtimeType, simpleType, was changed from timeDuration to duration. </xsd:documentation>
<xsd:documentation> Any attribute declarations that have use="default" had to change to use="optional" </xsd:documentation>
<xsd:documentation> - attr.type. Any attribute declarations that have value ="somevalue" had to change </xsd:documentation>
<xsd:documentation> to default = "somevalue" - attr.type (URI) </xsd:documentation>
<xsd:documentation> 2001-09-04 Schawn Thropp </xsd:documentation>
<xsd:documentation> Changed the targetNamespace and namespace of schema to reflect version change </xsd:documentation>
</xsd:annotation>
<!-- *************************** -->
<!-- ** Attribute Declaration ** -->
<!-- *************************** -->
<xsd:attributeGroup name="attr.type">
<xsd:attribute name="type" use="optional" default="URI">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="URI"/>
<xsd:enumeration value="TEXT"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:group name="grp.any">
<xsd:annotation>
<xsd:documentation>Any namespaced element from any namespace may be used for an &quot;any&quot; element. The namespace for the imported element must be defined in the instance, and the schema must be imported. </xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:any namespace="##any" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:group>
<!-- ************************* -->
<!-- ** Element Declaration ** -->
<!-- ************************* -->
<xsd:element name="aggregationlevel" type="aggregationlevelType"/>
<xsd:element name="annotation" type="annotationType"/>
<xsd:element name="catalogentry" type="catalogentryType"/>
<xsd:element name="catalog" type="catalogType"/>
<xsd:element name="centity" type="centityType"/>
<xsd:element name="classification" type="classificationType"/>
<xsd:element name="context" type="contextType"/>
<xsd:element name="contribute" type="contributeType"/>
<xsd:element name="copyrightandotherrestrictions" type="copyrightandotherrestrictionsType"/>
<xsd:element name="cost" type="costType"/>
<xsd:element name="coverage" type="coverageType"/>
<xsd:element name="date" type="dateType"/>
<xsd:element name="datetime" type="datetimeType"/>
<xsd:element name="description" type="descriptionType"/>
<xsd:element name="difficulty" type="difficultyType"/>
<xsd:element name="educational" type="educationalType"/>
<xsd:element name="entry" type="entryType"/>
<xsd:element name="format" type="formatType"/>
<xsd:element name="general" type="generalType"/>
<xsd:element name="identifier" type="xsd:string"/>
<xsd:element name="intendedenduserrole" type="intendedenduserroleType"/>
<xsd:element name="interactivitylevel" type="interactivitylevelType"/>
<xsd:element name="interactivitytype" type="interactivitytypeType"/>
<xsd:element name="keyword" type="keywordType"/>
<xsd:element name="kind" type="kindType"/>
<xsd:element name="langstring" type="langstringType"/>
<xsd:element name="language" type="xsd:string"/>
<xsd:element name="learningresourcetype" type="learningresourcetypeType"/>
<xsd:element name="lifecycle" type="lifecycleType"/>
<xsd:element name="location" type="locationType"/>
<xsd:element name="lom" type="lomType"/>
<xsd:element name="maximumversion" type="minimumversionType"/>
<xsd:element name="metadatascheme" type="metadataschemeType"/>
<xsd:element name="metametadata" type="metametadataType"/>
<xsd:element name="minimumversion" type="maximumversionType"/>
<xsd:element name="name" type="nameType"/>
<xsd:element name="purpose" type="purposeType"/>
<xsd:element name="relation" type="relationType"/>
<xsd:element name="requirement" type="requirementType"/>
<xsd:element name="resource" type="resourceType"/>
<xsd:element name="rights" type="rightsType"/>
<xsd:element name="role" type="roleType"/>
<xsd:element name="semanticdensity" type="semanticdensityType"/>
<xsd:element name="size" type="sizeType"/>
<xsd:element name="source" type="sourceType"/>
<xsd:element name="status" type="statusType"/>
<xsd:element name="structure" type="structureType"/>
<xsd:element name="taxon" type="taxonType"/>
<xsd:element name="taxonpath" type="taxonpathType"/>
<xsd:element name="technical" type="technicalType"/>
<xsd:element name="title" type="titleType"/>
<xsd:element name="type" type="typeType"/>
<xsd:element name="typicalagerange" type="typicalagerangeType"/>
<xsd:element name="typicallearningtime" type="typicallearningtimeType"/>
<xsd:element name="value" type="valueType"/>
<xsd:element name="person" type="personType"/>
<xsd:element name="vcard" type="xsd:string"/>
<xsd:element name="version" type="versionType"/>
<xsd:element name="installationremarks" type="installationremarksType"/>
<xsd:element name="otherplatformrequirements" type="otherplatformrequirementsType"/>
<xsd:element name="duration" type="durationType"/>
<xsd:element name="id" type="idType"/>
<!-- ******************* -->
<!-- ** Complex Types ** -->
<!-- ******************* -->
<xsd:complexType name="aggregationlevelType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="annotationType" mixed="true">
<xsd:sequence>
<xsd:element ref="person" minOccurs="0"/>
<xsd:element ref="date" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="catalogentryType" mixed="true">
<xsd:sequence>
<xsd:element ref="catalog"/>
<xsd:element ref="entry"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="centityType">
<xsd:sequence>
<xsd:element ref="vcard"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="classificationType" mixed="true">
<xsd:sequence>
<xsd:element ref="purpose" minOccurs="0"/>
<xsd:element ref="taxonpath" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="description" minOccurs="0"/>
<xsd:element ref="keyword" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="contextType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="contributeType" mixed="true">
<xsd:sequence>
<xsd:element ref="role"/>
<xsd:element ref="centity" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="date" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="copyrightandotherrestrictionsType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="costType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="coverageType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="dateType">
<xsd:sequence>
<xsd:element ref="datetime" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="descriptionType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="difficultyType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="durationType">
<xsd:sequence>
<xsd:element ref="datetime" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="educationalType" mixed="true">
<xsd:sequence>
<xsd:element ref="interactivitytype" minOccurs="0"/>
<xsd:element ref="learningresourcetype" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="interactivitylevel" minOccurs="0"/>
<xsd:element ref="semanticdensity" minOccurs="0"/>
<xsd:element ref="intendedenduserrole" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="context" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="typicalagerange" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="difficulty" minOccurs="0"/>
<xsd:element ref="typicallearningtime" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
<xsd:element ref="language" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="entryType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="generalType" mixed="true">
<xsd:sequence>
<xsd:element ref="identifier" minOccurs="0"/>
<xsd:element ref="title" minOccurs="0"/>
<xsd:element ref="catalogentry" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="language" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="description" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="keyword" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="coverage" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="structure" minOccurs="0"/>
<xsd:element ref="aggregationlevel" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="installationremarksType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="intendedenduserroleType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="interactivitylevelType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="interactivitytypeType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="keywordType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="kindType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="langstringType">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute ref="xml:lang"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="learningresourcetypeType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="lifecycleType" mixed="true">
<xsd:sequence>
<xsd:element ref="version" minOccurs="0"/>
<xsd:element ref="status" minOccurs="0"/>
<xsd:element ref="contribute" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="locationType">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attributeGroup ref="attr.type"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="lomType">
<xsd:sequence>
<xsd:element ref="general" minOccurs="0"/>
<xsd:element ref="lifecycle" minOccurs="0"/>
<xsd:element ref="metametadata" minOccurs="0"/>
<xsd:element ref="technical" minOccurs="0"/>
<xsd:element ref="educational" minOccurs="0"/>
<xsd:element ref="rights" minOccurs="0"/>
<xsd:element ref="relation" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="annotation" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="classification" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="metametadataType" mixed="true">
<xsd:sequence>
<xsd:element ref="identifier" minOccurs="0"/>
<xsd:element ref="catalogentry" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="contribute" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="metadatascheme" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="language" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="nameType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="otherplatformrequirementsType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="personType">
<xsd:sequence>
<xsd:element ref="vcard"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="purposeType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="relationType" mixed="true">
<xsd:sequence>
<xsd:element ref="kind" minOccurs="0"/>
<xsd:element ref="resource" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="requirementType" mixed="true">
<xsd:sequence>
<xsd:element ref="type" minOccurs="0"/>
<xsd:element ref="name" minOccurs="0"/>
<xsd:element ref="minimumversion" minOccurs="0"/>
<xsd:element ref="maximumversion" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="resourceType" mixed="true">
<xsd:sequence>
<xsd:element ref="identifier" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
<xsd:element ref="catalogentry" minOccurs="0" maxOccurs="unbounded"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="rightsType" mixed="true">
<xsd:sequence>
<xsd:element ref="cost" minOccurs="0"/>
<xsd:element ref="copyrightandotherrestrictions" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="roleType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="semanticdensityType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="sourceType">
<xsd:sequence>
<xsd:element ref="langstring"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="statusType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="stringType">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute ref="xml:lang"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<xsd:complexType name="structureType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="taxonpathType">
<xsd:sequence>
<xsd:element ref="source" minOccurs="0"/>
<xsd:element ref="taxon" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="taxonType">
<xsd:sequence>
<xsd:element ref="id" minOccurs="0"/>
<xsd:element ref="entry" minOccurs="0"/>
<xsd:element ref="taxon" minOccurs="0" maxOccurs="1"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="technicalType" mixed="true">
<xsd:sequence>
<xsd:element ref="format" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="size" minOccurs="0"/>
<xsd:element ref="location" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="requirement" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element ref="installationremarks" minOccurs="0"/>
<xsd:element ref="otherplatformrequirements" minOccurs="0"/>
<xsd:element ref="duration" minOccurs="0"/>
<xsd:group ref="grp.any"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="titleType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="typeType">
<xsd:sequence>
<xsd:element ref="source"/>
<xsd:element ref="value"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="typicalagerangeType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="typicallearningtimeType">
<xsd:sequence>
<xsd:element ref="datetime" minOccurs="0"/>
<xsd:element ref="description" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="valueType">
<xsd:sequence>
<xsd:element ref="langstring"/>
</xsd:sequence>
</xsd:complexType>
<xsd:complexType name="versionType">
<xsd:sequence>
<xsd:element ref="langstring" minOccurs="1" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
<!-- ****************** -->
<!-- ** Simple Types ** -->
<!-- ****************** -->
<xsd:simpleType name="formatType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="sizeType">
<xsd:restriction base="xsd:int"/>
</xsd:simpleType>
<xsd:simpleType name="datetimeType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="idType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="metadataschemeType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="catalogType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="minimumversionType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
<xsd:simpleType name="maximumversionType">
<xsd:restriction base="xsd:string"/>
</xsd:simpleType>
</xsd:schema>

146
main/lp/packaging/xml.xsd Normal file
View File

@@ -0,0 +1,146 @@
<?xml version='1.0'?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace" xmlns:xs="http://www.w3.org/2001/XMLSchema" xml:lang="en">
<xs:annotation>
<xs:documentation>
See http://www.w3.org/XML/1998/namespace.html and
http://www.w3.org/TR/REC-xml for information about this namespace.
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
Note that local names in this namespace are intended to be defined
only by the World Wide Web Consortium or its subgroups. The
following names are currently defined in this namespace and should
not be used with conflicting semantics by any Working Group,
specification, or document instance:
base (as an attribute name): denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.
id (as an attribute name): denotes an attribute whose value
should be interpreted as if declared to be of type ID.
The xml:id specification is not yet a W3C Recommendation,
but this attribute is included here to facilitate experimentation
with the mechanisms it proposes. Note that it is _not_ included
in the specialAttrs attribute group.
lang (as an attribute name): denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.
space (as an attribute name): denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.
Father (in any context at all): denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
In appreciation for his vision, leadership and dedication
the W3C XML Plenary on this 10th day of February, 2000
reserves for Jon Bosak in perpetuity the XML name
xml:Father
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>This schema defines attributes and an attribute group
suitable for use by
schemas wishing to allow xml:base, xml:lang, xml:space or xml:id
attributes on elements they define.
To enable this, such a schema must import this schema
for the XML namespace, e.g. as follows:
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
Subsequently, qualified reference to any of the attributes
or the group defined below will have the desired effect, e.g.
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
will define a type which will schema-validate an instance
element with any of those attributes</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
http://www.w3.org/2005/08/xml.xsd.
At the date of issue it can also be found at
http://www.w3.org/2001/xml.xsd.
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML Schema
itself, or with the XML namespace itself. In other words, if the XML
Schema or XML namespaces change, the version of this document at
http://www.w3.org/2001/xml.xsd will change
accordingly; the version at
http://www.w3.org/2005/08/xml.xsd will not change.
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang">
<xs:annotation>
<xs:documentation>Attempting to install the relevant ISO 2- and 3-letter
codes as the enumerated possible values is probably never
going to be a realistic possibility. See
RFC 3066 at http://www.ietf.org/rfc/rfc3066.txt and the IANA registry
at http://www.iana.org/assignments/lang-tag-apps.htm for
further information.
The union allows for the 'un-declaration' of xml:lang with
the empty string.</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="xs:language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value=""/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="space">
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xmlbase/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>See http://www.w3.org/TR/xml-id/ for
information about this attribute.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
</xs:attributeGroup>
</xs:schema>

68
main/lp/readout_text.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
/* For licensing terms, see /license.txt */
use Chamilo\CourseBundle\Entity\CDocument;
/**
* Print a read-out text inside a session.
*/
$_in_course = true;
require_once __DIR__.'/../inc/global.inc.php';
$current_course_tool = TOOL_LEARNPATH;
api_protect_course_script(true);
$id = isset($_GET['id']) ? (int) $_GET['id'] : 0;
$lpId = isset($_GET['lp_id']) ? (int) $_GET['lp_id'] : 0;
$courseInfo = api_get_course_info();
$courseCode = $courseInfo['code'];
$courseId = $courseInfo['real_id'];
$userId = api_get_user_id();
$sessionId = api_get_session_id();
$em = Database::getManager();
$documentRepo = $em->getRepository('ChamiloCourseBundle:CDocument');
// This page can only be shown from inside a learning path
if (!$id && !$lpId) {
api_not_allowed(true);
}
/** @var CDocument $document */
$document = $documentRepo->findOneBy(['cId' => $courseId, 'iid' => $id]);
if (empty($document)) {
// Try with normal id
/** @var CDocument $document */
$document = $documentRepo->findOneBy(['cId' => $courseId, 'id' => $id]);
if (empty($document)) {
Display::return_message(get_lang('FileNotFound'), 'error');
exit;
}
}
$documentPathInfo = pathinfo($document->getPath());
$coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'];
$documentPath = '/document'.$document->getPath();
$documentText = file_get_contents($coursePath.$documentPath);
$documentText = api_remove_tags_with_space($documentText);
$wordsInfo = preg_split('/ |\n/', $documentText, -1, PREG_SPLIT_OFFSET_CAPTURE);
$words = [];
foreach ($wordsInfo as $wordInfo) {
$words[$wordInfo[1]] = nl2br($wordInfo[0]);
}
$htmlHeadXtra[] = '<script>
var words = '.json_encode($words, JSON_OBJECT_AS_ARRAY).',
wordsCount = '.count($words).'
</script>';
$htmlHeadXtra[] = api_get_js('readout_text/js/start.js');
$htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_JS_PATH).'readout_text/css/start.css');
$template = new Template(strip_tags($document->getTitle()));
$template->display_blank_template();

1165
main/lp/scorm.class.php Normal file

File diff suppressed because it is too large Load Diff

191
main/lp/scormItem.class.php Normal file
View File

@@ -0,0 +1,191 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Class scormItem
* This class handles the <item> elements from an imsmanifest file.
* Container for the scormItem class that deals with <item> elements in an imsmanifest file.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
class scormItem extends learnpathItem
{
public $identifier = '';
public $identifierref = '';
public $isvisible = '';
public $parameters = '';
public $title = '';
public $sub_items = [];
public $metadata;
//public $prerequisites = ''; - defined in learnpathItem.class.php
// Modified by Ivan Tcholakov, 06-FEB-2010.
//public $max_time_allowed = ''; //should be something like HHHH:MM:SS.SS
public $max_time_allowed = '00:00:00';
public $timelimitaction = '';
public $datafromlms = '';
public $mastery_score = '';
public $scorm_contact;
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormItem
* object from database records or from the DOM element given as parameter.
*
* @param string $type Type of construction needed ('db' or 'manifest', default = 'manifest')
* @param mixed $element Depending on the type given, DB id for the lp_item or reference to the DOM element
* @param int $course_id
*/
public function __construct($type, &$element, $course_id = 0)
{
if (isset($element)) {
// Parsing using PHP5 DOMXML methods.
switch ($type) {
case 'db':
parent::__construct($element, api_get_user_id(), $course_id);
$this->scorm_contact = false;
// TODO: Implement this way of metadata object creation.
break;
case 'manifest': // Do the same as the default.
default:
//if ($first_item->type == XML_ELEMENT_NODE) this is already check prior to the call to this function.
$children = $element->childNodes;
foreach ($children as $child) {
switch ($child->nodeType) {
case XML_ELEMENT_NODE:
switch ($child->tagName) {
case 'title':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->title = $child->firstChild->nodeValue;
}
break;
case 'max_score':
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->max_score = $child->firstChild->nodeValue;
}
break;
case 'maxtimeallowed':
case 'adlcp:maxtimeallowed':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->max_time_allowed = $child->firstChild->nodeValue;
}
break;
case 'prerequisites':
case 'adlcp:prerequisites':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->prereq_string = $child->firstChild->nodeValue;
}
break;
case 'timelimitaction':
case 'adlcp:timelimitaction':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->timelimitaction = $child->firstChild->nodeValue;
}
break;
case 'datafromlms':
case 'adlcp:datafromlms':
case 'adlcp:launchdata': //in some cases (Wouters)
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->datafromlms = $child->firstChild->nodeValue;
}
break;
case 'masteryscore':
case 'adlcp:masteryscore':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->mastery_score = $child->firstChild->nodeValue;
}
break;
case 'item':
$oItem = new scormItem('manifest', $child);
if ('' != $oItem->identifier) {
$this->sub_items[$oItem->identifier] = $oItem;
}
break;
case 'metadata':
$this->metadata = new scormMetadata('manifest', $child);
break;
}
break;
case XML_TEXT_NODE:
// This case is actually treated by looking into ELEMENT_NODEs above.
break;
}
}
if ($element->hasAttributes()) {
$attributes = $element->attributes;
//$keep_href = '';
foreach ($attributes as $attrib) {
switch ($attrib->name) {
case 'identifier':
$this->identifier = $attrib->value;
break;
case 'identifierref':
$this->identifierref = $attrib->value;
break;
case 'isvisible':
$this->isvisible = $attrib->value;
break;
case 'parameters':
$this->parameters = $attrib->value;
break;
}
}
}
}
// End parsing using PHP5 DOMXML methods.
}
}
/**
* Builds a flat list with the current item and calls itself recursively on all children.
*
* @param array Reference to the array to complete with the current item
* @param int Optional absolute order (pointer) of the item in this learning path
* @param int Optional relative order of the item at this level
* @param int Optional level. If not given, assumes it's level 0
*/
public function get_flat_list(&$list, &$abs_order, $rel_order = 1, $level = 0)
{
$list[] = [
'abs_order' => $abs_order,
'datafromlms' => $this->datafromlms,
'identifier' => $this->identifier,
'identifierref' => $this->identifierref,
'isvisible' => $this->isvisible,
'level' => $level,
'masteryscore' => $this->mastery_score,
'maxtimeallowed' => $this->max_time_allowed,
'metadata' => $this->metadata,
'parameters' => $this->parameters,
'prerequisites' => (!empty($this->prereq_string) ? $this->prereq_string : ''),
'rel_order' => $rel_order,
'timelimitaction' => $this->timelimitaction,
'title' => $this->title,
'max_score' => $this->max_score,
];
$abs_order++;
$i = 1;
foreach ($this->sub_items as $id => $dummy) {
$oSubitem = &$this->sub_items[$id];
$oSubitem->get_flat_list($list, $abs_order, $i, $level + 1);
$i++;
}
}
/**
* Save function. Uses the parent save function and adds a layer for SCORM.
*
* @param bool Save from URL params (1) or from object attributes (0)
*/
public function save($from_outside = true, $prereqs_complete = false)
{
parent::save($from_outside, $prereqs_complete);
// Under certain conditions, the scorm_contact should not be set, because no scorm signal was sent.
$this->scorm_contact = true;
}
}

View File

@@ -0,0 +1,101 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Container for the scormMetadata class, setup to hold information about the <metadata> element in imsmanifest files.
*/
/**
* scormMetadata class, handling each <metadata> element found in an imsmanifest file.
*/
class scormMetadata
{
public $lom = '';
public $schema = '';
public $schemaversion = '';
public $location = '';
public $text = '';
public $attribs = [];
/**
* Class constructor. Works in two different ways defined by the first element, being 'db' or 'manifest'.
* If 'db', then it is built using the information available in the Chamilo database. If 'manifest', then it
* is built using the element given as a parameter, expecting it to be a <metadata> element pointer from the
* DOM parser.
*
* @param string Type of creation required. Can be 'db' or 'manifest' (default)
* @param mixed Depending on the type, can be the DB ID of the learnpath item or
* the pointer to the <metadata> element in the imsmanifest.xml file
*/
public function __construct($type, &$element)
{
if (isset($element)) {
// Parsing using PHP5 DOMXML methods.
switch ($type) {
case 'db':
// TODO: Implement this way of metadata object creation.
break;
//break;
case 'manifest': // Do the same as the default.
$children = $element->childNodes;
foreach ($children as $child) {
switch ($child->nodeType) {
case XML_ELEMENT_NODE:
// Could be 'lom', 'schema', 'schemaversion' or 'location'.
switch ($child->tagName) {
case 'lom':
$childchildren = $child->childNodes;
foreach ($childchildren as $childchild) {
$this->lom = $childchild->nodeValue;
}
break;
case 'schema':
$childchildren = $child->childNodes;
foreach ($childchildren as $childchild) {
// There is generally only one child here.
$this->schema = $childchild->nodeValue;
}
break;
case 'schemaversion':
$childchildren = $child->childNodes;
foreach ($childchildren as $childchild) {
// There is generally only one child here.
$this->schemaversion = $childchild->nodeValue;
}
break;
case 'location':
$childchildren = $child->childNodes;
foreach ($childchildren as $childchild) {
// There is generally only one child here.
$this->location = $childchild->nodeValue;
}
break;
}
break;
case XML_TEXT_NODE:
if ('' != trim($child->textContent)) {
if (count(1 == $children)) {
// If this is the only child at this level and it is a content... save differently.
$this->text = $child->textContent;
} else {
$this->text[$element->tagName] = $child->textContent;
}
}
break;
}
}
$attributes = $element->attributes;
//$keep_href = '';
if (is_array($attributes)) {
foreach ($attributes as $attrib) {
if ('' != trim($attrib->value)) {
$this->attribs[$attrib->name] = $attrib->value;
}
}
}
//break;
}
// End parsing using PHP5 DOMXML methods.
}
}
}

View File

@@ -0,0 +1,157 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Container for the scormOrganization class.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
/**
* Class defining the <organization> tag in an imsmanifest.xml file.
*/
class scormOrganization
{
public $identifier = '';
public $structure = '';
public $title = '';
public $items = [];
public $metadata;
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'),
* will create a scormOrganization
* object from database records or from the DOM element given as parameter.
*
* @param string Type of construction needed ('db' or 'manifest', default = 'manifest')
* @param mixed Depending on the type given, DB id for the lp_item or reference to the DOM element
*/
public function __construct($type, &$element, $scorm_charset = 'UTF-8')
{
if (isset($element)) {
// Parsing using PHP5 DOMXML methods.
switch ($type) {
case 'db':
// TODO: Implement this way of metadata object creation.
break;
case 'manifest': // Do the same as the default.
default:
// this is already check prior to the call to this function.
$children = $element->childNodes;
foreach ($children as $child) {
switch ($child->nodeType) {
case XML_ELEMENT_NODE:
switch ($child->tagName) {
case 'item':
$oItem = new scormItem(
'manifest',
$child
);
if ('' != $oItem->identifier) {
$this->items[$oItem->identifier] = $oItem;
}
break;
case 'metadata':
$this->metadata = new scormMetadata(
'manifest',
$child
);
break;
case 'title':
$tmp_children = $child->childNodes;
if (1 == $tmp_children->length && '' != $child->firstChild->nodeValue) {
$this->title = api_utf8_decode(
api_html_entity_decode(
$child->firstChild->nodeValue,
ENT_QUOTES,
'UTF-8'
)
);
}
break;
}
break;
case XML_TEXT_NODE:
break;
}
}
if ($element->hasAttributes()) {
$attributes = $element->attributes;
//$keep_href = '';
foreach ($attributes as $attrib) {
switch ($attrib->name) {
case 'identifier':
$this->identifier = $attrib->value;
break;
case 'structure':
$this->structure = $attrib->value;
break;
}
}
}
}
// End parsing using PHP5 DOMXML methods.
}
}
/**
* Get a flat list of items in the organization.
*
* @return array Array containing an ordered list of all items with
* their level and all information related to each item
*/
public function get_flat_items_list()
{
$list = [];
$i = 1;
foreach ($this->items as $id => $dummy) {
$abs_order = 0;
// Passes the array as a pointer so it is modified in $list directly.
$this->items[$id]->get_flat_list($list, $abs_order, $i, 0);
$i++;
}
return $list;
}
/**
* Name getter.
*
* @return string Name or empty string
*/
public function get_name()
{
if (!empty($this->title)) {
return Database::escape_string($this->title);
} else {
return '';
}
}
/**
* Reference identifier getter.
*
* @return string Identifier or empty string
*/
public function get_ref()
{
if (!empty($this->identifier)) {
return Database::escape_string($this->identifier);
} else {
return '';
}
}
/**
* Sets the title element.
*
* @param string $title New title to set
*/
public function set_name($title)
{
if (!empty($title)) {
$this->title = Database::escape_string($title);
}
}
}

View File

@@ -0,0 +1,134 @@
<?php
/* For licensing terms, see /license.txt */
/**
* Container for the scormResource class.
*
* @author Yannick Warnier <ywarnier@beeznest.org>
*/
/**
* Class defining the <resource> tag in an imsmanifest.xml file.
*/
class scormResource
{
public $identifier = '';
public $type = 'webcontent';
//public $identifierref = '';
public $scormtype = 'sco'; // Fix problems with ENI content where asset is not defined.
public $base = '';
public $href = '';
public $metadata;
//public $file_href;
//public $file_metadata;
public $files = [];
public $dependencies = [];
/**
* Class constructor. Depending of the type of construction called ('db' or 'manifest'), will create a scormResource
* object from database records or from the DOM element given as parameter.
*
* @param string Type of construction needed ('db' or 'manifest', default = 'manifest')
* @param mixed Depending on the type given, DB id for the lp_item or reference to the DOM element
*/
public function __construct($type = 'manifest', &$element)
{
if (isset($element)) {
// Parsing using PHP5 DOMXML methods.
switch ($type) {
case 'db':
// TODO: Implement this way of metadata object creation.
return false;
case 'manifest': // Do the same as the default.
default:
//if ($first_item->type == XML_ELEMENT_NODE) this is already check prior to the call to this function.
$children = $element->childNodes;
if (is_array($children)) {
foreach ($children as $child) {
switch ($child->nodeType) {
case XML_ELEMENT_NODE:
switch ($child->tagName) {
case 'file':
//echo "Child is a file tag<br />\n";
$this->files[] = $child->getAttribute('href');
break;
case 'metadata':
//echo "Child is a metadata tag<br />\n";
$this->metadata = new scormMetadata('manifest', $child);
break;
case 'dependency':
// Need to get identifierref attribute inside dependency node.
// dependencies[] array represents all <dependency identifierref='x'> tags united.
$this->dependencies[] = $child->getAttribute('identifierref');
break;
}
break;
}
}
}
//$keep_href = '';
if ($element->hasAttributes()) { //in some cases we get here with an empty attributes array
// TODO: Find when and why we get such a case (empty array).
$attributes = $element->attributes;
foreach ($attributes as $attrib) {
switch ($attrib->name) {
case 'identifier':
$this->identifier = $attrib->value;
break;
case 'type':
if (!empty($attrib->value)) {
$this->type = $attrib->value;
}
break;
case 'scormtype':
if (!empty($attrib->value)) {
$this->scormtype = $attrib->value;
}
break;
case 'base':
$this->base = $attrib->value;
break;
case 'href':
$this->href = $attrib->value;
break;
}
}
}
return true;
}
// End parsing using PHP5 DOMXML methods.
}
return false;
}
/**
* Path getter.
*
* @return string Path for this resource
*/
public function get_path()
{
if (!empty($this->href)) {
return Database::escape_string($this->href);
} else {
return '';
}
}
/**
* Scorm type getter.
*
* @return string generally 'asset' or 'sco' as these are the only two values defined in SCORM 1.2
*/
public function get_scorm_type()
{
if (!empty($this->scormtype)) {
return Database::escape_string($this->scormtype);
} else {
return '';
}
}
}

2642
main/lp/scorm_api.php Normal file

File diff suppressed because it is too large Load Diff

306
main/lp/storageapi.php Normal file
View File

@@ -0,0 +1,306 @@
<?php
// Storage API
// PHP Backend
// CBlue SPRL, Jean-Karim Bockstael, <jeankarim@cblue.be>
require_once '../inc/global.inc.php';
// variable cleaning...
foreach (["svkey", "svvalue"] as $key) {
$_REQUEST[$key] = Database::escape_string($_REQUEST[$key]);
}
foreach (["svuser", "svcourse", "svsco", "svlength", "svasc"] as $key) {
$_REQUEST[$key] = intval($_REQUEST[$key]);
}
switch ($_REQUEST['action']) {
case "get":
print storage_get($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey']);
break;
case "set":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_set($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey'], $_REQUEST['svvalue']);
}
break;
case "getall":
print storage_getall($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco']);
break;
case "stackpush":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_stack_push($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey'], $_REQUEST['svvalue']);
}
break;
case "stackpop":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_stack_pop($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey']);
}
break;
case "stacklength":
print storage_stack_length($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey']);
break;
case "stackclear":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_stack_clear($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey']);
}
break;
case "stackgetall":
if (storage_can_set($_REQUEST['svuser'])) {
echo storage_stack_getall($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey']);
}
break;
case "getposition":
print storage_get_position($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey'], $_REQUEST['svasc']);
break;
case "getleaders":
print storage_get_leaders($_REQUEST['svuser'], $_REQUEST['svcourse'], $_REQUEST['svsco'], $_REQUEST['svkey'], $_REQUEST['svasc'], $_REQUEST['svlength']);
break;
case "usersgetall":
// security issue
print "NOT allowed, security issue, see sources";
// print storage_get_all_users();
break;
default:
// Do nothing
}
function storage_can_set($sv_user)
{
// platform admin can change any user's stored values, other users can only change their own values
$allowed = ((api_is_platform_admin()) || (!empty($sv_user) && $sv_user == api_get_user_id()));
if (!$allowed) {
echo "ERROR : Not allowed";
}
return $allowed;
}
function storage_get($sv_user, $sv_course, $sv_sco, $sv_key)
{
$sql = "select sv_value
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)."
where user_id= '$sv_user'
and sco_id = '$sv_sco'
and course_id = '$sv_course'
and sv_key = '$sv_key'";
$res = Database::query($sql);
if (Database::num_rows($res) > 0) {
$row = Database::fetch_assoc($res);
return Security::remove_XSS($row['sv_value']);
} else {
return null;
}
}
function storage_get_leaders($sv_user, $sv_course, $sv_sco, $sv_key, $sv_asc, $sv_length)
{
// get leaders
$sql_leaders = "select u.user_id, firstname, lastname, email, username, sv_value as value
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)." sv,
".Database::get_main_table(TABLE_MAIN_USER)." u
where u.user_id=sv.user_id
and sco_id = '$sv_sco'
and course_id = '$sv_course'
and sv_key = '$sv_key'
order by sv_value ".($sv_asc ? "ASC" : "DESC")." limit $sv_length";
// $sql_data = "select sv.user_id as user_id, sv_key as variable, sv_value as value
// from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)." sv
// where sv.user_id in (select u2.user_id from ($sql_leaders) u2)
// and sco_id = '$sv_sco'
// and course_id = '$sv_course'";
// $resData = Database::query($sql_data);
// $data = Array();
// while($row = Database::fetch_assoc($resData))
// $data[] = $row; // fetching all data
//
$resLeaders = Database::query($sql_leaders);
$result = [];
while ($row = Database::fetch_assoc($resLeaders)) {
$row["values"] = [];
// foreach($data as $dataRow) {
// if ($dataRow["user_id"] = $row["user_id"])
// $row["values"][$dataRow["variable"]] = $dataRow["value"];
// }
$row['sv_value'] = Security::remove_XSS($row['sv_value']);
$result[] = $row;
}
return json_encode($result);
}
function storage_get_position($sv_user, $sv_course, $sv_sco, $sv_key, $sv_asc, $sv_length)
{
$sql = "select count(list.user_id) as position
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)." search,
".Database::get_main_table(TABLE_TRACK_STORED_VALUES)." list
where search.user_id= '$sv_user'
and search.sco_id = '$sv_sco'
and search.course_id = '$sv_course'
and search.sv_key = '$sv_key'
and list.sv_value ".($sv_asc ? "<=" : ">=")." search.sv_value
and list.sco_id = search.sco_id
and list.course_id = search.course_id
and list.sv_key = search.sv_key
order by list.sv_value";
$res = Database::query($sql);
if (Database::num_rows($res) > 0) {
$row = Database::fetch_assoc($res);
return $row['position'];
} else {
return null;
}
}
function storage_set($sv_user, $sv_course, $sv_sco, $sv_key, $sv_value)
{
$sv_value = Database::escape_string($sv_value);
$sql = "replace into ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)."
(user_id, sco_id, course_id, sv_key, sv_value)
values
('$sv_user','$sv_sco','$sv_course','$sv_key','$sv_value')";
$res = Database::query($sql);
return Database::affected_rows($res);
}
function storage_getall($sv_user, $sv_course, $sv_sco)
{
$sql = "select sv_key, sv_value
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES)."
where user_id= '$sv_user'
and sco_id = '$sv_sco'
and course_id = '$sv_course'";
$res = Database::query($sql);
$data = [];
while ($row = Database::fetch_assoc($res)) {
$row['sv_value'] = Security::remove_XSS($row['sv_value']);
$row['sv_key'] = Security::remove_XSS($row['sv_key']);
$data[] = $row;
}
return json_encode($data);
}
function storage_stack_push($sv_user, $sv_course, $sv_sco, $sv_key, $sv_value)
{
$sv_value = Database::escape_string($sv_value);
Database::query("start transaction");
$sqlorder = "select ifnull((select max(stack_order)
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'
), 0) as stack_order";
$resorder = Database::query($sqlorder);
$row = Database::fetch_assoc($resorder);
$stack_order = (1 + $row['stack_order']);
$sqlinsert = "insert into ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
(user_id, sco_id, course_id, sv_key, stack_order, sv_value)
values
('$sv_user', '$sv_sco', '$sv_course', '$sv_key', '$stack_order', '$sv_value')";
$resinsert = Database::query($sqlinsert);
if ($resorder && $resinsert) {
Database::query("commit");
return 1;
} else {
Database::query("rollback");
return 0;
}
}
function storage_stack_pop($sv_user, $sv_course, $sv_sco, $sv_key)
{
Database::query("start transaction");
$sqlselect = "select sv_value, stack_order
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'
order by stack_order desc
limit 1";
$resselect = Database::query($sqlselect);
$rowselect = Database::fetch_assoc($resselect);
$stack_order = $rowselect['stack_order'];
$sqldelete = "delete
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'
and stack_order='$stack_order'";
$resdelete = Database::query($sqldelete);
if ($resselect && $resdelete) {
Database::query("commit");
return Security::remove_XSS($rowselect['sv_value']);
} else {
Database::query("rollback");
return null;
}
}
function storage_stack_length($sv_user, $sv_course, $sv_sco, $sv_key)
{
$sql = "select count(*) as length
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'";
$res = Database::query($sql);
$row = Database::fetch_assoc($res);
return $row['length'];
}
function storage_stack_clear($sv_user, $sv_course, $sv_sco, $sv_key)
{
$sql = "delete
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'";
$res = Database::query($sql);
return Database::num_rows($res);
}
function storage_stack_getall($sv_user, $sv_course, $sv_sco, $sv_key)
{
$sql = "select stack_order as stack_order, sv_value as value
from ".Database::get_main_table(TABLE_TRACK_STORED_VALUES_STACK)."
where user_id= '$sv_user'
and sco_id='$sv_sco'
and course_id='$sv_course'
and sv_key='$sv_key'";
$res = Database::query($sql);
$results = [];
while ($row = Database::fetch_assoc($res)) {
$row['value'] = Security::remove_XSS($row['value']);
$results[] = $row;
}
return json_encode($results);
}
function storage_get_all_users()
{
$sql = "select user_id, username, firstname, lastname
from ".Database::get_main_table(TABLE_MAIN_USER)."
order by user_id asc";
$res = Database::query($sql);
$results = [];
while ($row = Database::fetch_assoc($res)) {
$results[] = $row;
}
return json_encode($results);
}