From 8874d44634d1fe5b39632779066d2e2f02e076f5 Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 1 Oct 2010 18:08:23 +0200 Subject: [PATCH] Adding orig_lp_item_view_id in the track_e_exercises see #1334 Cleaning exercise_show.php, exercise_submit.php files --- main/exercice/exercice.php | 233 +- main/exercice/exercice_submit.php | 26 +- main/exercice/exercise.class.php | 14 +- main/exercice/exercise.lib.php | 13 +- main/exercice/exercise_result.php | 26 +- main/exercice/exercise_show.php | 89 +- main/inc/lib/events.lib.inc.php | 110 +- main/install/db_stats.sql | 1 + main/install/migrate-db-1.8.7-1.8.8-pre.sql | 2 + main/newscorm/blank.php | 8 +- main/newscorm/learnpath.class.php | 16782 +++++++++--------- main/newscorm/learnpathItem.class.php | 4848 ++--- main/newscorm/lp_stats.php | 4 +- main/newscorm/lp_view.php | 58 +- 14 files changed, 11092 insertions(+), 11122 deletions(-) mode change 100644 => 100755 main/install/db_stats.sql diff --git a/main/exercice/exercice.php b/main/exercice/exercice.php index eaae323605..cf2c7f6055 100755 --- a/main/exercice/exercice.php +++ b/main/exercice/exercice.php @@ -128,11 +128,18 @@ if ($_GET['delete'] == 'delete' && ($is_allowedToEdit || api_is_coach()) && !emp } if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit || $is_tutor) && $_GET['exeid']== strval(intval($_GET['exeid']))) { - $id = intval($_GET['exeid']); //filtered by post-condition + $id = intval($_GET['exeid']); //filtered by post-condition $track_exercise_info = get_exercise_track_exercise_info($id); + if (empty($track_exercise_info)) { + api_not_allowed(); + } $test = $track_exercise_info['title']; - $my_user_id = $track_exercise_info['exe_user_id']; - $user_info = api_get_user_info($my_user_id); + $student_id = $track_exercise_info['exe_user_id']; + $lp_id = $track_exercise_info['orig_lp_id']; + $lp_item_id = $track_exercise_info['orig_lp_item_id']; + $lp_item_view_id = $track_exercise_info['orig_lp_item_view_id']; + + $user_info = api_get_user_info($student_id); $emailid = $user_info['mail']; @@ -159,7 +166,7 @@ if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit } else { $array_content_id_exe=$post_content_id; } - + var_dump($_POST); for ($i=0;$i<$loop_in_track;$i++) { $my_marks = Database::escape_string($_POST['marks_'.$array_content_id_exe[$i]]); $contain_comments = Database::escape_string($_POST['comments_'.$array_content_id_exe[$i]]); @@ -175,83 +182,25 @@ if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit $query = "UPDATE $TBL_TRACK_ATTEMPT SET marks = '$my_marks',teacher_comment = '$my_comments' WHERE question_id = '".$my_questionid."' AND exe_id='".$id."'"; - Database::query($query); - - $qry = 'SELECT sum(marks) as tot FROM '.$TBL_TRACK_ATTEMPT.' WHERE exe_id = '.$id.' GROUP BY question_id'; - + //Not necessary to update the weight + /* + $qry = 'SELECT sum(marks) as tot FROM '.$TBL_TRACK_ATTEMPT.' WHERE exe_id = '.$id; $res = Database::query($qry); - $tot = Database::result($res,0,'tot'); + $tot = Database::result($res,0,'tot'); //updating also the total weight - $totquery = "UPDATE $TBL_TRACK_EXERCICES SET exe_result = '".Database::escape_string($tot)."', exe_weighting = '".Database::escape_string($total_weighting)."' + $totquery = "UPDATE $TBL_TRACK_EXERCICES SET exe_result = '".intval($tot)."', exe_weighting = '".Database::escape_string($total_weighting)."' WHERE exe_Id='".$id."'"; Database::query($totquery); + */ //@todo Why we insert this? $recording_changes = 'INSERT INTO '.$TBL_TRACK_ATTEMPT_RECORDING.' (exe_id, question_id, marks, insert_date, author, teacher_comment) VALUES ('."'$id','".$my_questionid."','$my_marks','".api_get_utc_datetime()."','".api_get_user_id()."'".',"'.$my_comments.'")'; Database::query($recording_changes); } - - $post_content_id=array(); - $array_content_id_exe=array(); - /*foreach ($_POST as $key => $v) { - $keyexp = explode('_', $key); - - $id = Database :: escape_string($id); - $v = Database :: escape_string($v); - $my_questionid = Database :: escape_string($keyexp[1]); - - if ($keyexp[0] == "marks") { - $sql = "SELECT question from $TBL_QUESTIONS WHERE id = '$my_questionid'"; - $result = Database::query($sql); - $ques_name = Database :: result($result, 0, "question"); - - $query = "UPDATE $TBL_TRACK_ATTEMPT SET marks = '" . $v . "' - WHERE question_id = '" . $my_questionid . "' - AND exe_id='" . $id . "'"; - Database::query($query); - - $qry = 'SELECT sum(marks) as tot - FROM ' . $TBL_TRACK_ATTEMPT . ' WHERE exe_id = ' . intval($id) . ' - GROUP BY question_id'; - - $res = Database::query($qry); - $tot = Database :: result($res, 0, 'tot'); - //updating also the total weight - $totquery = "UPDATE $TBL_TRACK_EXERCICES SET exe_result = '" . Database :: escape_string($tot) . "', exe_weighting = '" . Database :: escape_string($total_weighting) . "' - WHERE exe_Id='" . Database :: escape_string($id) . "'"; - - Database::query($totquery); - - $recording_changes = 'INSERT INTO ' . $TBL_RECORDING . ' ' . - '(exe_id, - question_id, - marks, - insert_date, - author) - VALUES - (' . "'$id','" . $my_questionid . "','$v','" . date('Y-m-d H:i:s') . "','" . api_get_user_id() . "'" . ')'; - Database::query($recording_changes); - } else { - $query = "UPDATE $TBL_TRACK_ATTEMPT SET teacher_comment = '" . $v . "' - WHERE question_id = '" . $my_questionid . "' - AND exe_id = '" . $id . "'"; - Database::query($query); - - $recording_changes = 'INSERT INTO ' . $TBL_RECORDING . ' ' . - '(exe_id, - question_id, - teacher_comment, - insert_date, - author) - VALUES - (' . "'$id','" . $my_questionid . "','$v','" . date('Y-m-d H:i:s') . "','" . api_get_user_id() . "'" . ')'; - Database::query($recording_changes); - } - }*/ - + $qry = 'SELECT DISTINCT question_id, marks - FROM ' . $TBL_TRACK_ATTEMPT . ' where exe_id = ' . intval($id) . ' + FROM ' . $TBL_TRACK_ATTEMPT . ' where exe_id = ' . $id . ' GROUP BY question_id'; $res = Database::query($qry); @@ -259,10 +208,11 @@ if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit while ($row = Database :: fetch_array($res, 'ASSOC')) { $tot += $row['marks']; } - - $totquery = "UPDATE $TBL_TRACK_EXERCICES SET exe_result = '" . Database :: escape_string($tot) . "' WHERE exe_Id='" . Database :: escape_string($id) . "'"; - + $totquery = "UPDATE $TBL_TRACK_EXERCICES SET exe_result = '" . intval($tot) . "' WHERE exe_id='" . $id . "'"; + Database::query($totquery); + //search items + /* if (isset($_POST['my_exe_exo_id']) && isset($_POST['student_id'])) { $sql_lp='SELECT li.id as lp_item_id,li.lp_id,li.item_type,li.path,liv.id AS lp_view_id,liv.user_id,max(liv.view_count) AS view_count FROM '.$TBL_LP_ITEM.' li INNER JOIN '.$TBL_LP_VIEW.' liv ON li.lp_id=liv.lp_id WHERE li.path="'.Database::escape_string($_POST['my_exe_exo_id']).'" AND li.item_type="quiz" AND user_id="'.Database::escape_string($_POST['student_id']).'" '; @@ -274,7 +224,7 @@ if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit $rs_lp_view=Database::query($sql_lp_view); } } - Database::query($totquery); + Database::query($totquery);*/ $subject = get_lang('ExamSheetVCC'); $htmlmessage = '' . @@ -329,78 +279,41 @@ if ($show == 'result' && $_REQUEST['comments'] == 'update' && ($is_allowedToEdit //mail($emailid, $subject, $mess,$headers); @api_mail_html($emailid, $emailid, $subject, $mess, $from_name, $from); + + //Updating LP score here + if (in_array($origin, array ('tracking_course','user_course'))) { - - if (isset ($_POST['lp_item_id']) && isset ($_POST['lp_item_view_id']) && isset ($_POST['student_id']) && isset ($_POST['total_score']) && isset ($_POST['total_time']) && isset ($_POST['totalWeighting'])) { - $lp_item_id = $_POST['lp_item_id']; - $lp_item_view_id = $_POST['lp_item_view_id']; - $student_id = $_POST['student_id']; - $totalWeighting = $_POST['totalWeighting']; - - if ($lp_item_id == strval(intval($lp_item_id)) && $lp_item_view_id == strval(intval($lp_item_view_id)) && $student_id == strval(intval($student_id))) { - - $score = Database :: escape_string($_POST['total_score']); //This is the new note - $total_time = Database :: escape_string($_POST['total_time']); - - //I need the lp_item_view_id in order to update the record - - //@todo add the lp_item_view_id in the track_exercise table in order to have a real match between the lp_item_view and the track_exercise - - //$my_real_lp_item_view_id = Database :: escape_string($_POST['real_lp_item_view_id']); - - $lp_item_id = Database :: escape_string($lp_item_id); - $lp_item_view_id = Database :: escape_string($lp_item_view_id); - $student_id = Database :: escape_string($student_id); - $totalWeighting = Database :: escape_string($totalWeighting); - - /* - $sql = "SELECT (view_count) FROM $TBL_LP_ITEM_VIEW - WHERE lp_item_id = '" . (int) $lp_item_view_id . "' AND lp_view_id = $my_real_lp_item_view_id ORDER BY id DESC LIMIT 1"; - $res_view_count = Database::query($sql); - $res_view_count = Database :: fetch_row($res_view_count); - $my_view_count = intval($res_view_count[0]); - */ - - //Checking if this is the lastest attempt - $sql = "SELECT exe_id FROM $TBL_TRACK_EXERCICES - WHERE exe_user_id = '" . Database :: escape_string($_POST['student_id']) . "' AND exe_cours_id = '" . api_get_course_id() . "' AND orig_lp_id = '$lp_item_id' AND orig_lp_item_id = '$lp_item_view_id' AND session_id = '" . api_get_session_id() . "' AND status = '' - ORDER BY exe_id DESC LIMIT 1 "; - $res_view_count = Database::query($sql); - $res_view_count = Database :: fetch_row($res_view_count); - $my_view_count = intval($res_view_count[0]); - - //Update lp_item_view if this attempts is the latest - $sql = "SELECT MAX(view_count) FROM $TBL_LP_ITEM_VIEW - WHERE lp_item_id = '" . (int) $lp_item_view_id . "' AND lp_view_id = (SELECT id from $TBL_LP_VIEW WHERE user_id = '" . (int) $student_id . "' and lp_id='" . (int) $lp_item_id . "')"; - $res_max_view_count = Database::query($sql); - $row_max_view_count = Database :: fetch_row($res_max_view_count); - $max_view_count = intval($row_max_view_count[0]); - - //Only update if is the last attempt - if ($my_view_count == $_GET['exeid']) { - // update score and total_time from last attempt when you qualify the exercise in Learning path detail - $sql_update_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = '" . intval($tot) . "' WHERE lp_item_id = '" . (int) $lp_item_view_id . "' - AND lp_view_id = (SELECT id from $TBL_LP_VIEW WHERE user_id = '" . (int) $student_id . "' and lp_id='" . (int) $lp_item_id . "') AND view_count = '$max_view_count'"; - Database::query($sql_update_score); - } - - /* - /* - // update score and total_time from last attempt when you qualify the exercise in Learning path detail - $sql_update_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = '" . (float) $score . "',total_time = '" . (int) $total_time . "' WHERE lp_item_id = '" . (int) $lp_item_view_id . "' - AND lp_view_id = (SELECT id from $TBL_LP_VIEW WHERE user_id = '" . (int) $student_id . "' and lp_id='" . (int) $lp_item_id . "') AND view_count = '$max_view_count'"; - Database::query($sql_update_score);*/ - - // update max_score from a exercise in lp - //$sql_update_max_score = "UPDATE $TBL_LP_ITEM SET max_score = '" . (float) $totalWeighting . "' WHERE id = '" . (int) $lp_item_view_id . "'"; - - //Database::query($sql_update_max_score); - - } - } - if ($origin == 'tracking_course' && !empty($_POST['lp_item_id'])) { + + /* + * We do not need this because lp_item_view_id comes to the rescue + //Checking if this is the lastest attempt + $sql = "SELECT exe_id FROM $TBL_TRACK_EXERCICES + WHERE exe_user_id = '" . Database :: escape_string($_POST['student_id']) . "' AND exe_cours_id = '" . api_get_course_id() . "' AND orig_lp_id = '$lp_item_id' AND orig_lp_item_id = '$lp_item_view_id' AND session_id = '" . api_get_session_id() . "' AND status = '' + ORDER BY exe_id DESC LIMIT 1 "; + $res_view_count = Database::query($sql); + $res_view_count = Database :: fetch_row($res_view_count); + $my_view_count = intval($res_view_count[0]); + + //Update lp_item_view if this attempts is the latest + $sql = "SELECT MAX(view_count) FROM $TBL_LP_ITEM_VIEW + WHERE lp_item_id = '" . (int) $lp_item_view_id . "' AND lp_view_id = (SELECT id from $TBL_LP_VIEW WHERE user_id = '" . (int) $student_id . "' and lp_id='" . (int) $lp_item_id . "')"; + $res_max_view_count = Database::query($sql); + $row_max_view_count = Database :: fetch_row($res_max_view_count); + $max_view_count = intval($row_max_view_count[0]); + + //Only update if is the last attempt + if ($my_view_count == $_GET['exeid']) { + // update score and total_time from last attempt when you qualify the exercise in Learning path detail + $sql_update_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = '" . intval($tot) . "' WHERE lp_item_id = '" . (int) $lp_item_view_id . "' + AND lp_view_id = (SELECT id from $TBL_LP_VIEW WHERE user_id = '" . (int) $student_id . "' and lp_id='" . (int) $lp_item_id . "') AND view_count = '$max_view_count'"; + Database::query($sql_update_score); + }*/ + $sql_update_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = '" . intval($tot) . "' WHERE id = " .$lp_item_view_id; + Database::query($sql_update_score); + + if ($origin == 'tracking_course') { //Redirect to the course detail in lp - header('location: ../mySpace/lp_tracking.php?course=' . Security :: remove_XSS($_GET['course']) . '&origin=' . $origin . '&lp_id=' . Security :: remove_XSS($_POST['lp_item_id']) . '&student_id=' . Security :: remove_XSS($_GET['student']).'&from='.Security::remove_XSS($_GET['from'])); + header('location: ../mySpace/lp_tracking.php?course=' . api_get_course_id() . '&origin=' . $origin . '&my_lp_id=' . $lp_item_id . '&lp_id=' . $lp_id . '&student_id=' . $student_id.'&extend_attempt=1&from='.Security::remove_XSS($_GET['from'])); exit; } else { //Redirect to the reporting @@ -843,9 +756,9 @@ if ($show == 'test') { if ($time_limits) { // check if start time - $start_time = api_strtotime(api_get_local_time($row['start_time'])); - $end_time = api_strtotime(api_get_local_time($row['end_time'])); - $now = api_strtotime(api_get_local_time()); + $start_time = api_strtotime($row['start_time'],'UTC'); + $end_time = api_strtotime($row['end_time'], 'UTC'); + $now = time(); $is_actived_time = false; if ($now > $start_time && $end_time > $now ) { @@ -1302,13 +1215,11 @@ if ($_configuration['tracking_enabled'] && ($show == 'result')) { } else { if ($revised) { //echo "" . get_lang('Show') . " "; - $html_link.="" . get_lang('Show') . " "; - + $html_link.="" . get_lang('Show') . " "; } else { // echo ' ' . get_lang('NoResult'); $html_link.=' ' . get_lang('NoResult'); } - } $more_details_list = $html_link; if ($is_allowedToEdit || $is_tutor) { @@ -1347,17 +1258,21 @@ if ($_configuration['tracking_enabled'] && ($show == 'result')) { $parameters=array('cidReq'=>Security::remove_XSS($_GET['cidReq']),'show'=>Security::remove_XSS($_GET['show']),'filter' => Security::remove_XSS($_GET['filter']),'gradebook' =>Security::remove_XSS($_GET['gradebook'])); + $table = new SortableTableFromArrayConfig($list_info, 1,20,'quiz_table'); $table->set_additional_parameters($parameters); - if (api_is_western_name_order()) { - $table->set_header(0, get_lang('FirstName')); - $table->set_header(1, get_lang('LastName')); - $secuence = 0; - } else { - $table->set_header(0, get_lang('LastName')); - $table->set_header(1, get_lang('FirstName')); - $secuence = 0; - } + $secuence = 2; + + if ($is_allowedToEdit || $is_tutor) { + $secuence = 0; + if (api_is_western_name_order()) { + $table->set_header(0, get_lang('FirstName')); + $table->set_header(1, get_lang('LastName')); + } else { + $table->set_header(0, get_lang('LastName')); + $table->set_header(1, get_lang('FirstName')); + } + } $table->set_header(-$secuence + 2, get_lang('Exercice')); $table->set_header(-$secuence + 3, get_lang('Duration'),false); diff --git a/main/exercice/exercice_submit.php b/main/exercice/exercice_submit.php index 1db454842d..ab3865ad8a 100755 --- a/main/exercice/exercice_submit.php +++ b/main/exercice/exercice_submit.php @@ -39,6 +39,8 @@ api_protect_course_script(true); $is_allowedToEdit = api_is_allowed_to_edit(null,true); //Blocking access in LPs +//This funcionality has been moved to the get_link function in the learnpath_class.php +/* if ($origin == 'learnpath' && isset ($_GET['not_multiple_attempt']) && $_GET['not_multiple_attempt'] == strval(intval($_GET['not_multiple_attempt']))) { $not_multiple_attempt = (int) $_GET['not_multiple_attempt']; if ($not_multiple_attempt === 1) { @@ -47,7 +49,7 @@ if ($origin == 'learnpath' && isset ($_GET['not_multiple_attempt']) && $_GET['no Display :: display_warning_message(get_lang('ReachedOneAttempt')); exit; } -} +}*/ $htmlHeadXtra[] = ''; //jQuery //$htmlHeadXtra[] = ''; //Not necessary to use jquery corner to do that effect use CSS3 @@ -81,6 +83,9 @@ if (empty ($learnpath_id)) { if (empty ($learnpath_item_id)) { $learnpath_item_id = intval($_REQUEST['learnpath_item_id']); } +if (empty ($learnpath_item_view_id)) { + $learnpath_item_view_id = intval($_REQUEST['learnpath_item_view_id']); +} if (empty ($formSent)) { $formSent = $_REQUEST['formSent']; } @@ -116,7 +121,7 @@ $error = ''; // if the user has clicked on the "Cancel" button if ($buttonCancel) { // returns to the exercise list - header("Location: exercice.php?origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id"); + header("Location: exercice.php?origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id"); exit; } if ($origin == 'builder') { @@ -151,11 +156,12 @@ if ($origin == 'builder') { if (isset ($_SESSION['exerciseResultCoordinates'])) { api_session_unregister('exerciseResultCoordinates'); unset ($exerciseResultCoordinates); - } + } } $safe_lp_id = ($learnpath_id == '') ? 0 : $learnpath_id; $safe_lp_item_id = ($learnpath_item_id == '') ? 0 : $learnpath_item_id; +$safe_lp_item_view_id = ($learnpath_item_view_id == '') ? 0 : $learnpath_item_view_id; // Loading the $objExercise variable @@ -193,7 +199,7 @@ $exerciseType = $objExercise->type; $current_timestamp = time(); //Getting track exercise info -$exercise_stat_info = $objExercise->get_stat_track_exercise_info($safe_lp_id, $safe_lp_item_id); +$exercise_stat_info = $objExercise->get_stat_track_exercise_info($safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id); if ($debug) {error_log('$objExercise->get_stat_track_exercise_info function called:: '.print_r($exercise_stat_info, 1)); }; /* @@ -385,10 +391,10 @@ if ($formSent) { if ( api_is_allowed_to_session_edit() ) { // goes to the script that will show the result of the exercise if ($exerciseType == ALL_ON_ONE_PAGE) { - header("Location: exercise_result.php?exerciseType=$exerciseType&origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id"); + header("Location: exercise_result.php?exerciseType=$exerciseType&origin=$origin&learnpath_id=$safe_lp_id&learnpath_item_id=$safe_lp_item_id&learnpath_item_view_id=$safe_lp_item_view_id"); exit; } else { - if ($exe_id != '') { + if (!empty($exe_id) && is_numeric($exe_id)) { //Verify if the current test is fraudulent if (exercise_time_control_is_valid($exerciseId)) { $sql_exe_result = ""; @@ -397,11 +403,11 @@ if ($formSent) { } //Clean incomplete - @todo why setting to blank the data_tracking? //$update_query = 'UPDATE ' . $stat_table . ' SET ' . "status = '', data_tracking='', exe_date = '" . api_get_utc_datetime() . "' $sql_exe_result " . ' WHERE exe_id = ' . Database::escape_string($exe_id); - $update_query = 'UPDATE ' . $stat_table . ' SET ' . "status = '', exe_date = '" . api_get_utc_datetime() . "' $sql_exe_result " . ' WHERE exe_id = ' . Database::escape_string($exe_id); + $update_query = "UPDATE $stat_table SET status = '', exe_date = '".api_get_utc_datetime() ."' , orig_lp_item_view_id = '$safe_lp_item_view_id' $sql_exe_result WHERE exe_id = ".$exe_id; if ($debug) {error_log($update_query);}; Database::query($update_query); } - header("Location: exercise_show.php?id=$exe_id&exerciseType=$exerciseType&origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id"); + header("Location: exercise_show.php?id=$exe_id&exerciseType=$exerciseType&origin=$origin&learnpath_id=$safe_lp_id&learnpath_item_id=$safe_lp_item_id&learnpath_item_view_id=$safe_lp_item_view_id"); exit; } } else { @@ -497,7 +503,7 @@ echo "

" . $exerciseTitle . "

"; $show_clock = true; $user_id = api_get_user_id(); if ($objExercise->selectAttempts() > 0) { - $attempt_count = get_attempt_count($user_id, $exerciseId, $safe_lp_id, $safe_lp_item_id); + $attempt_count = get_attempt_count($user_id, $exerciseId, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id); if ($attempt_count >= $objExercise->selectAttempts()) { $show_clock = false; if (!api_is_allowed_to_edit(null,true)) { @@ -648,7 +654,7 @@ if (!empty ($error)) { } if ($_configuration['live_exercise_tracking'] && $objExercise->feedbacktype != EXERCISE_FEEDBACK_TYPE_DIRECT) { if (empty($exercise_stat_info)) { - $objExercise->save_stat_track_exercise_info($clock_expired_time, $safe_lp_id, $safe_lp_item_id,$questionList); + $objExercise->save_stat_track_exercise_info($clock_expired_time, $safe_lp_id, $safe_lp_item_id, $safe_lp_item_view_id, $questionList); } } if ($origin != 'learnpath') { diff --git a/main/exercice/exercise.class.php b/main/exercice/exercise.class.php index 4716306b68..aaceb2a8d0 100755 --- a/main/exercice/exercise.class.php +++ b/main/exercice/exercise.class.php @@ -1354,7 +1354,7 @@ class Exercise { $new_answer_obj->read(); $new_answer_obj->duplicate($new_id); } - } + } /** * Changes the exercise id @@ -1374,20 +1374,24 @@ class Exercise { $this->active = $status; } - public function get_stat_track_exercise_info($lp_id = 0, $lp_item_id = 0) { + public function get_stat_track_exercise_info($lp_id = 0, $lp_item_id = 0, $lp_item_view_id = 0) { $track_exercises = Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); if (empty($lp_id)) { $lp_id = 0; } if (empty($lp_item_id)) { $lp_item_id = 0; - } + } + if (empty($lp_item_view_id)) { + $lp_item_view_id = 0; + } $condition = ' WHERE exe_exo_id = ' . "'" . $this->id . "'" .' AND exe_user_id = ' . "'" . api_get_user_id() . "'" . ' AND exe_cours_id = ' . "'" . api_get_course_id() . "'" . ' AND status = ' . "'incomplete'" . ' AND orig_lp_id = ' . "'" . $lp_id . "'" . ' AND - orig_lp_item_id = ' . "'" . $lp_item_id . "'" . ' AND + orig_lp_item_id = ' . "'" . $lp_item_id . "'" . ' AND + orig_lp_item_view_id = ' . "'" . $lp_item_view_id . "'" . ' AND session_id = ' . "'" . api_get_session_id() . "' LIMIT 1"; //Adding limit 1 just in case $sql_track = 'SELECT * FROM '.$track_exercises.$condition; @@ -1401,7 +1405,7 @@ class Exercise { } - public function save_stat_track_exercise_info($clock_expired_time = 0, $safe_lp_id = 0, $safe_lp_item_id = 0, $questionList = array()) { + public function save_stat_track_exercise_info($clock_expired_time = 0, $safe_lp_id = 0, $safe_lp_item_id = 0, $safe_lp_item_view_id = 0, $questionList = array()) { $track_exercises = Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); if (empty($safe_lp_id)) { diff --git a/main/exercice/exercise.lib.php b/main/exercice/exercise.lib.php index e4e57c387f..958ccde47c 100755 --- a/main/exercice/exercise.lib.php +++ b/main/exercice/exercise.lib.php @@ -541,11 +541,14 @@ function showQuestion($questionId, $onlyAnswers = false, $origin = false, $curre function get_exercise_track_exercise_info($exe_id) { $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST); $TBL_TRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); - - $sql_fb_type='SELECT * FROM '.$TBL_EXERCICES.' as e INNER JOIN '.$TBL_TRACK_EXERCICES.' as te ON (e.id=te.exe_exo_id) WHERE te.exe_id='.Database::escape_string($exe_id); - $res_fb_type=Database::query($sql_fb_type); - $row_fb_type=Database::fetch_array($res_fb_type, 'ASSOC'); - return $row_fb_type; + $exe_id = intval($exe_id); + $result = array(); + if (!empty($exe_id)) { + $sql_fb_type = 'SELECT * FROM '.$TBL_EXERCICES.' as e INNER JOIN '.$TBL_TRACK_EXERCICES.' as te ON (e.id=te.exe_exo_id) WHERE te.exe_id='.$exe_id; + $res_fb_type = Database::query($sql_fb_type); + $result = Database::fetch_array($res_fb_type, 'ASSOC'); + } + return $result; } diff --git a/main/exercice/exercise_result.php b/main/exercice/exercise_result.php index 9e8b133463..a19270c892 100755 --- a/main/exercice/exercise_result.php +++ b/main/exercice/exercise_result.php @@ -21,14 +21,14 @@ require_once 'question.class.php'; require_once 'answer.class.php'; if ($_GET['origin']=='learnpath') { - require_once ('../newscorm/learnpath.class.php'); - require_once ('../newscorm/learnpathItem.class.php'); - require_once ('../newscorm/scorm.class.php'); - require_once ('../newscorm/scormItem.class.php'); - require_once ('../newscorm/aicc.class.php'); - require_once ('../newscorm/aiccItem.class.php'); + require_once '../newscorm/learnpath.class.php'; + require_once '../newscorm/learnpathItem.class.php'; + require_once '../newscorm/scorm.class.php'; + require_once '../newscorm/scormItem.class.php'; + require_once '../newscorm/aicc.class.php'; + require_once '../newscorm/aiccItem.class.php'; } -global $_cid; + // name of the language file that needs to be included $language_file='exercice'; @@ -63,11 +63,15 @@ if ( empty ( $origin ) ) { $origin = Security::remove_XSS($_REQUEST['origin']); } if ( empty ( $learnpath_id ) ) { - $learnpath_id = Security::remove_XSS($_REQUEST['learnpath_id']); + $learnpath_id = intval($_REQUEST['learnpath_id']); } if ( empty ( $learnpath_item_id ) ) { - $learnpath_item_id = Security::remove_XSS($_REQUEST['learnpath_item_id']); + $learnpath_item_id = intval($_REQUEST['learnpath_item_id']); +} +if ( empty ( $learnpath_item_view_id ) ) { + $learnpath_item_view_id = intval($_REQUEST['learnpath_item_view_id']); } + if ( empty ( $formSent ) ) { $formSent = $_REQUEST['formSent']; } @@ -341,6 +345,7 @@ $exerciseTitle=text_filter($exerciseTitle); + selectId(),$totalScore,$totalWeighting,api_get_session_id(),$safe_lp_id,$safe_lp_item_id,$quizDuration); + update_event_exercice($exeId, $objExercise->selectId(),$totalScore, $totalWeighting,api_get_session_id(),$safe_lp_id,$safe_lp_item_id,$safe_lp_item_view_id, $quizDuration); } } diff --git a/main/exercice/exercise_show.php b/main/exercice/exercise_show.php index bcef09999c..699d2f6589 100755 --- a/main/exercice/exercise_show.php +++ b/main/exercice/exercise_show.php @@ -51,12 +51,6 @@ if($debug>0) { } // general parameters passed via POST/GET -if ( empty ( $learnpath_id ) ) { - $learnpath_id = $_REQUEST['learnpath_id']; -} -if ( empty ( $learnpath_item_id ) ) { - $learnpath_item_id = $_REQUEST['learnpath_item_id']; -} if ( empty ( $formSent ) ) { $formSent= $_REQUEST['formSent']; } @@ -92,26 +86,52 @@ if ( empty ( $action ) ) { $current_time = time(); $emailId = $_REQUEST['email']; -$id = $_REQUEST['id']; +$id = $_REQUEST['id']; //exe id if (empty($id)) { api_not_allowed(); } +$is_allowedToEdit=api_is_allowed_to_edit(null,true) || $is_courseTutor; + +//Getting results $track_exercise_info = get_exercise_track_exercise_info($id); -$exercise_id = $track_exercise_info['id']; -$course_code = api_get_course_id(); + +//No track info +if (empty($track_exercise_info)) { + api_not_allowed(); +} + + +$exercise_id = $track_exercise_info['id']; +$student_id = $track_exercise_info['exe_user_id']; +$learnpath_id = $track_exercise_info['orig_lp_id']; +$learnpath_item_id = $track_exercise_info['orig_lp_item_id']; +$lp_item_view_id = $track_exercise_info['orig_lp_item_view_id']; +$course_code = api_get_course_id(); + +//Check if user can see the results +if (!$is_allowedToEdit) { + $current_user_id = api_get_user_id(); + if ($student_id != $current_user_id) { + api_not_allowed(); + } +} + + if (!exercise_time_control_is_valid($exercise_id)) { - $sql_fraud = "UPDATE $TBL_TRACK_ATTEMPT SET answer = 0, marks=0, position=0 WHERE exe_id = '{$track_exercise_info['exe_id']}' "; + $sql_fraud = "UPDATE $TBL_TRACK_ATTEMPT SET answer = 0, marks=0, position=0 WHERE exe_id = $id "; Database::query($sql_fraud); } + //Unset session for clock time exercise_time_control_delete($exercise_id); -$is_allowedToEdit=api_is_allowed_to_edit(null,true) || $is_courseTutor; + + $nameTools=get_lang('CorrectTest'); if (isset($_SESSION['gradebook'])) { - $gradebook= $_SESSION['gradebook']; + $gradebook= Security::remove_XSS($_SESSION['gradebook']); } if (!empty($gradebook) && $gradebook=='view') { @@ -120,14 +140,14 @@ if (!empty($gradebook) && $gradebook=='view') { $fromlink = ''; if($origin=='user_course') { $interbreadcrumb[] = array ("url" => "../user/user.php?cidReq=".Security::remove_XSS($_GET['course']), "name" => get_lang("Users")); - $interbreadcrumb[] = array("url" => "../mySpace/myStudents.php?student=".Security::remove_XSS($_GET['student'])."&course=".$_course['id']."&details=true&origin=".Security::remove_XSS($_GET['origin']) , "name" => get_lang("DetailsStudentInCourse")); + $interbreadcrumb[] = array("url" => "../mySpace/myStudents.php?student=".$student_id."&course=".$_course['id']."&details=true&origin=".Security::remove_XSS($_GET['origin']) , "name" => get_lang("DetailsStudentInCourse")); } else if($origin=='tracking_course') { //$interbreadcrumb[] = array ("url" => "../mySpace/index.php", "name" => get_lang('MySpace')); - //$interbreadcrumb[] = array ("url" => "../mySpace/myStudents.php?student=".Security::remove_XSS($_GET['student']).'&details=true&origin='.$origin.'&course='.Security::remove_XSS($_GET['cidReq']), "name" => get_lang("DetailsStudentInCourse")); + //$interbreadcrumb[] = array ("url" => "../mySpace/myStudents.php?student=".Security::remove_XSS($student_id).'&details=true&origin='.$origin.'&course='.Security::remove_XSS($_GET['cidReq']), "name" => get_lang("DetailsStudentInCourse")); $interbreadcrumb[] = array ("url" => api_get_path(WEB_COURSE_PATH).$_course['directory'], 'name' => $_course['title']); $interbreadcrumb[] = array ("url" => "../tracking/courseLog.php?cidReq=".$cidReq.'&studentlist=true&id_session='.$_SESSION['id_session'], "name" => get_lang("Tracking")); - $interbreadcrumb[] = array ("url" => "../mySpace/myStudents.php?student=".Security::remove_XSS($_GET['student']).'&details=true&origin='.$origin.'&course='.Security::remove_XSS($_GET['cidReq']), "name" => get_lang("DetailsStudentInCourse")); - $interbreadcrumb[] = array ("url" => "../mySpace/lp_tracking.php?action=stats&course=".$cidReq."&student_id=".Security::remove_XSS($_GET['student'])."&lp_id=".Security::remove_XSS($_GET['my_lp_id'])."&origin=".Security::remove_XSS($_GET['origin']) , "name" => get_lang("LearningPathDetails")); + $interbreadcrumb[] = array ("url" => "../mySpace/myStudents.php?student=".$student_id.'&details=true&origin='.$origin.'&course='.Security::remove_XSS($_GET['cidReq']), "name" => get_lang("DetailsStudentInCourse")); + $interbreadcrumb[] = array ("url" => "../mySpace/lp_tracking.php?action=stats&course=".$cidReq."&student_id=".$student_id."&lp_id=".Security::remove_XSS($_GET['my_lp_id'])."&origin=".Security::remove_XSS($_GET['origin']) , "name" => get_lang("LearningPathDetails")); $from_myspace = false; if (isset ($_GET['from']) && $_GET['from'] == 'myspace') { @@ -238,24 +258,26 @@ if($num>1) {
'.(get_lang('BackToExercisesList')).'', false); echo ' '; } } + } } else { Display::display_warning_message(get_lang('CantViewResults')); @@ -264,6 +286,7 @@ if (!empty($track_exercise_info)) { '; } + if ($origin == 'learnpath' && !isset($_GET['fb_type']) ) { $show_results = false; } @@ -1012,20 +1035,19 @@ if (is_array($arrid) && is_array($arrmarks)) { if ($is_allowedToEdit) { if (in_array($origin, array('tracking_course','user_course'))) { - echo '
'; + echo ' '; echo ' '; - if (isset($_GET['myid']) && isset($_GET['my_lp_id']) && isset($_GET['student'])) { - ?> - - - - - - - '; + echo ''; + echo ''; + echo ' '; + //echo ' '; + echo ' '; } } else { - echo ' '; + echo ' '; } if ($origin!='learnpath' && $origin!='student_progress') { ?> @@ -1034,8 +1056,10 @@ if ($is_allowedToEdit) { - + +//Came from lpstats in a lp +if ($origin =='student_progress') {?> + @@ -1064,6 +1088,7 @@ if ($origin != 'learnpath') { ExerciseShowFunctions::send_notification($arrques, $arrans, $to); } Display::display_normal_message(get_lang('ExerciseFinished').' '.get_lang('ToContinueUseMenu')); + echo '
'; } } diff --git a/main/inc/lib/events.lib.inc.php b/main/inc/lib/events.lib.inc.php index 5853f061a1..ac3887fd1b 100755 --- a/main/inc/lib/events.lib.inc.php +++ b/main/inc/lib/events.lib.inc.php @@ -31,14 +31,12 @@ $TABLETRACK_DEFAULT = $_configuration['statistics_database'].".track_e_default" * @author Sebastien Piraux * @desc Record information for open event (when homepage is opened) */ -function event_open() -{ +function event_open() { global $_configuration; global $TABLETRACK_OPEN; // if tracking is disabled record nothing - if (!$_configuration['tracking_enabled']) - { + if (!$_configuration['tracking_enabled']) { return 0; } @@ -48,9 +46,7 @@ function event_open() if(isset($_SERVER['HTT_REFERER'])) { $referer = Database::escape_string($_SERVER['HTTP_REFERER']); - } - else - { + } else { $referer = ''; } // record informations only if user comes from another site @@ -79,8 +75,7 @@ function event_open() * @desc Record information for login event * (when an user identifies himself with username & password) */ -function event_login() -{ +function event_login() { global $_configuration; global $_user; global $TABLETRACK_LOGIN; @@ -89,12 +84,12 @@ function event_login() if (!$_configuration['tracking_enabled']) { return 0; } - $reallyNow = time(); + $reallyNow = api_get_utc_datetime(); $sql = "INSERT INTO ".$TABLETRACK_LOGIN." (login_user_id, login_ip, login_date, logout_date) VALUES ('".$_user['user_id']."', '".Database::escape_string($_SERVER['REMOTE_ADDR'])."', - FROM_UNIXTIME(".$reallyNow."), - FROM_UNIXTIME(".$reallyNow.") + '".$reallyNow."', + '".$reallyNow."' )"; $res = Database::query($sql); } @@ -130,7 +125,7 @@ function event_access_course() { VALUES (".$user_id.", '".$_cid."', - FROM_UNIXTIME(".$reallyNow."), + '".$reallyNow."', '".$id_session."')"; $res = Database::query($sql); // added for "what's new" notification @@ -161,8 +156,7 @@ function event_access_course() { * * Functionality for "what's new" notification is added by Toon Van Hoecke */ -function event_access_tool($tool, $id_session=0) -{ +function event_access_tool($tool, $id_session=0) { global $_configuration; // if tracking is disabled record nothing // if( ! $_configuration['tracking_enabled'] ) return 0; //commented because "what's new" notification must always occur @@ -173,10 +167,10 @@ function event_access_tool($tool, $id_session=0) global $_course; global $TABLETRACK_LASTACCESS; //for "what's new" notification - $id_session = api_get_session_id(); - $tool = Database::escape_string($tool); - $reallyNow = api_get_utc_datetime(); - $user_id = $_user['user_id'] ? "'".$_user['user_id']."'" : "0"; // no one + $id_session = api_get_session_id(); + $tool = Database::escape_string($tool); + $reallyNow = api_get_utc_datetime(); + $user_id = $_user['user_id'] ? "'".$_user['user_id']."'" : "0"; // no one // record information // only if user comes from the course $_cid //if( eregi($_configuration['root_web'].$_cid,$_SERVER['HTTP_REFERER'] ) ) @@ -185,8 +179,7 @@ function event_access_tool($tool, $id_session=0) // added for "what's new" notification $pos2 = strpos(strtolower($_SERVER['HTTP_REFERER']), strtolower($_configuration['root_web']."index")); // end "what's new" notification - if ($_configuration['tracking_enabled'] && ($pos !== false || $pos2 !== false)) { - + if ($_configuration['tracking_enabled'] && ($pos !== false || $pos2 !== false)) { $sql = "INSERT INTO ".$TABLETRACK_ACCESS." (access_user_id, access_cours_code, @@ -235,6 +228,7 @@ function event_download($doc_url) { if (!$_configuration['tracking_enabled']) { return 0; } + $doc_url = Database::escape_string($doc_url); $reallyNow = api_get_utc_datetime(); if ($_user['user_id']) { @@ -252,7 +246,7 @@ function event_download($doc_url) { VALUES ( ".$user_id.", '".$_cid."', - '".htmlspecialchars($doc_url, ENT_QUOTES)."', + '".$doc_url."', '".$reallyNow."', '".api_get_session_id()."' )"; @@ -267,8 +261,7 @@ function event_download($doc_url) { * used in the works tool to record informations when * an user upload 1 work */ -function event_upload($doc_id) -{ +function event_upload($doc_id) { global $_configuration; global $_user; global $_cid; @@ -321,16 +314,13 @@ function event_link($link_id) { // anonymous $user_id = "0"; } - $sql = "INSERT INTO ".$TABLETRACK_LINKS." ( links_user_id, links_cours_id, links_link_id, links_date, links_session_id - ) - VALUES - ( + ) VALUES ( ".$user_id.", '".api_get_course_id()."', '".Database::escape_string($link_id)."', @@ -355,12 +345,12 @@ function event_link($link_id) { * * @author Sebastien Piraux * @author Julio Montoya Armas Reworked 2010 - * @desc Record result of user when an exercice was done + * @desc Record result of user when an exercice was done */ -function update_event_exercice($exeid,$exo_id, $score, $weighting,$session_id,$learnpath_id=0,$learnpath_item_id=0, $duration) { +function update_event_exercice($exeid, $exo_id, $score, $weighting,$session_id,$learnpath_id=0, $learnpath_item_id=0, $learnpath_item_view_id = 0, $duration) { if ($exeid!='') { - // Validation in case of fraud with actived control time - + + // Validation in case of fraud with actived control time if (!exercise_time_control_is_valid($exo_id)) { $score = 0; } @@ -383,15 +373,21 @@ function update_event_exercice($exeid,$exo_id, $score, $weighting,$session_id,$l session_id = '".Database::escape_string($session_id)."', orig_lp_id = '".Database::escape_string($learnpath_id)."', orig_lp_item_id = '".Database::escape_string($learnpath_item_id)."', + orig_lp_item_view_id = '".Database::escape_string($learnpath_item_view_id)."', exe_duration = '".Database::escape_string($duration)."', - exe_date = ".api_get_utc_datetime().", - status = '', - data_tracking = '', - start_date = '".api_get_utc_datetime($start_date)."') + exe_date = '".api_get_utc_datetime()."', + status = '', + start_date = '".api_get_utc_datetime($start_date)."' WHERE exe_id = '".Database::escape_string($exeid)."'"; - $res = @Database::query($sql); + + $res = @Database::query($sql); + + //Deleting control time session track exercise_time_control_delete($exo_id); + error_log('update_event_exercice'); + error_log($sql); return $res; + } else return false; } @@ -405,6 +401,7 @@ function update_event_exercice($exeid,$exo_id, $score, $weighting,$session_id,$l */ function create_event_exercice($exo_id) { global $_user, $_configuration; + error_log('create_event_exercice'); $TABLETRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST); $reallyNow = time(); @@ -414,7 +411,7 @@ function create_event_exercice($exo_id) { // anonymous $user_id = "0"; } - + //@todo who did this? should be remove, need other function instead if(defined('ENABLED_LIVE_EXERCISE_TRACKING')){ $condition = ' WHERE ' . 'exe_exo_id = '."'".Database::escape_string($exo_id)."'".' AND ' . @@ -485,8 +482,7 @@ function exercise_attempt($score, $answer, $quesId, $exeId, $j, $exercise_id = 0 } else { // anonymous $user_id = api_get_anonymous_id(); - } - $_SESSION['current_exercice_attempt'][$user_id] = $exeId; + } $sql = "INSERT INTO $TBL_TRACK_ATTEMPT ( exe_id, @@ -508,14 +504,13 @@ function exercise_attempt($score, $answer, $quesId, $exeId, $j, $exercise_id = 0 '".$j."', '".$reallyNow."' )"; + if (!empty($quesId) && !empty($exeId) && !empty($user_id)) { $res = Database::query($sql); - if (defined('ENABLED_LIVE_EXERCISE_TRACKING')){ $TBL_RECORDING = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING); $recording_changes = "INSERT INTO $TBL_RECORDING (exe_id, question_id, marks, insert_date, author) VALUES ('$exeId','$quesId','$score','".api_get_utc_datetime()."','') "; - Database::query($recording_changes); - error_log($recording_changes); + Database::query($recording_changes); } return $res; } else { @@ -568,8 +563,7 @@ function exercise_attempt_hotspot($exe_id, $question_id, $answer_id, $correct, $ * @param integer User ID (defaults to null) * @param string Course code (defaults to null) */ -function event_system($event_type, $event_value_type, $event_value, $timestamp = null, $user_id=null, $course_code=null) -{ +function event_system($event_type, $event_value_type, $event_value, $timestamp = null, $user_id=null, $course_code=null) { global $_configuration; global $_user; global $TABLETRACK_DEFAULT; @@ -580,26 +574,22 @@ function event_system($event_type, $event_value_type, $event_value, $timestamp = $timestamp = Database::escape_string($timestamp); $user_id = Database::escape_string($user_id); $course_code = Database::escape_string($course_code); + + // if tracking is disabled record nothing + if (!$_configuration['tracking_enabled']) { + return 0; + } + - - // if tracking is disabled record nothing - if (!$_configuration['tracking_enabled']) - { - return 0; - } - if(!isset($timestamp)) - { - $timestamp = time(); + if(!isset($timestamp)) { + $timestamp = api_get_utc_datetime(); } - if(!isset($user_id)) - { + if(!isset($user_id)) { $user_id = 0; } - if(!isset($course_code)) - { + if(!isset($course_code)) { $course_code = ''; } - $sql = "INSERT INTO $TABLETRACK_DEFAULT (default_user_id, default_cours_code, @@ -611,7 +601,7 @@ function event_system($event_type, $event_value_type, $event_value, $timestamp = VALUES ('$user_id.', '$course_code', - FROM_UNIXTIME($timestamp), + '$timestamp', '$event_type', '$event_value_type', '$event_value')"; diff --git a/main/install/db_stats.sql b/main/install/db_stats.sql old mode 100644 new mode 100755 index 40b06ac7e8..34bd169a81 --- a/main/install/db_stats.sql +++ b/main/install/db_stats.sql @@ -116,6 +116,7 @@ ALTER TABLE track_e_exercices ADD orig_lp_id int NOT NULL default 0; ALTER TABLE track_e_exercices ADD orig_lp_item_id int NOT NULL default 0; ALTER TABLE track_e_exercices ADD exe_duration int UNSIGNED NOT NULL default 0; ALTER TABLE track_e_exercices ADD COLUMN expired_time_control datetime NOT NULL DEFAULT '0000-00-00 00:00:00'; +ALTER TABLE track_e_exercices ADD COLUMN orig_lp_item_view_id INT NOT NULL DEFAULT 0; CREATE TABLE track_e_attempt ( exe_id int default NULL, diff --git a/main/install/migrate-db-1.8.7-1.8.8-pre.sql b/main/install/migrate-db-1.8.7-1.8.8-pre.sql index f0ad0b3527..aebaf4073c 100755 --- a/main/install/migrate-db-1.8.7-1.8.8-pre.sql +++ b/main/install/migrate-db-1.8.7-1.8.8-pre.sql @@ -30,6 +30,8 @@ ALTER TABLE sys_calendar ADD COLUMN access_url_id INT NOT NULL default 1; -- xxSTATSxx +ALTER TABLE track_e_exercices ADD COLUMN orig_lp_item_view_id INT NOT NULL DEFAULT 0; + -- xxUSERxx diff --git a/main/newscorm/blank.php b/main/newscorm/blank.php index c2f9c3cb1c..55e51995a0 100755 --- a/main/newscorm/blank.php +++ b/main/newscorm/blank.php @@ -7,14 +7,12 @@ * @author Yannick Warnier */ -$language_file = array('learnpath', 'document'); +$language_file = array('learnpath', 'document','exercice'); // Flag to allow for anonymous user - needs to be set before global.inc.php. $use_anonymous = true; - require_once '../inc/global.inc.php'; require_once '../inc/reduced_header.inc.php'; - echo ''; if (isset($_GET['error'])) { @@ -31,6 +29,10 @@ if (isset($_GET['error'])) { echo '

'; Display::display_normal_message(get_lang('FileNotFound')); break; + case 'reached_one_attempt': + echo '

'; + Display::display_normal_message(get_lang('ReachedOneAttempt')); + break; default: break; } diff --git a/main/newscorm/learnpath.class.php b/main/newscorm/learnpath.class.php index 1946b1f644..062623ba37 100755 --- a/main/newscorm/learnpath.class.php +++ b/main/newscorm/learnpath.class.php @@ -18,8402 +18,8404 @@ class learnpath { - public $attempt = 0; // The number for the current ID view. - public $cc; // Course (code) this learnpath is located in. - public $current; // Id of the current item the user is viewing. - public $current_score; // The score of the current item. - public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet). - public $current_time_stop; // The time the user closed this resource. - public $default_status = 'not attempted'; - public $encoding = 'UTF-8'; - public $error = ''; - public $extra_information = ''; // This string can be used by proprietary SCORM contents to store data about the current learnpath. - public $force_commit = false; // For SCORM only - if set to true, will send a scorm LMSCommit() request on each LMSSetValue(). - public $index; // The index of the active learnpath_item in $ordered_items array. - public $items = array(); - public $last; // item_id of last item viewed in the learning path. - public $last_item_seen = 0; // In case we have already come in this learnpath, reuse the last item seen if authorized. - public $license; // Which license this course has been given - not used yet on 20060522. - public $lp_id; // DB ID for this learnpath. - public $lp_view_id; // DB ID for lp_view - public $log_file; // File where to log learnpath API msg. - public $maker; // Which maker has conceived the content (ENI, Articulate, ...). - public $message = ''; - public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded). - public $name; // Learnpath name (they generally have one). - public $ordered_items = array(); // List of the learnpath items in the order they are to be read. - public $path = ''; // Path inside the scorm directory (if scorm). - public $theme; // The current theme of the learning path. - public $preview_image; // The current image of the learning path. - - // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1). - public $prevent_reinit = 1; - - // Describes the mode of progress bar display. - public $progress_bar_mode = '%'; - - // Percentage progress as saved in the db. - public $progress_db = '0'; - public $proximity; // Wether the content is distant or local or unknown. - public $refs_list = array (); //list of items by ref => db_id. Used only for prerequisites match. - // !!!This array (refs_list) is built differently depending on the nature of the LP. - // If SCORM, uses ref, if Chamilo, uses id to keep a unique value. - public $type; //type of learnpath. Could be 'dokeos', 'scorm', 'scorm2004', 'aicc', ... - // TODO: Check if this type variable is useful here (instead of just in the controller script). - public $user_id; //ID of the user that is viewing/using the course - public $update_queue = array(); - public $scorm_debug = 0; - - public $arrMenu = array(); // Array for the menu items. - - public $debug = 0; // Logging level. - - public $lp_session_id =0; - public $lp_view_session_id =0; // The specific view might be bound to a session. - - public $prerequisite = 0; - - /** - * Class constructor. Needs a database handler, a course code and a learnpath id from the database. - * Also builds the list of items into $this->items. - * @param string Course code - * @param integer Learnpath ID - * @param integer User ID - * @return boolean True on success, false on error - */ - public function __construct($course, $lp_id, $user_id) { - // Check params. - // Check course code. - if ($this->debug > 0) {error_log('New LP - In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')', 0); } - if (empty($course)) { - $this->error = 'Course code is empty'; - return false; - } else { - $main_table = Database::get_main_table(TABLE_MAIN_COURSE); - //$course = Database::escape_string($course); - $course = $this->escape_string($course); - $sql = "SELECT * FROM $main_table WHERE code = '$course'"; - if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying course: '.$sql, 0); } - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $this->cc = $course; - } else { - $this->error = 'Course code does not exist in database ('.$sql.')'; - return false; - } - } - // Check learnpath ID. - if (empty($lp_id)) { - $this->error = 'Learnpath ID is empty'; - return false; - } else { - // TODO: Make it flexible to use any course_code (still using env course code here). - $lp_table = Database::get_course_table(TABLE_LP_MAIN); - - //$id = Database::escape_integer($id); - $lp_id = $this->escape_string($lp_id); - $sql = "SELECT * FROM $lp_table WHERE id = '$lp_id'"; - if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0); } - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $this->lp_id = $lp_id; - $row = Database::fetch_array($res); - $this->type = $row['lp_type']; - $this->name = stripslashes($row['name']); - $this->encoding = $row['default_encoding']; - $this->proximity = $row['content_local']; - $this->theme = $row['theme']; - $this->maker = $row['content_maker']; - $this->prevent_reinit = $row['prevent_reinit']; - $this->license = $row['content_license']; - $this->scorm_debug = $row['debug']; - $this->js_lib = $row['js_lib']; - $this->path = $row['path']; - $this->preview_image= $row['preview_image']; - $this->author= $row['author']; - $this->lp_session_id = $row['session_id']; - - if ($this->type == 2) { - if ($row['force_commit'] == 1) { - $this->force_commit = true; - } - } - $this->mode = $row['default_view_mod']; - } else { - $this->error = 'Learnpath ID does not exist in database ('.$sql.')'; - return false; - } - } - // Check user ID. - if (empty($user_id)) { - $this->error = 'User ID is empty'; - return false; - } else { - //$main_table = Database::get_main_user_table(); - $main_table = Database::get_main_table(TABLE_MAIN_USER); - //$user_id = Database::escape_integer($user_id); - $user_id = $this->escape_string($user_id); - $sql = "SELECT * FROM $main_table WHERE user_id = '$user_id'"; - if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying user: '.$sql, 0); } - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $this->user_id = $user_id; - } else { - $this->error = 'User ID does not exist in database ('.$sql.')'; - return false; - } - } - // End of variables checking. - - $session_id = api_get_session_id(); - // Get the session condition for learning paths of the base + session. - $session = api_get_session_condition($session_id); - // Now get the latest attempt from this user on this LP, if available, otherwise create a new one. - $lp_table = Database::get_course_table(TABLE_LP_VIEW); - // Selecting by view_count descending allows to get the highest view_count first. - $sql = "SELECT * FROM $lp_table WHERE lp_id = '$lp_id' AND user_id = '$user_id' $session ORDER BY view_count DESC"; - if ($this->debug > 2) { error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - querying lp_view: ' . $sql, 0); } - $res = Database::query($sql); - $view_id = 0; // Used later to query lp_item_view. - if (Database :: num_rows($res) > 0) { - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - Found previous view', 0); - } - $row = Database :: fetch_array($res); - $this->attempt = $row['view_count']; - $this->lp_view_id = $row['id']; - $this->last_item_seen = $row['last_item']; - $this->progress_db = $row['progress']; - $this->lp_view_session_id = $row['session_id']; - } else { - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - NOT Found previous view', 0); - } - $this->attempt = 1; - $sql_ins = "INSERT INTO $lp_table (lp_id,user_id,view_count, session_id) VALUES ($lp_id,$user_id,1,$session_id)"; - $res_ins = Database::query($sql_ins); - $this->lp_view_id = Database :: insert_id(); - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - inserting new lp_view: ' . $sql_ins, 0); - } - } - - // Initialise items. - $lp_item_table = Database::get_course_table(TABLE_LP_ITEM); - $sql = "SELECT * FROM $lp_item_table WHERE lp_id = '".$this->lp_id."' ORDER BY parent_item_id, display_order"; - $res = Database::query($sql); - - while ($row = Database::fetch_array($res)) { - $oItem = ''; - //$this->ordered_items[] = $row['id']; - switch ($this->type) { - - case 3: //aicc - $oItem = new aiccItem('db', $row['id']); - if (is_object($oItem)) { - $my_item_id = $oItem->get_id(); - $oItem->set_lp_view($this->lp_view_id); - $oItem->set_prevent_reinit($this->prevent_reinit); - // Don't use reference here as the next loop will make the pointed object change. - $this->items[$my_item_id] = $oItem; - $this->refs_list[$oItem->ref] = $my_item_id; - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() - aicc object with id ' . $my_item_id . ' set in items[]', 0); - } - } - break; - case 2: - - require_once 'scorm.class.php'; - require_once 'scormItem.class.php'; - $oItem = new scormItem('db', $row['id']); - if (is_object($oItem)) { - $my_item_id = $oItem->get_id(); - $oItem->set_lp_view($this->lp_view_id); - $oItem->set_prevent_reinit($this->prevent_reinit); - // Don't use reference here as the next loop will make the pointed object change. - $this->items[$my_item_id] = $oItem; - $this->refs_list[$oItem->ref] = $my_item_id; - if ($this->debug > 2) { - error_log('New LP - object with id ' . $my_item_id . ' set in items[]', 0); - } - } - break; - - case 1: - - default: - require_once 'learnpathItem.class.php'; - $oItem = new learnpathItem($row['id'], $user_id); - if (is_object($oItem)) { - $my_item_id = $oItem->get_id(); - //$oItem->set_lp_view($this->lp_view_id); // Moved down to when we are sure the item_view exists. - $oItem->set_prevent_reinit($this->prevent_reinit); - // Don't use reference here as the next loop will make the pointed object change. - $this->items[$my_item_id] = $oItem; - $this->refs_list[$my_item_id] = $my_item_id; - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - object with id ' . $my_item_id . ' set in items[]', 0); - } - } - break; - } - - // Items is a list of pointers to all items, classified by DB ID, not SCO id. - if ($row['parent_item_id'] == 0 || empty ($this->items[$row['parent_item_id']])) { - $this->items[$row['id']]->set_level(0); - } else { - $level = $this->items[$row['parent_item_id']]->get_level() + 1; - $this->items[$row['id']]->set_level($level); - if (is_object($this->items[$row['parent_item_id']])) { - // Items is a list of pointers from item DB ids to item objects. - $this->items[$row['parent_item_id']]->add_child($row['id']); - } else { - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - The parent item (' . $row['parent_item_id'] . ') of item ' . $row['id'] . ' could not be found', 0); - } - } - } - - // Get last viewing vars. - $lp_item_view_table = Database :: get_course_table(TABLE_LP_ITEM_VIEW); - // This query should only return one or zero result. - $sql = "SELECT * " . - "FROM $lp_item_view_table " . - "WHERE lp_view_id = " . $this->lp_view_id . " " . - "AND lp_item_id = " . $row['id'] . " ORDER BY view_count DESC "; - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() - Selecting item_views: ' . $sql, 0); - } - // Get the item status. - $res2 = Database::query($sql); - if (Database :: num_rows($res2) > 0) { - // If this learnpath has already been used by this user, get his last attempt count and - // the last item seen back into this object. - //$max = 0; - $row2 = Database :: fetch_array($res2); - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() - Got item_view: ' . print_r($row2, true), 0); - } - $this->items[$row['id']]->set_status($row2['status']); - if (empty ($row2['status'])) { - $this->items[$row['id']]->set_status($this->default_status); - } - //$this->attempt = $row['view_count']; - //$this->last_item = $row['id']; - } else { // No item has been found in lp_item_view for this view. - // The first attempt of this user. Set attempt to 1 and last_item to 0 (first item available). - // TODO: If the learnpath has not got attempts activated, always use attempt '1'. - //$this->attempt = 1; - //$this->last_item = 0; - $this->items[$row['id']]->set_status($this->default_status); - // Add that row to the lp_item_view table so that we have something to show in the stats page. - $sql_ins = "INSERT INTO $lp_item_view_table " . - "(lp_item_id, lp_view_id, view_count, status) VALUES " . - "(" . $row['id'] . "," . $this->lp_view_id . ",1,'not attempted')"; - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - Inserting blank item_view : ' . $sql_ins, 0); - } - $res_ins = Database::query($sql_ins); - } - // Setting the view in the item object. - $this->items[$row['id']]->set_lp_view($this->lp_view_id); - } - $this->ordered_items = $this->get_flat_ordered_items_list($this->get_id(), 0); - $this->max_ordered_items = 0; - foreach ($this->ordered_items as $index => $dummy) { - if ($index > $this->max_ordered_items && !empty($dummy)) { - $this->max_ordered_items = $index; - } - } - // TODO: Define the current item better. - $this->first(); - if ($this->debug > 2) { - error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - End of learnpath constructor for learnpath ' . $this->get_id(), 0); - } - } - - /** - * Function rewritten based on old_add_item() from Yannick Warnier. Due the fact that users can decide where the item should come, I had to overlook this function and - * I found it better to rewrite it. Old function is still available. Added also the possibility to add a description. - * - * @param int $parent - * @param int $previous - * @param string $type - * @param int $id - * @param string $title - * @param string $description - * @return int - */ - public function add_item($parent, $previous, $type = 'dokeos_chapter', $id, $title, $description, $prerequisites = 0, $max_time_allowed = 0) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::add_item(' . $parent . ',' . $previous . ',' . $type . ',' . $id . ',' . $title . ')', 0); - } - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $parent = intval($parent); - $previous = intval($previous); - $type = $this->escape_string($type); - $id = intval($id); - $max_time_allowed = $this->escape_string(htmlentities($max_time_allowed)); - if (empty ($max_time_allowed)) { - $max_time_allowed = 0; - } - //$title = htmlspecialchars($title, ENT_QUOTES, $charset); // TODO: To be checked for encoding problems. - //$title = $this->escape_string(api_convert_encoding($title, $this->encoding, $charset)); - $title = $this->escape_string(api_convert_encoding($title, $this->encoding, api_get_system_encoding())); - $description = $this->escape_string(api_convert_encoding($description, $this->encoding, api_get_system_encoding())); - $sql_count = " SELECT COUNT(id) AS num - FROM " . $tbl_lp_item . " - WHERE lp_id = " . $this->get_id() . " AND parent_item_id = " . $parent; - - $res_count = Database::query($sql_count); - $row = Database :: fetch_array($res_count); - $num = $row['num']; - - if ($num > 0) { - if ($previous == 0) { - $sql = "SELECT id, next_item_id, display_order - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->get_id() . " AND - parent_item_id = " . $parent . " AND - previous_item_id = 0 OR previous_item_id=" . $parent; - $result = Database::query($sql); - $row = Database :: fetch_array($result); - - $tmp_previous = 0; - $next = $row['id']; - $display_order = 0; - } else { - $previous = (int) $previous; - $sql = " - SELECT - id, - previous_item_id, - next_item_id, - display_order - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->get_id() . " AND - id = " . $previous; - - $result = Database::query($sql); - $row = Database :: fetch_array($result); - - $tmp_previous = $row['id']; - $next = $row['next_item_id']; - - $display_order = $row['display_order']; - } - } else { - $tmp_previous = 0; - $next = 0; - $display_order = 0; - } - - $new_item_id = -1; - $id = $this->escape_string($id); - - if ($type == 'quiz') { - $sql = 'SELECT SUM(ponderation) - FROM ' . Database :: get_course_table(TABLE_QUIZ_QUESTION) . ' as quiz_question - INNER JOIN ' . Database :: get_course_table(TABLE_QUIZ_TEST_QUESTION) . ' as quiz_rel_question - ON quiz_question.id = quiz_rel_question.question_id - AND quiz_rel_question.exercice_id = ' . $id; - $rsQuiz = Database::query($sql); - $max_score = Database :: result($rsQuiz, 0, 0); - } else { - $max_score = 100; - } - - if ($prerequisites != 0) { - $sql_ins = " - INSERT INTO " . $tbl_lp_item . " ( - lp_id, - item_type, - ref, - title, - description, - path, - max_score, - parent_item_id, - previous_item_id, - next_item_id, - display_order, - prerequisite, - max_time_allowed - - ) VALUES ( - " . $this->get_id() . ", - '" . $type . "', - '', - '" . $title . "', - '" . $description . "', - '" . $id . "', - '" . $max_score . "', - " . $parent . ", - " . $previous . ", - " . $next . ", - " . ($display_order +1) . ", - " . $prerequisites . ", - " . $max_time_allowed . " - )"; - } else { - // Insert new item. - $sql_ins = " - INSERT INTO " . $tbl_lp_item . " ( - lp_id, - item_type, - ref, - title, - description, - path, - max_score, - parent_item_id, - previous_item_id, - next_item_id, - display_order, - max_time_allowed - ) VALUES ( - " . $this->get_id() . ", - '" . $type . "', - '', - '" . $title . "', - '" . $description . "', - '" . $id . "', - '" . $max_score . "', - " . $parent . ", - " . $previous . ", - " . $next . ", - " . ($display_order +1) . ", - " . $max_time_allowed . " - )"; - } - - if ($this->debug > 2) { - error_log('New LP - Inserting dokeos_chapter: ' . $sql_ins, 0); - } - - $res_ins = Database::query($sql_ins); - - if ($res_ins > 0) { - $new_item_id = Database :: insert_id($res_ins); - - // Update the item that should come after the new item. - $sql_update_next = " - UPDATE " . $tbl_lp_item . " - SET previous_item_id = " . $new_item_id . " - WHERE id = " . $next; - - $res_update_next = Database::query($sql_update_next); - - // Update the item that should be before the new item. - $sql_update_previous = " - UPDATE " . $tbl_lp_item . " - SET next_item_id = " . $new_item_id . " - WHERE id = " . $tmp_previous; - - $res_update_previous = Database::query($sql_update_previous); - - // Update all the items after the new item. - $sql_update_order = " - UPDATE " . $tbl_lp_item . " - SET display_order = display_order + 1 - WHERE - lp_id = " . $this->get_id() . " AND - id <> " . $new_item_id . " AND - parent_item_id = " . $parent . " AND - display_order > " . $display_order; - - $res_update_previous = Database::query($sql_update_order); - - // Update the item that should come after the new item. - $sql_update_ref = " - UPDATE " . $tbl_lp_item . " - SET ref = " . $new_item_id . " - WHERE id = " . $new_item_id; - - Database::query($sql_update_ref); - - } - - // Upload audio. - if (!empty ($_FILES['mp3']['name'])) { - // Create the audio folder if it does not exist yet. - global $_course; - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; - if (!is_dir($filepath . 'audio')) { - mkdir($filepath . 'audio', api_get_permissions_for_new_directories()); - $audio_id = add_document($_course, '/audio', 'folder', 0, 'audio'); - api_item_property_update($_course, TOOL_DOCUMENT, $audio_id, 'FolderCreated', api_get_user_id(), null, null, null, null, api_get_session_id()); - } - - // Upload the file in the documents tool. - include_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; - $file_path = handle_uploaded_document($_course, $_FILES['mp3'], api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document', '/audio', api_get_user_id(), '', '', '', '', '', false); - - // Getting the filename only. - $file_components = explode('/', $file_path); - $file = $file_components[count($file_components) - 1]; - - // Store the mp3 file in the lp_item table. - $sql_insert_audio = "UPDATE $tbl_lp_item SET audio = '" . Database :: escape_string($file) . "' WHERE id = '" . Database :: escape_string($new_item_id) . "'"; - Database::query($sql_insert_audio); - } - return $new_item_id; - } - - /** - * Static admin function allowing addition of a learnpath to a course. - * @param string Course code - * @param string Learnpath name - * @param string Learnpath description string, if provided - * @param string Type of learnpath (default = 'guess', others = 'dokeos', 'aicc',...) - * @param string Type of files origin (default = 'zip', others = 'dir','web_dir',...) - * @param string Zip file containing the learnpath or directory containing the learnpath - * @return integer The new learnpath ID on success, 0 on failure - */ - public function add_lp($course, $name, $description = '', $learnpath = 'guess', $origin = 'zip', $zipname = '') { - global $charset; - - //if ($this->debug > 0) { error_log('New LP - In learnpath::add_lp()', 0); } - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - // Check course code exists. - // Check lp_name doesn't exist, otherwise append something. - $i = 0; - $name = learnpath :: escape_string($name); - - // Session id. - $session_id = api_get_session_id(); - - $check_name = "SELECT * FROM $tbl_lp WHERE name = '$name'"; - //if ($this->debug > 2) { error_log('New LP - Checking the name for new LP: '.$check_name, 0); } - $res_name = Database::query($check_name); - while (Database :: num_rows($res_name)) { - // There is already one such name, update the current one a bit. - $i++; - $name = $name . ' - ' . $i; - $check_name = "SELECT * FROM $tbl_lp WHERE name = '$name'"; - //if ($this->debug > 2) { error_log('New LP - Checking the name for new LP: '.$check_name, 0); } - $res_name = Database::query($check_name); - } - // New name does not exist yet; keep it. - // Escape description. - $description = learnpath :: escape_string(api_htmlentities($description, ENT_QUOTES, $charset)); // Kevin: added htmlentities(). - $type = 1; - switch ($learnpath) { - case 'guess': - break; - case 'dokeos': - $type = 1; - break; - case 'aicc': - break; - } - switch ($origin) { - case 'zip': - // Check zipname string. If empty, we are currently creating a new Chamilo learnpath. - break; - case 'manual': - default: - $get_max = "SELECT MAX(display_order) FROM $tbl_lp"; - $res_max = Database::query($get_max); - if (Database :: num_rows($res_max) < 1) { - $dsp = 1; - } else { - $row = Database :: fetch_array($res_max); - $dsp = $row[0] + 1; - } - $sql_insert = "INSERT INTO $tbl_lp " . - "(lp_type,name,description,path,default_view_mod," . - "default_encoding,display_order,content_maker," . - "content_local,js_lib,session_id) " . - "VALUES ($type,'$name','$description','','embedded'," . - "'UTF-8','$dsp','Chamilo'," . - "'local','','".Database::escape_string($session_id)."')"; - //if ($this->debug > 2) { error_log('New LP - Inserting new lp '.$sql_insert, 0); } - $res_insert = Database::query($sql_insert); - $id = Database :: insert_id(); - if ($id > 0) { - // Insert into item_property. - api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $id, 'LearnpathAdded', api_get_user_id()); - return $id; - } - break; - } - } - - /** - * Appends a message to the message attribute - * @param string Message to append. - */ - public function append_message($string) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::append_message()', 0); - } - $this->message .= $string; - } - - /** - * Autocompletes the parents of an item in case it's been completed or passed - * @param integer Optional ID of the item from which to look for parents - */ - public function autocomplete_parents($item) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::autocomplete_parents()', 0); - } - if (empty ($item)) { - $item = $this->current; - } - $parent_id = $this->items[$item]->get_parent(); - if ($this->debug > 2) { - error_log('New LP - autocompleting parent of item ' . $item . ' (item ' . $parent_id . ')', 0); - } - if (is_object($this->items[$item]) and !empty ($parent_id)) { - // if $item points to an object and there is a parent. - if ($this->debug > 2) { - error_log('New LP - ' . $item . ' is an item, proceed', 0); - } - $current_item = & $this->items[$item]; - $parent = & $this->items[$parent_id]; // Get the parent. - // New experiment including failed and browsed in completed status. - $current_status = $current_item->get_status(); - if ($current_item->is_done() || $current_status == 'browsed' || $current_status == 'failed') { - // If the current item is completed or passes or succeeded. - $completed = true; - if ($this->debug > 2) { - error_log('New LP - Status of current item is alright', 0); - } - foreach ($parent->get_children() as $child) { - // Check all his brothers (his parent's children) for completion status. - if ($child != $item) { - if ($this->debug > 2) { - error_log('New LP - Looking at brother with ID ' . $child . ', status is ' . $this->items[$child]->get_status(), 0); - } - //if($this->items[$child]->status_is(array('completed','passed','succeeded'))) - // Trying completing parents of failed and browsed items as well. - if ($this->items[$child]->status_is(array ( - 'completed', - 'passed', - 'succeeded', - 'browsed', - 'failed' - ))) { - // Keep completion status to true. - } else { - if ($this->debug > 2) { - error_log('New LP - Found one incomplete child of ' . $parent_id . ': ' . $child . ' is ' . $this->items[$child]->get_status(), 0); - } - $completed = false; - } - } - } - if ($completed) { // If all the children were completed: - $parent->set_status('completed'); - $parent->save(false, $this->prerequisites_match($parent->get_id())); - $this->update_queue[$parent->get_id()] = $parent->get_status(); - if ($this->debug > 2) { - error_log('New LP - Added parent to update queue ' . print_r($this->update_queue, true), 0); - } - $this->autocomplete_parents($parent->get_id()); // Recursive call. - } - } else { - //error_log('New LP - status of current item is not enough to get bothered with it', 0); - } - } - } - - /** - * Autosaves the current results into the database for the whole learnpath - */ - public function autosave() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::autosave()', 0); - } - // TODO: Add save operations for the learnpath itself. - } - - /** - * Clears the message attribute - */ - public function clear_message() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::clear_message()', 0); - } - $this->message = ''; - } - - /** - * Closes the current resource - * - * Stops the timer - * Saves into the database if required - * Clears the current resource data from this object - * @return boolean True on success, false on failure - */ - public function close() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::close()', 0); - } - if (empty ($this->lp_id)) { - $this->error = 'Trying to close this learnpath but no ID is set'; - return false; - } - $this->current_time_stop = time(); - if ($this->save) { - $learnpath_view_table = Database :: get_course_table(TABLE_LP_VIEW); - /* - $sql = "UPDATE $learnpath_view_table " . - "SET " . - "stop_time = ".$this->current_time_stop.", " . - "score = ".$this->current_score.", ". - "WHERE learnpath_id = '".$this->lp_id."'"; - //$res = Database::query($sql); - $res = Database::query($res); - if (Database::affected_rows($res) < 1) { - $this->error = 'Could not update learnpath_view table while closing learnpath'; - return false; - } - */ - } - $this->ordered_items = array (); - $this->index = 0; - unset ($this->lp_id); - //unset other stuff - return true; - } - - /** - * Static admin function allowing removal of a learnpath - * @param string Course code - * @param integer Learnpath ID - * @param string Whether to delete data or keep it (default: 'keep', others: 'remove') - * @return boolean True on success, false on failure (might change that to return number of elements deleted) - */ - public function delete($course = null, $id = null, $delete = 'keep') { - // TODO: Implement a way of getting this to work when the current object is not set. - // In clear: implement this in the item class as well (abstract class) and use the given ID in queries. - //if (empty($course)) { $course = api_get_course_id(); } - //if (empty($id)) { $id = $this->get_id(); } - // If an ID is specifically given and the current LP is not the same, prevent delete. - if (!empty ($id) && ($id != $this->lp_id)) { - return false; - } - - $lp = Database :: get_course_table(TABLE_LP_MAIN); - $lp_item = Database :: get_course_table(TABLE_LP_ITEM); // Proposed by Christophe (clefevre), see below. - $lp_view = Database :: get_course_table(TABLE_LP_VIEW); - $lp_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW); - - //if ($this->debug > 0) { error_log('New LP - In learnpath::delete()', 0); } - // Delete lp item id. - foreach ($this->items as $id => $dummy) { - //$this->items[$id]->delete(); - $sql_del_view = "DELETE FROM $lp_item_view WHERE lp_item_id = '" . $id . "'"; - $res_del_item_view = Database::query($sql_del_view); - } - - // Proposed by Christophe (nickname: clefevre), see http://www.dokeos.com/forum/viewtopic.php?t=29673 - $sql_del_item = "DELETE FROM $lp_item WHERE lp_id = " . $this->lp_id; - $res_del_item = Database::query($sql_del_item); - - $sql_del_view = "DELETE FROM $lp_view WHERE lp_id = " . $this->lp_id; - //if ($this->debug > 2) { error_log('New LP - Deleting views bound to lp '.$this->lp_id.': '.$sql_del_view, 0); } - $res_del_view = Database::query($sql_del_view); - $this->toggle_publish($this->lp_id, 'i'); - //if ($this->debug > 2) { error_log('New LP - Deleting lp '.$this->lp_id.' of type '.$this->type, 0); } - if ($this->type == 2 || $this->type == 3) { - // This is a scorm learning path, delete the files as well. - $sql = "SELECT path FROM $lp WHERE id = " . $this->lp_id; - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $path = $row['path']; - $sql = "SELECT id FROM $lp WHERE path = '$path' AND id != " . $this->lp_id; - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { // Another learning path uses this directory, so don't delete it. - if ($this->debug > 2) { - error_log('New LP - In learnpath::delete(), found other LP using path ' . $path . ', keeping directory', 0); - } - } else { - // No other LP uses that directory, delete it. - $course_rel_dir = api_get_course_path() . '/scorm/'; // scorm dir web path starting from /courses - $course_scorm_dir = api_get_path(SYS_COURSE_PATH) . $course_rel_dir; // The absolute system path for this course. - if ($delete == 'remove' && is_dir($course_scorm_dir . $path) and !empty ($course_scorm_dir)) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::delete(), found SCORM, deleting directory: ' . $course_scorm_dir . $path, 0); - } - // Proposed by Christophe (clefevre). - if (strcmp(substr($path, -2), "/.") == 0) { - $path = substr($path, 0, -1); // Remove "." at the end. - } - //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way. - rmdirr($course_scorm_dir . $path); - } - } - } - } - $sql_del_lp = "DELETE FROM $lp WHERE id = " . $this->lp_id; - //if ($this->debug > 2) { error_log('New LP - Deleting lp '.$this->lp_id.': '.$sql_del_lp, 0); } - $res_del_lp = Database::query($sql_del_lp); - $this->update_display_order(); // Updates the display order of all lps. - api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $this->lp_id, 'delete', api_get_user_id()); - - require_once '../gradebook/lib/be.inc.php'; - $tbl_grade_link = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_LINK); - // Delete link of gradebook tool. - $sql = 'SELECT gl.id FROM ' . $tbl_grade_link . ' gl WHERE gl.type="4" AND gl.ref_id="' . $id . '";'; - $result = Database::query($sql); - $row = Database :: fetch_array($result, 'ASSOC'); - - // Fixing gradebook link deleted see #5229. - if (!empty($row['id'])) { - $link = LinkFactory :: load($row['id']); - if ($link[0] != null) { - $link[0]->delete(); - } - } - - // TODO: Also delete items and item-views. - if (api_get_setting('search_enabled') == 'true') { - require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; - $r = delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id); - } - } - - /** - * Removes all the children of one item - dangerous! - * @param integer Element ID of which children have to be removed - * @return integer Total number of children removed - */ - public function delete_children_items($id) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::delete_children_items(' . $id . ')', 0); - } - $num = 0; - if (empty ($id) || $id != strval(intval($id))) { - return false; - } - $lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql = "SELECT * FROM $lp_item WHERE parent_item_id = $id"; - $res = Database::query($sql); - while ($row = Database :: fetch_array($res)) { - $num += $this->delete_children_items($row['id']); - $sql_del = "DELETE FROM $lp_item WHERE id = " . $row['id']; - $res_del = Database::query($sql_del); - $num++; - } - return $num; - } - - /** - * Removes an item from the current learnpath - * @param integer Elem ID (0 if first) - * @param integer Whether to remove the resource/data from the system or leave it (default: 'keep', others 'remove') - * @return integer Number of elements moved - * @todo implement resource removal - */ - public function delete_item($id, $remove = 'keep') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::delete_item()', 0); - } - // TODO: Implement the resource removal. - if (empty ($id) || $id != strval(intval($id))) { - return false; - } - // First select item to get previous, next, and display order. - $lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql_sel = "SELECT * FROM $lp_item WHERE id = $id"; - $res_sel = Database::query($sql_sel); - if (Database :: num_rows($res_sel) < 1) { - return false; - } - $row = Database :: fetch_array($res_sel); - $previous = $row['previous_item_id']; - $next = $row['next_item_id']; - $display = $row['display_order']; - $parent = $row['parent_item_id']; - $lp = $row['lp_id']; - // Delete children items. - $num = $this->delete_children_items($id); - if ($this->debug > 2) { - error_log('New LP - learnpath::delete_item() - deleted ' . $num . ' children of element ' . $id, 0); - } - // Now delete the item. - $sql_del = "DELETE FROM $lp_item WHERE id = $id"; - if ($this->debug > 2) { - error_log('New LP - Deleting item: ' . $sql_del, 0); - } - $res_del = Database::query($sql_del); - // Now update surrounding items. - $sql_upd = "UPDATE $lp_item SET next_item_id = $next WHERE id = $previous"; - $res_upd = Database::query($sql_upd); - $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous WHERE id = $next"; - $res_upd = Database::query($sql_upd); - // Now update all following items with new display order. - $sql_all = "UPDATE $lp_item SET display_order = display_order-1 WHERE lp_id = $lp AND parent_item_id = $parent AND display_order > $display"; - $res_all = Database::query($sql_all); - // Remove from search engine if enabled. - if (api_get_setting('search_enabled') == 'true') { - $tbl_se_ref = Database :: get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); - $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; - $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row2 = Database :: fetch_array($res); - require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; - $di = new DokeosIndexer(); - $di->remove_document((int) $row2['search_did']); - } - $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; - $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); - Database::query($sql); - } - } - - /** - * Updates an item's content in place - * @param integer Element ID - * @param integer Parent item ID - * @param integer Previous item ID - * @param string Item title - * @param string Item description - * @param string Prerequisites (optional) - * @param string Indexing terms (optional) - * @param array The array resulting of the $_FILES[mp3] element - * @return boolean True on success, false on error - */ - public function edit_item($id, $parent, $previous, $title, $description, $prerequisites = 0, $audio = null, $max_time_allowed = 0) { - - global $charset; - - if ($this->debug > 0) { - error_log('New LP - In learnpath::edit_item()', 0); - } - if (empty ($max_time_allowed)) { - $max_time_allowed = 0; - } - if (empty ($id) || ($id != strval(intval($id))) || empty ($title)) { - return false; - } - $title = api_convert_encoding($title, $this->encoding, $charset); - $description = api_convert_encoding($description, $this->encoding, $charset); - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql_select = "SELECT * FROM " . $tbl_lp_item . " WHERE id = " . $id; - $res_select = Database::query($sql_select); - $row_select = Database :: fetch_array($res_select); - $audio_update_sql = ''; - if (is_array($audio) && !empty ($audio['tmp_name']) && $audio['error'] === 0) { - // Create the audio folder if it does not exist yet. - global $_course; - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; - if (!is_dir($filepath . 'audio')) { - mkdir($filepath . 'audio', api_get_permissions_for_new_directories()); - $audio_id = add_document($_course, '/audio', 'folder', 0, 'audio'); - api_item_property_update($_course, TOOL_DOCUMENT, $audio_id, 'FolderCreated', api_get_user_id(), null, null, null, null, api_get_session_id()); - } - - // Upload file in documents. - $pi = pathinfo($audio['name']); - if ($pi['extension'] == 'mp3') { - $c_det = api_get_course_info($this->cc); - $bp = api_get_path(SYS_COURSE_PATH) . $c_det['path'] . '/document'; - $path = handle_uploaded_document($c_det, $audio, $bp, '/audio', api_get_user_id(), 0, null, '', 0, 'rename', false, 0); - $path = substr($path, 7); - // Update reference in lp_item - audio path is the path from inside de document/audio/ dir. - $audio_update_sql = ", audio = '" . Database :: escape_string($path) . "' "; - } - } - - $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false; - $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false; - - // TODO: htmlspecialchars to be checked for encoding related problems. - if ($same_parent && $same_previous) { - // Only update title and description. - $sql_update = " UPDATE " . $tbl_lp_item . " - SET title = '" . $this->escape_string($title) . "', - prerequisite = '" . $prerequisites . "', - description = '" . $this->escape_string($description) . "' - " . $audio_update_sql . ", - max_time_allowed = '" . $this->escape_string($max_time_allowed) . "' - WHERE id = " . $id; - $res_update = Database::query($sql_update); - } else { - $old_parent = $row_select['parent_item_id']; - $old_previous = $row_select['previous_item_id']; - $old_next = $row_select['next_item_id']; - $old_order = $row_select['display_order']; - $old_prerequisite = $row_select['prerequisite']; - $old_max_time_allowed = $row_select['max_time_allowed']; - - /* BEGIN -- virtually remove the current item id */ - /* for the next and previous item it is like the current item doesn't exist anymore */ - - if ($old_previous != 0) { - $sql_update_next = " - UPDATE " . $tbl_lp_item . " - SET next_item_id = " . $old_next . " - WHERE id = " . $old_previous; - $res_update_next = Database::query($sql_update_next); - //echo '

' . $sql_update_next . '

'; - } - - if ($old_next != 0) { - $sql_update_previous = " - UPDATE " . $tbl_lp_item . " - SET previous_item_id = " . $old_previous . " - WHERE id = " . $old_next; - $res_update_previous = Database::query($sql_update_previous); - - //echo '

' . $sql_update_previous . '

'; - } - - // display_order - 1 for every item with a display_order bigger then the display_order of the current item. - $sql_update_order = " - UPDATE " . $tbl_lp_item . " - SET display_order = display_order - 1 - WHERE - display_order > " . $old_order . " AND lp_id = " . $this->lp_id . " AND - parent_item_id = " . $old_parent; - $res_update_order = Database::query($sql_update_order); - - //echo '

' . $sql_update_order . '

'; - - /* END -- virtually remove the current item id */ - - /* BEGIN -- update the current item id to his new location */ - - if ($previous == 0) { - // Select the data of the item that should come after the current item. - $sql_select_old = " - SELECT - id, - display_order - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id . " AND - parent_item_id = " . $parent . " AND - previous_item_id = " . $previous; - $res_select_old = Database::query($sql_select_old); - $row_select_old = Database :: fetch_array($res_select_old); - - //echo '

' . $sql_select_old . '

'; - - // If the new parent didn't have children before. - if (Database :: num_rows($res_select_old) == 0) { - $new_next = 0; - $new_order = 1; - } else { - $new_next = $row_select_old['id']; - $new_order = $row_select_old['display_order']; - } - - //echo 'New next_item_id of current item: ' . $new_next . '
'; - //echo 'New previous_item_id of current item: ' . $previous . '
'; - //echo 'New display_order of current item: ' . $new_order . '
'; - - } else { - // Select the data of the item that should come before the current item. - $sql_select_old = " - SELECT - next_item_id, - display_order - FROM " . $tbl_lp_item . " - WHERE id = " . $previous; - $res_select_old = Database::query($sql_select_old); - $row_select_old = Database :: fetch_array($res_select_old); - - //echo '

' . $sql_select_old . '

'; - - //echo 'New next_item_id of current item: ' . $row_select_old['next_item_id'] . '
'; - //echo 'New previous_item_id of current item: ' . $previous . '
'; - //echo 'New display_order of current item: ' . ($row_select_old['display_order'] + 1) . '
'; - - $new_next = $row_select_old['next_item_id']; - $new_order = $row_select_old['display_order'] + 1; - } - - // TODO: htmlspecialchars to be checked for encoding related problems. - // Update the current item with the new data. - $sql_update = " - UPDATE " . $tbl_lp_item . " - SET - title = '" . $this->escape_string($title) . "', - description = '" . $this->escape_string($description) . "', - parent_item_id = " . $parent . ", - previous_item_id = " . $previous . ", - next_item_id = " . $new_next . ", - display_order = " . $new_order . " - " . $audio_update_sql . " - WHERE id = " . $id; - $res_update_next = Database::query($sql_update); - //echo '

' . $sql_update . '

'; - - if ($previous != 0) { - // Update the previous item's next_item_id. - $sql_update_previous = " - UPDATE " . $tbl_lp_item . " - SET next_item_id = " . $id . " - WHERE id = " . $previous; - $res_update_next = Database::query($sql_update_previous); - //echo '

' . $sql_update_previous . '

'; - } - - if ($new_next != 0) { - // Update the next item's previous_item_id. - $sql_update_next = " - UPDATE " . $tbl_lp_item . " - SET previous_item_id = " . $id . " - WHERE id = " . $new_next; - $res_update_next = Database::query($sql_update_next); - //echo '

' . $sql_update_next . '

'; - } - - if ($old_prerequisite != $prerequisites) { - $sql_update_next = " - UPDATE " . $tbl_lp_item . " - SET prerequisite = " . $prerequisites . " - WHERE id = " . $id; - $res_update_next = Database::query($sql_update_next); - } - - if ($old_max_time_allowed != $max_time_allowed) { - $sql_update_max_time_allowed = " - UPDATE " . $tbl_lp_item . " - SET max_time_allowed = " . $max_time_allowed . " - WHERE id = " . $id; - $res_update_max_time_allowed = Database::query($sql_update_max_time_allowed); - } - - // Update all the items with the same or a bigger display_order than the current item. - $sql_update_order = " - UPDATE " . $tbl_lp_item . " - SET display_order = display_order + 1 - WHERE - lp_id = " . $this->get_id() . " AND - id <> " . $id . " AND - parent_item_id = " . $parent . " AND - display_order >= " . $new_order; - - $res_update_next = Database::query($sql_update_order); - } - } - - /** - * Updates an item's prereq in place - * @param integer Element ID - * @param string Prerequisite Element ID - * @param string Prerequisite item type - * @param string Prerequisite min score - * @param string Prerequisite max score - * @return boolean True on success, false on error - */ - public function edit_item_prereq($id, $prerequisite_id, $mastery_score = 0, $max_score = 100) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::edit_item_prereq(' . $id . ',' . $prerequisite_id . ',' . $mastery_score . ',' . $max_score . ')', 0); - } - - if (empty ($id) or ($id != strval(intval($id))) or empty ($prerequisite_id)) { - return false; - } - - $prerequisite_id = $this->escape_string($prerequisite_id); - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - if (!is_numeric($mastery_score) || $mastery_score < 0) - $mastery_score = 0; - - if (!is_numeric($max_score) || $max_score < 0) - $max_score = 100; - - if ($mastery_score > $max_score) - $max_score = $mastery_score; - - if (!is_numeric($prerequisite_id)) - $prerequisite_id = 'NULL'; - - $sql_upd = " UPDATE " . $tbl_lp_item . " - SET prerequisite = " . $prerequisite_id . " WHERE id = " . $id; - $res_upd = Database::query($sql_upd); - - if ($prerequisite_id != 'NULL' && $prerequisite_id != '') { - $sql_upd = " UPDATE " . $tbl_lp_item . " SET - mastery_score = " . $mastery_score . - //", max_score = " . $max_score . " " . // Max score cannot be changed in the form anyway - see display_item_prerequisites_form(). - " WHERE ref = '" . $prerequisite_id . "'"; // Will this be enough to ensure unicity? - $res_upd = Database::query($sql_upd); - } - // TODO: Update the item object (can be ignored for now because refreshed). - return true; - } - - /** - * Escapes a string with the available database escape function - * @param string String to escape - * @return string String escaped - */ - public function escape_string($string) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::escape_string('.$string.')', 0); } - return Database :: escape_string($string); - } - - /** - * Static admin function exporting a learnpath into a zip file - * @param string Export type (scorm, zip, cd) - * @param string Course code - * @param integer Learnpath ID - * @param string Zip file name - * @return string Zip file path (or false on error) - */ - public function export_lp($type, $course, $id, $zipname) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::export_lp()', 0); } - if (empty($type) || empty($course) || empty($id) || empty($zipname)) { - return false; - } - $url = ''; - - switch ($type) { - case 'scorm': - break; - case 'zip': - break; - case 'cdrom': - break; - } - return $url; - } - - /** - * Gets all the chapters belonging to the same parent as the item/chapter given - * Can also be called as abstract method - * @param integer Item ID - * @return array A list of all the "brother items" (or an empty array on failure) - */ - public function get_brother_chapters($id) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_brother_chapters()', 0); - } - - if (empty ($id) OR $id != strval(intval($id))) { - return array (); - } - - $lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql_parent = "SELECT * FROM $lp_item WHERE id = $id AND item_type='dokeos_chapter'"; - $res_parent = Database::query($sql_parent); - if (Database :: num_rows($res_parent) > 0) { - $row_parent = Database :: fetch_array($res_parent); - $parent = $row_parent['parent_item_id']; - $sql_bros = "SELECT * FROM $lp_item WHERE parent_item_id = $parent AND id = $id AND item_type='dokeos_chapter' ORDER BY display_order"; - $res_bros = Database::query($sql_bros); - $list = array (); - while ($row_bro = Database :: fetch_array($res_bros)) { - $list[] = $row_bro; - } - return $list; - } - return array (); - } - - /** - * Gets all the items belonging to the same parent as the item given - * Can also be called as abstract method - * @param integer Item ID - * @return array A list of all the "brother items" (or an empty array on failure) - */ - public function get_brother_items($id) { - - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_brother_items(' . $id . ')', 0); - } - - if (empty ($id) OR $id != strval(intval($id))) { - return array (); - } - - $lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql_parent = "SELECT * FROM $lp_item WHERE id = $id"; - $res_parent = Database::query($sql_parent); - if (Database :: num_rows($res_parent) > 0) { - $row_parent = Database :: fetch_array($res_parent); - $parent = $row_parent['parent_item_id']; - $sql_bros = "SELECT * FROM $lp_item WHERE parent_item_id = $parent ORDER BY display_order"; - $res_bros = Database::query($sql_bros); - $list = array (); - while ($row_bro = Database :: fetch_array($res_bros)) { - $list[] = $row_bro; - } - return $list; - } - return array (); - } - - /** - * Get the specific prefix index terms of this learning path - * @return array Array of terms - */ - public function get_common_index_terms_by_prefix($prefix) { - require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; - $terms = get_specific_field_values_list_by_prefix($prefix, $this->cc, TOOL_LEARNPATH, $this->lp_id); - $prefix_terms = array(); - foreach ($terms as $term) { - $prefix_terms[] = $term['value']; - } - return $prefix_terms; - } - - /** - * Gets the number of items currently completed - * @return integer The number of items currently completed - */ - public function get_complete_items_count() { - - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_complete_items_count()', 0); - } - - $i = 0; - - foreach ($this->items as $id => $dummy) { - // Trying failed and browsed considered "progressed" as well. - if ($this->items[$id]->status_is(array ( - 'completed', - 'passed', - 'succeeded', - 'browsed', - 'failed' - )) && $this->items[$id]->get_type() != 'dokeos_chapter' && $this->items[$id]->get_type() != 'dir') { - $i++; - } - } - return $i; - } - - /** - * Gets the current item ID - * @return integer The current learnpath item id - */ - public function get_current_item_id() { - $current = 0; - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_current_item_id()', 0); - } - if (!empty ($this->current)) { - $current = $this->current; - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_current_item_id() - Returning ' . $current, 0); - } - return $current; - } - - /** - * Force to get the first learnpath item id - * @return integer The current learnpath item id - */ - public function get_first_item_id() { - $current = 0; - if (is_array($this->ordered_items)) { - $current = $this->ordered_items[0]; - } - return $current; - } - - /** - * Gets the total number of items available for viewing in this SCORM - * @return integer The total number of items - */ - public function get_total_items_count() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_total_items_count()', 0); - } - return count($this->items); - } - - /** - * Gets the total number of items available for viewing in this SCORM but without chapters - * @return integer The total no-chapters number of items - */ - public function get_total_items_count_without_chapters() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_total_items_count_without_chapters()', 0); - } - $total = 0; - foreach ($this->items as $temp => $temp2) { - if (!in_array($temp2->get_type(), array ( - 'dokeos_chapter', - 'chapter', - 'dir' - ))) - $total++; - } - return $total; - } - - /** - * Gets the first element URL. - * @return string URL to load into the viewer - */ - public function first() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::first()', 0); - } - // Test if the last_item_seen exists and is not a dir. - if (count($this->ordered_items) == 0) { - $this->index = 0; - } - if (!empty($this->last_item_seen) && !empty($this->items[$this->last_item_seen]) && $this->items[$this->last_item_seen]->get_type() != 'dir' && $this->items[$this->last_item_seen]->get_type() != 'dokeos_chapter' && !$this->items[$this->last_item_seen]->is_done()) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::first() - Last item seen is ' . $this->last_item_seen . ' of type ' . $this->items[$this->last_item_seen]->get_type(), 0); - } - $index = -1; - foreach ($this->ordered_items as $myindex => $item_id) { - if ($item_id == $this->last_item_seen) { - $index = $myindex; - break; - } - } - if ($index == -1) { - // Index hasn't changed, so item not found - panic (this shouldn't happen). - if ($this->debug > 2) { - error_log('New LP - Last item (' . $this->last_item_seen . ') was found in items but not in ordered_items, panic!', 0); - } - return false; - } else { - $this->last = $this->last_item_seen; - $this->current = $this->last_item_seen; - $this->index = $index; - } - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::first() - No last item seen', 0); - } - $index = 0; - // Loop through all ordered items and stop at the first item that is - // not a directory *and* that has not been completed yet. - while (!empty($this->ordered_items[$index]) AND is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' OR $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter' OR $this->items[$this->ordered_items[$index]]->is_done() === true) AND $index < $this->max_ordered_items) { - $index++; - } - $this->last = $this->current; - // current is - $this->current = $this->ordered_items[$index]; - $this->index = $index; - if ($this->debug > 2) { - error_log('New LP - In learnpath::first() - No last item seen. New last = ' . $this->last . '(' . $this->ordered_items[$index] . ')', 0); - } - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::first() - First item is ' . $this->get_current_item_id()); - } - } - - /** - * Gets the information about an item in a format usable as JavaScript to update - * the JS API by just printing this content into the section of the message frame - * @param integer Item ID - * @return string - */ - public function get_js_info($item_id = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_js_info(' . $item_id . ')', 0); - } - - $info = ''; - $item_id = $this->escape_string($item_id); - - if (!empty($item_id) && is_object($this->items[$item_id])) { - //if item is defined, return values from DB - $oItem = $this->items[$item_id]; - $info .= ''; - if ($this->debug > 2) { - error_log('New LP - in learnpath::get_js_info(' . $item_id . ') - returning: ' . $info, 0); - } - return $info; - - } else { - - // If item_id is empty, just update to default SCORM data. - $info .= ''; - if ($this->debug > 2) { - error_log('New LP - in learnpath::get_js_info(' . $item_id . ') - returning: ' . $info, 0); - } - return $info; - } - } - - /** - * Gets the js library from the database - * @return string The name of the javascript library to be used - */ - public function get_js_lib() { - $lib = ''; - if (!empty ($this->js_lib)) { - $lib = $this->js_lib; - } - return $lib; - } - - /** - * Gets the learnpath database ID - * @return integer Learnpath ID in the lp table - */ - public function get_id() { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_id()', 0); } - if (!empty ($this->lp_id)) { - return $this->lp_id; - } else { - return 0; - } - } - - /** - * Gets the last element URL. - * @return string URL to load into the viewer - */ - public function get_last() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_last()', 0); - } - $this->index = count($this->ordered_items) - 1; - return $this->ordered_items[$this->index]; - } - - /** - * Gets the navigation bar for the learnpath display screen - * @return string The HTML string to use as a navigation bar - */ - public function get_navigation_bar() { - - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_navigation_bar()', 0); - } - - // TODO: Find a good value for the following variables. - $file = ''; - $openDir = ''; - $edoceo = ''; - $time = 0; - $navbar = ''; - $RequestUri = ''; - $mycurrentitemid = $this->get_current_item_id(); - if ($this->mode == 'fullscreen') { - $navbar = '' . "\n" . - ' ' . "\n" . - ' ' . "\n"; - $return .= ''; - $return .= ''; - $return .= ''; - - return $return; - } - - /** - * Return HTML list to allow prerequisites selection for lp - * @param integer Item ID - * @return string HTML form - */ - public function display_lp_prerequisites_list() { - global $charset; - $lp_id = $this->lp_id; - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - - // get current prerequisite - $sql = "SELECT * FROM $tbl_lp WHERE id = $lp_id "; - $result = Database::query($sql); - $row = Database :: fetch_array($result); - $preq_id = $row['prerequisite']; - $session_id = api_get_session_id(); - $session_condition = api_get_session_condition($session_id, false); - $sql = "SELECT * FROM $tbl_lp $session_condition ORDER BY display_order "; - $rs = Database::query($sql); - $return = ''; - $return .= ''; - return $return; - } - /** - * Creates a list with all the documents in it - * @return string - */ - public function get_documents() { - global $_course; - - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $tbl_item_prop = Database::get_course_table(TABLE_ITEM_PROPERTY); - $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE); - $path = '/'; - $path = Database::escape_string(str_replace('_', '\_', $path)); - $added_slash = ($path == '/') ? '' : '/'; - - //condition for the session - $current_session_id = api_get_session_id(); - $condition_session = " AND (id_session = '$current_session_id' OR (id_session = '0' AND insert_date <= (SELECT creation_date FROM $tbl_course WHERE code = '{$_course[id]}')))"; - - $sql_doc = "SELECT docs.* - FROM $tbl_item_prop AS last, $tbl_doc AS docs - WHERE docs.id = last.ref - AND docs.path LIKE '".$path.$added_slash."%' - AND docs.path NOT LIKE '%_DELETED_%' - AND last.tool = '".TOOL_DOCUMENT."' $condition_session ORDER BY docs.path ASC"; - - //$sql_doc = "SELECT * FROM $tbl_doc WHERE path NOT LIKE '%_DELETED_%' ORDER BY path ASC"; - $res_doc = Database::query($sql_doc); - - $return = '
'.Display::return_icon('folder_document.gif',get_lang('Documents'),array('style'=>'margin-right:5px;', 'height' => '16px')).' '. get_lang('Documents') . '
'; - $return .= '
'; - $resources = Database::store_result($res_doc); - $resources_sorted = array(); - - // If you want to debug it, I advise you to do "echo" on the eval statements. - - foreach ($resources as $resource) { - $resource_paths = explode('/', $resource['path']); - array_shift($resource_paths); - $path_to_eval = $last_path = ''; - $is_file = false; - foreach ($resource_paths as $key => $resource_path) { - if (strpos($resource_path, '.') === false && $key != count($resource_paths) - 1) { // It's a folder. - $path_to_eval .= '["' . $resource_path . '"]["files"]'; - } else - if (strpos($resource_path, '.') !== false) - $is_file = true; - $last_path = $resource_path; - } - if ($is_file) { - //eval ('$resources_sorted' . $path_to_eval . '[' . $resource['id'] . '] = "' . $last_path . '";'); - eval ('$resources_sorted' . $path_to_eval . '[' . $resource['id'] . '] = "' .$resource['title']."/". $last_path. '";'); - } else { - eval ('$resources_sorted' . $path_to_eval . '["' . $last_path . '"]["id"]=' . $resource['id'] . ';'); - } - - } - $return .= $this->write_resources_tree($resources_sorted); - if (Database :: num_rows($res_doc) == 0) { - $return .= '
' . get_lang('NoDocuments') . '
'; - } - - $return .= '
'; - return $return; - } - - /** - * Generate and return an HTML list of resources based on a given array. - * This list is used to show the course creator a list of available resources to choose from - * when creating a learning path. - * @param array Array of elements to add to the list - * @param integer Enables the tree display by shifting the new elements a certain distance to the right - * @return string The HTML list - */ - public function write_resources_tree($resources_sorted, $num = 0) { - - require_once api_get_path(LIBRARY_PATH).'fileDisplay.lib.php'; - if (count($resources_sorted) > 0) { - foreach ($resources_sorted as $key => $resource) { - if (is_int($resource['id'])) { - // It's a folder. - $return .= '
 ' . $key . '
'; - } else { - if (!is_array($resource)) { - // It's a file. - $icon = choose_image($resource); - $position = strrpos($icon, '.'); - $icon = substr($icon, 0, $position) . '_small.gif'; - $file_info = explode('/', $resource); - $my_file_title = $file_info[0]; - $my_file_name = $file_info[1]; - //$return .= ''; - // Show the "image name" not the filename of the image. - $return .= '\r\n"; } - } - } - } - return $return; - } - - /** - * Creates a list with all the exercises (quiz) in it - * @return string - */ - public function get_exercises() { - // New for hotpotatoes. - $uploadPath = DIR_HOTPOTATOES; //defined in main_api - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $tbl_quiz = Database :: get_course_table(TABLE_QUIZ_TEST); - - $session_id = api_get_session_id(); - $condition_session = api_get_session_condition($session_id); - - $sql_quiz = " - SELECT * - FROM " . $tbl_quiz . " - WHERE active<>'-1' $condition_session - ORDER BY title ASC"; - - $sql_hot = "SELECT * FROM " . $tbl_doc . " " . - " WHERE path LIKE '" . $uploadPath . "/%/%htm%' $condition_session " . - " ORDER BY id ASC"; - - $res_quiz = Database::query($sql_quiz); - $res_hot = Database::query($sql_hot); - - $return .= '
' . get_lang('Quiz') . '
'; - $return .= '
'; - - while ($row_hot = Database :: fetch_array($res_hot)) { - $return .= '
'; - // Display quizhotpotatoes. - $return .= 'hp'; - $return .= '' . ((!empty ($row_hot['comment'])) ? $row_hot['comment'] : Security :: remove_XSS($row_hot['title'])) . ''; - //$return .= $row_quiz['title']; - $return .= '
'; - } - - while ($row_quiz = Database :: fetch_array($res_quiz)) { - $return .= '
'; - $return .= ''; - $return .= '' . Security :: remove_XSS($row_quiz['title']) . ''; - //$return .= $row_quiz['title']; - $return .= '
'; - } - - if (Database :: num_rows($res_quiz) == 0) { - $return .= '
' . get_lang('NoExercisesAvailable') . '
'; - } - $return .= '
'; - $return .= ''; - $return .= '' . get_lang('NewExercise') . ''; - $return .= '
'; - $return .= '
'; - return $return; - } - - /** - * Creates a list with all the links in it - * @return string - */ - public function get_links() { - $tbl_link = Database :: get_course_table(TABLE_LINK); - - $session_id = api_get_session_id(); - $condition_session = api_get_session_condition($session_id,false); - - $sql_link = "SELECT * FROM $tbl_link $condition_session ORDER BY title ASC"; - $res_link = Database::query($sql_link); - - $return .= '
' . get_lang('Links') . '
'; - $return .= ''; - - return $return; - } - - /** - * Creates a list with all the student publications in it - * @return unknown - */ - public function get_student_publications() { - $tbl_student = Database :: get_course_table(TABLE_STUDENT_PUBLICATION); - $session_id = api_get_session_id(); - $condition_session = api_get_session_condition($session_id, false); - $sql_student = "SELECT * FROM $tbl_student $condition_session ORDER BY title ASC"; - $res_student = Database::query($sql_student); - $return .= '
' . get_lang('Student_publication') . '
'; - $return .= '
'; - $return .= '
'; - $return .= ''; - $return .= '' . get_lang('AddAssignmentPage') . ''; - $return .= '
'; - $return .= '
'; - return $return; - } - - /** - * Creates a list with all the forums in it - * @return string - */ - public function get_forums() { - include '../forum/forumfunction.inc.php'; - include '../forum/forumconfig.inc.php'; - global $table_forums, $table_threads, $table_posts, $table_item_property, $table_users; - $table_forums = Database :: get_course_table(TABLE_FORUM); - $table_threads = Database :: get_course_table(TABLE_FORUM_THREAD); - $table_posts = Database :: get_course_table(TABLE_FORUM_POST); - $table_item_property = Database :: get_course_table(TABLE_ITEM_PROPERTY); - $table_users = Database :: get_main_table(TABLE_MAIN_USER); - $a_forums = get_forums(); - - $return .= '
' . get_lang('Forums') . '
'; - $return .= '
'; - - foreach ($a_forums as $forum) { - $return .= '
'; - $return .= ' - '; - - if (!empty($forum['forum_id'])) { - $return .= ''; - $return .= ' - ' . Security :: remove_XSS($forum['forum_title']) . '
'; - } - - $return .= '
'; - $return .= ''; - $return .= '' . get_lang('CreateANewForum') . ''; - $return .= '
'; - - return $return; - } - - /** - * Exports the learning path as a SCORM package. This is the main function that - * gathers the content, transforms it, writes the imsmanifest.xml file, zips the - * whole thing and returns the zip. - * - * This method needs to be called in PHP5, as it will fail with non-adequate - * XML package (like the ones for PHP4), and it is *not* a static method, so - * you need to call it on a learnpath object. - * @TODO The method might be redefined later on in the scorm class itself to avoid - * creating a SCORM structure if there is one already. However, if the initial SCORM - * path has been modified, it should use the generic method here below. - * @TODO link this function with the export_lp() function in the same class - * @param string Optional name of zip file. If none, title of learnpath is - * domesticated and trailed with ".zip" - * @return string Returns the zip package string, or null if error - */ - public function scorm_export() { - global $_course; - global $charset; - - if (!class_exists('DomDocument')) { - error_log('DOM functions not supported for PHP version below 5.0', 0); - $this->error = 'PHP DOM functions not supported for PHP versions below 5.0'; - return null; - } - - // Remove memory and time limits as much as possible as this might be a long process... - if (function_exists('ini_set')) { - $mem = ini_get('memory_limit'); - if (substr($mem, -1, 1) == 'M') { - $mem_num = substr($mem, 0, -1); - if ($mem_num < 128) { - ini_set('memory_limit', '128M'); - } - } else { - ini_set('memory_limit', '128M'); - } - ini_set('max_execution_time', 600); - //} else { - //error_log('Scorm export: could not change memory and time limits', 0); - } - - // Create the zip handler (this will remain available throughout the method). - $archive_path = api_get_path(SYS_ARCHIVE_PATH); - $sys_course_path = api_get_path(SYS_COURSE_PATH); - $temp_dir_short = uniqid(); - $temp_zip_dir = $archive_path.'/'.$temp_dir_short; - $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip'; - $zip_folder = new PclZip($temp_zip_file); - $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path(); - $root_path = $main_path = api_get_path(SYS_PATH); - $files_cleanup = array(); - - // Place to temporarily stash the zipfiles. - // create the temp dir if it doesn't exist - // or do a cleanup befor creating the zipfile. - if (!is_dir($temp_zip_dir)) { - mkdir($temp_zip_dir, api_get_permissions_for_new_directories()); - } else { - // Cleanup: Check the temp dir for old files and delete them. - $handle=opendir($temp_zip_dir); - while (false !== ($file = readdir($handle))) { - if ($file != '.' && $file != '..') { - unlink("$temp_zip_dir/$file"); - } - } - closedir($handle); - } - $zip_files = $zip_files_abs = $zip_files_dist = array(); - if (is_dir($current_course_path.'/scorm/'.$this->path) && is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')) { - // Remove the possible . at the end of the path. - $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path; - $dest_path_to_scorm_folder = str_replace('//','/',$temp_zip_dir.'/scorm/'.$dest_path_to_lp); - mkdir($dest_path_to_scorm_folder, api_get_permissions_for_new_directories(), true); - $zip_files_dist = copyr($current_course_path.'/scorm/'.$this->path, $dest_path_to_scorm_folder, array('imsmanifest'), $zip_files); - } - // Build a dummy imsmanifest structure. Do not add to the zip yet (we still need it). - // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content - // Aggregation Model official document, secion "2.3 Content Packaging". - $xmldoc = new DOMDocument('1.0', $this->encoding); - $root = $xmldoc->createElement('manifest'); - $root->setAttribute('identifier', 'SingleCourseManifest'); - $root->setAttribute('version', '1.1'); - $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2'); - $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2'); - $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); - $root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'); - // Build mandatory sub-root container elements. - $metadata = $xmldoc->createElement('metadata'); - $md_schema = $xmldoc->createElement('schema', 'ADL SCORM'); - $metadata->appendChild($md_schema); - $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2'); - $metadata->appendChild($md_schemaversion); - $root->appendChild($metadata); - - $organizations = $xmldoc->createElement('organizations'); - - $resources = $xmldoc->createElement('resources'); - - // Build the only organization we will use in building our learnpaths. - $organizations->setAttribute('default','dokeos_scorm_export'); - $organization = $xmldoc->createElement('organization'); - $organization->setAttribute('identifier','dokeos_scorm_export'); - // To set the title of the SCORM entity (=organization), we take the name given - // in Chamilo and convert it to HTML entities using the Chamilo charset (not the - // learning path charset) as it is the encoding that defines how it is stored - // in the database. Then we convert it to HTML entities again as the "&" character - // alone is not authorized in XML (must be &). - // The title is then decoded twice when extracting (see scorm::parse_manifest). - $org_title = $xmldoc->createElement('title',htmlentities(api_htmlentities($this->get_name(),ENT_QUOTES,$charset))); - $organization->appendChild($org_title); - - // For each element, add it to the imsmanifest structure, then add it to the zip. - // Always call the learnpathItem->scorm_export() method to change it to the SCORM format. - $link_updates = array(); - - foreach ($this->items as $index => $item) { - if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) { - // Get included documents from this item. - if ($item->type == 'sco') - $inc_docs = $item->get_resources_from_source(null,api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()); - else - $inc_docs = $item->get_resources_from_source(); - // Give a child element to the element. - $my_item_id = $item->get_id(); - $my_item = $xmldoc->createElement('item'); - $my_item->setAttribute('identifier','ITEM_'.$my_item_id); - $my_item->setAttribute('identifierref','RESOURCE_'.$my_item_id); - $my_item->setAttribute('isvisible','true'); - // Give a child element to the <item> element. - $my_title = $xmldoc->createElement('title',htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); - $my_item->appendChild($my_title); - // Give a child element <adlcp:prerequisites> to the <item> element. - $my_prereqs = $xmldoc->createElement('adlcp:prerequisites',$this->get_scorm_prereq_string($my_item_id)); - $my_prereqs->setAttribute('type','aicc_script'); - $my_item->appendChild($my_prereqs); - // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:maxtimeallowed',''); - // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:timelimitaction',''); - // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:datafromlms',''); - // Give a child element <adlcp:masteryscore> to the <item> element. - $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore',$item->get_mastery_score()); - $my_item->appendChild($my_masteryscore); - - // Attach this item to the organization element or hits parent if there is one. - if (!empty($item->parent) && $item->parent != 0) { - $children = $organization->childNodes; - $possible_parent = &$this->get_scorm_xml_node($children,'ITEM_'.$item->parent); - if (is_object($possible_parent)) { - $possible_parent->appendChild($my_item); - } else { - if ($this->debug > 0) { error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found'); } - } - } else { - if ($this->debug > 0) { error_log('No parent'); } - $organization->appendChild($my_item); - } - - // Get the path of the file(s) from the course directory root. - $my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); - - $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); - $my_sub_dir = dirname($my_file_path); - $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); - // Give a <resource> child to the <resources> element - $my_resource = $xmldoc->createElement('resource'); - $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); - $my_resource->setAttribute('type','webcontent'); - $my_resource->setAttribute('href',$my_xml_file_path); - // adlcp:scormtype can be either 'sco' or 'asset'. - if ($item->type == 'sco') { - $my_resource->setAttribute('adlcp:scormtype', 'sco'); - } else { - $my_resource->setAttribute('adlcp:scormtype', 'asset'); - } - // xml:base is the base directory to find the files declared in this resource. - $my_resource->setAttribute('xml:base',''); - // Give a <file> child to the <resource> element. - $my_file = $xmldoc->createElement('file'); - $my_file->setAttribute('href',$my_xml_file_path); - $my_resource->appendChild($my_file); - - // Dependency to other files - not yet supported. - $i = 1; - foreach ($inc_docs as $doc_info) { - if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } - $my_dep = $xmldoc->createElement('resource'); - $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; - $my_dep->setAttribute('identifier', $res_id); - $my_dep->setAttribute('type', 'webcontent'); - $my_dep->setAttribute('adlcp:scormtype', 'asset'); - $my_dep_file = $xmldoc->createElement('file'); - // Check type of URL. - //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0); - if ($doc_info[1] == 'remote') { - // Remote file. Save url as is. - $my_dep_file->setAttribute('href', $doc_info[0]); - $my_dep->setAttribute('xml:base', ''); - } elseif ($doc_info[1] == 'local') { - switch ($doc_info[2]) { - case 'url': // Local URL - save path as url for now, don't zip file. - $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH),'',$doc_info[0]); - $current_dir = dirname($abs_path); - $file_path = realpath($abs_path); - $my_dep_file->setAttribute('href', $file_path); - $my_dep->setAttribute('xml:base', ''); - if (strstr($file_path,$main_path) !== false) { - // The calculated real path is really inside Chamilo's root path. - // Reduce file path to what's under the DocumentRoot. - $file_path = substr($file_path, strlen($root_path) - 1); - //echo $file_path;echo '<br /><br />'; - //error_log(__LINE__.'Reduced url path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href', $file_path); - $my_dep->setAttribute('xml:base', ''); - } - elseif (empty($file_path)) { - /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); - if (strpos($document_root, -1) == '/') { - $document_root = substr(0, -1, $document_root); - }*/ - $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; - $file_path = str_replace('//', '/', $file_path); - if (file_exists($file_path)) { - $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. - $zip_files[] = $my_sub_dir.'/'.$file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href', $file_path); - $my_dep->setAttribute('xml:base', ''); - } - } - break; - case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. - $my_dep_file->setAttribute('href', $doc_info[0]); - $my_dep->setAttribute('xml:base', ''); - - //$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; - // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas - // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar - $abs_img_path_without_subdir = $doc_info[0]; - $relp = api_get_path(REL_PATH); // The url-append config param. - $pos = strpos($abs_img_path_without_subdir, $relp); - if ($pos === 0) { - $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp)); - } - //$file_path = realpath(api_get_path(SYS_PATH).$doc_info[0]); - $file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir); - $file_path = str_replace('\\', '/', $file_path); - $file_path = str_replace('//', '/', $file_path); - //error_log(__LINE__.'Abs path: '.$file_path, 0); - // Prepare the current directory path (until just under 'document') with a trailing slash. - $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/'; - // Check if the current document is in that path. - if (strstr($file_path,$cur_path) !== false) { - // The document is in that path, now get the relative path - // to the containing document. - $orig_file_path = dirname($cur_path.$my_file_path).'/'; - $relative_path = ''; - if(strstr($file_path,$cur_path) !== false) { - $relative_path = substr($file_path,strlen($orig_file_path)); - $file_path = substr($file_path,strlen($cur_path)); - } else { - // This case is still a problem as it's difficult to calculate a relative path easily - // might still generate wrong links. - //$file_path = substr($file_path,strlen($cur_path)); - // Calculate the directory path to the current file (without trailing slash). - $my_relative_path = dirname($file_path); - $my_relative_file = basename($file_path); - // Calculate the directory path to the containing file (without trailing slash). - $my_orig_file_path = substr($orig_file_path,0,-1); - $dotdot = ''; - $subdir = ''; - while(strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) { - $my_relative_path2 = dirname($my_relative_path); - $my_orig_file_path = dirname($my_orig_file_path); - $subdir = substr($my_relative_path,strlen($my_relative_path2) + 1).'/'.$subdir; - $dotdot += '../'; - $my_relative_path = $my_relative_path2; - } - $relative_path = $dotdot.$subdir.$my_relative_file; - } - // Put the current document in the zip (this array is the array - // that will manage documents already in the course folder - relative). - $zip_files[] = $file_path; - // Update the links to the current document in the containing document (make them relative). - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path); - $my_dep_file->setAttribute('href', $file_path); - $my_dep->setAttribute('xml:base', ''); - } - elseif(strstr($file_path,$main_path) !== false) { - // The calculated real path is really inside Chamilo's root path. - // Reduce file path to what's under the DocumentRoot. - $file_path = substr($file_path,strlen($root_path)); - //echo $file_path;echo '<br /><br />'; - //error_log('Reduced path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - elseif (empty($file_path)) { - /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); - if(strpos($document_root,-1) == '/') { - $document_root = substr(0, -1, $document_root); - }*/ - $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; - $file_path = str_replace('//', '/', $file_path); - if (file_exists($file_path)) { - $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. - $zip_files[] = $my_sub_dir.'/'.$file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href','document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - } - break; - case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path - if (substr($doc_info[0], 0, 2) == '..') { - // Relative path going up. - $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; - $file_path = realpath($current_dir.$doc_info[0]); - //error_log($file_path.' <-> '.$main_path,0); - if (strstr($file_path,$main_path) !== false) { - // The calculated real path is really inside Chamilo's root path. - // Reduce file path to what's under the DocumentRoot. - $file_path = substr($file_path, strlen($root_path)); - //error_log('Reduced path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - } else { - $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; - $my_dep_file->setAttribute('href', $doc_info[0]); - $my_dep->setAttribute('xml:base', $my_xml_sub_dir); - } - break; - default: - $my_dep_file->setAttribute('href', $doc_info[0]); - $my_dep->setAttribute('xml:base', ''); - break; - } - } - $my_dep->appendChild($my_dep_file); - $resources->appendChild($my_dep); - $dependency = $xmldoc->createElement('dependency'); - $dependency->setAttribute('identifierref', $res_id); - $my_resource->appendChild($dependency); - $i++; - } - //$my_dependency = $xmldoc->createElement('dependency'); - //$my_dependency->setAttribute('identifierref', ''); - $resources->appendChild($my_resource); - $zip_files[] = $my_file_path; - - //error_log('File '.$my_file_path. ' added to $zip_files', 0); - } else { - // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it. - if ($item->type == TOOL_LINK) { - $my_item = $xmldoc->createElement('item'); - $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); - $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); - $my_item->setAttribute('isvisible', 'true'); - // Give a child element <title> to the <item> element. - $my_title = $xmldoc->createElement('title',htmlspecialchars($item->get_title(),ENT_QUOTES)); - $my_item->appendChild($my_title); - // Give a child element <adlcp:prerequisites> to the <item> element. - $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); - $my_prereqs->setAttribute('type', 'aicc_script'); - $my_item->appendChild($my_prereqs); - // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:maxtimeallowed', ''); - // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:timelimitaction', ''); - // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. - //$xmldoc->createElement('adlcp:datafromlms', ''); - // Give a child element <adlcp:masteryscore> to the <item> element. - $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); - $my_item->appendChild($my_masteryscore); - - // Attach this item to the organization element or its parent if there is one. - if (!empty($item->parent) && $item->parent != 0) { - $children = $organization->childNodes; - for ($i = 0; $i < $children->length; $i++) { - $item_temp = $children->item($i); - if ($item_temp -> nodeName == 'item') { - if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { - $item_temp -> appendChild($my_item); - } - - } - } - } else { - $organization->appendChild($my_item); - } - - $my_file_path = 'link_'.$item->get_id().'.html'; - $sql = 'SELECT url, title FROM '.Database :: get_course_table(TABLE_LINK).' WHERE id='.$item->path; - $rs = Database::query($sql); - if ($link = Database :: fetch_array($rs)) { - $url = $link['url']; - $title = stripslashes($link['title']); - $links_to_create[$my_file_path] = array('title' => $title, 'url' => $url); - $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); - $my_sub_dir = dirname($my_file_path); - $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); - // Give a <resource> child to the <resources> element. - $my_resource = $xmldoc->createElement('resource'); - $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); - $my_resource->setAttribute('type','webcontent'); - $my_resource->setAttribute('href',$my_xml_file_path); - // adlcp:scormtype can be either 'sco' or 'asset'. - $my_resource->setAttribute('adlcp:scormtype','asset'); - // xml:base is the base directory to find the files declared in this resource. - $my_resource->setAttribute('xml:base',''); - // give a <file> child to the <resource> element. - $my_file = $xmldoc->createElement('file'); - $my_file->setAttribute('href',$my_xml_file_path); - $my_resource->appendChild($my_file); - $resources->appendChild($my_resource); - } - } - elseif ($item->type == TOOL_QUIZ) { - require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php'; - $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard. - $exe = new Exercise(); - $exe->read($exe_id); - $my_item = $xmldoc->createElement('item'); - $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); - $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); - $my_item->setAttribute('isvisible', 'true'); - // Give a child element <title> to the <item> element. - $my_title = $xmldoc->createElement('title', htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); - $my_item->appendChild($my_title); - $my_max_score = $xmldoc->createElement('max_score', $item->get_max()); - //$my_item->appendChild($my_max_score); - // Give a child element <adlcp:prerequisites> to the <item> element. - $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); - $my_prereqs->setAttribute('type','aicc_script'); - $my_item->appendChild($my_prereqs); - // Give a child element <adlcp:masteryscore> to the <item> element. - $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); - $my_item->appendChild($my_masteryscore); - - // Attach this item to the organization element or hits parent if there is one. - if (!empty($item->parent) && $item->parent != 0) { - $children = $organization->childNodes; - for ($i = 0; $i < $children->length; $i++) { - $item_temp = $children->item($i); - if ($item_temp -> nodeName == 'item') { - if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { - $item_temp -> appendChild($my_item); - } - - } - } - } else { - $organization->appendChild($my_item); - } - - // Include export scripts. - require_once api_get_path(SYS_CODE_PATH).'exercice/export/scorm/scorm_export.php'; - - // Get the path of the file(s) from the course directory root - //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); - $my_file_path = 'quiz_'.$item->get_id().'.html'; - // Write the contents of the exported exercise into a (big) html file - // to later pack it into the exported SCORM. The file will be removed afterwards. - $contents = export_exercise($exe_id,true); - $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path; - $res = file_put_contents($tmp_file_path, $contents); - if ($res === false) { error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0); } - $files_cleanup[] = $tmp_file_path; - //error_log($tmp_path); die(); - $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); - $my_sub_dir = dirname($my_file_path); - $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); - // Give a <resource> child to the <resources> element. - $my_resource = $xmldoc->createElement('resource'); - $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); - $my_resource->setAttribute('type','webcontent'); - $my_resource->setAttribute('href',$my_xml_file_path); - // adlcp:scormtype can be either 'sco' or 'asset'. - $my_resource->setAttribute('adlcp:scormtype','sco'); - // xml:base is the base directory to find the files declared in this resource. - $my_resource->setAttribute('xml:base',''); - // Give a <file> child to the <resource> element. - $my_file = $xmldoc->createElement('file'); - $my_file->setAttribute('href',$my_xml_file_path); - $my_resource->appendChild($my_file); - - // Get included docs. - $inc_docs = $item->get_resources_from_source(null,$tmp_file_path); - // Dependency to other files - not yet supported. - $i = 1; - foreach ($inc_docs as $doc_info) { - if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } - $my_dep = $xmldoc->createElement('resource'); - $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; - $my_dep->setAttribute('identifier', $res_id); - $my_dep->setAttribute('type', 'webcontent'); - $my_dep->setAttribute('adlcp:scormtype', 'asset'); - $my_dep_file = $xmldoc->createElement('file'); - // Check type of URL. - //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0); - if ($doc_info[1] == 'remote') { - // Remote file. Save url as is. - $my_dep_file->setAttribute('href',$doc_info[0]); - $my_dep->setAttribute('xml:base', ''); - } elseif ($doc_info[1] == 'local') { - switch ($doc_info[2]) { - case 'url': // Local URL - save path as url for now, don't zip file. - //save file but as local file (retrieve from URL) - $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH),'',$doc_info[0]); - $current_dir = dirname($abs_path); - $file_path = realpath($abs_path); - $my_dep_file->setAttribute('href','document/'.$file_path); - $my_dep->setAttribute('xml:base',''); - if (strstr($file_path, $main_path) !== false) { - // The calculated real path is really inside the chamilo root path. - // Reduce file path to what's under the DocumentRoot. - $file_path = substr($file_path, strlen($root_path)); - //echo $file_path;echo '<br /><br />'; - //error_log('Reduced path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - elseif (empty($file_path)) { - /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); - if (strpos($document_root,-1) == '/') { - $document_root = substr(0, -1, $document_root); - }*/ - $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; - $file_path = str_replace('//', '/', $file_path); - if (file_exists($file_path)) { - $file_path = substr($file_path, strlen($current_dir)); // We get the relative path. - $zip_files[] = $my_sub_dir.'/'.$file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0],'dest' => 'document/'.$file_path); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - } - break; - case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. - $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; - $file_path = realpath($doc_info[0]); - $my_dep_file->setAttribute('href',$file_path); - $my_dep->setAttribute('xml:base',''); - - if (strstr($file_path,$main_path) !== false) { - // The calculated real path is really inside the chamilo root path. - // Reduce file path to what's under the DocumentRoot. - $file_path = substr($file_path,strlen($root_path)); - //echo $file_path;echo '<br /><br />'; - //error_log('Reduced path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - $link_updates[$my_file_path][] = array('orig'=>$doc_info[0],'dest'=>$file_path); - $my_dep_file->setAttribute('href','document/'.$file_path); - $my_dep->setAttribute('xml:base',''); - } - elseif (empty($file_path)) { - /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); - if (strpos($document_root,-1) == '/') { - $document_root = substr(0, -1, $document_root); - }*/ - $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; - $file_path = str_replace('//', '/', $file_path); - if (file_exists($file_path)) { - $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. - $zip_files[] = $my_sub_dir.'/'.$file_path; - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - } - break; - case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path - if (substr($doc_info[0], 0, 2) == '..') { - // Relative path going up. - $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; - $file_path = realpath($current_dir.$doc_info[0]); - //error_log($file_path.' <-> '.$main_path, 0); - if (strstr($file_path, $main_path) !== false) { - // The calculated real path is really inside Chamilo's root path. - // Reduce file path to what's under the DocumentRoot. - - $file_path = substr($file_path, strlen($root_path)); - $file_path_dest = $file_path; - - // File path is courses/CHAMILO/document/.... - $info_file_path = explode('/', $file_path); - if ($info_file_path[0] == 'courses') { // Add character "/" in file path. - $file_path_dest = 'document/'.$file_path; - } - - //error_log('Reduced path: '.$file_path, 0); - $zip_files_abs[] = $file_path; - - $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest); - $my_dep_file->setAttribute('href', 'document/'.$file_path); - $my_dep->setAttribute('xml:base', ''); - } - } else { - $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; - $my_dep_file->setAttribute('href', $doc_info[0]); - $my_dep->setAttribute('xml:base', $my_xml_sub_dir); - } - - break; - default: - $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/ - $my_dep->setAttribute('xml:base', ''); - break; - } - } - $my_dep->appendChild($my_dep_file); - $resources->appendChild($my_dep); - $dependency = $xmldoc->createElement('dependency'); - $dependency->setAttribute('identifierref', $res_id); - $my_resource->appendChild($dependency); - $i++; - } - $resources->appendChild($my_resource); - $zip_files[] = $my_file_path; - - } else { - - // Get the path of the file(s) from the course directory root - $my_file_path = 'non_exportable.html'; - $my_xml_file_path = api_htmlentities($my_file_path, ENT_COMPAT, $this->encoding); - $my_sub_dir = dirname($my_file_path); - $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_COMPAT, $this->encoding); - // Give a <resource> child to the <resources> element. - $my_resource = $xmldoc->createElement('resource'); - $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); - $my_resource->setAttribute('type','webcontent'); - $my_resource->setAttribute('href','document/'.$my_xml_file_path); - // adlcp:scormtype can be either 'sco' or 'asset'. - $my_resource->setAttribute('adlcp:scormtype','asset'); - // xml:base is the base directory to find the files declared in this resource. - $my_resource->setAttribute('xml:base',''); - // Give a <file> child to the <resource> element. - $my_file = $xmldoc->createElement('file'); - $my_file->setAttribute('href','document/'.$my_xml_file_path); - $my_resource->appendChild($my_file); - $resources->appendChild($my_resource); - - } - } - } - $organizations->appendChild($organization); - $root->appendChild($organizations); - $root->appendChild($resources); - $xmldoc->appendChild($root); - // TODO: Add a readme file here, with a short description and a link to the Reload player - // then add the file to the zip, then destroy the file (this is done automatically). - // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138 - - //error_log(print_r($zip_files,true), 0); - foreach ($zip_files as $file_path) { - if (empty($file_path)) { continue; } - //error_log(__LINE__.'getting document from '.$sys_course_path.$_course['path'].'/'.$file_path.' removing '.$sys_course_path.$_course['path'].'/',0); - $dest_file = $archive_path.$temp_dir_short.'/'.$file_path; - $this->create_path($dest_file); - //error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path,0); - //echo $main_path.$file_path.'<br />'; - @copy($sys_course_path.$_course['path'].'/'.$file_path, $dest_file); - // Check if the file needs a link update. - if (in_array($file_path, array_keys($link_updates))) { - $string = file_get_contents($dest_file); - unlink($dest_file); - foreach ($link_updates[$file_path] as $old_new) { - //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0); - // This is an ugly hack that allows .flv files to be found by the flv player that - // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs - // to find the flv to play in document/main/, so we replace main/ in the flv path by - // ../../.. to return from inc/lib/flv_player to the document/main path. - if(substr($old_new['dest'],-3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { - $old_new['dest'] = str_replace('main/','../../../',$old_new['dest']); - } - elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') { - $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']); - } - $string = str_replace($old_new['orig'], $old_new['dest'], $string); - } - file_put_contents($dest_file, $string); - } - } - foreach($zip_files_abs as $file_path) { - if (empty($file_path)) { continue; } - //error_log(__LINE__.'checking existence of '.$main_path.$file_path.'', 0); - if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) { continue; } - //error_log(__LINE__.'getting document from '.$main_path.$file_path.' removing '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/', 0); - $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path; - $this->create_path($dest_file); - //error_log('Created path '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/document/'.$file_path, 0); - //error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path, 0); - //echo $main_path.$file_path.' - '.$dest_file.'<br />'; - - copy($main_path.$file_path, $dest_file); - // Check if the file needs a link update. - if (in_array($file_path,array_keys($link_updates))) { - $string = file_get_contents($dest_file); - unlink($dest_file); - foreach ($link_updates[$file_path] as $old_new) { - //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0); - // This is an ugly hack that allows .flv files to be found by the flv player that - // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs - // to find the flv to play in document/main/, so we replace main/ in the flv path by - // ../../.. to return from inc/lib/flv_player to the document/main path. - if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { - $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); - } - $string = str_replace($old_new['orig'], $old_new['dest'], $string); - } - file_put_contents($dest_file, $string); - } - } - if (is_array($links_to_create)) { - foreach ($links_to_create as $file => $link) { - $file_content = '<html><body><div style="text-align:center"><a href="'.$link['url'].'">'.$link['title'].'</a></div></body></html>'; - file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content); - } - } - // Add non exportable message explanation. - $lang_not_exportable = get_lang('ThisItemIsNotExportable'); - $file_content = + ' </td>' . "\n" . + ' </tr>' . "\n" . + '</table>' . "\n"; + + } else { + $navbar = '<table cellpadding="0" cellspacing="0" align="left">' . "\n" . + ' <tr> ' . "\n" . + ' <td>' . "\n" . + ' <div class="buttons">' . "\n" . + ' <a href="lp_controller.php?action=stats" onclick="window.parent.API.save_asset();return true;" target="content_name" title="stats" id="stats_link"><img border="0" src="../img/lp_stats.gif" title="' . get_lang('Reporting') . '"></a>' . "\n" . + ' <a href="" onclick="switch_item(' . $mycurrentitemid . ',\'previous\');return false;" title="previous"><img border="0" src="../img/lp_leftarrow.gif" title="' . get_lang('ScormPrevious') . '"></a>' . "\n" . + ' <a href="" onclick="switch_item(' . $mycurrentitemid . ',\'next\');return false;" title="next" ><img border="0" src="../img/lp_rightarrow.gif" title="' . get_lang('ScormNext') . '"></a>' . "\n" . + // ' <a href="lp_controller.php?action=mode&mode=fullscreen" target="_top" title="fullscreen"><img border="0" src="../img/view_fullscreen.gif" width="18" height="18" title="'.get_lang('ScormFullScreen').'"></a>'."\n" . + ' </div>' . "\n" . + ' </td>' . "\n" . + ' </tr>' . "\n" . + '</table>' . "\n"; + } + return $navbar; + } + + /** + * Gets the next resource in queue (url). + * @return string URL to load into the viewer + */ + public function get_next_index() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_next_index()', 0); + } + // TODO + $index = $this->index; + $index++; + if ($this->debug > 2) { + error_log('New LP - Now looking at ordered_items[' . ($index) . '] - type is ' . $this->items[$this->ordered_items[$index]]->type, 0); + } + while (!empty ($this->ordered_items[$index]) AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter') AND $index < $this->max_ordered_items) { + $index++; + if ($index == $this->max_ordered_items){ + if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter') { + return $this->index; + } else { + return $index; + } + } + } + if (empty ($this->ordered_items[$index])) { + return $this->index; + } + if ($this->debug > 2) { + error_log('New LP - index is now ' . $index, 0); + } + return $index; + } + + /** + * Gets item_id for the next element + * @return integer Next item (DB) ID + */ + public function get_next_item_id() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_next_item_id()', 0); + } + $new_index = $this->get_next_index(); + if (!empty ($new_index)) { + if (isset ($this->ordered_items[$new_index])) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_next_index() - Returning ' . $this->ordered_items[$new_index], 0); + } + return $this->ordered_items[$new_index]; + } + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_next_index() - Problem - Returning 0', 0); + } + return 0; + } + + /** + * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...) + * + * Generally, the package provided is in the form of a zip file, so the function + * has been written to test a zip file. If not a zip, the function will return the + * default return value: '' + * @param string the path to the file + * @param string the original name of the file + * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized + */ + public function get_package_type($file_path, $file_name) { + + // Get name of the zip file without the extension. + $file_info = pathinfo($file_name); + $filename = $file_info['basename']; // Name including extension. + $extension = $file_info['extension']; // Extension only. + + if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), array ( + 'dll', + 'exe' + ))) { + return 'oogie'; + } + if (!empty($_POST['woogie']) && !in_array(strtolower($extension), array ( + 'dll', + 'exe' + ))) { + return 'woogie'; + } + + $file_base_name = str_replace('.' . $extension, '', $filename); // Filename without its extension. + + $zipFile = new pclZip($file_path); + // Check the zip content (real size and file extension). + $zipContentArray = $zipFile->listContent(); + $package_type = ''; + $at_root = false; + $manifest = ''; + + // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?). + if (is_array($zipContentArray) && count($zipContentArray) > 0) { + foreach ($zipContentArray as $thisContent) { + if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) { + // New behaviour: Don't do anything. These files will be removed in scorm::import_package. + } + elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) { + $manifest = $thisContent['filename']; // Just the relative directory inside scorm/ + $package_type = 'scorm'; + break; // Exit the foreach loop. + } + elseif (preg_match('/aicc\//i', $thisContent['filename'])) { + // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)). + $package_type = 'aicc'; + //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC. + } else { + $package_type = ''; + } + } + } + return $package_type; + } + + /** + * Gets the previous resource in queue (url). Also initialises time values for this viewing + * @return string URL to load into the viewer + */ + public function get_previous_index() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_previous_index()', 0); + } + $index = $this->index; + if (isset ($this->ordered_items[$index -1])) { + $index--; + while (isset ($this->ordered_items[$index]) AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter')) { + $index--; + if ($index < 0) { + return $this->index; + } + } + } else { + if ($this->debug > 2) { + error_log('New LP - get_previous_index() - there was no previous index available, reusing ' . $index, 0); + } + // There is no previous item. + } + return $index; + } + + /** + * Gets item_id for the next element + * @return integer Previous item (DB) ID + */ + public function get_previous_item_id() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_previous_item_id()', 0); + } + $new_index = $this->get_previous_index(); + return $this->ordered_items[$new_index]; + } + + /** + * Gets the progress value from the progress_db attribute + * @return integer Current progress value + */ + public function get_progress() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_progress()', 0); + } + if (!empty ($this->progress_db)) { + return $this->progress_db; + } + return 0; + } + + /** + * Gets the progress value from the progress field in the database (allows use as abstract method) + * @param integer Learnpath ID + * @param integer User ID + * @param string Mode of display ('%','abs' or 'both') + * @param string Course database name (optional, defaults to '') + * @param boolean Whether to return null if no record was found (true), or 0 (false) (optional, defaults to false) + * @return integer Current progress value as found in the database + */ + public function get_db_progress($lp_id, $user_id, $mode = '%', $course_db = '', $sincere = false) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_db_progress()', 0); } + $session_id = api_get_session_id(); + $session_condition = api_get_session_condition($session_id); + $table = Database :: get_course_table(TABLE_LP_VIEW, $course_db); + $sql = "SELECT * FROM $table WHERE lp_id = $lp_id AND user_id = $user_id $session_condition"; + $res = Database::query($sql); + $view_id = 0; + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $progress = $row['progress']; + $view_id = $row['id']; + } else { + if ($sincere) { + return null; + } + } + if (empty ($progress)) { + $progress = '0'; + } + if ($mode == '%') { + return $progress . '%'; + } else { + // Get the number of items completed and the number of items total. + $tbl = Database :: get_course_table(TABLE_LP_ITEM, $course_db); + $sql = "SELECT count(*) FROM $tbl WHERE lp_id = " . $lp_id . " + AND item_type NOT IN('dokeos_chapter','chapter','dir')"; + $res = Database::query($sql); + $row = Database :: fetch_array($res); + $total = $row[0]; + $tbl_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW, $course_db); + $tbl_item = Database :: get_course_table(TABLE_LP_ITEM, $course_db); + + //$sql = "SELECT count(distinct(lp_item_id)) FROM $tbl WHERE lp_view_id = ".$view_id." AND status IN ('passed','completed','succeeded')"; + // Trying as also counting browsed and failed items. + $sql = "SELECT count(distinct(lp_item_id)) + FROM $tbl_item_view as item_view + INNER JOIN $tbl_item as item + ON item.id = item_view.lp_item_id + AND item_type NOT IN('dokeos_chapter','chapter','dir') + WHERE lp_view_id = " . $view_id . " + AND status IN ('passed','completed','succeeded','browsed','failed')"; + $res = Database::query($sql); + $row = Database :: fetch_array($res); + $completed = $row[0]; + if ($mode == 'abs') { + return $completed . '/' . $total; + } + elseif ($mode == 'both') { + if ($progress < ($completed / ($total ? $total : 1))) { + $progress = number_format(($completed / ($total ? $total : 1)) * 100, 0); + } + return $progress . '% (' . $completed . '/' . $total . ')'; + } + } + return $progress; + } + + /** + * Returns the HTML necessary to print a mediaplayer block inside a page + * @return string The mediaplayer HTML + */ + public function get_mediaplayer($autostart='true') { + + global $_course; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_lp_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW); + + // Getting all the information about the item. + $sql = "SELECT * FROM " . $tbl_lp_item . " as lp inner join " . $tbl_lp_item_view . " as lp_view on lp.id = lp_view.lp_item_id " . + "WHERE lp.id = '" . $_SESSION['oLP']->current . "'"; + $result = Database::query($sql); + $row = Database::fetch_assoc($result); + $output = ''; + + if (!empty ($row['audio'])) { + + $list = $_SESSION['oLP']->get_toc(); + $type_quiz = false; + + foreach($list as $toc) { + if ($toc['id'] == $_SESSION['oLP']->current && ($toc['type']=='quiz') ) { + $type_quiz = true; + } + } + + if ($type_quiz) { + if ($_SESSION['oLP']->prevent_reinit == 1) { + $row['status'] === 'completed' ? $autostart_audio = 'false' : $autostart_audio = 'true'; + } else { + $autostart_audio = $autostart; + } + } else { + $autostart_audio = 'true'; + } + + // The mp3 player. + $output = '<div id="container">'; + $output .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>'; + $output .= '<script type="text/javascript"> + var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF"); + s1.addParam("allowscriptaccess","always"); + s1.addParam("flashvars","file=' . api_get_path(WEB_COURSE_PATH) . $_course['path'] . '/document/audio/' . $row['audio'] . '&autostart=' . $autostart_audio.'"); + s1.write("container"); + </script></div>'; + } + return $output; + } + + /** + * This function check if the learnpath is visible for student after the progress of its prerequisite is completed + * @param int Learnpath id + * @param int Student id + * @return bool True if + */ + public function is_lp_visible_for_student($lp_id, $student_id) { + + $tbl_learnpath = Database :: get_course_table(TABLE_LP_MAIN); + + // Get current prerequisite. + $sql = "SELECT prerequisite FROM $tbl_learnpath WHERE id = $lp_id"; + $rs = Database::query($sql); + $row = Database :: fetch_array($rs); + $prerequisite = $row['prerequisite']; + $is_visible = true; + $progress = 0; + + if (!empty($prerequisite)) { + $progress = self::get_db_progress($prerequisite,$student_id,'%'); + $progress = intval($progress); + if ($progress < 100) { + $is_visible = false; + } + } + + return $is_visible; + } + + /** + * Gets a progress bar for the learnpath by counting the number of items in it and the number of items + * completed so far. + * @param string Mode in which we want the values + * @param integer Progress value to display (optional but mandatory if used in abstract context) + * @param string Text to display near the progress value (optional but mandatory in abstract context) + * @param boolean true if it comes from a Diplay LP view + * @return string HTML string containing the progress bar + */ + public function get_progress_bar($mode = '', $percentage = -1, $text_add = '', $from_lp = false) { + //if ($this->debug > 0) {error_log('New LP - In learnpath::get_progress_bar('.$mode.','.$percentage.','.$text_add.','.$from_lp.')', 0); } + global $lp_theme_css; + + // Setting up the CSS path of the current style if exists. + if (!empty ($lp_theme_css)) { + $css_path = api_get_path(WEB_CODE_PATH) . 'css/' . $lp_theme_css . '/images/'; + } else { + $css_path = '../img/'; + } + + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_progress_bar()', 0); } + if (isset($this) && is_object($this) && ($percentage == '-1' OR $text_add == '')) { + list($percentage, $text_add) = $this->get_progress_bar_text($mode); + } + $text = $percentage . $text_add; + + // Default progress bar config + // times that will be greater or shorter + $factor = 1.2; + if ($from_lp) + $progress_height = '26'; + else + $progress_height = '16'; + $size = str_replace('%', '', $percentage); + + $output = + '<table border="0" cellpadding="0" cellspacing="0"><tr><td>' . + '<img id="progress_img_limit_left" src="' . $css_path . 'bar_1.gif" width="1" height="' . $progress_height . '">' . + '<img id="progress_img_full" src="' . $css_path . 'bar_1u.gif" width="' . $size * $factor . 'px" height="' . $progress_height . '" id="full_portion">' . + '<img id="progress_img_limit_middle" src="' . $css_path . 'bar_1m.gif" width="1" height="' . $progress_height . '">'; + + if ($percentage <= 98) { + $output .= '<img id="progress_img_empty" src="' . $css_path . 'bar_1r.gif" width="' . (100 - $size) * $factor . 'px" height="' . $progress_height . '" id="empty_portion">'; + } else { + $output .= '<img id="progress_img_empty" src="' . $css_path . 'bar_1r.gif" width="0" height="' . $progress_height . '" id="empty_portion">'; + } + + $output .= '<img id="progress_bar_img_limit_right" src="' . $css_path . 'bar_1.gif" width="1" height="' . $progress_height . '"></td></tr></table>' . + '<div class="progresstext" id="progress_text">' . $text . '</div>'; + + return $output; + } + + /** + * Gets the progress bar info to display inside the progress bar. Also used by scorm_api.php + * @param string Mode of display (can be '%' or 'abs').abs means we display a number of completed elements per total elements + * @param integer Additional steps to fake as completed + * @return list Percentage or number and symbol (% or /xx) + */ + public function get_progress_bar_text($mode = '', $add = 0) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_progress_bar_text()', 0); + } + if (empty ($mode)) { + $mode = $this->progress_bar_mode; + } + $total_items = $this->get_total_items_count_without_chapters(); + if ($this->debug > 2) { + error_log('New LP - Total items available in this learnpath: ' . $total_items, 0); + } + $i = $this->get_complete_items_count(); + if ($this->debug > 2) { + error_log('New LP - Items completed so far: ' . $i, 0); + } + if ($add != 0) { + $i += $add; + if ($this->debug > 2) { + error_log('New LP - Items completed so far (+modifier): ' . $i, 0); + } + } + $text = ''; + if ($i > $total_items) { + $i = $total_items; + } + if ($mode == '%') { + if ($total_items > 0) { + $percentage = ((float) $i / (float) $total_items) * 100; + } else { + $percentage = 0; + } + $percentage = number_format($percentage, 0); + $text = '%'; + } + elseif ($mode == 'abs') { + $percentage = $i; + $text = '/' . $total_items; + } + return array ( + $percentage, + $text + ); + } + + /** + * Gets the progress bar mode + * @return string The progress bar mode attribute + */ + public function get_progress_bar_mode() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_progress_bar_mode()', 0); + } + if (!empty ($this->progress_bar_mode)) { + return $this->progress_bar_mode; + } else { + return '%'; + } + } + + /** + * Gets the learnpath proximity (remote or local) + * @return string Learnpath proximity + */ + public function get_proximity() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_proximity()', 0); + } + if (!empty ($this->proximity)) { + return $this->proximity; + } else { + return ''; + } + } + + /** + * Gets the learnpath theme (remote or local) + * @return string Learnpath theme + */ + public function get_theme() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_theme()', 0); + } + if (!empty ($this->theme)) { + return $this->theme; + } else { + return ''; + } + } + + /** + * Gets the learnpath session id + * @return string Learnpath theme + */ + public function get_lp_session_id() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_lp_session_id()', 0); + } + if (!empty ($this->lp_session_id)) { + return $this->lp_session_id; + } else { + return 0; + } + } + + /** + * Gets the learnpath image + * @return string Web URL of the LP image + */ + public function get_preview_image() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_preview_image()', 0); + } + if (!empty ($this->preview_image)) { + return $this->preview_image; + } else { + return ''; + } + } + + /** + * Gets the learnpath author + * @return string LP's author + */ + public function get_author() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_author()', 0); + } + if (!empty ($this->author)) { + return $this->author; + } else { + return ''; + } + } + + /** + * Generate a new prerequisites string for a given item. If this item was a sco and + * its prerequisites were strings (instead of IDs), then transform those strings into + * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table. + * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the + * same rule as the scorm_export() method + * @param integer Item ID + * @return string Prerequisites string ready for the export as SCORM + */ + public function get_scorm_prereq_string($item_id) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_scorm_prereq_string()', 0); + } + if (!is_object($this->items[$item_id])) { + return false; + } + $oItem = $this->items[$item_id]; + $prereq = $oItem->get_prereq_string(); + if (empty ($prereq)) { + return ''; + } + if (preg_match('/^\d+$/', $prereq) && is_object($this->items[$prereq])) { // If the prerequisite is a simple integer ID and this ID exists as an item ID, + // then simply return it (with the ITEM_ prefix). + return 'ITEM_' . $prereq; + } else { + if (isset ($this->refs_list[$prereq])) { + // It's a simple string item from which the ID can be found in the refs list, + // so we can transform it directly to an ID for export. + return 'ITEM_' . $this->refs_list[$prereq]; + } else { + // The last case, if it's a complex form, then find all the IDs (SCORM strings) + // and replace them, one by one, by the internal IDs (chamilo db) + // TODO: Modify the '*' replacement to replace the multiplier in front of it + // by a space as well. + $find = array ( + '&', + '|', + '~', + '=', + '<>', + '{', + '}', + '*', + '(', + ')' + ); + $replace = array ( + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ', + ' ' + ); + $prereq_mod = str_replace($find, $replace, $prereq); + $ids = split(' ', $prereq_mod); + foreach ($ids as $id) { + $id = trim($id); + if (isset ($this->refs_list[$id])) { + $prereq = preg_replace('/[^a-zA-Z_0-9](' . $id . ')[^a-zA-Z_0-9]/', 'ITEM_' . $this->refs_list[$id], $prereq); + } + } + error_log('New LP - In learnpath::get_scorm_prereq_string(): returning modified string: ' . $prereq, 0); + return $prereq; + } + } + } + + /** + * Returns the XML DOM document's node + * @param resource Reference to a list of objects to search for the given ITEM_* + * @param string The identifier to look for + * @return mixed The reference to the element found with that identifier. False if not found + */ + public function get_scorm_xml_node(& $children, $id) { + for ($i = 0; $i < $children->length; $i++) { + $item_temp = $children->item($i); + if ($item_temp->nodeName == 'item') { + if ($item_temp->getAttribute('identifier') == $id) { + return $item_temp; + } + } + $subchildren = $item_temp->childNodes; + if ($subchildren->length > 0) { + $val = $this->get_scorm_xml_node($subchildren, $id); + if (is_object($val)) { + return $val; + } + } + } + return false; + } + + /** + * Returns a usable array of stats related to the current learnpath and user + * @return array Well-formatted array containing status for the current learnpath + */ + public function get_stats() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_stats()', 0); + } + // TODO + } + + /** + * Static method. Can be re-implemented by children. Gives an array of statistics for + * the given course (for all learnpaths and all users) + * @param string Course code + * @return array Well-formatted array containing status for the course's learnpaths + */ + public function get_stats_course($course) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_course()', 0); } + // TODO + } + + /** + * Static method. Can be re-implemented by children. Gives an array of statistics for + * the given course and learnpath (for all users) + * @param string Course code + * @param integer Learnpath ID + * @return array Well-formatted array containing status for the specified learnpath + */ + public function get_stats_lp($course, $lp) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_lp()', 0); } + // TODO + } + + /** + * Static method. Can be re-implemented by children. Gives an array of statistics for + * the given course, learnpath and user. + * @param string Course code + * @param integer Learnpath ID + * @param integer User ID + * @return array Well-formatted array containing status for the specified learnpath and user + */ + public function get_stats_lp_user($course, $lp, $user) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_lp_user()', 0); } + // TODO + } + + /** + * Static method. Can be re-implemented by children. Gives an array of statistics for + * the given course and learnpath (for all users) + * @param string Course code + * @param integer User ID + * @return array Well-formatted array containing status for the user's learnpaths + */ + public function get_stats_user($course, $user) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_user()', 0); } + // TODO + } + + /** + * Gets the status list for all LP's items + * @return array Array of [index] => [item ID => current status] + */ + public function get_items_status_list() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_items_status_list()', 0); + } + $list = array (); + foreach ($this->ordered_items as $item_id) { + $list[] = array ( + $item_id => $this->items[$item_id]->get_status() + ); + } + return $list; + } + + /** + * Return the number of interactions for the given learnpath Item View ID. + * This method can be used as static. + * @param integer Item View ID + * @return integer Number of interactions + */ + public function get_interactions_count_from_db($lp_iv_id = 0) { + if (empty ($lp_iv_id)) { + return -1; + } + $table = Database :: get_course_table(TABLE_LP_IV_INTERACTION); + $sql = "SELECT count(*) FROM $table WHERE lp_iv_id = $lp_iv_id"; + $res = Database::query($sql); + $row = Database :: fetch_array($res); + $num = $row[0]; + return $num; + } + + /** + * Return the interactions as an array for the given lp_iv_id. + * This method can be used as static. + * @param integer Learnpath Item View ID + * @return array + * @todo Transcode labels instead of switching to HTML (which requires to know the encoding of the LP) + */ + public function get_iv_interactions_array($lp_iv_id = 0) { + + $charset = api_get_system_encoding(); + + $list = array (); + $table = Database :: get_course_table(TABLE_LP_IV_INTERACTION); + $sql = "SELECT * FROM $table WHERE lp_iv_id = $lp_iv_id ORDER BY order_id ASC"; + $res = Database::query($sql); + $num = Database :: num_rows($res); + if ($num > 0) { + $list[] = array ( + 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES, $charset), + 'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES, $charset), + 'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES, $charset), + 'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES, $charset), + 'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES, $charset), + 'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES, $charset), + 'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES, $charset), + 'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES, $charset) + ); + while ($row = Database :: fetch_array($res)) { + $list[] = array ( + 'order_id' => ($row['order_id'] + 1), + 'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that + 'type' => $row['interaction_type'], + 'time' => $row['completion_time'], + //'correct_responses' => $row['correct_responses'], + 'correct_responses' => '', // Hide correct responses from students. + 'student_response' => $row['student_response'], + 'result' => $row['result'], + 'latency' => $row['latency'] + ); + } + } + return $list; + } + + /** + * Return the number of objectives for the given learnpath Item View ID. + * This method can be used as static. + * @param integer Item View ID + * @return integer Number of objectives + */ + public function get_objectives_count_from_db($lp_iv_id = 0) { + if (empty ($lp_iv_id)) { + return -1; + } + $table = Database :: get_course_table(TABLE_LP_IV_OBJECTIVE); + $sql = "SELECT count(*) FROM $table WHERE lp_iv_id = $lp_iv_id"; + $res = Database::query($sql); + $row = Database :: fetch_array($res); + $num = $row[0]; + return $num; + } + + /** + * Return the objectives as an array for the given lp_iv_id. + * This method can be used as static. + * @param integer Learnpath Item View ID + * @return array + * @todo Translate labels + */ + public function get_iv_objectives_array($lp_iv_id = 0) { + + global $charset; + + $list = array(); + $table = Database :: get_course_table(TABLE_LP_IV_OBJECTIVE); + $sql = "SELECT * FROM $table WHERE lp_iv_id = $lp_iv_id ORDER BY order_id ASC"; + $res = Database::query($sql); + $num = Database :: num_rows($res); + if ($num > 0) { + $list[] = array ( + 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES, $charset), + 'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES, $charset), + 'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES, $charset), + 'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES, $charset), + 'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES, $charset), + 'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES, $charset) + ); + while ($row = Database :: fetch_array($res)) { + $list[] = array ( + 'order_id' => ($row['order_id'] + 1), + 'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F or stuff like that. + 'score_raw' => $row['score_raw'], + 'score_max' => $row['score_max'], + 'score_min' => $row['score_min'], + 'status' => $row['status'] + ); + } + } + return $list; + } + + /** + * Generate and return the table of contents for this learnpath. The (flat) table returned can be + * used by get_html_toc() to be ready to display + * @return array TOC as a table with 4 elements per row: title, link, status and level + */ + public function get_toc() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_toc()', 0); + } + $toc = array (); + //echo "<pre>".print_r($this->items,true)."</pre>"; + foreach ($this->ordered_items as $item_id) { + if ($this->debug > 2) { + error_log('New LP - learnpath::get_toc(): getting info for item ' . $item_id, 0); + } + // TODO: Change this link generation and use new function instead. + $toc[] = array ( + 'id' => $item_id, + 'title' => $this->items[$item_id]->get_title(), + //'link' => get_addedresource_link_in_learnpath('document', $item_id, 1), + 'status' => $this->items[$item_id]->get_status(), + 'level' => $this->items[$item_id]->get_level(), + 'type' => $this->items[$item_id]->get_type(), + 'description' => $this->items[$item_id]->get_description(), + 'path' => $this->items[$item_id]->get_path(), + + ); + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_toc() - TOC array: ' . print_r($toc, true), 0); + } + return $toc; + } + + /** + * Generate and return the table of contents for this learnpath. The JS + * table returned is used inside of scorm_api.php + * @return string A JS array vairiable construction + */ + public function get_items_details_as_js($varname='olms.lms_item_types') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_items_details_as_js()', 0); + } + $toc = $varname.' = new Array();'; + //echo "<pre>".print_r($this->items,true)."</pre>"; + foreach ($this->ordered_items as $item_id) { + if ($this->debug > 2) { + error_log('New LP - learnpath::get_items_details_as_js(): getting info for item ' . $item_id, 0); + } + $toc.= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';"; + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_items_details_as_js() - TOC array: ' . print_r($toc, true), 0); + } + return $toc; + } + + /** + * Gets the learning path type + * @param boolean Return the name? If false, return the ID. Default is false. + * @return mixed Type ID or name, depending on the parameter + */ + public function get_type($get_name = false) { + $res = false; + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_type()', 0); + } + if (!empty ($this->type)) { + if ($get_name) { + // Get it from the lp_type table in main db. + } else { + $res = $this->type; + } + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_type() - Returning ' . ($res ? $res : 'false'), 0); + } + return $res; + } + + /** + * Gets the learning path type as static method + * @param boolean Return the name? If false, return the ID. Default is false. + * @return mixed Type ID or name, depending on the parameter + */ + public function get_type_static($lp_id = 0) { + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT lp_type FROM $tbl_lp WHERE id = '" . $lp_id . "'"; + $res = Database::query($sql); + if ($res === false) { + return null; + } + if (Database :: num_rows($res) <= 0) { + return null; + } + $row = Database :: fetch_array($res); + return $row['lp_type']; + } + + /** + * Gets a flat list of item IDs ordered for display (level by level ordered by order_display) + * This method can be used as abstract and is recursive + * @param integer Learnpath ID + * @param integer Parent ID of the items to look for + * @return mixed Ordered list of item IDs or false on error + */ + public function get_flat_ordered_items_list($lp, $parent = 0) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_flat_ordered_items_list('.$lp.','.$parent.')', 0); } + $list = array(); + if (empty ($lp)) { + return false; + } + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * FROM $tbl_lp_item WHERE lp_id = $lp AND parent_item_id = $parent ORDER BY display_order"; + $res = Database::query($sql); + while ($row = Database :: fetch_array($res)) { + $sublist = learnpath :: get_flat_ordered_items_list($lp, $row['id']); + $list[] = $row['id']; + foreach ($sublist as $item) { + $list[] = $item; + } + } + return $list; + } + + /** + * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display + * @return string HTML TOC ready to display + */ + public function get_html_toc() { + + $is_allowed_to_edit = api_is_allowed_to_edit(null, true); + + $charset = api_get_system_encoding(); + $display_action_links_with_icons = false; + + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_html_toc()', 0); + } + $list = $this->get_toc(); + //echo $this->current; + //$parent = $this->items[$this->current]->get_parent(); + //if (empty($parent)) { $parent = $this->ordered_items[$this->items[$this->current]->get_previous_index()]; } + $html = '<div id="scorm_title" class="scorm_title"><div class="scorm_title_text">' . Security::remove_XSS(api_convert_encoding($this->get_name(), $this->encoding, $charset)) . '</div></div>'; + // Build, display. + if ($is_allowed_to_edit) { + $gradebook = Security :: remove_XSS($_GET['gradebook']); + + //var_dump($this->get_lp_session_id()); + if ($this->get_lp_session_id() == api_get_session_id()) { + $html .= '<div id="actions_lp" class="actions_lp">'; + if ($display_action_links_with_icons) { + $html .= '<div style = "text-align:center;">'; + $html .= "<a href='lp_controller.php?" . api_get_cidreq() . "&action=build&lp_id=" . $this->lp_id . "' target='_parent'>" . Display :: return_icon('learnpath_build.gif', get_lang('Build')) . ' ' . get_lang('Build') . "</a>"; + $html .= "<a href='lp_controller.php?" . api_get_cidreq() . "&action=admin_view&lp_id=" . $this->lp_id . "' target='_parent'>" . Display :: return_icon('learnpath_organize.gif', get_lang('BasicOverview')) . ' ' . get_lang('BasicOverview') . "</a>"; + $html .= '<span>' . Display :: return_icon('learnpath_view_na.gif', get_lang('Display')) . ' <b>' . get_lang('Display') . '</b></span><br />'; + $html .= '<a href="lp_controller.php?' . api_get_cidreq() . '">'. get_lang('ReturnToLPList') . '</a>'; + $html .= '</div>'; + } else { + $html .= '<div style = "text-align:center;">'; + $html .= "<a href='lp_controller.php?" . api_get_cidreq() . "&gradebook=$gradebook&action=build&lp_id=" . $this->lp_id . "' target='_parent'>" . get_lang('Build') . "</a>"; + $html .= "<a href='lp_controller.php?" . api_get_cidreq() . "&action=admin_view&lp_id=" . $this->lp_id . "' target='_parent'>" . get_lang('BasicOverview') . "</a>"; + $html .= '<span><b>' . get_lang('Display') . '</b></span><br />'; + $html .= '<a href="lp_controller.php?' . api_get_cidreq() . '">'. get_lang('ReturnToLPList') . '</a>'; + $html .= '</div>'; + } + $html .= '</div>'; + } + + } + $html .= '<div id="inner_lp_toc" class="inner_lp_toc">' . "\n"; + require_once 'resourcelinker.inc.php'; + + // Temporary variables. + $mycurrentitemid = $this->get_current_item_id(); + $color_counter = 0; + $i = 0; + foreach ($list as $item) { + if ($this->debug > 2) { + error_log('New LP - learnpath::get_html_toc(): using item ' . $item['id'], 0); + } + // TODO: Complete this. + $icon_name = array ( + 'not attempted' => '../img/notattempted.gif', + 'incomplete' => '../img/incomplete.gif', + 'failed' => '../img/failed.gif', + 'completed' => '../img/completed.gif', + 'passed' => '../img/passed.gif', + 'succeeded' => '../img/succeeded.gif', + 'browsed' => '../img/completed.gif' + ); + + $style = 'scorm_item'; + $scorm_color_background = 'scorm_item'; + $style_item = 'scorm_item'; + $current = false; + + if ($item['id'] == $this->current) { + $style = 'scorm_item_highlight'; + $scorm_color_background = 'scorm_item_highlight'; + } else + if ($color_counter % 2 == 0) { + $scorm_color_background = 'scorm_item_1'; + } else { + $scorm_color_background = 'scorm_item_2'; + } + + if ($scorm_color_background != '') { + $html .= '<div id="toc_' . $item['id'] . '" class="' . $scorm_color_background . '">'; + } + + // The anchor will let us center the TOC on the currently viewed item &^D + if ($item['type'] != 'dokeos_module' && $item['type'] != 'dokeos_chapter') { + $html .= '<a name="atoc_' . $item['id'] . '" />'; + $html .= '<div class="' . $style_item . '" style="padding-left: ' . ($item['level'] * 1.5) . 'em; padding-right:' . ($item['level'] / 2) . 'em" title="' . $item['description'] . '" >'; + } else { + $html .= '<div class="' . $style_item . '" style="padding-left: ' . ($item['level'] * 2) . 'em; padding-right:' . ($item['level'] * 1.5) . 'em" title="' . $item['description'] . '" >'; + } + $title = $item['title']; + if (empty ($title)) { + $title = rl_get_resource_name(api_get_course_id(), $this->get_id(), $item['id']); + } + //$title = api_htmlentities($title, ENT_QUOTES, $this->encoding); + $title = Security::remove_XSS($title); + if ($item['type'] != 'dokeos_chapter' && $item['type'] != 'dir' && $item['type'] != 'dokeos_module') { + //$html .= "<a href='lp_controller.php?".api_get_cidreq()."&action=content&lp_id=".$this->get_id()."&item_id=".$item['id']."' target='lp_content_frame_name'>".$title."</a>" ; + $url = $this->get_link('http', $item['id']); + //$html .= '<a href="'.$url.'" target="content_name" onclick="top.load_item('.$item['id'].',\''.$url.'\');">'.$title.'</a>' ; + //$html .= '<a href="" onclick="top.load_item('.$item['id'].',\''.$url.'\');return false;">'.$title.'</a>' ; + + //<img align="absbottom" width="13" height="13" src="../img/lp_document.png"> background:#aaa; + $html .= '<a href="" onclick="switch_item(' . + $mycurrentitemid . ',' . + $item['id'] . ');' . + 'return false;" >' . stripslashes($title) . '</a>'; + } elseif ($item['type'] == 'dokeos_module' || $item['type'] == 'dokeos_chapter') { + $html .= "<img align='absbottom' width='13' height='13' src='../img/lp_dokeos_module.png'> " . stripslashes($title); + } elseif ($item['type'] == 'dir') { + $html .= stripslashes($title); + } + + $tbl_track_e_exercises = Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $user_id = api_get_user_id(); + $course_id = api_get_course_id(); + $sql = "SELECT path FROM $tbl_track_e_exercises, $tbl_lp_item + WHERE path = '" . $item['path'] . "' AND exe_user_id = '$user_id' AND exe_cours_id = '$course_id' AND path = exe_exo_id AND status <> 'incomplete'"; + $result = Database::query($sql); + $count = Database :: num_rows($result); + if ($item['type'] == 'quiz') { + if ($item['status'] == 'completed') { + $html .= " <img id='toc_img_" . $item['id'] . "' src='" . $icon_name[$item['status']] . "' alt='" . substr($item['status'], 0, 1) . "' width='12' height='12' />"; + } + } else { + if ($item['type'] != 'dokeos_chapter' && $item['type'] != 'dokeos_module' && $item['type'] != 'dir') { + $html .= " <img id='toc_img_" . $item['id'] . "' src='" . $icon_name[$item['status']] . "' alt='" . substr($item['status'], 0, 1) . "' width='12' height='12' />"; + } + } + + $html .= "</div>"; + + if ($scorm_color_background != '') { + $html .= '</div>'; + } + + $color_counter++; + } + $html .= "</div>\n"; + return $html; + } + + /** + * Gets the learnpath maker name - generally the editor's name + * @return string Learnpath maker name + */ + public function get_maker() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_maker()', 0); + } + if (!empty ($this->maker)) { + return $this->maker; + } else { + return ''; + } + } + + /** + * Gets the user-friendly message stored in $this->message + * @return string Message + */ + public function get_message() { + + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_message()', 0); + } + return $this->message; + } + + /** + * Gets the learnpath name/title + * @return string Learnpath name/title + */ + public function get_name() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_name()', 0); + } + if (!empty ($this->name)) { + return $this->name; + } else { + return 'N/A'; + } + } + + /** + * Gets a link to the resource from the present location, depending on item ID. + * @param string Type of link expected + * @param integer Learnpath item ID + * @return string Link to the lp_item resource + */ + public function get_link($type = 'http', $item_id = null) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_link(' . $type . ',' . $item_id . ')', 0); + } + if (empty($item_id)) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() - no item id given in learnpath::get_link(), using current: ' . $this->get_current_item_id(), 0); + } + $item_id = $this->get_current_item_id(); + } + + if (empty ($item_id)) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() - no current item id found in learnpath object', 0); + } + //still empty, this means there was no item_id given and we are not in an object context or + //the object property is empty, return empty link + $item_id = $this->first(); + return ''; + } + + $file = ''; + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_item_table = Database :: get_course_table(TABLE_LP_ITEM); + $lp_item_view_table = Database :: get_course_table(TABLE_LP_ITEM_VIEW); + $item_id = Database::escape_string($item_id); + + $sel = "SELECT l.lp_type as ltype, l.path as lpath, li.item_type as litype, li.path as lipath, li.parameters as liparams " . + "FROM $lp_table l, $lp_item_table li WHERE li.id = $item_id AND li.lp_id = l.id"; + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() - selecting item ' . $sel, 0); + } + $res = Database::query($sel); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + //var_dump($row); + $lp_type = $row['ltype']; + $lp_path = $row['lpath']; + $lp_item_type = $row['litype']; + $lp_item_path = $row['lipath']; + $lp_item_params = $row['liparams']; + if (empty ($lp_item_params) && strpos($lp_item_path, '?') !== false) { + list ($lp_item_path, $lp_item_params) = explode('?', $lp_item_path); + } + //$lp_item_params = '?'.$lp_item_params; + + //add ? if none - left commented to give freedom to scorm implementation + //if(substr($lp_item_params,0,1)!='?'){ + // $lp_item_params = '?'.$lp_item_params; + //} + $sys_course_path = api_get_path(SYS_COURSE_PATH) . api_get_course_path(); + if ($type == 'http') { + $course_path = api_get_path(WEB_COURSE_PATH) . api_get_course_path(); //web path + } else { + $course_path = $sys_course_path; //system path + } + + + // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc), then change the lp type to thread it as a normal Chamilo LP not a SCO. + if (in_array($lp_item_type, array('quiz', 'document', 'link', 'forum', 'thread', 'student_publication'))) { + $lp_type = 1; + } + // Now go through the specific cases to get the end of the path. + + // @todo Use constants instead of int values. + switch ($lp_type) { + case 1 : + if ($lp_item_type == 'dokeos_chapter') { + $file = 'lp_content.php?type=dir'; + } else { + require_once 'resourcelinker.inc.php'; + $file = rl_get_resource_link_for_learnpath(api_get_course_id(), $this->get_id(), $item_id); + + // check how much attempts of a exercise exits in lp + $lp_item_id = $this->get_current_item_id(); + $lp_view_id = $this->get_view_id(); + $prevent_reinit = $this->items[$this->current]->get_prevent_reinit(); // 1 = 1 attempts - 2 multiple attempts + + $list = $this->get_toc(); + $type_quiz = false; + + foreach ($list as $toc) { + if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) { + $type_quiz = true; + } + } + + if ($type_quiz) { + $lp_item_id = Database :: escape_string($lp_item_id); + $lp_view_id = Database :: escape_string($lp_view_id); + $sql = "SELECT count(*) FROM $lp_item_view_table WHERE lp_item_id='" . (int) $lp_item_id . "' AND lp_view_id ='" . (int) $lp_view_id . "' AND status='completed'"; + $result = Database::query($sql); + $row_count = Database :: fetch_row($result); + $count_item_view = (int) $row_count[0]; + + $not_multiple_attempt = 0; + if ($prevent_reinit === 1 && $count_item_view > 0) { + $not_multiple_attempt = 1; + $file = 'blank.php?error=reached_one_attempt'; + } + //$file .= '¬_multiple_attempt=' . $not_multiple_attempt; + } + + $tmp_array = explode('/', $file); + $document_name = $tmp_array[count($tmp_array) - 1]; + if (strpos($document_name, '_DELETED_')) { + $file = 'blank.php?error=document_deleted'; + } + } + break; + case 2 : + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Item type: ' . $lp_item_type, 0); + } + if ($lp_item_type != 'dir') { + // Quite complex here: + // We want to make sure 'http://' (and similar) links can + // be loaded as is (withouth the Chamilo path in front) but + // some contents use this form: resource.htm?resource=http://blablabla + // which means we have to find a protocol at the path's start, otherwise + // it should not be considered as an external URL. + + //if ($this->prerequisites_match($item_id)) { + if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Found match for protocol in ' . $lp_item_path, 0); + } + // Distant url, return as is. + $file = $lp_item_path; + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - No starting protocol in ' . $lp_item_path, 0); + } + // Prevent getting untranslatable urls. + $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); + $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); + // Prepare the path. + $file = $course_path . '/scorm/' . $lp_path . '/' . $lp_item_path; + // TODO: Fix this for urls with protocol header. + $file = str_replace('//', '/', $file); + $file = str_replace(':/', '://', $file); + if (substr($lp_path, -1) == '/') { + $lp_path = substr($lp_path, 0, -1); + } + + if (!is_file(realpath($sys_course_path . '/scorm/' . $lp_path . '/' . $lp_item_path))) { + // if file not found. + $decoded = html_entity_decode($lp_item_path); + list ($decoded) = explode('?', $decoded); + if (!is_file(realpath($sys_course_path . '/scorm/' . $lp_path . '/' . $decoded))) { + require_once 'resourcelinker.inc.php'; + $file = rl_get_resource_link_for_learnpath(api_get_course_id(), $this->get_id(), $item_id); + if (empty($file)) { + $file = 'blank.php?error=document_not_found'; + } else { + $tmp_array = explode('/', $file); + $document_name = $tmp_array[count($tmp_array) - 1]; + if (strpos($document_name, '_DELETED_')) { + $file = 'blank.php?error=document_deleted'; + } else { + $file = 'blank.php?error=document_not_found'; + } + } + } else { + $file = $course_path . '/scorm/' . $lp_path . '/' . $decoded; + } + } + } + //}else{ + //prerequisites did not match + //$file = 'blank.php'; + //} + // We want to use parameters if they were defined in the imsmanifest. + if ($file != 'blank.php') { + $file .= (strstr($file, '?') === false ? '?' : '') . $lp_item_params; + } + } else { + $file = 'lp_content.php?type=dir'; + } + break; + case 3 : + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Item type: ' . $lp_item_type, 0); + } + // Formatting AICC HACP append URL. + $aicc_append = '?aicc_sid=' . urlencode(session_id()) . '&aicc_url=' . urlencode(api_get_path(WEB_CODE_PATH) . 'newscorm/aicc_hacp.php') . '&'; + if ($lp_item_type != 'dir') { + // Quite complex here: + // We want to make sure 'http://' (and similar) links can + // be loaded as is (withouth the Chamilo path in front) but + // some contents use this form: resource.htm?resource=http://blablabla + // which means we have to find a protocol at the path's start, otherwise + // it should not be considered as an external URL. + + if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Found match for protocol in ' . $lp_item_path, 0); + } + // Distant url, return as is. + $file = $lp_item_path; + // Enabled and modified by Ivan Tcholakov, 16-OCT-2008. + /* + if (stristr($file,'<servername>') !== false) { + $file = str_replace('<servername>', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path); + } + */ + if (stripos($file, '<servername>') !== false) { + //$file = str_replace('<servername>',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path); + $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path)); + $file = str_replace('<servername>', $web_course_path . '/scorm/' . $lp_path, $lp_item_path); + } + // + $file .= $aicc_append; + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - No starting protocol in ' . $lp_item_path, 0); + } + // Prevent getting untranslatable urls. + $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); + $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); + // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name. + $file = $course_path . '/scorm/' . $lp_path . '/' . $lp_item_path; + // TODO: Fix this for urls with protocol header. + $file = str_replace('//', '/', $file); + $file = str_replace(':/', '://', $file); + $file .= $aicc_append; + } + } else { + $file = 'lp_content.php?type=dir'; + } + break; + case 4 : + break; + default : + break; + } + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_link() - returning "' . $file . '" from get_link', 0); + } + return $file; + } + + /** + * Gets the latest usable view or generate a new one + * @param integer Optional attempt number. If none given, takes the highest from the lp_view table + * @return integer DB lp_view id + */ + public function get_view($attempt_num = 0) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_view()', 0); + } + $search = ''; + // Use $attempt_num to enable multi-views management (disabled so far). + if ($attempt_num != 0 AND intval(strval($attempt_num)) == $attempt_num) { + $search = 'AND view_count = ' . $attempt_num; + } + // When missing $attempt_num, search for a unique lp_view record for this lp and user. + $lp_view_table = Database :: get_course_table(TABLE_LP_VIEW); + $sql = "SELECT id, view_count FROM $lp_view_table " . + "WHERE lp_id = " . $this->get_id() . " " . + "AND user_id = " . $this->get_user_id() . " " . + $search . + " ORDER BY view_count DESC"; + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $this->lp_view_id = $row['id']; + } else { + // There is no database record, create one. + $sql = "INSERT INTO $lp_view_table(lp_id,user_id,view_count)" . + "VALUES (" . $this->get_id() . "," . $this->get_user_id() . ",1)"; + $res = Database::query($sql); + $id = Database :: insert_id(); + $this->lp_view_id = $id; + } + return $this->lp_view_id; + } + + /** + * Gets the current view id + * @return integer View ID (from lp_view) + */ + public function get_view_id() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_view_id()', 0); + } + if (!empty ($this->lp_view_id)) { + return $this->lp_view_id; + } else { + return 0; + } + } + + /** + * Gets the update queue + * @return array Array containing IDs of items to be updated by JavaScript + */ + public function get_update_queue() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_update_queue()', 0); + } + return $this->update_queue; + } + + /** + * Gets the user ID + * @return integer User ID + */ + public function get_user_id() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_user_id()', 0); + } + if (!empty ($this->user_id)) { + return $this->user_id; + } else { + return false; + } + } + + /** + * Checks if any of the items has an audio element attached + * @return bool True or false + */ + public function has_audio() { + if ($this->debug > 1) { + error_log('New LP - In learnpath::has_audio()', 0); + } + $has = false; + foreach ($this->items as $i => $item) { + if (!empty ($this->items[$i]->audio)) { + $has = true; + break; + } + } + return $has; + } + + /** + * Logs a message into a file + * @param string Message to log + * @return boolean True on success, false on error or if msg empty + */ + public function log($msg) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::log()', 0); + } + // TODO + $this->error .= $msg . "\n"; + return true; + } + + /** + * Moves an item up and down at its level + * @param integer Item to move up and down + * @param string Direction 'up' or 'down' + * @return integer New display order, or false on error + */ + public function move_item($id, $direction) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::move_item(' . $id . ',' . $direction . ')', 0); + } + if (empty ($id) or empty ($direction)) { + return false; + } + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_sel = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE id = " . $id; + $res_sel = Database::query($sql_sel); + // Check if elem exists. + if (Database :: num_rows($res_sel) < 1) { + return false; + } + // Gather data. + $row = Database :: fetch_array($res_sel); + $previous = $row['previous_item_id']; + $next = $row['next_item_id']; + $display = $row['display_order']; + $parent = $row['parent_item_id']; + $lp = $row['lp_id']; + // Update the item (switch with previous/next one). + switch ($direction) { + case 'up' : + if ($this->debug > 2) { + error_log('Movement up detected', 0); + } + if ($display <= 1) { /*do nothing*/ + } else { + $sql_sel2 = "SELECT * + FROM $tbl_lp_item + WHERE id = $previous"; + + if ($this->debug > 2) { + error_log('Selecting previous: ' . $sql_sel2, 0); + } + $res_sel2 = Database::query($sql_sel2); + if (Database :: num_rows($res_sel2) < 1) { + $previous_previous = 0; + } + // Gather data. + $row2 = Database :: fetch_array($res_sel2); + $previous_previous = $row2['previous_item_id']; + // Update previous_previous item (switch "next" with current). + if ($previous_previous != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $id WHERE id = $previous_previous"; + if ($this->debug > 2) { + error_log($sql_upd2, 0); + } + $res_upd2 = Database::query($sql_upd2); + } + // Update previous item (switch with current). + if ($previous != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $next, previous_item_id = $id, display_order = display_order +1 WHERE id = $previous"; + if ($this->debug > 2) { + error_log($sql_upd2, 0); + } + $res_upd2 = Database::query($sql_upd2); + } + + // Update current item (switch with previous). + if ($id != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $previous, previous_item_id = $previous_previous, display_order = display_order-1 WHERE id = $id"; + if ($this->debug > 2) { + error_log($sql_upd2, 0); + } + $res_upd2 = Database::query($sql_upd2); + } + // Update next item (new previous item). + if ($next != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous WHERE id = $next"; + if ($this->debug > 2) { + error_log($sql_upd2, 0); + } + $res_upd2 = Database::query($sql_upd2); + } + $display = $display -1; + } + break; + + case 'down' : + if ($this->debug > 2) { + error_log('Movement down detected', 0); + } + if ($next == 0) { /* Do nothing. */ + } else { + $sql_sel2 = "SELECT * FROM $tbl_lp_item WHERE id = $next"; + if ($this->debug > 2) { + error_log('Selecting next: ' . $sql_sel2, 0); + } + $res_sel2 = Database::query($sql_sel2); + if (Database :: num_rows($res_sel2) < 1) { + $next_next = 0; + } + // Gather data. + $row2 = Database :: fetch_array($res_sel2); + $next_next = $row2['next_item_id']; + // Update previous item (switch with current). + if ($previous != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $next WHERE id = $previous"; + $res_upd2 = Database::query($sql_upd2); + } + // Update current item (switch with previous). + if ($id != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $next, next_item_id = $next_next, display_order = display_order+1 WHERE id = $id"; + $res_upd2 = Database::query($sql_upd2); + } + + // Update next item (new previous item). + if ($next != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous, next_item_id = $id, display_order = display_order-1 WHERE id = $next"; + $res_upd2 = Database::query($sql_upd2); + } + + // Update next_next item (switch "previous" with current). + if ($next_next != 0) { + $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $id WHERE id = $next_next"; + $res_upd2 = Database::query($sql_upd2); + } + $display = $display +1; + } + + break; + default : + return false; + } + return $display; + } + + /** + * Move a learnpath up (display_order) + * @param integer Learnpath ID + */ + public function move_up($lp_id) { + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table ORDER BY display_order"; + $res = Database::query($sql); + if ($res === false) + return false; + $lps = array (); + $lp_order = array (); + $num = Database :: num_rows($res); + // First check the order is correct, globally (might be wrong because + // of versions < 1.8.4) + if ($num > 0) { + $i = 1; + while ($row = Database :: fetch_array($res)) { + if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. + $need_fix = true; + $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; + $res_u = Database::query($sql_u); + } + $row['display_order'] = $i; + $lps[$row['id']] = $row; + $lp_order[$i] = $row['id']; + $i++; + } + } + if ($num > 1) { // If there's only one element, no need to sort. + $order = $lps[$lp_id]['display_order']; + if ($order > 1) { // If it's the first element, no need to move up. + $sql_u1 = "UPDATE $lp_table SET display_order = $order WHERE id = " . $lp_order[$order - 1]; + $res_u1 = Database::query($sql_u1); + $sql_u2 = "UPDATE $lp_table SET display_order = " . ($order - 1) . " WHERE id = " . $lp_id; + $res_u2 = Database::query($sql_u2); + } + } + } + + /** + * Move a learnpath down (display_order) + * @param integer Learnpath ID + */ + public function move_down($lp_id) { + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table ORDER BY display_order"; + $res = Database::query($sql); + if ($res === false) + return false; + $lps = array (); + $lp_order = array (); + $num = Database :: num_rows($res); + $max = 0; + // First check the order is correct, globally (might be wrong because + // of versions < 1.8.4). + if ($num > 0) { + $i = 1; + while ($row = Database :: fetch_array($res)) { + $max = $i; + if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. + $need_fix = true; + $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; + $res_u = Database::query($sql_u); + } + $row['display_order'] = $i; + $lps[$row['id']] = $row; + $lp_order[$i] = $row['id']; + $i++; + } + } + if ($num > 1) { // If there's only one element, no need to sort. + $order = $lps[$lp_id]['display_order']; + if ($order < $max) { // If it's the first element, no need to move up. + $sql_u1 = "UPDATE $lp_table SET display_order = $order WHERE id = " . $lp_order[$order + 1]; + $res_u1 = Database::query($sql_u1); + $sql_u2 = "UPDATE $lp_table SET display_order = " . ($order + 1) . " WHERE id = " . $lp_id; + $res_u2 = Database::query($sql_u2); + } + } + } + + /** + * Updates learnpath attributes to point to the next element + * The last part is similar to set_current_item but processing the other way around + */ + public function next() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::next()', 0); + } + $this->last = $this->get_current_item_id(); + $this->items[$this->last]->save(false, $this->prerequisites_match($this->last)); + $this->autocomplete_parents($this->last); + $new_index = $this->get_next_index(); + if ($this->debug > 2) { + error_log('New LP - New index: ' . $new_index, 0); + } + $this->index = $new_index; + if ($this->debug > 2) { + error_log('New LP - Now having orderedlist[' . $new_index . '] = ' . $this->ordered_items[$new_index], 0); + } + $this->current = $this->ordered_items[$new_index]; + if ($this->debug > 2) { + error_log('New LP - new item id is ' . $this->current . '-' . $this->get_current_item_id(), 0); + } + } + + /** + * Open a resource = initialise all local variables relative to this resource. Depending on the child + * class, this might be redefined to allow several behaviours depending on the document type. + * @param integer Resource ID + * @return boolean True on success, false otherwise + */ + public function open($id) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::open()', 0); + } + // TODO: + // set the current resource attribute to this resource + // switch on element type (redefine in child class?) + // set status for this item to "opened" + // start timer + // initialise score + $this->index = 0; //or = the last item seen (see $this->last) + } + + /** + * Check that all prerequisites are fulfilled. Returns true and an empty string on succes, returns false + * and the prerequisite string on error. + * This function is based on the rules for aicc_script language as described in the SCORM 1.2 CAM documentation page 108. + * @param integer Optional item ID. If none given, uses the current open item. + * @return boolean True if prerequisites are matched, false otherwise + * @return string Empty string if true returned, prerequisites string otherwise. + */ + public function prerequisites_match($item = null) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::prerequisites_match()', 0); + } + if (empty ($item)) { + $item = $this->current; + } + if (is_object($this->items[$item])) { + $prereq_string = $this->items[$item]->get_prereq_string(); + if (empty ($prereq_string)) { + return true; + } + // Clean spaces. + $prereq_string = str_replace(' ', '', $prereq_string); + if ($this->debug > 0) { + error_log('Found prereq_string: ' . $prereq_string, 0); + } + // Now send to the parse_prereq() function that will check this component's prerequisites. + $result = $this->items[$item]->parse_prereq($prereq_string, $this->items, $this->refs_list, $this->get_user_id()); + + if ($result === false) { + $this->set_error_msg($this->items[$item]->prereq_alert); + } + } else { + $result = true; + if ($this->debug > 1) { + error_log('New LP - $this->items[' . $item . '] was not an object', 0); + } + } + + if ($this->debug > 1) { + error_log('New LP - End of prerequisites_match(). Error message is now ' . $this->error, 0); + } + return $result; + } + + /** + * Updates learnpath attributes to point to the previous element + * The last part is similar to set_current_item but processing the other way around + */ + public function previous() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::previous()', 0); + } + $this->last = $this->get_current_item_id(); + $this->items[$this->last]->save(false, $this->prerequisites_match($this->last)); + $this->autocomplete_parents($this->last); + $new_index = $this->get_previous_index(); + $this->index = $new_index; + $this->current = $this->ordered_items[$new_index]; + } + + /** + * Publishes a learnpath. This basically means show or hide the learnpath + * to normal users. + * Can be used as abstract + * @param integer Learnpath ID + * @param string New visibility + */ + public function toggle_visibility($lp_id, $set_visibility = 1) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::toggle_visibility()', 0); } + $action = 'visible'; + if ($set_visibility != 1) { + $action = 'invisible'; + } + return api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $lp_id, $action, api_get_user_id()); + } + + /** + * Publishes a learnpath. This basically means show or hide the learnpath + * on the course homepage + * Can be used as abstract + * @param integer Learnpath ID + * @param string New visibility + */ + public function toggle_publish($lp_id, $set_visibility = 'v') { + //if ($this->debug > 0) { error_log('New LP - In learnpath::toggle_publish()', 0); } + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $tbl_lp where id=$lp_id"; + $result = Database::query($sql); + $row = Database :: fetch_array($result); + $name = domesticate($row['name']); + if ($set_visibility == 'i') { + $s = $name . " " . get_lang('_no_published'); + $dialogBox = $s; + $v = 0; + } + if ($set_visibility == 'v') { + $s = $name . " " . get_lang('_published'); + $dialogBox = $s; + $v = 1; + } + + $session_id = api_get_session_id(); + $session_condition = api_get_session_condition($session_id); + + $tbl_tool = Database :: get_course_table(TABLE_TOOL_LIST); + $link = 'newscorm/lp_controller.php?action=view&lp_id=' . $lp_id; + $sql = "SELECT * FROM $tbl_tool where name='$name' and image='scormbuilder.gif' and link LIKE '$link%' $session_condition"; + $result = Database::query($sql); + $num = Database :: num_rows($result); + $row2 = Database :: fetch_array($result); + //if ($this->debug > 2) { error_log('New LP - '.$sql.' - '.$num, 0); } + if (($set_visibility == 'i') && ($num > 0)) { + $sql = "DELETE FROM $tbl_tool WHERE (name='$name' and image='scormbuilder.gif' and link LIKE '$link%' $session_condition)"; + } + elseif (($set_visibility == 'v') && ($num == 0)) { + $sql = "INSERT INTO $tbl_tool (name, link, image, visibility, admin, address, added_tool, session_id) VALUES ('$name','newscorm/lp_controller.php?action=view&lp_id=$lp_id','scormbuilder.gif','$v','0','pastillegris.gif',0, $session_id)"; + } else { + // Parameter and database incompatible, do nothing. + } + $result = Database::query($sql); + //if ($this->debug > 2) { error_log('New LP - Leaving learnpath::toggle_visibility: '.$sql, 0); } + } + + /** + * Restart the whole learnpath. Return the URL of the first element. + * Make sure the results are saved with anoter method. This method should probably be + * redefined in children classes. + * To use a similar method statically, use the create_new_attempt() method + * @return string URL to load in the viewer + */ + public function restart() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::restart()', 0); + } + // TODO + // Call autosave method to save the current progress. + //$this->index = 0; + $session_id = api_get_session_id(); + $lp_view_table = Database :: get_course_table(TABLE_LP_VIEW); + $sql = "INSERT INTO $lp_view_table (lp_id, user_id, view_count, session_id) " . + "VALUES (" . $this->lp_id . "," . $this->get_user_id() . "," . ($this->attempt + 1) . ", $session_id)"; + if ($this->debug > 2) { + error_log('New LP - Inserting new lp_view for restart: ' . $sql, 0); + } + $res = Database::query($sql); + if ($view_id = Database :: insert_id($res)) { + $this->lp_view_id = $view_id; + $this->attempt = $this->attempt + 1; + } else { + $this->error = 'Could not insert into item_view table...'; + return false; + } + $this->autocomplete_parents($this->current); + foreach ($this->items as $index => $dummy) { + $this->items[$index]->restart(); + $this->items[$index]->set_lp_view($this->lp_view_id); + } + $this->first(); + return true; + } + + /** + * Saves the current item + * @return boolean + */ + public function save_current() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::save_current()', 0); + } + // TODO: Do a better check on the index pointing to the right item (it is supposed to be working + // on $ordered_items[] but not sure it's always safe to use with $items[]). + if ($this->debug > 2) { + error_log('New LP - save_current() saving item ' . $this->current, 0); + } + if ($this->debug > 2) { + error_log('' . print_r($this->items, true), 0); + } + if (is_object($this->items[$this->current])) { + //$res = $this->items[$this->current]->save(false); + $res = $this->items[$this->current]->save(false, $this->prerequisites_match($this->current)); + $this->autocomplete_parents($this->current); + $status = $this->items[$this->current]->get_status(); + $this->append_message('new_item_status: ' . $status); + $this->update_queue[$this->current] = $status; + return $res; + } + return false; + } + + /** + * Saves the given item + * @param integer Item ID. Optional (will take from $_REQUEST if null) + * @param boolean Save from url params (true) or from current attributes (false). Optional. Defaults to true + * @return boolean + */ + public function save_item($item_id = null, $from_outside = true) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::save_item(' . $item_id . ',' . $from_outside . ')', 0); + } + // TODO: Do a better check on the index pointing to the right item (it is supposed to be working + // on $ordered_items[] but not sure it's always safe to use with $items[]). + if (empty ($item_id)) { + $item_id = $this->escape_string($_REQUEST['id']); + } + if (empty ($item_id)) { + $item_id = $this->get_current_item_id(); + } + if ($this->debug > 2) { + error_log('New LP - save_current() saving item ' . $item_id, 0); + } + if (is_object($this->items[$item_id])) { + $res = $this->items[$item_id]->save($from_outside, $this->prerequisites_match($item_id)); + //$res = $this->items[$item_id]->save($from_outside); + $this->autocomplete_parents($item_id); + $status = $this->items[$item_id]->get_status(); + $this->append_message('new_item_status: ' . $status); + $this->update_queue[$item_id] = $status; + return $res; + } + return false; + } + + /** + * Saves the last item seen's ID only in case + */ + public function save_last() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::save_last()', 0); + } + $session_condition = api_get_session_condition(api_get_session_id(), true, false); + $table = Database :: get_course_table(TABLE_LP_VIEW); + if (isset ($this->current)) { + if ($this->debug > 2) { + error_log('New LP - Saving current item (' . $this->current . ') for later review', 0); + } + $sql = "UPDATE $table SET last_item = " . Database::escape_string($this->get_current_item_id()). " " . + "WHERE lp_id = " . $this->get_id() . " AND user_id = " . $this->get_user_id().' '.$session_condition; + + if ($this->debug > 2) { + error_log('New LP - Saving last item seen : ' . $sql, 0); + } + $res = Database::query($sql); + } + + // Save progress. + list($progress, $text) = $this->get_progress_bar_text('%'); + if ($progress >= 0 && $progress <= 100) { + $progress = (int) $progress; + $sql = "UPDATE $table SET progress = $progress " . + "WHERE lp_id = " . $this->get_id() . " AND " . + "user_id = " . $this->get_user_id().' '.$session_condition; + $res = Database::query($sql); // Ignore errors as some tables might not have the progress field just yet. + $this->progress_db = $progress; + } + } + + /** + * Sets the current item ID (checks if valid and authorized first) + * @param integer New item ID. If not given or not authorized, defaults to current + */ + public function set_current_item($item_id = null) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_current_item(' . $item_id . ')', 0); + } + if (empty ($item_id)) { + if ($this->debug > 2) { + error_log('New LP - No new current item given, ignore...', 0); + } + // Do nothing. + } else { + if ($this->debug > 2) { + error_log('New LP - New current item given is ' . $item_id . '...', 0); + } + if (is_numeric($item_id)) { + $item_id = $this->escape_string($item_id); + // TODO: Check in database here. + $this->last = $this->current; + $this->current = $item_id; + // TODO: Update $this->index as well. + foreach ($this->ordered_items as $index => $item) { + if ($item == $this->current) { + $this->index = $index; + break; + } + } + if ($this->debug > 2) { + error_log('New LP - set_current_item(' . $item_id . ') done. Index is now : ' . $this->index, 0); + } + } else { + error_log('New LP - set_current_item(' . $item_id . ') failed. Not a numeric value: ', 0); + } + } + } + + /** + * Sets the encoding + * @param string New encoding + */ + public function set_encoding($enc = 'ISO-8859-15') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_encoding()', 0); + } + $enc = strtoupper($enc); + $encodings = array ( + 'UTF-8', + 'ISO-8859-1', + 'ISO-8859-15', + 'cp1251', + 'cp1252', + 'KOI8-R', + 'BIG5', + 'GB2312', + 'Shift_JIS', + 'EUC-JP', + '' + ); + if (in_array($enc, $encodings)) { // TODO: Incorrect comparison, fix it. + $lp = $this->get_id(); + if ($lp != 0) { + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' WHERE id = " . $lp; + $res = Database::query($sql); + return $res; + } + } + return false; + } + + /** + * Sets the JS lib setting in the database directly. + * This is the JavaScript library file this lp needs to load on startup + * @param string Proximity setting + * @return boolean True on update success. False otherwise. + */ + public function set_jslib($lib = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_jslib()', 0); + } + $lp = $this->get_id(); + if ($lp != 0) { + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "UPDATE $tbl_lp SET js_lib = '$lib' WHERE id = " . $lp; + $res = Database::query($sql); + return $res; + } else { + return false; + } + } + + /** + * Sets the name of the LP maker (publisher) (and save) + * @param string Optional string giving the new content_maker of this learnpath + * @return boolean True + */ + public function set_maker($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_maker()', 0); + } + if (empty ($name)) + return false; + $this->maker = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET content_maker = '" . $this->maker . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new content_maker : ' . $this->maker, 0); + } + $res = Database::query($sql); + return true; + } + + /** + * Sets the name of the current learnpath (and save) + * @param string Optional string giving the new name of this learnpath + * @return boolean True/False + */ + public function set_name($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_name()', 0); + } + if (empty ($name)) + return false; + + $this->name = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET name = '" . $this->name . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new name : ' . $this->name, 0); + } + $res = Database::query($sql); + // If the lp is visible on the homepage, change his name there. + if (Database::affected_rows()) { + $table = Database :: get_course_table(TABLE_TOOL_LIST); + $sql = 'UPDATE ' . $table . ' SET + name = "' . $this->name . '" + WHERE link = "newscorm/lp_controller.php?action=view&lp_id=' . $lp_id . '"'; + Database::query($sql); + } + return true; + } + + /** + * Set index specified prefix terms for all items in this path + * @param string Comma-separated list of terms + * @param char Xapian term prefix + * @return boolean False on error, true otherwise + */ + public function set_terms_by_prefix($terms_string, $prefix) { + if (api_get_setting('search_enabled') !== 'true') + return false; + + $terms_string = trim($terms_string); + $terms = explode(',', $terms_string); + array_walk($terms, 'trim_value'); + + $stored_terms = $this->get_common_index_terms_by_prefix($prefix); + //var_dump($stored_terms); + //var_dump($terms); + + // Don't do anything if no change, verify only at DB, not the search engine. + if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) + return false; + + require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API. + require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; + require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php'; + require_once api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php'; + + $items_table = Database :: get_course_table(TABLE_LP_ITEM); + // TODO: Make query secure agains XSS : use member attr instead of post var. + $lp_id = intval($_POST['lp_id']); + $sql = "SELECT * FROM $items_table WHERE lp_id = $lp_id"; + $result = Database::query($sql); + $di = new DokeosIndexer(); + + while ($lp_item = Database :: fetch_array($result)) { + // Get search_did. + $tbl_se_ref = Database :: get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); + $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; + $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']); + + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + + $se_ref = Database :: fetch_array($res); + + // Compare terms. + $doc = $di->get_document($se_ref['search_did']); + + $xapian_terms = xapian_get_doc_terms($doc, $prefix); + //var_dump($xapian_terms); + $xterms = array (); + foreach ($xapian_terms as $xapian_term) { + $xterms[] = substr($xapian_term['name'], 1); + } + + $dterms = $terms; + + $missing_terms = array_diff($dterms, $xterms); + $deprecated_terms = array_diff($xterms, $dterms); + + // Save it to search engine. + foreach ($missing_terms as $term) { + $doc->add_term($prefix . $term, 1); + } + foreach ($deprecated_terms as $term) { + $doc->remove_term($prefix . $term); + } + $di->getDb()->replace_document((int) $se_ref['search_did'], $doc); + $di->getDb()->flush(); + } + } + return true; + } + + /** + * Sets the theme of the LP (local/remote) (and save) + * @param string Optional string giving the new theme of this learnpath + * @return bool Returns true if theme name is not empty + */ + public function set_theme($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_theme()', 0); + } + $this->theme = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET theme = '" . $this->theme . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new theme : ' . $this->theme, 0); + } + //$res = Database::query($sql); + $res = Database::query($sql); + return true; + } + + /** + * Sets the image of an LP (and save) + * @param string Optional string giving the new image of this learnpath + * @return bool Returns true if theme name is not empty + */ + public function set_preview_image($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_preview_image()', 0); + } + $this->preview_image = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET preview_image = '" . $this->preview_image . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new preview image : ' . $this->preview_image, 0); + } + $res = Database::query($sql); + return true; + } + + /** + * Sets the author of a LP (and save) + * @param string Optional string giving the new author of this learnpath + * @return bool Returns true if author's name is not empty + */ + public function set_author($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_author()', 0); + } + $this->author = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET author = '" . $this->author . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new preview author : ' . $this->author, 0); + } + $res = Database::query($sql); + return true; + } + + /** + * Sets the prerequisite of a LP (and save) + * @param int integer giving the new prerequisite of this learnpath + * @return bool returns true if prerequisite is not empty + */ + public function set_prerequisite($prerequisite) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_prerequisite()', 0); + } + $this->prerequisite = intval($prerequisite); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET prerequisite = '".$this->prerequisite."' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new preview requisite : ' . $this->requisite, 0); + } + $res = Database::query($sql); + return true; + } + + /** + * Sets the location/proximity of the LP (local/remote) (and save) + * @param string Optional string giving the new location of this learnpath + * @return boolean True on success / False on error + */ + public function set_proximity($name = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_proximity()', 0); + } + if (empty ($name)) + return false; + + $this->proximity = $this->escape_string($name); + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $lp_id = $this->get_id(); + $sql = "UPDATE $lp_table SET content_local = '" . $this->proximity . "' WHERE id = '$lp_id'"; + if ($this->debug > 2) { + error_log('New LP - lp updated with new proximity : ' . $this->proximity, 0); + } + //$res = Database::query($sql); + $res = Database::query($sql); + return true; + } + + /** + * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item + * @param integer DB ID of the item + */ + public function set_previous_item($id) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_previous_item()', 0); + } + $this->last = $id; + } + + /** + * Sets the object's error message + * @param string Error message. If empty, reinits the error string + * @return void + */ + public function set_error_msg($error = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::set_error_msg()', 0); + } + if (empty ($error)) { + $this->error = ''; + } else { + $this->error .= $error; + } + } + + /** + * Launches the current item if not 'sco' (starts timer and make sure there is a record ready in the DB) + * @param boolean Whether to allow a new attempt or not + * @return boolean True + */ + public function start_current_item($allow_new_attempt = false) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::start_current_item()', 0); + } + if ($this->current != 0 AND is_object($this->items[$this->current])) { + $type = $this->get_type(); + $item_type = $this->items[$this->current]->get_type(); + if (($type == 2 && $item_type != 'sco') OR ($type == 3 && $item_type != 'au') OR ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)) { + $this->items[$this->current]->open($allow_new_attempt); + + $this->autocomplete_parents($this->current); + $prereq_check = $this->prerequisites_match($this->current); + $this->items[$this->current]->save(false, $prereq_check); + //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); + } else { + // If sco, then it is supposed to have been updated by some other call. + } + if ($item_type == 'sco') { + $this->items[$this->current]->restart(); + } + } + if ($this->debug > 0) { + error_log('New LP - End of learnpath::start_current_item()', 0); + } + return true; + } + + /** + * Stops the processing and counters for the old item (as held in $this->last) + * @return boolean True/False + */ + public function stop_previous_item() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::stop_previous_item()', 0); + } + + if ($this->last != 0 && $this->last != $this->current && is_object($this->items[$this->last])) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' is object', 0); + } + switch ($this->get_type()) { + case '3' : + if ($this->items[$this->last]->get_type() != 'au') { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 3 is <> au', 0); + } + $this->items[$this->last]->close(); + //$this->autocomplete_parents($this->last); + //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals', 0); + } + } + case '2' : + if ($this->items[$this->last]->get_type() != 'sco') { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 2 is <> sco', 0); + } + $this->items[$this->last]->close(); + //$this->autocomplete_parents($this->last); + //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals', 0); + } + } + break; + case '1' : + default : + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 1 is asset', 0); + } + $this->items[$this->last]->close(); + break; + } + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::stop_previous_item() - No previous element found, ignoring...', 0); + } + return false; + } + return true; + } + + /** + * Updates the default view mode from fullscreen to embedded and inversely + * @return string The current default view mode ('fullscreen' or 'embedded') + */ + public function update_default_view_mode() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::update_default_view_mode()', 0); + } + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $view_mode = $row['default_view_mod']; + if ($view_mode == 'fullscreen') { + $view_mode = 'embedded'; + } elseif ($view_mode == 'embedded') { + $view_mode = 'fullscreen'; + } + $sql = "UPDATE $lp_table SET default_view_mod = '$view_mode' WHERE id = " . $this->get_id(); + $res = Database::query($sql); + $this->mode = $view_mode; + return $view_mode; + } else { + if ($this->debug > 2) { + error_log('New LP - Problem in update_default_view() - could not find LP ' . $this->get_id() . ' in DB', 0); + } + } + return -1; + } + + /** + * Updates the default behaviour about auto-commiting SCORM updates + * @return boolean True if auto-commit has been set to 'on', false otherwise + */ + public function update_default_scorm_commit() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::update_default_scorm_commit()', 0); + } + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $force = $row['force_commit']; + if ($force == 1) { + $force = 0; + $force_return = false; + } elseif ($force == 0) { + $force = 1; + $force_return = true; + } + $sql = "UPDATE $lp_table SET force_commit = $force WHERE id = " . $this->get_id(); + $res = Database::query($sql); + $this->force_commit = $force_return; + return $force_return; + } else { + if ($this->debug > 2) { + error_log('New LP - Problem in update_default_scorm_commit() - could not find LP ' . $this->get_id() . ' in DB', 0); + } + } + return -1; + } + + /** + * Updates the order of learning paths (goes through all of them by order and fills the gaps) + * @return bool True on success, false on failure + */ + public function update_display_order() { + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table ORDER BY display_order"; + $res = Database::query($sql); + if ($res === false) + return false; + $lps = array (); + $lp_order = array (); + $num = Database :: num_rows($res); + // First check the order is correct, globally (might be wrong because + // of versions < 1.8.4). + if ($num > 0) { + $i = 1; + while ($row = Database :: fetch_array($res)) { + if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. + $need_fix = true; + $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; + $res_u = Database::query($sql_u); + } + $i++; + } + } + return true; + } + + /** + * Updates the "prevent_reinit" value that enables control on reinitialising items on second view + * @return boolean True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case) + */ + public function update_reinit() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::update_reinit()', 0); + } + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $force = $row['prevent_reinit']; + if ($force == 1) { + $force = 0; + } elseif ($force == 0) { + $force = 1; + } + $sql = "UPDATE $lp_table SET prevent_reinit = $force WHERE id = " . $this->get_id(); + $res = Database::query($sql); + $this->prevent_reinit = $force; + return $force; + } else { + if ($this->debug > 2) { + error_log('New LP - Problem in update_reinit() - could not find LP ' . $this->get_id() . ' in DB', 0); + } + } + return -1; + } + + /** + * Updates the "scorm_debug" value that shows or hide the debug window + * @return boolean True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case) + */ + public function update_scorm_debug() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::update_scorm_debug()', 0); + } + $lp_table = Database :: get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $force = $row['debug']; + if ($force == 1) { + $force = 0; + } elseif ($force == 0) { + $force = 1; + } + $sql = "UPDATE $lp_table SET debug = $force WHERE id = " . $this->get_id(); + $res = Database::query($sql); + $this->scorm_debug = $force; + return $force; + } else { + if ($this->debug > 2) { + error_log('New LP - Problem in update_scorm_debug() - could not find LP ' . $this->get_id() . ' in DB', 0); + } + } + return -1; + } + + /** + * Function that makes a call to the function sort_tree_array and create_tree_array + * @author Kevin Van Den Haute + * @param array + */ + public function tree_array($array) { + if ($this->debug > 1) { + error_log('New LP - In learnpath::tree_array()', 0); + } + $array = $this->sort_tree_array($array); + $this->create_tree_array($array); + } + + /** + * Creates an array with the elements of the learning path tree in it + * + * @author Kevin Van Den Haute + * @param array $array + * @param int $parent + * @param int $depth + * @param array $tmp + */ + public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = array ()) { + if ($this->debug > 1) { + error_log('New LP - In learnpath::create_tree_array())', 0); + } + if (is_array($array)) { + for ($i = 0; $i < count($array); $i++) { + if ($array[$i]['parent_item_id'] == $parent) { + if (!in_array($array[$i]['parent_item_id'], $tmp)) { + $tmp[] = $array[$i]['parent_item_id']; + $depth++; + } + $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']); + $this->arrMenu[] = array ( + 'id' => $array[$i]['id'], + 'item_type' => $array[$i]['item_type'], + 'title' => $array[$i]['title'], + 'path' => $array[$i]['path'], + 'description' => $array[$i]['description'], + 'parent_item_id' => $array[$i]['parent_item_id'], + 'previous_item_id' => $array[$i]['previous_item_id'], + 'next_item_id' => $array[$i]['next_item_id'], + 'min_score' => $array[$i]['min_score'], + 'max_score' => $array[$i]['max_score'], + 'mastery_score' => $array[$i]['mastery_score'], + 'display_order' => $array[$i]['display_order'], + 'prerequisite' => $preq, + 'depth' => $depth, + 'audio' => $array[$i]['audio'] + ); + + $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp); + } + } + } + } + + /** + * Sorts a multi dimensional array by parent id and display order + * @author Kevin Van Den Haute + * + * @param array $array (array with al the learning path items in it) + * + * @return array + */ + public function sort_tree_array($array) { + foreach ($array as $key => $row) { + $parent[$key] = $row['parent_item_id']; + $position[$key] = $row['display_order']; + } + + if (count($array) > 0) + array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array); + + return $array; + } + + /** + * Function that creates a table structure with a learning path his modules, chapters and documents. + * Also the actions for the modules, chapters and documents are in this table. + * @author Kevin Van Den Haute + * @param int $lp_id + * @return string + */ + public function overview() { + $is_allowed_to_edit = api_is_allowed_to_edit(null,true); + + $platform_charset = api_get_system_encoding(); + if ($this->debug > 0) { + error_log('New LP - In learnpath::overview()', 0); + } + global $charset, $_course; + $_SESSION['gradebook'] = Security :: remove_XSS($_GET['gradebook']); + $return = ''; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + $sql = "SELECT * FROM " . $tbl_lp_item . " + WHERE lp_id = " . $this->lp_id; + + $result = Database::query($sql); + $arrLP = array (); + while ($row = Database :: fetch_array($result)) { + $row['title'] = Security :: remove_XSS(api_convert_encoding($row['title'], $platform_charset, $this->encoding)); + $row['description'] = Security :: remove_XSS(api_convert_encoding($row['description'], $platform_charset, $this->encoding)); + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'], + 'display_order' => $row['display_order'], + 'audio' => $row['audio'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($is_allowed_to_edit) { + $token = Security::get_token(); + + $gradebook = Security :: remove_XSS($_GET['gradebook']); + $return .= '<div class="actions">'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=build&lp_id=' . $this->lp_id . '" title="' . get_lang('Build') . '">' . Display :: return_icon('learnpath_build.gif', get_lang('Build')) . ' ' . get_lang('Build') . '</a>'; + $return .= '<span>' . Display :: return_icon('learnpath_organize_na.gif', get_lang('BasicOverview')) . ' <b>' . get_lang('BasicOverview') . '</b></span>'; + $return .= '<a href="lp_controller.php?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=view&lp_id=' . $this->lp_id . '">' . Display :: return_icon('learnpath_view.gif', get_lang('Display')) . ' ' . get_lang('Display') . '</a> ' . Display :: return_icon('i.gif'); + $return .= '<a href="lp_controller.php?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit&lp_id=' . Security :: remove_XSS($_GET['lp_id']) . '">' . Display :: return_icon('edit.gif', get_lang('CourseSettings')) . ' ' . get_lang('CourseSettings') . '</a>'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=' . Security :: remove_XSS($_GET['action']) . '&lp_id=' . Security :: remove_XSS($_GET['lp_id']) . '&updateaudio=true">' . Display :: return_icon('audio.gif', get_lang('UpdateAllAudioFragments')) . ' ' . get_lang('UpdateAllAudioFragments') . '</a>'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=add_item&type=chapter&lp_id=' . $_SESSION['oLP']->lp_id . '" title="' . get_lang('NewChapter') . '"><img alt="' . get_lang('NewChapter') . '" src="../img/lp_dokeos_chapter_add.gif" title="' . get_lang('NewChapter') . '" />' . get_lang('NewChapter') . '</a>'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=add_item&type=step&lp_id=' . $_SESSION['oLP']->lp_id . '" title="' . get_lang('NewStep') . '"><img alt="' . get_lang('NewStep') . '" src="../img/new_test.gif" title="' . get_lang('NewStep') . '" />' . get_lang('NewStep') . '</a>'; + $return .= '</div>'; + } + + // we need to start a form when we want to update all the mp3 files + if ($_GET['updateaudio'] == 'true' AND count($arrLP) <> 0) { + $return .= '<form action="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=' . Security :: remove_XSS($_GET['action']) . '&lp_id=' . Security :: remove_XSS($_GET['lp_id']) . '" method="post" enctype="multipart/form-data" name="updatemp3" id="updatemp3">'; + $return .= Display :: display_warning_message(get_lang('LeaveEmptyToKeepCurrentFile')); + } + $return .= '<table class="data_table">' . "\n"; + $return .= "\t" . '<tr>' . "\n"; + $return .= "\t" . '<th width="60%">' . get_lang('Title') . '</th>' . "\n"; + //$return .= "\t" . '<th>'.get_lang('Description').'</th>' . "\n"; + $return .= "\t" . '<th>' . get_lang('Audio') . '</th>' . "\n"; + $return .= "\t" . '<th>' . get_lang('Move') . '</th>' . "\n"; + $return .= "\t" . '<th>' . get_lang('Actions') . '</th>' . "\n"; + $return .= "\t" . '</tr>' . "\n"; + + for ($i = 0; $i < count($arrLP); $i++) { + $title = $arrLP[$i]['title']; + if ($arrLP[$i]['description'] == '') + $arrLP[$i]['description'] = ' '; + + if (($i % 2) == 0) { + $oddclass = 'row_odd'; + } else { + $oddclass = 'row_even'; + } + + $return .= "\t" . '<tr class="' . $oddclass . '">' . "\n"; + + $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']); + if (file_exists('../img/lp_' . $icon_name . '.png')) { + $return .= "\t\t" . '<td style="padding-left:' . $arrLP[$i]['depth'] * 10 . 'px;"><img align="left" src="../img/lp_' . $icon_name . '.png" style="margin-right:3px;" />' . $title . '</td>' . "\n"; + } else { + if (file_exists('../img/lp_' . $icon_name . '.gif')) { + $return .= "\t\t" . '<td style="padding-left:' . $arrLP[$i]['depth'] * 10 . 'px;"><img align="left" src="../img/lp_' . $icon_name . '.gif" style="margin-right:3px;" />' . $title . '</td>' . "\n"; + } else { + //$return .= "\t\t" . '<td style="padding-left:' . $arrLP[$i]['depth'] * 10 . 'px;">' . Display::display_icon('folder_document.gif','',array('style'=>'margin-right:3px;')) . $title . '</td>' . "\n"; + $return .= "\t\t" . '<td style="padding-left:' . $arrLP[$i]['depth'] * 10 . 'px;"><img align="left" src="../img/folder_document.gif" style="margin-right:3px;" />' . $title . '</td>' . "\n"; + } + } + //$return .= "\t\t" . '<td>' . stripslashes($arrLP[$i]['description']) . '</td>' . "\n"; + + // The audio column. + $return .= "\t\t" . '<td align="center">'; + if (!$_GET['updateaudio'] OR $_GET['updateaudio'] <> 'true') { + if (!empty ($arrLP[$i]['audio'])) { + $return .= '<span id="container' . $i . '"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</span>'; + $return .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>'; + $return .= '<script type="text/javascript"> + var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF"); + s1.addParam("allowscriptaccess","always"); + s1.addParam("flashvars","file=../../courses/' . $_course['path'] . '/document/audio/' . $arrLP[$i]['audio'] . '"); + s1.write("container' . $i . '"); + </script>'; + } else { + $return .= ' - '; + } + } else { + if ($arrLP[$i]['item_type'] != 'dokeos_chapter' && $arrLP[$i]['item_type'] != 'dokeos_module' && $arrLP[$i]['item_type'] != 'dir') { + + $return .= ' <input type="file" name="mp3file' . $arrLP[$i]['id'] . '" id="mp3file" />'; + if (!empty ($arrLP[$i]['audio'])) { + $return .= '<br />'.Security::remove_XSS($arrLP[$i]['audio']).'<input type="checkbox" name="removemp3' . $arrLP[$i]['id'] . '" id="checkbox' . $arrLP[$i]['id'] . '" />' . get_lang('RemoveAudio'); + } + } + } + $return .= '</td>'; + + if ($is_allowed_to_edit) { + $return .= '<td align="center">'; + + if ($arrLP[$i]['previous_item_id'] != 0) { + + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=move_item&direction=up&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '&sec_token='.$token.'">'; + $return .= '<img style="margin:1px;" alt="" src="../img/arrow_up_' . ($arrLP[$i]['depth'] % 3) . '.gif" title="' . get_lang('MoveUp') . '"/>'; + $return .= '</a>'; + + } else + $return .= '<img alt="" src="../img/blanco.png" title="" />'; + + if ($arrLP[$i]['next_item_id'] != 0) { + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']). '&action=move_item&direction=down&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '&sec_token='.$token.'">'; + $return .= '<img style="margin:1px;" src="../img/arrow_down_' . ($arrLP[$i]['depth'] % 3) . '.gif" title="' . get_lang('MoveDown') . '" />'; + + $return .= '</a>'; + } else + $return .= '<img alt="" src="../img/blanco.png" title="" />'; + + $return .= '</td>'; + + $return .= '<td align="center">'; + + if ($arrLP[$i]['item_type'] != 'dokeos_chapter' && $arrLP[$i]['item_type'] != 'dokeos_module') { + $return .= "\t\t\t" . '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit_item&view=build&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '&path_item=' . $arrLP[$i]['path'] . '">'; + $return .= '<img style="margin:1px;" alt="" src="../img/edit.gif" title="' . get_lang('_edit_learnpath_module') . '" />'; + $return .= '</a>' . "\n"; + } else { + $return .= "\t\t\t" . '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit_item&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '&path_item=' . $arrLP[$i]['path'] . '">'; + $return .= '<img style="margin:1px;" alt="" src="../img/edit.gif" title="' . get_lang('_edit_learnpath_module') . '" />'; + $return .= '</a>' . "\n"; + } + + $return .= "\t\t\t" . '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=delete_item&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '" onclick="return confirmation(\'' . addslashes($title) . '\');">'; + $return .= '<img style="margin:1px;" alt="" src="../img/delete.gif" title="' . get_lang('_delete_learnpath_module') . '" />'; + $return .= '</a>' . "\n"; + + $return .= '</td>'; + } + + $return .= '</tr>'; + } + + if (count($arrLP) == 0) { + $return .= "\t" . '<tr>' . "\n"; + $return .= "\t\t" . '<td colspan="4">' . get_lang('NoItemsInLp') . '</td>' . "\n"; + $return .= "\t" . '</tr>' . "\n"; + } + $return .= '</table>' . "\n"; + // We need to close the form when we are updating the mp3 files. + + if ($_GET['updateaudio'] == 'true') { + $return .= '<div style="margin:40px 0; float:right;"><button class="save" type="submit" name="save_audio" id="save_audio">' . get_lang('SaveAudioAndOrganization') . '</button></div>'; // TODO: What kind of language variable is this? + } + + // We need to close the form when we are updating the mp3 files. + if ($_GET['updateaudio'] == 'true' && count($arrLP) != 0) { + $return .= '</form>'; + } + + return $return; + } + + /** + * This function builds the action menu + * @return void + */ + public function build_action_menu() { + $gradebook = Security :: remove_XSS($_GET['gradebook']); + echo '<div class="actions">'; + //echo '<span>'.Display::return_icon('learnpath_build.gif').' '.get_lang('Build').'</span>'; + echo '<span>' . Display :: return_icon('learnpath_build_na.gif', get_lang('Build')) . ' <b>' . get_lang('Build') . '</b></span>'; + echo '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=admin_view&lp_id=' . $_SESSION['oLP']->lp_id . '" title="' . get_lang('BasicOverview') . '">' . Display :: return_icon('learnpath_organize.gif', get_lang('BasicOverview')) . ' ' . get_lang('BasicOverview') . '</a>'; + echo '<a href="lp_controller.php?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=view&lp_id=' . $_SESSION['oLP']->lp_id . '">' . Display :: return_icon('learnpath_view.gif', get_lang('Display')) . ' ' . get_lang('Display') . '</a>'; + Display :: display_icon('i.gif'); + echo '<a href="lp_controller.php?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit&lp_id=' . Security :: remove_XSS($_GET['lp_id']) . '">' . Display :: return_icon('edit.gif', get_lang('CourseSettings')) . ' ' . get_lang('CourseSettings') . '</a>'; + echo '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=admin_view&lp_id=' . Security :: remove_XSS($_GET['lp_id']) . '&updateaudio=true">' . Display :: return_icon('audio.gif', get_lang('UpdateAllAudioFragments')) . ' ' . get_lang('UpdateAllAudioFragments') . '</a>'; + echo '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=add_item&type=chapter&lp_id=' . $_SESSION['oLP']->lp_id . '" title="' . get_lang('NewChapter') . '">' . Display :: return_icon('lp_dokeos_chapter_add.gif', get_lang('NewChapter')) . ' ' . get_lang('NewChapter') . '</a>'; + echo '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&gradebook=' . $gradebook . '&action=add_item&type=step&lp_id=' . $_SESSION['oLP']->lp_id . '" title="' . get_lang('NewStep') . '">' . Display :: return_icon('new_test.gif', get_lang('NewStep')) . ' ' . get_lang('NewStep') . '</a>'; + echo '</div>'; + } + + /** + * This functions builds the LP tree based on data from the database. + * @return string + * @uses dtree.js :: necessary javascript for building this tree + */ + public function build_tree() { + $platform_charset = api_get_system_encoding(); + $return = "<script type=\"text/javascript\">\n"; + $return .= "\tm = new dTree('m');\n\n"; + $return .= "\tm.config.folderLinks = true;\n"; + $return .= "\tm.config.useCookies = true;\n"; + $return .= "\tm.config.useIcons = true;\n"; + $return .= "\tm.config.useLines = true;\n"; + $return .= "\tm.config.useSelection = true;\n"; + $return .= "\tm.config.useStatustext = false;\n\n"; + + $menu = 0; + $parent = ''; + $return .= "\tm.add(" . $menu . ", -1, '" . addslashes(Security::remove_XSS(($this->name))) . "');\n"; + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + $sql = " SELECT id, title, description, item_type, path, parent_item_id, previous_item_id, next_item_id, max_score, min_score, mastery_score, display_order + FROM " . $tbl_lp_item . " + WHERE lp_id = " . Database :: escape_string($this->lp_id); + $result = Database::query($sql); + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $row['title'] = Security :: remove_XSS(api_convert_encoding($row['title'], $platform_charset, $this->encoding)); + $row['description'] = Security :: remove_XSS(api_convert_encoding($row['description'], $platform_charset, $this->encoding)); + + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'display_order' => $row['display_order'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + $title = ''; + for ($i = 0; $i < count($arrLP); $i++) { + $title = addslashes($arrLP[$i]['title']); + $menu_page = api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=view_item&id=' . $arrLP[$i]['id'] . '&lp_id=' . $_SESSION['oLP']->lp_id; + $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']); + if (file_exists('../img/lp_' . $icon_name . '.png')) { + $return .= "\tm.add(" . $arrLP[$i]['id'] . ", " . $arrLP[$i]['parent_item_id'] . ", '" . $title . "', '" . $menu_page . "', '', '', '../img/lp_" . $icon_name . ".png', '../img/lp_" . $icon_name . ".png');\n"; + } else + if (file_exists('../img/lp_' . $icon_name . '.gif')) { + $return .= "\tm.add(" . $arrLP[$i]['id'] . ", " . $arrLP[$i]['parent_item_id'] . ", '" . $title . "', '" . $menu_page . "', '', '', '../img/lp_" . $icon_name . ".gif', '../img/lp_" . $icon_name . ".gif');\n"; + } else { + $return .= "\tm.add(" . $arrLP[$i]['id'] . ", " . $arrLP[$i]['parent_item_id'] . ", '" . $title . "', '" . $menu_page . "', '', '', '../img/folder_document.gif', '../img/folder_document.gif');\n"; + } + if ($menu < $arrLP[$i]['id']) + $menu = $arrLP[$i]['id']; + } + + $return .= "\n\tdocument.write(m);\n"; + $return .= "\t if(!m.selectedNode) m.s(1);"; + $return .= "</script>\n"; + + return $return; + } + + /** + * Create a new document //still needs some finetuning + * @param array $_course + * @return string + */ + public function create_document($_course) { + global $charset; + $dir = isset ($_GET['dir']) ? $_GET['dir'] : $_POST['dir']; // Please, do not modify this dirname formatting. + if (strstr($dir, '..')) + $dir = '/'; + if ($dir[0] == '.') + $dir = substr($dir, 1); + if ($dir[0] != '/') + $dir = '/' . $dir; + if ($dir[strlen($dir) - 1] != '/') + $dir .= '/'; + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $dir; + if (!is_dir($filepath)) { + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; + $dir = '/'; + } + // stripslashes() before calling replace_dangerous_char() because $_POST['title'] + // is already escaped twice when it gets here. + $title = replace_dangerous_char(stripslashes($_POST['title'])); + $title = disable_dangerous_file($title); + + $filename = $title; + $content = $_POST['content_lp']; + + $tmp_filename = $filename; + + $i = 0; + while (file_exists($filepath . $tmp_filename . '.html')) + $tmp_filename = $filename . '_' . ++ $i; + $filename = $tmp_filename . '.html'; + $content = stripslashes(text_filter($content)); + $content = str_replace(api_get_path(WEB_COURSE_PATH), api_get_path(REL_PATH) . 'courses/', $content); + // Change the path of mp3 to absolute. + // The first regexp deals with ../../../ urls. + $content = preg_replace("|(flashvars=\"file=)(\.+/)+|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/', $content); + // The second regexp deals with audio/ urls. + $content = preg_replace("|(flashvars=\"file=)([^/]+)/|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/$2/', $content); + // For flv player: To prevent edition problem with firefox, we have to use a strange tip (don't blame me please). + $content = str_replace('</body>', '<style type="text/css">body{}</style></body>', $content); + + if (!file_exists($filepath . $filename)) { + if ($fp = @ fopen($filepath . $filename, 'w')) { + fputs($fp, $content); + fclose($fp); + + $file_size = filesize($filepath . $filename); + $save_file_path = $dir . $filename; + + $document_id = add_document($_course, $save_file_path, 'file', $file_size, $filename . '.html'); + + if ($document_id) { + api_item_property_update($_course, TOOL_DOCUMENT, $document_id, 'DocumentAdded', api_get_user_id(), null, null, null, null, api_get_session_id()); + + // Update parent folders. + //item_property_update_on_folder($_course, $_GET['dir'], $_user['user_id']); + + $new_comment = (isset($_POST['comment'])) ? trim($_POST['comment']) : ''; + $new_title = (isset($_POST['title'])) ? trim($_POST['title']) : ''; + + if ($new_comment || $new_title) { + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $ct = ''; + + if ($new_comment) + $ct .= ", comment='" . $new_comment . "'"; + + if ($new_title) + $ct .= ", title='" . Database :: escape_string(htmlspecialchars($new_title, ENT_QUOTES, $charset)) . ".html '"; + + $sql_update = " + UPDATE " . $tbl_doc . " + SET " . substr($ct, 1) . " + WHERE id = " . $document_id; + Database::query($sql_update); + } + } + + return $document_id; + } + } + } + + /** + * Edit a document based on $_POST and $_GET parameters 'dir' and 'path' + * @param array $_course array + * @return void + */ + public function edit_document($_course) { + global $_configuration; + $dir = isset ($_GET['dir']) ? $_GET['dir'] : $_POST['dir']; // Please, do not modify this dirname formatting. + + if (strstr($dir, '..')) + $dir = '/'; + + if ($dir[0] == '.') + $dir = substr($dir, 1); + + if ($dir[0] != '/') + $dir = '/' . $dir; + + if ($dir[strlen($dir) - 1] != '/') + $dir .= '/'; + + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $dir; + + if (!is_dir($filepath)) { + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; + $dir = '/'; + } + + $table_doc = Database :: get_course_table(TABLE_DOCUMENT); + if (isset($_POST['path']) && !empty($_POST['path'])) { + $sql = "SELECT path + FROM " . $table_doc . " + WHERE id = " . Database::escape_string($_POST['path']); + $res = Database::query($sql); + $row = Database :: fetch_array($res); + $content = stripslashes($_POST['content_lp']); + $file = $filepath . $row['path']; + + if ($fp = @ fopen($file, 'w')) { + $content = text_filter($content); + $content = str_replace(api_get_path(WEB_COURSE_PATH), $_configuration['url_append'] . '/courses/', $content); + + // Change the path of mp3 to absolute. + // The first regexp deals with ../../../ urls. + $content = preg_replace("|(flashvars=\"file=)(\.+/)+|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/', $content); + // The second regexp deals with audio/ urls. + $content = preg_replace("|(flashvars=\"file=)([^/]+)/|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/$2/', $content); + + fputs($fp, $content); + fclose($fp); + } + } + } + + /** + * Displays the selected item, with a panel for manipulating the item + * @param int $item_id + * @param string $msg + * @return string + */ + public function display_item($item_id, $iframe = true, $msg = '') { + global $_course; // It will disappear. + $return = ''; + if (is_numeric($item_id)) { + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $sql = "SELECT lp.* FROM " . $tbl_lp_item . " as lp + WHERE lp.id = " . Database :: escape_string($item_id); + $result = Database::query($sql); + while ($row = Database :: fetch_array($result)) { + $_SESSION['parent_item_id'] = ($row['item_type'] == 'dokeos_chapter' || $row['item_type'] == 'dokeos_module' || $row['item_type'] == 'dir') ? $item_id : 0; + + // Prevents wrong parent selection for document, see Bug#1251. + if ($row['item_type'] != 'dokeos_chapter' || $row['item_type'] != 'dokeos_module') { + $_SESSION['parent_item_id'] = $row['parent_item_id']; + } + + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= '<div style="padding:10px;">'; + if ($msg != '') + $return .= $msg; + $row['title'] = Security :: remove_XSS(api_convert_encoding($row['title'], api_get_system_encoding(), $this->encoding)); + //$row['title'] = Security::remove_XSS(api_convert_encoding($row['title'], $this->encoding, api_get_system_encoding())); + $return .= '<p class="lp_title">' . $row['title'] . '</p>'; + //$return .= '<p class="lp_text">' . ((trim($row['description']) == '') ? 'no description' : stripslashes($row['description'])) . '</p>'; + //$return .= '<hr />'; + if ($row['item_type'] == TOOL_DOCUMENT) { + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $sql_doc = "SELECT path FROM " . $tbl_doc . " WHERE id = " . Database :: escape_string($row['path']); + $result = Database::query($sql_doc); + $path_file = Database :: result($result, 0, 0); + $path_parts = pathinfo($path_file); + // TODO: Correct the following naive comparisons, also, htm extension is missing. + if (in_array($path_parts['extension'], array ( + 'html', + 'txt', + 'png', + 'jpg', + 'JPG', + 'jpeg', + 'JPEG', + 'gif', + 'swf' + ))) { + $return .= $this->display_document($row['path'], true, true); + } + } + $return .= '</div>'; + } + } + return $return; + } + + /** + * Shows the needed forms for editing a specific item + * @param int $item_id + * @return string + */ + public function display_edit_item($item_id) { + global $_course; // It will disappear. + $return = ''; + if (is_numeric($item_id)) { + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * + FROM " . $tbl_lp_item . " + WHERE id = " . Database :: escape_string($item_id); + $res = Database::query($sql); + $row = Database :: fetch_array($res); + switch ($row['item_type']) { + case 'dokeos_chapter' : + case 'dir' : + case 'asset' : + case 'sco' : + if (isset ($_GET['view']) && $_GET['view'] == 'build') { + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_item_form($row['item_type'], get_lang('EditCurrentChapter') . ' :', 'edit', $item_id, $row); + } else { + $return .= $this->display_item_small_form($row['item_type'], get_lang('EditCurrentChapter') . ' :', $row); + } + break; + case TOOL_DOCUMENT : + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $sql_step = " SELECT lp.*, doc.path as dir + FROM " . $tbl_lp_item . " as lp + LEFT JOIN " . $tbl_doc . " as doc ON doc.id = lp.path + WHERE lp.id = " . Database :: escape_string($item_id); + $res_step = Database::query($sql_step); + $row_step = Database :: fetch_array($res_step); + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_document_form('edit', $item_id, $row_step); + break; + case TOOL_LINK : + $link_id = (string) $row['path']; + if (ctype_digit($link_id)) { + $tbl_link = Database :: get_course_table(TABLE_LINK); + $sql_select = 'SELECT url FROM ' . $tbl_link . ' WHERE id = ' . Database :: escape_string($link_id); + $res_link = Database::query($sql_select); + $row_link = Database :: fetch_array($res_link); + if (is_array($row_link)) { + $row['url'] = $row_link['url']; + } + } + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_link_form('edit', $item_id, $row); + break; + case 'dokeos_module' : + if (isset ($_GET['view']) && $_GET['view'] == 'build') { + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_item_form($row['item_type'], get_lang('EditCurrentModule') . ' :', 'edit', $item_id, $row); + } else { + $return .= $this->display_item_small_form($row['item_type'], get_lang('EditCurrentModule') . ' :', $row); + } + break; + case TOOL_QUIZ : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_quiz_form('edit', $item_id, $row); + break; + case TOOL_HOTPOTATOES : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_hotpotatoes_form('edit', $item_id, $row); + break; + case TOOL_STUDENTPUBLICATION : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_student_publication_form('edit', $item_id, $row); + break; + case TOOL_FORUM : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_forum_form('edit', $item_id, $row); + break; + case TOOL_THREAD : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_thread_form('edit', $item_id, $row); + break; + } + } + + return $return; + } + + /** + * Function that displays a list with al the resources that could be added to the learning path + * @return string + */ + public function display_resources() { + global $_course; // TODO: Don't use globals. + + $return .= '<div class="sectiontitle">' . get_lang('CreateNewStep') . '</div>'; + $return .= '<div class="sectioncomment"><a href="' . api_get_self() . '?cidReq=' . $_GET['cidReq'] . '&action=add_item&type=' . TOOL_DOCUMENT . '&lp_id=' . $_SESSION['oLP']->lp_id . '">' . '<img title="Nuevo documento" src="../img/new_doc.gif" alt="Nuevo documento"/> ' . get_lang('NewDocument') . '</a></div>'; // TODO: A hardcoded Spanish text is here. + $return .= '<div class="sectiontitle">' . get_lang('UseAnExistingResource') . '</div>'; + $return .= '<div class="sectioncomment">'; + + /* Get all the docs. */ + $return .= $this->get_documents(); + + /* Get all the exercises. */ + $return .= $this->get_exercises(); + + /* Get all the links. */ + $return .= $this->get_links(); + + /* Get al the student publications. */ + $return .= $this->get_student_publications(); + + /* Get al the forums. */ + $return .= $this->get_forums(); + + $return .= '</div>'; + + return $return; + } + + /** + * Returns the extension of a document + * @param string filename + * @return string Extension (part after the last dot) + */ + public function get_extension($filename) { + $explode = explode('.', $filename); + return $explode[count($explode) - 1]; + } + + /** + * Displays a document by id + * + * @param unknown_type $id + * @return unknown + */ + public function display_document($id, $show_title = false, $iframe = true, $edit_link = false) { + global $_course; // It is temporary. + $return = ''; + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $sql_doc = "SELECT * FROM " . $tbl_doc . " + WHERE id = " . $id; + $res_doc = Database::query($sql_doc); + $row_doc = Database :: fetch_array($res_doc); + + //if ($show_title) + //$return .= '<p class="lp_title">' . $row_doc['title'] . ($edit_link ? ' [ <a href="' .api_get_self(). '?cidReq=' . $_GET['cidReq'] . '&action=add_item&type=' . TOOL_DOCUMENT . '&file=' . $_GET['file'] . '&edit=true&lp_id=' . $_GET['lp_id'] . '">Edit this document</a> ]' : '') . '</p>'; + + // TODO: Add a path filter. + if ($iframe) { + $return .= '<iframe id="learnpath_preview_frame" frameborder="0" height="400" width="100%" scrolling="auto" src="' . api_get_path(WEB_COURSE_PATH) . $_course['path'] . '/document' . str_replace('%2F', '/', urlencode($row_doc['path'])) . '?' . api_get_cidreq() . '"></iframe>'; + } else { + $return .= file_get_contents(api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $row_doc['path']); + } + + return $return; + } + + /** + * Return HTML form to add/edit a quiz + * @param string Action (add/edit) + * @param integer Item ID if already exists + * @param mixed Extra information (quiz ID if integer) + * @return string HTML form + */ + public function display_quiz_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_quiz = Database :: get_course_table(TABLE_QUIZ_TEST); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + $item_description = stripslashes($extra_info['description']); + } + elseif (is_numeric($extra_info)) { + $sql_quiz = " + SELECT + title, + description + FROM " . $tbl_quiz . " + WHERE id = " . $extra_info; + + $result = Database::query($sql_quiz); + $row = Database :: fetch_array($result); + $item_title = $row['title']; + $item_description = $row['description']; + } else { + $item_title = ''; + $item_description = ''; + } + + $return = ' <div class="row"> + <div class="form_header">'; + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + $arrLP = array (); + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'], + 'max_time_allowed' => $row['max_time_allowed'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($action == 'add') + $return .= get_lang('CreateTheExercise') . ' :' . "\n"; + elseif ($action == 'move') $return .= get_lang('MoveTheCurrentExercise') . ' :' . "\n"; + else + $return .= get_lang('EditCurrentExecice') . ' :' . "\n"; + + if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { + $return .= Display :: return_warning_message('<p class="lp_title">' . get_lang('Warning') . ' !</p>' . get_lang('WarningEditingDocument')); + } + $return .= ' </div> + </div>'; + $return .= '<div class="sectioncomment">'; + + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table class="lp_form">' . "\n"; + + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" name="title" size="44" type="text" class="learnpath_item_form" value="' . $item_title . '" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + } + + $return .= "\t\t" . '<tr>' . "\n"; + + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + + $return .= "\t\t\t\t" . '<select id="idParent" style="width:100%;" name="parent" onchange="javascript: load_cbo(this.value);" class="learnpath_item_form" size="1">'; + + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + + $arrHide = array ( + $id + ); + //$parent_item_id = $_SESSION['parent_item_id']; + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + if (is_array($arrLP)) { + reset($arrLP); + } + + $return .= '</select>'; + $return .= '</td>'; + $return .= '</tr>'; + $return .= '<tr>'; + + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + + $return .= "\t\t\t\t" . '<select class="learnpath_item_form" style="width:100%;" id="idPosition" name="previous" size="1">'; + + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + if ($action != 'move') { + $id_prerequisite = 0; + if (is_array($arrLP)) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + $arrHide = array (); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + } + } + /*// Commented the prerequisites, only visible in edit (exercise). + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPrerequisites">'.get_lang('Prerequisites').'</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><select name="prerequisites" id="prerequisites" class="learnpath_item_form"><option value="0">'.get_lang('NoPrerequisites').'</option>'; + + foreach($arrHide as $key => $value){ + if($key==$s_selected_position && $action == 'add'){ + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + elseif($key==$id_prerequisite && $action == 'edit'){ + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + else{ + $return .= '<option value="'.$key.'">'.$value['value'].'</option>'; + } + } + + $return .= "</select></td>"; + */ + $return .= "\t\t" . '</tr>' . "\n"; + /*$return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="maxTimeAllowed">' . get_lang('MaxTimeAllowed') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input name="maxTimeAllowed" style="width:98%;" id="maxTimeAllowed" value="' . $extra_info['max_time_allowed'] . '" /></td>'; + + // Remove temporarily the test description. + //$return .= "\t\t\t" . '<td class="label"><label for="idDescription">'.get_lang('Description').' :</label></td>' . "\n"; + //$return .= "\t\t\t" . '<td class="input"><textarea id="idDescription" name="description" rows="4">' . $item_description . '</textarea></td>' . "\n"; + + $return .= "\t\t" . '</tr>' . "\n"; */ + } + + $return .= "\t\t" . '<tr>' . "\n"; + if ($action == 'add') { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('AddExercise') . '</button></td>'; + } else { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('EditCurrentExecice') . '</button></td>'; + } + + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t" . '</table>' . "\n"; + + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } + elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_QUIZ . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + + $return .= '</form>' . "\n"; + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Addition of Hotpotatoes tests + * @param string Action + * @param integer Internal ID of the item + * @param mixed Extra information - can be an array with title and description indexes + * @return string HTML structure to display the hotpotatoes addition formular + */ + public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + $uploadPath = DIR_HOTPOTATOES; //defined in main_api + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + $item_description = stripslashes($extra_info['description']); + } + elseif (is_numeric($extra_info)) { + $TBL_DOCUMENT = Database :: get_course_table(TABLE_DOCUMENT); + + $sql_hot = "SELECT * FROM " . $TBL_DOCUMENT . " + WHERE path LIKE '" . $uploadPath . "/%/%htm%' + AND id = " . (int) $extra_info . " ORDER BY id ASC"; + + $res_hot = Database::query($sql_hot); + + $row = Database :: fetch_array($res_hot); + + $item_title = $row['title']; + $item_description = $row['description']; + + if (!empty ($row['comment'])) { + $item_title = $row['comment']; + } + } else { + $item_title = ''; + $item_description = ''; + } + + $item_title = api_convert_encoding($item_title, $charset, $this->encoding); + $item_description = api_convert_encoding($item_description, $charset, $this->encoding); + + $return = '<div style="margin:3px 12px;">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'], + 'max_time_allowed' => $row['max_time_allowed'] + ); + } + + $this->tree_array($arrLP); + + $arrLP = $this->arrMenu; + + unset ($this->arrMenu); + + if ($action == 'add') + $return .= '<p class="lp_title">' . get_lang('CreateTheExercise') . ' :</p>' . "\n"; + elseif ($action == 'move') $return .= '<p class="lp_title">' . get_lang('MoveTheCurrentExercise') . ' :</p>' . "\n"; + else + $return .= '<p class="lp_title">' . get_lang('EditCurrentExecice') . ' :</p>' . "\n"; + if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { + $return .= Display :: return_warning_message('<p class="lp_title">' . get_lang('Warning') . ' !</p>' . get_lang('WarningEditingDocument')); + } + + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table cellpadding="0" cellspacing="0" class="lp_form">' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idParent" name="parent" onchange="javascript: load_cbo(this.value);" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + $arrHide = array ( + $id + ); + + if (count($arrLP) > 0) { + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + + reset($arrLP); + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idPosition" name="previous" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" name="title" type="text" value="' . $item_title . '" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $id_prerequisite = 0; + if (is_array($arrLP) && count($arrLP) > 0) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + + $arrHide = array (); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + + } + } + } + + $return .= "\t\t" . '<tr>' . "\n"; + + //$return .= "\t\t\t" . '<td class="label"><label for="idPrerequisites">'.get_lang('Prerequisites').' :</label></td>' . "\n"; + //$return .= "\t\t\t" . '<td class="input"><select name="prerequisites" id="prerequisites"><option value="0">'.get_lang('NoPrerequisites').'</option>'; + + foreach ($arrHide as $key => $value) { + if ($key == $s_selected_position && $action == 'add') { + $return .= '<option value="' . $key . '" selected="selected">' . $value['value'] . '</option>'; + } + elseif ($key == $id_prerequisite && $action == 'edit') { + $return .= '<option value="' . $key . '" selected="selected">' . $value['value'] . '</option>'; + } else { + $return .= '<option value="' . $key . '">' . $value['value'] . '</option>'; + } + } + + $return .= "</select></td>"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + } + + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td>  </td><td><button class="save" name="submit_button" action="edit" type="submit">' . get_lang('SaveHotpotatoes') . '</button></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t" . '</table>' . "\n"; + + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } + elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_HOTPOTATOES . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + + $return .= '</form>' . "\n"; + + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Return the form to display the forum edit/add option + * @param string Action (add/edit) + * @param integer ID of the lp_item if already exists + * @param mixed Forum ID or title + * @return string HTML form + */ + public function display_forum_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_forum = Database :: get_course_table(TABLE_FORUM); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + } + elseif (is_numeric($extra_info)) { + $sql_forum = " + SELECT + forum_title as title, forum_comment as comment + FROM " . $tbl_forum . " + WHERE forum_id = " . $extra_info; + + $result = Database::query($sql_forum); + $row = Database :: fetch_array($result); + + $item_title = $row['title']; + $item_description = $row['comment']; + } else { + $item_title = ''; + $item_description = ''; + } + + $item_title = api_convert_encoding($item_title, $charset, $this->encoding); + $item_description = api_convert_encoding($item_description, $charset, $this->encoding); + + $return = ' <div class="row"> + <div class="form_header">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($action == 'add') + $return .= get_lang('CreateTheForum') . ' :' . "\n"; + elseif ($action == 'move') $return .= get_lang('MoveTheCurrentForum') . ' :' . "\n"; + else + $return .= get_lang('EditCurrentForum') . ' :' . "\n"; + + $return .= ' </div> + </div>'; + $return .= '<div class="sectioncomment">'; + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table class="lp_form">' . "\n"; + + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" size="44" name="title" type="text" value="' . $item_title . '" class="learnpath_item_form" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + } + + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idParent" style="width:100%;" name="parent" onchange="javascript: load_cbo(this.value);" class="learnpath_item_form" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + $arrHide = array ( + $id + ); + + //$parent_item_id = $_SESSION['parent_item_id']; + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + if (is_array($arrLP)) { + reset($arrLP); + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idPosition" name="previous" style="width:100%;" size="1" class="learnpath_item_form">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $id_prerequisite = 0; + if (is_array($arrLP)) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + + $arrHide = array (); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + } + } + $return .= "\t\t" . '</tr>' . "\n"; + } + $return .= "\t\t" . '<tr>' . "\n"; + + if ($action == 'add') { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit"> ' . get_lang('AddForumToCourse') . ' </button></td>'; + } else { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit"> ' . get_lang('EditCurrentForum') . ' </button></td>'; + } + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t" . '</table>' . "\n"; + + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } + elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_FORUM . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + $return .= '</form>' . "\n"; + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Return HTML form to add/edit forum threads + * @param string Action (add/edit) + * @param integer Item ID if already exists in learning path + * @param mixed Extra information (thread ID if integer) + * @return string HTML form + */ + public function display_thread_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + echo ' + <style> + + div.row div.label { + width:110px; + } + + div.row div.formw { + width: 82%; + } + </style>'; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_forum = Database :: get_course_table(TABLE_FORUM_THREAD); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + } + elseif (is_numeric($extra_info)) { + $sql_forum = " + SELECT + thread_title as title + FROM " . $tbl_forum . " + WHERE thread_id = " . $extra_info; + + $result = Database::query($sql_forum); + $row = Database :: fetch_array($result); + + $item_title = $row['title']; + $item_description = ''; + } else { + $item_title = ''; + $item_description = ''; + } + $item_title = api_convert_encoding($item_title, $charset, $this->encoding); + $item_description = api_convert_encoding($item_description, $charset, $this->encoding); + + $return = '<div style="margin:3px 12px;">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'] + ); + } + + $this->tree_array($arrLP); + + $arrLP = $this->arrMenu; + + unset ($this->arrMenu); + + if ($action == 'add') + $return .= '<p class="lp_title">' . get_lang('CreateTheForum') . ' :</p>' . "\n"; + elseif ($action == 'move') $return .= '<p class="lp_title">' . get_lang('MoveTheCurrentForum') . ' :</p>' . "\n"; + else + $return .= '<p class="lp_title">' . get_lang('EditCurrentForum') . ' :</p>' . "\n"; + + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table cellpadding="0" cellspacing="0" class="lp_form">' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idParent" name="parent" onchange="javascript: load_cbo(this.value);" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + $arrHide = array ( + $id + ); + + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + if ($arrLP != null) { + reset($arrLP); + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idPosition" name="previous" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" name="title" type="text" value="' . $item_title . '" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + + $id_prerequisite = 0; + if ($arrLP != null) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + + $arrHide = array(); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + + } + } + + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPrerequisites">' . get_lang('Prerequisites') . ' :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><select name="prerequisites" id="prerequisites"><option value="0">' . get_lang('NoPrerequisites') . '</option>'; + + foreach ($arrHide as $key => $value) { + if ($key == $s_selected_position && $action == 'add') { + $return .= '<option value="' . $key . '" selected="selected">' . $value['value'] . '</option>'; + } + elseif ($key == $id_prerequisite && $action == 'edit') { + $return .= '<option value="' . $key . '" selected="selected">' . $value['value'] . '</option>'; + } else { + $return .= '<option value="' . $key . '">' . $value['value'] . '</option>'; + } + } + + $return .= "</select></td>"; + $return .= "\t\t" . '</tr>' . "\n"; + + } + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td colspan="2"> + <button class="save" name="submit_button" type="submit" value="'.get_lang('Ok').'" />'.get_lang('Ok').'</button></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t" . '</table>' . "\n"; + + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } + elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_THREAD . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + $return .= '</form>' . "\n"; + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Return the HTML form to display an item (generally a section/module item) + * @param string Item type (module/dokeos_module) + * @param string Title (optional, only when creating) + * @param string Action ('add'/'edit') + * @param integer lp_item ID + * @param mixed Extra info + * @return string HTML form + */ + public function display_item_form($item_type, $title = '', $action = 'add', $id = 0, $extra_info = 'new') { + global $_course; + global $charset; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + if ($id != 0 && is_array($extra_info)) { + $item_title = $extra_info['title']; + $item_description = $extra_info['description']; + $item_path = api_get_path(WEB_COURSE_PATH) . $_course['path'] . '/scorm/' . $this->path . '/' . stripslashes($extra_info['path']); + } else { + $item_title = ''; + $item_description = ''; + } + + $return = ' <div class="row"> + <div class="form_header">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE lp_id = " . $this->lp_id . " AND id != " . $id . " "; + + if ($item_type == 'module') + $sql .= " AND parent_item_id = 0"; + + $result = Database::query($sql); + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'], + 'display_order' => $row['display_order'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + $return .= $title . "\n"; + $return .= ' </div> + </div>'; + + require_once api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'; + $form = new FormValidator('form', 'POST', api_get_self() . '?' . $_SERVER['QUERY_STRING']); + //$defaults['title'] = api_convert_encoding($item_title, $charset, $this->encoding); + $defaults['title'] = api_html_entity_decode(api_convert_encoding($item_title, $charset, $this->encoding), ENT_QUOTES, $charset); + $defaults['description'] = api_convert_encoding($item_description, $charset, $this->encoding); + + $form->addElement('html', $return); + + //$arrHide = array($id); + $arrHide[0]['value'] = Security :: remove_XSS(api_convert_encoding($this->name, api_get_system_encoding(), $_SESSION['oLP']->encoding)); + $arrHide[0]['padding'] = 3; + $charset = api_get_system_encoding(); + if ($item_type != 'module' && $item_type != 'dokeos_module') { + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; + if ($parent == $arrLP[$i]['id']) { + $s_selected_parent = $arrHide[$arrLP[$i]['id']]; + } + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') { + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; + if ($parent == $arrLP[$i]['id']) { + $s_selected_parent = $arrHide[$arrLP[$i]['id']]; + } + } + } + } + + if ($action != 'move') { + $form->addElement('text', 'title', get_lang('Title'), 'id="idTitle" class="learnpath_chapter_form" size="40%"'); + $form->applyFilter('title', 'html_filter'); + $form->addRule('title', '<div class="required">' . get_lang('ThisFieldIsRequired'), 'required'); + //$form->addElement('textarea', 'description', get_lang('Description').' :', 'id="idDescription"'); + } else { + $form->addElement('hidden', 'title'); + } + + $parent_select = & $form->addElement('select', 'parent', get_lang('Parent'), '', 'class="learnpath_chapter_form" style="width:37%;" id="Parent" onchange="javascript: load_cbo(this.value);"'); + + foreach ($arrHide as $key => $value) { + $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); + } + $parent_select->setSelected($s_selected_parent); + } + if (is_array($arrLP)) { + reset($arrLP); + } + + $arrHide = array (); + + // POSITION + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; + + $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"'; + } + } + + $position = & $form->addElement('select', 'previous', get_lang('Position'), '', 'id="idPosition" class="learnpath_chapter_form" style="width:37%;"'); + + $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:' . $value['padding'] . 'px;"'); + + foreach ($arrHide as $key => $value) { + $position->addOption($value['value'] . '"', $key, 'style="padding-left:' . $value['padding'] . 'px;"'); + } + + if (!empty ($s_selected_position)) { + $position->setSelected($s_selected_position); + } + + if (is_array($arrLP)) { + reset($arrLP); + } + + $form->addElement('style_submit_button', 'submit_button', get_lang('SaveSection'), 'class="save"'); + + if ($item_type == 'module' || $item_type == 'dokeos_module') { + $form->addElement('hidden', 'parent', '0'); + } + + $extension = pathinfo($item_path, PATHINFO_EXTENSION); + if (($item_type == 'asset' || $item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) { + if ($item_type == 'sco') { + $form->addElement('html', '<script type="text/javascript">alert("' . get_lang('WarningWhenEditingScorm') . '")</script>'); + } + $renderer = $form->defaultRenderer(); + $renderer->setElementTemplate('<br />      {label}<br />{element}', 'content_lp'); + $form->addElement('html_editor', 'content_lp', '', null, array('ToolbarSet' => 'LearningPathDocuments', 'Width' => '100%', 'Height' => '400', 'FullPage' => true)); + $defaults['content_lp'] = file_get_contents($item_path); + } + + $form->addElement('hidden', 'type', 'dokeos_' . $item_type); + $form->addElement('hidden', 'post_time', time()); + $form->setDefaults($defaults); + return $form->return_form(); + } + + /** + * Returns the form to update or create a document + * @param string Action (add/edit) + * @param integer ID of the lp_item (if already exists) + * @param mixed Integer if document ID, string if info ('new') + * @return string HTML form + */ + public function display_document_form($action = 'add', $id = 0, $extra_info = 'new') { + global $charset; + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + + $path_parts = pathinfo($extra_info['dir']); + $no_display_edit_textarea = false; + + //If action==edit document + //We don't display the document form if it's not an editable document (html or txt file) + if ($action == "edit") { + if (is_array($extra_info)) { + if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") { + $no_display_edit_textarea = true; + } + } + } + $no_display_add = false; + + // If action==add an existing document + // We don't display the document form if it's not an editable document (html or txt file). + if ($action == "add") { + if (is_numeric($extra_info)) { + $sql_doc = "SELECT path FROM " . $tbl_doc . "WHERE id = " . Database :: escape_string($extra_info); + $result = Database::query($sql_doc); + $path_file = Database :: result($result, 0, 0); + $path_parts = pathinfo($path_file); + if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") { + $no_display_add = true; + } + } + } + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + $item_description = stripslashes($extra_info['description']); + $item_terms = stripslashes($extra_info['terms']); + if (empty ($item_title)) { + $path_parts = pathinfo($extra_info['path']); + $item_title = stripslashes($path_parts['filename']); + } + } elseif (is_numeric($extra_info)) { + $sql_doc = "SELECT path, title FROM " . $tbl_doc . " + WHERE id = " . Database :: escape_string($extra_info); + + $result = Database::query($sql_doc); + $row = Database :: fetch_array($result); + + $explode = explode('.', $row['title']); + + if (count($explode) > 1) { + for ($i = 0; $i < count($explode) - 1; $i++) + $item_title .= $explode[$i]; + } else { + $item_title = $row['title']; + } + + $item_title = str_replace('_', ' ', $item_title); + if (empty ($item_title)) { + $path_parts = pathinfo($row['path']); + $item_title = stripslashes($path_parts['filename']); + } + + } else { + $item_title = ''; + $item_description = ''; + } + + $return = ' <div class="row"> + <div class="form_header">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = "SELECT * FROM " . $tbl_lp_item . " + WHERE lp_id = " . $this->lp_id; + $result = Database::query($sql); + $arrLP = array (); + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($action == 'add') { + $return .= get_lang('CreateTheDocument') . "\n"; + } + elseif ($action == 'move') { + $return .= get_lang('MoveTheCurrentDocument') . "\n"; + } else { + $return .= get_lang('EditTheCurrentDocument') . "\n"; + } + + $return .= '</div> + </div>'; + + if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { + $return .= Display :: return_warning_message('<strong>' . get_lang('Warning') . ' !</strong><br />' . get_lang('WarningEditingDocument'), false); + } + require_once api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'; + $form = new FormValidator('form', 'POST', api_get_self() . '?' . $_SERVER['QUERY_STRING'], '', 'enctype="multipart/form-data"'); + $defaults['title'] = Security :: remove_XSS(api_convert_encoding($item_title, api_get_system_encoding() ,$this->encoding)); + if (empty($item_title)) { + $defaults['title'] = Security::remove_XSS($item_title); + } + $defaults['description'] = api_convert_encoding($item_description, $charset, $this->encoding); + $form->addElement('html', $return); + if ($action != 'move') { + $form->addElement('text', 'title', get_lang('Title'), 'id="idTitle" class="learnpath_item_form" size=44%'); + $form->applyFilter('title', 'html_filter'); + } + + //$arrHide = array($id); + + $arrHide[0]['value'] = $this->name; + $arrHide[0]['padding'] = 3; + + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title']; + $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; + if ($parent == $arrLP[$i]['id']) { + $s_selected_parent = $arrHide[$arrLP[$i]['id']]; + } + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') { + $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title']; + $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; + if ($parent == $arrLP[$i]['id']) { + $s_selected_parent = $arrHide[$arrLP[$i]['id']]; + } + } + } + } + + $parent_select = & $form->addElement('select', 'parent', get_lang('Parent'), '', 'class="learnpath_item_form" style="width:40%;" onchange="javascript: load_cbo(this.value);"'); + $my_count=0; + foreach ($arrHide as $key => $value) { + if ($my_count!=0) { + // The LP name is also the first section and is not in the same charset like the other sections. + $value['value'] = Security :: remove_XSS(api_convert_encoding($value['value'], api_get_system_encoding(), $this->encoding)); + $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); + } else { + $value['value'] = Security :: remove_XSS($value['value']); + $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); + } + $my_count++; + } + + if (!empty ($id)) { + $parent_select->setSelected($parent); + } else { + $parent_item_id = $_SESSION['parent_item_id']; + $parent_select->setSelected($parent_item_id); + } + + + + if (is_array($arrLP)) { + reset($arrLP); + } + + $arrHide = array (); + + //POSITION + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; + + $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"'; + + } + } + + $position = & $form->addElement('select', 'previous', get_lang('Position'), '', 'id="idPosition" class="learnpath_item_form" style="width:40%;"'); + $position->addOption(get_lang('FirstPosition'), 0); + + foreach ($arrHide as $key => $value) { + $position->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); + } + $position->setSelected($s_selected_position); + if (is_array($arrLP)) { + reset($arrLP); + } + + if ($action != 'move') { + $id_prerequisite = 0; + if (is_array($arrLP)) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + // Commented the prerequisites, only visible in edit (new document). + //$select_prerequisites=$form->addElement('select', 'prerequisites', get_lang('Prerequisites'),null,'id="prerequisites" class="learnpath_item_form" style="width:263px;"'); + //$select_prerequisites->addOption(get_lang('NoPrerequisites'), 0); + + // Form element for uploading an mp3 file. + //$form->addElement('file','mp3',get_lang('UploadMp3audio'),'id="mp3" size="33"'); + //$form->addRule('file', 'The extension of the Song file should be *.mp3', 'filename', '/^.*\.mp3$/'); + + /* Code deprecated - moved to lp level (not lp-item). + if (api_get_setting('search_enabled') === 'true') { + //add terms field + $terms = $form->addElement('text', 'terms', get_lang('SearchFeatureTerms').' :', 'id="idTerms" class="learnpath_item_form"'); + $terms->setValue($item_terms); + } + */ + + $arrHide = array (); + + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; + + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + + } + } + + /* foreach ($arrHide as $key => $value) { + $select_prerequisites->addOption($value['value'], $key, 'style="padding-left:'.$value['padding'].'px;"'); + if ($key == $s_selected_position && $action == 'add') { + $select_prerequisites -> setSelected(0); + } + elseif ($key == $id_prerequisite && $action == 'edit') { + $select_prerequisites -> setSelected($id_prerequisite); + } + } + */ + if (!$no_display_add) { + if (($extra_info == 'new' || $extra_info['item_type'] == TOOL_DOCUMENT || $_GET['edit'] == 'true')) { + + if (isset ($_POST['content'])) + $content = stripslashes($_POST['content']); + elseif (is_array($extra_info)) { + //If it's an html document or a text file + if (!$no_display_edit_textarea) { + $content = $this->display_document($extra_info['path'], false, false); + } + } + elseif (is_numeric($extra_info)) $content = $this->display_document($extra_info, false, false); + else + $content = ''; + + if (!$no_display_edit_textarea) { + // We need to claculate here some specific settings for the online editor. + // The calculated settings work for documents in the Documents tool + // (on the root or in subfolders). + // For documents in native scorm packages it is unclear whether the + // online editor should be activated or not. + $relative_path = $extra_info['dir']; + if ($relative_path == 'n/') { + // A new document, it is in the root of the repository. + $relative_path = ''; + $relative_prefix = ''; + } else { + // The document already exists. Whe have to determine its relative path towards the repository root. + $relative_path = explode('/', $relative_path); + $cnt = count($relative_path) - 2; + if ($cnt < 0) { + $cnt = 0; + } + $relative_prefix = str_repeat('../', $cnt); + $relative_path = array_slice($relative_path, 1, $cnt); + $relative_path = implode('/', $relative_path); + if (strlen($relative_path) > 0) { + $relative_path = $relative_path . '/'; + } + } + $editor_config = array('ToolbarSet' => 'LearningPathDocuments', 'Width' => '100%', 'Height' => '700', 'FullPage' => true, + 'CreateDocumentDir' => $relative_prefix, + 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/', + 'BaseHref' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/' . $relative_path + ); + + if ($_GET['action'] == 'add_item') { + $class = 'add'; + $text = get_lang('LPCreateDocument'); + } else + if ($_GET['action'] == 'edit_item') { + $class = 'save'; + $text = get_lang('SaveDocument'); + } + + $form->addElement('style_submit_button', 'submit_button', $text, 'class="' . $class . '"'); + $renderer = $form->defaultRenderer(); + $renderer->setElementTemplate('<br />      {label}<br />{element}', 'content_lp'); + + $form->addElement('html', '<div style="margin:3px 12px">'); + $form->addElement('html_editor', 'content_lp', '', null, $editor_config); + $form->addElement('html', '</div>'); + $defaults['content_lp'] = $content; + } + } + + elseif (is_numeric($extra_info)) { + + $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'class="save"'); + + $return = $this->display_document($extra_info, true, true, true); + $form->addElement('html', $return); + } + } + + } + if ($action == 'move') { + $form->addElement('hidden', 'title', $item_title); + $form->addElement('hidden', 'description', $item_description); + } + if (is_numeric($extra_info)) { + $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'value="submit_button", class="save"'); + $form->addElement('hidden', 'path', $extra_info); + } + elseif (is_array($extra_info)) { + $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'class="save"'); + $form->addElement('hidden', 'path', $extra_info['path']); + } + + $form->addElement('hidden', 'type', TOOL_DOCUMENT); + $form->addElement('hidden', 'post_time', time()); + + $form->setDefaults($defaults); + + return $form->return_form(); + } + + /** + * Return HTML form to add/edit a link item + * @param string Action (add/edit) + * @param integer Item ID if exists + * @param mixed Extra info + * @return string HTML form + */ + public function display_link_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_link = Database :: get_course_table(TABLE_LINK); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + $item_description = stripslashes($extra_info['description']); + $item_url = stripslashes($extra_info['url']); + } + elseif (is_numeric($extra_info)) { + $sql_link = " + SELECT + title, + description, + url + FROM " . $tbl_link . " + WHERE id = " . $extra_info; + + $result = Database::query($sql_link); + $row = Database :: fetch_array($result); + + $item_title = $row['title']; + + $item_description = $row['description']; + $item_url = $row['url']; + } else { + $item_title = ''; + $item_description = ''; + $item_url = ''; + } + $item_title = api_convert_encoding($item_title, $charset, $this->encoding); + $item_description = api_convert_encoding($item_description, $charset, $this->encoding); + $return = ' <div class="row"> + <div class="form_header">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($action == 'add') + $return .= get_lang('CreateTheLink') . ' :' . "\n"; + elseif ($action == 'move') $return .= get_lang('MoveCurrentLink') . ' :' . "\n"; + else + $return .= get_lang('EditCurrentLink') . ' :' . "\n"; + + $return .= ' </div> + </div>'; + $return .= '<div class="sectioncomment">'; + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table>' . "\n"; + + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" name="title" size="44" type="text" value="' . $item_title . '" class="learnpath_item_form"/></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + } + + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idParent" style="width:100%;" name="parent" onchange="javascript: load_cbo(this.value);" class="learnpath_item_form" size="1">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + $arrHide = array ( + $id + ); + + $parent_item_id = $_SESSION['parent_item_id']; + + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent_item_id == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + + if (is_array($arrLP)) { + reset($arrLP); + } + + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idPosition" name="previous" style="width:100%;" size="1" class="learnpath_item_form">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent_item_id && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idURL">' . get_lang('Url') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input' . (is_numeric($extra_info) ? ' disabled="disabled"' : '') . ' id="idURL" name="url" style="width:99%;" type="text" value="' . $item_url . '" class="learnpath_item_form" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $id_prerequisite = 0; + if (is_array($arrLP)) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + + $arrHide = array (); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + + } + } + + /*// Commented the prerequisites, only visible in edit (link). + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPrerequisites">'.get_lang('Prerequisites').'</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><select name="prerequisites" id="prerequisites" class="learnpath_item_form"><option value="0">'.get_lang('NoPrerequisites').'</option>'; + + foreach($arrHide as $key => $value) { + if ($key == $s_selected_position && $action == 'add') { + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + elseif ($key == $id_prerequisite && $action == 'edit') { + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + else { + $return .= '<option value="'.$key.'">'.$value['value'].'</option>'; + } + } + + $return .= "</select></td>"; + */ + $return .= "\t\t" . '</tr>' . "\n"; + + } + + $return .= "\t\t" . '<tr>' . "\n"; + if ($action == 'add') { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('AddLinkToCourse') . '</button></td>'; + } else { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('EditCurrentLink') . '</button></td>'; + } + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t" . '</table>' . "\n"; + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } + elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_LINK . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + $return .= '</form>' . "\n"; + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Return HTML form to add/edit a student publication (work) + * @param string Action (add/edit) + * @param integer Item ID if already exists + * @param mixed Extra info (work ID if integer) + * @return string HTML form + */ + public function display_student_publication_form($action = 'add', $id = 0, $extra_info = '') { + global $charset; + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $tbl_publication = Database :: get_course_table(TABLE_STUDENT_PUBLICATION); + + if ($id != 0 && is_array($extra_info)) { + $item_title = stripslashes($extra_info['title']); + $item_description = stripslashes($extra_info['description']); + } + elseif (is_numeric($extra_info)) { + $sql_publication = " + SELECT + title, + description + FROM " . $tbl_publication . " + WHERE id = " . $extra_info; + + $result = Database::query($sql_publication); + $row = Database :: fetch_array($result); + + $item_title = $row['title']; + } else { + $item_title = get_lang('Student_publication'); + } + + $item_title = api_convert_encoding($item_title, $charset, $this->encoding); + $return = ' <div class="row"> + <div class="form_header">'; + + if ($id != 0 && is_array($extra_info)) + $parent = $extra_info['parent_item_id']; + else + $parent = 0; + + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + + $result = Database::query($sql); + + $arrLP = array (); + + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => $row['title'], + 'path' => $row['path'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'] + ); + } + + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + if ($action == 'add') + $return .= get_lang('Student_publication') . ' :' . "\n"; + elseif ($action == 'move') $return .= get_lang('MoveCurrentStudentPublication') . ' :' . "\n"; + else + $return .= get_lang('EditCurrentStudentPublication') . ' :' . "\n"; + $return .= ' </div> + </div>'; + $return .= '<div class="sectioncomment">'; + $return .= '<form method="POST">' . "\n"; + $return .= "\t" . '<table class="lp_form">' . "\n"; + if ($action != 'move') { + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">' . get_lang('Title') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input id="idTitle" name="title" size="44" type="text" value="' . $item_title . '" class="learnpath_item_form" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + } + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idParent">' . get_lang('Parent') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idParent" name="parent" style="width:100%;" onchange="javascript: load_cbo(this.value);" class="learnpath_item_form" size="1">'; + //$parent_item_id = $_SESSION['parent_item_id']; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . $this->name . '</option>'; + $arrHide = array ( + $id + ); + + for ($i = 0; $i < count($arrLP); $i++) { + if ($action != 'add') { + if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } else { + $arrHide[] = $arrLP[$i]['id']; + } + } else { + if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') + $return .= "\t\t\t\t\t" . '<option ' . (($parent == $arrLP[$i]['id']) ? 'selected="selected" ' : '') . 'style="padding-left:' . ($arrLP[$i]['depth'] * 10) . 'px;" value="' . $arrLP[$i]['id'] . '">' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '</option>'; + } + } + + if (is_array($arrLP)) { + reset($arrLP); + } + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPosition">' . get_lang('Position') . '</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input">' . "\n"; + $return .= "\t\t\t\t" . '<select id="idPosition" name="previous" style="width:100%;" size="1" class="learnpath_item_form">'; + $return .= "\t\t\t\t\t" . '<option class="top" value="0">' . get_lang('FirstPosition') . '</option>'; + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $selected = 'selected="selected" '; + elseif ($action == 'add') $selected = 'selected="selected" '; + else + $selected = ''; + + $return .= "\t\t\t\t\t" . '<option ' . $selected . 'value="' . $arrLP[$i]['id'] . '">' . get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"</option>'; + } + } + $return .= "\t\t\t\t" . '</select>'; + $return .= "\t\t\t" . '</td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + if ($action != 'move') { + $id_prerequisite = 0; + if (is_array($arrLP)) { + foreach ($arrLP as $key => $value) { + if ($value['id'] == $id) { + $id_prerequisite = $value['prerequisite']; + break; + } + } + } + $arrHide = array (); + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { + if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) + $s_selected_position = $arrLP[$i]['id']; + elseif ($action == 'add') $s_selected_position = 0; + $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); + + } + } + + // Commented the prerequisites, only visible in edit (work). + /* + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idPrerequisites">'.get_lang('Prerequisites').'</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><select name="prerequisites" id="prerequisites" class="learnpath_item_form"><option value="0">'.get_lang('NoPrerequisites').'</option>'; + + foreach($arrHide as $key => $value) { + if ($key == $s_selected_position && $action == 'add') { + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + elseif ($key == $id_prerequisite && $action == 'edit') { + $return .= '<option value="'.$key.'" selected="selected">'.$value['value'].'</option>'; + } + else { + $return .= '<option value="'.$key.'">'.$value['value'].'</option>'; + } + } + + $return .= "</select></td>"; + */ + $return .= "\t\t" . '</tr>' . "\n"; + + } + + $return .= "\t\t" . '<tr>' . "\n"; + if ($action == 'add') { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('AddAssignmentToCourse') . '</button></td>'; + } else { + $return .= '<td> </td><td><button class="save" name="submit_button" type="submit">' . get_lang('EditCurrentStudentPublication') . '</button></td>'; + } + $return .= "\t\t" . '</tr>' . "\n"; + + $return .= "\t" . '</table>' . "\n"; + + if ($action == 'move') { + $return .= "\t" . '<input name="title" type="hidden" value="' . $item_title . '" />' . "\n"; + $return .= "\t" . '<input name="description" type="hidden" value="' . $item_description . '" />' . "\n"; + } + + if (is_numeric($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info . '" />' . "\n"; + } elseif (is_array($extra_info)) { + $return .= "\t" . '<input name="path" type="hidden" value="' . $extra_info['path'] . '" />' . "\n"; + } + $return .= "\t" . '<input name="type" type="hidden" value="' . TOOL_STUDENTPUBLICATION . '" />' . "\n"; + $return .= "\t" . '<input name="post_time" type="hidden" value="' . time() . '" />' . "\n"; + $return .= '</form>' . "\n"; + $return .= '</div>' . "\n"; + return $return; + } + + /** + * Displays the menu for manipulating a step + * @return unknown + */ + public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT) { + global $charset, $_course; + $return = '<div class="actions">'; + + switch ($item_type) { + case 'dokeos_chapter' : + case 'chapter' : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateChapter'); + break; + + case 'dokeos_module' : + case 'module' : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateModule'); + break; + + case TOOL_DOCUMENT : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateDocument'); + break; + + case TOOL_LINK : + case 'link' : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateLink'); + break; + + case TOOL_QUIZ : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateQuiz'); + break; + + case TOOL_STUDENTPUBLICATION : + // Commented the message cause should not show it. + //$lang = get_lang('TitleManipulateStudentPublication'); + + break; + } + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql = " + SELECT + * + FROM " . $tbl_lp_item . " as lp + WHERE + lp.id = " . $item_id; + + $result = Database::query($sql); + + $row = Database::fetch_assoc($result); + $s_title = $row['title']; + $s_title = api_convert_encoding($s_title, $charset, $this->encoding); + + // we display an audio player if needed + if (!empty ($row['audio'])) { + $return .= '<div class="lp_mediaplayer" id="container"><a href="http://www.macromedia.com/go/getflashplayer">Get the Flash Player</a> to see this player.</div>'; + $return .= '<script type="text/javascript" src="../inc/lib/mediaplayer/swfobject.js"></script>'; + $return .= '<script type="text/javascript"> + var s1 = new SWFObject("../inc/lib/mediaplayer/player.swf","ply","250","20","9","#FFFFFF"); + s1.addParam("allowscriptaccess","always"); + s1.addParam("flashvars","file=../../courses/' . $_course['path'] . '/document/audio/' . $row['audio'] . '&autostart=true"); + s1.write("container"); + </script>'; + } + // Commented ":" for message in step. + //$return .= $lang.': '; + + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit_item&view=build&id=' . $item_id . '&lp_id=' . $this->lp_id . '&path_item=' . $row['path'] . '" title="' . get_lang('Edit') . '"><img align="absbottom" alt="Edit the current item" src="../img/edit.gif" title="' . get_lang('Edit') . '" /> ' . get_lang('Edit') . '</a>'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=move_item&view=build&id=' . $item_id . '&lp_id=' . $this->lp_id . '" title="Move the current item"><img align="absbottom" alt="Move the current item" src="../img/deplacer_fichier.gif" title="' . get_lang('Move') . '" /> ' . get_lang('Move') . '</a>'; + // Commented for now as prerequisites cannot be added to chapters. + if ($item_type != 'dokeos_chapter' && $item_type != 'chapter') { + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=edit_item_prereq&view=build&id=' . $item_id . '&lp_id=' . $this->lp_id . '" title="' . get_lang('Prerequisites') . '"><img align="absbottom" alt="' . get_lang('Prerequisites') . '" src="../img/right.gif" title="' . get_lang('Prerequisites') . '" /> ' . get_lang('Prerequisites') . '</a>'; + } + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=delete_item&view=build&id=' . $item_id . '&lp_id=' . $this->lp_id . '" onclick="return confirmation(\'' . addslashes($s_title) . '\');" title="Delete the current item"><img alt="Delete the current item" align="absbottom" src="../img/delete.gif" title="' . get_lang('Delete') . '" /> ' . get_lang('Delete') . '</a>'; + + //$return .= '<br /><br /><p class="lp_text">' . ((trim($s_description) == '') ? ''.get_lang('NoDescription').'' : stripslashes(nl2br($s_description))) . '</p>'; + + //$return.="</td><td valign='top'>"; + + // Get the audiorecorder. Use of ob_* functions since there are echos in the file. + ob_start(); + $audio_recorder_studentview = 'false'; + $audio_recorder_item_id = $item_id; + if (api_get_setting('service_visio', 'active') == 'true') { + include 'audiorecorder.inc.php'; + } + $return .= ob_get_contents(); + ob_end_clean(); + // End of audiorecorder include. + + $return .= '</div>'; + + return $return; + } + + /** + * Creates the javascript needed for filling up the checkboxes without page reload + * + * @return string + */ + public function create_js() { + $return = '<script language="javascript" type="text/javascript">' . "\n"; + $return .= 'function load_cbo(id){' . "\n"; + $return .= "var cbo = document.getElementById('idPosition');\n"; + $return .= 'for(var i = cbo.length - 1; i > 0; i--)'; + $return .= 'cbo.options[i] = null;' . "\n"; + $return .= 'var k=0;' . "\n"; + $return .= 'for(var i = 1; i <= child_name[id].length; i++){' . "\n"; + $return .= ' cbo.options[i] = new Option(child_name[id][i-1], child_value[id][i-1]);' . "\n"; + $return .= 'k=i;'; + $return .= '}' . "\n\n"; + $return .= 'if( typeof cbo != "undefined" ) {cbo.options[k].selected = true;}'; + $return .= '}'; + $return .= 'var child_name = new Array();' . "\n"; + $return .= 'var child_value = new Array();' . "\n\n"; + $return .= 'child_name[0] = new Array();' . "\n"; + $return .= 'child_value[0] = new Array();' . "\n\n"; + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_zero = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id . " AND + parent_item_id = 0 + ORDER BY display_order ASC"; + $res_zero = Database::query($sql_zero); + + global $charset; + $i = 0; + + while ($row_zero = Database :: fetch_array($res_zero)) { + $return .= 'child_name[0][' . $i . '] = "' . get_lang('After') . ' \"' . api_convert_encoding($row_zero['title'], $charset, $this->encoding) . '\"";' . "\n"; + $return .= 'child_value[0][' . $i++ . '] = "' . $row_zero['id'] . '";' . "\n"; + } + $return .= "\n"; + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id; + $res = Database::query($sql); + while ($row = Database :: fetch_array($res)) { + $sql_parent = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE parent_item_id = " . $row['id'] . " + ORDER BY display_order ASC"; + $res_parent = Database::query($sql_parent); + $i = 0; + $return .= 'child_name[' . $row['id'] . '] = new Array();' . "\n"; + $return .= 'child_value[' . $row['id'] . '] = new Array();' . "\n\n"; + + while ($row_parent = Database :: fetch_array($res_parent)) { + $return .= 'child_name[' . $row['id'] . '][' . $i . '] = "' . get_lang('After') . ' \"' . api_html_entity_decode(api_convert_encoding ($row_parent['title'], $charset, $this->encoding), ENT_QUOTES, $charset) . '\"";' . "\n"; + $return .= 'child_value[' . $row['id'] . '][' . $i++ . '] = "' . $row_parent['id'] . '";' . "\n"; + } + $return .= "\n"; + } + $return .= '</script>' . "\n"; + return $return; + } + + /** + * Display the form to allow moving an item + * @param integer Item ID + * @return string HTML form + */ + public function display_move_item($item_id) { + global $_course; //will disappear + global $charset; + $return = ''; + + if (is_numeric($item_id)) { + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + $sql = "SELECT * + FROM " . $tbl_lp_item . " + WHERE id = " . $item_id; + + $res = Database::query($sql); + $row = Database :: fetch_array($res); + + switch ($row['item_type']) { + case 'dokeos_chapter' : + case 'dir' : + case 'asset' : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_item_form($row['item_type'], get_lang('MoveCurrentChapter'), 'move', $item_id, $row); + break; + + case 'dokeos_module' : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_item_form($row['item_type'], 'Move th current module:', 'move', $item_id, $row); + break; + case TOOL_DOCUMENT : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_document_form('move', $item_id, $row); + break; + case TOOL_LINK : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_link_form('move', $item_id, $row); + break; + case TOOL_HOTPOTATOES : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_link_form('move', $item_id, $row); + break; + case TOOL_QUIZ : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_quiz_form('move', $item_id, $row); + break; + case TOOL_STUDENTPUBLICATION : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_student_publication_form('move', $item_id, $row); + break; + case TOOL_FORUM : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_forum_form('move', $item_id, $row); + break; + case TOOL_THREAD : + $return .= $this->display_manipulate($item_id, $row['item_type']); + $return .= $this->display_forum_form('move', $item_id, $row); + break; + } + } + + return $return; + } + + /** + * Displays a basic form on the overview page for changing the item title and the item description. + * @param string $item_type + * @param string $title + * @param array $data + * @return string + */ + public function display_item_small_form($item_type, $title = '', $data) { + global $charset; + + $return .= '<div class="lp_small_form">' . "\n"; + $return .= '<p class="lp_title">' . $title . '</p>'; + $return .= '<form method="post">' . "\n"; + $return .= '<table cellpadding="0" cellspacing="0" class="lp_form">'; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td class="label"><label for="idTitle">Title :</label></td>' . "\n"; + $return .= "\t\t\t" . '<td class="input"><input class="small_form" id="idTitle" name="title" type="text" value="' . api_html_entity_decode(api_convert_encoding(htmlspecialchars($data['title']), $charset, $this->encoding), ENT_QUOTES, $charset) . '" /></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + // It said these lines of code - see SVN#11724 and SVN#10770 + //$return .= "\t\t" . '<tr>' . "\n"; + //$return .= "\t\t\t" . '<td class="label"><label for="idDescription">Description :</label></td>' . "\n"; + //$return .= "\t\t\t" . '<td class="input"><textarea class="small_form" id="idDescription" name="description" rows="4">' . $data['description'] . '</textarea></td>' . "\n"; + //$return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '<tr>' . "\n"; + $return .= "\t\t\t" . '<td colspan="2"><button class="save" name="submit_button" type="submit">' . get_lang('Save') . '</button></td>' . "\n"; + $return .= "\t\t" . '</tr>' . "\n"; + $return .= "\t\t" . '</table>' . "\n"; + $return .= "\t" . '<input name="parent" type="hidden" value="' . $data['parent_item_id'] . '"/>' . "\n"; + $return .= "\t" . '<input name="previous" type="hidden" value="' . $data['previous_item_id'] . '"/>' . "\n"; + $return .= '</form>'; + $return .= '</div>'; + return $return; + } + + /** + * Return HTML form to allow prerequisites selection + * @param integer Item ID + * @return string HTML form + */ + public function display_item_prerequisites_form($item_id) { + global $charset; + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + /* Current prerequisite */ + $sql = " + SELECT * + FROM " . $tbl_lp_item . " + WHERE id = " . $item_id; + $result = Database::query($sql); + $row = Database :: fetch_array($result); + + $preq_id = $row['prerequisite']; + //$preq_mastery = $row['mastery_score']; + //$preq_max = $row['max_score']; + + $return = $this->display_manipulate($item_id, TOOL_DOCUMENT); + $return = ' <div class="row"> + <div class="form_header">'; + $return .= get_lang('AddEditPrerequisites'); + $return .= '</div> </div>'; + $return .= '<div class="sectioncomment">'; + $return .= '<form method="POST">'; + $return .= '<table class="lp_prerequisites" style="border-collapse:collapse;">'; + $return .= '<tr style="border-collapse:collapse;">'; + $return .= '<th height="24" style="border:1px solid gray; background-color:#F0F0F0;">' . get_lang('Prerequisites') . '</th>'; + $return .= '<th width="70" height="24" style="border:1px solid gray; background-color:#F0F0F0;">' . get_lang('Minimum') . '</th>'; + $return .= '<th width="70" height="24" style="border:1px solid gray; background-color:#F0F0F0;">' . get_lang('Maximum') . '</th>'; + $return .= '</tr>'; + + // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146 + $return .= '<tr >'; + $return .= '<td colspan="3" class="radio" style="border:1px solid gray;border-collapse:collapse;">'; + $return .= '<input checked="checked" id="idNone" name="prerequisites" style="margin-left:0; margin-right:10px;" type="radio" />'; + $return .= '<label for="idNone">' . get_lang('None') . '</label>'; + $return .= '</td><td />'; + $return .= '</tr>'; + + $sql = "SELECT * FROM " . $tbl_lp_item . " WHERE lp_id = " . $this->lp_id; + $result = Database::query($sql); + $arrLP = array (); + while ($row = Database :: fetch_array($result)) { + $arrLP[] = array ( + 'id' => $row['id'], + 'item_type' => $row['item_type'], + 'title' => api_convert_encoding($row['title'], $charset, $this->encoding), + 'ref' => $row['ref'], + 'description' => $row['description'], + 'parent_item_id' => $row['parent_item_id'], + 'previous_item_id' => $row['previous_item_id'], + 'next_item_id' => $row['next_item_id'], + 'max_score' => $row['max_score'], + 'min_score' => $row['min_score'], + 'mastery_score' => $row['mastery_score'], + 'prerequisite' => $row['prerequisite'], + 'next_item_id' => $row['next_item_id'], + 'display_order' => $row['display_order'] + ); + if ($row['ref'] == $preq_id) { + $preq_mastery = $row['mastery_score']; + $preq_max = $row['max_score']; + } + } + $this->tree_array($arrLP); + $arrLP = $this->arrMenu; + unset ($this->arrMenu); + + for ($i = 0; $i < count($arrLP); $i++) { + if ($arrLP[$i]['id'] == $item_id) + break; + $return .= '<tr >'; + $return .= '<td style="border:1px solid #B0B0B0; border-collapse:collapse; height:25px; padding: 3px 5px 3px 3px;" class="radio"' . (($arrLP[$i]['item_type'] != TOOL_QUIZ && $arrLP[$i]['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '') . '>'; + $return .= '<input' . (($arrLP[$i]['id'] == $preq_id) ? ' checked="checked" ' : '') . (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter') ? ' disabled="disabled" ' : ' ') . 'id="id' . $arrLP[$i]['id'] . '" name="prerequisites" style="margin-left:' . $arrLP[$i]['depth'] * 10 . 'px; margin-right:10px;" type="radio" value="' . $arrLP[$i]['id'] . '" />'; + $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']); + if (file_exists('../img/lp_' . $icon_name . '.png')) { + $return .= '<img alt="" src="../img/lp_' . $icon_name . '.png" style="margin-right:5px;" title="" />'; + } else + if (file_exists('../img/lp_' . $icon_name . '.gif')) { + $return .= '<img alt="" src="../img/lp_' . $icon_name . '.gif" style="margin-right:5px;" title="" />'; + } else { + $return .= Display::return_icon('folder_document.gif','',array('style'=>'margin-right:5px;')); + } + $return .= '<label for="id' . $arrLP[$i]['id'] . '">' . $arrLP[$i]['title'] . '</label>'; + $return .= '</td>'; + //$return .= '<td class="radio"' . (($arrLP[$i]['item_type'] != TOOL_HOTPOTATOES) ? ' colspan="3"' : '') . ' />'; + + if ($arrLP[$i]['item_type'] == TOOL_QUIZ) { + $return .= '<td class="exercise" style="border:1px solid #B0B0B0;">'; + $return .= '<center><input size="4" maxlength="3" name="min_' . $arrLP[$i]['id'] . '" type="text" value="' . (($arrLP[$i]['id'] == $preq_id) ? $preq_mastery : 0) . '" /></center>'; + $return .= '</td>'; + $return .= '<td class="exercise" style="border:1px solid #B0B0B0;">'; + $return .= '<center><input size="4" maxlength="3" name="max_' . $arrLP[$i]['id'] . '" type="text" value="' . $arrLP[$i]['max_score'] . '" disabled="true" /></center>'; + $return .= '</td>'; + } + if ($arrLP[$i]['item_type'] == TOOL_HOTPOTATOES) { + $return .= '<td class="exercise" style="border:1px solid #B0B0B0;">'; + $return .= '<center><input size="4" maxlength="3" name="min_' . $arrLP[$i]['id'] . '" type="text" value="' . (($arrLP[$i]['id'] == $preq_id) ? $preq_mastery : 0) . '" /></center>'; + $return .= '</td>'; + $return .= '<td class="exercise" style="border:1px solid #B0B0B0;">'; + $return .= '<center><input size="4" maxlength="3" name="max_' . $arrLP[$i]['id'] . '" type="text" value="' . $arrLP[$i]['max_score'] . '" disabled="true" /></center>'; + $return .= '</td>'; + } + $return .= '</tr>'; + } + $return .= '<tr>'; + $return .= '<td colspan="3">'; + $return .= '</td>'; + $return .= '</tr>'; + $return .= '</table>'; + $return .= '<div style="padding-top:3px;">'; + $return .= '<button class="save" name="submit_button" type="submit">' . get_lang('ModifyPrerequisites') . ' </button></td>' . "\n"; + $return .= '</div>'; + $return .= '</form>'; + $return .= '</div>'; + + return $return; + } + + /** + * Return HTML list to allow prerequisites selection for lp + * @param integer Item ID + * @return string HTML form + */ + public function display_lp_prerequisites_list() { + global $charset; + $lp_id = $this->lp_id; + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + + // get current prerequisite + $sql = "SELECT * FROM $tbl_lp WHERE id = $lp_id "; + $result = Database::query($sql); + $row = Database :: fetch_array($result); + $preq_id = $row['prerequisite']; + $session_id = api_get_session_id(); + $session_condition = api_get_session_condition($session_id, false); + $sql = "SELECT * FROM $tbl_lp $session_condition ORDER BY display_order "; + $rs = Database::query($sql); + $return = ''; + $return .= '<select name="prerequisites" >'; + $return .= '<option value="0">'.get_lang('None').'</option>'; + if (Database::num_rows($rs) > 0) { + while ($row = Database::fetch_array($rs)) { + if ($row['id'] == $lp_id) { + continue; + } + $return .= '<option value="'.$row['id'].'" '.(($row['id']==$preq_id)?' selected ' : '').'>'.$row['name'].'</option>'; + } + } + $return .= '</select>'; + return $return; + } + /** + * Creates a list with all the documents in it + * @return string + */ + public function get_documents() { + global $_course; + + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $tbl_item_prop = Database::get_course_table(TABLE_ITEM_PROPERTY); + $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE); + $path = '/'; + $path = Database::escape_string(str_replace('_', '\_', $path)); + $added_slash = ($path == '/') ? '' : '/'; + + //condition for the session + $current_session_id = api_get_session_id(); + $condition_session = " AND (id_session = '$current_session_id' OR (id_session = '0' AND insert_date <= (SELECT creation_date FROM $tbl_course WHERE code = '{$_course[id]}')))"; + + $sql_doc = "SELECT docs.* + FROM $tbl_item_prop AS last, $tbl_doc AS docs + WHERE docs.id = last.ref + AND docs.path LIKE '".$path.$added_slash."%' + AND docs.path NOT LIKE '%_DELETED_%' + AND last.tool = '".TOOL_DOCUMENT."' $condition_session ORDER BY docs.path ASC"; + + //$sql_doc = "SELECT * FROM $tbl_doc WHERE path NOT LIKE '%_DELETED_%' ORDER BY path ASC"; + $res_doc = Database::query($sql_doc); + + $return = '<div class="lp_resource_header"' . " onclick=\"if(document.getElementById('resDoc').style.display == 'block') {document.getElementById('resDoc').style.display = 'none';} else {document.getElementById('resDoc').style.display = 'block';}\"" . '>'.Display::return_icon('folder_document.gif',get_lang('Documents'),array('style'=>'margin-right:5px;', 'height' => '16px')).' '. get_lang('Documents') . '</div>'; + $return .= '<div class="lp_resource_elements" id="resDoc">'; + $resources = Database::store_result($res_doc); + $resources_sorted = array(); + + // If you want to debug it, I advise you to do "echo" on the eval statements. + + foreach ($resources as $resource) { + $resource_paths = explode('/', $resource['path']); + array_shift($resource_paths); + $path_to_eval = $last_path = ''; + $is_file = false; + foreach ($resource_paths as $key => $resource_path) { + if (strpos($resource_path, '.') === false && $key != count($resource_paths) - 1) { // It's a folder. + $path_to_eval .= '["' . $resource_path . '"]["files"]'; + } else + if (strpos($resource_path, '.') !== false) + $is_file = true; + $last_path = $resource_path; + } + if ($is_file) { + //eval ('$resources_sorted' . $path_to_eval . '[' . $resource['id'] . '] = "' . $last_path . '";'); + eval ('$resources_sorted' . $path_to_eval . '[' . $resource['id'] . '] = "' .$resource['title']."/". $last_path. '";'); + } else { + eval ('$resources_sorted' . $path_to_eval . '["' . $last_path . '"]["id"]=' . $resource['id'] . ';'); + } + + } + $return .= $this->write_resources_tree($resources_sorted); + if (Database :: num_rows($res_doc) == 0) { + $return .= '<div class="lp_resource_element">' . get_lang('NoDocuments') . '</div>'; + } + + $return .= '</div>'; + return $return; + } + + /** + * Generate and return an HTML list of resources based on a given array. + * This list is used to show the course creator a list of available resources to choose from + * when creating a learning path. + * @param array Array of elements to add to the list + * @param integer Enables the tree display by shifting the new elements a certain distance to the right + * @return string The HTML list + */ + public function write_resources_tree($resources_sorted, $num = 0) { + + require_once api_get_path(LIBRARY_PATH).'fileDisplay.lib.php'; + if (count($resources_sorted) > 0) { + foreach ($resources_sorted as $key => $resource) { + if (is_int($resource['id'])) { + // It's a folder. + $return .= '<div><div style="margin-left:' . ($num * 15) . 'px;margin-right:5px;"><img style="cursor: pointer;" src="../img/nolines_plus.gif" align="absmiddle" id="img_' . $resource['id'] . '" onclick="javascript: testResources(\'' . $resource['id'] . '\',\'img_' . $resource['id'] . '\')"><img alt="" src="../img/lp_folder.gif" title="" align="absmiddle" /> <span onclick="javascript: testResources(\'' . $resource['id'] . '\',\'img_' . $resource['id'] . '\')" style="cursor: pointer;" >' . $key . '</span></div><div style="display: none;" id="' . $resource['id'] . '">'; + $return .= $this->write_resources_tree($resource['files'], $num +1); + $return .= '</div></div>'; + } else { + if (!is_array($resource)) { + // It's a file. + $icon = choose_image($resource); + $position = strrpos($icon, '.'); + $icon = substr($icon, 0, $position) . '_small.gif'; + $file_info = explode('/', $resource); + $my_file_title = $file_info[0]; + $my_file_name = $file_info[1]; + //$return .= '<div><div style="margin-left:' . (($num +1) * 15) . 'px;margin-right:5px;"><a href="' . api_get_self() . '?cidReq=' . $_GET['cidReq'] . '&action=add_item&type=' . TOOL_DOCUMENT . '&file=' . $key . '&lp_id=' . $this->lp_id . '"><img alt="" src="../img/' . $icon . '" title="" /> ' . $resource .'</a></div></div>'; + // Show the "image name" not the filename of the image. + $return .= '<div><div style="margin-left:' . (($num +1) * 15) . 'px;margin-right:5px;"><a href="' . api_get_self() . '?cidReq=' . Security::remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_DOCUMENT . '&file=' . $key . '&lp_id=' . $this->lp_id . '"><img alt="" src="../img/' . $icon . '" title="" /> ' . $my_file_title . "</a></div></div>\r\n"; } + } + } + } + return $return; + } + + /** + * Creates a list with all the exercises (quiz) in it + * @return string + */ + public function get_exercises() { + // New for hotpotatoes. + $uploadPath = DIR_HOTPOTATOES; //defined in main_api + $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); + $tbl_quiz = Database :: get_course_table(TABLE_QUIZ_TEST); + + $session_id = api_get_session_id(); + $condition_session = api_get_session_condition($session_id); + + $sql_quiz = " + SELECT * + FROM " . $tbl_quiz . " + WHERE active<>'-1' $condition_session + ORDER BY title ASC"; + + $sql_hot = "SELECT * FROM " . $tbl_doc . " " . + " WHERE path LIKE '" . $uploadPath . "/%/%htm%' $condition_session " . + " ORDER BY id ASC"; + + $res_quiz = Database::query($sql_quiz); + $res_hot = Database::query($sql_hot); + + $return .= '<div class="lp_resource_header"' . " onclick=\"javascript: if(document.getElementById('resExercise').style.display == 'block') {document.getElementById('resExercise').style.display = 'none';} else {document.getElementById('resExercise').style.display = 'block';}\"" . ' ><img align="left" alt="" src="../img/lp_' . TOOL_QUIZ . '.gif" style="margin-right:5px;" title="" />' . get_lang('Quiz') . '</div>'; + $return .= '<div class="lp_resource_elements" id="resExercise">'; + + while ($row_hot = Database :: fetch_array($res_hot)) { + $return .= '<div class="lp_resource_element">'; + // Display quizhotpotatoes. + $return .= '<img alt="hp" src="../img/hotpotatoes_s.png" style="margin-right:5px;" title="" width="18px" height="18px" />'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_HOTPOTATOES . '&file=' . $row_hot['id'] . '&lp_id=' . $this->lp_id . '">' . ((!empty ($row_hot['comment'])) ? $row_hot['comment'] : Security :: remove_XSS($row_hot['title'])) . '</a>'; + //$return .= $row_quiz['title']; + $return .= '</div>'; + } + + while ($row_quiz = Database :: fetch_array($res_quiz)) { + $return .= '<div class="lp_resource_element">'; + $return .= '<img alt="" src="../img/quizz_small.gif" style="margin-right:5px;" title="" />'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_QUIZ . '&file=' . $row_quiz['id'] . '&lp_id=' . $this->lp_id . '">' . Security :: remove_XSS($row_quiz['title']) . '</a>'; + //$return .= $row_quiz['title']; + $return .= '</div>'; + } + + if (Database :: num_rows($res_quiz) == 0) { + $return .= '<div class="lp_resource_element">' . get_lang('NoExercisesAvailable') . '</div>'; + } + $return .= '<div class="lp_resource_element">'; + $return .= '<img alt="" src="../img/new_test_small.gif" style="margin-right:5px;" title="" />'; + $return .= '<a href="' . api_get_path(REL_CODE_PATH) . 'exercice/exercise_admin.php">' . get_lang('NewExercise') . '</a>'; + $return .= '</div>'; + $return .= '</div>'; + return $return; + } + + /** + * Creates a list with all the links in it + * @return string + */ + public function get_links() { + $tbl_link = Database :: get_course_table(TABLE_LINK); + + $session_id = api_get_session_id(); + $condition_session = api_get_session_condition($session_id,false); + + $sql_link = "SELECT * FROM $tbl_link $condition_session ORDER BY title ASC"; + $res_link = Database::query($sql_link); + + $return .= '<div class="lp_resource_header"' . " onclick=\"javascript: if(document.getElementById('resLink').style.display == 'block') {document.getElementById('resLink').style.display = 'none';} else {document.getElementById('resLink').style.display = 'block';}\"" . '><img alt="" src="../img/lp_' . TOOL_LINK . '.gif" style="margin-right:5px;" title="" />' . get_lang('Links') . '</div>'; + $return .= '<div class="lp_resource_elements" id="resLink">'; + + while ($row_link = Database :: fetch_array($res_link)) { + $return .= '<div class="lp_resource_element">'; + + $return .= '<img alt="" src="../img/lp_link.gif" style="margin-right:5px;" title="" />'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_LINK . '&file=' . $row_link['id'] . '&lp_id=' . $this->lp_id . '">' . $row_link['title'] . '</a>'; + + $return .= '</div>'; + } + $return .= '<div class="lp_resource_element">'; + $return .= '<img alt="" src="../img/linksnew.gif" style="margin-right:5px;width:16px" title="" />'; + $return .= '<a href="' . api_get_path(REL_CODE_PATH) . 'link/link.php?' . api_get_cidreq() . '&action=addlink" title="' . get_lang('LinkAdd') . '">' . get_lang('LinkAdd') . '</a>'; + $return .= '</div>'; + + if (Database :: num_rows($res_link) == 0) + $return .= '<div class="lp_resource_element">' . get_lang('NoLinksAvailable') . '</div>'; + + $return .= '</div>'; + + return $return; + } + + /** + * Creates a list with all the student publications in it + * @return unknown + */ + public function get_student_publications() { + $tbl_student = Database :: get_course_table(TABLE_STUDENT_PUBLICATION); + $session_id = api_get_session_id(); + $condition_session = api_get_session_condition($session_id, false); + $sql_student = "SELECT * FROM $tbl_student $condition_session ORDER BY title ASC"; + $res_student = Database::query($sql_student); + $return .= '<div class="lp_resource_header"' . " onclick=\"javascript: if(document.getElementById('resStudent').style.display == 'block') {document.getElementById('resStudent').style.display = 'none';} else {document.getElementById('resStudent').style.display = 'block';}\"" . '><img alt="" src="../img/lp_' . TOOL_STUDENTPUBLICATION . '.gif" style="margin-right:5px;" title="" />' . get_lang('Student_publication') . '</div>'; + $return .= '<div class="lp_resource_elements" id="resStudent">'; + $return .= '<div class="lp_resource_element">'; + $return .= '<img align="left" alt="" src="../img/works_small.gif" style="margin-right:5px;" title="" />'; + $return .= '<a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_STUDENTPUBLICATION . '&lp_id=' . $this->lp_id . '">' . get_lang('AddAssignmentPage') . '</a>'; + $return .= '</div>'; + $return .= '</div>'; + return $return; + } + + /** + * Creates a list with all the forums in it + * @return string + */ + public function get_forums() { + include '../forum/forumfunction.inc.php'; + include '../forum/forumconfig.inc.php'; + global $table_forums, $table_threads, $table_posts, $table_item_property, $table_users; + $table_forums = Database :: get_course_table(TABLE_FORUM); + $table_threads = Database :: get_course_table(TABLE_FORUM_THREAD); + $table_posts = Database :: get_course_table(TABLE_FORUM_POST); + $table_item_property = Database :: get_course_table(TABLE_ITEM_PROPERTY); + $table_users = Database :: get_main_table(TABLE_MAIN_USER); + $a_forums = get_forums(); + + $return .= '<div class="lp_resource_header"' . " onclick=\"javascript: if(document.getElementById('forums').style.display == 'block') {document.getElementById('forums').style.display = 'none';} else {document.getElementById('forums').style.display = 'block';}\"" . '><img alt="" src="../img/lp_forum.gif" style="margin-right:5px;" title="" />' . get_lang('Forums') . '</div>'; + $return .= '<div class="lp_resource_elements" style="border:1px solid #999999;" id="forums">'; + + foreach ($a_forums as $forum) { + $return .= '<div class="lp_resource_element">'; + $return .= '<script type="text/javascript"> + function toggle_forum(forum_id){ + if(document.getElementById("forum_"+forum_id+"_content").style.display == "none"){ + document.getElementById("forum_"+forum_id+"_content").style.display = "block"; + document.getElementById("forum_"+forum_id+"_opener").src = "' . api_get_path(WEB_IMG_PATH) . 'remove.gif"; + } + else { + document.getElementById("forum_"+forum_id+"_content").style.display = "none"; + document.getElementById("forum_"+forum_id+"_opener").src = "' . api_get_path(WEB_IMG_PATH) . 'add.gif"; + } + } + </script> + '; + + if (!empty($forum['forum_id'])) { + $return .= '<img alt="" src="../img/lp_forum.gif" style="margin-right:5px;" title="" />'; + $return .= '<a style="cursor:hand" onclick="javascript: toggle_forum(' . $forum['forum_id'] . ')" style="vertical-align:middle"><img src="' . api_get_path(WEB_IMG_PATH) . 'add.gif" id="forum_' . $forum['forum_id'] . '_opener" align="absbottom" /></a> + <a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_FORUM . '&forum_id=' . $forum['forum_id'] . '&lp_id=' . $this->lp_id . '" style="vertical-align:middle">' . Security :: remove_XSS($forum['forum_title']) . '</a><ul style="display:none" id="forum_' . $forum['forum_id'] . '_content">'; + } + + $a_threads = get_threads($forum['forum_id']); + if (is_array($a_threads)) { + foreach ($a_threads as $thread) { + $return .= '<li><a href="' . api_get_self() . '?cidReq=' . Security :: remove_XSS($_GET['cidReq']) . '&action=add_item&type=' . TOOL_THREAD . '&thread_id=' . $thread['thread_id'] . '&lp_id=' . $this->lp_id . '">' . Security :: remove_XSS($thread['thread_title']) . '</a></li>'; + } + } + $return .= '</ul></div>'; + } + + $return .= '<div class="lp_resource_element">'; + $return .= '<img alt="" src="../img/forum_new_small.gif" style="margin-right:5px;" title="" />'; + $return .= '<a href="' . api_get_path(REL_CODE_PATH) . 'forum/index.php?' . api_get_cidreq() . '&action=add&content=forum" title="' . get_lang('CreateANewForum') . '">' . get_lang('CreateANewForum') . '</a>'; + $return .= '</div>'; + + return $return; + } + + /** + * Exports the learning path as a SCORM package. This is the main function that + * gathers the content, transforms it, writes the imsmanifest.xml file, zips the + * whole thing and returns the zip. + * + * This method needs to be called in PHP5, as it will fail with non-adequate + * XML package (like the ones for PHP4), and it is *not* a static method, so + * you need to call it on a learnpath object. + * @TODO The method might be redefined later on in the scorm class itself to avoid + * creating a SCORM structure if there is one already. However, if the initial SCORM + * path has been modified, it should use the generic method here below. + * @TODO link this function with the export_lp() function in the same class + * @param string Optional name of zip file. If none, title of learnpath is + * domesticated and trailed with ".zip" + * @return string Returns the zip package string, or null if error + */ + public function scorm_export() { + global $_course; + global $charset; + + if (!class_exists('DomDocument')) { + error_log('DOM functions not supported for PHP version below 5.0', 0); + $this->error = 'PHP DOM functions not supported for PHP versions below 5.0'; + return null; + } + + // Remove memory and time limits as much as possible as this might be a long process... + if (function_exists('ini_set')) { + $mem = ini_get('memory_limit'); + if (substr($mem, -1, 1) == 'M') { + $mem_num = substr($mem, 0, -1); + if ($mem_num < 128) { + ini_set('memory_limit', '128M'); + } + } else { + ini_set('memory_limit', '128M'); + } + ini_set('max_execution_time', 600); + //} else { + //error_log('Scorm export: could not change memory and time limits', 0); + } + + // Create the zip handler (this will remain available throughout the method). + $archive_path = api_get_path(SYS_ARCHIVE_PATH); + $sys_course_path = api_get_path(SYS_COURSE_PATH); + $temp_dir_short = uniqid(); + $temp_zip_dir = $archive_path.'/'.$temp_dir_short; + $temp_zip_file = $temp_zip_dir.'/'.md5(time()).'.zip'; + $zip_folder = new PclZip($temp_zip_file); + $current_course_path = api_get_path(SYS_COURSE_PATH).api_get_course_path(); + $root_path = $main_path = api_get_path(SYS_PATH); + $files_cleanup = array(); + + // Place to temporarily stash the zipfiles. + // create the temp dir if it doesn't exist + // or do a cleanup befor creating the zipfile. + if (!is_dir($temp_zip_dir)) { + mkdir($temp_zip_dir, api_get_permissions_for_new_directories()); + } else { + // Cleanup: Check the temp dir for old files and delete them. + $handle=opendir($temp_zip_dir); + while (false !== ($file = readdir($handle))) { + if ($file != '.' && $file != '..') { + unlink("$temp_zip_dir/$file"); + } + } + closedir($handle); + } + $zip_files = $zip_files_abs = $zip_files_dist = array(); + if (is_dir($current_course_path.'/scorm/'.$this->path) && is_file($current_course_path.'/scorm/'.$this->path.'/imsmanifest.xml')) { + // Remove the possible . at the end of the path. + $dest_path_to_lp = substr($this->path, -1) == '.' ? substr($this->path, 0, -1) : $this->path; + $dest_path_to_scorm_folder = str_replace('//','/',$temp_zip_dir.'/scorm/'.$dest_path_to_lp); + mkdir($dest_path_to_scorm_folder, api_get_permissions_for_new_directories(), true); + $zip_files_dist = copyr($current_course_path.'/scorm/'.$this->path, $dest_path_to_scorm_folder, array('imsmanifest'), $zip_files); + } + // Build a dummy imsmanifest structure. Do not add to the zip yet (we still need it). + // This structure is developed following regulations for SCORM 1.2 packaging in the SCORM 1.2 Content + // Aggregation Model official document, secion "2.3 Content Packaging". + $xmldoc = new DOMDocument('1.0', $this->encoding); + $root = $xmldoc->createElement('manifest'); + $root->setAttribute('identifier', 'SingleCourseManifest'); + $root->setAttribute('version', '1.1'); + $root->setAttribute('xmlns', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2'); + $root->setAttribute('xmlns:adlcp', 'http://www.adlnet.org/xsd/adlcp_rootv1p2'); + $root->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance'); + $root->setAttribute('xsi:schemaLocation', 'http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd http://www.imsglobal.org/xsd/imsmd_rootv1p2p1 imsmd_rootv1p2p1.xsd http://www.adlnet.org/xsd/adlcp_rootv1p2 adlcp_rootv1p2.xsd'); + // Build mandatory sub-root container elements. + $metadata = $xmldoc->createElement('metadata'); + $md_schema = $xmldoc->createElement('schema', 'ADL SCORM'); + $metadata->appendChild($md_schema); + $md_schemaversion = $xmldoc->createElement('schemaversion', '1.2'); + $metadata->appendChild($md_schemaversion); + $root->appendChild($metadata); + + $organizations = $xmldoc->createElement('organizations'); + + $resources = $xmldoc->createElement('resources'); + + // Build the only organization we will use in building our learnpaths. + $organizations->setAttribute('default','dokeos_scorm_export'); + $organization = $xmldoc->createElement('organization'); + $organization->setAttribute('identifier','dokeos_scorm_export'); + // To set the title of the SCORM entity (=organization), we take the name given + // in Chamilo and convert it to HTML entities using the Chamilo charset (not the + // learning path charset) as it is the encoding that defines how it is stored + // in the database. Then we convert it to HTML entities again as the "&" character + // alone is not authorized in XML (must be &). + // The title is then decoded twice when extracting (see scorm::parse_manifest). + $org_title = $xmldoc->createElement('title',htmlentities(api_htmlentities($this->get_name(),ENT_QUOTES,$charset))); + $organization->appendChild($org_title); + + // For each element, add it to the imsmanifest structure, then add it to the zip. + // Always call the learnpathItem->scorm_export() method to change it to the SCORM format. + $link_updates = array(); + + foreach ($this->items as $index => $item) { + if (!in_array($item->type, array(TOOL_QUIZ, TOOL_FORUM, TOOL_THREAD, TOOL_LINK, TOOL_STUDENTPUBLICATION))) { + // Get included documents from this item. + if ($item->type == 'sco') + $inc_docs = $item->get_resources_from_source(null,api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.'scorm/'.$this->path.'/'.$item->get_path()); + else + $inc_docs = $item->get_resources_from_source(); + // Give a child element <item> to the <organization> element. + $my_item_id = $item->get_id(); + $my_item = $xmldoc->createElement('item'); + $my_item->setAttribute('identifier','ITEM_'.$my_item_id); + $my_item->setAttribute('identifierref','RESOURCE_'.$my_item_id); + $my_item->setAttribute('isvisible','true'); + // Give a child element <title> to the <item> element. + $my_title = $xmldoc->createElement('title',htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); + $my_item->appendChild($my_title); + // Give a child element <adlcp:prerequisites> to the <item> element. + $my_prereqs = $xmldoc->createElement('adlcp:prerequisites',$this->get_scorm_prereq_string($my_item_id)); + $my_prereqs->setAttribute('type','aicc_script'); + $my_item->appendChild($my_prereqs); + // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:maxtimeallowed',''); + // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:timelimitaction',''); + // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:datafromlms',''); + // Give a child element <adlcp:masteryscore> to the <item> element. + $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore',$item->get_mastery_score()); + $my_item->appendChild($my_masteryscore); + + // Attach this item to the organization element or hits parent if there is one. + if (!empty($item->parent) && $item->parent != 0) { + $children = $organization->childNodes; + $possible_parent = &$this->get_scorm_xml_node($children,'ITEM_'.$item->parent); + if (is_object($possible_parent)) { + $possible_parent->appendChild($my_item); + } else { + if ($this->debug > 0) { error_log('Parent ITEM_'.$item->parent.' of item ITEM_'.$my_item_id.' not found'); } + } + } else { + if ($this->debug > 0) { error_log('No parent'); } + $organization->appendChild($my_item); + } + + // Get the path of the file(s) from the course directory root. + $my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); + + $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); + $my_sub_dir = dirname($my_file_path); + $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); + // Give a <resource> child to the <resources> element + $my_resource = $xmldoc->createElement('resource'); + $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); + $my_resource->setAttribute('type','webcontent'); + $my_resource->setAttribute('href',$my_xml_file_path); + // adlcp:scormtype can be either 'sco' or 'asset'. + if ($item->type == 'sco') { + $my_resource->setAttribute('adlcp:scormtype', 'sco'); + } else { + $my_resource->setAttribute('adlcp:scormtype', 'asset'); + } + // xml:base is the base directory to find the files declared in this resource. + $my_resource->setAttribute('xml:base',''); + // Give a <file> child to the <resource> element. + $my_file = $xmldoc->createElement('file'); + $my_file->setAttribute('href',$my_xml_file_path); + $my_resource->appendChild($my_file); + + // Dependency to other files - not yet supported. + $i = 1; + foreach ($inc_docs as $doc_info) { + if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } + $my_dep = $xmldoc->createElement('resource'); + $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; + $my_dep->setAttribute('identifier', $res_id); + $my_dep->setAttribute('type', 'webcontent'); + $my_dep->setAttribute('adlcp:scormtype', 'asset'); + $my_dep_file = $xmldoc->createElement('file'); + // Check type of URL. + //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0); + if ($doc_info[1] == 'remote') { + // Remote file. Save url as is. + $my_dep_file->setAttribute('href', $doc_info[0]); + $my_dep->setAttribute('xml:base', ''); + } elseif ($doc_info[1] == 'local') { + switch ($doc_info[2]) { + case 'url': // Local URL - save path as url for now, don't zip file. + $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH),'',$doc_info[0]); + $current_dir = dirname($abs_path); + $file_path = realpath($abs_path); + $my_dep_file->setAttribute('href', $file_path); + $my_dep->setAttribute('xml:base', ''); + if (strstr($file_path,$main_path) !== false) { + // The calculated real path is really inside Chamilo's root path. + // Reduce file path to what's under the DocumentRoot. + $file_path = substr($file_path, strlen($root_path) - 1); + //echo $file_path;echo '<br /><br />'; + //error_log(__LINE__.'Reduced url path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href', $file_path); + $my_dep->setAttribute('xml:base', ''); + } + elseif (empty($file_path)) { + /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); + if (strpos($document_root, -1) == '/') { + $document_root = substr(0, -1, $document_root); + }*/ + $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; + $file_path = str_replace('//', '/', $file_path); + if (file_exists($file_path)) { + $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. + $zip_files[] = $my_sub_dir.'/'.$file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href', $file_path); + $my_dep->setAttribute('xml:base', ''); + } + } + break; + case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. + $my_dep_file->setAttribute('href', $doc_info[0]); + $my_dep->setAttribute('xml:base', ''); + + //$current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; + // The next lines fix a bug when using the "subdir" mode of Chamilo, whereas + // an image path would be constructed as /var/www/subdir/subdir/img/foo.bar + $abs_img_path_without_subdir = $doc_info[0]; + $relp = api_get_path(REL_PATH); // The url-append config param. + $pos = strpos($abs_img_path_without_subdir, $relp); + if ($pos === 0) { + $abs_img_path_without_subdir = '/'.substr($abs_img_path_without_subdir, strlen($relp)); + } + //$file_path = realpath(api_get_path(SYS_PATH).$doc_info[0]); + $file_path = realpath(api_get_path(SYS_PATH).$abs_img_path_without_subdir); + $file_path = str_replace('\\', '/', $file_path); + $file_path = str_replace('//', '/', $file_path); + //error_log(__LINE__.'Abs path: '.$file_path, 0); + // Prepare the current directory path (until just under 'document') with a trailing slash. + $cur_path = substr($current_course_path, -1) == '/' ? $current_course_path : $current_course_path.'/'; + // Check if the current document is in that path. + if (strstr($file_path,$cur_path) !== false) { + // The document is in that path, now get the relative path + // to the containing document. + $orig_file_path = dirname($cur_path.$my_file_path).'/'; + $relative_path = ''; + if(strstr($file_path,$cur_path) !== false) { + $relative_path = substr($file_path,strlen($orig_file_path)); + $file_path = substr($file_path,strlen($cur_path)); + } else { + // This case is still a problem as it's difficult to calculate a relative path easily + // might still generate wrong links. + //$file_path = substr($file_path,strlen($cur_path)); + // Calculate the directory path to the current file (without trailing slash). + $my_relative_path = dirname($file_path); + $my_relative_file = basename($file_path); + // Calculate the directory path to the containing file (without trailing slash). + $my_orig_file_path = substr($orig_file_path,0,-1); + $dotdot = ''; + $subdir = ''; + while(strstr($my_relative_path, $my_orig_file_path) === false && (strlen($my_orig_file_path) > 1) && (strlen($my_relative_path) > 1)) { + $my_relative_path2 = dirname($my_relative_path); + $my_orig_file_path = dirname($my_orig_file_path); + $subdir = substr($my_relative_path,strlen($my_relative_path2) + 1).'/'.$subdir; + $dotdot += '../'; + $my_relative_path = $my_relative_path2; + } + $relative_path = $dotdot.$subdir.$my_relative_file; + } + // Put the current document in the zip (this array is the array + // that will manage documents already in the course folder - relative). + $zip_files[] = $file_path; + // Update the links to the current document in the containing document (make them relative). + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $relative_path); + $my_dep_file->setAttribute('href', $file_path); + $my_dep->setAttribute('xml:base', ''); + } + elseif(strstr($file_path,$main_path) !== false) { + // The calculated real path is really inside Chamilo's root path. + // Reduce file path to what's under the DocumentRoot. + $file_path = substr($file_path,strlen($root_path)); + //echo $file_path;echo '<br /><br />'; + //error_log('Reduced path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + elseif (empty($file_path)) { + /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); + if(strpos($document_root,-1) == '/') { + $document_root = substr(0, -1, $document_root); + }*/ + $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; + $file_path = str_replace('//', '/', $file_path); + if (file_exists($file_path)) { + $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. + $zip_files[] = $my_sub_dir.'/'.$file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href','document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + } + break; + case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path + if (substr($doc_info[0], 0, 2) == '..') { + // Relative path going up. + $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; + $file_path = realpath($current_dir.$doc_info[0]); + //error_log($file_path.' <-> '.$main_path,0); + if (strstr($file_path,$main_path) !== false) { + // The calculated real path is really inside Chamilo's root path. + // Reduce file path to what's under the DocumentRoot. + $file_path = substr($file_path, strlen($root_path)); + //error_log('Reduced path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + } else { + $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; + $my_dep_file->setAttribute('href', $doc_info[0]); + $my_dep->setAttribute('xml:base', $my_xml_sub_dir); + } + break; + default: + $my_dep_file->setAttribute('href', $doc_info[0]); + $my_dep->setAttribute('xml:base', ''); + break; + } + } + $my_dep->appendChild($my_dep_file); + $resources->appendChild($my_dep); + $dependency = $xmldoc->createElement('dependency'); + $dependency->setAttribute('identifierref', $res_id); + $my_resource->appendChild($dependency); + $i++; + } + //$my_dependency = $xmldoc->createElement('dependency'); + //$my_dependency->setAttribute('identifierref', ''); + $resources->appendChild($my_resource); + $zip_files[] = $my_file_path; + + //error_log('File '.$my_file_path. ' added to $zip_files', 0); + } else { + // If the item is a quiz or a link or whatever non-exportable, we include a step indicating it. + if ($item->type == TOOL_LINK) { + $my_item = $xmldoc->createElement('item'); + $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); + $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); + $my_item->setAttribute('isvisible', 'true'); + // Give a child element <title> to the <item> element. + $my_title = $xmldoc->createElement('title',htmlspecialchars($item->get_title(),ENT_QUOTES)); + $my_item->appendChild($my_title); + // Give a child element <adlcp:prerequisites> to the <item> element. + $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); + $my_prereqs->setAttribute('type', 'aicc_script'); + $my_item->appendChild($my_prereqs); + // Give a child element <adlcp:maxtimeallowed> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:maxtimeallowed', ''); + // Give a child element <adlcp:timelimitaction> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:timelimitaction', ''); + // Give a child element <adlcp:datafromlms> to the <item> element - not yet supported. + //$xmldoc->createElement('adlcp:datafromlms', ''); + // Give a child element <adlcp:masteryscore> to the <item> element. + $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); + $my_item->appendChild($my_masteryscore); + + // Attach this item to the organization element or its parent if there is one. + if (!empty($item->parent) && $item->parent != 0) { + $children = $organization->childNodes; + for ($i = 0; $i < $children->length; $i++) { + $item_temp = $children->item($i); + if ($item_temp -> nodeName == 'item') { + if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { + $item_temp -> appendChild($my_item); + } + + } + } + } else { + $organization->appendChild($my_item); + } + + $my_file_path = 'link_'.$item->get_id().'.html'; + $sql = 'SELECT url, title FROM '.Database :: get_course_table(TABLE_LINK).' WHERE id='.$item->path; + $rs = Database::query($sql); + if ($link = Database :: fetch_array($rs)) { + $url = $link['url']; + $title = stripslashes($link['title']); + $links_to_create[$my_file_path] = array('title' => $title, 'url' => $url); + $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); + $my_sub_dir = dirname($my_file_path); + $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); + // Give a <resource> child to the <resources> element. + $my_resource = $xmldoc->createElement('resource'); + $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); + $my_resource->setAttribute('type','webcontent'); + $my_resource->setAttribute('href',$my_xml_file_path); + // adlcp:scormtype can be either 'sco' or 'asset'. + $my_resource->setAttribute('adlcp:scormtype','asset'); + // xml:base is the base directory to find the files declared in this resource. + $my_resource->setAttribute('xml:base',''); + // give a <file> child to the <resource> element. + $my_file = $xmldoc->createElement('file'); + $my_file->setAttribute('href',$my_xml_file_path); + $my_resource->appendChild($my_file); + $resources->appendChild($my_resource); + } + } + elseif ($item->type == TOOL_QUIZ) { + require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php'; + $exe_id = $item->path; // Should be using ref when everything will be cleaned up in this regard. + $exe = new Exercise(); + $exe->read($exe_id); + $my_item = $xmldoc->createElement('item'); + $my_item->setAttribute('identifier', 'ITEM_'.$item->get_id()); + $my_item->setAttribute('identifierref', 'RESOURCE_'.$item->get_id()); + $my_item->setAttribute('isvisible', 'true'); + // Give a child element <title> to the <item> element. + $my_title = $xmldoc->createElement('title', htmlspecialchars($item->get_title(), ENT_QUOTES, $this->encoding)); + $my_item->appendChild($my_title); + $my_max_score = $xmldoc->createElement('max_score', $item->get_max()); + //$my_item->appendChild($my_max_score); + // Give a child element <adlcp:prerequisites> to the <item> element. + $my_prereqs = $xmldoc->createElement('adlcp:prerequisites', $item->get_prereq_string()); + $my_prereqs->setAttribute('type','aicc_script'); + $my_item->appendChild($my_prereqs); + // Give a child element <adlcp:masteryscore> to the <item> element. + $my_masteryscore = $xmldoc->createElement('adlcp:masteryscore', $item->get_mastery_score()); + $my_item->appendChild($my_masteryscore); + + // Attach this item to the organization element or hits parent if there is one. + if (!empty($item->parent) && $item->parent != 0) { + $children = $organization->childNodes; + for ($i = 0; $i < $children->length; $i++) { + $item_temp = $children->item($i); + if ($item_temp -> nodeName == 'item') { + if ($item_temp->getAttribute('identifier') == 'ITEM_'.$item->parent) { + $item_temp -> appendChild($my_item); + } + + } + } + } else { + $organization->appendChild($my_item); + } + + // Include export scripts. + require_once api_get_path(SYS_CODE_PATH).'exercice/export/scorm/scorm_export.php'; + + // Get the path of the file(s) from the course directory root + //$my_file_path = $item->get_file_path('scorm/'.$this->path.'/'); + $my_file_path = 'quiz_'.$item->get_id().'.html'; + // Write the contents of the exported exercise into a (big) html file + // to later pack it into the exported SCORM. The file will be removed afterwards. + $contents = export_exercise($exe_id,true); + $tmp_file_path = $archive_path.$temp_dir_short.'/'.$my_file_path; + $res = file_put_contents($tmp_file_path, $contents); + if ($res === false) { error_log('Could not write into file '.$tmp_file_path.' '.__FILE__.' '.__LINE__, 0); } + $files_cleanup[] = $tmp_file_path; + //error_log($tmp_path); die(); + $my_xml_file_path = api_htmlentities($my_file_path, ENT_QUOTES, $this->encoding); + $my_sub_dir = dirname($my_file_path); + $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_QUOTES, $this->encoding); + // Give a <resource> child to the <resources> element. + $my_resource = $xmldoc->createElement('resource'); + $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); + $my_resource->setAttribute('type','webcontent'); + $my_resource->setAttribute('href',$my_xml_file_path); + // adlcp:scormtype can be either 'sco' or 'asset'. + $my_resource->setAttribute('adlcp:scormtype','sco'); + // xml:base is the base directory to find the files declared in this resource. + $my_resource->setAttribute('xml:base',''); + // Give a <file> child to the <resource> element. + $my_file = $xmldoc->createElement('file'); + $my_file->setAttribute('href',$my_xml_file_path); + $my_resource->appendChild($my_file); + + // Get included docs. + $inc_docs = $item->get_resources_from_source(null,$tmp_file_path); + // Dependency to other files - not yet supported. + $i = 1; + foreach ($inc_docs as $doc_info) { + if (count($doc_info) < 1 || empty($doc_info[0])) { continue; } + $my_dep = $xmldoc->createElement('resource'); + $res_id = 'RESOURCE_'.$item->get_id().'_'.$i; + $my_dep->setAttribute('identifier', $res_id); + $my_dep->setAttribute('type', 'webcontent'); + $my_dep->setAttribute('adlcp:scormtype', 'asset'); + $my_dep_file = $xmldoc->createElement('file'); + // Check type of URL. + //error_log(__LINE__.'Now dealing with '.$doc_info[0].' of type '.$doc_info[1].'-'.$doc_info[2], 0); + if ($doc_info[1] == 'remote') { + // Remote file. Save url as is. + $my_dep_file->setAttribute('href',$doc_info[0]); + $my_dep->setAttribute('xml:base', ''); + } elseif ($doc_info[1] == 'local') { + switch ($doc_info[2]) { + case 'url': // Local URL - save path as url for now, don't zip file. + //save file but as local file (retrieve from URL) + $abs_path = api_get_path(SYS_PATH).str_replace(api_get_path(WEB_PATH),'',$doc_info[0]); + $current_dir = dirname($abs_path); + $file_path = realpath($abs_path); + $my_dep_file->setAttribute('href','document/'.$file_path); + $my_dep->setAttribute('xml:base',''); + if (strstr($file_path, $main_path) !== false) { + // The calculated real path is really inside the chamilo root path. + // Reduce file path to what's under the DocumentRoot. + $file_path = substr($file_path, strlen($root_path)); + //echo $file_path;echo '<br /><br />'; + //error_log('Reduced path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => 'document/'.$file_path); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + elseif (empty($file_path)) { + /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH),api_get_path(REL_PATH))); + if (strpos($document_root,-1) == '/') { + $document_root = substr(0, -1, $document_root); + }*/ + $file_path = $_SERVER['DOCUMENT_ROOT'].$abs_path; + $file_path = str_replace('//', '/', $file_path); + if (file_exists($file_path)) { + $file_path = substr($file_path, strlen($current_dir)); // We get the relative path. + $zip_files[] = $my_sub_dir.'/'.$file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0],'dest' => 'document/'.$file_path); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + } + break; + case 'abs': // Absolute path from DocumentRoot. Save file and leave path as is in the zip. + $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; + $file_path = realpath($doc_info[0]); + $my_dep_file->setAttribute('href',$file_path); + $my_dep->setAttribute('xml:base',''); + + if (strstr($file_path,$main_path) !== false) { + // The calculated real path is really inside the chamilo root path. + // Reduce file path to what's under the DocumentRoot. + $file_path = substr($file_path,strlen($root_path)); + //echo $file_path;echo '<br /><br />'; + //error_log('Reduced path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + $link_updates[$my_file_path][] = array('orig'=>$doc_info[0],'dest'=>$file_path); + $my_dep_file->setAttribute('href','document/'.$file_path); + $my_dep->setAttribute('xml:base',''); + } + elseif (empty($file_path)) { + /*$document_root = substr(api_get_path(SYS_PATH), 0, strpos(api_get_path(SYS_PATH), api_get_path(REL_PATH))); + if (strpos($document_root,-1) == '/') { + $document_root = substr(0, -1, $document_root); + }*/ + $file_path = $_SERVER['DOCUMENT_ROOT'].$doc_info[0]; + $file_path = str_replace('//', '/', $file_path); + if (file_exists($file_path)) { + $file_path = substr($file_path,strlen($current_dir)); // We get the relative path. + $zip_files[] = $my_sub_dir.'/'.$file_path; + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + } + break; + case 'rel': // Path relative to the current document. Save xml:base as current document's directory and save file in zip as subdir.file_path + if (substr($doc_info[0], 0, 2) == '..') { + // Relative path going up. + $current_dir = dirname($current_course_path.'/'.$item->get_file_path()).'/'; + $file_path = realpath($current_dir.$doc_info[0]); + //error_log($file_path.' <-> '.$main_path, 0); + if (strstr($file_path, $main_path) !== false) { + // The calculated real path is really inside Chamilo's root path. + // Reduce file path to what's under the DocumentRoot. + + $file_path = substr($file_path, strlen($root_path)); + $file_path_dest = $file_path; + + // File path is courses/CHAMILO/document/.... + $info_file_path = explode('/', $file_path); + if ($info_file_path[0] == 'courses') { // Add character "/" in file path. + $file_path_dest = 'document/'.$file_path; + } + + //error_log('Reduced path: '.$file_path, 0); + $zip_files_abs[] = $file_path; + + $link_updates[$my_file_path][] = array('orig' => $doc_info[0], 'dest' => $file_path_dest); + $my_dep_file->setAttribute('href', 'document/'.$file_path); + $my_dep->setAttribute('xml:base', ''); + } + } else { + $zip_files[] = $my_sub_dir.'/'.$doc_info[0]; + $my_dep_file->setAttribute('href', $doc_info[0]); + $my_dep->setAttribute('xml:base', $my_xml_sub_dir); + } + + break; + default: + $my_dep_file->setAttribute('href', $doc_info[0]); // ../../courses/ + $my_dep->setAttribute('xml:base', ''); + break; + } + } + $my_dep->appendChild($my_dep_file); + $resources->appendChild($my_dep); + $dependency = $xmldoc->createElement('dependency'); + $dependency->setAttribute('identifierref', $res_id); + $my_resource->appendChild($dependency); + $i++; + } + $resources->appendChild($my_resource); + $zip_files[] = $my_file_path; + + } else { + + // Get the path of the file(s) from the course directory root + $my_file_path = 'non_exportable.html'; + $my_xml_file_path = api_htmlentities($my_file_path, ENT_COMPAT, $this->encoding); + $my_sub_dir = dirname($my_file_path); + $my_xml_sub_dir = api_htmlentities($my_sub_dir, ENT_COMPAT, $this->encoding); + // Give a <resource> child to the <resources> element. + $my_resource = $xmldoc->createElement('resource'); + $my_resource->setAttribute('identifier','RESOURCE_'.$item->get_id()); + $my_resource->setAttribute('type','webcontent'); + $my_resource->setAttribute('href','document/'.$my_xml_file_path); + // adlcp:scormtype can be either 'sco' or 'asset'. + $my_resource->setAttribute('adlcp:scormtype','asset'); + // xml:base is the base directory to find the files declared in this resource. + $my_resource->setAttribute('xml:base',''); + // Give a <file> child to the <resource> element. + $my_file = $xmldoc->createElement('file'); + $my_file->setAttribute('href','document/'.$my_xml_file_path); + $my_resource->appendChild($my_file); + $resources->appendChild($my_resource); + + } + } + } + $organizations->appendChild($organization); + $root->appendChild($organizations); + $root->appendChild($resources); + $xmldoc->appendChild($root); + // TODO: Add a readme file here, with a short description and a link to the Reload player + // then add the file to the zip, then destroy the file (this is done automatically). + // http://www.reload.ac.uk/scormplayer.html - once done, don't forget to close FS#138 + + //error_log(print_r($zip_files,true), 0); + foreach ($zip_files as $file_path) { + if (empty($file_path)) { continue; } + //error_log(__LINE__.'getting document from '.$sys_course_path.$_course['path'].'/'.$file_path.' removing '.$sys_course_path.$_course['path'].'/',0); + $dest_file = $archive_path.$temp_dir_short.'/'.$file_path; + $this->create_path($dest_file); + //error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path,0); + //echo $main_path.$file_path.'<br />'; + @copy($sys_course_path.$_course['path'].'/'.$file_path, $dest_file); + // Check if the file needs a link update. + if (in_array($file_path, array_keys($link_updates))) { + $string = file_get_contents($dest_file); + unlink($dest_file); + foreach ($link_updates[$file_path] as $old_new) { + //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0); + // This is an ugly hack that allows .flv files to be found by the flv player that + // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs + // to find the flv to play in document/main/, so we replace main/ in the flv path by + // ../../.. to return from inc/lib/flv_player to the document/main path. + if(substr($old_new['dest'],-3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { + $old_new['dest'] = str_replace('main/','../../../',$old_new['dest']); + } + elseif (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 6) == 'video/') { + $old_new['dest'] = str_replace('video/', '../../../../video/', $old_new['dest']); + } + $string = str_replace($old_new['orig'], $old_new['dest'], $string); + } + file_put_contents($dest_file, $string); + } + } + foreach($zip_files_abs as $file_path) { + if (empty($file_path)) { continue; } + //error_log(__LINE__.'checking existence of '.$main_path.$file_path.'', 0); + if (!is_file($main_path.$file_path) || !is_readable($main_path.$file_path)) { continue; } + //error_log(__LINE__.'getting document from '.$main_path.$file_path.' removing '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/', 0); + $dest_file = $archive_path.$temp_dir_short.'/document/'.$file_path; + $this->create_path($dest_file); + //error_log('Created path '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/document/'.$file_path, 0); + //error_log('copy '.api_get_path(SYS_COURSE_PATH).$_course['path'].'/'.$file_path.' to '.api_get_path(SYS_ARCHIVE_PATH).$temp_dir_short.'/'.$file_path, 0); + //echo $main_path.$file_path.' - '.$dest_file.'<br />'; + + copy($main_path.$file_path, $dest_file); + // Check if the file needs a link update. + if (in_array($file_path,array_keys($link_updates))) { + $string = file_get_contents($dest_file); + unlink($dest_file); + foreach ($link_updates[$file_path] as $old_new) { + //error_log('Replacing '.$old_new['orig'].' by '.$old_new['dest'].' in '.$file_path, 0); + // This is an ugly hack that allows .flv files to be found by the flv player that + // will be added in document/main/inc/lib/flv_player/flv_player.swf and that needs + // to find the flv to play in document/main/, so we replace main/ in the flv path by + // ../../.. to return from inc/lib/flv_player to the document/main path. + if (substr($old_new['dest'], -3) == 'flv' && substr($old_new['dest'], 0, 5) == 'main/') { + $old_new['dest'] = str_replace('main/', '../../../', $old_new['dest']); + } + $string = str_replace($old_new['orig'], $old_new['dest'], $string); + } + file_put_contents($dest_file, $string); + } + } + if (is_array($links_to_create)) { + foreach ($links_to_create as $file => $link) { + $file_content = '<html><body><div style="text-align:center"><a href="'.$link['url'].'">'.$link['title'].'</a></div></body></html>'; + file_put_contents($archive_path.$temp_dir_short.'/'.$file, $file_content); + } + } + // Add non exportable message explanation. + $lang_not_exportable = get_lang('ThisItemIsNotExportable'); + $file_content = <<<EOD <html> - <head> - <style> - .error-message { - font-family: arial, verdana, helvetica, sans-serif; - border-width: 1px; - border-style: solid; - left: 50%; - margin: 10px auto; - min-height: 30px; - padding: 5px; - right: 50%; - width: 500px; - background-color: #FFD1D1; - border-color: #FF0000; - color: #000; - } - </style> - <body> - <div class="error-message"> - $lang_not_exportable - </div> - </body> + <head> + <style> + .error-message { + font-family: arial, verdana, helvetica, sans-serif; + border-width: 1px; + border-style: solid; + left: 50%; + margin: 10px auto; + min-height: 30px; + padding: 5px; + right: 50%; + width: 500px; + background-color: #FFD1D1; + border-color: #FF0000; + color: #000; + } + </style> + <body> + <div class="error-message"> + $lang_not_exportable + </div> + </body> </html> EOD; - if (!is_dir($archive_path.$temp_dir_short.'/document')) { - @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories()); - } - file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content); - - // Add the extra files that go along with a SCORM package. - $main_code_path = api_get_path(SYS_CODE_PATH).'newscorm/packaging/'; - $extra_files = scandir($main_code_path); - foreach ($extra_files as $extra_file) { - if (strpos($extra_file, '.') === 0) - continue; - else { - $dest_file = $archive_path . $temp_dir_short . '/' . $extra_file; - $this->create_path($dest_file); - copy($main_code_path.$extra_file,$dest_file); - } - } - - // Finalize the imsmanifest structure, add to the zip, then return the zip. - - $xmldoc->save($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml'); - - $zip_folder->add($archive_path.'/'.$temp_dir_short, PCLZIP_OPT_REMOVE_PATH, $archive_path.'/'.$temp_dir_short.'/'); - - // Clean possible temporary files. - foreach ($files_cleanup as $file) { - $res = unlink($file); - if ($res === false) { error_log('Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__, 0); } - } - //Send file to client - //$name = 'scorm_export_'.$this->lp_id.'.zip'; - require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; - $name = preg_replace('([^a-zA-Z0-9_\.])','-',html_entity_decode($this->get_name(),ENT_QUOTES)).'.zip'; - DocumentManager::file_send_for_download($temp_zip_file,true,$name); - } - - /** - * Temp function to be moved in main_api or the best place around for this. Creates a file path - * if it doesn't exist - */ - public function create_path($path) { - $path_bits = split('/', dirname($path)); - - // IS_WINDOWS_OS has been defined in main_api.lib.php - $path_built = IS_WINDOWS_OS ? '' : '/'; - - foreach ($path_bits as $bit) { - if (!empty ($bit)) { - $new_path = $path_built . $bit; - if (is_dir($new_path)) { - $path_built = $new_path . '/'; - } else { - mkdir($new_path, api_get_permissions_for_new_directories()); - $path_built = $new_path . '/'; - } - } - } - } - - /** - * Delete the image relative to this learning path. No parameter. Only works on instanciated object. - * @return boolean The results of the unlink function, or false if there was no image to start with - */ - public function delete_lp_image() { - $img = $this->get_preview_image(); - if ($img != '') { - $del_file = api_get_path(SYS_COURSE_PATH) . api_get_course_path() . '/upload/learning_path/images/' . $img; - $this->set_preview_image(''); - return @ unlink($del_file); - } else { - return false; - } - - } - - /** - * Uploads an author image to the upload/learning_path/images directory - * @param array The image array, coming from the $_FILES superglobal - * @return boolean True on success, false on error - */ - public function upload_image($image_array) { - $image_moved = false; - if (!empty ($image_array['name'])) { - $upload_ok = process_uploaded_file($image_array); - $has_attachment = true; - } else { - $image_moved = true; - } - - if ($upload_ok) { - if ($has_attachment) { - $courseDir = api_get_course_path() . '/upload/learning_path/images'; - $sys_course_path = api_get_path(SYS_COURSE_PATH); - $updir = $sys_course_path . $courseDir; - // Try to add an extension to the file if it hasn't one. - $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']); - - if (!filter_extension($new_file_name)) { - //Display :: display_error_message(get_lang('UplUnableToSaveFileFilteredExtension')); - $image_moved = false; - } else { - $file_extension = explode('.', $image_array['name']); - $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]); - $new_file_name = uniqid('') . '.' . $file_extension; - $new_path = $updir . '/' . $new_file_name; - - //$result = @move_uploaded_file($image_array['tmp_name'], $new_path); - // Resize the image. - include_once api_get_path(LIBRARY_PATH).'image.lib.php'; - $temp = new image($image_array['tmp_name']); - $picture_infos = @getimagesize($image_array['tmp_name']); // $picture_infos[0]-> width - if ($picture_infos[0] > 104) { - $thumbwidth = 104; - } else { - $thumbwidth = $picture_infos[0]; - } - if ($picture_infos[1] > 96) { - $new_height = 96; - } else { - $new_height = $picture_infos[1]; - } - //$new_height = round(($thumbwidth/$picture_infos[0])*$picture_infos[1]); - - $temp->resize($thumbwidth,$new_height, 0); - $type = $picture_infos[2]; - $result = false; - - switch ($type) { - case 2 : - $result = $temp->send_image('JPG', $new_path); - break; - case 3 : - $result = $temp->send_image('PNG', $new_path); - break; - case 1 : - $result = $temp->send_image('GIF', $new_path); - break; - } - - $temp->resize($thumbwidth, $new_height, 0); - $type = $picture_infos[2]; - $result = false; - - switch ($type) { - case 2 : - $result = $temp->send_image('JPG', $new_path); - break; - case 3 : - $result = $temp->send_image('PNG', $new_path); - break; - case 1 : - $result = $temp->send_image('GIF', $new_path); - break; - } - - // Storing the image filename. - if ($result) { - $image_moved = true; - $this->set_preview_image($new_file_name); - return true; - } - - } - } - } - return false; - } + if (!is_dir($archive_path.$temp_dir_short.'/document')) { + @mkdir($archive_path.$temp_dir_short.'/document', api_get_permissions_for_new_directories()); + } + file_put_contents($archive_path.$temp_dir_short.'/document/non_exportable.html', $file_content); + + // Add the extra files that go along with a SCORM package. + $main_code_path = api_get_path(SYS_CODE_PATH).'newscorm/packaging/'; + $extra_files = scandir($main_code_path); + foreach ($extra_files as $extra_file) { + if (strpos($extra_file, '.') === 0) + continue; + else { + $dest_file = $archive_path . $temp_dir_short . '/' . $extra_file; + $this->create_path($dest_file); + copy($main_code_path.$extra_file,$dest_file); + } + } + + // Finalize the imsmanifest structure, add to the zip, then return the zip. + + $xmldoc->save($archive_path.'/'.$temp_dir_short.'/imsmanifest.xml'); + + $zip_folder->add($archive_path.'/'.$temp_dir_short, PCLZIP_OPT_REMOVE_PATH, $archive_path.'/'.$temp_dir_short.'/'); + + // Clean possible temporary files. + foreach ($files_cleanup as $file) { + $res = unlink($file); + if ($res === false) { error_log('Could not delete temp file '.$file.' '.__FILE__.' '.__LINE__, 0); } + } + //Send file to client + //$name = 'scorm_export_'.$this->lp_id.'.zip'; + require_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; + $name = preg_replace('([^a-zA-Z0-9_\.])','-',html_entity_decode($this->get_name(),ENT_QUOTES)).'.zip'; + DocumentManager::file_send_for_download($temp_zip_file,true,$name); + } + + /** + * Temp function to be moved in main_api or the best place around for this. Creates a file path + * if it doesn't exist + */ + public function create_path($path) { + $path_bits = split('/', dirname($path)); + + // IS_WINDOWS_OS has been defined in main_api.lib.php + $path_built = IS_WINDOWS_OS ? '' : '/'; + + foreach ($path_bits as $bit) { + if (!empty ($bit)) { + $new_path = $path_built . $bit; + if (is_dir($new_path)) { + $path_built = $new_path . '/'; + } else { + mkdir($new_path, api_get_permissions_for_new_directories()); + $path_built = $new_path . '/'; + } + } + } + } + + /** + * Delete the image relative to this learning path. No parameter. Only works on instanciated object. + * @return boolean The results of the unlink function, or false if there was no image to start with + */ + public function delete_lp_image() { + $img = $this->get_preview_image(); + if ($img != '') { + $del_file = api_get_path(SYS_COURSE_PATH) . api_get_course_path() . '/upload/learning_path/images/' . $img; + $this->set_preview_image(''); + return @ unlink($del_file); + } else { + return false; + } + + } + + /** + * Uploads an author image to the upload/learning_path/images directory + * @param array The image array, coming from the $_FILES superglobal + * @return boolean True on success, false on error + */ + public function upload_image($image_array) { + $image_moved = false; + if (!empty ($image_array['name'])) { + $upload_ok = process_uploaded_file($image_array); + $has_attachment = true; + } else { + $image_moved = true; + } + + if ($upload_ok) { + if ($has_attachment) { + $courseDir = api_get_course_path() . '/upload/learning_path/images'; + $sys_course_path = api_get_path(SYS_COURSE_PATH); + $updir = $sys_course_path . $courseDir; + // Try to add an extension to the file if it hasn't one. + $new_file_name = add_ext_on_mime(stripslashes($image_array['name']), $image_array['type']); + + if (!filter_extension($new_file_name)) { + //Display :: display_error_message(get_lang('UplUnableToSaveFileFilteredExtension')); + $image_moved = false; + } else { + $file_extension = explode('.', $image_array['name']); + $file_extension = strtolower($file_extension[sizeof($file_extension) - 1]); + $new_file_name = uniqid('') . '.' . $file_extension; + $new_path = $updir . '/' . $new_file_name; + + //$result = @move_uploaded_file($image_array['tmp_name'], $new_path); + // Resize the image. + include_once api_get_path(LIBRARY_PATH).'image.lib.php'; + $temp = new image($image_array['tmp_name']); + $picture_infos = @getimagesize($image_array['tmp_name']); // $picture_infos[0]-> width + if ($picture_infos[0] > 104) { + $thumbwidth = 104; + } else { + $thumbwidth = $picture_infos[0]; + } + if ($picture_infos[1] > 96) { + $new_height = 96; + } else { + $new_height = $picture_infos[1]; + } + //$new_height = round(($thumbwidth/$picture_infos[0])*$picture_infos[1]); + + $temp->resize($thumbwidth,$new_height, 0); + $type = $picture_infos[2]; + $result = false; + + switch ($type) { + case 2 : + $result = $temp->send_image('JPG', $new_path); + break; + case 3 : + $result = $temp->send_image('PNG', $new_path); + break; + case 1 : + $result = $temp->send_image('GIF', $new_path); + break; + } + + $temp->resize($thumbwidth, $new_height, 0); + $type = $picture_infos[2]; + $result = false; + + switch ($type) { + case 2 : + $result = $temp->send_image('JPG', $new_path); + break; + case 3 : + $result = $temp->send_image('PNG', $new_path); + break; + case 1 : + $result = $temp->send_image('GIF', $new_path); + break; + } + + // Storing the image filename. + if ($result) { + $image_moved = true; + $this->set_preview_image($new_file_name); + return true; + } + + } + } + } + return false; + } } if (!function_exists('trim_value')) { - function trim_value(& $value) { - $value = trim($value); - } + function trim_value(& $value) { + $value = trim($value); + } } diff --git a/main/newscorm/learnpathItem.class.php b/main/newscorm/learnpathItem.class.php index 7b13abfc7b..74855bf432 100755 --- a/main/newscorm/learnpathItem.class.php +++ b/main/newscorm/learnpathItem.class.php @@ -12,2429 +12,2429 @@ * information that enables tracking a user's progress in a learning path */ class learnpathItem { - public $attempt_id; // Also called "objectives" SCORM-wise. - public $audio; // The path to an audio file (stored in document/audio/). - public $children = array(); // Contains the ids of children items. - public $condition; // If this item has a special condition embedded. - public $current_score; - public $current_start_time; - public $current_stop_time; - public $current_data = ''; - public $db_id; - public $db_item_view_id = ''; - public $description = ''; - public $file; - // At the moment, interactions are just an array of arrays with a structure of 8 text fields - // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) - public $interactions = array(); - public $interactions_count = 0; - public $objectives = array(); - public $objectives_count = 0; - public $launch_data = ''; - public $lesson_location = ''; - public $level = 0; - //var $location; // Only set this for SCORM? - public $lp_id; - public $max_score; - public $mastery_score; - public $min_score; - public $max_time_allowed = ''; - public $name; - public $next; - public $parent; - public $path; // In some cases the exo_id = exercise_id in courseDb exercices table. - public $possible_status = array('not attempted','incomplete','completed','passed','failed','browsed'); - public $prereq_string = ''; - public $prereq_alert = ''; - public $prereqs = array(); - public $previous; - public $prevent_reinit = 1; // 0 = multiple attempts 1 = one attempt - public $ref; - public $save_on_close = true; - public $search_did = null; - public $status; - public $title; - public $type; // This attribute can contain chapter|link|student_publication|module|quiz|document|forum|thread - public $view_id; - - const debug = 0; // Logging parameter. - - /** - * Class constructor. Prepares the learnpath_item for later launch - * - * Don't forget to use set_lp_view() if applicable after creating the item. - * Setting an lp_view will finalise the item_view data collection - * @param integer Learnpath item ID - * @param integer User ID - * @return boolean True on success, false on failure - */ - public function learnpathItem($db_id, $user_id) { - // Get items table. - if (self::debug > 0) { error_log('New LP - In learnpathItem constructor: '.$db_id.','.$user_id, 0); } - $items_table = Database::get_course_table(TABLE_LP_ITEM); - $id = (int) $db_id; - $sql = "SELECT * FROM $items_table WHERE id = $id"; - //error_log('New LP - Creating item object from DB: '.$sql, 0); - $res = @Database::query($sql); - if (Database::num_rows($res) < 1) { - $this->error = 'Could not find given learnpath item in learnpath_item table'; - //error_log('New LP - '.$this->error, 0); - return false; - } - $row = Database::fetch_array($res); - $this->lp_id = $row['lp_id']; - $this->max_score = $row['max_score']; - $this->min_score = $row['min_score']; - $this->name = $row['title']; - $this->type = $row['item_type']; - $this->ref = $row['ref']; - $this->title = $row['title']; - $this->description = $row['description']; - $this->path = $row['path']; - $this->mastery_score = $row['mastery_score']; - $this->parent = $row['parent_item_id']; - $this->next = $row['next_item_id']; - $this->previous = $row['previous_item_id']; - $this->display_order = $row['display_order']; - $this->prereq_string = $row['prerequisite']; - $this->max_time_allowed = $row['max_time_allowed']; - if (isset($row['launch_data'])){ - $this->launch_data = $row['launch_data']; - } - $this->save_on_close = true; - $this->db_id = $id; - - // Get search_did. - if (api_get_setting('search_enabled')=='true') { - $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); - $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; - // TODO: Verify if it's possible to assume the actual course instead of getting it from db. - $sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $this->lp_id, $id); - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $se_ref = Database::fetch_array($res); - $this->search_did = (int)$se_ref['search_did']; - } - } - $this->audio = $row['audio']; - - //error_log('New LP - End of learnpathItem constructor for item '.$id, 0); - return true; - } - - /** - * Adds a child to the current item - */ - public function add_child($item) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::add_child()', 0); } - if (!empty($item)) { - // Do not check in DB as we expect the call to come from the learnpath class which should - // be aware of any fake. - $this->children[] = $item; - } - } - - /** - * Adds an interaction to the current item - * @param int Index (order ID) of the interaction inside this item - * @param array Array of parameters: id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) - * @result void - */ - public function add_interaction($index, $params) { - $this->interactions[$index] = $params; - // Take the current maximum index to generate the interactions_count. - if(($index+1)>$this->interactions_count){ - $this->interactions_count = $index + 1; - } - /* - if (is_array($this->interactions[$index]) && count($this->interactions[$index]) > 0) { - $this->interactions[$index] = $params; - return false; - } else { - if (count($params)==8) { // We rely on the calling script to provide parameters in the right order. - $this->interactions[$index] = $params; - return true; - } else { - return false; - } - } - */ - } - - /** - * Adds an objective to the current item - * @param array Array of parameters: id(0), status(1), score_raw(2), score_max(3), score_min(4) - * @result void - */ - public function add_objective($index, $params) { - if(empty($params[0])){return null;} - $this->objectives[$index] = $params; - // Take the current maximum index to generate the objectives_count. - if ((count($this->objectives) + 1) > $this->objectives_count) { - $this->objectives_count = (count($this->objectives) + 1); - } - } - - /** - * Closes/stops the item viewing. Finalises runtime values. If required, save to DB. - * @return boolean True on success, false otherwise - */ - public function close() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::close()', 0); } - $this->current_stop_time = time(); - $type = $this->get_type(); - if ($type != 'sco') { - if ($type == TOOL_QUIZ or $type == TOOL_HOTPOTATOES) { - $this->get_status(true,true); // Update status (second option forces the update). - } else { - $this->status = $this->possible_status[2]; - } - } - if ($this->save_on_close) { - $this->save(); - } - return true; - } - - /** - * Deletes all traces of this item in the database - * @return boolean true. Doesn't check for errors yet. - */ - public function delete() { - if (self::debug > 0) { error_log('New LP - In learnpath_item::delete() for item '.$this->db_id, 0); } - $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $lp_item = Database::get_course_table(TABLE_LP_ITEM); - $sql_del_view = "DELETE FROM $lp_item_view WHERE lp_item_id = ".$this->db_id; - //error_log('New LP - Deleting from lp_item_view: '.$sql_del_view, 0); - $res_del_view = Database::query($sql_del_view); - - $sql_sel = "SELECT * FROM $lp_item WHERE id = ".$this->db_id; - $res_sel = Database::query($sql_sel); - if (Database::num_rows($res_sel) < 1) { return false; } - $row = Database::fetch_array($res_sel); - - $sql_del_item = "DELETE FROM $lp_item WHERE id = ".$this->db_id; - //error_log('New LP - Deleting from lp_item: '.$sql_del_view, 0); - $res_del_item = Database::query($sql_del_item); - - if (api_get_setting('search_enabled') == 'true') { - if (!is_null($this->search_did)) { - require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; - $di = new DokeosIndexer(); - $di->remove_document($this->search_did); - } - } - - return true; - } - - /** - * Drops a child from the children array - * @param string index of child item to drop - * @return void - */ - public function drop_child($item) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::drop_child()', 0); } - if (!empty($item)) { - foreach ($this->children as $index => $child) { - if ($child == $item) { - $this->children[$index] = null; - } - } - } - } - - /** - * Gets the current attempt_id for this user on this item - * @return integer The attempt_id for this item view by this user, or 1 if none defined - */ - public function get_attempt_id() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_attempt_id() on item '.$this->db_id, 0); } - $res = 1; - if (!empty($this->attempt_id)) { - $res = $this->attempt_id; - } - if (self::debug > 0) { error_log('New LP - End of learnpathItem::get_attempt_id() on item '.$this->db_id.' - Returning '.$res, 0); } - return $res; - } - - /** - * Gets a list of the item's children - * @return array Array of children items IDs - */ - public function get_children() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_children()', 0); } - $list = array(); - foreach ($this->children as $child) { - if (!empty($child)) { - //error_log('New LP - Found '.$child, 0); - $list[] = $child; - } - } - return $list; - } - - /** - * Gets the core_exit value from the database - */ - public function get_core_exit() { - return $this->core_exit; - } - - /** - * Gets the credit information (rather scorm-stuff) based on current status and reinit - * autorization. Credit tells the sco(content) if Chamilo will record the data it is sent (credit) or not (no-credit) - * @return string 'credit' or 'no-credit'. Defaults to 'credit' because if we don't know enough about this item, it's probably because it was never used before. - */ - public function get_credit() { - if (self::debug > 1) {error_log('New LP - In learnpathItem::get_credit()', 0); } - $credit = 'credit'; - // Now check the value of prevent_reinit (if it's 0, return credit as the default was). - if ($this->get_prevent_reinit() != 0) { // If prevent_reinit == 1 (or more). - // If status is not attempted or incomplete, credit anyway. Otherwise: - // Check the status in the database rather than in the object, as checking in the object - // would always return "no-credit" when we want to set it to completed. - $status = $this->get_status(true); - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_credit() - get_prevent_reinit!=0 and status is '.$status, 0); } - if ($status != $this->possible_status[0] && $status != $this->possible_status[1]) { - $credit = 'no-credit'; - } - } - return $credit; - } - - /** - * Gets the current start time property - * @return integer Current start time, or current time if none - */ - public function get_current_start_time() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_current_start_time()', 0); } - if (empty($this->current_start_time)) { - return time(); - } else { - return $this->current_start_time; - } - } - - /** - * Gets the item's description - * @return string Description - */ - public function get_description() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_description()', 0); } - if (empty($this->description)) { return ''; } - return $this->description; - } - - /** - * Gets the file path from the course's root directory, no matter what tool it is from. - * @return string The file path, or an empty string if there is no file attached, or '-1' if the file must be replaced by an error page - */ - public function get_file_path($path_to_scorm_dir = '') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_file_path()', 0); } - $path = $this->get_path(); - $type = $this->get_type(); - if (empty($path)) { - if ($type == 'dokeos_chapter' || $type == 'chapter' || $type == 'dir') { - return ''; - } else { - return '-1'; - } - } elseif ($path == strval(intval($path))) { - // The path is numeric, so it is a reference to a Chamilo object. - switch ($type) { - case 'dokeos_chapter': - case 'dir': - case 'chapter': - return ''; - case TOOL_DOCUMENT: - $table_doc = Database::get_course_table(TABLE_DOCUMENT); - $sql = 'SELECT path FROM '.$table_doc.' WHERE id = '.$path; - $res = Database::query($sql); - $row = Database::fetch_array($res); - $real_path = 'document'.$row['path']; - return $real_path; - case TOOL_STUDENTPUBLICATION: - case TOOL_QUIZ: - case TOOL_FORUM: - case TOOL_THREAD: - case TOOL_LINK: - default: - return '-1'; - } - } else { - if (!empty($path_to_scorm_dir)) { - $path = $path_to_scorm_dir.$path; - } - return $path; - } - } - - /** - * Gets the DB ID - * @return integer Database ID for the current item - */ - public function get_id() { - if (self::debug > 1) {error_log('New LP - In learnpathItem::get_id()', 0); } - if (!empty($this->db_id)) { - return $this->db_id; - } - // TODO: Check this return value is valid for children classes (SCORM?). - return 0; - } - - /** - * Loads the interactions into the item object, from the database. - * If object interactions exist, they will be overwritten by this function, - * using the database elements only. - * @return void Directly sets the interactions attribute in memory - */ - public function load_interactions() { - $this->interactions = array(); - $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT id FROM $tbl " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $row = Database::fetch_array($res); - $lp_iv_id = $row[0]; - $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); - $iva_sql = "SELECT * FROM $iva_table " . - "WHERE lp_iv_id = $lp_iv_id "; - $res_sql = Database::query($iva_sql); - while ($row = Database::fetch_array($res_sql)) { - $this->interactions[$row['interaction_id']] = array($row['interaction_id'], $row['interaction_type'], $row['weighting'], $row['completion_time'], $row['correct_responses'], $row['student_responses'], $row['result'], $row['latency']); - } - } - } - - /** - * Gets the current count of interactions recorded in the database - * @param bool Whether to count from database or not (defaults to no) - * @return int The current number of interactions recorder - */ - public function get_interactions_count($checkdb = false) { - if (self::debug > 1) { error_log('New LP - In learnpathItem::get_interactions_count()', 0); } - $return = 0; - if ($checkdb) { - $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT id FROM $tbl " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $row = Database::fetch_array($res); - $lp_iv_id = $row[0]; - $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); - $iva_sql = "SELECT count(id) as mycount FROM $iva_table " . - "WHERE lp_iv_id = $lp_iv_id "; - $res_sql = Database::query($iva_sql); - if (Database::num_rows($res_sql) > 0) { - $row = Database::fetch_array($res_sql); - $return = $row['mycount']; - } - } - } else { - if(!empty($this->interactions_count)){ - $return = $this->interactions_count; - } - } - return $return; - } - - /** - * Gets the JavaScript array content to fill the interactions array. - * @params bool Whether to check directly into the database (default no) - * @return string An empty string if no interaction, a JS array definition otherwise - */ - public function get_interactions_js_array($checkdb = false) { - $return = ''; - if ($checkdb) { - $this->load_interactions(true); - } - foreach ($this->interactions as $id => $in) { - $return .= "['$id','".$in[1]."','".$in[2]."','".$in[3]."','".$in[4]."','".$in[5]."','".$in[6]."','".$in[7]."'],"; - } - if (!empty($return)) { - $return = substr($return, 0, -1); - } - return $return; - } - - /** - * Gets the current count of objectives recorded in the database - * @return int The current number of objectives recorder - */ - public function get_objectives_count() { - if (self::debug > 1) { error_log('New LP - In learnpathItem::get_objectives_count()', 0);} - $res = 0; - if (!empty($this->objectives_count)) { - $res = $this->objectives_count; - } - return $res; - } - - /** - * Gets the launch_data field found in imsmanifests (this is SCORM- or AICC-related, really) - * @return string Launch data as found in imsmanifest and stored in Chamilo (read only). Defaults to ''. - */ - public function get_launch_data() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_launch_data()', 0); } - if (!empty($this->launch_data)) { - return $this->launch_data; - } - return ''; - } - - /** - * Gets the lesson location - * @return string lesson location as recorded by the SCORM and AICC elements. Defaults to '' - */ - public function get_lesson_location() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_lesson_location()', 0); } - if (!empty($this->lesson_location)) { return $this->lesson_location; } else { return ''; } - } - - /** - * Gets the lesson_mode (scorm feature, but might be used by aicc as well as dokeos paths) - * - * The "browse" mode is not supported yet (because there is no such way of seeing a sco in Chamilo) - * @return string 'browse','normal' or 'review'. Defaults to 'normal' - */ - public function get_lesson_mode() { - $mode = 'normal'; - if ($this->get_prevent_reinit() != 0) { // If prevent_reinit == 0 - $my_status = $this->get_status(); - if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) { - $mode = 'review'; - } - } - return $mode; - } - - /** - * Gets the depth level - * @return int Level. Defaults to 0 - */ - public function get_level() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_level()', 0); } - if (empty($this->level)) { return 0; } - return $this->level; - } - - /** - * Gets the mastery score - */ - public function get_mastery_score() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_mastery_score()', 0); } - if (isset($this->mastery_score)) { return $this->mastery_score; } else { return -1; } - } - - /** - * Gets the maximum (score) - * @return int Maximum score. Defaults to 100 if nothing else is defined - */ - public function get_max(){ - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_max()', 0); } - if ($this->type == 'sco') { - if (!empty($this->view_max_score) && $this->view_max_score > 0) { - return $this->view_max_score; - } elseif ($this->view_max_score === '') { - return $this->view_max_score; - } else { - if (!empty($this->max_score)) { return $this->max_score; } else { return 100; } - } - } else { - if (!empty($this->max_score)) { return $this->max_score; } else { return 100; } - } - } - - /** - * Gets the maximum time allowed for this user in this attempt on this item - * @return string Time string in SCORM format (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS) - */ - public function get_max_time_allowed() { - if (self::debug > 0) {error_log('New LP - In learnpathItem::get_max_time_allowed()', 0); } - if (!empty($this->max_time_allowed)) { return $this->max_time_allowed; } else { return ''; } - } - - /** - * Gets the minimum (score) - * @return int Minimum score. Defaults to 0 - */ - public function get_min() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_min()', 0); } - if (!empty($this->min_score)) { return $this->min_score; } else { return 0; } - } - - /** - * Gets the parent ID - * @return int Parent ID. Defaults to null - */ - public function get_parent(){ - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_parent()', 0); } - if (!empty($this->parent)) { - return $this->parent; - } - // TODO: Check this return value is valid for children classes (SCORM?). - return null; - } - - /** - * Gets the path attribute. - * @return string Path. Defaults to '' - */ - public function get_path(){ - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_path()', 0); } - if (empty($this->path)) { return ''; } - return $this->path; - } - - /** - * Gets the prerequisites string - * @return string Empty string or prerequisites string if defined. Defaults to - */ - public function get_prereq_string() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_prereq_string()', 0); } - if (!empty($this->prereq_string)) { - return $this->prereq_string; - } else { - return ''; - } - } - - /** - * Gets the prevent_reinit attribute value (and sets it if not set already) - * @return int 1 or 0 (defaults to 1) - */ - public function get_prevent_reinit() { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_prevent_reinit()', 0); } - if (!isset($this->prevent_reinit)) { - if (!empty($this->lp_id)) { - $db = Database::get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $db WHERE id = ".$this->lp_id; - $res = @Database::query($sql); - if (Database::num_rows($res) < 1) { - $this->error = "Could not find parent learnpath in learnpath table"; - if (self::debug > 2) { error_log('New LP - End of learnpathItem::get_prevent_reinit() - Returning false', 0); } - return false; - } else { - $row = Database::fetch_array($res); - $this->prevent_reinit = $row['prevent_reinit']; - } - } else { - $this->prevent_reinit = 1; // Prevent reinit is always 1 by default - see learnpath.class.php. - } - } - if (self::debug > 2) { error_log('New LP - End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit, 0); } - return $this->prevent_reinit; - } - - /** - * Gets the item's reference column - * @return string The item's reference field (generally used for SCORM identifiers) - */ - public function get_ref() { - return $this->ref; - } - - /** - * Gets the list of included resources as a list of absolute or relative paths of - * resources included in the current item. This allows for a better SCORM export. - * The list will generally include pictures, flash objects, java applets, or any other - * stuff included in the source of the current item. The current item is expected - * to be an HTML file. If it is not, then the function will return and empty list. - * @param string type (one of the Chamilo tools) - optional (otherwise takes the current item's type) - * @param string path (absolute file path) - optional (otherwise takes the current item's path) - * @param int level of recursivity we're in - * @return array List of file paths. An additional field containing 'local' or 'remote' helps determine if the file should be copied into the zip or just linked - */ - public function get_resources_from_source($type = null, $abs_path = null, $recursivity = 1) { - $max = 5; - if ($recursivity > $max) { - return array(); - } - if (!isset($type)) { - $type = $this->get_type(); - } - if (!isset($abs_path)) { - $path = $this->get_file_path(); - $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path; - //echo "Abs path coming from item : ".$abs_path."<br />\n"; - } - /* - else { - echo "Abs path coming from param: ".$abs_path."<br />\n"; - } - */ - //error_log(str_repeat(' ',$recursivity).'Analyse file '.$abs_path, 0); - $files_list = array(); - $type = $this->get_type(); - switch ($type) { - case TOOL_DOCUMENT : - case TOOL_QUIZ: - case 'sco': - // Get the document and, if HTML, open it. - - if (is_file($abs_path)) { - // for now, read the whole file in one go (that's gonna be a problem when the file is too big). - $info = pathinfo($abs_path); - $ext = $info['extension']; - switch(strtolower($ext)) { - case 'html': - case 'htm': - case 'shtml': - case 'css': - $wanted_attributes = array('src', 'url', '@import', 'href', 'value'); - // Parse it for included resources. - $file_content = file_get_contents($abs_path); - // Get an array of attributes from the HTML source. - $attributes = DocumentManager::parse_HTML_attributes($file_content, $wanted_attributes); - // Look at 'src' attributes in this file. - foreach ($wanted_attributes as $attr) { - if (isset($attributes[$attr])) { - // Find which kind of path these are (local or remote). - $sources = $attributes[$attr]; - - foreach ($sources as $source) { - // Skip what is obviously not a resource. - if (strpos($source, "+this.")) continue; // javascript code - will still work unaltered. - if (strpos($source, '.') === false) continue; // No dot, should not be an external file anyway. - if (strpos($source, 'mailto:')) continue; // mailto link. - if (strpos($source, ';') && !strpos($source, '&')) continue; // Avoid code - that should help. - - if ($attr == 'value') { - if (strpos($source , 'mp3file')) { - $files_list[] = array(substr($source, 0, strpos($source , '.swf') + 4), 'local', 'abs'); - $mp3file = substr($source , strpos($source , 'mp3file=') + 8); - if (substr($mp3file, 0, 1) == '/') - $files_list[] = array($mp3file, 'local', 'abs'); - else - $files_list[] = array($mp3file, 'local', 'rel'); - } - elseif (strpos($source, 'flv=') === 0) { - $source = substr($source, 4); - if (strpos($source, '&') > 0) { - $source = substr($source, 0, strpos($source, '&')); - } - if (strpos($source, '://') > 0) { - if (strpos($source, api_get_path(WEB_PATH)) !== false) { - // We found the current portal url. - $files_list[] = array($source, 'local', 'url'); - } else { - // We didn't find any trace of current portal. - $files_list[] = array($source, 'remote', 'url'); - } - } else { - $files_list[] = array($source, 'local', 'abs'); - } - continue; // Skipping anything else to avoid two entries (while the others can have sub-files in their url, flv's can't). - } - } - if (strpos($source, '://') > 0) { - - // Cut at '?' in a URL with params. - if (strpos($source, '?') > 0) { - $second_part = substr($source, strpos($source, '?')); - if (strpos($second_part, '://') > 0) { - // If the second part of the url contains a url too, treat the second one before cutting. - - $pos1 = strpos($second_part, '='); - $pos2 = strpos($second_part, '&'); - $second_part = substr($second_part, $pos1 + 1, $pos2 - ($pos1 + 1)); - if (strpos($second_part, api_get_path(WEB_PATH)) !== false) { - // We found the current portal url. - $files_list[] = array($second_part, 'local', 'url'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $second_part, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // We didn't find any trace of current portal. - $files_list[] = array($second_part, 'remote', 'url'); - } - } - elseif(strpos($second_part, '=') > 0) { - if (substr($second_part, 0, 1) === '/') { - // Link starts with a /, making it absolute (relative to DocumentRoot). - $files_list[] = array($second_part, 'local', 'abs'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $second_part, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - elseif(strstr($second_part, '..') === 0) { - // Link is relative but going back in the hierarchy. - $files_list[] = array($second_part, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$second_part); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // No starting '/', making it relative to current document's path. - if (substr($second_part, 0, 2) == './') { - $second_part = substr($second_part, 2); - } - $files_list[] = array($second_part, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$second_part); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - } - // Leave that second part behind now. - $source = substr($source, 0, strpos($source, '?')); - if (strpos($source,'://') > 0) { - if (strpos($source, api_get_path(WEB_PATH)) !== false) { - // We found the current portal url. - $files_list[] = array($source, 'local', 'url'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // We didn't find any trace of current portal. - $files_list[] = array($source, 'remote', 'url'); - } - } else { - // No protocol found, make link local. - if (substr($source, 0, 1) === '/') { - // Link starts with a /, making it absolute (relative to DocumentRoot). - $files_list[] = array($source, 'local', 'abs'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - elseif (strstr($source, '..') === 0) { - // Link is relative but going back in the hierarchy. - $files_list[] = array($source, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$source); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // No starting '/', making it relative to current document's path. - if (substr($source, 0, 2) == './') { - $source = substr($source, 2); - } - $files_list[] = array($source, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$source); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - } - } - // Found some protocol there. - if (strpos($source, api_get_path(WEB_PATH)) !== false) { - // We found the current portal url. - $files_list[] = array($source, 'local', 'url'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // We didn't find any trace of current portal. - $files_list[] = array($source, 'remote', 'url'); - } - } else { - // No protocol found, make link local. - if (substr($source, 0, 1) === '/') { - // Link starts with a /, making it absolute (relative to DocumentRoot). - $files_list[] = array($source, 'local', 'abs'); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - elseif (strstr($source, '..') === 0) { - // Link is relative but going back in the hierarchy. - $files_list[] = array($source, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$source); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } else { - // No starting '/', making it relative to current document's path. - if (substr($source, 0, 2) == './') { - $source = substr($source, 2); - } - $files_list[] = array($source, 'local', 'rel'); - $dir = dirname($abs_path); - $new_abs_path = realpath($dir.'/'.$source); - $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); - if (count($in_files_list) > 0) { - $files_list = array_merge($files_list, $in_files_list); - } - } - } - } - } - } - break; - default: - break; - } - - } else { - // The file could not be found. - return false; - } - break; - default: // Ignore. - break; - } - //error_log(str_repeat(' ', $recursivity), 'found files '.print_r($files_list, true), 0); - //return $files_list; - $checked_files_list = array(); - $checked_array_list = array(); - foreach ($files_list as $idx => $file) { - if (!empty($file[0])) { - if (!in_array($file[0], $checked_files_list)) { - $checked_files_list[] = $files_list[$idx][0]; - $checked_array_list[] = $files_list[$idx]; - } - } - } - return $checked_array_list; - } - - /** - * Gets the score - * @return float The current score or 0 if no score set yet - */ - public function get_score() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_score()', 0); } - $res = 0; - if (!empty($this->current_score)) { - $res = $this->current_score; - } - if (self::debug > 1) { error_log('New LP - Out of learnpathItem::get_score() - returning '.$res, 0); } - return $res; - } - - /** - * Gets the item status - * @param boolean Do or don't check into the database for the latest value. Optional. Default is true - * @param boolean Do or don't update the local attribute value with what's been found in DB - * @return string Current status or 'Nnot attempted' if no status set yet - */ - public function get_status($check_db = true, $update_local = false) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_status() on item '.$this->db_id, 0); } - if ($check_db) { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status(): checking db', 0); } - $table = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT * FROM $table WHERE id = '".$this->db_item_view_id."' AND view_count = '".$this->get_attempt_id()."'"; - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Checking DB: '.$sql, 0); } - - $res = Database::query($sql); - if (Database::num_rows($res) == 1) { - $row = Database::fetch_array($res); - if ($update_local) { - $this->set_status($row['status']); - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning db value '.$row['status'], 0); } - return $row['status']; - } - } else { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - in get_status: using attrib', 0); } - if (!empty($this->status)) { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning attrib: '.$this->status, 0); } - return $this->status; - } - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning default '.$this->possible_status[0], 0); } - return $this->possible_status[0]; - } - - /** - * Gets the suspend data - */ - public function get_suspend_data() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_suspend_data()', 0); } - // TODO: Improve cleaning of breaklines ... it works but is it really a beautiful way to do it ? - if (!empty($this->current_data)) { return str_replace(array("\r", "\n"), array('\r', '\n'), $this->current_data); } else { return ''; } - } - - /** - * Gets the total time spent on this item view so far - * @param string Origin of the request. If coming from PHP, send formatted as xxhxx'xx", otherwise use scorm format 00:00:00 - * @param integer Given time is a default time to return formatted - */ - public function get_scorm_time($origin = 'php', $given_time = null, $query_db = false) { - $h = get_lang('h'); - if (!isset($given_time)) { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time, 0); } - if (is_object($this)) { - if ($query_db === true) { - $table = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT start_time, total_time FROM $table WHERE id = '".$this->db_item_view_id."' AND view_count = '".$this->get_attempt_id()."'"; - $res = Database::query($sql); - $row = Database::fetch_array($res); - $start = $row['start_time']; - $stop = $start + $row['total_time']; - } else { - $start = $this->current_start_time; - $stop = $this->current_stop_time; - } - if (!empty($start)) { - if (!empty($stop)) { - $time = $stop - $start; - } else { - $time = time() - $start; - } - } - } else { - if ($origin == 'js') { - return '00:00:00'; - } else { - return '00'.$h.'00\'00"'; - } - } - } else { - $time = $given_time; - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time(): intermediate = '.$time, 0); } - $hours = $time/3600; - $mins = ($time%3600)/60; - $secs = ($time%60); - if ($origin == 'js') { - $scorm_time = trim(sprintf("%4d:%02d:%02d", $hours, $mins, $secs)); - } else { - $scorm_time = trim(sprintf("%4d$h%02d'%02d\"", $hours, $mins, $secs)); - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time('.$scorm_time.')', 0); } - return $scorm_time; - } - - public function get_terms() { - $lp_item = Database::get_course_table(TABLE_LP_ITEM); - $sql = "SELECT * FROM $lp_item WHERE id='".Database::escape_string($this->db_id)."'"; - $res = Database::query($sql); - $row = Database::fetch_array($res); - return $row['terms']; - } - - /** - * Returns the item's title - * @return string Title - */ - public function get_title() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_title()', 0); } - if (empty($this->title)) { return ''; } - return $this->title; - } - - /** - * Returns the total time used to see that item - * @return integer Total time - */ - public function get_total_time() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_total_time()', 0); } - if ($this->current_start_time == 0) { // Shouldn't be necessary thanks to the open() method. - $this->current_start_time = time(); - } - //$this->current_stop_time=time(); - if (time() < $this->current_stop_time) { - // If this case occurs, then we risk to write huge time data in db. - // In theory, stop time should be *always* updated here, but it might be used in some unknown goal. - $this->current_stop_time = time(); - } - $time = $this->current_stop_time - $this->current_start_time; - if ($time < 0) { - return 0; - } else { - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_total_time() - Current stop time = '.$this->current_stop_time.', current start time = '.$this->current_start_time.' Returning '.$time, 0); } - return $time; - } - } - - /** - * Gets the item type - * @return string The item type (can be doc, dir, sco, asset) - */ - public function get_type() { - $res = 'asset'; - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_type() on item '.$this->db_id, 0); } - if (!empty($this->type)) { - //error_log('In item::get_type() - returning '.$this->type, 0); - $res = $this->type; - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::get_type() - Returning '.$res.' for item '.$this->db_id, 0); } - return $res; - } - - /** - * Gets the view count for this item - * @return int Number of attempts or 0 - */ - public function get_view_count() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::get_view_count()', 0); } - if (!empty($this->attempt_id)) { - return $this->attempt_id; - } else { - return 0; - } - } - - /** - * Tells if an item is done ('completed','passed','succeeded') or not - * @return bool True if the item is done ('completed','passed','succeeded'), false otherwise - */ - function is_done(){ - if ($this->status_is(array('completed', 'passed', 'succeeded'))) { - if (self::debug > 2) { error_log('New LP - In learnpath::is_done() - Item '.$this->get_id().' is complete', 0); } - return true; - }else{ - if (self::debug > 2) { error_log('New LP - In learnpath::is_done() - Item '.$this->get_id().' is not complete', 0); } - return false; - } - } - - /** - * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status) - * @return integer -1 if retaking the sco another time for credit is not allowed, - * 0 if it is not allowed but the item has to be finished - * 1 if it is allowed. Defaults to 1 - */ - public function is_restart_allowed() { - if (self::debug > 2) { error_log('New LP - In learnpathItem::is_restart_allowed()', 0); } - $restart = 1; - $mystatus = $this->get_status(true); - if ($this->get_prevent_reinit() > 0){ // If prevent_reinit == 1 (or more) - // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise: - if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) { - $restart = -1; - } else { - $restart = 0; - } - } else { - if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) { - $restart = -1; - } - } - if (self::debug > 2) { error_log('New LP - End of learnpathItem::is_restart_allowed() - Returning '.$restart, 0); } - return $restart; - } - - /** - * Opens/launches the item. Initialises runtime values. - * @return boolean True on success, false on failure. - */ - public function open($allow_new_attempt = false) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::open()', 0); } - if ($this->prevent_reinit == 0) { - $this->current_score = 0; - $this->current_start_time = time(); - // In this case, as we are opening the item, what is important to us - // is the database status, in order to know if this item has already - // been used in the past (rather than just loaded and modified by - // some javascript but not written in the database). - // If the database status is different from 'not attempted', we can - // consider this item has already been used, and as such we can - // open a new attempt. Otherwise, we'll just reuse the current - // attempt, which is generally created the first time the item is - // loaded (for example as part of the table of contents). - $stat = $this->get_status(true); - if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) { - $this->attempt_id = $this->attempt_id + 1; // Open a new attempt. - } - $this->status = $this->possible_status[1]; - } else { - /*if ($this->current_start_time == 0) { - // Small exception for start time, to avoid amazing values. - $this->current_start_time = time(); - }*/ - // If we don't init start time here, the time is sometimes calculated from the las start time. - $this->current_start_time = time(); - - //error_log('New LP - reinit blocked by setting', 0); - } - } - - /** - * Outputs the item contents - * @return string HTML file (displayable in an <iframe>) or empty string if no path defined - */ - function output() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::output()', 0); } - if (!empty($this->path) and is_file($this->path)) { - $output = ''; - $output .= file_get_contents($this->path); - return $output; - } - return ''; - } - - /** - * Parses the prerequisites string with the AICC logic language - * @param string The prerequisites string as it figures in imsmanifest.xml - * @param Array Array of items in the current learnpath object. Although we're in the learnpathItem object, it's necessary to have a list of all items to be able to check the current item's prerequisites - * @param Array List of references (the "ref" column in the lp_item table) that are strings used in the expression of prerequisites. - * @param integer The user ID. In some cases like Chamilo quizzes, it's necessary to have the user ID to query other tables (like the results of quizzes) - * @return boolean True if the list of prerequisites given is entirely satisfied, false otherwise - */ - public function parse_prereq($prereqs_string, $items, $refs_list, $user_id) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string, 0); } - // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order. - $this->prereq_alert = ''; - // First parse all parenthesis by using a sequential loop (looking for less-inclusives first). - if ($prereqs_string == '_true_') { return true; } - if ($prereqs_string == '_false_') { - if (empty($this->prereq_alert)) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return false; - } - while (strpos($prereqs_string, '(') !== false) { - // Remove any () set and replace with its value. - $matches = array(); - $res = preg_match_all('/(\(([^\(\)]*)\))/', $prereqs_string, $matches); - if ($res) { - foreach ($matches[2] as $id => $match) { - $str_res = $this->parse_prereq($match, $items, $refs_list, $user_id); - if ($str_res) { - $prereqs_string = str_replace($matches[1][$id], '_true_', $prereqs_string); - } else { - $prereqs_string = str_replace($matches[1][$id], '_false_', $prereqs_string); - } - } - } - } - - // Parenthesis removed, now look for ORs as it is the lesser-priority binary operator (= always uses one text operand). - if (strpos($prereqs_string, '|') === false) { - if (self::debug > 1) { error_log('New LP - Didnt find any OR, looking for AND', 0); } - if (strpos($prereqs_string, '&') !== false) { - $list = split('&', $prereqs_string); - if (count($list) > 1) { - $andstatus = true; - foreach ($list as $condition) { - $andstatus = $andstatus && $this->parse_prereq($condition, $items, $refs_list, $user_id); - if (!$andstatus) { - if (self::debug > 1) { error_log('New LP - One condition in AND was false, short-circuit', 0); } - break; - } - } - if (empty($this->prereq_alert) && !$andstatus) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $andstatus; - } else { - if (isset($items[$refs_list[$list[0]]])) { - $status = $items[$refs_list[$list[0]]]->get_status(true); - $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); - if (empty($this->prereq_alert) && !$returnstatus) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $returnstatus; - } - $this->prereq_alert = get_lang('_prereq_not_complete'); - return false; - } - } else { - // No ORs found, now look for ANDs. - - if (self::debug > 1) { error_log('New LP - Didnt find any AND, looking for =', 0); } - if (strpos($prereqs_string, '=') !== false) { - if (self::debug > 1) { error_log('New LP - Found =, looking into it', 0); } - // We assume '=' signs only appear when there's nothing else around. - $params = split('=', $prereqs_string); - if (count($params) == 2) { - // Right number of operands. - if (isset($items[$refs_list[$params[0]]])) { - $status = $items[$refs_list[$params[0]]]->get_status(true); - $returnstatus = ($status == $params[1]); - if (empty($this->prereq_alert) && !$returnstatus) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $returnstatus; - } - $this->prereq_alert = get_lang('_prereq_not_complete'); - return false; - } - } else { - - // No ANDs found, look for <> - - if (self::debug > 1) { error_log('New LP - Didnt find any =, looking for <>', 0); } - if (strpos($prereqs_string, '<>') !== false) { - if (self::debug > 1) { error_log('New LP - Found <>, looking into it', 0); } - // We assume '<>' signs only appear when there's nothing else around. - $params = split('<>', $prereqs_string); - if (count($params) == 2) { - // Right number of operands. - if (isset($items[$refs_list[$params[0]]])) { - $status = $items[$refs_list[$params[0]]]->get_status(true); - $returnstatus = ($status != $params[1]); - if (empty($this->prereq_alert) && !$returnstatus) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $returnstatus; - } - $this->prereq_alert = get_lang('_prereq_not_complete'); - return false; - } - } else { - - // No <> found, look for ~ (unary). - - if (self::debug > 1) { error_log('New LP - Didnt find any =, looking for ~', 0); } - // Only remains: ~ and X*{} - if (strpos($prereqs_string, '~') !== false) { - // Found NOT. - if (self::debug > 1) { error_log('New LP - Found ~, looking into it', 0); } - $list = array(); - $myres = preg_match('/~([^(\d+\*)\{]*)/', $prereqs_string, $list); - if ($myres) { - $returnstatus = !$this->parse_prereq($list[1], $items, $refs_list, $user_id); - if (empty($this->prereq_alert) && !$returnstatus) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $returnstatus; - } else { - // Strange... - if (self::debug > 1) { error_log('New LP - Found ~ but strange string: '.$prereqs_string, 0); } - } - } else { - - // Finally, look for sets/groups. - - if (self::debug > 1) { error_log('New LP - Didnt find any ~, looking for groups', 0); } - // Only groups here. - $groups = array(); - $groups_there = preg_match_all('/((\d+\*)?\{([^\}]+)\}+)/', $prereqs_string, $groups); - if ($groups_there) { - foreach ($groups[1] as $gr) { // Only take the results that correspond to the big brackets-enclosed condition. - if (self::debug > 1) { error_log('New LP - Dealing with group '.$gr, 0); } - $multi = array(); - $mycond = false; - if (preg_match('/(\d+)\*\{([^\}]+)\}/', $gr, $multi)) { - if (self::debug > 1) { error_log('New LP - Found multiplier '.$multi[0], 0); } - $count = $multi[1]; - $list = split(',', $multi[2]); - $mytrue = 0; - foreach ($list as $cond) { - if (isset($items[$refs_list[$cond]])) { - $status = $items[$refs_list[$cond]]->get_status(true); - if (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])) { - $mytrue ++; - if (self::debug > 1) { error_log('New LP - Found true item, counting.. ('.($mytrue).')', 0); } - } - } else { - if (self::debug > 1) { error_log('New LP - item '.$cond.' does not exist in items list', 0); } - } - } - if ($mytrue >= $count) { - if (self::debug > 1) { error_log('New LP - Got enough true results, return true', 0); } - $mycond = true; - } else { - if (self::debug > 1) { error_log('New LP - Not enough true results', 0); } - } - } - else { - if (self::debug > 1) { error_log('New LP - No multiplier', 0); } - $list = split(',', $gr); - $mycond = true; - foreach ($list as $cond) { - if (isset($items[$refs_list[$cond]])) { - $status = $items[$refs_list[$cond]]->get_status(true); - if (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])){ - $mycond = true; - if (self::debug > 1) { error_log('New LP - Found true item', 0); } - } else { - if (self::debug > 1) { error_log('New LP - Found false item, the set is not true, return false', 0); } - $mycond = false; - break; - } - } else { - if (self::debug > 1) { error_log('New LP - item '.$cond.' does not exist in items list', 0); } - if (self::debug > 1) { error_log('New LP - Found false item, the set is not true, return false', 0); } - $mycond = false; - break; - } - } - } - if (!$mycond && empty($this->prereq_alert)) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $mycond; - } - } else { - - // Nothing found there either. Now return the value of the corresponding resource completion status. - if (self::debug > 1) { error_log('New LP - Didnt find any group, returning value for '.$prereqs_string, 0); } - - if (isset($items[$refs_list[$prereqs_string]])) { - if ($items[$refs_list[$prereqs_string]]->type == 'quiz') { - - // 1. Checking the status in current items. - $status = $items[$refs_list[$prereqs_string]]->get_status(true); - //error_log('hello '.$status); - $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); - - if (!$returnstatus) { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } - } else { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } - } - - // For one attempt LPs. - if ($this->prevent_reinit == 1) { - - // 2. If is completed we check the results in the DB of the quiz. - if ($returnstatus) { - //AND origin_lp_item_id = '.$user_id.' - $sql = 'SELECT exe_result, exe_weighting - FROM '.Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES).' - WHERE exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' - AND exe_user_id = '.$user_id.' - AND orig_lp_id = '.$this->lp_id.' AND orig_lp_item_id = '.$prereqs_string.' - AND status <> "incomplete" - ORDER BY exe_date DESC - LIMIT 0, 1'; - //error_log('results :'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); - - $rs_quiz = Database::query($sql); - if ($quiz = Database :: fetch_array($rs_quiz)) { - if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { - $returnstatus = true; - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - } - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - } - } - - } else { - // 3. for multiple attempts we check that there are minimun 1 item completed. - - // Checking in the database. - $sql = 'SELECT exe_result, exe_weighting - FROM '.Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES).' - WHERE exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' - AND exe_user_id = '.$user_id.' AND orig_lp_id = '.$this->lp_id.' AND orig_lp_item_id = '.$prereqs_string.' '; - //error_log('results 2:'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); - - $rs_quiz = Database::query($sql); - if (Database::num_rows($rs_quiz) > 0) { - while ($quiz = Database :: fetch_array($rs_quiz)) { - if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { - $returnstatus = true; - break; - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - } - } - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - } - - } - return $returnstatus; - } else { - - $status = $items[$refs_list[$prereqs_string]]->get_status(false); - $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); - - if (!$returnstatus) { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } - } else { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } - } - //error_log('status of document'.$status); - //var_dump($returnstatus); - //$returnstatus = true; - if ($returnstatus && $this->prevent_reinit == 1) { - // I would prefer check in the database. - $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $lp_view = Database::get_course_table(TABLE_LP_VIEW); - - $sql = 'SELECT id FROM '.$lp_view.' - WHERE user_id = '.$user_id.' AND lp_id = '.$this->lp_id.' LIMIT 0, 1'; - $rs_lp = Database::query($sql); - $lp_id = Database :: fetch_row($rs_lp); - $my_lp_id = $lp_id[0]; - - $sql = 'SELECT status FROM '.$lp_item_view.' - WHERE lp_view_id = '.$my_lp_id.' AND lp_item_id = '.$refs_list[$prereqs_string].' LIMIT 0, 1'; - $rs_lp = Database::query($sql); - $status_array = Database :: fetch_row($rs_lp); - $status = $status_array[0]; - - //var_dump($status); - $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); - if (!$returnstatus && empty($this->prereq_alert)){ - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - if (!$returnstatus) { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } - } else { - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } - } - } - - //error_log('results :'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); - /*$rs_quiz = Database::query($sql); - if ($quiz = Database :: fetch_array($rs_quiz)) { - if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { - $returnstatus = true; - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - } - } else { - $this->prereq_alert = get_lang('_prereq_not_complete'); - $returnstatus = false; - }*/ - - /* - $status = $items[$refs_list[$prereqs_string]]->get_status(true); - //error_log(print_r($items, 1)); - //error_log($refs_list[$prereqs_string]); - - $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); - if (!$returnstatus && empty($this->prereq_alert)) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - if(!$returnstatus){ - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } - }else{ - if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } - } - */ - - //$returnstatus =false; - return $returnstatus; - } - } else { - if (self::debug > 1) { error_log('New LP - Could not find '.$prereqs_string.' in '.print_r($refs_list, true), 0); } - } - } - } - } - } - } - } else { - $list = split("\|",$prereqs_string); - if(count($list)>1){ - if (self::debug > 1) { error_log('New LP - Found OR, looking into it', 0); } - $orstatus = false; - foreach ($list as $condition) { - if (self::debug > 1) { error_log('New LP - Found OR, adding it ('.$condition.')', 0); } - $orstatus = $orstatus || $this->parse_prereq($condition, $items, $refs_list, $user_id); - if ($orstatus) { - // Shortcircuit OR. - if (self::debug > 1) { error_log('New LP - One condition in OR was true, short-circuit', 0); } - break; - } - } - if (!$orstatus && empty($this->prereq_alert)) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $orstatus; - } else { - if (self::debug>1) { error_log('New LP - OR was found but only one elem present !?', 0); } - if (isset($items[$refs_list[$list[0]]])) { - $status = $items[$refs_list[$list[0]]]->get_status(true); - $returnstatus = (($status == 'completed') OR ($status == 'passed')); - if (!$returnstatus && empty($this->prereq_alert)) { - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - return $returnstatus; - } - } - } - if(empty($this->prereq_alert)){ - $this->prereq_alert = get_lang('_prereq_not_complete'); - } - if (self::debug > 1) { error_log('New LP - End of parse_prereq. Error code is now '.$this->prereq_alert, 0); } - return false; - } - - /** - * Reinits all local values as the learnpath is restarted - * @return boolean True on success, false otherwise - */ - public function restart() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::restart()', 0); } - $this->save(); - $allowed = $this->is_restart_allowed(); - if ($allowed === -1) { - // Nothing allowed, do nothing. - } elseif ($allowed === 1) { - // Restart as new attempt is allowed, record a new attempt. - $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id. - $this->current_score = 0; - $this->current_start_time = 0; - $this->current_stop_time = 0; - $this->current_data = ''; - $this->status = $this->possible_status[0]; - $this->interactions_count = 0; - $this->interactions = array(); - $this->objectives_count = 0; - $this->objectives = array(); - $this->lesson_location = ''; - if ($this->type != TOOL_QUIZ) { - $this->write_to_db(); - } - } else { - // Restart current element is allowed (because it's not finished yet), - // reinit current. - $this->current_score = 0; - $this->current_start_time = 0; - $this->current_stop_time = 0; - $this->current_data = ''; - $this->status = $this->possible_status[0]; - $this->interactions_count = $this->get_interactions_count(true); - } - return true; - } - - /** - * Saves data in the database - * @param boolean Save from URL params (1) or from object attributes (0) - * @param boolean The results of a check on prerequisites for this item. True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au - * @return boolean True on success, false on failure - */ - public function save($from_outside = true, $prereqs_complete = false) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::save()', 0); } - //$item_view_table = Database::get_course_table(COURSEID, LEARNPATH_ITEM_VIEW_TABLE); - $item_id = $this->get_id(); - // First check if parameters passed via GET can be saved here - // in case it's a SCORM, we should get: - if ($this->type == 'sco' || $this->type== 'au') { - $s = $this->get_status(true); - if ($this->prevent_reinit == 1 AND - $s != $this->possible_status[0] AND $s != $this->possible_status[1]) { - if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - save reinit blocked by setting', 0); } - // Do nothing because the status has already been set. Don't allow it to change. - // TODO: Check there isn't a special circumstance where this should be saved. - } else { - if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - SCORM save request received', 0); } - //get all new settings from the URL - if ($from_outside) { - if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - Getting item data from outside', 0); } - foreach ($_GET as $param => $value) { - $value = Database::escape_string($value); - switch ($param) { - case 'score': - $this->set_score($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting score to '.$value, 0); } - break; - case 'max': - $this->set_max_score($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting view_max_score to '.$value, 0); } - break; - case 'min': - $this->min_score = $value; - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting min_score to '.$value, 0); } - break; - case 'lesson_status': - if(!empty($value)){ - $this->set_status($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting status to '.$value, 0); } - } - break; - case 'time': - $this->set_time($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting time to '.$value, 0); } - break; - case 'suspend_data': - $this->current_data = $value; - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting suspend_data to '.$value, 0); } - break; - case 'lesson_location': - $this->set_lesson_location($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting lesson_location to '.$value, 0); } - break; - case 'core_exit': - $this->set_core_exit($value); - if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting core_exit to '.$value, 0); } - break; - case 'interactions': - //$interactions = unserialize($value); - //foreach($interactions as $interaction){ - // ; - //} - break; - case 'objectives': - break; - //case 'maxtimeallowed': - //$this->set_max_time_allowed($value); - //break; - /* - case 'objectives._count': - $this->attempt_id = $value; - break; - */ - default: - // Ignore. - break; - } - } - } else { - if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - Using inside item status', 0); } - // Do nothing, just let the local attributes be used. - } - } - } else { // If not SCO, such messages should not be expected. - $type = strtolower($this->type); - switch ($type) { - case 'asset': - if ($prereqs_complete) { - $this->set_status($this->possible_status[2]); - } - break; - case TOOL_HOTPOTATOES: break; - case TOOL_QUIZ: return false;break; - default: - // For now, everything that is not sco and not asset is set to - // completed when saved. - if ($prereqs_complete) { - $this->set_status($this->possible_status[2]); - } - break; - } - } - //$time = $this->time - if (self::debug > 1) { error_log('New LP - End of learnpathItem::save() - Calling write_to_db()', 0); } - return $this->write_to_db(); - } - - /** - * Sets the number of attempt_id to a given value - * @param integer The given value to set attempt_id to - * @return boolean TRUE on success, FALSE otherwise - */ - public function set_attempt_id($num) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_attempt_id()', 0); } - if ($num == strval(intval($num)) && $num >= 0) { - $this->attempt_id = $num; - return true; - } - return false; - } - - /** - * Sets the core_exit value to the one given - * @return bool True (always) - */ - public function set_core_exit($value) { - switch($value){ - case '': - $this->core_exit = ''; - break; - case 'suspend': - $this->core_exit = 'suspend'; - break; - default: - $this->core_exit = 'none'; - break; - } - return true; - } - - /** - * Sets the item's description - * @param string Description - * @return void - */ - public function set_description($string = '') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_description()', 0); } - if (!empty($string)) { $this->description = $string; } - } - - /** - * Sets the lesson_location value - * @param string lesson_location as provided by the SCO - * @return boolean True on success, false otherwise - */ - public function set_lesson_location($location) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_lesson_location()', 0); } - if (isset($location)) { - $this->lesson_location = Database::escape_string($location); - return true; - } - return false; - } - - /** - * Sets the item's depth level in the LP tree (0 is at root) - * @param integer Level - * @return void - */ - public function set_level($int = 0) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_level('.$int.')', 0); } - if (!empty($int) AND $int == strval(intval($int))) { $this->level = $int; } - } - - /** - * Sets the lp_view id this item view is registered to - * @param integer lp_view DB ID - * @return void - * @todo //todo insert into lp_item_view if lp_view not exists - */ - public function set_lp_view($lp_view_id) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_lp_view('.$lp_view_id.')', 0); } - if (!empty($lp_view_id) and $lp_view_id = intval(strval($lp_view_id))) { - $this->view_id = $lp_view_id; - $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); - // Get the lp_item_view with the highest view_count. - $sql = "SELECT * FROM $item_view_table WHERE lp_item_id = ".$this->get_id()." " . - " AND lp_view_id = ".$lp_view_id." ORDER BY view_count DESC"; - if (self::debug > 2) { error_log('New LP - In learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql, 0); } - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $row = Database::fetch_array($res); - $this->db_item_view_id = $row['id']; - $this->attempt_id = $row['view_count']; - $this->current_score = $row['score']; - $this->current_data = $row['suspend_data']; - $this->view_max_score = $row['max_score']; - //$this->view_min_score = $row['min_score']; - $this->status = $row['status']; - $this->current_start_time = $row['start_time']; - $this->current_stop_time = $this->current_start_time + $row['total_time']; - $this->lesson_location = $row['lesson_location']; - $this->core_exit = $row['core_exit']; - if (self::debug > 2) { error_log('New LP - In learnpathItem::set_lp_view() - Updated item object with database values', 0); } - - // Now get the number of interactions for this little guy. - $item_view_interaction_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); - $sql = "SELECT * FROM $item_view_interaction_table WHERE lp_iv_id = '".$this->db_item_view_id."'"; - $res = Database::query($sql); - if ($res !== false) { - $this->interactions_count = Database::num_rows($res); - } else { - $this->interactions_count = 0; - } - // Now get the number of objectives for this little guy. - $item_view_objective_table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); - $sql = "SELECT * FROM $item_view_objective_table WHERE lp_iv_id = '".$this->db_item_view_id."'"; - $res = Database::query($sql); - if ($res !== false) { - $this->objectives_count = Database::num_rows($res); - } else { - $this->objectives_count = 0; - } - } - } - // End - if (self::debug > 2) { error_log('New LP - End of learnpathItem::set_lp_view()', 0); } - } - - /** - * Sets the path - * @param string Path - * @return void - */ - public function set_path($string = '') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_path()', 0); } - if (!empty($string)) { $this->path = $string; } - } - - /** - * Sets the prevent_reinit attribute. This is based on the LP value and is set at creation time for - * each learnpathItem. It is a (bad?) way of avoiding a reference to the LP when saving an item. - * @param integer 1 for "prevent", 0 for "don't prevent" saving freshened values (new "not attempted" status etc) - * @return void - */ - public function set_prevent_reinit($prevent) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_prevent_reinit()', 0); } - if ($prevent) { - $this->prevent_reinit = 1; - } else { - $this->prevent_reinit = 0; - } - } - - /** - * Sets the score value. If the mastery_score is set and the score reaches - * it, then set the status to 'passed'. - * @param float Score - * @return boolean True on success, false otherwise - */ - public function set_score($score) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_score('.$score.')', 0); } - if (($this->max_score<=0 || $score <= $this->max_score) && ($score >= $this->min_score)) { - $this->current_score = $score; - $master = $this->get_mastery_score(); - $current_status = $this->get_status(false); - // If mastery_score is set AND the current score reaches the mastery score AND the current status is different from 'completed', then set it to 'passed'. - if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) { - $this->set_status($this->possible_status[3]); - } elseif ($master != -1 && $this->current_score < $master) { - $this->set_status($this->possible_status[4]); - } - return true; - } - return false; - } - - /** - * Sets the maximum score for this item - * @param int Maximum score - must be a decimal or an empty string - * @return boolean True on success, false on error - */ - public function set_max_score($score) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_max_score('.$score.')', 0); } - if (is_int($score) or $score == '') { - $this->view_max_score = Database::escape_string($score); - if (self::debug > 1) { error_log('New LP - In learnpathItem::set_max_score() - Updated object score of item '.$this->db_id.' to '.$this->view_max_score, 0); } - return true; - } - return false; - } - - /** - * Sets the status for this item - * @param string Status - must be one of the values defined in $this->possible_status - * @return boolean True on success, false on error - */ - public function set_status($status) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_status('.$status.')', 0); } - $found = false; - foreach ($this->possible_status as $possible) { - if (preg_match('/^'.$possible.'$/i', $status)) { - $found = true; - } - } - //if (in_array($status, $this->possible_status)) { - if ($found) { - $this->status = Database::escape_string($status); - if (self::debug > 1) { error_log('New LP - In learnpathItem::set_status() - Updated object status of item '.$this->db_id.' to '.$this->status, 0); } - return true; - } - //error_log('New LP - '.$status.' was not in the possible status', 0); - $this->status = $this->possible_status[0]; - return false; - } - - /** - * Set the terms for this learnpath item - * @param string Terms, as a comma-split list - * @return boolean Always return true - */ - public function set_terms($terms) { - global $charset; - $lp_item = Database::get_course_table(TABLE_LP_ITEM); - require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; - $a_terms = split(',', $terms); - $i_terms = split(',', $this->get_terms()); - foreach ($i_terms as $term) { - if (!in_array($term, $a_terms)) { array_push($a_terms, $term); } - } - $new_terms = $a_terms; - $new_terms_string = implode(',', $new_terms); - $terms_update_sql = ''; - // TODO: Validate csv string. - $terms_update_sql = "UPDATE $lp_item SET terms = '". Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset)) . "' WHERE id=".$this->get_id(); - $res = Database::query($terms_update_sql); - // Save it to search engine. - if (api_get_setting('search_enabled') == 'true') { - $di = new DokeosIndexer(); - $di->update_terms($this->get_search_did(), $new_terms); - } - return true; - } - - /** - * Get the document ID from inside the text index database - * @return int Search index database document ID - */ - public function get_search_did() { - return $this->search_did; - } - - /** - * Sets the item viewing time in a usable form, given that SCORM packages often give it as 00:00:00.0000 - * @param string Time as given by SCORM - * @return void - */ - public function set_time($scorm_time, $format = 'scorm') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_time('.$scorm_time.')', 0); } - if ($scorm_time == 0 and ($this->type!='sco') and $this->current_start_time != 0) { - $my_time = time() - $this->current_start_time; - if ($my_time > 0) { - $this->update_time($my_time); - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_time('.$scorm_time.') - found asset - set time to '.$my_time, 0); } - } - } else { - if ($format == 'scorm') { - $res = array(); - if (preg_match('/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/', $scorm_time, $res)) { - $time = time(); - $hour = $res[1]; - $min = $res[2]; - $sec = $res[3]; - // Getting total number of seconds spent. - $total_sec = $hour*3600 + $min*60 + $sec; - $this->update_time($total_sec); - } - } elseif ($format == 'int') { - $this->update_time($scorm_time); - } - } - } - - /** - * Sets the item's title - * @param string Title - * @return void - */ - public function set_title($string = '') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_title()', 0); } - if (!empty($string)) { $this->title = $string; } - } - - /** - * Sets the item's type - * @param string Type - * @return void - */ - public function set_type($string = '') { - if (self::debug > 0) { error_log('New LP - In learnpathItem::set_type()', 0); } - if (!empty($string)) { $this->type = $string; } - } - - /** - * Checks if the current status is part of the list of status given - * @param strings_array An array of status to check for. If the current status is one of the strings, return true - * @return boolean True if the status was one of the given strings, false otherwise - */ - public function status_is($list = array()) { - if (self::debug > 1) { error_log('New LP - In learnpathItem::status_is('.print_r($list,true).') on item '.$this->db_id, 0); } - $mystatus = $this->get_status(true); - if (empty($mystatus)) { - return false; - } - $found = false; - foreach ($list as $status) { - if (preg_match('/^'.$status.'$/i', $mystatus)) { - if (self::debug > 2) { error_log('New LP - learnpathItem::status_is() - Found status '.$status.' corresponding to current status', 0); } - $found = true; - return $found; - } - } - if (self::debug > 2) { error_log('New LP - learnpathItem::status_is() - Status '.$mystatus.' did not match request', 0); } - return $found; - } - - /** - * Updates the time info according to the given session_time - * @param integer Time in seconds - * @return void - * TODO: Make this method better by allowing better/multiple time slices. - */ - public function update_time($total_sec = 0) { - if (self::debug > 0) { error_log('New LP - In learnpathItem::update_time('.$total_sec.')', 0); } - if ($total_sec >= 0) { - // Getting start time from finish time. The only problem in the calculation is it might be - // modified by the scripts processing time. - $now = time(); - $start = $now-$total_sec; - $this->current_start_time = $start; - $this->current_stop_time = $now; - /*if (empty($this->current_start_time)) { - $this->current_start_time = $start; - $this->current_stop_time = $now; - } else { - //if ($this->current_stop_time != $this->current_start_time) { - // If the stop time has already been set before to something else - // than the start time, add the given time to what's already been - // recorder. - // This is the SCORM way of doing things, because the time comes from - // core.session_time, not core.total_time - // UPDATE: adding time to previous time is only done on SCORM's finish() - // call, not normally, so for now ignore this section. - //$this->current_stop_time = $this->current_stop_time + $stop; - //error_log('New LP - Adding '.$stop.' seconds - now '.$this->current_stop_time, 0); - //} else { - // If no previous stop time set, use the one just calculated now from - // start time. - //$this->current_start_time = $start; - //$this->current_stop_time = $now; - //error_log('New LP - Setting '.$stop.' seconds - now '.$this->current_stop_time, 0); - //} - }*/ - } - } - - /** - * Write objectives to DB. This method is separate from write_to_db() because otherwise - * objectives are lost as a side effect to AJAX and session concurrent access - * @return boolean True or false on error - */ - public function write_objectives_to_db() { - if (self::debug > 0) { error_log('New LP - In learnpathItem::write_objectives_to_db()', 0); } - if (is_array($this->objectives) && count($this->objectives) > 0) { - // Save objectives. - $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT id FROM $tbl " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $row = Database::fetch_array($res); - $lp_iv_id = $row[0]; - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Got item_view_id '.$lp_iv_id.', now checking objectives ', 0); } - foreach($this->objectives as $index => $objective){ - $iva_table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); - $iva_sql = "SELECT id FROM $iva_table " . - "WHERE lp_iv_id = $lp_iv_id " . - //"AND order_id = $index"; - //also check for the objective ID as it must be unique for this SCO view - "AND objective_id = '".Database::escape_string($objective[0])."'"; - $iva_res = Database::query($iva_sql); - // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) - if(Database::num_rows($iva_res)>0){ - // Update (or don't). - $iva_row = Database::fetch_array($iva_res); - $iva_id = $iva_row[0]; - $ivau_sql = "UPDATE $iva_table " . - "SET objective_id = '".Database::escape_string($objective[0])."'," . - "status = '".Database::escape_string($objective[1])."'," . - "score_raw = '".Database::escape_string($objective[2])."'," . - "score_min = '".Database::escape_string($objective[4])."'," . - "score_max = '".Database::escape_string($objective[3])."' " . - "WHERE id = $iva_id"; - $ivau_res = Database::query($ivau_sql); - //error_log($ivau_sql, 0); - }else{ - // Insert new one. - $ivai_sql = "INSERT INTO $iva_table " . - "(lp_iv_id, order_id, objective_id, status, score_raw, score_min, score_max )" . - "VALUES" . - "(".$lp_iv_id.", ".$index.",'".Database::escape_string($objective[0])."','".Database::escape_string($objective[1])."'," . - "'".Database::escape_string($objective[2])."','".Database::escape_string($objective[4])."','".Database::escape_string($objective[3])."')"; - $ivai_res = Database::query($ivai_sql); - //error_log($ivai_sql); - } - } - } - } else { - //error_log('no objective to save: '.print_r($this->objectives, 1)); - } - } - - /** - * Writes the current data to the database - * @return boolean Query result - */ - public function write_to_db() { - - // Check the session visibility. - if (!api_is_allowed_to_session_edit()) { - return false; - } - - if (self::debug > 0) { error_log('New LP - In learnpathItem::write_to_db()', 0); } - $mode = $this->get_lesson_mode(); - $credit = $this->get_credit(); - $my_verified_status=$this->get_status(false); - - $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql_verified = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" ;'; - $rs_verified = Database::query($sql_verified); - $row_verified = Database::fetch_array($rs_verified); - $my_case_completed = array('completed', 'passed', 'browsed', 'failed'); // Added by Isaac Flores. - if (in_array($sql_verified['status'], $my_case_completed)) { - $save = false; - } else { - $save = true; - } - - if (($save === false && $this->type == 'sco') ||(($this->type == 'sco') && ($credit == 'no-credit' OR $mode == 'review' OR $mode == 'browse'))) { - // This info shouldn't be saved as the credit or lesson mode info prevent it. - if (self::debug > 1) { error_log('New LP - In learnpathItem::write_to_db() - credit('.$credit.') or lesson_mode('.$mode.') prevent recording!', 0); } - } else { - // Check the row exists. - $inserted = false; - - // This a special case for multiple attempts and Chamilo exercises. - if ($this->type == 'quiz' && $this->get_prevent_reinit() == 0 && $this->get_status() == 'completed') { - // We force the item to be restarted. - $this->restart(); - - $sql = "INSERT INTO $item_view_table " . - "(total_time, " . - "start_time, " . - "score, " . - "status, " . - "max_score, ". - "lp_item_id, " . - "lp_view_id, " . - "view_count, " . - "suspend_data, " . - //"max_time_allowed," . - "lesson_location)" . - "VALUES" . - "(".$this->get_total_time()."," . - "".$this->current_start_time."," . - "".$this->get_score()."," . - "'".$this->get_status(false)."'," . - "'".$this->get_max()."'," . - "".$this->db_id."," . - "".$this->view_id."," . - "".$this->get_attempt_id()."," . - "'".Database::escape_string($this->current_data)."'," . - //"'".$this->get_max_time_allowed()."'," . - "'".$this->lesson_location."')"; - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Inserting into item_view forced: '.$sql, 0); } - $res = Database::query($sql); - $this->db_item_view_id = Database::insert_id(); - $inserted = true; - } - - $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $check = "SELECT * FROM $item_view_table " . - "WHERE lp_item_id = ".$this->db_id. " " . - "AND lp_view_id = ".$this->view_id. " ". - "AND view_count = ".$this->get_attempt_id(); - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Querying item_view: '.$check, 0); } - $check_res = Database::query($check); - // Depending on what we want (really), we'll update or insert a new row - // now save into DB. - $res = 0; - if (!$inserted && Database::num_rows($check_res) < 1) { - /*$my_status = ''; - if ($this->type != TOOL_QUIZ) { - $my_status = $this->get_status(false); - }*/ - - $sql = "INSERT INTO $item_view_table " . - "(total_time, " . - "start_time, " . - "score, " . - "status, " . - "max_score, ". - "lp_item_id, " . - "lp_view_id, " . - "view_count, " . - "suspend_data, " . - //"max_time_allowed," . - "lesson_location)" . - "VALUES" . - "(".$this->get_total_time()."," . - "".$this->current_start_time."," . - "".$this->get_score()."," . - "'".$this->get_status(false)."'," . - "'".$this->get_max()."'," . - "".$this->db_id."," . - "".$this->view_id."," . - "".$this->get_attempt_id()."," . - "'".Database::escape_string($this->current_data)."'," . - //"'".$this->get_max_time_allowed()."'," . - "'".$this->lesson_location."')"; - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Inserting into item_view: '.$sql, 0); } - $res = Database::query($sql); - $this->db_item_view_id = Database::insert_id(); - } else { - $sql = ''; - if ($this->type == 'hotpotatoes') { - $sql = "UPDATE $item_view_table " . - "SET total_time = ".$this->get_total_time().", " . - " start_time = ".$this->get_current_start_time().", " . - " score = ".$this->get_score().", " . - " status = '".$this->get_status(false)."'," . - " max_score = '".$this->get_max()."'," . - " suspend_data = '".Database::escape_string($this->current_data)."'," . - " lesson_location = '".$this->lesson_location."' " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - } else { - // For all other content types... - if ($this->type == 'quiz') { - $my_status = ' '; - $total_time = ' '; - if (!empty($_REQUEST['exeId'])) { - $TBL_TRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); - - $safe_exe_id = Database::escape_string($_REQUEST['exeId']); - $sql = 'SELECT start_date,exe_date FROM ' . $TBL_TRACK_EXERCICES . ' WHERE exe_id = '.(int)$safe_exe_id; - $res = Database::query($sql); - $row_dates = Database::fetch_array($res); - - $time_start_date = convert_mysql_date($row_dates['start_date']); - $time_exe_date = convert_mysql_date($row_dates['exe_date']); - $mytime = ((int)$time_exe_date-(int)$time_start_date); - $total_time =" total_time = ".$mytime.", "; - } - } else { - $my_type_lp = learnpath::get_type_static($this->lp_id); - // This is a array containing values finished - $case_completed = array('completed', 'passed', 'browsed'); - - if ($this->get_prevent_reinit() == 1) { - // Multiple attempts are prevented. - - // Process of status verified into data base. - $sql_verified = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" ;'; - $rs_verified = Database::query($sql_verified); - $row_verified = Database::fetch_array($rs_verified); - - // Get type lp: 1=lp dokeos and 2=scorm. - // If not is completed or passed or browsed and learning path is scorm. - if (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2 ) { //&& $this->type!='dir' - $total_time =" total_time = total_time +".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } else { - // Verified into data base. - if (!in_array($row_verified['status'], $case_completed) && $my_type_lp == 2 ) { //&& $this->type!='dir' - $total_time =" total_time = total_time +".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } elseif (in_array($row_verified['status'], $case_completed) && $my_type_lp == 2 && $this->type != 'sco' ) { //&& $this->type!='dir' - $total_time =" total_time = total_time +".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } else { - //&& !in_array($row_verified['status'], $case_completed) - //is lp dokeos - if ($my_type_lp == 1 && $this->type != 'chapter') { - $total_time = " total_time = total_time + ".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } - } - } - } else { - // Multiple attempts are allowed. - if (in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) { - // Reset zero new attempt ? - $my_status = " status = '".$this->get_status(false)."' ,"; - } elseif (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) { - $total_time =" total_time = ".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } else { - // It is dokeos LP. - $total_time =" total_time = total_time +".$this->get_total_time().", "; - $my_status = " status = '".$this->get_status(false)."' ,"; - } - - // Code added by Isaac Flores. - // This code line fixes the problem of wrong status. - if ($my_type_lp == 2) { - // Verify current status in multiples attempts. - $sql_status = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" '; - $rs_status = Database::query($sql_status); - $current_status = Database::result($rs_status, 0, 'status'); - if (in_array($current_status, $case_completed)) { - $my_status = ''; - $total_time = ''; - } else { - $total_time = " total_time = total_time +".$this->get_total_time().", "; - } - } - } - /*if ($my_type_lp == 1 && !in_array($row_verified['status'], $case_completed)) { - $total_time =" total_time = total_time + ".$this->get_total_time().", "; - }*/ - } - - $sql = "UPDATE $item_view_table " . - "SET " .$total_time. - " start_time = ".$this->get_current_start_time().", " . - " score = ".$this->get_score().", " . - $my_status. - " max_score = '".$this->get_max()."'," . - " suspend_data = '".Database::escape_string($this->current_data)."'," . - //" max_time_allowed = '".$this->get_max_time_allowed()."'," . - " lesson_location = '".$this->lesson_location."' " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - - $this->current_start_time = time(); - } - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Updating item_view: '.$sql, 0); } - $res = Database::query($sql); - } - //if(!$res) - //{ - // $this->error = 'Could not update item_view table...'.Database::error(); - //} - if (is_array($this->interactions) && count($this->interactions) > 0) { - // Save interactions. - $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $sql = "SELECT id FROM $tbl " . - "WHERE lp_item_id = ".$this->db_id." " . - "AND lp_view_id = ".$this->view_id." " . - "AND view_count = ".$this->attempt_id; - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - $row = Database::fetch_array($res); - $lp_iv_id = $row[0]; - if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Got item_view_id '.$lp_iv_id.', now checking interactions ', 0); } - foreach ($this->interactions as $index => $interaction) { - $correct_resp = ''; - if (is_array($interaction[4]) && !empty($interaction[4][0])) { - foreach ($interaction[4] as $resp) { - $correct_resp .= $resp.','; - } - $correct_resp = substr($correct_resp, 0, strlen($correct_resp) - 1); - } - $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); - $iva_sql = "SELECT id FROM $iva_table " . - "WHERE lp_iv_id = $lp_iv_id " . + public $attempt_id; // Also called "objectives" SCORM-wise. + public $audio; // The path to an audio file (stored in document/audio/). + public $children = array(); // Contains the ids of children items. + public $condition; // If this item has a special condition embedded. + public $current_score; + public $current_start_time; + public $current_stop_time; + public $current_data = ''; + public $db_id; + public $db_item_view_id = ''; + public $description = ''; + public $file; + // At the moment, interactions are just an array of arrays with a structure of 8 text fields + // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) + public $interactions = array(); + public $interactions_count = 0; + public $objectives = array(); + public $objectives_count = 0; + public $launch_data = ''; + public $lesson_location = ''; + public $level = 0; + //var $location; // Only set this for SCORM? + public $lp_id; + public $max_score; + public $mastery_score; + public $min_score; + public $max_time_allowed = ''; + public $name; + public $next; + public $parent; + public $path; // In some cases the exo_id = exercise_id in courseDb exercices table. + public $possible_status = array('not attempted','incomplete','completed','passed','failed','browsed'); + public $prereq_string = ''; + public $prereq_alert = ''; + public $prereqs = array(); + public $previous; + public $prevent_reinit = 1; // 0 = multiple attempts 1 = one attempt + public $ref; + public $save_on_close = true; + public $search_did = null; + public $status; + public $title; + public $type; // This attribute can contain chapter|link|student_publication|module|quiz|document|forum|thread + public $view_id; + + const debug = 0; // Logging parameter. + + /** + * Class constructor. Prepares the learnpath_item for later launch + * + * Don't forget to use set_lp_view() if applicable after creating the item. + * Setting an lp_view will finalise the item_view data collection + * @param integer Learnpath item ID + * @param integer User ID + * @return boolean True on success, false on failure + */ + public function learnpathItem($db_id, $user_id) { + // Get items table. + if (self::debug > 0) { error_log('New LP - In learnpathItem constructor: '.$db_id.','.$user_id, 0); } + $items_table = Database::get_course_table(TABLE_LP_ITEM); + $id = (int) $db_id; + $sql = "SELECT * FROM $items_table WHERE id = $id"; + //error_log('New LP - Creating item object from DB: '.$sql, 0); + $res = @Database::query($sql); + if (Database::num_rows($res) < 1) { + $this->error = 'Could not find given learnpath item in learnpath_item table'; + //error_log('New LP - '.$this->error, 0); + return false; + } + $row = Database::fetch_array($res); + $this->lp_id = $row['lp_id']; + $this->max_score = $row['max_score']; + $this->min_score = $row['min_score']; + $this->name = $row['title']; + $this->type = $row['item_type']; + $this->ref = $row['ref']; + $this->title = $row['title']; + $this->description = $row['description']; + $this->path = $row['path']; + $this->mastery_score = $row['mastery_score']; + $this->parent = $row['parent_item_id']; + $this->next = $row['next_item_id']; + $this->previous = $row['previous_item_id']; + $this->display_order = $row['display_order']; + $this->prereq_string = $row['prerequisite']; + $this->max_time_allowed = $row['max_time_allowed']; + if (isset($row['launch_data'])){ + $this->launch_data = $row['launch_data']; + } + $this->save_on_close = true; + $this->db_id = $id; + + // Get search_did. + if (api_get_setting('search_enabled')=='true') { + $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); + $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; + // TODO: Verify if it's possible to assume the actual course instead of getting it from db. + $sql = sprintf($sql, $tbl_se_ref, api_get_course_id(), TOOL_LEARNPATH, $this->lp_id, $id); + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $se_ref = Database::fetch_array($res); + $this->search_did = (int)$se_ref['search_did']; + } + } + $this->audio = $row['audio']; + + //error_log('New LP - End of learnpathItem constructor for item '.$id, 0); + return true; + } + + /** + * Adds a child to the current item + */ + public function add_child($item) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::add_child()', 0); } + if (!empty($item)) { + // Do not check in DB as we expect the call to come from the learnpath class which should + // be aware of any fake. + $this->children[] = $item; + } + } + + /** + * Adds an interaction to the current item + * @param int Index (order ID) of the interaction inside this item + * @param array Array of parameters: id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) + * @result void + */ + public function add_interaction($index, $params) { + $this->interactions[$index] = $params; + // Take the current maximum index to generate the interactions_count. + if(($index+1)>$this->interactions_count){ + $this->interactions_count = $index + 1; + } + /* + if (is_array($this->interactions[$index]) && count($this->interactions[$index]) > 0) { + $this->interactions[$index] = $params; + return false; + } else { + if (count($params)==8) { // We rely on the calling script to provide parameters in the right order. + $this->interactions[$index] = $params; + return true; + } else { + return false; + } + } + */ + } + + /** + * Adds an objective to the current item + * @param array Array of parameters: id(0), status(1), score_raw(2), score_max(3), score_min(4) + * @result void + */ + public function add_objective($index, $params) { + if(empty($params[0])){return null;} + $this->objectives[$index] = $params; + // Take the current maximum index to generate the objectives_count. + if ((count($this->objectives) + 1) > $this->objectives_count) { + $this->objectives_count = (count($this->objectives) + 1); + } + } + + /** + * Closes/stops the item viewing. Finalises runtime values. If required, save to DB. + * @return boolean True on success, false otherwise + */ + public function close() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::close()', 0); } + $this->current_stop_time = time(); + $type = $this->get_type(); + if ($type != 'sco') { + if ($type == TOOL_QUIZ or $type == TOOL_HOTPOTATOES) { + $this->get_status(true,true); // Update status (second option forces the update). + } else { + $this->status = $this->possible_status[2]; + } + } + if ($this->save_on_close) { + $this->save(); + } + return true; + } + + /** + * Deletes all traces of this item in the database + * @return boolean true. Doesn't check for errors yet. + */ + public function delete() { + if (self::debug > 0) { error_log('New LP - In learnpath_item::delete() for item '.$this->db_id, 0); } + $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $lp_item = Database::get_course_table(TABLE_LP_ITEM); + $sql_del_view = "DELETE FROM $lp_item_view WHERE lp_item_id = ".$this->db_id; + //error_log('New LP - Deleting from lp_item_view: '.$sql_del_view, 0); + $res_del_view = Database::query($sql_del_view); + + $sql_sel = "SELECT * FROM $lp_item WHERE id = ".$this->db_id; + $res_sel = Database::query($sql_sel); + if (Database::num_rows($res_sel) < 1) { return false; } + $row = Database::fetch_array($res_sel); + + $sql_del_item = "DELETE FROM $lp_item WHERE id = ".$this->db_id; + //error_log('New LP - Deleting from lp_item: '.$sql_del_view, 0); + $res_del_item = Database::query($sql_del_item); + + if (api_get_setting('search_enabled') == 'true') { + if (!is_null($this->search_did)) { + require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; + $di = new DokeosIndexer(); + $di->remove_document($this->search_did); + } + } + + return true; + } + + /** + * Drops a child from the children array + * @param string index of child item to drop + * @return void + */ + public function drop_child($item) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::drop_child()', 0); } + if (!empty($item)) { + foreach ($this->children as $index => $child) { + if ($child == $item) { + $this->children[$index] = null; + } + } + } + } + + /** + * Gets the current attempt_id for this user on this item + * @return integer The attempt_id for this item view by this user, or 1 if none defined + */ + public function get_attempt_id() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_attempt_id() on item '.$this->db_id, 0); } + $res = 1; + if (!empty($this->attempt_id)) { + $res = $this->attempt_id; + } + if (self::debug > 0) { error_log('New LP - End of learnpathItem::get_attempt_id() on item '.$this->db_id.' - Returning '.$res, 0); } + return $res; + } + + /** + * Gets a list of the item's children + * @return array Array of children items IDs + */ + public function get_children() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_children()', 0); } + $list = array(); + foreach ($this->children as $child) { + if (!empty($child)) { + //error_log('New LP - Found '.$child, 0); + $list[] = $child; + } + } + return $list; + } + + /** + * Gets the core_exit value from the database + */ + public function get_core_exit() { + return $this->core_exit; + } + + /** + * Gets the credit information (rather scorm-stuff) based on current status and reinit + * autorization. Credit tells the sco(content) if Chamilo will record the data it is sent (credit) or not (no-credit) + * @return string 'credit' or 'no-credit'. Defaults to 'credit' because if we don't know enough about this item, it's probably because it was never used before. + */ + public function get_credit() { + if (self::debug > 1) {error_log('New LP - In learnpathItem::get_credit()', 0); } + $credit = 'credit'; + // Now check the value of prevent_reinit (if it's 0, return credit as the default was). + if ($this->get_prevent_reinit() != 0) { // If prevent_reinit == 1 (or more). + // If status is not attempted or incomplete, credit anyway. Otherwise: + // Check the status in the database rather than in the object, as checking in the object + // would always return "no-credit" when we want to set it to completed. + $status = $this->get_status(true); + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_credit() - get_prevent_reinit!=0 and status is '.$status, 0); } + if ($status != $this->possible_status[0] && $status != $this->possible_status[1]) { + $credit = 'no-credit'; + } + } + return $credit; + } + + /** + * Gets the current start time property + * @return integer Current start time, or current time if none + */ + public function get_current_start_time() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_current_start_time()', 0); } + if (empty($this->current_start_time)) { + return time(); + } else { + return $this->current_start_time; + } + } + + /** + * Gets the item's description + * @return string Description + */ + public function get_description() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_description()', 0); } + if (empty($this->description)) { return ''; } + return $this->description; + } + + /** + * Gets the file path from the course's root directory, no matter what tool it is from. + * @return string The file path, or an empty string if there is no file attached, or '-1' if the file must be replaced by an error page + */ + public function get_file_path($path_to_scorm_dir = '') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_file_path()', 0); } + $path = $this->get_path(); + $type = $this->get_type(); + if (empty($path)) { + if ($type == 'dokeos_chapter' || $type == 'chapter' || $type == 'dir') { + return ''; + } else { + return '-1'; + } + } elseif ($path == strval(intval($path))) { + // The path is numeric, so it is a reference to a Chamilo object. + switch ($type) { + case 'dokeos_chapter': + case 'dir': + case 'chapter': + return ''; + case TOOL_DOCUMENT: + $table_doc = Database::get_course_table(TABLE_DOCUMENT); + $sql = 'SELECT path FROM '.$table_doc.' WHERE id = '.$path; + $res = Database::query($sql); + $row = Database::fetch_array($res); + $real_path = 'document'.$row['path']; + return $real_path; + case TOOL_STUDENTPUBLICATION: + case TOOL_QUIZ: + case TOOL_FORUM: + case TOOL_THREAD: + case TOOL_LINK: + default: + return '-1'; + } + } else { + if (!empty($path_to_scorm_dir)) { + $path = $path_to_scorm_dir.$path; + } + return $path; + } + } + + /** + * Gets the DB ID + * @return integer Database ID for the current item + */ + public function get_id() { + if (self::debug > 1) {error_log('New LP - In learnpathItem::get_id()', 0); } + if (!empty($this->db_id)) { + return $this->db_id; + } + // TODO: Check this return value is valid for children classes (SCORM?). + return 0; + } + + /** + * Loads the interactions into the item object, from the database. + * If object interactions exist, they will be overwritten by this function, + * using the database elements only. + * @return void Directly sets the interactions attribute in memory + */ + public function load_interactions() { + $this->interactions = array(); + $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT id FROM $tbl " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $row = Database::fetch_array($res); + $lp_iv_id = $row[0]; + $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); + $iva_sql = "SELECT * FROM $iva_table " . + "WHERE lp_iv_id = $lp_iv_id "; + $res_sql = Database::query($iva_sql); + while ($row = Database::fetch_array($res_sql)) { + $this->interactions[$row['interaction_id']] = array($row['interaction_id'], $row['interaction_type'], $row['weighting'], $row['completion_time'], $row['correct_responses'], $row['student_responses'], $row['result'], $row['latency']); + } + } + } + + /** + * Gets the current count of interactions recorded in the database + * @param bool Whether to count from database or not (defaults to no) + * @return int The current number of interactions recorder + */ + public function get_interactions_count($checkdb = false) { + if (self::debug > 1) { error_log('New LP - In learnpathItem::get_interactions_count()', 0); } + $return = 0; + if ($checkdb) { + $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT id FROM $tbl " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $row = Database::fetch_array($res); + $lp_iv_id = $row[0]; + $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); + $iva_sql = "SELECT count(id) as mycount FROM $iva_table " . + "WHERE lp_iv_id = $lp_iv_id "; + $res_sql = Database::query($iva_sql); + if (Database::num_rows($res_sql) > 0) { + $row = Database::fetch_array($res_sql); + $return = $row['mycount']; + } + } + } else { + if(!empty($this->interactions_count)){ + $return = $this->interactions_count; + } + } + return $return; + } + + /** + * Gets the JavaScript array content to fill the interactions array. + * @params bool Whether to check directly into the database (default no) + * @return string An empty string if no interaction, a JS array definition otherwise + */ + public function get_interactions_js_array($checkdb = false) { + $return = ''; + if ($checkdb) { + $this->load_interactions(true); + } + foreach ($this->interactions as $id => $in) { + $return .= "['$id','".$in[1]."','".$in[2]."','".$in[3]."','".$in[4]."','".$in[5]."','".$in[6]."','".$in[7]."'],"; + } + if (!empty($return)) { + $return = substr($return, 0, -1); + } + return $return; + } + + /** + * Gets the current count of objectives recorded in the database + * @return int The current number of objectives recorder + */ + public function get_objectives_count() { + if (self::debug > 1) { error_log('New LP - In learnpathItem::get_objectives_count()', 0);} + $res = 0; + if (!empty($this->objectives_count)) { + $res = $this->objectives_count; + } + return $res; + } + + /** + * Gets the launch_data field found in imsmanifests (this is SCORM- or AICC-related, really) + * @return string Launch data as found in imsmanifest and stored in Chamilo (read only). Defaults to ''. + */ + public function get_launch_data() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_launch_data()', 0); } + if (!empty($this->launch_data)) { + return $this->launch_data; + } + return ''; + } + + /** + * Gets the lesson location + * @return string lesson location as recorded by the SCORM and AICC elements. Defaults to '' + */ + public function get_lesson_location() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_lesson_location()', 0); } + if (!empty($this->lesson_location)) { return $this->lesson_location; } else { return ''; } + } + + /** + * Gets the lesson_mode (scorm feature, but might be used by aicc as well as dokeos paths) + * + * The "browse" mode is not supported yet (because there is no such way of seeing a sco in Chamilo) + * @return string 'browse','normal' or 'review'. Defaults to 'normal' + */ + public function get_lesson_mode() { + $mode = 'normal'; + if ($this->get_prevent_reinit() != 0) { // If prevent_reinit == 0 + $my_status = $this->get_status(); + if ($my_status != $this->possible_status[0] && $my_status != $this->possible_status[1]) { + $mode = 'review'; + } + } + return $mode; + } + + /** + * Gets the depth level + * @return int Level. Defaults to 0 + */ + public function get_level() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_level()', 0); } + if (empty($this->level)) { return 0; } + return $this->level; + } + + /** + * Gets the mastery score + */ + public function get_mastery_score() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_mastery_score()', 0); } + if (isset($this->mastery_score)) { return $this->mastery_score; } else { return -1; } + } + + /** + * Gets the maximum (score) + * @return int Maximum score. Defaults to 100 if nothing else is defined + */ + public function get_max(){ + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_max()', 0); } + if ($this->type == 'sco') { + if (!empty($this->view_max_score) && $this->view_max_score > 0) { + return $this->view_max_score; + } elseif ($this->view_max_score === '') { + return $this->view_max_score; + } else { + if (!empty($this->max_score)) { return $this->max_score; } else { return 100; } + } + } else { + if (!empty($this->max_score)) { return $this->max_score; } else { return 100; } + } + } + + /** + * Gets the maximum time allowed for this user in this attempt on this item + * @return string Time string in SCORM format (HH:MM:SS or HH:MM:SS.SS or HHHH:MM:SS.SS) + */ + public function get_max_time_allowed() { + if (self::debug > 0) {error_log('New LP - In learnpathItem::get_max_time_allowed()', 0); } + if (!empty($this->max_time_allowed)) { return $this->max_time_allowed; } else { return ''; } + } + + /** + * Gets the minimum (score) + * @return int Minimum score. Defaults to 0 + */ + public function get_min() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_min()', 0); } + if (!empty($this->min_score)) { return $this->min_score; } else { return 0; } + } + + /** + * Gets the parent ID + * @return int Parent ID. Defaults to null + */ + public function get_parent(){ + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_parent()', 0); } + if (!empty($this->parent)) { + return $this->parent; + } + // TODO: Check this return value is valid for children classes (SCORM?). + return null; + } + + /** + * Gets the path attribute. + * @return string Path. Defaults to '' + */ + public function get_path(){ + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_path()', 0); } + if (empty($this->path)) { return ''; } + return $this->path; + } + + /** + * Gets the prerequisites string + * @return string Empty string or prerequisites string if defined. Defaults to + */ + public function get_prereq_string() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_prereq_string()', 0); } + if (!empty($this->prereq_string)) { + return $this->prereq_string; + } else { + return ''; + } + } + + /** + * Gets the prevent_reinit attribute value (and sets it if not set already) + * @return int 1 or 0 (defaults to 1) + */ + public function get_prevent_reinit() { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_prevent_reinit()', 0); } + if (!isset($this->prevent_reinit)) { + if (!empty($this->lp_id)) { + $db = Database::get_course_table(TABLE_LP_MAIN); + $sql = "SELECT * FROM $db WHERE id = ".$this->lp_id; + $res = @Database::query($sql); + if (Database::num_rows($res) < 1) { + $this->error = "Could not find parent learnpath in learnpath table"; + if (self::debug > 2) { error_log('New LP - End of learnpathItem::get_prevent_reinit() - Returning false', 0); } + return false; + } else { + $row = Database::fetch_array($res); + $this->prevent_reinit = $row['prevent_reinit']; + } + } else { + $this->prevent_reinit = 1; // Prevent reinit is always 1 by default - see learnpath.class.php. + } + } + if (self::debug > 2) { error_log('New LP - End of learnpathItem::get_prevent_reinit() - Returned '.$this->prevent_reinit, 0); } + return $this->prevent_reinit; + } + + /** + * Gets the item's reference column + * @return string The item's reference field (generally used for SCORM identifiers) + */ + public function get_ref() { + return $this->ref; + } + + /** + * Gets the list of included resources as a list of absolute or relative paths of + * resources included in the current item. This allows for a better SCORM export. + * The list will generally include pictures, flash objects, java applets, or any other + * stuff included in the source of the current item. The current item is expected + * to be an HTML file. If it is not, then the function will return and empty list. + * @param string type (one of the Chamilo tools) - optional (otherwise takes the current item's type) + * @param string path (absolute file path) - optional (otherwise takes the current item's path) + * @param int level of recursivity we're in + * @return array List of file paths. An additional field containing 'local' or 'remote' helps determine if the file should be copied into the zip or just linked + */ + public function get_resources_from_source($type = null, $abs_path = null, $recursivity = 1) { + $max = 5; + if ($recursivity > $max) { + return array(); + } + if (!isset($type)) { + $type = $this->get_type(); + } + if (!isset($abs_path)) { + $path = $this->get_file_path(); + $abs_path = api_get_path(SYS_COURSE_PATH).api_get_course_path().'/'.$path; + //echo "Abs path coming from item : ".$abs_path."<br />\n"; + } + /* + else { + echo "Abs path coming from param: ".$abs_path."<br />\n"; + } + */ + //error_log(str_repeat(' ',$recursivity).'Analyse file '.$abs_path, 0); + $files_list = array(); + $type = $this->get_type(); + switch ($type) { + case TOOL_DOCUMENT : + case TOOL_QUIZ: + case 'sco': + // Get the document and, if HTML, open it. + + if (is_file($abs_path)) { + // for now, read the whole file in one go (that's gonna be a problem when the file is too big). + $info = pathinfo($abs_path); + $ext = $info['extension']; + switch(strtolower($ext)) { + case 'html': + case 'htm': + case 'shtml': + case 'css': + $wanted_attributes = array('src', 'url', '@import', 'href', 'value'); + // Parse it for included resources. + $file_content = file_get_contents($abs_path); + // Get an array of attributes from the HTML source. + $attributes = DocumentManager::parse_HTML_attributes($file_content, $wanted_attributes); + // Look at 'src' attributes in this file. + foreach ($wanted_attributes as $attr) { + if (isset($attributes[$attr])) { + // Find which kind of path these are (local or remote). + $sources = $attributes[$attr]; + + foreach ($sources as $source) { + // Skip what is obviously not a resource. + if (strpos($source, "+this.")) continue; // javascript code - will still work unaltered. + if (strpos($source, '.') === false) continue; // No dot, should not be an external file anyway. + if (strpos($source, 'mailto:')) continue; // mailto link. + if (strpos($source, ';') && !strpos($source, '&')) continue; // Avoid code - that should help. + + if ($attr == 'value') { + if (strpos($source , 'mp3file')) { + $files_list[] = array(substr($source, 0, strpos($source , '.swf') + 4), 'local', 'abs'); + $mp3file = substr($source , strpos($source , 'mp3file=') + 8); + if (substr($mp3file, 0, 1) == '/') + $files_list[] = array($mp3file, 'local', 'abs'); + else + $files_list[] = array($mp3file, 'local', 'rel'); + } + elseif (strpos($source, 'flv=') === 0) { + $source = substr($source, 4); + if (strpos($source, '&') > 0) { + $source = substr($source, 0, strpos($source, '&')); + } + if (strpos($source, '://') > 0) { + if (strpos($source, api_get_path(WEB_PATH)) !== false) { + // We found the current portal url. + $files_list[] = array($source, 'local', 'url'); + } else { + // We didn't find any trace of current portal. + $files_list[] = array($source, 'remote', 'url'); + } + } else { + $files_list[] = array($source, 'local', 'abs'); + } + continue; // Skipping anything else to avoid two entries (while the others can have sub-files in their url, flv's can't). + } + } + if (strpos($source, '://') > 0) { + + // Cut at '?' in a URL with params. + if (strpos($source, '?') > 0) { + $second_part = substr($source, strpos($source, '?')); + if (strpos($second_part, '://') > 0) { + // If the second part of the url contains a url too, treat the second one before cutting. + + $pos1 = strpos($second_part, '='); + $pos2 = strpos($second_part, '&'); + $second_part = substr($second_part, $pos1 + 1, $pos2 - ($pos1 + 1)); + if (strpos($second_part, api_get_path(WEB_PATH)) !== false) { + // We found the current portal url. + $files_list[] = array($second_part, 'local', 'url'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $second_part, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // We didn't find any trace of current portal. + $files_list[] = array($second_part, 'remote', 'url'); + } + } + elseif(strpos($second_part, '=') > 0) { + if (substr($second_part, 0, 1) === '/') { + // Link starts with a /, making it absolute (relative to DocumentRoot). + $files_list[] = array($second_part, 'local', 'abs'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $second_part, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + elseif(strstr($second_part, '..') === 0) { + // Link is relative but going back in the hierarchy. + $files_list[] = array($second_part, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$second_part); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // No starting '/', making it relative to current document's path. + if (substr($second_part, 0, 2) == './') { + $second_part = substr($second_part, 2); + } + $files_list[] = array($second_part, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$second_part); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + } + // Leave that second part behind now. + $source = substr($source, 0, strpos($source, '?')); + if (strpos($source,'://') > 0) { + if (strpos($source, api_get_path(WEB_PATH)) !== false) { + // We found the current portal url. + $files_list[] = array($source, 'local', 'url'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // We didn't find any trace of current portal. + $files_list[] = array($source, 'remote', 'url'); + } + } else { + // No protocol found, make link local. + if (substr($source, 0, 1) === '/') { + // Link starts with a /, making it absolute (relative to DocumentRoot). + $files_list[] = array($source, 'local', 'abs'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + elseif (strstr($source, '..') === 0) { + // Link is relative but going back in the hierarchy. + $files_list[] = array($source, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$source); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // No starting '/', making it relative to current document's path. + if (substr($source, 0, 2) == './') { + $source = substr($source, 2); + } + $files_list[] = array($source, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$source); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + } + } + // Found some protocol there. + if (strpos($source, api_get_path(WEB_PATH)) !== false) { + // We found the current portal url. + $files_list[] = array($source, 'local', 'url'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // We didn't find any trace of current portal. + $files_list[] = array($source, 'remote', 'url'); + } + } else { + // No protocol found, make link local. + if (substr($source, 0, 1) === '/') { + // Link starts with a /, making it absolute (relative to DocumentRoot). + $files_list[] = array($source, 'local', 'abs'); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $source, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + elseif (strstr($source, '..') === 0) { + // Link is relative but going back in the hierarchy. + $files_list[] = array($source, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$source); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } else { + // No starting '/', making it relative to current document's path. + if (substr($source, 0, 2) == './') { + $source = substr($source, 2); + } + $files_list[] = array($source, 'local', 'rel'); + $dir = dirname($abs_path); + $new_abs_path = realpath($dir.'/'.$source); + $in_files_list[] = learnpathItem::get_resources_from_source(TOOL_DOCUMENT, $new_abs_path, $recursivity + 1); + if (count($in_files_list) > 0) { + $files_list = array_merge($files_list, $in_files_list); + } + } + } + } + } + } + break; + default: + break; + } + + } else { + // The file could not be found. + return false; + } + break; + default: // Ignore. + break; + } + //error_log(str_repeat(' ', $recursivity), 'found files '.print_r($files_list, true), 0); + //return $files_list; + $checked_files_list = array(); + $checked_array_list = array(); + foreach ($files_list as $idx => $file) { + if (!empty($file[0])) { + if (!in_array($file[0], $checked_files_list)) { + $checked_files_list[] = $files_list[$idx][0]; + $checked_array_list[] = $files_list[$idx]; + } + } + } + return $checked_array_list; + } + + /** + * Gets the score + * @return float The current score or 0 if no score set yet + */ + public function get_score() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_score()', 0); } + $res = 0; + if (!empty($this->current_score)) { + $res = $this->current_score; + } + if (self::debug > 1) { error_log('New LP - Out of learnpathItem::get_score() - returning '.$res, 0); } + return $res; + } + + /** + * Gets the item status + * @param boolean Do or don't check into the database for the latest value. Optional. Default is true + * @param boolean Do or don't update the local attribute value with what's been found in DB + * @return string Current status or 'Nnot attempted' if no status set yet + */ + public function get_status($check_db = true, $update_local = false) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_status() on item '.$this->db_id, 0); } + if ($check_db) { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status(): checking db', 0); } + $table = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT * FROM $table WHERE id = '".$this->db_item_view_id."' AND view_count = '".$this->get_attempt_id()."'"; + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Checking DB: '.$sql, 0); } + + $res = Database::query($sql); + if (Database::num_rows($res) == 1) { + $row = Database::fetch_array($res); + if ($update_local) { + $this->set_status($row['status']); + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning db value '.$row['status'], 0); } + return $row['status']; + } + } else { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - in get_status: using attrib', 0); } + if (!empty($this->status)) { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning attrib: '.$this->status, 0); } + return $this->status; + } + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_status() - Returning default '.$this->possible_status[0], 0); } + return $this->possible_status[0]; + } + + /** + * Gets the suspend data + */ + public function get_suspend_data() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_suspend_data()', 0); } + // TODO: Improve cleaning of breaklines ... it works but is it really a beautiful way to do it ? + if (!empty($this->current_data)) { return str_replace(array("\r", "\n"), array('\r', '\n'), $this->current_data); } else { return ''; } + } + + /** + * Gets the total time spent on this item view so far + * @param string Origin of the request. If coming from PHP, send formatted as xxhxx'xx", otherwise use scorm format 00:00:00 + * @param integer Given time is a default time to return formatted + */ + public function get_scorm_time($origin = 'php', $given_time = null, $query_db = false) { + $h = get_lang('h'); + if (!isset($given_time)) { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time(): given time empty, current_start_time = '.$this->current_start_time, 0); } + if (is_object($this)) { + if ($query_db === true) { + $table = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT start_time, total_time FROM $table WHERE id = '".$this->db_item_view_id."' AND view_count = '".$this->get_attempt_id()."'"; + $res = Database::query($sql); + $row = Database::fetch_array($res); + $start = $row['start_time']; + $stop = $start + $row['total_time']; + } else { + $start = $this->current_start_time; + $stop = $this->current_stop_time; + } + if (!empty($start)) { + if (!empty($stop)) { + $time = $stop - $start; + } else { + $time = time() - $start; + } + } + } else { + if ($origin == 'js') { + return '00:00:00'; + } else { + return '00'.$h.'00\'00"'; + } + } + } else { + $time = $given_time; + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time(): intermediate = '.$time, 0); } + $hours = $time/3600; + $mins = ($time%3600)/60; + $secs = ($time%60); + if ($origin == 'js') { + $scorm_time = trim(sprintf("%4d:%02d:%02d", $hours, $mins, $secs)); + } else { + $scorm_time = trim(sprintf("%4d$h%02d'%02d\"", $hours, $mins, $secs)); + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_scorm_time('.$scorm_time.')', 0); } + return $scorm_time; + } + + public function get_terms() { + $lp_item = Database::get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * FROM $lp_item WHERE id='".Database::escape_string($this->db_id)."'"; + $res = Database::query($sql); + $row = Database::fetch_array($res); + return $row['terms']; + } + + /** + * Returns the item's title + * @return string Title + */ + public function get_title() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_title()', 0); } + if (empty($this->title)) { return ''; } + return $this->title; + } + + /** + * Returns the total time used to see that item + * @return integer Total time + */ + public function get_total_time() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_total_time()', 0); } + if ($this->current_start_time == 0) { // Shouldn't be necessary thanks to the open() method. + $this->current_start_time = time(); + } + //$this->current_stop_time=time(); + if (time() < $this->current_stop_time) { + // If this case occurs, then we risk to write huge time data in db. + // In theory, stop time should be *always* updated here, but it might be used in some unknown goal. + $this->current_stop_time = time(); + } + $time = $this->current_stop_time - $this->current_start_time; + if ($time < 0) { + return 0; + } else { + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_total_time() - Current stop time = '.$this->current_stop_time.', current start time = '.$this->current_start_time.' Returning '.$time, 0); } + return $time; + } + } + + /** + * Gets the item type + * @return string The item type (can be doc, dir, sco, asset) + */ + public function get_type() { + $res = 'asset'; + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_type() on item '.$this->db_id, 0); } + if (!empty($this->type)) { + //error_log('In item::get_type() - returning '.$this->type, 0); + $res = $this->type; + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::get_type() - Returning '.$res.' for item '.$this->db_id, 0); } + return $res; + } + + /** + * Gets the view count for this item + * @return int Number of attempts or 0 + */ + public function get_view_count() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::get_view_count()', 0); } + if (!empty($this->attempt_id)) { + return $this->attempt_id; + } else { + return 0; + } + } + + /** + * Tells if an item is done ('completed','passed','succeeded') or not + * @return bool True if the item is done ('completed','passed','succeeded'), false otherwise + */ + function is_done(){ + if ($this->status_is(array('completed', 'passed', 'succeeded'))) { + if (self::debug > 2) { error_log('New LP - In learnpath::is_done() - Item '.$this->get_id().' is complete', 0); } + return true; + }else{ + if (self::debug > 2) { error_log('New LP - In learnpath::is_done() - Item '.$this->get_id().' is not complete', 0); } + return false; + } + } + + /** + * Tells if a restart is allowed (take it from $this->prevent_reinit and $this->status) + * @return integer -1 if retaking the sco another time for credit is not allowed, + * 0 if it is not allowed but the item has to be finished + * 1 if it is allowed. Defaults to 1 + */ + public function is_restart_allowed() { + if (self::debug > 2) { error_log('New LP - In learnpathItem::is_restart_allowed()', 0); } + $restart = 1; + $mystatus = $this->get_status(true); + if ($this->get_prevent_reinit() > 0){ // If prevent_reinit == 1 (or more) + // If status is not attempted or incomplete, authorize retaking (of the same) anyway. Otherwise: + if ($mystatus != $this->possible_status[0] && $mystatus != $this->possible_status[1]) { + $restart = -1; + } else { + $restart = 0; + } + } else { + if ($mystatus == $this->possible_status[0] || $mystatus == $this->possible_status[1]) { + $restart = -1; + } + } + if (self::debug > 2) { error_log('New LP - End of learnpathItem::is_restart_allowed() - Returning '.$restart, 0); } + return $restart; + } + + /** + * Opens/launches the item. Initialises runtime values. + * @return boolean True on success, false on failure. + */ + public function open($allow_new_attempt = false) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::open()', 0); } + if ($this->prevent_reinit == 0) { + $this->current_score = 0; + $this->current_start_time = time(); + // In this case, as we are opening the item, what is important to us + // is the database status, in order to know if this item has already + // been used in the past (rather than just loaded and modified by + // some javascript but not written in the database). + // If the database status is different from 'not attempted', we can + // consider this item has already been used, and as such we can + // open a new attempt. Otherwise, we'll just reuse the current + // attempt, which is generally created the first time the item is + // loaded (for example as part of the table of contents). + $stat = $this->get_status(true); + if ($allow_new_attempt && isset($stat) && ($stat != $this->possible_status[0])) { + $this->attempt_id = $this->attempt_id + 1; // Open a new attempt. + } + $this->status = $this->possible_status[1]; + } else { + /*if ($this->current_start_time == 0) { + // Small exception for start time, to avoid amazing values. + $this->current_start_time = time(); + }*/ + // If we don't init start time here, the time is sometimes calculated from the las start time. + $this->current_start_time = time(); + + //error_log('New LP - reinit blocked by setting', 0); + } + } + + /** + * Outputs the item contents + * @return string HTML file (displayable in an <iframe>) or empty string if no path defined + */ + function output() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::output()', 0); } + if (!empty($this->path) and is_file($this->path)) { + $output = ''; + $output .= file_get_contents($this->path); + return $output; + } + return ''; + } + + /** + * Parses the prerequisites string with the AICC logic language + * @param string The prerequisites string as it figures in imsmanifest.xml + * @param Array Array of items in the current learnpath object. Although we're in the learnpathItem object, it's necessary to have a list of all items to be able to check the current item's prerequisites + * @param Array List of references (the "ref" column in the lp_item table) that are strings used in the expression of prerequisites. + * @param integer The user ID. In some cases like Chamilo quizzes, it's necessary to have the user ID to query other tables (like the results of quizzes) + * @return boolean True if the list of prerequisites given is entirely satisfied, false otherwise + */ + public function parse_prereq($prereqs_string, $items, $refs_list, $user_id) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::parse_prereq() for learnpath '.$this->lp_id.' with string '.$prereqs_string, 0); } + // Deal with &, |, ~, =, <>, {}, ,, X*, () in reverse order. + $this->prereq_alert = ''; + // First parse all parenthesis by using a sequential loop (looking for less-inclusives first). + if ($prereqs_string == '_true_') { return true; } + if ($prereqs_string == '_false_') { + if (empty($this->prereq_alert)) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return false; + } + while (strpos($prereqs_string, '(') !== false) { + // Remove any () set and replace with its value. + $matches = array(); + $res = preg_match_all('/(\(([^\(\)]*)\))/', $prereqs_string, $matches); + if ($res) { + foreach ($matches[2] as $id => $match) { + $str_res = $this->parse_prereq($match, $items, $refs_list, $user_id); + if ($str_res) { + $prereqs_string = str_replace($matches[1][$id], '_true_', $prereqs_string); + } else { + $prereqs_string = str_replace($matches[1][$id], '_false_', $prereqs_string); + } + } + } + } + + // Parenthesis removed, now look for ORs as it is the lesser-priority binary operator (= always uses one text operand). + if (strpos($prereqs_string, '|') === false) { + if (self::debug > 1) { error_log('New LP - Didnt find any OR, looking for AND', 0); } + if (strpos($prereqs_string, '&') !== false) { + $list = split('&', $prereqs_string); + if (count($list) > 1) { + $andstatus = true; + foreach ($list as $condition) { + $andstatus = $andstatus && $this->parse_prereq($condition, $items, $refs_list, $user_id); + if (!$andstatus) { + if (self::debug > 1) { error_log('New LP - One condition in AND was false, short-circuit', 0); } + break; + } + } + if (empty($this->prereq_alert) && !$andstatus) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $andstatus; + } else { + if (isset($items[$refs_list[$list[0]]])) { + $status = $items[$refs_list[$list[0]]]->get_status(true); + $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); + if (empty($this->prereq_alert) && !$returnstatus) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $returnstatus; + } + $this->prereq_alert = get_lang('_prereq_not_complete'); + return false; + } + } else { + // No ORs found, now look for ANDs. + + if (self::debug > 1) { error_log('New LP - Didnt find any AND, looking for =', 0); } + if (strpos($prereqs_string, '=') !== false) { + if (self::debug > 1) { error_log('New LP - Found =, looking into it', 0); } + // We assume '=' signs only appear when there's nothing else around. + $params = split('=', $prereqs_string); + if (count($params) == 2) { + // Right number of operands. + if (isset($items[$refs_list[$params[0]]])) { + $status = $items[$refs_list[$params[0]]]->get_status(true); + $returnstatus = ($status == $params[1]); + if (empty($this->prereq_alert) && !$returnstatus) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $returnstatus; + } + $this->prereq_alert = get_lang('_prereq_not_complete'); + return false; + } + } else { + + // No ANDs found, look for <> + + if (self::debug > 1) { error_log('New LP - Didnt find any =, looking for <>', 0); } + if (strpos($prereqs_string, '<>') !== false) { + if (self::debug > 1) { error_log('New LP - Found <>, looking into it', 0); } + // We assume '<>' signs only appear when there's nothing else around. + $params = split('<>', $prereqs_string); + if (count($params) == 2) { + // Right number of operands. + if (isset($items[$refs_list[$params[0]]])) { + $status = $items[$refs_list[$params[0]]]->get_status(true); + $returnstatus = ($status != $params[1]); + if (empty($this->prereq_alert) && !$returnstatus) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $returnstatus; + } + $this->prereq_alert = get_lang('_prereq_not_complete'); + return false; + } + } else { + + // No <> found, look for ~ (unary). + + if (self::debug > 1) { error_log('New LP - Didnt find any =, looking for ~', 0); } + // Only remains: ~ and X*{} + if (strpos($prereqs_string, '~') !== false) { + // Found NOT. + if (self::debug > 1) { error_log('New LP - Found ~, looking into it', 0); } + $list = array(); + $myres = preg_match('/~([^(\d+\*)\{]*)/', $prereqs_string, $list); + if ($myres) { + $returnstatus = !$this->parse_prereq($list[1], $items, $refs_list, $user_id); + if (empty($this->prereq_alert) && !$returnstatus) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $returnstatus; + } else { + // Strange... + if (self::debug > 1) { error_log('New LP - Found ~ but strange string: '.$prereqs_string, 0); } + } + } else { + + // Finally, look for sets/groups. + + if (self::debug > 1) { error_log('New LP - Didnt find any ~, looking for groups', 0); } + // Only groups here. + $groups = array(); + $groups_there = preg_match_all('/((\d+\*)?\{([^\}]+)\}+)/', $prereqs_string, $groups); + if ($groups_there) { + foreach ($groups[1] as $gr) { // Only take the results that correspond to the big brackets-enclosed condition. + if (self::debug > 1) { error_log('New LP - Dealing with group '.$gr, 0); } + $multi = array(); + $mycond = false; + if (preg_match('/(\d+)\*\{([^\}]+)\}/', $gr, $multi)) { + if (self::debug > 1) { error_log('New LP - Found multiplier '.$multi[0], 0); } + $count = $multi[1]; + $list = split(',', $multi[2]); + $mytrue = 0; + foreach ($list as $cond) { + if (isset($items[$refs_list[$cond]])) { + $status = $items[$refs_list[$cond]]->get_status(true); + if (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])) { + $mytrue ++; + if (self::debug > 1) { error_log('New LP - Found true item, counting.. ('.($mytrue).')', 0); } + } + } else { + if (self::debug > 1) { error_log('New LP - item '.$cond.' does not exist in items list', 0); } + } + } + if ($mytrue >= $count) { + if (self::debug > 1) { error_log('New LP - Got enough true results, return true', 0); } + $mycond = true; + } else { + if (self::debug > 1) { error_log('New LP - Not enough true results', 0); } + } + } + else { + if (self::debug > 1) { error_log('New LP - No multiplier', 0); } + $list = split(',', $gr); + $mycond = true; + foreach ($list as $cond) { + if (isset($items[$refs_list[$cond]])) { + $status = $items[$refs_list[$cond]]->get_status(true); + if (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])){ + $mycond = true; + if (self::debug > 1) { error_log('New LP - Found true item', 0); } + } else { + if (self::debug > 1) { error_log('New LP - Found false item, the set is not true, return false', 0); } + $mycond = false; + break; + } + } else { + if (self::debug > 1) { error_log('New LP - item '.$cond.' does not exist in items list', 0); } + if (self::debug > 1) { error_log('New LP - Found false item, the set is not true, return false', 0); } + $mycond = false; + break; + } + } + } + if (!$mycond && empty($this->prereq_alert)) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $mycond; + } + } else { + + // Nothing found there either. Now return the value of the corresponding resource completion status. + if (self::debug > 1) { error_log('New LP - Didnt find any group, returning value for '.$prereqs_string, 0); } + + if (isset($items[$refs_list[$prereqs_string]])) { + if ($items[$refs_list[$prereqs_string]]->type == 'quiz') { + + // 1. Checking the status in current items. + $status = $items[$refs_list[$prereqs_string]]->get_status(true); + //error_log('hello '.$status); + $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); + + if (!$returnstatus) { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } + } else { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } + } + + // For one attempt LPs. + if ($this->prevent_reinit == 1) { + + // 2. If is completed we check the results in the DB of the quiz. + if ($returnstatus) { + //AND origin_lp_item_id = '.$user_id.' + $sql = 'SELECT exe_result, exe_weighting + FROM '.Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES).' + WHERE exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' + AND exe_user_id = '.$user_id.' + AND orig_lp_id = '.$this->lp_id.' AND orig_lp_item_id = '.$prereqs_string.' + AND status <> "incomplete" + ORDER BY exe_date DESC + LIMIT 0, 1'; + //error_log('results :'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); + + $rs_quiz = Database::query($sql); + if ($quiz = Database :: fetch_array($rs_quiz)) { + if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { + $returnstatus = true; + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + } + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + } + } + + } else { + // 3. for multiple attempts we check that there are minimun 1 item completed. + + // Checking in the database. + $sql = 'SELECT exe_result, exe_weighting + FROM '.Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES).' + WHERE exe_exo_id = '.$items[$refs_list[$prereqs_string]]->path.' + AND exe_user_id = '.$user_id.' AND orig_lp_id = '.$this->lp_id.' AND orig_lp_item_id = '.$prereqs_string.' '; + //error_log('results 2:'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); + + $rs_quiz = Database::query($sql); + if (Database::num_rows($rs_quiz) > 0) { + while ($quiz = Database :: fetch_array($rs_quiz)) { + if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { + $returnstatus = true; + break; + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + } + } + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + } + + } + return $returnstatus; + } else { + + $status = $items[$refs_list[$prereqs_string]]->get_status(false); + $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); + + if (!$returnstatus) { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } + } else { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } + } + //error_log('status of document'.$status); + //var_dump($returnstatus); + //$returnstatus = true; + if ($returnstatus && $this->prevent_reinit == 1) { + // I would prefer check in the database. + $lp_item_view = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $lp_view = Database::get_course_table(TABLE_LP_VIEW); + + $sql = 'SELECT id FROM '.$lp_view.' + WHERE user_id = '.$user_id.' AND lp_id = '.$this->lp_id.' LIMIT 0, 1'; + $rs_lp = Database::query($sql); + $lp_id = Database :: fetch_row($rs_lp); + $my_lp_id = $lp_id[0]; + + $sql = 'SELECT status FROM '.$lp_item_view.' + WHERE lp_view_id = '.$my_lp_id.' AND lp_item_id = '.$refs_list[$prereqs_string].' LIMIT 0, 1'; + $rs_lp = Database::query($sql); + $status_array = Database :: fetch_row($rs_lp); + $status = $status_array[0]; + + //var_dump($status); + $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); + if (!$returnstatus && empty($this->prereq_alert)){ + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + if (!$returnstatus) { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } + } else { + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } + } + } + + //error_log('results :'.$items[$refs_list[$prereqs_string]]->path. ':'.$user_id); + /*$rs_quiz = Database::query($sql); + if ($quiz = Database :: fetch_array($rs_quiz)) { + if ($quiz['exe_result'] >= $items[$refs_list[$prereqs_string]]->get_mastery_score()) { + $returnstatus = true; + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + } + } else { + $this->prereq_alert = get_lang('_prereq_not_complete'); + $returnstatus = false; + }*/ + + /* + $status = $items[$refs_list[$prereqs_string]]->get_status(true); + //error_log(print_r($items, 1)); + //error_log($refs_list[$prereqs_string]); + + $returnstatus = (($status == $this->possible_status[2]) OR ($status == $this->possible_status[3])); + if (!$returnstatus && empty($this->prereq_alert)) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + if(!$returnstatus){ + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' not complete', 0); } + }else{ + if (self::debug > 1) { error_log('New LP - Prerequisite '.$prereqs_string.' complete', 0); } + } + */ + + //$returnstatus =false; + return $returnstatus; + } + } else { + if (self::debug > 1) { error_log('New LP - Could not find '.$prereqs_string.' in '.print_r($refs_list, true), 0); } + } + } + } + } + } + } + } else { + $list = split("\|",$prereqs_string); + if(count($list)>1){ + if (self::debug > 1) { error_log('New LP - Found OR, looking into it', 0); } + $orstatus = false; + foreach ($list as $condition) { + if (self::debug > 1) { error_log('New LP - Found OR, adding it ('.$condition.')', 0); } + $orstatus = $orstatus || $this->parse_prereq($condition, $items, $refs_list, $user_id); + if ($orstatus) { + // Shortcircuit OR. + if (self::debug > 1) { error_log('New LP - One condition in OR was true, short-circuit', 0); } + break; + } + } + if (!$orstatus && empty($this->prereq_alert)) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $orstatus; + } else { + if (self::debug>1) { error_log('New LP - OR was found but only one elem present !?', 0); } + if (isset($items[$refs_list[$list[0]]])) { + $status = $items[$refs_list[$list[0]]]->get_status(true); + $returnstatus = (($status == 'completed') OR ($status == 'passed')); + if (!$returnstatus && empty($this->prereq_alert)) { + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + return $returnstatus; + } + } + } + if(empty($this->prereq_alert)){ + $this->prereq_alert = get_lang('_prereq_not_complete'); + } + if (self::debug > 1) { error_log('New LP - End of parse_prereq. Error code is now '.$this->prereq_alert, 0); } + return false; + } + + /** + * Reinits all local values as the learnpath is restarted + * @return boolean True on success, false otherwise + */ + public function restart() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::restart()', 0); } + $this->save(); + $allowed = $this->is_restart_allowed(); + if ($allowed === -1) { + // Nothing allowed, do nothing. + } elseif ($allowed === 1) { + // Restart as new attempt is allowed, record a new attempt. + $this->attempt_id = $this->attempt_id + 1; // Simply reuse the previous attempt_id. + $this->current_score = 0; + $this->current_start_time = 0; + $this->current_stop_time = 0; + $this->current_data = ''; + $this->status = $this->possible_status[0]; + $this->interactions_count = 0; + $this->interactions = array(); + $this->objectives_count = 0; + $this->objectives = array(); + $this->lesson_location = ''; + if ($this->type != TOOL_QUIZ) { + $this->write_to_db(); + } + } else { + // Restart current element is allowed (because it's not finished yet), + // reinit current. + $this->current_score = 0; + $this->current_start_time = 0; + $this->current_stop_time = 0; + $this->current_data = ''; + $this->status = $this->possible_status[0]; + $this->interactions_count = $this->get_interactions_count(true); + } + return true; + } + + /** + * Saves data in the database + * @param boolean Save from URL params (1) or from object attributes (0) + * @param boolean The results of a check on prerequisites for this item. True if prerequisites are completed, false otherwise. Defaults to false. Only used if not sco or au + * @return boolean True on success, false on failure + */ + public function save($from_outside = true, $prereqs_complete = false) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::save()', 0); } + //$item_view_table = Database::get_course_table(COURSEID, LEARNPATH_ITEM_VIEW_TABLE); + $item_id = $this->get_id(); + // First check if parameters passed via GET can be saved here + // in case it's a SCORM, we should get: + if ($this->type == 'sco' || $this->type== 'au') { + $s = $this->get_status(true); + if ($this->prevent_reinit == 1 AND + $s != $this->possible_status[0] AND $s != $this->possible_status[1]) { + if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - save reinit blocked by setting', 0); } + // Do nothing because the status has already been set. Don't allow it to change. + // TODO: Check there isn't a special circumstance where this should be saved. + } else { + if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - SCORM save request received', 0); } + //get all new settings from the URL + if ($from_outside) { + if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - Getting item data from outside', 0); } + foreach ($_GET as $param => $value) { + $value = Database::escape_string($value); + switch ($param) { + case 'score': + $this->set_score($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting score to '.$value, 0); } + break; + case 'max': + $this->set_max_score($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting view_max_score to '.$value, 0); } + break; + case 'min': + $this->min_score = $value; + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting min_score to '.$value, 0); } + break; + case 'lesson_status': + if(!empty($value)){ + $this->set_status($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting status to '.$value, 0); } + } + break; + case 'time': + $this->set_time($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting time to '.$value, 0); } + break; + case 'suspend_data': + $this->current_data = $value; + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting suspend_data to '.$value, 0); } + break; + case 'lesson_location': + $this->set_lesson_location($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting lesson_location to '.$value, 0); } + break; + case 'core_exit': + $this->set_core_exit($value); + if (self::debug > 2) { error_log('New LP - In learnpathItem::save() - setting core_exit to '.$value, 0); } + break; + case 'interactions': + //$interactions = unserialize($value); + //foreach($interactions as $interaction){ + // ; + //} + break; + case 'objectives': + break; + //case 'maxtimeallowed': + //$this->set_max_time_allowed($value); + //break; + /* + case 'objectives._count': + $this->attempt_id = $value; + break; + */ + default: + // Ignore. + break; + } + } + } else { + if (self::debug > 1) { error_log('New LP - In learnpathItem::save() - Using inside item status', 0); } + // Do nothing, just let the local attributes be used. + } + } + } else { // If not SCO, such messages should not be expected. + $type = strtolower($this->type); + switch ($type) { + case 'asset': + if ($prereqs_complete) { + $this->set_status($this->possible_status[2]); + } + break; + case TOOL_HOTPOTATOES: break; + case TOOL_QUIZ: return false;break; + default: + // For now, everything that is not sco and not asset is set to + // completed when saved. + if ($prereqs_complete) { + $this->set_status($this->possible_status[2]); + } + break; + } + } + //$time = $this->time + if (self::debug > 1) { error_log('New LP - End of learnpathItem::save() - Calling write_to_db()', 0); } + return $this->write_to_db(); + } + + /** + * Sets the number of attempt_id to a given value + * @param integer The given value to set attempt_id to + * @return boolean TRUE on success, FALSE otherwise + */ + public function set_attempt_id($num) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_attempt_id()', 0); } + if ($num == strval(intval($num)) && $num >= 0) { + $this->attempt_id = $num; + return true; + } + return false; + } + + /** + * Sets the core_exit value to the one given + * @return bool True (always) + */ + public function set_core_exit($value) { + switch($value){ + case '': + $this->core_exit = ''; + break; + case 'suspend': + $this->core_exit = 'suspend'; + break; + default: + $this->core_exit = 'none'; + break; + } + return true; + } + + /** + * Sets the item's description + * @param string Description + * @return void + */ + public function set_description($string = '') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_description()', 0); } + if (!empty($string)) { $this->description = $string; } + } + + /** + * Sets the lesson_location value + * @param string lesson_location as provided by the SCO + * @return boolean True on success, false otherwise + */ + public function set_lesson_location($location) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_lesson_location()', 0); } + if (isset($location)) { + $this->lesson_location = Database::escape_string($location); + return true; + } + return false; + } + + /** + * Sets the item's depth level in the LP tree (0 is at root) + * @param integer Level + * @return void + */ + public function set_level($int = 0) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_level('.$int.')', 0); } + if (!empty($int) AND $int == strval(intval($int))) { $this->level = $int; } + } + + /** + * Sets the lp_view id this item view is registered to + * @param integer lp_view DB ID + * @return void + * @todo //todo insert into lp_item_view if lp_view not exists + */ + public function set_lp_view($lp_view_id) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_lp_view('.$lp_view_id.')', 0); } + if (!empty($lp_view_id) and $lp_view_id = intval(strval($lp_view_id))) { + $this->view_id = $lp_view_id; + $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); + // Get the lp_item_view with the highest view_count. + $sql = "SELECT * FROM $item_view_table WHERE lp_item_id = ".$this->get_id()." " . + " AND lp_view_id = ".$lp_view_id." ORDER BY view_count DESC"; + if (self::debug > 2) { error_log('New LP - In learnpathItem::set_lp_view() - Querying lp_item_view: '.$sql, 0); } + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $row = Database::fetch_array($res); + $this->db_item_view_id = $row['id']; + $this->attempt_id = $row['view_count']; + $this->current_score = $row['score']; + $this->current_data = $row['suspend_data']; + $this->view_max_score = $row['max_score']; + //$this->view_min_score = $row['min_score']; + $this->status = $row['status']; + $this->current_start_time = $row['start_time']; + $this->current_stop_time = $this->current_start_time + $row['total_time']; + $this->lesson_location = $row['lesson_location']; + $this->core_exit = $row['core_exit']; + if (self::debug > 2) { error_log('New LP - In learnpathItem::set_lp_view() - Updated item object with database values', 0); } + + // Now get the number of interactions for this little guy. + $item_view_interaction_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); + $sql = "SELECT * FROM $item_view_interaction_table WHERE lp_iv_id = '".$this->db_item_view_id."'"; + $res = Database::query($sql); + if ($res !== false) { + $this->interactions_count = Database::num_rows($res); + } else { + $this->interactions_count = 0; + } + // Now get the number of objectives for this little guy. + $item_view_objective_table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); + $sql = "SELECT * FROM $item_view_objective_table WHERE lp_iv_id = '".$this->db_item_view_id."'"; + $res = Database::query($sql); + if ($res !== false) { + $this->objectives_count = Database::num_rows($res); + } else { + $this->objectives_count = 0; + } + } + } + // End + if (self::debug > 2) { error_log('New LP - End of learnpathItem::set_lp_view()', 0); } + } + + /** + * Sets the path + * @param string Path + * @return void + */ + public function set_path($string = '') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_path()', 0); } + if (!empty($string)) { $this->path = $string; } + } + + /** + * Sets the prevent_reinit attribute. This is based on the LP value and is set at creation time for + * each learnpathItem. It is a (bad?) way of avoiding a reference to the LP when saving an item. + * @param integer 1 for "prevent", 0 for "don't prevent" saving freshened values (new "not attempted" status etc) + * @return void + */ + public function set_prevent_reinit($prevent) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_prevent_reinit()', 0); } + if ($prevent) { + $this->prevent_reinit = 1; + } else { + $this->prevent_reinit = 0; + } + } + + /** + * Sets the score value. If the mastery_score is set and the score reaches + * it, then set the status to 'passed'. + * @param float Score + * @return boolean True on success, false otherwise + */ + public function set_score($score) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_score('.$score.')', 0); } + if (($this->max_score<=0 || $score <= $this->max_score) && ($score >= $this->min_score)) { + $this->current_score = $score; + $master = $this->get_mastery_score(); + $current_status = $this->get_status(false); + // If mastery_score is set AND the current score reaches the mastery score AND the current status is different from 'completed', then set it to 'passed'. + if ($master != -1 && $this->current_score >= $master && $current_status != $this->possible_status[2]) { + $this->set_status($this->possible_status[3]); + } elseif ($master != -1 && $this->current_score < $master) { + $this->set_status($this->possible_status[4]); + } + return true; + } + return false; + } + + /** + * Sets the maximum score for this item + * @param int Maximum score - must be a decimal or an empty string + * @return boolean True on success, false on error + */ + public function set_max_score($score) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_max_score('.$score.')', 0); } + if (is_int($score) or $score == '') { + $this->view_max_score = Database::escape_string($score); + if (self::debug > 1) { error_log('New LP - In learnpathItem::set_max_score() - Updated object score of item '.$this->db_id.' to '.$this->view_max_score, 0); } + return true; + } + return false; + } + + /** + * Sets the status for this item + * @param string Status - must be one of the values defined in $this->possible_status + * @return boolean True on success, false on error + */ + public function set_status($status) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_status('.$status.')', 0); } + $found = false; + foreach ($this->possible_status as $possible) { + if (preg_match('/^'.$possible.'$/i', $status)) { + $found = true; + } + } + //if (in_array($status, $this->possible_status)) { + if ($found) { + $this->status = Database::escape_string($status); + if (self::debug > 1) { error_log('New LP - In learnpathItem::set_status() - Updated object status of item '.$this->db_id.' to '.$this->status, 0); } + return true; + } + //error_log('New LP - '.$status.' was not in the possible status', 0); + $this->status = $this->possible_status[0]; + return false; + } + + /** + * Set the terms for this learnpath item + * @param string Terms, as a comma-split list + * @return boolean Always return true + */ + public function set_terms($terms) { + global $charset; + $lp_item = Database::get_course_table(TABLE_LP_ITEM); + require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; + $a_terms = split(',', $terms); + $i_terms = split(',', $this->get_terms()); + foreach ($i_terms as $term) { + if (!in_array($term, $a_terms)) { array_push($a_terms, $term); } + } + $new_terms = $a_terms; + $new_terms_string = implode(',', $new_terms); + $terms_update_sql = ''; + // TODO: Validate csv string. + $terms_update_sql = "UPDATE $lp_item SET terms = '". Database::escape_string(api_htmlentities($new_terms_string, ENT_QUOTES, $charset)) . "' WHERE id=".$this->get_id(); + $res = Database::query($terms_update_sql); + // Save it to search engine. + if (api_get_setting('search_enabled') == 'true') { + $di = new DokeosIndexer(); + $di->update_terms($this->get_search_did(), $new_terms); + } + return true; + } + + /** + * Get the document ID from inside the text index database + * @return int Search index database document ID + */ + public function get_search_did() { + return $this->search_did; + } + + /** + * Sets the item viewing time in a usable form, given that SCORM packages often give it as 00:00:00.0000 + * @param string Time as given by SCORM + * @return void + */ + public function set_time($scorm_time, $format = 'scorm') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_time('.$scorm_time.')', 0); } + if ($scorm_time == 0 and ($this->type!='sco') and $this->current_start_time != 0) { + $my_time = time() - $this->current_start_time; + if ($my_time > 0) { + $this->update_time($my_time); + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_time('.$scorm_time.') - found asset - set time to '.$my_time, 0); } + } + } else { + if ($format == 'scorm') { + $res = array(); + if (preg_match('/^(\d{1,4}):(\d{2}):(\d{2})(\.\d{1,4})?/', $scorm_time, $res)) { + $time = time(); + $hour = $res[1]; + $min = $res[2]; + $sec = $res[3]; + // Getting total number of seconds spent. + $total_sec = $hour*3600 + $min*60 + $sec; + $this->update_time($total_sec); + } + } elseif ($format == 'int') { + $this->update_time($scorm_time); + } + } + } + + /** + * Sets the item's title + * @param string Title + * @return void + */ + public function set_title($string = '') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_title()', 0); } + if (!empty($string)) { $this->title = $string; } + } + + /** + * Sets the item's type + * @param string Type + * @return void + */ + public function set_type($string = '') { + if (self::debug > 0) { error_log('New LP - In learnpathItem::set_type()', 0); } + if (!empty($string)) { $this->type = $string; } + } + + /** + * Checks if the current status is part of the list of status given + * @param strings_array An array of status to check for. If the current status is one of the strings, return true + * @return boolean True if the status was one of the given strings, false otherwise + */ + public function status_is($list = array()) { + if (self::debug > 1) { error_log('New LP - In learnpathItem::status_is('.print_r($list,true).') on item '.$this->db_id, 0); } + $mystatus = $this->get_status(true); + if (empty($mystatus)) { + return false; + } + $found = false; + foreach ($list as $status) { + if (preg_match('/^'.$status.'$/i', $mystatus)) { + if (self::debug > 2) { error_log('New LP - learnpathItem::status_is() - Found status '.$status.' corresponding to current status', 0); } + $found = true; + return $found; + } + } + if (self::debug > 2) { error_log('New LP - learnpathItem::status_is() - Status '.$mystatus.' did not match request', 0); } + return $found; + } + + /** + * Updates the time info according to the given session_time + * @param integer Time in seconds + * @return void + * TODO: Make this method better by allowing better/multiple time slices. + */ + public function update_time($total_sec = 0) { + if (self::debug > 0) { error_log('New LP - In learnpathItem::update_time('.$total_sec.')', 0); } + if ($total_sec >= 0) { + // Getting start time from finish time. The only problem in the calculation is it might be + // modified by the scripts processing time. + $now = time(); + $start = $now-$total_sec; + $this->current_start_time = $start; + $this->current_stop_time = $now; + /*if (empty($this->current_start_time)) { + $this->current_start_time = $start; + $this->current_stop_time = $now; + } else { + //if ($this->current_stop_time != $this->current_start_time) { + // If the stop time has already been set before to something else + // than the start time, add the given time to what's already been + // recorder. + // This is the SCORM way of doing things, because the time comes from + // core.session_time, not core.total_time + // UPDATE: adding time to previous time is only done on SCORM's finish() + // call, not normally, so for now ignore this section. + //$this->current_stop_time = $this->current_stop_time + $stop; + //error_log('New LP - Adding '.$stop.' seconds - now '.$this->current_stop_time, 0); + //} else { + // If no previous stop time set, use the one just calculated now from + // start time. + //$this->current_start_time = $start; + //$this->current_stop_time = $now; + //error_log('New LP - Setting '.$stop.' seconds - now '.$this->current_stop_time, 0); + //} + }*/ + } + } + + /** + * Write objectives to DB. This method is separate from write_to_db() because otherwise + * objectives are lost as a side effect to AJAX and session concurrent access + * @return boolean True or false on error + */ + public function write_objectives_to_db() { + if (self::debug > 0) { error_log('New LP - In learnpathItem::write_objectives_to_db()', 0); } + if (is_array($this->objectives) && count($this->objectives) > 0) { + // Save objectives. + $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT id FROM $tbl " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $row = Database::fetch_array($res); + $lp_iv_id = $row[0]; + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Got item_view_id '.$lp_iv_id.', now checking objectives ', 0); } + foreach($this->objectives as $index => $objective){ + $iva_table = Database::get_course_table(TABLE_LP_IV_OBJECTIVE); + $iva_sql = "SELECT id FROM $iva_table " . + "WHERE lp_iv_id = $lp_iv_id " . + //"AND order_id = $index"; + //also check for the objective ID as it must be unique for this SCO view + "AND objective_id = '".Database::escape_string($objective[0])."'"; + $iva_res = Database::query($iva_sql); + // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) + if(Database::num_rows($iva_res)>0){ + // Update (or don't). + $iva_row = Database::fetch_array($iva_res); + $iva_id = $iva_row[0]; + $ivau_sql = "UPDATE $iva_table " . + "SET objective_id = '".Database::escape_string($objective[0])."'," . + "status = '".Database::escape_string($objective[1])."'," . + "score_raw = '".Database::escape_string($objective[2])."'," . + "score_min = '".Database::escape_string($objective[4])."'," . + "score_max = '".Database::escape_string($objective[3])."' " . + "WHERE id = $iva_id"; + $ivau_res = Database::query($ivau_sql); + //error_log($ivau_sql, 0); + }else{ + // Insert new one. + $ivai_sql = "INSERT INTO $iva_table " . + "(lp_iv_id, order_id, objective_id, status, score_raw, score_min, score_max )" . + "VALUES" . + "(".$lp_iv_id.", ".$index.",'".Database::escape_string($objective[0])."','".Database::escape_string($objective[1])."'," . + "'".Database::escape_string($objective[2])."','".Database::escape_string($objective[4])."','".Database::escape_string($objective[3])."')"; + $ivai_res = Database::query($ivai_sql); + //error_log($ivai_sql); + } + } + } + } else { + //error_log('no objective to save: '.print_r($this->objectives, 1)); + } + } + + /** + * Writes the current data to the database + * @return boolean Query result + */ + public function write_to_db() { + + // Check the session visibility. + if (!api_is_allowed_to_session_edit()) { + return false; + } + + if (self::debug > 0) { error_log('New LP - In learnpathItem::write_to_db()', 0); } + $mode = $this->get_lesson_mode(); + $credit = $this->get_credit(); + $my_verified_status=$this->get_status(false); + + $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql_verified = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" ;'; + $rs_verified = Database::query($sql_verified); + $row_verified = Database::fetch_array($rs_verified); + $my_case_completed = array('completed', 'passed', 'browsed', 'failed'); // Added by Isaac Flores. + if (in_array($sql_verified['status'], $my_case_completed)) { + $save = false; + } else { + $save = true; + } + + if (($save === false && $this->type == 'sco') ||(($this->type == 'sco') && ($credit == 'no-credit' OR $mode == 'review' OR $mode == 'browse'))) { + // This info shouldn't be saved as the credit or lesson mode info prevent it. + if (self::debug > 1) { error_log('New LP - In learnpathItem::write_to_db() - credit('.$credit.') or lesson_mode('.$mode.') prevent recording!', 0); } + } else { + // Check the row exists. + $inserted = false; + + // This a special case for multiple attempts and Chamilo exercises. + if ($this->type == 'quiz' && $this->get_prevent_reinit() == 0 && $this->get_status() == 'completed') { + // We force the item to be restarted. + $this->restart(); + + $sql = "INSERT INTO $item_view_table " . + "(total_time, " . + "start_time, " . + "score, " . + "status, " . + "max_score, ". + "lp_item_id, " . + "lp_view_id, " . + "view_count, " . + "suspend_data, " . + //"max_time_allowed," . + "lesson_location)" . + "VALUES" . + "(".$this->get_total_time()."," . + "".$this->current_start_time."," . + "".$this->get_score()."," . + "'".$this->get_status(false)."'," . + "'".$this->get_max()."'," . + "".$this->db_id."," . + "".$this->view_id."," . + "".$this->get_attempt_id()."," . + "'".Database::escape_string($this->current_data)."'," . + //"'".$this->get_max_time_allowed()."'," . + "'".$this->lesson_location."')"; + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Inserting into item_view forced: '.$sql, 0); } + $res = Database::query($sql); + $this->db_item_view_id = Database::insert_id(); + $inserted = true; + } + + $item_view_table = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $check = "SELECT * FROM $item_view_table " . + "WHERE lp_item_id = ".$this->db_id. " " . + "AND lp_view_id = ".$this->view_id. " ". + "AND view_count = ".$this->get_attempt_id(); + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Querying item_view: '.$check, 0); } + $check_res = Database::query($check); + // Depending on what we want (really), we'll update or insert a new row + // now save into DB. + $res = 0; + if (!$inserted && Database::num_rows($check_res) < 1) { + /*$my_status = ''; + if ($this->type != TOOL_QUIZ) { + $my_status = $this->get_status(false); + }*/ + + $sql = "INSERT INTO $item_view_table " . + "(total_time, " . + "start_time, " . + "score, " . + "status, " . + "max_score, ". + "lp_item_id, " . + "lp_view_id, " . + "view_count, " . + "suspend_data, " . + //"max_time_allowed," . + "lesson_location)" . + "VALUES" . + "(".$this->get_total_time()."," . + "".$this->current_start_time."," . + "".$this->get_score()."," . + "'".$this->get_status(false)."'," . + "'".$this->get_max()."'," . + "".$this->db_id."," . + "".$this->view_id."," . + "".$this->get_attempt_id()."," . + "'".Database::escape_string($this->current_data)."'," . + //"'".$this->get_max_time_allowed()."'," . + "'".$this->lesson_location."')"; + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Inserting into item_view: '.$sql, 0); } + $res = Database::query($sql); + $this->db_item_view_id = Database::insert_id(); + } else { + $sql = ''; + if ($this->type == 'hotpotatoes') { + $sql = "UPDATE $item_view_table " . + "SET total_time = ".$this->get_total_time().", " . + " start_time = ".$this->get_current_start_time().", " . + " score = ".$this->get_score().", " . + " status = '".$this->get_status(false)."'," . + " max_score = '".$this->get_max()."'," . + " suspend_data = '".Database::escape_string($this->current_data)."'," . + " lesson_location = '".$this->lesson_location."' " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + } else { + // For all other content types... + if ($this->type == 'quiz') { + $my_status = ' '; + $total_time = ' '; + if (!empty($_REQUEST['exeId'])) { + $TBL_TRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); + + $safe_exe_id = Database::escape_string($_REQUEST['exeId']); + $sql = 'SELECT start_date,exe_date FROM ' . $TBL_TRACK_EXERCICES . ' WHERE exe_id = '.(int)$safe_exe_id; + $res = Database::query($sql); + $row_dates = Database::fetch_array($res); + + $time_start_date = convert_mysql_date($row_dates['start_date']); + $time_exe_date = convert_mysql_date($row_dates['exe_date']); + $mytime = ((int)$time_exe_date-(int)$time_start_date); + $total_time =" total_time = ".$mytime.", "; + } + } else { + $my_type_lp = learnpath::get_type_static($this->lp_id); + // This is a array containing values finished + $case_completed = array('completed', 'passed', 'browsed'); + + if ($this->get_prevent_reinit() == 1) { + // Multiple attempts are prevented. + + // Process of status verified into data base. + $sql_verified = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" ;'; + $rs_verified = Database::query($sql_verified); + $row_verified = Database::fetch_array($rs_verified); + + // Get type lp: 1=lp dokeos and 2=scorm. + // If not is completed or passed or browsed and learning path is scorm. + if (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2 ) { //&& $this->type!='dir' + $total_time =" total_time = total_time +".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } else { + // Verified into data base. + if (!in_array($row_verified['status'], $case_completed) && $my_type_lp == 2 ) { //&& $this->type!='dir' + $total_time =" total_time = total_time +".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } elseif (in_array($row_verified['status'], $case_completed) && $my_type_lp == 2 && $this->type != 'sco' ) { //&& $this->type!='dir' + $total_time =" total_time = total_time +".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } else { + //&& !in_array($row_verified['status'], $case_completed) + //is lp dokeos + if ($my_type_lp == 1 && $this->type != 'chapter') { + $total_time = " total_time = total_time + ".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } + } + } + } else { + // Multiple attempts are allowed. + if (in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) { + // Reset zero new attempt ? + $my_status = " status = '".$this->get_status(false)."' ,"; + } elseif (!in_array($this->get_status(false), $case_completed) && $my_type_lp == 2) { + $total_time =" total_time = ".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } else { + // It is dokeos LP. + $total_time =" total_time = total_time +".$this->get_total_time().", "; + $my_status = " status = '".$this->get_status(false)."' ,"; + } + + // Code added by Isaac Flores. + // This code line fixes the problem of wrong status. + if ($my_type_lp == 2) { + // Verify current status in multiples attempts. + $sql_status = 'SELECT status FROM '.$item_view_table.' WHERE lp_item_id="'.$this->db_id.'" AND lp_view_id="'.$this->view_id.'" AND view_count="'.$this->attempt_id.'" '; + $rs_status = Database::query($sql_status); + $current_status = Database::result($rs_status, 0, 'status'); + if (in_array($current_status, $case_completed)) { + $my_status = ''; + $total_time = ''; + } else { + $total_time = " total_time = total_time +".$this->get_total_time().", "; + } + } + } + /*if ($my_type_lp == 1 && !in_array($row_verified['status'], $case_completed)) { + $total_time =" total_time = total_time + ".$this->get_total_time().", "; + }*/ + } + + $sql = "UPDATE $item_view_table " . + "SET " .$total_time. + " start_time = ".$this->get_current_start_time().", " . + " score = ".$this->get_score().", " . + $my_status. + " max_score = '".$this->get_max()."'," . + " suspend_data = '".Database::escape_string($this->current_data)."'," . + //" max_time_allowed = '".$this->get_max_time_allowed()."'," . + " lesson_location = '".$this->lesson_location."' " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + + $this->current_start_time = time(); + } + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Updating item_view: '.$sql, 0); } + $res = Database::query($sql); + } + //if(!$res) + //{ + // $this->error = 'Could not update item_view table...'.Database::error(); + //} + if (is_array($this->interactions) && count($this->interactions) > 0) { + // Save interactions. + $tbl = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $sql = "SELECT id FROM $tbl " . + "WHERE lp_item_id = ".$this->db_id." " . + "AND lp_view_id = ".$this->view_id." " . + "AND view_count = ".$this->attempt_id; + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $row = Database::fetch_array($res); + $lp_iv_id = $row[0]; + if (self::debug > 2) { error_log('New LP - In learnpathItem::write_to_db() - Got item_view_id '.$lp_iv_id.', now checking interactions ', 0); } + foreach ($this->interactions as $index => $interaction) { + $correct_resp = ''; + if (is_array($interaction[4]) && !empty($interaction[4][0])) { + foreach ($interaction[4] as $resp) { + $correct_resp .= $resp.','; + } + $correct_resp = substr($correct_resp, 0, strlen($correct_resp) - 1); + } + $iva_table = Database::get_course_table(TABLE_LP_IV_INTERACTION); + $iva_sql = "SELECT id FROM $iva_table " . + "WHERE lp_iv_id = $lp_iv_id " . // "AND order_id = $index"; - //also check for the interaction ID as it must be unique for this SCO view - "AND (order_id = $index " . - "OR interaction_id = '".Database::escape_string($interaction[0])."')"; - $iva_res = Database::query($iva_sql); - // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) - if (Database::num_rows($iva_res) > 0) { - // Update (or don't). - $iva_row = Database::fetch_array($iva_res); - $iva_id = $iva_row[0]; - $ivau_sql = "UPDATE $iva_table " . - "SET interaction_id = '".Database::escape_string($interaction[0])."'," . - "interaction_type = '".Database::escape_string($interaction[1])."'," . - "weighting = '".Database::escape_string($interaction[3])."'," . - "completion_time = '".Database::escape_string($interaction[2])."'," . - "correct_responses = '".Database::escape_string($correct_resp)."'," . - "student_response = '".Database::escape_string($interaction[5])."'," . - "result = '".Database::escape_string($interaction[6])."'," . - "latency = '".Database::escape_string($interaction[7])."'" . - "WHERE id = $iva_id"; - $ivau_res = Database::query($ivau_sql); - } else { - // Insert new one. - $ivai_sql = "INSERT INTO $iva_table " . - "(order_id, lp_iv_id, interaction_id, interaction_type, " . - "weighting, completion_time, correct_responses, " . - "student_response, result, latency)" . - "VALUES" . - "(".$index.",".$lp_iv_id.",'".Database::escape_string($interaction[0])."','".Database::escape_string($interaction[1])."'," . - "'".Database::escape_string($interaction[3])."','".Database::escape_string($interaction[2])."','".Database::escape_string($correct_resp)."'," . - "'".Database::escape_string($interaction[5])."','".Database::escape_string($interaction[6])."','".Database::escape_string($interaction[7])."'" . - ")"; - $ivai_res = Database::query($ivai_sql); - } - } - } - } - } - if (self::debug > 2) { error_log('New LP - End of learnpathItem::write_to_db()', 0); } - return true; - } + //also check for the interaction ID as it must be unique for this SCO view + "AND (order_id = $index " . + "OR interaction_id = '".Database::escape_string($interaction[0])."')"; + $iva_res = Database::query($iva_sql); + // id(0), type(1), time(2), weighting(3), correct_responses(4), student_response(5), result(6), latency(7) + if (Database::num_rows($iva_res) > 0) { + // Update (or don't). + $iva_row = Database::fetch_array($iva_res); + $iva_id = $iva_row[0]; + $ivau_sql = "UPDATE $iva_table " . + "SET interaction_id = '".Database::escape_string($interaction[0])."'," . + "interaction_type = '".Database::escape_string($interaction[1])."'," . + "weighting = '".Database::escape_string($interaction[3])."'," . + "completion_time = '".Database::escape_string($interaction[2])."'," . + "correct_responses = '".Database::escape_string($correct_resp)."'," . + "student_response = '".Database::escape_string($interaction[5])."'," . + "result = '".Database::escape_string($interaction[6])."'," . + "latency = '".Database::escape_string($interaction[7])."'" . + "WHERE id = $iva_id"; + $ivau_res = Database::query($ivau_sql); + } else { + // Insert new one. + $ivai_sql = "INSERT INTO $iva_table " . + "(order_id, lp_iv_id, interaction_id, interaction_type, " . + "weighting, completion_time, correct_responses, " . + "student_response, result, latency)" . + "VALUES" . + "(".$index.",".$lp_iv_id.",'".Database::escape_string($interaction[0])."','".Database::escape_string($interaction[1])."'," . + "'".Database::escape_string($interaction[3])."','".Database::escape_string($interaction[2])."','".Database::escape_string($correct_resp)."'," . + "'".Database::escape_string($interaction[5])."','".Database::escape_string($interaction[6])."','".Database::escape_string($interaction[7])."'" . + ")"; + $ivai_res = Database::query($ivai_sql); + } + } + } + } + } + if (self::debug > 2) { error_log('New LP - End of learnpathItem::write_to_db()', 0); } + return true; + } } diff --git a/main/newscorm/lp_stats.php b/main/newscorm/lp_stats.php index bdcededd48..e1e09f4348 100755 --- a/main/newscorm/lp_stats.php +++ b/main/newscorm/lp_stats.php @@ -678,13 +678,13 @@ if (is_array($list) && count($list) > 0) { if (!$is_allowed_to_edit && $result_disabled_ext_all) { $output .= '<td><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz_na.gif" alt="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'"></td>'; } else { - $output .= '<td><a href="../exercice/exercise_show.php?origin=student_progress&myid='.$my_orig_lp.'&my_lp_id='.$my_orig_lp_item.'&id=' . $my_exe_id . '&cidReq=' . $course_code . '&student=' . $student_id .$from_link. '" target="_parent"><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz.gif" alt="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'"></a></td>'; + $output .= '<td><a href="../exercice/exercise_show.php?origin=student_progress&id=' . $my_exe_id . '&cidReq=' . $course_code . $from_link. '" target="_parent"><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz.gif" alt="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAttempt'), $lp_charset, $dokeos_charset).'"></a></td>'; } } else { if (!$is_allowed_to_edit && $result_disabled_ext_all ) { $output .= '<td><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz_na.gif" alt="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'"></td>'; } else { - $output .= '<td><a href="../exercice/exercise_show.php?origin=tracking_course&myid='.$my_orig_lp.'&my_lp_id='.$my_orig_lp_item.'&id=' . $my_exe_id . '&cidReq=' . $course_code . '&student=' . $student_id . '&total_time='.$mytime.'&my_exe_exo_id='.$my_exo_exe_id.$from_link.' " target="_parent"><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz.gif" alt="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'"></a></td>'; + $output .= '<td><a href="../exercice/exercise_show.php?origin=tracking_course&id=' . $my_exe_id . '&cidReq=' . $course_code.$from_link.' " target="_parent"><img src="' . api_get_path(WEB_IMG_PATH) . 'quiz.gif" alt="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'" title="'.api_convert_encoding(get_lang('ShowAndQualifyAttempt'), $lp_charset, $dokeos_charset).'"></a></td>'; } } $output .= '</tr>'; diff --git a/main/newscorm/lp_view.php b/main/newscorm/lp_view.php index 18c3c36b2c..b9f794e6a8 100755 --- a/main/newscorm/lp_view.php +++ b/main/newscorm/lp_view.php @@ -11,7 +11,7 @@ * @author Denes Nagy, principal author * @author Isthvan Mandak, several new features * @author Roan Embrechts, code improvements and refactoring -* @license GNU/GPL - See Chamilo license directory for details +* @license GNU/GPL - See Chamilo license directory for details */ /* INIT SECTION */ @@ -43,14 +43,14 @@ if (!empty($_SESSION['oLP']->encoding)) { $charset = api_get_system_encoding(); } -$oLearnpath = false; -$course_code = api_get_course_id(); -$user_id = api_get_user_id(); +$oLearnpath = false; +$course_code = api_get_course_id(); +$user_id = api_get_user_id(); $platform_theme = api_get_setting('stylesheets'); // Plataform's css. -$my_style = $platform_theme; +$my_style = $platform_theme; // Escape external variables. -/* Header */ +/* Header */ $htmlHeadXtra[] = '<script src="../inc/lib/javascript/jquery.js" type="text/javascript" language="javascript"></script>'; @@ -72,8 +72,10 @@ $htmlHeadXtra[] = '<script language="JavaScript" type="text/javascript"> </script>'; $_SESSION['oLP']->error = ''; -$lp_type = $_SESSION['oLP']->get_type(); +$lp_type = $_SESSION['oLP']->get_type(); $lp_item_id = $_SESSION['oLP']->get_current_item_id(); + + //$lp_item_id = learnpath::escape_string($_GET['item_id']); //$_SESSION['oLP']->set_current_item($lp_item_id); // Already done by lp_controller.php. @@ -143,6 +145,12 @@ if (!isset($src)) { $list = $_SESSION['oLP']->get_toc(); $type_quiz = false; +$current_item = $_SESSION['oLP']->items[$_SESSION['oLP']->get_current_item_id()]; +$attempt_id = $current_item->get_attempt_id(); +error_log('get attempts'.$current_item->get_attempt_id()); + + + foreach($list as $toc) { if ($toc['id'] == $lp_item_id && ($toc['type']=='quiz')) { $type_quiz = true; @@ -154,22 +162,23 @@ $autostart = 'true'; if ($type_quiz && !empty($_REQUEST['exeId']) && isset($_GET['lp_id']) && isset($_GET['lp_item_id'])) { global $src; $_SESSION['oLP']->items[$_SESSION['oLP']->current]->write_to_db(); - $TBL_TRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); - $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW); - $TBL_LP_VIEW = Database::get_course_table(TABLE_LP_VIEW); - $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM); - $safe_item_id = Database::escape_string($_GET['lp_item_id']); - $safe_id = Database::escape_string($_GET['lp_id']); - $safe_exe_id = Database::escape_string($_REQUEST['exeId']); + $TBL_TRACK_EXERCICES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); + $TBL_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW); + $TBL_LP_VIEW = Database::get_course_table(TABLE_LP_VIEW); + $TBL_LP_ITEM = Database::get_course_table(TABLE_LP_ITEM); + $safe_item_id = Database::escape_string($_GET['lp_item_id']); + $safe_id = Database::escape_string($_GET['lp_id']); + $safe_exe_id = intval($_REQUEST['exeId']); if ($safe_id == strval(intval($safe_id)) && $safe_item_id == strval(intval($safe_item_id))) { - $sql = 'SELECT start_date,exe_date,exe_result,exe_weighting FROM ' . $TBL_TRACK_EXERCICES . ' WHERE exe_id = '.(int)$safe_exe_id; + $sql = 'SELECT start_date,exe_date,exe_result,exe_weighting FROM ' . $TBL_TRACK_EXERCICES . ' WHERE exe_id = '.$safe_exe_id; $res = Database::query($sql); $row_dates = Database::fetch_array($res); - $time_start_date = convert_mysql_date($row_dates['start_date']); - $time_exe_date = convert_mysql_date($row_dates['exe_date']); + $time_start_date = api_strtotime($row_dates['start_date'],'UTC'); + $time_exe_date = api_strtotime($row_dates['exe_date'],'UTC'); + $mytime = ((int)$time_exe_date-(int)$time_start_date); $score = (float)$row_dates['exe_result']; $max_score = (float)$row_dates['exe_weighting']; @@ -184,11 +193,16 @@ if ($type_quiz && !empty($_REQUEST['exeId']) && isset($_GET['lp_id']) && isset($ $sql_last_attempt = "SELECT id FROM $TBL_LP_ITEM_VIEW WHERE lp_item_id = '$safe_item_id' AND lp_view_id = '".$_SESSION['oLP']->lp_view_id."' order by id desc limit 1"; $res_last_attempt = Database::query($sql_last_attempt); $row_last_attempt = Database::fetch_row($res_last_attempt); + $lp_item_view_id = $row_last_attempt[0]; if (Database::num_rows($res_last_attempt) > 0) { - $sql_upd_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = $score,total_time = $mytime WHERE id='".$row_last_attempt[0]."'"; + $sql_upd_score = "UPDATE $TBL_LP_ITEM_VIEW SET score = $score,total_time = $mytime WHERE id='".$lp_item_view_id."'"; Database::query($sql_upd_score); - } + + $update_query = "UPDATE $TBL_TRACK_EXERCICES SET orig_lp_item_view_id = $lp_item_view_id WHERE exe_id = ".$safe_exe_id; + error_log('dddd-->'.$update_query); + Database::query($update_query); + } } if (intval($_GET['fb_type']) > 0) { @@ -286,11 +300,11 @@ if ($_SESSION['oLP']->mode == 'fullscreen') { $lp_theme_css = $my_style; } - $progress_bar = $_SESSION['oLP']->get_progress_bar('', -1, '', true); + $progress_bar = $_SESSION['oLP']->get_progress_bar('', -1, '', true); $navigation_bar = $_SESSION['oLP']->get_navigation_bar(); - $mediaplayer = $_SESSION['oLP']->get_mediaplayer($autostart); + $mediaplayer = $_SESSION['oLP']->get_mediaplayer($autostart); - $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); + $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM); $show_audioplayer = false; // Getting all the information about the item. $sql = "SELECT audio FROM " . $tbl_lp_item . " WHERE lp_id = '" . $_SESSION['oLP']->lp_id."'";
' . "\n" . - '
' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - //' '."\n" . - //' '."\n" . + public $attempt = 0; // The number for the current ID view. + public $cc; // Course (code) this learnpath is located in. + public $current; // Id of the current item the user is viewing. + public $current_score; // The score of the current item. + public $current_time_start; // The time the user loaded this resource (this does not mean he can see it yet). + public $current_time_stop; // The time the user closed this resource. + public $default_status = 'not attempted'; + public $encoding = 'UTF-8'; + public $error = ''; + public $extra_information = ''; // This string can be used by proprietary SCORM contents to store data about the current learnpath. + public $force_commit = false; // For SCORM only - if set to true, will send a scorm LMSCommit() request on each LMSSetValue(). + public $index; // The index of the active learnpath_item in $ordered_items array. + public $items = array(); + public $last; // item_id of last item viewed in the learning path. + public $last_item_seen = 0; // In case we have already come in this learnpath, reuse the last item seen if authorized. + public $license; // Which license this course has been given - not used yet on 20060522. + public $lp_id; // DB ID for this learnpath. + public $lp_view_id; // DB ID for lp_view + public $log_file; // File where to log learnpath API msg. + public $maker; // Which maker has conceived the content (ENI, Articulate, ...). + public $message = ''; + public $mode = 'embedded'; // Holds the video display mode (fullscreen or embedded). + public $name; // Learnpath name (they generally have one). + public $ordered_items = array(); // List of the learnpath items in the order they are to be read. + public $path = ''; // Path inside the scorm directory (if scorm). + public $theme; // The current theme of the learning path. + public $preview_image; // The current image of the learning path. + + // Tells if all the items of the learnpath can be tried again. Defaults to "no" (=1). + public $prevent_reinit = 1; + + // Describes the mode of progress bar display. + public $progress_bar_mode = '%'; + + // Percentage progress as saved in the db. + public $progress_db = '0'; + public $proximity; // Wether the content is distant or local or unknown. + public $refs_list = array (); //list of items by ref => db_id. Used only for prerequisites match. + // !!!This array (refs_list) is built differently depending on the nature of the LP. + // If SCORM, uses ref, if Chamilo, uses id to keep a unique value. + public $type; //type of learnpath. Could be 'dokeos', 'scorm', 'scorm2004', 'aicc', ... + // TODO: Check if this type variable is useful here (instead of just in the controller script). + public $user_id; //ID of the user that is viewing/using the course + public $update_queue = array(); + public $scorm_debug = 0; + + public $arrMenu = array(); // Array for the menu items. + + public $debug = 0; // Logging level. + + public $lp_session_id =0; + public $lp_view_session_id =0; // The specific view might be bound to a session. + + public $prerequisite = 0; + + /** + * Class constructor. Needs a database handler, a course code and a learnpath id from the database. + * Also builds the list of items into $this->items. + * @param string Course code + * @param integer Learnpath ID + * @param integer User ID + * @return boolean True on success, false on error + */ + public function __construct($course, $lp_id, $user_id) { + // Check params. + // Check course code. + if ($this->debug > 0) {error_log('New LP - In learnpath::__construct('.$course.','.$lp_id.','.$user_id.')', 0); } + if (empty($course)) { + $this->error = 'Course code is empty'; + return false; + } else { + $main_table = Database::get_main_table(TABLE_MAIN_COURSE); + //$course = Database::escape_string($course); + $course = $this->escape_string($course); + $sql = "SELECT * FROM $main_table WHERE code = '$course'"; + if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying course: '.$sql, 0); } + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $this->cc = $course; + } else { + $this->error = 'Course code does not exist in database ('.$sql.')'; + return false; + } + } + // Check learnpath ID. + if (empty($lp_id)) { + $this->error = 'Learnpath ID is empty'; + return false; + } else { + // TODO: Make it flexible to use any course_code (still using env course code here). + $lp_table = Database::get_course_table(TABLE_LP_MAIN); + + //$id = Database::escape_integer($id); + $lp_id = $this->escape_string($lp_id); + $sql = "SELECT * FROM $lp_table WHERE id = '$lp_id'"; + if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying lp: '.$sql, 0); } + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $this->lp_id = $lp_id; + $row = Database::fetch_array($res); + $this->type = $row['lp_type']; + $this->name = stripslashes($row['name']); + $this->encoding = $row['default_encoding']; + $this->proximity = $row['content_local']; + $this->theme = $row['theme']; + $this->maker = $row['content_maker']; + $this->prevent_reinit = $row['prevent_reinit']; + $this->license = $row['content_license']; + $this->scorm_debug = $row['debug']; + $this->js_lib = $row['js_lib']; + $this->path = $row['path']; + $this->preview_image= $row['preview_image']; + $this->author= $row['author']; + $this->lp_session_id = $row['session_id']; + + if ($this->type == 2) { + if ($row['force_commit'] == 1) { + $this->force_commit = true; + } + } + $this->mode = $row['default_view_mod']; + } else { + $this->error = 'Learnpath ID does not exist in database ('.$sql.')'; + return false; + } + } + // Check user ID. + if (empty($user_id)) { + $this->error = 'User ID is empty'; + return false; + } else { + //$main_table = Database::get_main_user_table(); + $main_table = Database::get_main_table(TABLE_MAIN_USER); + //$user_id = Database::escape_integer($user_id); + $user_id = $this->escape_string($user_id); + $sql = "SELECT * FROM $main_table WHERE user_id = '$user_id'"; + if ($this->debug > 2) { error_log('New LP - learnpath::__construct() '.__LINE__.' - Querying user: '.$sql, 0); } + $res = Database::query($sql); + if (Database::num_rows($res) > 0) { + $this->user_id = $user_id; + } else { + $this->error = 'User ID does not exist in database ('.$sql.')'; + return false; + } + } + // End of variables checking. + + $session_id = api_get_session_id(); + // Get the session condition for learning paths of the base + session. + $session = api_get_session_condition($session_id); + // Now get the latest attempt from this user on this LP, if available, otherwise create a new one. + $lp_table = Database::get_course_table(TABLE_LP_VIEW); + // Selecting by view_count descending allows to get the highest view_count first. + $sql = "SELECT * FROM $lp_table WHERE lp_id = '$lp_id' AND user_id = '$user_id' $session ORDER BY view_count DESC"; + if ($this->debug > 2) { error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - querying lp_view: ' . $sql, 0); } + $res = Database::query($sql); + $view_id = 0; // Used later to query lp_item_view. + if (Database :: num_rows($res) > 0) { + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - Found previous view', 0); + } + $row = Database :: fetch_array($res); + $this->attempt = $row['view_count']; + $this->lp_view_id = $row['id']; + $this->last_item_seen = $row['last_item']; + $this->progress_db = $row['progress']; + $this->lp_view_session_id = $row['session_id']; + } else { + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - NOT Found previous view', 0); + } + $this->attempt = 1; + $sql_ins = "INSERT INTO $lp_table (lp_id,user_id,view_count, session_id) VALUES ($lp_id,$user_id,1,$session_id)"; + $res_ins = Database::query($sql_ins); + $this->lp_view_id = Database :: insert_id(); + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - inserting new lp_view: ' . $sql_ins, 0); + } + } + + // Initialise items. + $lp_item_table = Database::get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * FROM $lp_item_table WHERE lp_id = '".$this->lp_id."' ORDER BY parent_item_id, display_order"; + $res = Database::query($sql); + + while ($row = Database::fetch_array($res)) { + $oItem = ''; + //$this->ordered_items[] = $row['id']; + switch ($this->type) { + + case 3: //aicc + $oItem = new aiccItem('db', $row['id']); + if (is_object($oItem)) { + $my_item_id = $oItem->get_id(); + $oItem->set_lp_view($this->lp_view_id); + $oItem->set_prevent_reinit($this->prevent_reinit); + // Don't use reference here as the next loop will make the pointed object change. + $this->items[$my_item_id] = $oItem; + $this->refs_list[$oItem->ref] = $my_item_id; + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() - aicc object with id ' . $my_item_id . ' set in items[]', 0); + } + } + break; + case 2: + + require_once 'scorm.class.php'; + require_once 'scormItem.class.php'; + $oItem = new scormItem('db', $row['id']); + if (is_object($oItem)) { + $my_item_id = $oItem->get_id(); + $oItem->set_lp_view($this->lp_view_id); + $oItem->set_prevent_reinit($this->prevent_reinit); + // Don't use reference here as the next loop will make the pointed object change. + $this->items[$my_item_id] = $oItem; + $this->refs_list[$oItem->ref] = $my_item_id; + if ($this->debug > 2) { + error_log('New LP - object with id ' . $my_item_id . ' set in items[]', 0); + } + } + break; + + case 1: + + default: + require_once 'learnpathItem.class.php'; + $oItem = new learnpathItem($row['id'], $user_id); + if (is_object($oItem)) { + $my_item_id = $oItem->get_id(); + //$oItem->set_lp_view($this->lp_view_id); // Moved down to when we are sure the item_view exists. + $oItem->set_prevent_reinit($this->prevent_reinit); + // Don't use reference here as the next loop will make the pointed object change. + $this->items[$my_item_id] = $oItem; + $this->refs_list[$my_item_id] = $my_item_id; + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - object with id ' . $my_item_id . ' set in items[]', 0); + } + } + break; + } + + // Items is a list of pointers to all items, classified by DB ID, not SCO id. + if ($row['parent_item_id'] == 0 || empty ($this->items[$row['parent_item_id']])) { + $this->items[$row['id']]->set_level(0); + } else { + $level = $this->items[$row['parent_item_id']]->get_level() + 1; + $this->items[$row['id']]->set_level($level); + if (is_object($this->items[$row['parent_item_id']])) { + // Items is a list of pointers from item DB ids to item objects. + $this->items[$row['parent_item_id']]->add_child($row['id']); + } else { + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - The parent item (' . $row['parent_item_id'] . ') of item ' . $row['id'] . ' could not be found', 0); + } + } + } + + // Get last viewing vars. + $lp_item_view_table = Database :: get_course_table(TABLE_LP_ITEM_VIEW); + // This query should only return one or zero result. + $sql = "SELECT * " . + "FROM $lp_item_view_table " . + "WHERE lp_view_id = " . $this->lp_view_id . " " . + "AND lp_item_id = " . $row['id'] . " ORDER BY view_count DESC "; + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() - Selecting item_views: ' . $sql, 0); + } + // Get the item status. + $res2 = Database::query($sql); + if (Database :: num_rows($res2) > 0) { + // If this learnpath has already been used by this user, get his last attempt count and + // the last item seen back into this object. + //$max = 0; + $row2 = Database :: fetch_array($res2); + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() - Got item_view: ' . print_r($row2, true), 0); + } + $this->items[$row['id']]->set_status($row2['status']); + if (empty ($row2['status'])) { + $this->items[$row['id']]->set_status($this->default_status); + } + //$this->attempt = $row['view_count']; + //$this->last_item = $row['id']; + } else { // No item has been found in lp_item_view for this view. + // The first attempt of this user. Set attempt to 1 and last_item to 0 (first item available). + // TODO: If the learnpath has not got attempts activated, always use attempt '1'. + //$this->attempt = 1; + //$this->last_item = 0; + $this->items[$row['id']]->set_status($this->default_status); + // Add that row to the lp_item_view table so that we have something to show in the stats page. + $sql_ins = "INSERT INTO $lp_item_view_table " . + "(lp_item_id, lp_view_id, view_count, status) VALUES " . + "(" . $row['id'] . "," . $this->lp_view_id . ",1,'not attempted')"; + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - Inserting blank item_view : ' . $sql_ins, 0); + } + $res_ins = Database::query($sql_ins); + } + // Setting the view in the item object. + $this->items[$row['id']]->set_lp_view($this->lp_view_id); + } + $this->ordered_items = $this->get_flat_ordered_items_list($this->get_id(), 0); + $this->max_ordered_items = 0; + foreach ($this->ordered_items as $index => $dummy) { + if ($index > $this->max_ordered_items && !empty($dummy)) { + $this->max_ordered_items = $index; + } + } + // TODO: Define the current item better. + $this->first(); + if ($this->debug > 2) { + error_log('New LP - learnpath::__construct() ' . __LINE__ . ' - End of learnpath constructor for learnpath ' . $this->get_id(), 0); + } + } + + /** + * Function rewritten based on old_add_item() from Yannick Warnier. Due the fact that users can decide where the item should come, I had to overlook this function and + * I found it better to rewrite it. Old function is still available. Added also the possibility to add a description. + * + * @param int $parent + * @param int $previous + * @param string $type + * @param int $id + * @param string $title + * @param string $description + * @return int + */ + public function add_item($parent, $previous, $type = 'dokeos_chapter', $id, $title, $description, $prerequisites = 0, $max_time_allowed = 0) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::add_item(' . $parent . ',' . $previous . ',' . $type . ',' . $id . ',' . $title . ')', 0); + } + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $parent = intval($parent); + $previous = intval($previous); + $type = $this->escape_string($type); + $id = intval($id); + $max_time_allowed = $this->escape_string(htmlentities($max_time_allowed)); + if (empty ($max_time_allowed)) { + $max_time_allowed = 0; + } + //$title = htmlspecialchars($title, ENT_QUOTES, $charset); // TODO: To be checked for encoding problems. + //$title = $this->escape_string(api_convert_encoding($title, $this->encoding, $charset)); + $title = $this->escape_string(api_convert_encoding($title, $this->encoding, api_get_system_encoding())); + $description = $this->escape_string(api_convert_encoding($description, $this->encoding, api_get_system_encoding())); + $sql_count = " SELECT COUNT(id) AS num + FROM " . $tbl_lp_item . " + WHERE lp_id = " . $this->get_id() . " AND parent_item_id = " . $parent; + + $res_count = Database::query($sql_count); + $row = Database :: fetch_array($res_count); + $num = $row['num']; + + if ($num > 0) { + if ($previous == 0) { + $sql = "SELECT id, next_item_id, display_order + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->get_id() . " AND + parent_item_id = " . $parent . " AND + previous_item_id = 0 OR previous_item_id=" . $parent; + $result = Database::query($sql); + $row = Database :: fetch_array($result); + + $tmp_previous = 0; + $next = $row['id']; + $display_order = 0; + } else { + $previous = (int) $previous; + $sql = " + SELECT + id, + previous_item_id, + next_item_id, + display_order + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->get_id() . " AND + id = " . $previous; + + $result = Database::query($sql); + $row = Database :: fetch_array($result); + + $tmp_previous = $row['id']; + $next = $row['next_item_id']; + + $display_order = $row['display_order']; + } + } else { + $tmp_previous = 0; + $next = 0; + $display_order = 0; + } + + $new_item_id = -1; + $id = $this->escape_string($id); + + if ($type == 'quiz') { + $sql = 'SELECT SUM(ponderation) + FROM ' . Database :: get_course_table(TABLE_QUIZ_QUESTION) . ' as quiz_question + INNER JOIN ' . Database :: get_course_table(TABLE_QUIZ_TEST_QUESTION) . ' as quiz_rel_question + ON quiz_question.id = quiz_rel_question.question_id + AND quiz_rel_question.exercice_id = ' . $id; + $rsQuiz = Database::query($sql); + $max_score = Database :: result($rsQuiz, 0, 0); + } else { + $max_score = 100; + } + + if ($prerequisites != 0) { + $sql_ins = " + INSERT INTO " . $tbl_lp_item . " ( + lp_id, + item_type, + ref, + title, + description, + path, + max_score, + parent_item_id, + previous_item_id, + next_item_id, + display_order, + prerequisite, + max_time_allowed + + ) VALUES ( + " . $this->get_id() . ", + '" . $type . "', + '', + '" . $title . "', + '" . $description . "', + '" . $id . "', + '" . $max_score . "', + " . $parent . ", + " . $previous . ", + " . $next . ", + " . ($display_order +1) . ", + " . $prerequisites . ", + " . $max_time_allowed . " + )"; + } else { + // Insert new item. + $sql_ins = " + INSERT INTO " . $tbl_lp_item . " ( + lp_id, + item_type, + ref, + title, + description, + path, + max_score, + parent_item_id, + previous_item_id, + next_item_id, + display_order, + max_time_allowed + ) VALUES ( + " . $this->get_id() . ", + '" . $type . "', + '', + '" . $title . "', + '" . $description . "', + '" . $id . "', + '" . $max_score . "', + " . $parent . ", + " . $previous . ", + " . $next . ", + " . ($display_order +1) . ", + " . $max_time_allowed . " + )"; + } + + if ($this->debug > 2) { + error_log('New LP - Inserting dokeos_chapter: ' . $sql_ins, 0); + } + + $res_ins = Database::query($sql_ins); + + if ($res_ins > 0) { + $new_item_id = Database :: insert_id($res_ins); + + // Update the item that should come after the new item. + $sql_update_next = " + UPDATE " . $tbl_lp_item . " + SET previous_item_id = " . $new_item_id . " + WHERE id = " . $next; + + $res_update_next = Database::query($sql_update_next); + + // Update the item that should be before the new item. + $sql_update_previous = " + UPDATE " . $tbl_lp_item . " + SET next_item_id = " . $new_item_id . " + WHERE id = " . $tmp_previous; + + $res_update_previous = Database::query($sql_update_previous); + + // Update all the items after the new item. + $sql_update_order = " + UPDATE " . $tbl_lp_item . " + SET display_order = display_order + 1 + WHERE + lp_id = " . $this->get_id() . " AND + id <> " . $new_item_id . " AND + parent_item_id = " . $parent . " AND + display_order > " . $display_order; + + $res_update_previous = Database::query($sql_update_order); + + // Update the item that should come after the new item. + $sql_update_ref = " + UPDATE " . $tbl_lp_item . " + SET ref = " . $new_item_id . " + WHERE id = " . $new_item_id; + + Database::query($sql_update_ref); + + } + + // Upload audio. + if (!empty ($_FILES['mp3']['name'])) { + // Create the audio folder if it does not exist yet. + global $_course; + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; + if (!is_dir($filepath . 'audio')) { + mkdir($filepath . 'audio', api_get_permissions_for_new_directories()); + $audio_id = add_document($_course, '/audio', 'folder', 0, 'audio'); + api_item_property_update($_course, TOOL_DOCUMENT, $audio_id, 'FolderCreated', api_get_user_id(), null, null, null, null, api_get_session_id()); + } + + // Upload the file in the documents tool. + include_once api_get_path(LIBRARY_PATH).'fileUpload.lib.php'; + $file_path = handle_uploaded_document($_course, $_FILES['mp3'], api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document', '/audio', api_get_user_id(), '', '', '', '', '', false); + + // Getting the filename only. + $file_components = explode('/', $file_path); + $file = $file_components[count($file_components) - 1]; + + // Store the mp3 file in the lp_item table. + $sql_insert_audio = "UPDATE $tbl_lp_item SET audio = '" . Database :: escape_string($file) . "' WHERE id = '" . Database :: escape_string($new_item_id) . "'"; + Database::query($sql_insert_audio); + } + return $new_item_id; + } + + /** + * Static admin function allowing addition of a learnpath to a course. + * @param string Course code + * @param string Learnpath name + * @param string Learnpath description string, if provided + * @param string Type of learnpath (default = 'guess', others = 'dokeos', 'aicc',...) + * @param string Type of files origin (default = 'zip', others = 'dir','web_dir',...) + * @param string Zip file containing the learnpath or directory containing the learnpath + * @return integer The new learnpath ID on success, 0 on failure + */ + public function add_lp($course, $name, $description = '', $learnpath = 'guess', $origin = 'zip', $zipname = '') { + global $charset; + + //if ($this->debug > 0) { error_log('New LP - In learnpath::add_lp()', 0); } + $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); + // Check course code exists. + // Check lp_name doesn't exist, otherwise append something. + $i = 0; + $name = learnpath :: escape_string($name); + + // Session id. + $session_id = api_get_session_id(); + + $check_name = "SELECT * FROM $tbl_lp WHERE name = '$name'"; + //if ($this->debug > 2) { error_log('New LP - Checking the name for new LP: '.$check_name, 0); } + $res_name = Database::query($check_name); + while (Database :: num_rows($res_name)) { + // There is already one such name, update the current one a bit. + $i++; + $name = $name . ' - ' . $i; + $check_name = "SELECT * FROM $tbl_lp WHERE name = '$name'"; + //if ($this->debug > 2) { error_log('New LP - Checking the name for new LP: '.$check_name, 0); } + $res_name = Database::query($check_name); + } + // New name does not exist yet; keep it. + // Escape description. + $description = learnpath :: escape_string(api_htmlentities($description, ENT_QUOTES, $charset)); // Kevin: added htmlentities(). + $type = 1; + switch ($learnpath) { + case 'guess': + break; + case 'dokeos': + $type = 1; + break; + case 'aicc': + break; + } + switch ($origin) { + case 'zip': + // Check zipname string. If empty, we are currently creating a new Chamilo learnpath. + break; + case 'manual': + default: + $get_max = "SELECT MAX(display_order) FROM $tbl_lp"; + $res_max = Database::query($get_max); + if (Database :: num_rows($res_max) < 1) { + $dsp = 1; + } else { + $row = Database :: fetch_array($res_max); + $dsp = $row[0] + 1; + } + $sql_insert = "INSERT INTO $tbl_lp " . + "(lp_type,name,description,path,default_view_mod," . + "default_encoding,display_order,content_maker," . + "content_local,js_lib,session_id) " . + "VALUES ($type,'$name','$description','','embedded'," . + "'UTF-8','$dsp','Chamilo'," . + "'local','','".Database::escape_string($session_id)."')"; + //if ($this->debug > 2) { error_log('New LP - Inserting new lp '.$sql_insert, 0); } + $res_insert = Database::query($sql_insert); + $id = Database :: insert_id(); + if ($id > 0) { + // Insert into item_property. + api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $id, 'LearnpathAdded', api_get_user_id()); + return $id; + } + break; + } + } + + /** + * Appends a message to the message attribute + * @param string Message to append. + */ + public function append_message($string) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::append_message()', 0); + } + $this->message .= $string; + } + + /** + * Autocompletes the parents of an item in case it's been completed or passed + * @param integer Optional ID of the item from which to look for parents + */ + public function autocomplete_parents($item) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::autocomplete_parents()', 0); + } + if (empty ($item)) { + $item = $this->current; + } + $parent_id = $this->items[$item]->get_parent(); + if ($this->debug > 2) { + error_log('New LP - autocompleting parent of item ' . $item . ' (item ' . $parent_id . ')', 0); + } + if (is_object($this->items[$item]) and !empty ($parent_id)) { + // if $item points to an object and there is a parent. + if ($this->debug > 2) { + error_log('New LP - ' . $item . ' is an item, proceed', 0); + } + $current_item = & $this->items[$item]; + $parent = & $this->items[$parent_id]; // Get the parent. + // New experiment including failed and browsed in completed status. + $current_status = $current_item->get_status(); + if ($current_item->is_done() || $current_status == 'browsed' || $current_status == 'failed') { + // If the current item is completed or passes or succeeded. + $completed = true; + if ($this->debug > 2) { + error_log('New LP - Status of current item is alright', 0); + } + foreach ($parent->get_children() as $child) { + // Check all his brothers (his parent's children) for completion status. + if ($child != $item) { + if ($this->debug > 2) { + error_log('New LP - Looking at brother with ID ' . $child . ', status is ' . $this->items[$child]->get_status(), 0); + } + //if($this->items[$child]->status_is(array('completed','passed','succeeded'))) + // Trying completing parents of failed and browsed items as well. + if ($this->items[$child]->status_is(array ( + 'completed', + 'passed', + 'succeeded', + 'browsed', + 'failed' + ))) { + // Keep completion status to true. + } else { + if ($this->debug > 2) { + error_log('New LP - Found one incomplete child of ' . $parent_id . ': ' . $child . ' is ' . $this->items[$child]->get_status(), 0); + } + $completed = false; + } + } + } + if ($completed) { // If all the children were completed: + $parent->set_status('completed'); + $parent->save(false, $this->prerequisites_match($parent->get_id())); + $this->update_queue[$parent->get_id()] = $parent->get_status(); + if ($this->debug > 2) { + error_log('New LP - Added parent to update queue ' . print_r($this->update_queue, true), 0); + } + $this->autocomplete_parents($parent->get_id()); // Recursive call. + } + } else { + //error_log('New LP - status of current item is not enough to get bothered with it', 0); + } + } + } + + /** + * Autosaves the current results into the database for the whole learnpath + */ + public function autosave() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::autosave()', 0); + } + // TODO: Add save operations for the learnpath itself. + } + + /** + * Clears the message attribute + */ + public function clear_message() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::clear_message()', 0); + } + $this->message = ''; + } + + /** + * Closes the current resource + * + * Stops the timer + * Saves into the database if required + * Clears the current resource data from this object + * @return boolean True on success, false on failure + */ + public function close() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::close()', 0); + } + if (empty ($this->lp_id)) { + $this->error = 'Trying to close this learnpath but no ID is set'; + return false; + } + $this->current_time_stop = time(); + if ($this->save) { + $learnpath_view_table = Database :: get_course_table(TABLE_LP_VIEW); + /* + $sql = "UPDATE $learnpath_view_table " . + "SET " . + "stop_time = ".$this->current_time_stop.", " . + "score = ".$this->current_score.", ". + "WHERE learnpath_id = '".$this->lp_id."'"; + //$res = Database::query($sql); + $res = Database::query($res); + if (Database::affected_rows($res) < 1) { + $this->error = 'Could not update learnpath_view table while closing learnpath'; + return false; + } + */ + } + $this->ordered_items = array (); + $this->index = 0; + unset ($this->lp_id); + //unset other stuff + return true; + } + + /** + * Static admin function allowing removal of a learnpath + * @param string Course code + * @param integer Learnpath ID + * @param string Whether to delete data or keep it (default: 'keep', others: 'remove') + * @return boolean True on success, false on failure (might change that to return number of elements deleted) + */ + public function delete($course = null, $id = null, $delete = 'keep') { + // TODO: Implement a way of getting this to work when the current object is not set. + // In clear: implement this in the item class as well (abstract class) and use the given ID in queries. + //if (empty($course)) { $course = api_get_course_id(); } + //if (empty($id)) { $id = $this->get_id(); } + // If an ID is specifically given and the current LP is not the same, prevent delete. + if (!empty ($id) && ($id != $this->lp_id)) { + return false; + } + + $lp = Database :: get_course_table(TABLE_LP_MAIN); + $lp_item = Database :: get_course_table(TABLE_LP_ITEM); // Proposed by Christophe (clefevre), see below. + $lp_view = Database :: get_course_table(TABLE_LP_VIEW); + $lp_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW); + + //if ($this->debug > 0) { error_log('New LP - In learnpath::delete()', 0); } + // Delete lp item id. + foreach ($this->items as $id => $dummy) { + //$this->items[$id]->delete(); + $sql_del_view = "DELETE FROM $lp_item_view WHERE lp_item_id = '" . $id . "'"; + $res_del_item_view = Database::query($sql_del_view); + } + + // Proposed by Christophe (nickname: clefevre), see http://www.dokeos.com/forum/viewtopic.php?t=29673 + $sql_del_item = "DELETE FROM $lp_item WHERE lp_id = " . $this->lp_id; + $res_del_item = Database::query($sql_del_item); + + $sql_del_view = "DELETE FROM $lp_view WHERE lp_id = " . $this->lp_id; + //if ($this->debug > 2) { error_log('New LP - Deleting views bound to lp '.$this->lp_id.': '.$sql_del_view, 0); } + $res_del_view = Database::query($sql_del_view); + $this->toggle_publish($this->lp_id, 'i'); + //if ($this->debug > 2) { error_log('New LP - Deleting lp '.$this->lp_id.' of type '.$this->type, 0); } + if ($this->type == 2 || $this->type == 3) { + // This is a scorm learning path, delete the files as well. + $sql = "SELECT path FROM $lp WHERE id = " . $this->lp_id; + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row = Database :: fetch_array($res); + $path = $row['path']; + $sql = "SELECT id FROM $lp WHERE path = '$path' AND id != " . $this->lp_id; + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { // Another learning path uses this directory, so don't delete it. + if ($this->debug > 2) { + error_log('New LP - In learnpath::delete(), found other LP using path ' . $path . ', keeping directory', 0); + } + } else { + // No other LP uses that directory, delete it. + $course_rel_dir = api_get_course_path() . '/scorm/'; // scorm dir web path starting from /courses + $course_scorm_dir = api_get_path(SYS_COURSE_PATH) . $course_rel_dir; // The absolute system path for this course. + if ($delete == 'remove' && is_dir($course_scorm_dir . $path) and !empty ($course_scorm_dir)) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::delete(), found SCORM, deleting directory: ' . $course_scorm_dir . $path, 0); + } + // Proposed by Christophe (clefevre). + if (strcmp(substr($path, -2), "/.") == 0) { + $path = substr($path, 0, -1); // Remove "." at the end. + } + //exec('rm -rf ' . $course_scorm_dir . $path); // See Bug #5208, this is not OS-portable way. + rmdirr($course_scorm_dir . $path); + } + } + } + } + $sql_del_lp = "DELETE FROM $lp WHERE id = " . $this->lp_id; + //if ($this->debug > 2) { error_log('New LP - Deleting lp '.$this->lp_id.': '.$sql_del_lp, 0); } + $res_del_lp = Database::query($sql_del_lp); + $this->update_display_order(); // Updates the display order of all lps. + api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $this->lp_id, 'delete', api_get_user_id()); + + require_once '../gradebook/lib/be.inc.php'; + $tbl_grade_link = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_LINK); + // Delete link of gradebook tool. + $sql = 'SELECT gl.id FROM ' . $tbl_grade_link . ' gl WHERE gl.type="4" AND gl.ref_id="' . $id . '";'; + $result = Database::query($sql); + $row = Database :: fetch_array($result, 'ASSOC'); + + // Fixing gradebook link deleted see #5229. + if (!empty($row['id'])) { + $link = LinkFactory :: load($row['id']); + if ($link[0] != null) { + $link[0]->delete(); + } + } + + // TODO: Also delete items and item-views. + if (api_get_setting('search_enabled') == 'true') { + require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; + $r = delete_all_values_for_item($this->cc, TOOL_LEARNPATH, $this->lp_id); + } + } + + /** + * Removes all the children of one item - dangerous! + * @param integer Element ID of which children have to be removed + * @return integer Total number of children removed + */ + public function delete_children_items($id) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::delete_children_items(' . $id . ')', 0); + } + $num = 0; + if (empty ($id) || $id != strval(intval($id))) { + return false; + } + $lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql = "SELECT * FROM $lp_item WHERE parent_item_id = $id"; + $res = Database::query($sql); + while ($row = Database :: fetch_array($res)) { + $num += $this->delete_children_items($row['id']); + $sql_del = "DELETE FROM $lp_item WHERE id = " . $row['id']; + $res_del = Database::query($sql_del); + $num++; + } + return $num; + } + + /** + * Removes an item from the current learnpath + * @param integer Elem ID (0 if first) + * @param integer Whether to remove the resource/data from the system or leave it (default: 'keep', others 'remove') + * @return integer Number of elements moved + * @todo implement resource removal + */ + public function delete_item($id, $remove = 'keep') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::delete_item()', 0); + } + // TODO: Implement the resource removal. + if (empty ($id) || $id != strval(intval($id))) { + return false; + } + // First select item to get previous, next, and display order. + $lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_sel = "SELECT * FROM $lp_item WHERE id = $id"; + $res_sel = Database::query($sql_sel); + if (Database :: num_rows($res_sel) < 1) { + return false; + } + $row = Database :: fetch_array($res_sel); + $previous = $row['previous_item_id']; + $next = $row['next_item_id']; + $display = $row['display_order']; + $parent = $row['parent_item_id']; + $lp = $row['lp_id']; + // Delete children items. + $num = $this->delete_children_items($id); + if ($this->debug > 2) { + error_log('New LP - learnpath::delete_item() - deleted ' . $num . ' children of element ' . $id, 0); + } + // Now delete the item. + $sql_del = "DELETE FROM $lp_item WHERE id = $id"; + if ($this->debug > 2) { + error_log('New LP - Deleting item: ' . $sql_del, 0); + } + $res_del = Database::query($sql_del); + // Now update surrounding items. + $sql_upd = "UPDATE $lp_item SET next_item_id = $next WHERE id = $previous"; + $res_upd = Database::query($sql_upd); + $sql_upd = "UPDATE $lp_item SET previous_item_id = $previous WHERE id = $next"; + $res_upd = Database::query($sql_upd); + // Now update all following items with new display order. + $sql_all = "UPDATE $lp_item SET display_order = display_order-1 WHERE lp_id = $lp AND parent_item_id = $parent AND display_order > $display"; + $res_all = Database::query($sql_all); + // Remove from search engine if enabled. + if (api_get_setting('search_enabled') == 'true') { + $tbl_se_ref = Database :: get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); + $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; + $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); + $res = Database::query($sql); + if (Database :: num_rows($res) > 0) { + $row2 = Database :: fetch_array($res); + require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; + $di = new DokeosIndexer(); + $di->remove_document((int) $row2['search_did']); + } + $sql = 'DELETE FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; + $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp, $id); + Database::query($sql); + } + } + + /** + * Updates an item's content in place + * @param integer Element ID + * @param integer Parent item ID + * @param integer Previous item ID + * @param string Item title + * @param string Item description + * @param string Prerequisites (optional) + * @param string Indexing terms (optional) + * @param array The array resulting of the $_FILES[mp3] element + * @return boolean True on success, false on error + */ + public function edit_item($id, $parent, $previous, $title, $description, $prerequisites = 0, $audio = null, $max_time_allowed = 0) { + + global $charset; + + if ($this->debug > 0) { + error_log('New LP - In learnpath::edit_item()', 0); + } + if (empty ($max_time_allowed)) { + $max_time_allowed = 0; + } + if (empty ($id) || ($id != strval(intval($id))) || empty ($title)) { + return false; + } + $title = api_convert_encoding($title, $this->encoding, $charset); + $description = api_convert_encoding($description, $this->encoding, $charset); + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_select = "SELECT * FROM " . $tbl_lp_item . " WHERE id = " . $id; + $res_select = Database::query($sql_select); + $row_select = Database :: fetch_array($res_select); + $audio_update_sql = ''; + if (is_array($audio) && !empty ($audio['tmp_name']) && $audio['error'] === 0) { + // Create the audio folder if it does not exist yet. + global $_course; + $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; + if (!is_dir($filepath . 'audio')) { + mkdir($filepath . 'audio', api_get_permissions_for_new_directories()); + $audio_id = add_document($_course, '/audio', 'folder', 0, 'audio'); + api_item_property_update($_course, TOOL_DOCUMENT, $audio_id, 'FolderCreated', api_get_user_id(), null, null, null, null, api_get_session_id()); + } + + // Upload file in documents. + $pi = pathinfo($audio['name']); + if ($pi['extension'] == 'mp3') { + $c_det = api_get_course_info($this->cc); + $bp = api_get_path(SYS_COURSE_PATH) . $c_det['path'] . '/document'; + $path = handle_uploaded_document($c_det, $audio, $bp, '/audio', api_get_user_id(), 0, null, '', 0, 'rename', false, 0); + $path = substr($path, 7); + // Update reference in lp_item - audio path is the path from inside de document/audio/ dir. + $audio_update_sql = ", audio = '" . Database :: escape_string($path) . "' "; + } + } + + $same_parent = ($row_select['parent_item_id'] == $parent) ? true : false; + $same_previous = ($row_select['previous_item_id'] == $previous) ? true : false; + + // TODO: htmlspecialchars to be checked for encoding related problems. + if ($same_parent && $same_previous) { + // Only update title and description. + $sql_update = " UPDATE " . $tbl_lp_item . " + SET title = '" . $this->escape_string($title) . "', + prerequisite = '" . $prerequisites . "', + description = '" . $this->escape_string($description) . "' + " . $audio_update_sql . ", + max_time_allowed = '" . $this->escape_string($max_time_allowed) . "' + WHERE id = " . $id; + $res_update = Database::query($sql_update); + } else { + $old_parent = $row_select['parent_item_id']; + $old_previous = $row_select['previous_item_id']; + $old_next = $row_select['next_item_id']; + $old_order = $row_select['display_order']; + $old_prerequisite = $row_select['prerequisite']; + $old_max_time_allowed = $row_select['max_time_allowed']; + + /* BEGIN -- virtually remove the current item id */ + /* for the next and previous item it is like the current item doesn't exist anymore */ + + if ($old_previous != 0) { + $sql_update_next = " + UPDATE " . $tbl_lp_item . " + SET next_item_id = " . $old_next . " + WHERE id = " . $old_previous; + $res_update_next = Database::query($sql_update_next); + //echo '

' . $sql_update_next . '

'; + } + + if ($old_next != 0) { + $sql_update_previous = " + UPDATE " . $tbl_lp_item . " + SET previous_item_id = " . $old_previous . " + WHERE id = " . $old_next; + $res_update_previous = Database::query($sql_update_previous); + + //echo '

' . $sql_update_previous . '

'; + } + + // display_order - 1 for every item with a display_order bigger then the display_order of the current item. + $sql_update_order = " + UPDATE " . $tbl_lp_item . " + SET display_order = display_order - 1 + WHERE + display_order > " . $old_order . " AND lp_id = " . $this->lp_id . " AND + parent_item_id = " . $old_parent; + $res_update_order = Database::query($sql_update_order); + + //echo '

' . $sql_update_order . '

'; + + /* END -- virtually remove the current item id */ + + /* BEGIN -- update the current item id to his new location */ + + if ($previous == 0) { + // Select the data of the item that should come after the current item. + $sql_select_old = " + SELECT + id, + display_order + FROM " . $tbl_lp_item . " + WHERE + lp_id = " . $this->lp_id . " AND + parent_item_id = " . $parent . " AND + previous_item_id = " . $previous; + $res_select_old = Database::query($sql_select_old); + $row_select_old = Database :: fetch_array($res_select_old); + + //echo '

' . $sql_select_old . '

'; + + // If the new parent didn't have children before. + if (Database :: num_rows($res_select_old) == 0) { + $new_next = 0; + $new_order = 1; + } else { + $new_next = $row_select_old['id']; + $new_order = $row_select_old['display_order']; + } + + //echo 'New next_item_id of current item: ' . $new_next . '
'; + //echo 'New previous_item_id of current item: ' . $previous . '
'; + //echo 'New display_order of current item: ' . $new_order . '
'; + + } else { + // Select the data of the item that should come before the current item. + $sql_select_old = " + SELECT + next_item_id, + display_order + FROM " . $tbl_lp_item . " + WHERE id = " . $previous; + $res_select_old = Database::query($sql_select_old); + $row_select_old = Database :: fetch_array($res_select_old); + + //echo '

' . $sql_select_old . '

'; + + //echo 'New next_item_id of current item: ' . $row_select_old['next_item_id'] . '
'; + //echo 'New previous_item_id of current item: ' . $previous . '
'; + //echo 'New display_order of current item: ' . ($row_select_old['display_order'] + 1) . '
'; + + $new_next = $row_select_old['next_item_id']; + $new_order = $row_select_old['display_order'] + 1; + } + + // TODO: htmlspecialchars to be checked for encoding related problems. + // Update the current item with the new data. + $sql_update = " + UPDATE " . $tbl_lp_item . " + SET + title = '" . $this->escape_string($title) . "', + description = '" . $this->escape_string($description) . "', + parent_item_id = " . $parent . ", + previous_item_id = " . $previous . ", + next_item_id = " . $new_next . ", + display_order = " . $new_order . " + " . $audio_update_sql . " + WHERE id = " . $id; + $res_update_next = Database::query($sql_update); + //echo '

' . $sql_update . '

'; + + if ($previous != 0) { + // Update the previous item's next_item_id. + $sql_update_previous = " + UPDATE " . $tbl_lp_item . " + SET next_item_id = " . $id . " + WHERE id = " . $previous; + $res_update_next = Database::query($sql_update_previous); + //echo '

' . $sql_update_previous . '

'; + } + + if ($new_next != 0) { + // Update the next item's previous_item_id. + $sql_update_next = " + UPDATE " . $tbl_lp_item . " + SET previous_item_id = " . $id . " + WHERE id = " . $new_next; + $res_update_next = Database::query($sql_update_next); + //echo '

' . $sql_update_next . '

'; + } + + if ($old_prerequisite != $prerequisites) { + $sql_update_next = " + UPDATE " . $tbl_lp_item . " + SET prerequisite = " . $prerequisites . " + WHERE id = " . $id; + $res_update_next = Database::query($sql_update_next); + } + + if ($old_max_time_allowed != $max_time_allowed) { + $sql_update_max_time_allowed = " + UPDATE " . $tbl_lp_item . " + SET max_time_allowed = " . $max_time_allowed . " + WHERE id = " . $id; + $res_update_max_time_allowed = Database::query($sql_update_max_time_allowed); + } + + // Update all the items with the same or a bigger display_order than the current item. + $sql_update_order = " + UPDATE " . $tbl_lp_item . " + SET display_order = display_order + 1 + WHERE + lp_id = " . $this->get_id() . " AND + id <> " . $id . " AND + parent_item_id = " . $parent . " AND + display_order >= " . $new_order; + + $res_update_next = Database::query($sql_update_order); + } + } + + /** + * Updates an item's prereq in place + * @param integer Element ID + * @param string Prerequisite Element ID + * @param string Prerequisite item type + * @param string Prerequisite min score + * @param string Prerequisite max score + * @return boolean True on success, false on error + */ + public function edit_item_prereq($id, $prerequisite_id, $mastery_score = 0, $max_score = 100) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::edit_item_prereq(' . $id . ',' . $prerequisite_id . ',' . $mastery_score . ',' . $max_score . ')', 0); + } + + if (empty ($id) or ($id != strval(intval($id))) or empty ($prerequisite_id)) { + return false; + } + + $prerequisite_id = $this->escape_string($prerequisite_id); + + $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); + + if (!is_numeric($mastery_score) || $mastery_score < 0) + $mastery_score = 0; + + if (!is_numeric($max_score) || $max_score < 0) + $max_score = 100; + + if ($mastery_score > $max_score) + $max_score = $mastery_score; + + if (!is_numeric($prerequisite_id)) + $prerequisite_id = 'NULL'; + + $sql_upd = " UPDATE " . $tbl_lp_item . " + SET prerequisite = " . $prerequisite_id . " WHERE id = " . $id; + $res_upd = Database::query($sql_upd); + + if ($prerequisite_id != 'NULL' && $prerequisite_id != '') { + $sql_upd = " UPDATE " . $tbl_lp_item . " SET + mastery_score = " . $mastery_score . + //", max_score = " . $max_score . " " . // Max score cannot be changed in the form anyway - see display_item_prerequisites_form(). + " WHERE ref = '" . $prerequisite_id . "'"; // Will this be enough to ensure unicity? + $res_upd = Database::query($sql_upd); + } + // TODO: Update the item object (can be ignored for now because refreshed). + return true; + } + + /** + * Escapes a string with the available database escape function + * @param string String to escape + * @return string String escaped + */ + public function escape_string($string) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::escape_string('.$string.')', 0); } + return Database :: escape_string($string); + } + + /** + * Static admin function exporting a learnpath into a zip file + * @param string Export type (scorm, zip, cd) + * @param string Course code + * @param integer Learnpath ID + * @param string Zip file name + * @return string Zip file path (or false on error) + */ + public function export_lp($type, $course, $id, $zipname) { + //if ($this->debug > 0) { error_log('New LP - In learnpath::export_lp()', 0); } + if (empty($type) || empty($course) || empty($id) || empty($zipname)) { + return false; + } + $url = ''; + + switch ($type) { + case 'scorm': + break; + case 'zip': + break; + case 'cdrom': + break; + } + return $url; + } + + /** + * Gets all the chapters belonging to the same parent as the item/chapter given + * Can also be called as abstract method + * @param integer Item ID + * @return array A list of all the "brother items" (or an empty array on failure) + */ + public function get_brother_chapters($id) { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_brother_chapters()', 0); + } + + if (empty ($id) OR $id != strval(intval($id))) { + return array (); + } + + $lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_parent = "SELECT * FROM $lp_item WHERE id = $id AND item_type='dokeos_chapter'"; + $res_parent = Database::query($sql_parent); + if (Database :: num_rows($res_parent) > 0) { + $row_parent = Database :: fetch_array($res_parent); + $parent = $row_parent['parent_item_id']; + $sql_bros = "SELECT * FROM $lp_item WHERE parent_item_id = $parent AND id = $id AND item_type='dokeos_chapter' ORDER BY display_order"; + $res_bros = Database::query($sql_bros); + $list = array (); + while ($row_bro = Database :: fetch_array($res_bros)) { + $list[] = $row_bro; + } + return $list; + } + return array (); + } + + /** + * Gets all the items belonging to the same parent as the item given + * Can also be called as abstract method + * @param integer Item ID + * @return array A list of all the "brother items" (or an empty array on failure) + */ + public function get_brother_items($id) { + + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_brother_items(' . $id . ')', 0); + } + + if (empty ($id) OR $id != strval(intval($id))) { + return array (); + } + + $lp_item = Database :: get_course_table(TABLE_LP_ITEM); + $sql_parent = "SELECT * FROM $lp_item WHERE id = $id"; + $res_parent = Database::query($sql_parent); + if (Database :: num_rows($res_parent) > 0) { + $row_parent = Database :: fetch_array($res_parent); + $parent = $row_parent['parent_item_id']; + $sql_bros = "SELECT * FROM $lp_item WHERE parent_item_id = $parent ORDER BY display_order"; + $res_bros = Database::query($sql_bros); + $list = array (); + while ($row_bro = Database :: fetch_array($res_bros)) { + $list[] = $row_bro; + } + return $list; + } + return array (); + } + + /** + * Get the specific prefix index terms of this learning path + * @return array Array of terms + */ + public function get_common_index_terms_by_prefix($prefix) { + require_once api_get_path(LIBRARY_PATH).'specific_fields_manager.lib.php'; + $terms = get_specific_field_values_list_by_prefix($prefix, $this->cc, TOOL_LEARNPATH, $this->lp_id); + $prefix_terms = array(); + foreach ($terms as $term) { + $prefix_terms[] = $term['value']; + } + return $prefix_terms; + } + + /** + * Gets the number of items currently completed + * @return integer The number of items currently completed + */ + public function get_complete_items_count() { + + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_complete_items_count()', 0); + } + + $i = 0; + + foreach ($this->items as $id => $dummy) { + // Trying failed and browsed considered "progressed" as well. + if ($this->items[$id]->status_is(array ( + 'completed', + 'passed', + 'succeeded', + 'browsed', + 'failed' + )) && $this->items[$id]->get_type() != 'dokeos_chapter' && $this->items[$id]->get_type() != 'dir') { + $i++; + } + } + return $i; + } + + /** + * Gets the current item ID + * @return integer The current learnpath item id + */ + public function get_current_item_id() { + $current = 0; + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_current_item_id()', 0); + } + if (!empty ($this->current)) { + $current = $this->current; + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::get_current_item_id() - Returning ' . $current, 0); + } + return $current; + } + + /** + * Force to get the first learnpath item id + * @return integer The current learnpath item id + */ + public function get_first_item_id() { + $current = 0; + if (is_array($this->ordered_items)) { + $current = $this->ordered_items[0]; + } + return $current; + } + + /** + * Gets the total number of items available for viewing in this SCORM + * @return integer The total number of items + */ + public function get_total_items_count() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_total_items_count()', 0); + } + return count($this->items); + } + + /** + * Gets the total number of items available for viewing in this SCORM but without chapters + * @return integer The total no-chapters number of items + */ + public function get_total_items_count_without_chapters() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_total_items_count_without_chapters()', 0); + } + $total = 0; + foreach ($this->items as $temp => $temp2) { + if (!in_array($temp2->get_type(), array ( + 'dokeos_chapter', + 'chapter', + 'dir' + ))) + $total++; + } + return $total; + } + + /** + * Gets the first element URL. + * @return string URL to load into the viewer + */ + public function first() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::first()', 0); + } + // Test if the last_item_seen exists and is not a dir. + if (count($this->ordered_items) == 0) { + $this->index = 0; + } + if (!empty($this->last_item_seen) && !empty($this->items[$this->last_item_seen]) && $this->items[$this->last_item_seen]->get_type() != 'dir' && $this->items[$this->last_item_seen]->get_type() != 'dokeos_chapter' && !$this->items[$this->last_item_seen]->is_done()) { + if ($this->debug > 2) { + error_log('New LP - In learnpath::first() - Last item seen is ' . $this->last_item_seen . ' of type ' . $this->items[$this->last_item_seen]->get_type(), 0); + } + $index = -1; + foreach ($this->ordered_items as $myindex => $item_id) { + if ($item_id == $this->last_item_seen) { + $index = $myindex; + break; + } + } + if ($index == -1) { + // Index hasn't changed, so item not found - panic (this shouldn't happen). + if ($this->debug > 2) { + error_log('New LP - Last item (' . $this->last_item_seen . ') was found in items but not in ordered_items, panic!', 0); + } + return false; + } else { + $this->last = $this->last_item_seen; + $this->current = $this->last_item_seen; + $this->index = $index; + } + } else { + if ($this->debug > 2) { + error_log('New LP - In learnpath::first() - No last item seen', 0); + } + $index = 0; + // Loop through all ordered items and stop at the first item that is + // not a directory *and* that has not been completed yet. + while (!empty($this->ordered_items[$index]) AND is_a($this->items[$this->ordered_items[$index]], 'learnpathItem') AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' OR $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter' OR $this->items[$this->ordered_items[$index]]->is_done() === true) AND $index < $this->max_ordered_items) { + $index++; + } + $this->last = $this->current; + // current is + $this->current = $this->ordered_items[$index]; + $this->index = $index; + if ($this->debug > 2) { + error_log('New LP - In learnpath::first() - No last item seen. New last = ' . $this->last . '(' . $this->ordered_items[$index] . ')', 0); + } + } + if ($this->debug > 2) { + error_log('New LP - In learnpath::first() - First item is ' . $this->get_current_item_id()); + } + } + + /** + * Gets the information about an item in a format usable as JavaScript to update + * the JS API by just printing this content into the section of the message frame + * @param integer Item ID + * @return string + */ + public function get_js_info($item_id = '') { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_js_info(' . $item_id . ')', 0); + } + + $info = ''; + $item_id = $this->escape_string($item_id); + + if (!empty($item_id) && is_object($this->items[$item_id])) { + //if item is defined, return values from DB + $oItem = $this->items[$item_id]; + $info .= ''; + if ($this->debug > 2) { + error_log('New LP - in learnpath::get_js_info(' . $item_id . ') - returning: ' . $info, 0); + } + return $info; + + } else { + + // If item_id is empty, just update to default SCORM data. + $info .= ''; + if ($this->debug > 2) { + error_log('New LP - in learnpath::get_js_info(' . $item_id . ') - returning: ' . $info, 0); + } + return $info; + } + } + + /** + * Gets the js library from the database + * @return string The name of the javascript library to be used + */ + public function get_js_lib() { + $lib = ''; + if (!empty ($this->js_lib)) { + $lib = $this->js_lib; + } + return $lib; + } + + /** + * Gets the learnpath database ID + * @return integer Learnpath ID in the lp table + */ + public function get_id() { + //if ($this->debug > 0) { error_log('New LP - In learnpath::get_id()', 0); } + if (!empty ($this->lp_id)) { + return $this->lp_id; + } else { + return 0; + } + } + + /** + * Gets the last element URL. + * @return string URL to load into the viewer + */ + public function get_last() { + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_last()', 0); + } + $this->index = count($this->ordered_items) - 1; + return $this->ordered_items[$this->index]; + } + + /** + * Gets the navigation bar for the learnpath display screen + * @return string The HTML string to use as a navigation bar + */ + public function get_navigation_bar() { + + if ($this->debug > 0) { + error_log('New LP - In learnpath::get_navigation_bar()', 0); + } + + // TODO: Find a good value for the following variables. + $file = ''; + $openDir = ''; + $edoceo = ''; + $time = 0; + $navbar = ''; + $RequestUri = ''; + $mycurrentitemid = $this->get_current_item_id(); + if ($this->mode == 'fullscreen') { + $navbar = '' . "\n" . + ' ' . "\n" . + ' ' . "\n" . - ' ' . "\n" . - '
' . "\n" . + '
' . "\n" . + ' ' . "\n" . + ' ' . "\n" . + ' ' . "\n" . + //' '."\n" . + //' '."\n" . '
' . "\n" . - '
' . "\n"; - - } else { - $navbar = '' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - '
' . "\n" . - '
' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - ' ' . "\n" . - // ' '."\n" . - '
' . "\n" . - '
' . "\n"; - } - return $navbar; - } - - /** - * Gets the next resource in queue (url). - * @return string URL to load into the viewer - */ - public function get_next_index() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_next_index()', 0); - } - // TODO - $index = $this->index; - $index++; - if ($this->debug > 2) { - error_log('New LP - Now looking at ordered_items[' . ($index) . '] - type is ' . $this->items[$this->ordered_items[$index]]->type, 0); - } - while (!empty ($this->ordered_items[$index]) AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter') AND $index < $this->max_ordered_items) { - $index++; - if ($index == $this->max_ordered_items){ - if ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter') { - return $this->index; - } else { - return $index; - } - } - } - if (empty ($this->ordered_items[$index])) { - return $this->index; - } - if ($this->debug > 2) { - error_log('New LP - index is now ' . $index, 0); - } - return $index; - } - - /** - * Gets item_id for the next element - * @return integer Next item (DB) ID - */ - public function get_next_item_id() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_next_item_id()', 0); - } - $new_index = $this->get_next_index(); - if (!empty ($new_index)) { - if (isset ($this->ordered_items[$new_index])) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_next_index() - Returning ' . $this->ordered_items[$new_index], 0); - } - return $this->ordered_items[$new_index]; - } - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_next_index() - Problem - Returning 0', 0); - } - return 0; - } - - /** - * Returns the package type ('scorm','aicc','scorm2004','dokeos','ppt'...) - * - * Generally, the package provided is in the form of a zip file, so the function - * has been written to test a zip file. If not a zip, the function will return the - * default return value: '' - * @param string the path to the file - * @param string the original name of the file - * @return string 'scorm','aicc','scorm2004','dokeos' or '' if the package cannot be recognized - */ - public function get_package_type($file_path, $file_name) { - - // Get name of the zip file without the extension. - $file_info = pathinfo($file_name); - $filename = $file_info['basename']; // Name including extension. - $extension = $file_info['extension']; // Extension only. - - if (!empty($_POST['ppt2lp']) && !in_array(strtolower($extension), array ( - 'dll', - 'exe' - ))) { - return 'oogie'; - } - if (!empty($_POST['woogie']) && !in_array(strtolower($extension), array ( - 'dll', - 'exe' - ))) { - return 'woogie'; - } - - $file_base_name = str_replace('.' . $extension, '', $filename); // Filename without its extension. - - $zipFile = new pclZip($file_path); - // Check the zip content (real size and file extension). - $zipContentArray = $zipFile->listContent(); - $package_type = ''; - $at_root = false; - $manifest = ''; - - // The following loop should be stopped as soon as we found the right imsmanifest.xml (how to recognize it?). - if (is_array($zipContentArray) && count($zipContentArray) > 0) { - foreach ($zipContentArray as $thisContent) { - if (preg_match('~.(php.*|phtml)$~i', $thisContent['filename'])) { - // New behaviour: Don't do anything. These files will be removed in scorm::import_package. - } - elseif (stristr($thisContent['filename'], 'imsmanifest.xml') !== false) { - $manifest = $thisContent['filename']; // Just the relative directory inside scorm/ - $package_type = 'scorm'; - break; // Exit the foreach loop. - } - elseif (preg_match('/aicc\//i', $thisContent['filename'])) { - // If found an aicc directory... (!= false means it cannot be false (error) or 0 (no match)). - $package_type = 'aicc'; - //break; // Don't exit the loop, because if we find an imsmanifest afterwards, we want it, not the AICC. - } else { - $package_type = ''; - } - } - } - return $package_type; - } - - /** - * Gets the previous resource in queue (url). Also initialises time values for this viewing - * @return string URL to load into the viewer - */ - public function get_previous_index() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_previous_index()', 0); - } - $index = $this->index; - if (isset ($this->ordered_items[$index -1])) { - $index--; - while (isset ($this->ordered_items[$index]) AND ($this->items[$this->ordered_items[$index]]->get_type() == 'dir' || $this->items[$this->ordered_items[$index]]->get_type() == 'dokeos_chapter')) { - $index--; - if ($index < 0) { - return $this->index; - } - } - } else { - if ($this->debug > 2) { - error_log('New LP - get_previous_index() - there was no previous index available, reusing ' . $index, 0); - } - // There is no previous item. - } - return $index; - } - - /** - * Gets item_id for the next element - * @return integer Previous item (DB) ID - */ - public function get_previous_item_id() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_previous_item_id()', 0); - } - $new_index = $this->get_previous_index(); - return $this->ordered_items[$new_index]; - } - - /** - * Gets the progress value from the progress_db attribute - * @return integer Current progress value - */ - public function get_progress() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_progress()', 0); - } - if (!empty ($this->progress_db)) { - return $this->progress_db; - } - return 0; - } - - /** - * Gets the progress value from the progress field in the database (allows use as abstract method) - * @param integer Learnpath ID - * @param integer User ID - * @param string Mode of display ('%','abs' or 'both') - * @param string Course database name (optional, defaults to '') - * @param boolean Whether to return null if no record was found (true), or 0 (false) (optional, defaults to false) - * @return integer Current progress value as found in the database - */ - public function get_db_progress($lp_id, $user_id, $mode = '%', $course_db = '', $sincere = false) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_db_progress()', 0); } - $session_id = api_get_session_id(); - $session_condition = api_get_session_condition($session_id); - $table = Database :: get_course_table(TABLE_LP_VIEW, $course_db); - $sql = "SELECT * FROM $table WHERE lp_id = $lp_id AND user_id = $user_id $session_condition"; - $res = Database::query($sql); - $view_id = 0; - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $progress = $row['progress']; - $view_id = $row['id']; - } else { - if ($sincere) { - return null; - } - } - if (empty ($progress)) { - $progress = '0'; - } - if ($mode == '%') { - return $progress . '%'; - } else { - // Get the number of items completed and the number of items total. - $tbl = Database :: get_course_table(TABLE_LP_ITEM, $course_db); - $sql = "SELECT count(*) FROM $tbl WHERE lp_id = " . $lp_id . " - AND item_type NOT IN('dokeos_chapter','chapter','dir')"; - $res = Database::query($sql); - $row = Database :: fetch_array($res); - $total = $row[0]; - $tbl_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW, $course_db); - $tbl_item = Database :: get_course_table(TABLE_LP_ITEM, $course_db); - - //$sql = "SELECT count(distinct(lp_item_id)) FROM $tbl WHERE lp_view_id = ".$view_id." AND status IN ('passed','completed','succeeded')"; - // Trying as also counting browsed and failed items. - $sql = "SELECT count(distinct(lp_item_id)) - FROM $tbl_item_view as item_view - INNER JOIN $tbl_item as item - ON item.id = item_view.lp_item_id - AND item_type NOT IN('dokeos_chapter','chapter','dir') - WHERE lp_view_id = " . $view_id . " - AND status IN ('passed','completed','succeeded','browsed','failed')"; - $res = Database::query($sql); - $row = Database :: fetch_array($res); - $completed = $row[0]; - if ($mode == 'abs') { - return $completed . '/' . $total; - } - elseif ($mode == 'both') { - if ($progress < ($completed / ($total ? $total : 1))) { - $progress = number_format(($completed / ($total ? $total : 1)) * 100, 0); - } - return $progress . '% (' . $completed . '/' . $total . ')'; - } - } - return $progress; - } - - /** - * Returns the HTML necessary to print a mediaplayer block inside a page - * @return string The mediaplayer HTML - */ - public function get_mediaplayer($autostart='true') { - - global $_course; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_lp_item_view = Database :: get_course_table(TABLE_LP_ITEM_VIEW); - - // Getting all the information about the item. - $sql = "SELECT * FROM " . $tbl_lp_item . " as lp inner join " . $tbl_lp_item_view . " as lp_view on lp.id = lp_view.lp_item_id " . - "WHERE lp.id = '" . $_SESSION['oLP']->current . "'"; - $result = Database::query($sql); - $row = Database::fetch_assoc($result); - $output = ''; - - if (!empty ($row['audio'])) { - - $list = $_SESSION['oLP']->get_toc(); - $type_quiz = false; - - foreach($list as $toc) { - if ($toc['id'] == $_SESSION['oLP']->current && ($toc['type']=='quiz') ) { - $type_quiz = true; - } - } - - if ($type_quiz) { - if ($_SESSION['oLP']->prevent_reinit == 1) { - $row['status'] === 'completed' ? $autostart_audio = 'false' : $autostart_audio = 'true'; - } else { - $autostart_audio = $autostart; - } - } else { - $autostart_audio = 'true'; - } - - // The mp3 player. - $output = '
'; - $output .= ''; - $output .= '
'; - } - return $output; - } - - /** - * This function check if the learnpath is visible for student after the progress of its prerequisite is completed - * @param int Learnpath id - * @param int Student id - * @return bool True if - */ - public function is_lp_visible_for_student($lp_id, $student_id) { - - $tbl_learnpath = Database :: get_course_table(TABLE_LP_MAIN); - - // Get current prerequisite. - $sql = "SELECT prerequisite FROM $tbl_learnpath WHERE id = $lp_id"; - $rs = Database::query($sql); - $row = Database :: fetch_array($rs); - $prerequisite = $row['prerequisite']; - $is_visible = true; - $progress = 0; - - if (!empty($prerequisite)) { - $progress = self::get_db_progress($prerequisite,$student_id,'%'); - $progress = intval($progress); - if ($progress < 100) { - $is_visible = false; - } - } - - return $is_visible; - } - - /** - * Gets a progress bar for the learnpath by counting the number of items in it and the number of items - * completed so far. - * @param string Mode in which we want the values - * @param integer Progress value to display (optional but mandatory if used in abstract context) - * @param string Text to display near the progress value (optional but mandatory in abstract context) - * @param boolean true if it comes from a Diplay LP view - * @return string HTML string containing the progress bar - */ - public function get_progress_bar($mode = '', $percentage = -1, $text_add = '', $from_lp = false) { - //if ($this->debug > 0) {error_log('New LP - In learnpath::get_progress_bar('.$mode.','.$percentage.','.$text_add.','.$from_lp.')', 0); } - global $lp_theme_css; - - // Setting up the CSS path of the current style if exists. - if (!empty ($lp_theme_css)) { - $css_path = api_get_path(WEB_CODE_PATH) . 'css/' . $lp_theme_css . '/images/'; - } else { - $css_path = '../img/'; - } - - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_progress_bar()', 0); } - if (isset($this) && is_object($this) && ($percentage == '-1' OR $text_add == '')) { - list($percentage, $text_add) = $this->get_progress_bar_text($mode); - } - $text = $percentage . $text_add; - - // Default progress bar config - // times that will be greater or shorter - $factor = 1.2; - if ($from_lp) - $progress_height = '26'; - else - $progress_height = '16'; - $size = str_replace('%', '', $percentage); - - $output = - '
' . - '' . - '' . - ''; - - if ($percentage <= 98) { - $output .= ''; - } else { - $output .= ''; - } - - $output .= '
' . - '
' . $text . '
'; - - return $output; - } - - /** - * Gets the progress bar info to display inside the progress bar. Also used by scorm_api.php - * @param string Mode of display (can be '%' or 'abs').abs means we display a number of completed elements per total elements - * @param integer Additional steps to fake as completed - * @return list Percentage or number and symbol (% or /xx) - */ - public function get_progress_bar_text($mode = '', $add = 0) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_progress_bar_text()', 0); - } - if (empty ($mode)) { - $mode = $this->progress_bar_mode; - } - $total_items = $this->get_total_items_count_without_chapters(); - if ($this->debug > 2) { - error_log('New LP - Total items available in this learnpath: ' . $total_items, 0); - } - $i = $this->get_complete_items_count(); - if ($this->debug > 2) { - error_log('New LP - Items completed so far: ' . $i, 0); - } - if ($add != 0) { - $i += $add; - if ($this->debug > 2) { - error_log('New LP - Items completed so far (+modifier): ' . $i, 0); - } - } - $text = ''; - if ($i > $total_items) { - $i = $total_items; - } - if ($mode == '%') { - if ($total_items > 0) { - $percentage = ((float) $i / (float) $total_items) * 100; - } else { - $percentage = 0; - } - $percentage = number_format($percentage, 0); - $text = '%'; - } - elseif ($mode == 'abs') { - $percentage = $i; - $text = '/' . $total_items; - } - return array ( - $percentage, - $text - ); - } - - /** - * Gets the progress bar mode - * @return string The progress bar mode attribute - */ - public function get_progress_bar_mode() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_progress_bar_mode()', 0); - } - if (!empty ($this->progress_bar_mode)) { - return $this->progress_bar_mode; - } else { - return '%'; - } - } - - /** - * Gets the learnpath proximity (remote or local) - * @return string Learnpath proximity - */ - public function get_proximity() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_proximity()', 0); - } - if (!empty ($this->proximity)) { - return $this->proximity; - } else { - return ''; - } - } - - /** - * Gets the learnpath theme (remote or local) - * @return string Learnpath theme - */ - public function get_theme() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_theme()', 0); - } - if (!empty ($this->theme)) { - return $this->theme; - } else { - return ''; - } - } - - /** - * Gets the learnpath session id - * @return string Learnpath theme - */ - public function get_lp_session_id() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_lp_session_id()', 0); - } - if (!empty ($this->lp_session_id)) { - return $this->lp_session_id; - } else { - return 0; - } - } - - /** - * Gets the learnpath image - * @return string Web URL of the LP image - */ - public function get_preview_image() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_preview_image()', 0); - } - if (!empty ($this->preview_image)) { - return $this->preview_image; - } else { - return ''; - } - } - - /** - * Gets the learnpath author - * @return string LP's author - */ - public function get_author() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_author()', 0); - } - if (!empty ($this->author)) { - return $this->author; - } else { - return ''; - } - } - - /** - * Generate a new prerequisites string for a given item. If this item was a sco and - * its prerequisites were strings (instead of IDs), then transform those strings into - * IDs, knowing that SCORM IDs are kept in the "ref" field of the lp_item table. - * Prefix all item IDs that end-up in the prerequisites string by "ITEM_" to use the - * same rule as the scorm_export() method - * @param integer Item ID - * @return string Prerequisites string ready for the export as SCORM - */ - public function get_scorm_prereq_string($item_id) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_scorm_prereq_string()', 0); - } - if (!is_object($this->items[$item_id])) { - return false; - } - $oItem = $this->items[$item_id]; - $prereq = $oItem->get_prereq_string(); - if (empty ($prereq)) { - return ''; - } - if (preg_match('/^\d+$/', $prereq) && is_object($this->items[$prereq])) { // If the prerequisite is a simple integer ID and this ID exists as an item ID, - // then simply return it (with the ITEM_ prefix). - return 'ITEM_' . $prereq; - } else { - if (isset ($this->refs_list[$prereq])) { - // It's a simple string item from which the ID can be found in the refs list, - // so we can transform it directly to an ID for export. - return 'ITEM_' . $this->refs_list[$prereq]; - } else { - // The last case, if it's a complex form, then find all the IDs (SCORM strings) - // and replace them, one by one, by the internal IDs (chamilo db) - // TODO: Modify the '*' replacement to replace the multiplier in front of it - // by a space as well. - $find = array ( - '&', - '|', - '~', - '=', - '<>', - '{', - '}', - '*', - '(', - ')' - ); - $replace = array ( - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ', - ' ' - ); - $prereq_mod = str_replace($find, $replace, $prereq); - $ids = split(' ', $prereq_mod); - foreach ($ids as $id) { - $id = trim($id); - if (isset ($this->refs_list[$id])) { - $prereq = preg_replace('/[^a-zA-Z_0-9](' . $id . ')[^a-zA-Z_0-9]/', 'ITEM_' . $this->refs_list[$id], $prereq); - } - } - error_log('New LP - In learnpath::get_scorm_prereq_string(): returning modified string: ' . $prereq, 0); - return $prereq; - } - } - } - - /** - * Returns the XML DOM document's node - * @param resource Reference to a list of objects to search for the given ITEM_* - * @param string The identifier to look for - * @return mixed The reference to the element found with that identifier. False if not found - */ - public function get_scorm_xml_node(& $children, $id) { - for ($i = 0; $i < $children->length; $i++) { - $item_temp = $children->item($i); - if ($item_temp->nodeName == 'item') { - if ($item_temp->getAttribute('identifier') == $id) { - return $item_temp; - } - } - $subchildren = $item_temp->childNodes; - if ($subchildren->length > 0) { - $val = $this->get_scorm_xml_node($subchildren, $id); - if (is_object($val)) { - return $val; - } - } - } - return false; - } - - /** - * Returns a usable array of stats related to the current learnpath and user - * @return array Well-formatted array containing status for the current learnpath - */ - public function get_stats() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_stats()', 0); - } - // TODO - } - - /** - * Static method. Can be re-implemented by children. Gives an array of statistics for - * the given course (for all learnpaths and all users) - * @param string Course code - * @return array Well-formatted array containing status for the course's learnpaths - */ - public function get_stats_course($course) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_course()', 0); } - // TODO - } - - /** - * Static method. Can be re-implemented by children. Gives an array of statistics for - * the given course and learnpath (for all users) - * @param string Course code - * @param integer Learnpath ID - * @return array Well-formatted array containing status for the specified learnpath - */ - public function get_stats_lp($course, $lp) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_lp()', 0); } - // TODO - } - - /** - * Static method. Can be re-implemented by children. Gives an array of statistics for - * the given course, learnpath and user. - * @param string Course code - * @param integer Learnpath ID - * @param integer User ID - * @return array Well-formatted array containing status for the specified learnpath and user - */ - public function get_stats_lp_user($course, $lp, $user) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_lp_user()', 0); } - // TODO - } - - /** - * Static method. Can be re-implemented by children. Gives an array of statistics for - * the given course and learnpath (for all users) - * @param string Course code - * @param integer User ID - * @return array Well-formatted array containing status for the user's learnpaths - */ - public function get_stats_user($course, $user) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_stats_user()', 0); } - // TODO - } - - /** - * Gets the status list for all LP's items - * @return array Array of [index] => [item ID => current status] - */ - public function get_items_status_list() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_items_status_list()', 0); - } - $list = array (); - foreach ($this->ordered_items as $item_id) { - $list[] = array ( - $item_id => $this->items[$item_id]->get_status() - ); - } - return $list; - } - - /** - * Return the number of interactions for the given learnpath Item View ID. - * This method can be used as static. - * @param integer Item View ID - * @return integer Number of interactions - */ - public function get_interactions_count_from_db($lp_iv_id = 0) { - if (empty ($lp_iv_id)) { - return -1; - } - $table = Database :: get_course_table(TABLE_LP_IV_INTERACTION); - $sql = "SELECT count(*) FROM $table WHERE lp_iv_id = $lp_iv_id"; - $res = Database::query($sql); - $row = Database :: fetch_array($res); - $num = $row[0]; - return $num; - } - - /** - * Return the interactions as an array for the given lp_iv_id. - * This method can be used as static. - * @param integer Learnpath Item View ID - * @return array - * @todo Transcode labels instead of switching to HTML (which requires to know the encoding of the LP) - */ - public function get_iv_interactions_array($lp_iv_id = 0) { - - $charset = api_get_system_encoding(); - - $list = array (); - $table = Database :: get_course_table(TABLE_LP_IV_INTERACTION); - $sql = "SELECT * FROM $table WHERE lp_iv_id = $lp_iv_id ORDER BY order_id ASC"; - $res = Database::query($sql); - $num = Database :: num_rows($res); - if ($num > 0) { - $list[] = array ( - 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES, $charset), - 'id' => api_htmlentities(get_lang('InteractionID'), ENT_QUOTES, $charset), - 'type' => api_htmlentities(get_lang('Type'), ENT_QUOTES, $charset), - 'time' => api_htmlentities(get_lang('TimeFinished'), ENT_QUOTES, $charset), - 'correct_responses' => api_htmlentities(get_lang('CorrectAnswers'), ENT_QUOTES, $charset), - 'student_response' => api_htmlentities(get_lang('StudentResponse'), ENT_QUOTES, $charset), - 'result' => api_htmlentities(get_lang('Result'), ENT_QUOTES, $charset), - 'latency' => api_htmlentities(get_lang('LatencyTimeSpent'), ENT_QUOTES, $charset) - ); - while ($row = Database :: fetch_array($res)) { - $list[] = array ( - 'order_id' => ($row['order_id'] + 1), - 'id' => urldecode($row['interaction_id']), //urldecode because they often have %2F or stuff like that - 'type' => $row['interaction_type'], - 'time' => $row['completion_time'], - //'correct_responses' => $row['correct_responses'], - 'correct_responses' => '', // Hide correct responses from students. - 'student_response' => $row['student_response'], - 'result' => $row['result'], - 'latency' => $row['latency'] - ); - } - } - return $list; - } - - /** - * Return the number of objectives for the given learnpath Item View ID. - * This method can be used as static. - * @param integer Item View ID - * @return integer Number of objectives - */ - public function get_objectives_count_from_db($lp_iv_id = 0) { - if (empty ($lp_iv_id)) { - return -1; - } - $table = Database :: get_course_table(TABLE_LP_IV_OBJECTIVE); - $sql = "SELECT count(*) FROM $table WHERE lp_iv_id = $lp_iv_id"; - $res = Database::query($sql); - $row = Database :: fetch_array($res); - $num = $row[0]; - return $num; - } - - /** - * Return the objectives as an array for the given lp_iv_id. - * This method can be used as static. - * @param integer Learnpath Item View ID - * @return array - * @todo Translate labels - */ - public function get_iv_objectives_array($lp_iv_id = 0) { - - global $charset; - - $list = array(); - $table = Database :: get_course_table(TABLE_LP_IV_OBJECTIVE); - $sql = "SELECT * FROM $table WHERE lp_iv_id = $lp_iv_id ORDER BY order_id ASC"; - $res = Database::query($sql); - $num = Database :: num_rows($res); - if ($num > 0) { - $list[] = array ( - 'order_id' => api_htmlentities(get_lang('Order'), ENT_QUOTES, $charset), - 'objective_id' => api_htmlentities(get_lang('ObjectiveID'), ENT_QUOTES, $charset), - 'score_raw' => api_htmlentities(get_lang('ObjectiveRawScore'), ENT_QUOTES, $charset), - 'score_max' => api_htmlentities(get_lang('ObjectiveMaxScore'), ENT_QUOTES, $charset), - 'score_min' => api_htmlentities(get_lang('ObjectiveMinScore'), ENT_QUOTES, $charset), - 'status' => api_htmlentities(get_lang('ObjectiveStatus'), ENT_QUOTES, $charset) - ); - while ($row = Database :: fetch_array($res)) { - $list[] = array ( - 'order_id' => ($row['order_id'] + 1), - 'objective_id' => urldecode($row['objective_id']), // urldecode() because they often have %2F or stuff like that. - 'score_raw' => $row['score_raw'], - 'score_max' => $row['score_max'], - 'score_min' => $row['score_min'], - 'status' => $row['status'] - ); - } - } - return $list; - } - - /** - * Generate and return the table of contents for this learnpath. The (flat) table returned can be - * used by get_html_toc() to be ready to display - * @return array TOC as a table with 4 elements per row: title, link, status and level - */ - public function get_toc() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_toc()', 0); - } - $toc = array (); - //echo "
".print_r($this->items,true)."
"; - foreach ($this->ordered_items as $item_id) { - if ($this->debug > 2) { - error_log('New LP - learnpath::get_toc(): getting info for item ' . $item_id, 0); - } - // TODO: Change this link generation and use new function instead. - $toc[] = array ( - 'id' => $item_id, - 'title' => $this->items[$item_id]->get_title(), - //'link' => get_addedresource_link_in_learnpath('document', $item_id, 1), - 'status' => $this->items[$item_id]->get_status(), - 'level' => $this->items[$item_id]->get_level(), - 'type' => $this->items[$item_id]->get_type(), - 'description' => $this->items[$item_id]->get_description(), - 'path' => $this->items[$item_id]->get_path(), - - ); - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_toc() - TOC array: ' . print_r($toc, true), 0); - } - return $toc; - } - - /** - * Generate and return the table of contents for this learnpath. The JS - * table returned is used inside of scorm_api.php - * @return string A JS array vairiable construction - */ - public function get_items_details_as_js($varname='olms.lms_item_types') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_items_details_as_js()', 0); - } - $toc = $varname.' = new Array();'; - //echo "
".print_r($this->items,true)."
"; - foreach ($this->ordered_items as $item_id) { - if ($this->debug > 2) { - error_log('New LP - learnpath::get_items_details_as_js(): getting info for item ' . $item_id, 0); - } - $toc.= $varname."['i$item_id'] = '".$this->items[$item_id]->get_type()."';"; - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_items_details_as_js() - TOC array: ' . print_r($toc, true), 0); - } - return $toc; - } - - /** - * Gets the learning path type - * @param boolean Return the name? If false, return the ID. Default is false. - * @return mixed Type ID or name, depending on the parameter - */ - public function get_type($get_name = false) { - $res = false; - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_type()', 0); - } - if (!empty ($this->type)) { - if ($get_name) { - // Get it from the lp_type table in main db. - } else { - $res = $this->type; - } - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_type() - Returning ' . ($res ? $res : 'false'), 0); - } - return $res; - } - - /** - * Gets the learning path type as static method - * @param boolean Return the name? If false, return the ID. Default is false. - * @return mixed Type ID or name, depending on the parameter - */ - public function get_type_static($lp_id = 0) { - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT lp_type FROM $tbl_lp WHERE id = '" . $lp_id . "'"; - $res = Database::query($sql); - if ($res === false) { - return null; - } - if (Database :: num_rows($res) <= 0) { - return null; - } - $row = Database :: fetch_array($res); - return $row['lp_type']; - } - - /** - * Gets a flat list of item IDs ordered for display (level by level ordered by order_display) - * This method can be used as abstract and is recursive - * @param integer Learnpath ID - * @param integer Parent ID of the items to look for - * @return mixed Ordered list of item IDs or false on error - */ - public function get_flat_ordered_items_list($lp, $parent = 0) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::get_flat_ordered_items_list('.$lp.','.$parent.')', 0); } - $list = array(); - if (empty ($lp)) { - return false; - } - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql = "SELECT * FROM $tbl_lp_item WHERE lp_id = $lp AND parent_item_id = $parent ORDER BY display_order"; - $res = Database::query($sql); - while ($row = Database :: fetch_array($res)) { - $sublist = learnpath :: get_flat_ordered_items_list($lp, $row['id']); - $list[] = $row['id']; - foreach ($sublist as $item) { - $list[] = $item; - } - } - return $list; - } - - /** - * Uses the table generated by get_toc() and returns an HTML-formatted string ready to display - * @return string HTML TOC ready to display - */ - public function get_html_toc() { - - $is_allowed_to_edit = api_is_allowed_to_edit(null, true); - - $charset = api_get_system_encoding(); - $display_action_links_with_icons = false; - - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_html_toc()', 0); - } - $list = $this->get_toc(); - //echo $this->current; - //$parent = $this->items[$this->current]->get_parent(); - //if (empty($parent)) { $parent = $this->ordered_items[$this->items[$this->current]->get_previous_index()]; } - $html = '
' . Security::remove_XSS(api_convert_encoding($this->get_name(), $this->encoding, $charset)) . '
'; - // Build, display. - if ($is_allowed_to_edit) { - $gradebook = Security :: remove_XSS($_GET['gradebook']); - - //var_dump($this->get_lp_session_id()); - if ($this->get_lp_session_id() == api_get_session_id()) { - $html .= '
'; - if ($display_action_links_with_icons) { - $html .= '
'; - $html .= "" . Display :: return_icon('learnpath_build.gif', get_lang('Build')) . ' ' . get_lang('Build') . ""; - $html .= "" . Display :: return_icon('learnpath_organize.gif', get_lang('BasicOverview')) . ' ' . get_lang('BasicOverview') . ""; - $html .= '' . Display :: return_icon('learnpath_view_na.gif', get_lang('Display')) . ' ' . get_lang('Display') . '
'; - $html .= ''. get_lang('ReturnToLPList') . ''; - $html .= '
'; - } else { - $html .= '
'; - $html .= "" . get_lang('Build') . ""; - $html .= "" . get_lang('BasicOverview') . ""; - $html .= '' . get_lang('Display') . '
'; - $html .= ''. get_lang('ReturnToLPList') . ''; - $html .= '
'; - } - $html .= '
'; - } - - } - $html .= '
' . "\n"; - require_once 'resourcelinker.inc.php'; - - // Temporary variables. - $mycurrentitemid = $this->get_current_item_id(); - $color_counter = 0; - $i = 0; - foreach ($list as $item) { - if ($this->debug > 2) { - error_log('New LP - learnpath::get_html_toc(): using item ' . $item['id'], 0); - } - // TODO: Complete this. - $icon_name = array ( - 'not attempted' => '../img/notattempted.gif', - 'incomplete' => '../img/incomplete.gif', - 'failed' => '../img/failed.gif', - 'completed' => '../img/completed.gif', - 'passed' => '../img/passed.gif', - 'succeeded' => '../img/succeeded.gif', - 'browsed' => '../img/completed.gif' - ); - - $style = 'scorm_item'; - $scorm_color_background = 'scorm_item'; - $style_item = 'scorm_item'; - $current = false; - - if ($item['id'] == $this->current) { - $style = 'scorm_item_highlight'; - $scorm_color_background = 'scorm_item_highlight'; - } else - if ($color_counter % 2 == 0) { - $scorm_color_background = 'scorm_item_1'; - } else { - $scorm_color_background = 'scorm_item_2'; - } - - if ($scorm_color_background != '') { - $html .= '
'; - } - - // The anchor will let us center the TOC on the currently viewed item &^D - if ($item['type'] != 'dokeos_module' && $item['type'] != 'dokeos_chapter') { - $html .= ''; - $html .= '
'; - } else { - $html .= '
'; - } - $title = $item['title']; - if (empty ($title)) { - $title = rl_get_resource_name(api_get_course_id(), $this->get_id(), $item['id']); - } - //$title = api_htmlentities($title, ENT_QUOTES, $this->encoding); - $title = Security::remove_XSS($title); - if ($item['type'] != 'dokeos_chapter' && $item['type'] != 'dir' && $item['type'] != 'dokeos_module') { - //$html .= "".$title."" ; - $url = $this->get_link('http', $item['id']); - //$html .= ''.$title.'' ; - //$html .= ''.$title.'' ; - - // background:#aaa; - $html .= '' . stripslashes($title) . ''; - } elseif ($item['type'] == 'dokeos_module' || $item['type'] == 'dokeos_chapter') { - $html .= " " . stripslashes($title); - } elseif ($item['type'] == 'dir') { - $html .= stripslashes($title); - } - - $tbl_track_e_exercises = Database :: get_statistic_table(TABLE_STATISTIC_TRACK_E_EXERCICES); - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $user_id = api_get_user_id(); - $course_id = api_get_course_id(); - $sql = "SELECT path FROM $tbl_track_e_exercises, $tbl_lp_item - WHERE path = '" . $item['path'] . "' AND exe_user_id = '$user_id' AND exe_cours_id = '$course_id' AND path = exe_exo_id AND status <> 'incomplete'"; - $result = Database::query($sql); - $count = Database :: num_rows($result); - if ($item['type'] == 'quiz') { - if ($item['status'] == 'completed') { - $html .= " " . substr($item["; - } - } else { - if ($item['type'] != 'dokeos_chapter' && $item['type'] != 'dokeos_module' && $item['type'] != 'dir') { - $html .= " " . substr($item["; - } - } - - $html .= "
"; - - if ($scorm_color_background != '') { - $html .= '
'; - } - - $color_counter++; - } - $html .= "
\n"; - return $html; - } - - /** - * Gets the learnpath maker name - generally the editor's name - * @return string Learnpath maker name - */ - public function get_maker() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_maker()', 0); - } - if (!empty ($this->maker)) { - return $this->maker; - } else { - return ''; - } - } - - /** - * Gets the user-friendly message stored in $this->message - * @return string Message - */ - public function get_message() { - - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_message()', 0); - } - return $this->message; - } - - /** - * Gets the learnpath name/title - * @return string Learnpath name/title - */ - public function get_name() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_name()', 0); - } - if (!empty ($this->name)) { - return $this->name; - } else { - return 'N/A'; - } - } - - /** - * Gets a link to the resource from the present location, depending on item ID. - * @param string Type of link expected - * @param integer Learnpath item ID - * @return string Link to the lp_item resource - */ - public function get_link($type = 'http', $item_id = null) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_link(' . $type . ',' . $item_id . ')', 0); - } - if (empty($item_id)) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() - no item id given in learnpath::get_link(), using current: ' . $this->get_current_item_id(), 0); - } - $item_id = $this->get_current_item_id(); - } - - if (empty ($item_id)) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() - no current item id found in learnpath object', 0); - } - //still empty, this means there was no item_id given and we are not in an object context or - //the object property is empty, return empty link - $item_id = $this->first(); - return ''; - } - - $file = ''; - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_item_table = Database :: get_course_table(TABLE_LP_ITEM); - $lp_item_view_table = Database :: get_course_table(TABLE_LP_ITEM_VIEW); - $item_id = Database::escape_string($item_id); - - $sel = "SELECT l.lp_type as ltype, l.path as lpath, li.item_type as litype, li.path as lipath, li.parameters as liparams " . - "FROM $lp_table l, $lp_item_table li WHERE li.id = $item_id AND li.lp_id = l.id"; - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() - selecting item ' . $sel, 0); - } - $res = Database::query($sel); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - //var_dump($row); - $lp_type = $row['ltype']; - $lp_path = $row['lpath']; - $lp_item_type = $row['litype']; - $lp_item_path = $row['lipath']; - $lp_item_params = $row['liparams']; - if (empty ($lp_item_params) && strpos($lp_item_path, '?') !== false) { - list ($lp_item_path, $lp_item_params) = explode('?', $lp_item_path); - } - //$lp_item_params = '?'.$lp_item_params; - - //add ? if none - left commented to give freedom to scorm implementation - //if(substr($lp_item_params,0,1)!='?'){ - // $lp_item_params = '?'.$lp_item_params; - //} - $sys_course_path = api_get_path(SYS_COURSE_PATH) . api_get_course_path(); - if ($type == 'http') { - $course_path = api_get_path(WEB_COURSE_PATH) . api_get_course_path(); //web path - } else { - $course_path = $sys_course_path; //system path - } - - - // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc), then change the lp type to thread it as a normal Chamilo LP not a SCO. - if (in_array($lp_item_type, array('quiz', 'document', 'link', 'forum', 'thread', 'student_publication'))) { - $lp_type = 1; - } - // Now go through the specific cases to get the end of the path. - - // @todo Use constants instead of int values. - switch ($lp_type) { - case 1 : - if ($lp_item_type == 'dokeos_chapter') { - $file = 'lp_content.php?type=dir'; - } else { - require_once 'resourcelinker.inc.php'; - $file = rl_get_resource_link_for_learnpath(api_get_course_id(), $this->get_id(), $item_id); - - // check how much attempts of a exercise exits in lp - $lp_item_id = $this->get_current_item_id(); - $lp_view_id = $this->get_view_id(); - $prevent_reinit = $this->items[$this->current]->get_prevent_reinit(); - $list = $this->get_toc(); - $type_quiz = false; - - foreach ($list as $toc) { - if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) { - $type_quiz = true; - } - } - - if ($type_quiz) { - $lp_item_id = Database :: escape_string($lp_item_id); - $lp_view_id = Database :: escape_string($lp_view_id); - $sql = "SELECT count(*) FROM $lp_item_view_table WHERE lp_item_id='" . (int) $lp_item_id . "' AND lp_view_id ='" . (int) $lp_view_id . "' AND status='completed'"; - $result = Database::query($sql); - $row_count = Database :: fetch_row($result); - $count_item_view = (int) $row_count[0]; - $not_multiple_attempt = 0; - if ($prevent_reinit === 1 && $count_item_view > 0) { - $not_multiple_attempt = 1; - } - $file .= '¬_multiple_attempt=' . $not_multiple_attempt; - } - - $tmp_array = explode('/', $file); - $document_name = $tmp_array[count($tmp_array) - 1]; - if (strpos($document_name, '_DELETED_')) { - $file = 'blank.php?error=document_deleted'; - } - } - break; - case 2 : - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Item type: ' . $lp_item_type, 0); - } - - if ($lp_item_type != 'dir') { - // Quite complex here: - // We want to make sure 'http://' (and similar) links can - // be loaded as is (withouth the Chamilo path in front) but - // some contents use this form: resource.htm?resource=http://blablabla - // which means we have to find a protocol at the path's start, otherwise - // it should not be considered as an external URL. - - //if ($this->prerequisites_match($item_id)) { - if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Found match for protocol in ' . $lp_item_path, 0); - } - // Distant url, return as is. - $file = $lp_item_path; - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - No starting protocol in ' . $lp_item_path, 0); - } - // Prevent getting untranslatable urls. - $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); - $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); - // Prepare the path. - $file = $course_path . '/scorm/' . $lp_path . '/' . $lp_item_path; - // TODO: Fix this for urls with protocol header. - $file = str_replace('//', '/', $file); - $file = str_replace(':/', '://', $file); - if (substr($lp_path, -1) == '/') { - $lp_path = substr($lp_path, 0, -1); - } - - if (!is_file(realpath($sys_course_path . '/scorm/' . $lp_path . '/' . $lp_item_path))) { - // if file not found. - $decoded = html_entity_decode($lp_item_path); - list ($decoded) = explode('?', $decoded); - if (!is_file(realpath($sys_course_path . '/scorm/' . $lp_path . '/' . $decoded))) { - require_once 'resourcelinker.inc.php'; - $file = rl_get_resource_link_for_learnpath(api_get_course_id(), $this->get_id(), $item_id); - if (empty($file)) { - $file = 'blank.php?error=document_not_found'; - } else { - $tmp_array = explode('/', $file); - $document_name = $tmp_array[count($tmp_array) - 1]; - if (strpos($document_name, '_DELETED_')) { - $file = 'blank.php?error=document_deleted'; - } else { - $file = 'blank.php?error=document_not_found'; - } - } - } else { - $file = $course_path . '/scorm/' . $lp_path . '/' . $decoded; - } - } - } - //}else{ - //prerequisites did not match - //$file = 'blank.php'; - //} - // We want to use parameters if they were defined in the imsmanifest. - if ($file != 'blank.php') { - $file .= (strstr($file, '?') === false ? '?' : '') . $lp_item_params; - } - } else { - $file = 'lp_content.php?type=dir'; - } - break; - case 3 : - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Item type: ' . $lp_item_type, 0); - } - // Formatting AICC HACP append URL. - $aicc_append = '?aicc_sid=' . urlencode(session_id()) . '&aicc_url=' . urlencode(api_get_path(WEB_CODE_PATH) . 'newscorm/aicc_hacp.php') . '&'; - if ($lp_item_type != 'dir') { - // Quite complex here: - // We want to make sure 'http://' (and similar) links can - // be loaded as is (withouth the Chamilo path in front) but - // some contents use this form: resource.htm?resource=http://blablabla - // which means we have to find a protocol at the path's start, otherwise - // it should not be considered as an external URL. - - if (preg_match('#^[a-zA-Z]{2,5}://#', $lp_item_path) != 0) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - Found match for protocol in ' . $lp_item_path, 0); - } - // Distant url, return as is. - $file = $lp_item_path; - // Enabled and modified by Ivan Tcholakov, 16-OCT-2008. - /* - if (stristr($file,'') !== false) { - $file = str_replace('', $course_path.'/scorm/'.$lp_path.'/', $lp_item_path); - } - */ - if (stripos($file, '') !== false) { - //$file = str_replace('',$course_path.'/scorm/'.$lp_path.'/',$lp_item_path); - $web_course_path = str_replace('https://', '', str_replace('http://', '', $course_path)); - $file = str_replace('', $web_course_path . '/scorm/' . $lp_path, $lp_item_path); - } - // - $file .= $aicc_append; - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() ' . __LINE__ . ' - No starting protocol in ' . $lp_item_path, 0); - } - // Prevent getting untranslatable urls. - $lp_item_path = preg_replace('/%2F/', '/', $lp_item_path); - $lp_item_path = preg_replace('/%3A/', ':', $lp_item_path); - // Prepare the path - lp_path might be unusable because it includes the "aicc" subdir name. - $file = $course_path . '/scorm/' . $lp_path . '/' . $lp_item_path; - // TODO: Fix this for urls with protocol header. - $file = str_replace('//', '/', $file); - $file = str_replace(':/', '://', $file); - $file .= $aicc_append; - } - } else { - $file = 'lp_content.php?type=dir'; - } - break; - case 4 : - break; - default : - break; - } - } - if ($this->debug > 2) { - error_log('New LP - In learnpath::get_link() - returning "' . $file . '" from get_link', 0); - } - return $file; - } - - /** - * Gets the latest usable view or generate a new one - * @param integer Optional attempt number. If none given, takes the highest from the lp_view table - * @return integer DB lp_view id - */ - public function get_view($attempt_num = 0) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_view()', 0); - } - $search = ''; - // Use $attempt_num to enable multi-views management (disabled so far). - if ($attempt_num != 0 AND intval(strval($attempt_num)) == $attempt_num) { - $search = 'AND view_count = ' . $attempt_num; - } - // When missing $attempt_num, search for a unique lp_view record for this lp and user. - $lp_view_table = Database :: get_course_table(TABLE_LP_VIEW); - $sql = "SELECT id, view_count FROM $lp_view_table " . - "WHERE lp_id = " . $this->get_id() . " " . - "AND user_id = " . $this->get_user_id() . " " . - $search . - " ORDER BY view_count DESC"; - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $this->lp_view_id = $row['id']; - } else { - // There is no database record, create one. - $sql = "INSERT INTO $lp_view_table(lp_id,user_id,view_count)" . - "VALUES (" . $this->get_id() . "," . $this->get_user_id() . ",1)"; - $res = Database::query($sql); - $id = Database :: insert_id(); - $this->lp_view_id = $id; - } - return $this->lp_view_id; - } - - /** - * Gets the current view id - * @return integer View ID (from lp_view) - */ - public function get_view_id() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_view_id()', 0); - } - if (!empty ($this->lp_view_id)) { - return $this->lp_view_id; - } else { - return 0; - } - } - - /** - * Gets the update queue - * @return array Array containing IDs of items to be updated by JavaScript - */ - public function get_update_queue() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_update_queue()', 0); - } - return $this->update_queue; - } - - /** - * Gets the user ID - * @return integer User ID - */ - public function get_user_id() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::get_user_id()', 0); - } - if (!empty ($this->user_id)) { - return $this->user_id; - } else { - return false; - } - } - - /** - * Checks if any of the items has an audio element attached - * @return bool True or false - */ - public function has_audio() { - if ($this->debug > 1) { - error_log('New LP - In learnpath::has_audio()', 0); - } - $has = false; - foreach ($this->items as $i => $item) { - if (!empty ($this->items[$i]->audio)) { - $has = true; - break; - } - } - return $has; - } - - /** - * Logs a message into a file - * @param string Message to log - * @return boolean True on success, false on error or if msg empty - */ - public function log($msg) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::log()', 0); - } - // TODO - $this->error .= $msg . "\n"; - return true; - } - - /** - * Moves an item up and down at its level - * @param integer Item to move up and down - * @param string Direction 'up' or 'down' - * @return integer New display order, or false on error - */ - public function move_item($id, $direction) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::move_item(' . $id . ',' . $direction . ')', 0); - } - if (empty ($id) or empty ($direction)) { - return false; - } - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql_sel = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE id = " . $id; - $res_sel = Database::query($sql_sel); - // Check if elem exists. - if (Database :: num_rows($res_sel) < 1) { - return false; - } - // Gather data. - $row = Database :: fetch_array($res_sel); - $previous = $row['previous_item_id']; - $next = $row['next_item_id']; - $display = $row['display_order']; - $parent = $row['parent_item_id']; - $lp = $row['lp_id']; - // Update the item (switch with previous/next one). - switch ($direction) { - case 'up' : - if ($this->debug > 2) { - error_log('Movement up detected', 0); - } - if ($display <= 1) { /*do nothing*/ - } else { - $sql_sel2 = "SELECT * - FROM $tbl_lp_item - WHERE id = $previous"; - - if ($this->debug > 2) { - error_log('Selecting previous: ' . $sql_sel2, 0); - } - $res_sel2 = Database::query($sql_sel2); - if (Database :: num_rows($res_sel2) < 1) { - $previous_previous = 0; - } - // Gather data. - $row2 = Database :: fetch_array($res_sel2); - $previous_previous = $row2['previous_item_id']; - // Update previous_previous item (switch "next" with current). - if ($previous_previous != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $id WHERE id = $previous_previous"; - if ($this->debug > 2) { - error_log($sql_upd2, 0); - } - $res_upd2 = Database::query($sql_upd2); - } - // Update previous item (switch with current). - if ($previous != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $next, previous_item_id = $id, display_order = display_order +1 WHERE id = $previous"; - if ($this->debug > 2) { - error_log($sql_upd2, 0); - } - $res_upd2 = Database::query($sql_upd2); - } - - // Update current item (switch with previous). - if ($id != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $previous, previous_item_id = $previous_previous, display_order = display_order-1 WHERE id = $id"; - if ($this->debug > 2) { - error_log($sql_upd2, 0); - } - $res_upd2 = Database::query($sql_upd2); - } - // Update next item (new previous item). - if ($next != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous WHERE id = $next"; - if ($this->debug > 2) { - error_log($sql_upd2, 0); - } - $res_upd2 = Database::query($sql_upd2); - } - $display = $display -1; - } - break; - - case 'down' : - if ($this->debug > 2) { - error_log('Movement down detected', 0); - } - if ($next == 0) { /* Do nothing. */ - } else { - $sql_sel2 = "SELECT * FROM $tbl_lp_item WHERE id = $next"; - if ($this->debug > 2) { - error_log('Selecting next: ' . $sql_sel2, 0); - } - $res_sel2 = Database::query($sql_sel2); - if (Database :: num_rows($res_sel2) < 1) { - $next_next = 0; - } - // Gather data. - $row2 = Database :: fetch_array($res_sel2); - $next_next = $row2['next_item_id']; - // Update previous item (switch with current). - if ($previous != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET next_item_id = $next WHERE id = $previous"; - $res_upd2 = Database::query($sql_upd2); - } - // Update current item (switch with previous). - if ($id != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $next, next_item_id = $next_next, display_order = display_order+1 WHERE id = $id"; - $res_upd2 = Database::query($sql_upd2); - } - - // Update next item (new previous item). - if ($next != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $previous, next_item_id = $id, display_order = display_order-1 WHERE id = $next"; - $res_upd2 = Database::query($sql_upd2); - } - - // Update next_next item (switch "previous" with current). - if ($next_next != 0) { - $sql_upd2 = "UPDATE $tbl_lp_item SET previous_item_id = $id WHERE id = $next_next"; - $res_upd2 = Database::query($sql_upd2); - } - $display = $display +1; - } - - break; - default : - return false; - } - return $display; - } - - /** - * Move a learnpath up (display_order) - * @param integer Learnpath ID - */ - public function move_up($lp_id) { - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table ORDER BY display_order"; - $res = Database::query($sql); - if ($res === false) - return false; - $lps = array (); - $lp_order = array (); - $num = Database :: num_rows($res); - // First check the order is correct, globally (might be wrong because - // of versions < 1.8.4) - if ($num > 0) { - $i = 1; - while ($row = Database :: fetch_array($res)) { - if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. - $need_fix = true; - $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; - $res_u = Database::query($sql_u); - } - $row['display_order'] = $i; - $lps[$row['id']] = $row; - $lp_order[$i] = $row['id']; - $i++; - } - } - if ($num > 1) { // If there's only one element, no need to sort. - $order = $lps[$lp_id]['display_order']; - if ($order > 1) { // If it's the first element, no need to move up. - $sql_u1 = "UPDATE $lp_table SET display_order = $order WHERE id = " . $lp_order[$order - 1]; - $res_u1 = Database::query($sql_u1); - $sql_u2 = "UPDATE $lp_table SET display_order = " . ($order - 1) . " WHERE id = " . $lp_id; - $res_u2 = Database::query($sql_u2); - } - } - } - - /** - * Move a learnpath down (display_order) - * @param integer Learnpath ID - */ - public function move_down($lp_id) { - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table ORDER BY display_order"; - $res = Database::query($sql); - if ($res === false) - return false; - $lps = array (); - $lp_order = array (); - $num = Database :: num_rows($res); - $max = 0; - // First check the order is correct, globally (might be wrong because - // of versions < 1.8.4). - if ($num > 0) { - $i = 1; - while ($row = Database :: fetch_array($res)) { - $max = $i; - if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. - $need_fix = true; - $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; - $res_u = Database::query($sql_u); - } - $row['display_order'] = $i; - $lps[$row['id']] = $row; - $lp_order[$i] = $row['id']; - $i++; - } - } - if ($num > 1) { // If there's only one element, no need to sort. - $order = $lps[$lp_id]['display_order']; - if ($order < $max) { // If it's the first element, no need to move up. - $sql_u1 = "UPDATE $lp_table SET display_order = $order WHERE id = " . $lp_order[$order + 1]; - $res_u1 = Database::query($sql_u1); - $sql_u2 = "UPDATE $lp_table SET display_order = " . ($order + 1) . " WHERE id = " . $lp_id; - $res_u2 = Database::query($sql_u2); - } - } - } - - /** - * Updates learnpath attributes to point to the next element - * The last part is similar to set_current_item but processing the other way around - */ - public function next() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::next()', 0); - } - $this->last = $this->get_current_item_id(); - $this->items[$this->last]->save(false, $this->prerequisites_match($this->last)); - $this->autocomplete_parents($this->last); - $new_index = $this->get_next_index(); - if ($this->debug > 2) { - error_log('New LP - New index: ' . $new_index, 0); - } - $this->index = $new_index; - if ($this->debug > 2) { - error_log('New LP - Now having orderedlist[' . $new_index . '] = ' . $this->ordered_items[$new_index], 0); - } - $this->current = $this->ordered_items[$new_index]; - if ($this->debug > 2) { - error_log('New LP - new item id is ' . $this->current . '-' . $this->get_current_item_id(), 0); - } - } - - /** - * Open a resource = initialise all local variables relative to this resource. Depending on the child - * class, this might be redefined to allow several behaviours depending on the document type. - * @param integer Resource ID - * @return boolean True on success, false otherwise - */ - public function open($id) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::open()', 0); - } - // TODO: - // set the current resource attribute to this resource - // switch on element type (redefine in child class?) - // set status for this item to "opened" - // start timer - // initialise score - $this->index = 0; //or = the last item seen (see $this->last) - } - - /** - * Check that all prerequisites are fulfilled. Returns true and an empty string on succes, returns false - * and the prerequisite string on error. - * This function is based on the rules for aicc_script language as described in the SCORM 1.2 CAM documentation page 108. - * @param integer Optional item ID. If none given, uses the current open item. - * @return boolean True if prerequisites are matched, false otherwise - * @return string Empty string if true returned, prerequisites string otherwise. - */ - public function prerequisites_match($item = null) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::prerequisites_match()', 0); - } - if (empty ($item)) { - $item = $this->current; - } - if (is_object($this->items[$item])) { - $prereq_string = $this->items[$item]->get_prereq_string(); - if (empty ($prereq_string)) { - return true; - } - // Clean spaces. - $prereq_string = str_replace(' ', '', $prereq_string); - if ($this->debug > 0) { - error_log('Found prereq_string: ' . $prereq_string, 0); - } - // Now send to the parse_prereq() function that will check this component's prerequisites. - $result = $this->items[$item]->parse_prereq($prereq_string, $this->items, $this->refs_list, $this->get_user_id()); - - if ($result === false) { - $this->set_error_msg($this->items[$item]->prereq_alert); - } - } else { - $result = true; - if ($this->debug > 1) { - error_log('New LP - $this->items[' . $item . '] was not an object', 0); - } - } - - if ($this->debug > 1) { - error_log('New LP - End of prerequisites_match(). Error message is now ' . $this->error, 0); - } - return $result; - } - - /** - * Updates learnpath attributes to point to the previous element - * The last part is similar to set_current_item but processing the other way around - */ - public function previous() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::previous()', 0); - } - $this->last = $this->get_current_item_id(); - $this->items[$this->last]->save(false, $this->prerequisites_match($this->last)); - $this->autocomplete_parents($this->last); - $new_index = $this->get_previous_index(); - $this->index = $new_index; - $this->current = $this->ordered_items[$new_index]; - } - - /** - * Publishes a learnpath. This basically means show or hide the learnpath - * to normal users. - * Can be used as abstract - * @param integer Learnpath ID - * @param string New visibility - */ - public function toggle_visibility($lp_id, $set_visibility = 1) { - //if ($this->debug > 0) { error_log('New LP - In learnpath::toggle_visibility()', 0); } - $action = 'visible'; - if ($set_visibility != 1) { - $action = 'invisible'; - } - return api_item_property_update(api_get_course_info(), TOOL_LEARNPATH, $lp_id, $action, api_get_user_id()); - } - - /** - * Publishes a learnpath. This basically means show or hide the learnpath - * on the course homepage - * Can be used as abstract - * @param integer Learnpath ID - * @param string New visibility - */ - public function toggle_publish($lp_id, $set_visibility = 'v') { - //if ($this->debug > 0) { error_log('New LP - In learnpath::toggle_publish()', 0); } - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $tbl_lp where id=$lp_id"; - $result = Database::query($sql); - $row = Database :: fetch_array($result); - $name = domesticate($row['name']); - if ($set_visibility == 'i') { - $s = $name . " " . get_lang('_no_published'); - $dialogBox = $s; - $v = 0; - } - if ($set_visibility == 'v') { - $s = $name . " " . get_lang('_published'); - $dialogBox = $s; - $v = 1; - } - - $session_id = api_get_session_id(); - $session_condition = api_get_session_condition($session_id); - - $tbl_tool = Database :: get_course_table(TABLE_TOOL_LIST); - $link = 'newscorm/lp_controller.php?action=view&lp_id=' . $lp_id; - $sql = "SELECT * FROM $tbl_tool where name='$name' and image='scormbuilder.gif' and link LIKE '$link%' $session_condition"; - $result = Database::query($sql); - $num = Database :: num_rows($result); - $row2 = Database :: fetch_array($result); - //if ($this->debug > 2) { error_log('New LP - '.$sql.' - '.$num, 0); } - if (($set_visibility == 'i') && ($num > 0)) { - $sql = "DELETE FROM $tbl_tool WHERE (name='$name' and image='scormbuilder.gif' and link LIKE '$link%' $session_condition)"; - } - elseif (($set_visibility == 'v') && ($num == 0)) { - $sql = "INSERT INTO $tbl_tool (name, link, image, visibility, admin, address, added_tool, session_id) VALUES ('$name','newscorm/lp_controller.php?action=view&lp_id=$lp_id','scormbuilder.gif','$v','0','pastillegris.gif',0, $session_id)"; - } else { - // Parameter and database incompatible, do nothing. - } - $result = Database::query($sql); - //if ($this->debug > 2) { error_log('New LP - Leaving learnpath::toggle_visibility: '.$sql, 0); } - } - - /** - * Restart the whole learnpath. Return the URL of the first element. - * Make sure the results are saved with anoter method. This method should probably be - * redefined in children classes. - * To use a similar method statically, use the create_new_attempt() method - * @return string URL to load in the viewer - */ - public function restart() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::restart()', 0); - } - // TODO - // Call autosave method to save the current progress. - //$this->index = 0; - $session_id = api_get_session_id(); - $lp_view_table = Database :: get_course_table(TABLE_LP_VIEW); - $sql = "INSERT INTO $lp_view_table (lp_id, user_id, view_count, session_id) " . - "VALUES (" . $this->lp_id . "," . $this->get_user_id() . "," . ($this->attempt + 1) . ", $session_id)"; - if ($this->debug > 2) { - error_log('New LP - Inserting new lp_view for restart: ' . $sql, 0); - } - $res = Database::query($sql); - if ($view_id = Database :: insert_id($res)) { - $this->lp_view_id = $view_id; - $this->attempt = $this->attempt + 1; - } else { - $this->error = 'Could not insert into item_view table...'; - return false; - } - $this->autocomplete_parents($this->current); - foreach ($this->items as $index => $dummy) { - $this->items[$index]->restart(); - $this->items[$index]->set_lp_view($this->lp_view_id); - } - $this->first(); - return true; - } - - /** - * Saves the current item - * @return boolean - */ - public function save_current() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::save_current()', 0); - } - // TODO: Do a better check on the index pointing to the right item (it is supposed to be working - // on $ordered_items[] but not sure it's always safe to use with $items[]). - if ($this->debug > 2) { - error_log('New LP - save_current() saving item ' . $this->current, 0); - } - if ($this->debug > 2) { - error_log('' . print_r($this->items, true), 0); - } - if (is_object($this->items[$this->current])) { - //$res = $this->items[$this->current]->save(false); - $res = $this->items[$this->current]->save(false, $this->prerequisites_match($this->current)); - $this->autocomplete_parents($this->current); - $status = $this->items[$this->current]->get_status(); - $this->append_message('new_item_status: ' . $status); - $this->update_queue[$this->current] = $status; - return $res; - } - return false; - } - - /** - * Saves the given item - * @param integer Item ID. Optional (will take from $_REQUEST if null) - * @param boolean Save from url params (true) or from current attributes (false). Optional. Defaults to true - * @return boolean - */ - public function save_item($item_id = null, $from_outside = true) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::save_item(' . $item_id . ',' . $from_outside . ')', 0); - } - // TODO: Do a better check on the index pointing to the right item (it is supposed to be working - // on $ordered_items[] but not sure it's always safe to use with $items[]). - if (empty ($item_id)) { - $item_id = $this->escape_string($_REQUEST['id']); - } - if (empty ($item_id)) { - $item_id = $this->get_current_item_id(); - } - if ($this->debug > 2) { - error_log('New LP - save_current() saving item ' . $item_id, 0); - } - if (is_object($this->items[$item_id])) { - $res = $this->items[$item_id]->save($from_outside, $this->prerequisites_match($item_id)); - //$res = $this->items[$item_id]->save($from_outside); - $this->autocomplete_parents($item_id); - $status = $this->items[$item_id]->get_status(); - $this->append_message('new_item_status: ' . $status); - $this->update_queue[$item_id] = $status; - return $res; - } - return false; - } - - /** - * Saves the last item seen's ID only in case - */ - public function save_last() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::save_last()', 0); - } - $session_condition = api_get_session_condition(api_get_session_id(), true, false); - $table = Database :: get_course_table(TABLE_LP_VIEW); - if (isset ($this->current)) { - if ($this->debug > 2) { - error_log('New LP - Saving current item (' . $this->current . ') for later review', 0); - } - $sql = "UPDATE $table SET last_item = " . Database::escape_string($this->get_current_item_id()). " " . - "WHERE lp_id = " . $this->get_id() . " AND user_id = " . $this->get_user_id().' '.$session_condition; - - if ($this->debug > 2) { - error_log('New LP - Saving last item seen : ' . $sql, 0); - } - $res = Database::query($sql); - } - - // Save progress. - list($progress, $text) = $this->get_progress_bar_text('%'); - if ($progress >= 0 && $progress <= 100) { - $progress = (int) $progress; - $sql = "UPDATE $table SET progress = $progress " . - "WHERE lp_id = " . $this->get_id() . " AND " . - "user_id = " . $this->get_user_id().' '.$session_condition; - $res = Database::query($sql); // Ignore errors as some tables might not have the progress field just yet. - $this->progress_db = $progress; - } - } - - /** - * Sets the current item ID (checks if valid and authorized first) - * @param integer New item ID. If not given or not authorized, defaults to current - */ - public function set_current_item($item_id = null) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_current_item(' . $item_id . ')', 0); - } - if (empty ($item_id)) { - if ($this->debug > 2) { - error_log('New LP - No new current item given, ignore...', 0); - } - // Do nothing. - } else { - if ($this->debug > 2) { - error_log('New LP - New current item given is ' . $item_id . '...', 0); - } - if (is_numeric($item_id)) { - $item_id = $this->escape_string($item_id); - // TODO: Check in database here. - $this->last = $this->current; - $this->current = $item_id; - // TODO: Update $this->index as well. - foreach ($this->ordered_items as $index => $item) { - if ($item == $this->current) { - $this->index = $index; - break; - } - } - if ($this->debug > 2) { - error_log('New LP - set_current_item(' . $item_id . ') done. Index is now : ' . $this->index, 0); - } - } else { - error_log('New LP - set_current_item(' . $item_id . ') failed. Not a numeric value: ', 0); - } - } - } - - /** - * Sets the encoding - * @param string New encoding - */ - public function set_encoding($enc = 'ISO-8859-15') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_encoding()', 0); - } - $enc = strtoupper($enc); - $encodings = array ( - 'UTF-8', - 'ISO-8859-1', - 'ISO-8859-15', - 'cp1251', - 'cp1252', - 'KOI8-R', - 'BIG5', - 'GB2312', - 'Shift_JIS', - 'EUC-JP', - '' - ); - if (in_array($enc, $encodings)) { // TODO: Incorrect comparison, fix it. - $lp = $this->get_id(); - if ($lp != 0) { - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "UPDATE $tbl_lp SET default_encoding = '$enc' WHERE id = " . $lp; - $res = Database::query($sql); - return $res; - } - } - return false; - } - - /** - * Sets the JS lib setting in the database directly. - * This is the JavaScript library file this lp needs to load on startup - * @param string Proximity setting - * @return boolean True on update success. False otherwise. - */ - public function set_jslib($lib = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_jslib()', 0); - } - $lp = $this->get_id(); - if ($lp != 0) { - $tbl_lp = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "UPDATE $tbl_lp SET js_lib = '$lib' WHERE id = " . $lp; - $res = Database::query($sql); - return $res; - } else { - return false; - } - } - - /** - * Sets the name of the LP maker (publisher) (and save) - * @param string Optional string giving the new content_maker of this learnpath - * @return boolean True - */ - public function set_maker($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_maker()', 0); - } - if (empty ($name)) - return false; - $this->maker = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET content_maker = '" . $this->maker . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new content_maker : ' . $this->maker, 0); - } - $res = Database::query($sql); - return true; - } - - /** - * Sets the name of the current learnpath (and save) - * @param string Optional string giving the new name of this learnpath - * @return boolean True/False - */ - public function set_name($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_name()', 0); - } - if (empty ($name)) - return false; - - $this->name = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET name = '" . $this->name . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new name : ' . $this->name, 0); - } - $res = Database::query($sql); - // If the lp is visible on the homepage, change his name there. - if (Database::affected_rows()) { - $table = Database :: get_course_table(TABLE_TOOL_LIST); - $sql = 'UPDATE ' . $table . ' SET - name = "' . $this->name . '" - WHERE link = "newscorm/lp_controller.php?action=view&lp_id=' . $lp_id . '"'; - Database::query($sql); - } - return true; - } - - /** - * Set index specified prefix terms for all items in this path - * @param string Comma-separated list of terms - * @param char Xapian term prefix - * @return boolean False on error, true otherwise - */ - public function set_terms_by_prefix($terms_string, $prefix) { - if (api_get_setting('search_enabled') !== 'true') - return false; - - $terms_string = trim($terms_string); - $terms = explode(',', $terms_string); - array_walk($terms, 'trim_value'); - - $stored_terms = $this->get_common_index_terms_by_prefix($prefix); - //var_dump($stored_terms); - //var_dump($terms); - - // Don't do anything if no change, verify only at DB, not the search engine. - if ((count(array_diff($terms, $stored_terms)) == 0) && (count(array_diff($stored_terms, $terms)) == 0)) - return false; - - require_once 'xapian.php'; // TODO: Try catch every xapian use or make wrappers on API. - require_once api_get_path(LIBRARY_PATH).'search/DokeosIndexer.class.php'; - require_once api_get_path(LIBRARY_PATH).'search/xapian/XapianQuery.php'; - require_once api_get_path(LIBRARY_PATH).'search/IndexableChunk.class.php'; - - $items_table = Database :: get_course_table(TABLE_LP_ITEM); - // TODO: Make query secure agains XSS : use member attr instead of post var. - $lp_id = intval($_POST['lp_id']); - $sql = "SELECT * FROM $items_table WHERE lp_id = $lp_id"; - $result = Database::query($sql); - $di = new DokeosIndexer(); - - while ($lp_item = Database :: fetch_array($result)) { - // Get search_did. - $tbl_se_ref = Database :: get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); - $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%d LIMIT 1'; - $sql = sprintf($sql, $tbl_se_ref, $this->cc, TOOL_LEARNPATH, $lp_id, $lp_item['id']); - - $res = Database::query($sql); - if (Database::num_rows($res) > 0) { - - $se_ref = Database :: fetch_array($res); - - // Compare terms. - $doc = $di->get_document($se_ref['search_did']); - - $xapian_terms = xapian_get_doc_terms($doc, $prefix); - //var_dump($xapian_terms); - $xterms = array (); - foreach ($xapian_terms as $xapian_term) { - $xterms[] = substr($xapian_term['name'], 1); - } - - $dterms = $terms; - - $missing_terms = array_diff($dterms, $xterms); - $deprecated_terms = array_diff($xterms, $dterms); - - // Save it to search engine. - foreach ($missing_terms as $term) { - $doc->add_term($prefix . $term, 1); - } - foreach ($deprecated_terms as $term) { - $doc->remove_term($prefix . $term); - } - $di->getDb()->replace_document((int) $se_ref['search_did'], $doc); - $di->getDb()->flush(); - } - } - return true; - } - - /** - * Sets the theme of the LP (local/remote) (and save) - * @param string Optional string giving the new theme of this learnpath - * @return bool Returns true if theme name is not empty - */ - public function set_theme($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_theme()', 0); - } - $this->theme = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET theme = '" . $this->theme . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new theme : ' . $this->theme, 0); - } - //$res = Database::query($sql); - $res = Database::query($sql); - return true; - } - - /** - * Sets the image of an LP (and save) - * @param string Optional string giving the new image of this learnpath - * @return bool Returns true if theme name is not empty - */ - public function set_preview_image($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_preview_image()', 0); - } - $this->preview_image = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET preview_image = '" . $this->preview_image . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new preview image : ' . $this->preview_image, 0); - } - $res = Database::query($sql); - return true; - } - - /** - * Sets the author of a LP (and save) - * @param string Optional string giving the new author of this learnpath - * @return bool Returns true if author's name is not empty - */ - public function set_author($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_author()', 0); - } - $this->author = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET author = '" . $this->author . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new preview author : ' . $this->author, 0); - } - $res = Database::query($sql); - return true; - } - - /** - * Sets the prerequisite of a LP (and save) - * @param int integer giving the new prerequisite of this learnpath - * @return bool returns true if prerequisite is not empty - */ - public function set_prerequisite($prerequisite) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_prerequisite()', 0); - } - $this->prerequisite = intval($prerequisite); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET prerequisite = '".$this->prerequisite."' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new preview requisite : ' . $this->requisite, 0); - } - $res = Database::query($sql); - return true; - } - - /** - * Sets the location/proximity of the LP (local/remote) (and save) - * @param string Optional string giving the new location of this learnpath - * @return boolean True on success / False on error - */ - public function set_proximity($name = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_proximity()', 0); - } - if (empty ($name)) - return false; - - $this->proximity = $this->escape_string($name); - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $lp_id = $this->get_id(); - $sql = "UPDATE $lp_table SET content_local = '" . $this->proximity . "' WHERE id = '$lp_id'"; - if ($this->debug > 2) { - error_log('New LP - lp updated with new proximity : ' . $this->proximity, 0); - } - //$res = Database::query($sql); - $res = Database::query($sql); - return true; - } - - /** - * Sets the previous item ID to a given ID. Generally, this should be set to the previous 'current' item - * @param integer DB ID of the item - */ - public function set_previous_item($id) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_previous_item()', 0); - } - $this->last = $id; - } - - /** - * Sets the object's error message - * @param string Error message. If empty, reinits the error string - * @return void - */ - public function set_error_msg($error = '') { - if ($this->debug > 0) { - error_log('New LP - In learnpath::set_error_msg()', 0); - } - if (empty ($error)) { - $this->error = ''; - } else { - $this->error .= $error; - } - } - - /** - * Launches the current item if not 'sco' (starts timer and make sure there is a record ready in the DB) - * @param boolean Whether to allow a new attempt or not - * @return boolean True - */ - public function start_current_item($allow_new_attempt = false) { - if ($this->debug > 0) { - error_log('New LP - In learnpath::start_current_item()', 0); - } - if ($this->current != 0 AND is_object($this->items[$this->current])) { - $type = $this->get_type(); - $item_type = $this->items[$this->current]->get_type(); - if (($type == 2 && $item_type != 'sco') OR ($type == 3 && $item_type != 'au') OR ($type == 1 && $item_type != TOOL_QUIZ && $item_type != TOOL_HOTPOTATOES)) { - $this->items[$this->current]->open($allow_new_attempt); - - $this->autocomplete_parents($this->current); - $prereq_check = $this->prerequisites_match($this->current); - $this->items[$this->current]->save(false, $prereq_check); - //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); - } else { - // If sco, then it is supposed to have been updated by some other call. - } - if ($item_type == 'sco') { - $this->items[$this->current]->restart(); - } - } - if ($this->debug > 0) { - error_log('New LP - End of learnpath::start_current_item()', 0); - } - return true; - } - - /** - * Stops the processing and counters for the old item (as held in $this->last) - * @return boolean True/False - */ - public function stop_previous_item() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::stop_previous_item()', 0); - } - - if ($this->last != 0 && $this->last != $this->current && is_object($this->items[$this->last])) { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' is object', 0); - } - switch ($this->get_type()) { - case '3' : - if ($this->items[$this->last]->get_type() != 'au') { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 3 is <> au', 0); - } - $this->items[$this->last]->close(); - //$this->autocomplete_parents($this->last); - //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - Item is an AU, saving is managed by AICC signals', 0); - } - } - case '2' : - if ($this->items[$this->last]->get_type() != 'sco') { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 2 is <> sco', 0); - } - $this->items[$this->last]->close(); - //$this->autocomplete_parents($this->last); - //$this->update_queue[$this->last] = $this->items[$this->last]->get_status(); - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - Item is a SCO, saving is managed by SCO signals', 0); - } - } - break; - case '1' : - default : - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - ' . $this->last . ' in lp_type 1 is asset', 0); - } - $this->items[$this->last]->close(); - break; - } - } else { - if ($this->debug > 2) { - error_log('New LP - In learnpath::stop_previous_item() - No previous element found, ignoring...', 0); - } - return false; - } - return true; - } - - /** - * Updates the default view mode from fullscreen to embedded and inversely - * @return string The current default view mode ('fullscreen' or 'embedded') - */ - public function update_default_view_mode() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::update_default_view_mode()', 0); - } - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $view_mode = $row['default_view_mod']; - if ($view_mode == 'fullscreen') { - $view_mode = 'embedded'; - } elseif ($view_mode == 'embedded') { - $view_mode = 'fullscreen'; - } - $sql = "UPDATE $lp_table SET default_view_mod = '$view_mode' WHERE id = " . $this->get_id(); - $res = Database::query($sql); - $this->mode = $view_mode; - return $view_mode; - } else { - if ($this->debug > 2) { - error_log('New LP - Problem in update_default_view() - could not find LP ' . $this->get_id() . ' in DB', 0); - } - } - return -1; - } - - /** - * Updates the default behaviour about auto-commiting SCORM updates - * @return boolean True if auto-commit has been set to 'on', false otherwise - */ - public function update_default_scorm_commit() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::update_default_scorm_commit()', 0); - } - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $force = $row['force_commit']; - if ($force == 1) { - $force = 0; - $force_return = false; - } elseif ($force == 0) { - $force = 1; - $force_return = true; - } - $sql = "UPDATE $lp_table SET force_commit = $force WHERE id = " . $this->get_id(); - $res = Database::query($sql); - $this->force_commit = $force_return; - return $force_return; - } else { - if ($this->debug > 2) { - error_log('New LP - Problem in update_default_scorm_commit() - could not find LP ' . $this->get_id() . ' in DB', 0); - } - } - return -1; - } - - /** - * Updates the order of learning paths (goes through all of them by order and fills the gaps) - * @return bool True on success, false on failure - */ - public function update_display_order() { - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table ORDER BY display_order"; - $res = Database::query($sql); - if ($res === false) - return false; - $lps = array (); - $lp_order = array (); - $num = Database :: num_rows($res); - // First check the order is correct, globally (might be wrong because - // of versions < 1.8.4). - if ($num > 0) { - $i = 1; - while ($row = Database :: fetch_array($res)) { - if ($row['display_order'] != $i) { // If we find a gap in the order, we need to fix it. - $need_fix = true; - $sql_u = "UPDATE $lp_table SET display_order = $i WHERE id = " . $row['id']; - $res_u = Database::query($sql_u); - } - $i++; - } - } - return true; - } - - /** - * Updates the "prevent_reinit" value that enables control on reinitialising items on second view - * @return boolean True if prevent_reinit has been set to 'on', false otherwise (or 1 or 0 in this case) - */ - public function update_reinit() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::update_reinit()', 0); - } - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $force = $row['prevent_reinit']; - if ($force == 1) { - $force = 0; - } elseif ($force == 0) { - $force = 1; - } - $sql = "UPDATE $lp_table SET prevent_reinit = $force WHERE id = " . $this->get_id(); - $res = Database::query($sql); - $this->prevent_reinit = $force; - return $force; - } else { - if ($this->debug > 2) { - error_log('New LP - Problem in update_reinit() - could not find LP ' . $this->get_id() . ' in DB', 0); - } - } - return -1; - } - - /** - * Updates the "scorm_debug" value that shows or hide the debug window - * @return boolean True if scorm_debug has been set to 'on', false otherwise (or 1 or 0 in this case) - */ - public function update_scorm_debug() { - if ($this->debug > 0) { - error_log('New LP - In learnpath::update_scorm_debug()', 0); - } - $lp_table = Database :: get_course_table(TABLE_LP_MAIN); - $sql = "SELECT * FROM $lp_table WHERE id = " . $this->get_id(); - $res = Database::query($sql); - if (Database :: num_rows($res) > 0) { - $row = Database :: fetch_array($res); - $force = $row['debug']; - if ($force == 1) { - $force = 0; - } elseif ($force == 0) { - $force = 1; - } - $sql = "UPDATE $lp_table SET debug = $force WHERE id = " . $this->get_id(); - $res = Database::query($sql); - $this->scorm_debug = $force; - return $force; - } else { - if ($this->debug > 2) { - error_log('New LP - Problem in update_scorm_debug() - could not find LP ' . $this->get_id() . ' in DB', 0); - } - } - return -1; - } - - /** - * Function that makes a call to the function sort_tree_array and create_tree_array - * @author Kevin Van Den Haute - * @param array - */ - public function tree_array($array) { - if ($this->debug > 1) { - error_log('New LP - In learnpath::tree_array()', 0); - } - $array = $this->sort_tree_array($array); - $this->create_tree_array($array); - } - - /** - * Creates an array with the elements of the learning path tree in it - * - * @author Kevin Van Den Haute - * @param array $array - * @param int $parent - * @param int $depth - * @param array $tmp - */ - public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = array ()) { - if ($this->debug > 1) { - error_log('New LP - In learnpath::create_tree_array())', 0); - } - if (is_array($array)) { - for ($i = 0; $i < count($array); $i++) { - if ($array[$i]['parent_item_id'] == $parent) { - if (!in_array($array[$i]['parent_item_id'], $tmp)) { - $tmp[] = $array[$i]['parent_item_id']; - $depth++; - } - $preq = (empty($array[$i]['prerequisite']) ? '' : $array[$i]['prerequisite']); - $this->arrMenu[] = array ( - 'id' => $array[$i]['id'], - 'item_type' => $array[$i]['item_type'], - 'title' => $array[$i]['title'], - 'path' => $array[$i]['path'], - 'description' => $array[$i]['description'], - 'parent_item_id' => $array[$i]['parent_item_id'], - 'previous_item_id' => $array[$i]['previous_item_id'], - 'next_item_id' => $array[$i]['next_item_id'], - 'min_score' => $array[$i]['min_score'], - 'max_score' => $array[$i]['max_score'], - 'mastery_score' => $array[$i]['mastery_score'], - 'display_order' => $array[$i]['display_order'], - 'prerequisite' => $preq, - 'depth' => $depth, - 'audio' => $array[$i]['audio'] - ); - - $this->create_tree_array($array, $array[$i]['id'], $depth, $tmp); - } - } - } - } - - /** - * Sorts a multi dimensional array by parent id and display order - * @author Kevin Van Den Haute - * - * @param array $array (array with al the learning path items in it) - * - * @return array - */ - public function sort_tree_array($array) { - foreach ($array as $key => $row) { - $parent[$key] = $row['parent_item_id']; - $position[$key] = $row['display_order']; - } - - if (count($array) > 0) - array_multisort($parent, SORT_ASC, $position, SORT_ASC, $array); - - return $array; - } - - /** - * Function that creates a table structure with a learning path his modules, chapters and documents. - * Also the actions for the modules, chapters and documents are in this table. - * @author Kevin Van Den Haute - * @param int $lp_id - * @return string - */ - public function overview() { - $is_allowed_to_edit = api_is_allowed_to_edit(null,true); - - $platform_charset = api_get_system_encoding(); - if ($this->debug > 0) { - error_log('New LP - In learnpath::overview()', 0); - } - global $charset, $_course; - $_SESSION['gradebook'] = Security :: remove_XSS($_GET['gradebook']); - $return = ''; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - $sql = "SELECT * FROM " . $tbl_lp_item . " - WHERE lp_id = " . $this->lp_id; - - $result = Database::query($sql); - $arrLP = array (); - while ($row = Database :: fetch_array($result)) { - $row['title'] = Security :: remove_XSS(api_convert_encoding($row['title'], $platform_charset, $this->encoding)); - $row['description'] = Security :: remove_XSS(api_convert_encoding($row['description'], $platform_charset, $this->encoding)); - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'], - 'display_order' => $row['display_order'], - 'audio' => $row['audio'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($is_allowed_to_edit) { - $token = Security::get_token(); - - $gradebook = Security :: remove_XSS($_GET['gradebook']); - $return .= ''; - } - - // we need to start a form when we want to update all the mp3 files - if ($_GET['updateaudio'] == 'true' AND count($arrLP) <> 0) { - $return .= ''; - $return .= Display :: display_warning_message(get_lang('LeaveEmptyToKeepCurrentFile')); - } - $return .= '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - //$return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - - for ($i = 0; $i < count($arrLP); $i++) { - $title = $arrLP[$i]['title']; - if ($arrLP[$i]['description'] == '') - $arrLP[$i]['description'] = ' '; - - if (($i % 2) == 0) { - $oddclass = 'row_odd'; - } else { - $oddclass = 'row_even'; - } - - $return .= "\t" . '' . "\n"; - - $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']); - if (file_exists('../img/lp_' . $icon_name . '.png')) { - $return .= "\t\t" . '' . "\n"; - } else { - if (file_exists('../img/lp_' . $icon_name . '.gif')) { - $return .= "\t\t" . '' . "\n"; - } else { - //$return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - } - //$return .= "\t\t" . '' . "\n"; - - // The audio column. - $return .= "\t\t" . ''; - - if ($is_allowed_to_edit) { - $return .= ''; - - $return .= ''; - } - - $return .= ''; - } - - if (count($arrLP) == 0) { - $return .= "\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - $return .= '
' . get_lang('Title') . ''.get_lang('Description').'' . get_lang('Audio') . '' . get_lang('Move') . '' . get_lang('Actions') . '
' . $title . '' . $title . '' . Display::display_icon('folder_document.gif','',array('style'=>'margin-right:3px;')) . $title . '' . $title . '' . stripslashes($arrLP[$i]['description']) . ''; - if (!$_GET['updateaudio'] OR $_GET['updateaudio'] <> 'true') { - if (!empty ($arrLP[$i]['audio'])) { - $return .= 'Get the Flash Player to see this player.'; - $return .= ''; - $return .= ''; - } else { - $return .= ' - '; - } - } else { - if ($arrLP[$i]['item_type'] != 'dokeos_chapter' && $arrLP[$i]['item_type'] != 'dokeos_module' && $arrLP[$i]['item_type'] != 'dir') { - - $return .= ' '; - if (!empty ($arrLP[$i]['audio'])) { - $return .= '
'.Security::remove_XSS($arrLP[$i]['audio']).'' . get_lang('RemoveAudio'); - } - } - } - $return .= '
'; - - if ($arrLP[$i]['previous_item_id'] != 0) { - - $return .= ''; - $return .= ''; - $return .= ''; - - } else - $return .= ''; - - if ($arrLP[$i]['next_item_id'] != 0) { - $return .= ''; - $return .= ''; - - $return .= ''; - } else - $return .= ''; - - $return .= ''; - - if ($arrLP[$i]['item_type'] != 'dokeos_chapter' && $arrLP[$i]['item_type'] != 'dokeos_module') { - $return .= "\t\t\t" . ''; - $return .= ''; - $return .= '' . "\n"; - } else { - $return .= "\t\t\t" . ''; - $return .= ''; - $return .= '' . "\n"; - } - - $return .= "\t\t\t" . ''; - $return .= ''; - $return .= '' . "\n"; - - $return .= '
' . get_lang('NoItemsInLp') . '
' . "\n"; - // We need to close the form when we are updating the mp3 files. - - if ($_GET['updateaudio'] == 'true') { - $return .= '
'; // TODO: What kind of language variable is this? - } - - // We need to close the form when we are updating the mp3 files. - if ($_GET['updateaudio'] == 'true' && count($arrLP) != 0) { - $return .= ''; - } - - return $return; - } - - /** - * This function builds the action menu - * @return void - */ - public function build_action_menu() { - $gradebook = Security :: remove_XSS($_GET['gradebook']); - echo ''; - } - - /** - * This functions builds the LP tree based on data from the database. - * @return string - * @uses dtree.js :: necessary javascript for building this tree - */ - public function build_tree() { - $platform_charset = api_get_system_encoding(); - $return = "\n"; - - return $return; - } - - /** - * Create a new document //still needs some finetuning - * @param array $_course - * @return string - */ - public function create_document($_course) { - global $charset; - $dir = isset ($_GET['dir']) ? $_GET['dir'] : $_POST['dir']; // Please, do not modify this dirname formatting. - if (strstr($dir, '..')) - $dir = '/'; - if ($dir[0] == '.') - $dir = substr($dir, 1); - if ($dir[0] != '/') - $dir = '/' . $dir; - if ($dir[strlen($dir) - 1] != '/') - $dir .= '/'; - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $dir; - if (!is_dir($filepath)) { - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; - $dir = '/'; - } - // stripslashes() before calling replace_dangerous_char() because $_POST['title'] - // is already escaped twice when it gets here. - $title = replace_dangerous_char(stripslashes($_POST['title'])); - $title = disable_dangerous_file($title); - - $filename = $title; - $content = $_POST['content_lp']; - - $tmp_filename = $filename; - - $i = 0; - while (file_exists($filepath . $tmp_filename . '.html')) - $tmp_filename = $filename . '_' . ++ $i; - $filename = $tmp_filename . '.html'; - $content = stripslashes(text_filter($content)); - $content = str_replace(api_get_path(WEB_COURSE_PATH), api_get_path(REL_PATH) . 'courses/', $content); - // Change the path of mp3 to absolute. - // The first regexp deals with ../../../ urls. - $content = preg_replace("|(flashvars=\"file=)(\.+/)+|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/', $content); - // The second regexp deals with audio/ urls. - $content = preg_replace("|(flashvars=\"file=)([^/]+)/|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/$2/', $content); - // For flv player: To prevent edition problem with firefox, we have to use a strange tip (don't blame me please). - $content = str_replace('', '', $content); - - if (!file_exists($filepath . $filename)) { - if ($fp = @ fopen($filepath . $filename, 'w')) { - fputs($fp, $content); - fclose($fp); - - $file_size = filesize($filepath . $filename); - $save_file_path = $dir . $filename; - - $document_id = add_document($_course, $save_file_path, 'file', $file_size, $filename . '.html'); - - if ($document_id) { - api_item_property_update($_course, TOOL_DOCUMENT, $document_id, 'DocumentAdded', api_get_user_id(), null, null, null, null, api_get_session_id()); - - // Update parent folders. - //item_property_update_on_folder($_course, $_GET['dir'], $_user['user_id']); - - $new_comment = (isset($_POST['comment'])) ? trim($_POST['comment']) : ''; - $new_title = (isset($_POST['title'])) ? trim($_POST['title']) : ''; - - if ($new_comment || $new_title) { - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $ct = ''; - - if ($new_comment) - $ct .= ", comment='" . $new_comment . "'"; - - if ($new_title) - $ct .= ", title='" . Database :: escape_string(htmlspecialchars($new_title, ENT_QUOTES, $charset)) . ".html '"; - - $sql_update = " - UPDATE " . $tbl_doc . " - SET " . substr($ct, 1) . " - WHERE id = " . $document_id; - Database::query($sql_update); - } - } - - return $document_id; - } - } - } - - /** - * Edit a document based on $_POST and $_GET parameters 'dir' and 'path' - * @param array $_course array - * @return void - */ - public function edit_document($_course) { - global $_configuration; - $dir = isset ($_GET['dir']) ? $_GET['dir'] : $_POST['dir']; // Please, do not modify this dirname formatting. - - if (strstr($dir, '..')) - $dir = '/'; - - if ($dir[0] == '.') - $dir = substr($dir, 1); - - if ($dir[0] != '/') - $dir = '/' . $dir; - - if ($dir[strlen($dir) - 1] != '/') - $dir .= '/'; - - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $dir; - - if (!is_dir($filepath)) { - $filepath = api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document/'; - $dir = '/'; - } - - $table_doc = Database :: get_course_table(TABLE_DOCUMENT); - if (isset($_POST['path']) && !empty($_POST['path'])) { - $sql = "SELECT path - FROM " . $table_doc . " - WHERE id = " . Database::escape_string($_POST['path']); - $res = Database::query($sql); - $row = Database :: fetch_array($res); - $content = stripslashes($_POST['content_lp']); - $file = $filepath . $row['path']; - - if ($fp = @ fopen($file, 'w')) { - $content = text_filter($content); - $content = str_replace(api_get_path(WEB_COURSE_PATH), $_configuration['url_append'] . '/courses/', $content); - - // Change the path of mp3 to absolute. - // The first regexp deals with ../../../ urls. - $content = preg_replace("|(flashvars=\"file=)(\.+/)+|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/', $content); - // The second regexp deals with audio/ urls. - $content = preg_replace("|(flashvars=\"file=)([^/]+)/|", "$1" . api_get_path(REL_COURSE_PATH) . $_course['path'] . '/document/$2/', $content); - - fputs($fp, $content); - fclose($fp); - } - } - } - - /** - * Displays the selected item, with a panel for manipulating the item - * @param int $item_id - * @param string $msg - * @return string - */ - public function display_item($item_id, $iframe = true, $msg = '') { - global $_course; // It will disappear. - $return = ''; - if (is_numeric($item_id)) { - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $sql = "SELECT lp.* FROM " . $tbl_lp_item . " as lp - WHERE lp.id = " . Database :: escape_string($item_id); - $result = Database::query($sql); - while ($row = Database :: fetch_array($result)) { - $_SESSION['parent_item_id'] = ($row['item_type'] == 'dokeos_chapter' || $row['item_type'] == 'dokeos_module' || $row['item_type'] == 'dir') ? $item_id : 0; - - // Prevents wrong parent selection for document, see Bug#1251. - if ($row['item_type'] != 'dokeos_chapter' || $row['item_type'] != 'dokeos_module') { - $_SESSION['parent_item_id'] = $row['parent_item_id']; - } - - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= '
'; - if ($msg != '') - $return .= $msg; - $row['title'] = Security :: remove_XSS(api_convert_encoding($row['title'], api_get_system_encoding(), $this->encoding)); - //$row['title'] = Security::remove_XSS(api_convert_encoding($row['title'], $this->encoding, api_get_system_encoding())); - $return .= '

' . $row['title'] . '

'; - //$return .= '

' . ((trim($row['description']) == '') ? 'no description' : stripslashes($row['description'])) . '

'; - //$return .= '
'; - if ($row['item_type'] == TOOL_DOCUMENT) { - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $sql_doc = "SELECT path FROM " . $tbl_doc . " WHERE id = " . Database :: escape_string($row['path']); - $result = Database::query($sql_doc); - $path_file = Database :: result($result, 0, 0); - $path_parts = pathinfo($path_file); - // TODO: Correct the following naive comparisons, also, htm extension is missing. - if (in_array($path_parts['extension'], array ( - 'html', - 'txt', - 'png', - 'jpg', - 'JPG', - 'jpeg', - 'JPEG', - 'gif', - 'swf' - ))) { - $return .= $this->display_document($row['path'], true, true); - } - } - $return .= '
'; - } - } - return $return; - } - - /** - * Shows the needed forms for editing a specific item - * @param int $item_id - * @return string - */ - public function display_edit_item($item_id) { - global $_course; // It will disappear. - $return = ''; - if (is_numeric($item_id)) { - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql = "SELECT * - FROM " . $tbl_lp_item . " - WHERE id = " . Database :: escape_string($item_id); - $res = Database::query($sql); - $row = Database :: fetch_array($res); - switch ($row['item_type']) { - case 'dokeos_chapter' : - case 'dir' : - case 'asset' : - case 'sco' : - if (isset ($_GET['view']) && $_GET['view'] == 'build') { - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_item_form($row['item_type'], get_lang('EditCurrentChapter') . ' :', 'edit', $item_id, $row); - } else { - $return .= $this->display_item_small_form($row['item_type'], get_lang('EditCurrentChapter') . ' :', $row); - } - break; - case TOOL_DOCUMENT : - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $sql_step = " SELECT lp.*, doc.path as dir - FROM " . $tbl_lp_item . " as lp - LEFT JOIN " . $tbl_doc . " as doc ON doc.id = lp.path - WHERE lp.id = " . Database :: escape_string($item_id); - $res_step = Database::query($sql_step); - $row_step = Database :: fetch_array($res_step); - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_document_form('edit', $item_id, $row_step); - break; - case TOOL_LINK : - $link_id = (string) $row['path']; - if (ctype_digit($link_id)) { - $tbl_link = Database :: get_course_table(TABLE_LINK); - $sql_select = 'SELECT url FROM ' . $tbl_link . ' WHERE id = ' . Database :: escape_string($link_id); - $res_link = Database::query($sql_select); - $row_link = Database :: fetch_array($res_link); - if (is_array($row_link)) { - $row['url'] = $row_link['url']; - } - } - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_link_form('edit', $item_id, $row); - break; - case 'dokeos_module' : - if (isset ($_GET['view']) && $_GET['view'] == 'build') { - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_item_form($row['item_type'], get_lang('EditCurrentModule') . ' :', 'edit', $item_id, $row); - } else { - $return .= $this->display_item_small_form($row['item_type'], get_lang('EditCurrentModule') . ' :', $row); - } - break; - case TOOL_QUIZ : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_quiz_form('edit', $item_id, $row); - break; - case TOOL_HOTPOTATOES : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_hotpotatoes_form('edit', $item_id, $row); - break; - case TOOL_STUDENTPUBLICATION : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_student_publication_form('edit', $item_id, $row); - break; - case TOOL_FORUM : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_forum_form('edit', $item_id, $row); - break; - case TOOL_THREAD : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_thread_form('edit', $item_id, $row); - break; - } - } - - return $return; - } - - /** - * Function that displays a list with al the resources that could be added to the learning path - * @return string - */ - public function display_resources() { - global $_course; // TODO: Don't use globals. - - $return .= '
' . get_lang('CreateNewStep') . '
'; - $return .= ''; // TODO: A hardcoded Spanish text is here. - $return .= '
' . get_lang('UseAnExistingResource') . '
'; - $return .= '
'; - - /* Get all the docs. */ - $return .= $this->get_documents(); - - /* Get all the exercises. */ - $return .= $this->get_exercises(); - - /* Get all the links. */ - $return .= $this->get_links(); - - /* Get al the student publications. */ - $return .= $this->get_student_publications(); - - /* Get al the forums. */ - $return .= $this->get_forums(); - - $return .= '
'; - - return $return; - } - - /** - * Returns the extension of a document - * @param string filename - * @return string Extension (part after the last dot) - */ - public function get_extension($filename) { - $explode = explode('.', $filename); - return $explode[count($explode) - 1]; - } - - /** - * Displays a document by id - * - * @param unknown_type $id - * @return unknown - */ - public function display_document($id, $show_title = false, $iframe = true, $edit_link = false) { - global $_course; // It is temporary. - $return = ''; - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - $sql_doc = "SELECT * FROM " . $tbl_doc . " - WHERE id = " . $id; - $res_doc = Database::query($sql_doc); - $row_doc = Database :: fetch_array($res_doc); - - //if ($show_title) - //$return .= '

' . $row_doc['title'] . ($edit_link ? ' [ Edit this document ]' : '') . '

'; - - // TODO: Add a path filter. - if ($iframe) { - $return .= ''; - } else { - $return .= file_get_contents(api_get_path(SYS_COURSE_PATH) . $_course['path'] . '/document' . $row_doc['path']); - } - - return $return; - } - - /** - * Return HTML form to add/edit a quiz - * @param string Action (add/edit) - * @param integer Item ID if already exists - * @param mixed Extra information (quiz ID if integer) - * @return string HTML form - */ - public function display_quiz_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_quiz = Database :: get_course_table(TABLE_QUIZ_TEST); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - $item_description = stripslashes($extra_info['description']); - } - elseif (is_numeric($extra_info)) { - $sql_quiz = " - SELECT - title, - description - FROM " . $tbl_quiz . " - WHERE id = " . $extra_info; - - $result = Database::query($sql_quiz); - $row = Database :: fetch_array($result); - $item_title = $row['title']; - $item_description = $row['description']; - } else { - $item_title = ''; - $item_description = ''; - } - - $return = '
-
'; - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - $arrLP = array (); - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'], - 'max_time_allowed' => $row['max_time_allowed'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($action == 'add') - $return .= get_lang('CreateTheExercise') . ' :' . "\n"; - elseif ($action == 'move') $return .= get_lang('MoveTheCurrentExercise') . ' :' . "\n"; - else - $return .= get_lang('EditCurrentExecice') . ' :' . "\n"; - - if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { - $return .= Display :: return_warning_message('

' . get_lang('Warning') . ' !

' . get_lang('WarningEditingDocument')); - } - $return .= '
-
'; - $return .= '
'; - - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - - $return .= "\t\t" . '' . "\n"; - - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . ''; - $return .= ''; - $return .= ''; - - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - if ($action != 'move') { - $id_prerequisite = 0; - if (is_array($arrLP)) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - $arrHide = array (); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - } - } - /*// Commented the prerequisites, only visible in edit (exercise). - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '"; - */ - $return .= "\t\t" . '' . "\n"; - /*$return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . ''; - - // Remove temporarily the test description. - //$return .= "\t\t\t" . '' . "\n"; - //$return .= "\t\t\t" . '' . "\n"; - - $return .= "\t\t" . '' . "\n"; */ - } - - $return .= "\t\t" . '' . "\n"; - if ($action == 'add') { - $return .= ''; - } else { - $return .= ''; - } - - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '
' . "\n"; - - $return .= "\t\t\t\t" . ''; - $return .= '
' . "\n"; - - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
  
' . "\n"; - - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } - elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - - $return .= '
' . "\n"; - $return .= '
' . "\n"; - return $return; - } - - /** - * Addition of Hotpotatoes tests - * @param string Action - * @param integer Internal ID of the item - * @param mixed Extra information - can be an array with title and description indexes - * @return string HTML structure to display the hotpotatoes addition formular - */ - public function display_hotpotatoes_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - $uploadPath = DIR_HOTPOTATOES; //defined in main_api - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - $item_description = stripslashes($extra_info['description']); - } - elseif (is_numeric($extra_info)) { - $TBL_DOCUMENT = Database :: get_course_table(TABLE_DOCUMENT); - - $sql_hot = "SELECT * FROM " . $TBL_DOCUMENT . " - WHERE path LIKE '" . $uploadPath . "/%/%htm%' - AND id = " . (int) $extra_info . " ORDER BY id ASC"; - - $res_hot = Database::query($sql_hot); - - $row = Database :: fetch_array($res_hot); - - $item_title = $row['title']; - $item_description = $row['description']; - - if (!empty ($row['comment'])) { - $item_title = $row['comment']; - } - } else { - $item_title = ''; - $item_description = ''; - } - - $item_title = api_convert_encoding($item_title, $charset, $this->encoding); - $item_description = api_convert_encoding($item_description, $charset, $this->encoding); - - $return = '
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'], - 'max_time_allowed' => $row['max_time_allowed'] - ); - } - - $this->tree_array($arrLP); - - $arrLP = $this->arrMenu; - - unset ($this->arrMenu); - - if ($action == 'add') - $return .= '

' . get_lang('CreateTheExercise') . ' :

' . "\n"; - elseif ($action == 'move') $return .= '

' . get_lang('MoveTheCurrentExercise') . ' :

' . "\n"; - else - $return .= '

' . get_lang('EditCurrentExecice') . ' :

' . "\n"; - if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { - $return .= Display :: return_warning_message('

' . get_lang('Warning') . ' !

' . get_lang('WarningEditingDocument')); - } - - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $id_prerequisite = 0; - if (is_array($arrLP) && count($arrLP) > 0) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - - $arrHide = array (); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - - } - } - } - - $return .= "\t\t" . '' . "\n"; - - //$return .= "\t\t\t" . '' . "\n"; - //$return .= "\t\t\t" . '"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
 
' . "\n"; - - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } - elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - - $return .= '
' . "\n"; - - $return .= '
' . "\n"; - return $return; - } - - /** - * Return the form to display the forum edit/add option - * @param string Action (add/edit) - * @param integer ID of the lp_item if already exists - * @param mixed Forum ID or title - * @return string HTML form - */ - public function display_forum_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_forum = Database :: get_course_table(TABLE_FORUM); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - } - elseif (is_numeric($extra_info)) { - $sql_forum = " - SELECT - forum_title as title, forum_comment as comment - FROM " . $tbl_forum . " - WHERE forum_id = " . $extra_info; - - $result = Database::query($sql_forum); - $row = Database :: fetch_array($result); - - $item_title = $row['title']; - $item_description = $row['comment']; - } else { - $item_title = ''; - $item_description = ''; - } - - $item_title = api_convert_encoding($item_title, $charset, $this->encoding); - $item_description = api_convert_encoding($item_description, $charset, $this->encoding); - - $return = '
-
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($action == 'add') - $return .= get_lang('CreateTheForum') . ' :' . "\n"; - elseif ($action == 'move') $return .= get_lang('MoveTheCurrentForum') . ' :' . "\n"; - else - $return .= get_lang('EditCurrentForum') . ' :' . "\n"; - - $return .= '
-
'; - $return .= '
'; - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $id_prerequisite = 0; - if (is_array($arrLP)) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - - $arrHide = array (); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - } - } - $return .= "\t\t" . '' . "\n"; - } - $return .= "\t\t" . '' . "\n"; - - if ($action == 'add') { - $return .= ''; - } else { - $return .= ''; - } - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
  
' . "\n"; - - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } - elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= '
' . "\n"; - $return .= '
' . "\n"; - return $return; - } - - /** - * Return HTML form to add/edit forum threads - * @param string Action (add/edit) - * @param integer Item ID if already exists in learning path - * @param mixed Extra information (thread ID if integer) - * @return string HTML form - */ - public function display_thread_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - echo ' - '; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_forum = Database :: get_course_table(TABLE_FORUM_THREAD); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - } - elseif (is_numeric($extra_info)) { - $sql_forum = " - SELECT - thread_title as title - FROM " . $tbl_forum . " - WHERE thread_id = " . $extra_info; - - $result = Database::query($sql_forum); - $row = Database :: fetch_array($result); - - $item_title = $row['title']; - $item_description = ''; - } else { - $item_title = ''; - $item_description = ''; - } - $item_title = api_convert_encoding($item_title, $charset, $this->encoding); - $item_description = api_convert_encoding($item_description, $charset, $this->encoding); - - $return = '
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'] - ); - } - - $this->tree_array($arrLP); - - $arrLP = $this->arrMenu; - - unset ($this->arrMenu); - - if ($action == 'add') - $return .= '

' . get_lang('CreateTheForum') . ' :

' . "\n"; - elseif ($action == 'move') $return .= '

' . get_lang('MoveTheCurrentForum') . ' :

' . "\n"; - else - $return .= '

' . get_lang('EditCurrentForum') . ' :

' . "\n"; - - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - - $id_prerequisite = 0; - if ($arrLP != null) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - - $arrHide = array(); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - - } - } - - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '"; - $return .= "\t\t" . '' . "\n"; - - } - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
-
' . "\n"; - - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } - elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= '
' . "\n"; - $return .= '
' . "\n"; - return $return; - } - - /** - * Return the HTML form to display an item (generally a section/module item) - * @param string Item type (module/dokeos_module) - * @param string Title (optional, only when creating) - * @param string Action ('add'/'edit') - * @param integer lp_item ID - * @param mixed Extra info - * @return string HTML form - */ - public function display_item_form($item_type, $title = '', $action = 'add', $id = 0, $extra_info = 'new') { - global $_course; - global $charset; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - if ($id != 0 && is_array($extra_info)) { - $item_title = $extra_info['title']; - $item_description = $extra_info['description']; - $item_path = api_get_path(WEB_COURSE_PATH) . $_course['path'] . '/scorm/' . $this->path . '/' . stripslashes($extra_info['path']); - } else { - $item_title = ''; - $item_description = ''; - } - - $return = '
-
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE lp_id = " . $this->lp_id . " AND id != " . $id . " "; - - if ($item_type == 'module') - $sql .= " AND parent_item_id = 0"; - - $result = Database::query($sql); - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'], - 'display_order' => $row['display_order'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - $return .= $title . "\n"; - $return .= '
-
'; - - require_once api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'; - $form = new FormValidator('form', 'POST', api_get_self() . '?' . $_SERVER['QUERY_STRING']); - //$defaults['title'] = api_convert_encoding($item_title, $charset, $this->encoding); - $defaults['title'] = api_html_entity_decode(api_convert_encoding($item_title, $charset, $this->encoding), ENT_QUOTES, $charset); - $defaults['description'] = api_convert_encoding($item_description, $charset, $this->encoding); - - $form->addElement('html', $return); - - //$arrHide = array($id); - $arrHide[0]['value'] = Security :: remove_XSS(api_convert_encoding($this->name, api_get_system_encoding(), $_SESSION['oLP']->encoding)); - $arrHide[0]['padding'] = 3; - $charset = api_get_system_encoding(); - if ($item_type != 'module' && $item_type != 'dokeos_module') { - for ($i = 0; $i < count($arrLP); $i++) { - if ($action != 'add') { - if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; - if ($parent == $arrLP[$i]['id']) { - $s_selected_parent = $arrHide[$arrLP[$i]['id']]; - } - } - } else { - if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') { - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; - if ($parent == $arrLP[$i]['id']) { - $s_selected_parent = $arrHide[$arrLP[$i]['id']]; - } - } - } - } - - if ($action != 'move') { - $form->addElement('text', 'title', get_lang('Title'), 'id="idTitle" class="learnpath_chapter_form" size="40%"'); - $form->applyFilter('title', 'html_filter'); - $form->addRule('title', '
' . get_lang('ThisFieldIsRequired'), 'required'); - //$form->addElement('textarea', 'description', get_lang('Description').' :', 'id="idDescription"'); - } else { - $form->addElement('hidden', 'title'); - } - - $parent_select = & $form->addElement('select', 'parent', get_lang('Parent'), '', 'class="learnpath_chapter_form" style="width:37%;" id="Parent" onchange="javascript: load_cbo(this.value);"'); - - foreach ($arrHide as $key => $value) { - $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); - } - $parent_select->setSelected($s_selected_parent); - } - if (is_array($arrLP)) { - reset($arrLP); - } - - $arrHide = array (); - - // POSITION - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; - - $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"'; - } - } - - $position = & $form->addElement('select', 'previous', get_lang('Position'), '', 'id="idPosition" class="learnpath_chapter_form" style="width:37%;"'); - - $position->addOption(get_lang('FirstPosition'), 0, 'style="padding-left:' . $value['padding'] . 'px;"'); - - foreach ($arrHide as $key => $value) { - $position->addOption($value['value'] . '"', $key, 'style="padding-left:' . $value['padding'] . 'px;"'); - } - - if (!empty ($s_selected_position)) { - $position->setSelected($s_selected_position); - } - - if (is_array($arrLP)) { - reset($arrLP); - } - - $form->addElement('style_submit_button', 'submit_button', get_lang('SaveSection'), 'class="save"'); - - if ($item_type == 'module' || $item_type == 'dokeos_module') { - $form->addElement('hidden', 'parent', '0'); - } - - $extension = pathinfo($item_path, PATHINFO_EXTENSION); - if (($item_type == 'asset' || $item_type == 'sco') && ($extension == 'html' || $extension == 'htm')) { - if ($item_type == 'sco') { - $form->addElement('html', ''); - } - $renderer = $form->defaultRenderer(); - $renderer->setElementTemplate('
      {label}
{element}', 'content_lp'); - $form->addElement('html_editor', 'content_lp', '', null, array('ToolbarSet' => 'LearningPathDocuments', 'Width' => '100%', 'Height' => '400', 'FullPage' => true)); - $defaults['content_lp'] = file_get_contents($item_path); - } - - $form->addElement('hidden', 'type', 'dokeos_' . $item_type); - $form->addElement('hidden', 'post_time', time()); - $form->setDefaults($defaults); - return $form->return_form(); - } - - /** - * Returns the form to update or create a document - * @param string Action (add/edit) - * @param integer ID of the lp_item (if already exists) - * @param mixed Integer if document ID, string if info ('new') - * @return string HTML form - */ - public function display_document_form($action = 'add', $id = 0, $extra_info = 'new') { - global $charset; - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_doc = Database :: get_course_table(TABLE_DOCUMENT); - - $path_parts = pathinfo($extra_info['dir']); - $no_display_edit_textarea = false; - - //If action==edit document - //We don't display the document form if it's not an editable document (html or txt file) - if ($action == "edit") { - if (is_array($extra_info)) { - if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") { - $no_display_edit_textarea = true; - } - } - } - $no_display_add = false; - - // If action==add an existing document - // We don't display the document form if it's not an editable document (html or txt file). - if ($action == "add") { - if (is_numeric($extra_info)) { - $sql_doc = "SELECT path FROM " . $tbl_doc . "WHERE id = " . Database :: escape_string($extra_info); - $result = Database::query($sql_doc); - $path_file = Database :: result($result, 0, 0); - $path_parts = pathinfo($path_file); - if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") { - $no_display_add = true; - } - } - } - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - $item_description = stripslashes($extra_info['description']); - $item_terms = stripslashes($extra_info['terms']); - if (empty ($item_title)) { - $path_parts = pathinfo($extra_info['path']); - $item_title = stripslashes($path_parts['filename']); - } - } elseif (is_numeric($extra_info)) { - $sql_doc = "SELECT path, title FROM " . $tbl_doc . " - WHERE id = " . Database :: escape_string($extra_info); - - $result = Database::query($sql_doc); - $row = Database :: fetch_array($result); - - $explode = explode('.', $row['title']); - - if (count($explode) > 1) { - for ($i = 0; $i < count($explode) - 1; $i++) - $item_title .= $explode[$i]; - } else { - $item_title = $row['title']; - } - - $item_title = str_replace('_', ' ', $item_title); - if (empty ($item_title)) { - $path_parts = pathinfo($row['path']); - $item_title = stripslashes($path_parts['filename']); - } - - } else { - $item_title = ''; - $item_description = ''; - } - - $return = '
-
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = "SELECT * FROM " . $tbl_lp_item . " - WHERE lp_id = " . $this->lp_id; - $result = Database::query($sql); - $arrLP = array (); - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($action == 'add') { - $return .= get_lang('CreateTheDocument') . "\n"; - } - elseif ($action == 'move') { - $return .= get_lang('MoveTheCurrentDocument') . "\n"; - } else { - $return .= get_lang('EditTheCurrentDocument') . "\n"; - } - - $return .= '
-
'; - - if (isset ($_GET['edit']) && $_GET['edit'] == 'true') { - $return .= Display :: return_warning_message('' . get_lang('Warning') . ' !
' . get_lang('WarningEditingDocument'), false); - } - require_once api_get_path(LIBRARY_PATH).'formvalidator/FormValidator.class.php'; - $form = new FormValidator('form', 'POST', api_get_self() . '?' . $_SERVER['QUERY_STRING'], '', 'enctype="multipart/form-data"'); - $defaults['title'] = Security :: remove_XSS(api_convert_encoding($item_title, api_get_system_encoding() ,$this->encoding)); - if (empty($item_title)) { - $defaults['title'] = Security::remove_XSS($item_title); - } - $defaults['description'] = api_convert_encoding($item_description, $charset, $this->encoding); - $form->addElement('html', $return); - if ($action != 'move') { - $form->addElement('text', 'title', get_lang('Title'), 'id="idTitle" class="learnpath_item_form" size=44%'); - $form->applyFilter('title', 'html_filter'); - } - - //$arrHide = array($id); - - $arrHide[0]['value'] = $this->name; - $arrHide[0]['padding'] = 3; - - for ($i = 0; $i < count($arrLP); $i++) { - if ($action != 'add') { - if (($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') && !in_array($arrLP[$i]['id'], $arrHide) && !in_array($arrLP[$i]['parent_item_id'], $arrHide)) { - $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title']; - $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; - if ($parent == $arrLP[$i]['id']) { - $s_selected_parent = $arrHide[$arrLP[$i]['id']]; - } - } - } else { - if ($arrLP[$i]['item_type'] == 'dokeos_module' || $arrLP[$i]['item_type'] == 'dokeos_chapter' || $arrLP[$i]['item_type'] == 'dir') { - $arrHide[$arrLP[$i]['id']]['value'] = $arrLP[$i]['title']; - $arrHide[$arrLP[$i]['id']]['padding'] = 3 + $arrLP[$i]['depth'] * 10; - if ($parent == $arrLP[$i]['id']) { - $s_selected_parent = $arrHide[$arrLP[$i]['id']]; - } - } - } - } - - $parent_select = & $form->addElement('select', 'parent', get_lang('Parent'), '', 'class="learnpath_item_form" style="width:40%;" onchange="javascript: load_cbo(this.value);"'); - $my_count=0; - foreach ($arrHide as $key => $value) { - if ($my_count!=0) { - // The LP name is also the first section and is not in the same charset like the other sections. - $value['value'] = Security :: remove_XSS(api_convert_encoding($value['value'], api_get_system_encoding(), $this->encoding)); - $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); - } else { - $value['value'] = Security :: remove_XSS($value['value']); - $parent_select->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); - } - $my_count++; - } - - if (!empty ($id)) { - $parent_select->setSelected($parent); - } else { - $parent_item_id = $_SESSION['parent_item_id']; - $parent_select->setSelected($parent_item_id); - } - - - - if (is_array($arrLP)) { - reset($arrLP); - } - - $arrHide = array (); - - //POSITION - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; - - $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After') . ' "' . api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding) . '"'; - - } - } - - $position = & $form->addElement('select', 'previous', get_lang('Position'), '', 'id="idPosition" class="learnpath_item_form" style="width:40%;"'); - $position->addOption(get_lang('FirstPosition'), 0); - - foreach ($arrHide as $key => $value) { - $position->addOption($value['value'], $key, 'style="padding-left:' . $value['padding'] . 'px;"'); - } - $position->setSelected($s_selected_position); - if (is_array($arrLP)) { - reset($arrLP); - } - - if ($action != 'move') { - $id_prerequisite = 0; - if (is_array($arrLP)) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - // Commented the prerequisites, only visible in edit (new document). - //$select_prerequisites=$form->addElement('select', 'prerequisites', get_lang('Prerequisites'),null,'id="prerequisites" class="learnpath_item_form" style="width:263px;"'); - //$select_prerequisites->addOption(get_lang('NoPrerequisites'), 0); - - // Form element for uploading an mp3 file. - //$form->addElement('file','mp3',get_lang('UploadMp3audio'),'id="mp3" size="33"'); - //$form->addRule('file', 'The extension of the Song file should be *.mp3', 'filename', '/^.*\.mp3$/'); - - /* Code deprecated - moved to lp level (not lp-item). - if (api_get_setting('search_enabled') === 'true') { - //add terms field - $terms = $form->addElement('text', 'terms', get_lang('SearchFeatureTerms').' :', 'id="idTerms" class="learnpath_item_form"'); - $terms->setValue($item_terms); - } - */ - - $arrHide = array (); - - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id']; - - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - - } - } - - /* foreach ($arrHide as $key => $value) { - $select_prerequisites->addOption($value['value'], $key, 'style="padding-left:'.$value['padding'].'px;"'); - if ($key == $s_selected_position && $action == 'add') { - $select_prerequisites -> setSelected(0); - } - elseif ($key == $id_prerequisite && $action == 'edit') { - $select_prerequisites -> setSelected($id_prerequisite); - } - } - */ - if (!$no_display_add) { - if (($extra_info == 'new' || $extra_info['item_type'] == TOOL_DOCUMENT || $_GET['edit'] == 'true')) { - - if (isset ($_POST['content'])) - $content = stripslashes($_POST['content']); - elseif (is_array($extra_info)) { - //If it's an html document or a text file - if (!$no_display_edit_textarea) { - $content = $this->display_document($extra_info['path'], false, false); - } - } - elseif (is_numeric($extra_info)) $content = $this->display_document($extra_info, false, false); - else - $content = ''; - - if (!$no_display_edit_textarea) { - // We need to claculate here some specific settings for the online editor. - // The calculated settings work for documents in the Documents tool - // (on the root or in subfolders). - // For documents in native scorm packages it is unclear whether the - // online editor should be activated or not. - $relative_path = $extra_info['dir']; - if ($relative_path == 'n/') { - // A new document, it is in the root of the repository. - $relative_path = ''; - $relative_prefix = ''; - } else { - // The document already exists. Whe have to determine its relative path towards the repository root. - $relative_path = explode('/', $relative_path); - $cnt = count($relative_path) - 2; - if ($cnt < 0) { - $cnt = 0; - } - $relative_prefix = str_repeat('../', $cnt); - $relative_path = array_slice($relative_path, 1, $cnt); - $relative_path = implode('/', $relative_path); - if (strlen($relative_path) > 0) { - $relative_path = $relative_path . '/'; - } - } - $editor_config = array('ToolbarSet' => 'LearningPathDocuments', 'Width' => '100%', 'Height' => '700', 'FullPage' => true, - 'CreateDocumentDir' => $relative_prefix, - 'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/', - 'BaseHref' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/' . $relative_path - ); - - if ($_GET['action'] == 'add_item') { - $class = 'add'; - $text = get_lang('LPCreateDocument'); - } else - if ($_GET['action'] == 'edit_item') { - $class = 'save'; - $text = get_lang('SaveDocument'); - } - - $form->addElement('style_submit_button', 'submit_button', $text, 'class="' . $class . '"'); - $renderer = $form->defaultRenderer(); - $renderer->setElementTemplate('
      {label}
{element}', 'content_lp'); - - $form->addElement('html', '
'); - $form->addElement('html_editor', 'content_lp', '', null, $editor_config); - $form->addElement('html', '
'); - $defaults['content_lp'] = $content; - } - } - - elseif (is_numeric($extra_info)) { - - $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'class="save"'); - - $return = $this->display_document($extra_info, true, true, true); - $form->addElement('html', $return); - } - } - - } - if ($action == 'move') { - $form->addElement('hidden', 'title', $item_title); - $form->addElement('hidden', 'description', $item_description); - } - if (is_numeric($extra_info)) { - $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'value="submit_button", class="save"'); - $form->addElement('hidden', 'path', $extra_info); - } - elseif (is_array($extra_info)) { - $form->addElement('style_submit_button', 'submit_button', get_lang('SaveDocument'), 'class="save"'); - $form->addElement('hidden', 'path', $extra_info['path']); - } - - $form->addElement('hidden', 'type', TOOL_DOCUMENT); - $form->addElement('hidden', 'post_time', time()); - - $form->setDefaults($defaults); - - return $form->return_form(); - } - - /** - * Return HTML form to add/edit a link item - * @param string Action (add/edit) - * @param integer Item ID if exists - * @param mixed Extra info - * @return string HTML form - */ - public function display_link_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_link = Database :: get_course_table(TABLE_LINK); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - $item_description = stripslashes($extra_info['description']); - $item_url = stripslashes($extra_info['url']); - } - elseif (is_numeric($extra_info)) { - $sql_link = " - SELECT - title, - description, - url - FROM " . $tbl_link . " - WHERE id = " . $extra_info; - - $result = Database::query($sql_link); - $row = Database :: fetch_array($result); - - $item_title = $row['title']; - - $item_description = $row['description']; - $item_url = $row['url']; - } else { - $item_title = ''; - $item_description = ''; - $item_url = ''; - } - $item_title = api_convert_encoding($item_title, $charset, $this->encoding); - $item_description = api_convert_encoding($item_description, $charset, $this->encoding); - $return = '
-
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($action == 'add') - $return .= get_lang('CreateTheLink') . ' :' . "\n"; - elseif ($action == 'move') $return .= get_lang('MoveCurrentLink') . ' :' . "\n"; - else - $return .= get_lang('EditCurrentLink') . ' :' . "\n"; - - $return .= '
-
'; - $return .= '
'; - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $id_prerequisite = 0; - if (is_array($arrLP)) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - - $arrHide = array (); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - - } - } - - /*// Commented the prerequisites, only visible in edit (link). - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '"; - */ - $return .= "\t\t" . '' . "\n"; - - } - - $return .= "\t\t" . '' . "\n"; - if ($action == 'add') { - $return .= ''; - } else { - $return .= ''; - } - $return .= "\t\t" . '' . "\n"; - $return .= "\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
  
' . "\n"; - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } - elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= '
' . "\n"; - $return .= '
' . "\n"; - return $return; - } - - /** - * Return HTML form to add/edit a student publication (work) - * @param string Action (add/edit) - * @param integer Item ID if already exists - * @param mixed Extra info (work ID if integer) - * @return string HTML form - */ - public function display_student_publication_form($action = 'add', $id = 0, $extra_info = '') { - global $charset; - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $tbl_publication = Database :: get_course_table(TABLE_STUDENT_PUBLICATION); - - if ($id != 0 && is_array($extra_info)) { - $item_title = stripslashes($extra_info['title']); - $item_description = stripslashes($extra_info['description']); - } - elseif (is_numeric($extra_info)) { - $sql_publication = " - SELECT - title, - description - FROM " . $tbl_publication . " - WHERE id = " . $extra_info; - - $result = Database::query($sql_publication); - $row = Database :: fetch_array($result); - - $item_title = $row['title']; - } else { - $item_title = get_lang('Student_publication'); - } - - $item_title = api_convert_encoding($item_title, $charset, $this->encoding); - $return = '
-
'; - - if ($id != 0 && is_array($extra_info)) - $parent = $extra_info['parent_item_id']; - else - $parent = 0; - - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE - lp_id = " . $this->lp_id; - - $result = Database::query($sql); - - $arrLP = array (); - - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => $row['title'], - 'path' => $row['path'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'] - ); - } - - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - if ($action == 'add') - $return .= get_lang('Student_publication') . ' :' . "\n"; - elseif ($action == 'move') $return .= get_lang('MoveCurrentStudentPublication') . ' :' . "\n"; - else - $return .= get_lang('EditCurrentStudentPublication') . ' :' . "\n"; - $return .= '
-
'; - $return .= '
'; - $return .= '
' . "\n"; - $return .= "\t" . '' . "\n"; - if ($action != 'move') { - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - } - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - if ($action != 'move') { - $id_prerequisite = 0; - if (is_array($arrLP)) { - foreach ($arrLP as $key => $value) { - if ($value['id'] == $id) { - $id_prerequisite = $value['prerequisite']; - break; - } - } - } - $arrHide = array (); - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') { - if ($extra_info['previous_item_id'] == $arrLP[$i]['id']) - $s_selected_position = $arrLP[$i]['id']; - elseif ($action == 'add') $s_selected_position = 0; - $arrHide[$arrLP[$i]['id']]['value'] = api_convert_encoding($arrLP[$i]['title'], $charset, $this->encoding); - - } - } - - // Commented the prerequisites, only visible in edit (work). - /* - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '"; - */ - $return .= "\t\t" . '' . "\n"; - - } - - $return .= "\t\t" . '' . "\n"; - if ($action == 'add') { - $return .= ''; - } else { - $return .= ''; - } - $return .= "\t\t" . '' . "\n"; - - $return .= "\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
' . "\n"; - $return .= "\t\t\t\t" . ''; - $return .= "\t\t\t" . '
  
' . "\n"; - - if ($action == 'move') { - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - } - - if (is_numeric($extra_info)) { - $return .= "\t" . '' . "\n"; - } elseif (is_array($extra_info)) { - $return .= "\t" . '' . "\n"; - } - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= '
' . "\n"; - $return .= '
' . "\n"; - return $return; - } - - /** - * Displays the menu for manipulating a step - * @return unknown - */ - public function display_manipulate($item_id, $item_type = TOOL_DOCUMENT) { - global $charset, $_course; - $return = '
'; - - switch ($item_type) { - case 'dokeos_chapter' : - case 'chapter' : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateChapter'); - break; - - case 'dokeos_module' : - case 'module' : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateModule'); - break; - - case TOOL_DOCUMENT : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateDocument'); - break; - - case TOOL_LINK : - case 'link' : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateLink'); - break; - - case TOOL_QUIZ : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateQuiz'); - break; - - case TOOL_STUDENTPUBLICATION : - // Commented the message cause should not show it. - //$lang = get_lang('TitleManipulateStudentPublication'); - - break; - } - - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - $sql = " - SELECT - * - FROM " . $tbl_lp_item . " as lp - WHERE - lp.id = " . $item_id; - - $result = Database::query($sql); - - $row = Database::fetch_assoc($result); - $s_title = $row['title']; - $s_title = api_convert_encoding($s_title, $charset, $this->encoding); - - // we display an audio player if needed - if (!empty ($row['audio'])) { - $return .= '
Get the Flash Player to see this player.
'; - $return .= ''; - $return .= ''; - } - // Commented ":" for message in step. - //$return .= $lang.': '; - - $return .= 'Edit the current item ' . get_lang('Edit') . ''; - $return .= 'Move the current item ' . get_lang('Move') . ''; - // Commented for now as prerequisites cannot be added to chapters. - if ($item_type != 'dokeos_chapter' && $item_type != 'chapter') { - $return .= '' . get_lang('Prerequisites') . ' ' . get_lang('Prerequisites') . ''; - } - $return .= 'Delete the current item ' . get_lang('Delete') . ''; - - //$return .= '

' . ((trim($s_description) == '') ? ''.get_lang('NoDescription').'' : stripslashes(nl2br($s_description))) . '

'; - - //$return.="
"; - - // Get the audiorecorder. Use of ob_* functions since there are echos in the file. - ob_start(); - $audio_recorder_studentview = 'false'; - $audio_recorder_item_id = $item_id; - if (api_get_setting('service_visio', 'active') == 'true') { - include 'audiorecorder.inc.php'; - } - $return .= ob_get_contents(); - ob_end_clean(); - // End of audiorecorder include. - - $return .= ''; - - return $return; - } - - /** - * Creates the javascript needed for filling up the checkboxes without page reload - * - * @return string - */ - public function create_js() { - $return = '' . "\n"; - return $return; - } - - /** - * Display the form to allow moving an item - * @param integer Item ID - * @return string HTML form - */ - public function display_move_item($item_id) { - global $_course; //will disappear - global $charset; - $return = ''; - - if (is_numeric($item_id)) { - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - $sql = "SELECT * - FROM " . $tbl_lp_item . " - WHERE id = " . $item_id; - - $res = Database::query($sql); - $row = Database :: fetch_array($res); - - switch ($row['item_type']) { - case 'dokeos_chapter' : - case 'dir' : - case 'asset' : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_item_form($row['item_type'], get_lang('MoveCurrentChapter'), 'move', $item_id, $row); - break; - - case 'dokeos_module' : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_item_form($row['item_type'], 'Move th current module:', 'move', $item_id, $row); - break; - case TOOL_DOCUMENT : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_document_form('move', $item_id, $row); - break; - case TOOL_LINK : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_link_form('move', $item_id, $row); - break; - case TOOL_HOTPOTATOES : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_link_form('move', $item_id, $row); - break; - case TOOL_QUIZ : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_quiz_form('move', $item_id, $row); - break; - case TOOL_STUDENTPUBLICATION : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_student_publication_form('move', $item_id, $row); - break; - case TOOL_FORUM : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_forum_form('move', $item_id, $row); - break; - case TOOL_THREAD : - $return .= $this->display_manipulate($item_id, $row['item_type']); - $return .= $this->display_forum_form('move', $item_id, $row); - break; - } - } - - return $return; - } - - /** - * Displays a basic form on the overview page for changing the item title and the item description. - * @param string $item_type - * @param string $title - * @param array $data - * @return string - */ - public function display_item_small_form($item_type, $title = '', $data) { - global $charset; - - $return .= '
' . "\n"; - $return .= '

' . $title . '

'; - $return .= '
' . "\n"; - $return .= ''; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - // It said these lines of code - see SVN#11724 and SVN#10770 - //$return .= "\t\t" . '' . "\n"; - //$return .= "\t\t\t" . '' . "\n"; - //$return .= "\t\t\t" . '' . "\n"; - //$return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t\t" . '' . "\n"; - $return .= "\t\t" . '' . "\n"; - $return .= "\t\t" . '
' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= "\t" . '' . "\n"; - $return .= '
'; - $return .= '
'; - return $return; - } - - /** - * Return HTML form to allow prerequisites selection - * @param integer Item ID - * @return string HTML form - */ - public function display_item_prerequisites_form($item_id) { - global $charset; - $tbl_lp_item = Database :: get_course_table(TABLE_LP_ITEM); - - /* Current prerequisite */ - $sql = " - SELECT * - FROM " . $tbl_lp_item . " - WHERE id = " . $item_id; - $result = Database::query($sql); - $row = Database :: fetch_array($result); - - $preq_id = $row['prerequisite']; - //$preq_mastery = $row['mastery_score']; - //$preq_max = $row['max_score']; - - $return = $this->display_manipulate($item_id, TOOL_DOCUMENT); - $return = '
-
'; - $return .= get_lang('AddEditPrerequisites'); - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - $return .= ''; - - // Adding the none option to the prerequisites see http://www.chamilo.org/es/node/146 - $return .= ''; - $return .= ''; - - $sql = "SELECT * FROM " . $tbl_lp_item . " WHERE lp_id = " . $this->lp_id; - $result = Database::query($sql); - $arrLP = array (); - while ($row = Database :: fetch_array($result)) { - $arrLP[] = array ( - 'id' => $row['id'], - 'item_type' => $row['item_type'], - 'title' => api_convert_encoding($row['title'], $charset, $this->encoding), - 'ref' => $row['ref'], - 'description' => $row['description'], - 'parent_item_id' => $row['parent_item_id'], - 'previous_item_id' => $row['previous_item_id'], - 'next_item_id' => $row['next_item_id'], - 'max_score' => $row['max_score'], - 'min_score' => $row['min_score'], - 'mastery_score' => $row['mastery_score'], - 'prerequisite' => $row['prerequisite'], - 'next_item_id' => $row['next_item_id'], - 'display_order' => $row['display_order'] - ); - if ($row['ref'] == $preq_id) { - $preq_mastery = $row['mastery_score']; - $preq_max = $row['max_score']; - } - } - $this->tree_array($arrLP); - $arrLP = $this->arrMenu; - unset ($this->arrMenu); - - for ($i = 0; $i < count($arrLP); $i++) { - if ($arrLP[$i]['id'] == $item_id) - break; - $return .= ''; - $return .= ''; - //$return .= ''; - $return .= ''; - } - if ($arrLP[$i]['item_type'] == TOOL_HOTPOTATOES) { - $return .= ''; - $return .= ''; - } - $return .= ''; - } - $return .= ''; - $return .= ''; - $return .= ''; - $return .= '
' . get_lang('Prerequisites') . '' . get_lang('Minimum') . '' . get_lang('Maximum') . '
'; - $return .= ''; - $return .= ''; - $return .= ''; - $return .= '
'; - $return .= ''; - $icon_name = str_replace(' ', '', $arrLP[$i]['item_type']); - if (file_exists('../img/lp_' . $icon_name . '.png')) { - $return .= ''; - } else - if (file_exists('../img/lp_' . $icon_name . '.gif')) { - $return .= ''; - } else { - $return .= Display::return_icon('folder_document.gif','',array('style'=>'margin-right:5px;')); - } - $return .= ''; - $return .= ''; - - if ($arrLP[$i]['item_type'] == TOOL_QUIZ) { - $return .= ''; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '
'; - $return .= '