Эх сурвалжийг харах

Adding new structure for Exercises: adding "iid" fields in quiz_ tables. Adding autolaunch for exercise

Julio Montoya 12 жил өмнө
parent
commit
5eeac46588
68 өөрчлөгдсөн 2481 нэмэгдсэн , 1545 устгасан
  1. 67 5
      main/course_home/course_home.php
  2. 10 0
      main/course_info/infocours.php
  3. 2 1
      main/coursecopy/classes/CourseArchiver.class.php
  4. 26 16
      main/coursecopy/classes/CourseBuilder.class.php
  5. 5 3
      main/coursecopy/classes/CourseRecycler.class.php
  6. 108 68
      main/coursecopy/classes/CourseRestorer.class.php
  7. 14 11
      main/coursecopy/classes/QuizQuestion.class.php
  8. 1 1
      main/coursecopy/classes/QuizQuestionOption.class.php
  9. 39 0
      main/css/base.css
  10. 1 1
      main/exercice/GC.php
  11. 1 2
      main/exercice/addlimits.php
  12. 6 5
      main/exercice/admin.php
  13. 135 61
      main/exercice/answer.class.php
  14. 71 140
      main/exercice/exercice.php
  15. 356 184
      main/exercice/exercise.class.php
  16. 41 28
      main/exercice/exercise.lib.php
  17. 62 9
      main/exercice/exercise_admin.php
  18. 75 72
      main/exercice/exercise_reminder.php
  19. 12 13
      main/exercice/exercise_report.php
  20. 3 4
      main/exercice/exercise_result.class.php
  21. 1 0
      main/exercice/exercise_show.php
  22. 63 59
      main/exercice/exercise_submit.php
  23. 3 2
      main/exercice/fill_blanks.class.php
  24. 49 51
      main/exercice/freeanswer.class.php
  25. 8 6
      main/exercice/global_multiple_answer.class.php
  26. 1 3
      main/exercice/hotpotatoes_exercise_result.class.php
  27. 1 4
      main/exercice/hotspot.class.php
  28. 24 29
      main/exercice/hotspot_actionscript.as.php
  29. 5 9
      main/exercice/hotspot_actionscript_admin.as.php
  30. 10 6
      main/exercice/hotspot_admin.inc.php
  31. 15 12
      main/exercice/hotspot_answers.as.php
  32. 7 5
      main/exercice/matching.class.php
  33. 7 7
      main/exercice/media.php
  34. 7 5
      main/exercice/media_question.class.php
  35. 8 10
      main/exercice/multiple_answer.class.php
  36. 9 11
      main/exercice/multiple_answer_combination.class.php
  37. 2 3
      main/exercice/multiple_answer_combination_true_false.class.php
  38. 10 11
      main/exercice/multiple_answer_true_false.class.php
  39. 3 4
      main/exercice/oral_expression.class.php
  40. 167 108
      main/exercice/question.class.php
  41. 6 6
      main/exercice/question_admin.inc.php
  42. 6 10
      main/exercice/question_list_admin.inc.php
  43. 66 105
      main/exercice/question_pool.php
  44. 1 1
      main/exercice/result.php
  45. 1 0
      main/exercice/savescores.php
  46. 422 117
      main/exercice/testcategory.class.php
  47. 149 66
      main/exercice/tests_category.php
  48. 19 22
      main/exercice/unique_answer.class.php
  49. 12 13
      main/exercice/unique_answer_no_option.class.php
  50. 6 37
      main/exercice/upload_exercise.php
  51. BIN
      main/img/icons/32/folder_category.gif
  52. BIN
      main/img/icons/32/folder_global_category.png
  53. BIN
      main/img/icons/32/folder_global_category_new.gif
  54. BIN
      main/img/icons/32/folder_global_category_new.png
  55. BIN
      main/img/icons/64/looknfeel.png
  56. 54 18
      main/inc/ajax/exercise.ajax.php
  57. 25 3
      main/inc/ajax/model.ajax.php
  58. 115 110
      main/inc/global.inc.php
  59. 3 3
      main/inc/lib/database.constants.inc.php
  60. 5 0
      main/inc/lib/database.lib.php
  61. 59 1
      main/inc/lib/display.lib.php
  62. 29 3
      main/inc/lib/events.lib.inc.php
  63. 23 25
      main/inc/lib/plugin.lib.php
  64. 1 1
      main/inc/local.inc.php
  65. 25 25
      main/install/1.10.0/db_course.sql
  66. 9 8
      main/install/1.10.0/db_main.sql
  67. 8 1
      main/install/1.10.0/migrate-db-1.9.0-1.10.0-pre.sql
  68. 2 1
      main/newscorm/learnpath_functions.inc.php

+ 67 - 5
main/course_home/course_home.php

@@ -166,14 +166,73 @@ if (!isset($coursesAlreadyVisited[$course_code])) {
     Session::write('coursesAlreadyVisited', $coursesAlreadyVisited);
 }
 
+$show_autolaunch_exercise_warning = false;
+
+//Exercise autolaunch
+$auto_launch = api_get_course_setting('enable_exercise_auto_launch');
+if (!empty($auto_launch)) {
+    $session_id = api_get_session_id();
+     //Exercise list
+    if ($auto_launch == 2) {
+        if (api_is_platform_admin() || api_is_allowed_to_edit()) {
+            $show_autolaunch_exercise_warning = true;
+        } else {
+            $session_key = 'exercise_autolunch_'.$session_id.'_'.api_get_course_int_id().'_'.api_get_user_id();
+            if (!isset($_SESSION[$session_key])) {
+                //redirecting to the Exercise
+                $url = api_get_path(WEB_CODE_PATH).'exercice/exercice.php?'.api_get_cidreq().'&id_session='.$session_id;
+                $_SESSION[$session_key] = true;
+                header("Location: $url");
+                exit;
+            }
+        }
+    } else {
+        $table = Database::get_course_table(TABLE_QUIZ_TEST);
+        $course_id = api_get_course_int_id();
+        $condition = '';
+        if (!empty($session_id)) {
+            $condition =  api_get_session_condition($session_id);
+            $sql = "SELECT iid FROM $table WHERE c_id = $course_id AND autolaunch = 1 $condition LIMIT 1";
+            $result = Database::query($sql);
+            //If we found nothing in the session we just called the session_id =  0 autolaunch
+            if (Database::num_rows($result) ==  0) {
+                $condition = '';
+            } else {
+            	//great, there is an specific auto lunch for this session we leave the $condition
+            }
+        }
+
+        $sql = "SELECT iid FROM $table WHERE c_id = $course_id AND autolaunch = 1 $condition LIMIT 1";
+        $result = Database::query($sql);
+        if (Database::num_rows($result) >  0) {
+            $data = Database::fetch_array($result,'ASSOC');
+            if (!empty($data['iid'])) {
+                if (api_is_platform_admin() || api_is_allowed_to_edit()) {
+                	$show_autolaunch_exercise_warning = true;
+                } else {
+                    $session_key = 'exercise_autolunch_'.$session_id.'_'.api_get_course_int_id().'_'.api_get_user_id();
+                    if (!isset($_SESSION[$session_key])) {
+                        //redirecting to the LP
+                        $url = api_get_path(WEB_CODE_PATH).'exercice/overview.php?'.api_get_cidreq().'&exerciseId='.$data['iid'];
+
+                        $_SESSION[$session_key] = true;
+                        header("Location: $url");
+                        exit;
+                    }
+                }
+            }
+        }
+    }
+}
 /*Auto launch code */
-$show_autolunch_lp_warning = false;
+$show_autolaunch_lp_warning = false;
 $auto_launch = api_get_course_setting('enable_lp_auto_launch');
 if (!empty($auto_launch)) {
     $session_id = api_get_session_id();
-    if ($auto_launch == 2) { //LP list
+     //LP list
+    if ($auto_launch == 2) {
         if (api_is_platform_admin() || api_is_allowed_to_edit()) {
-            $show_autolunch_lp_warning = true;
+            $show_autolaunch_lp_warning = true;
         } else {
             $session_key = 'lp_autolunch_'.$session_id.'_'.api_get_course_int_id().'_'.api_get_user_id();
             if (!isset($_SESSION[$session_key])) {
@@ -206,7 +265,7 @@ if (!empty($auto_launch)) {
             $lp_data = Database::fetch_array($result,'ASSOC');
             if (!empty($lp_data['id'])) {
                 if (api_is_platform_admin() || api_is_allowed_to_edit()) {
-                	$show_autolunch_lp_warning = true;
+                	$show_autolaunch_lp_warning = true;
                 } else {
                     $session_key = 'lp_autolunch_'.$session_id.'_'.api_get_course_int_id().'_'.api_get_user_id();
                     if (!isset($_SESSION[$session_key])) {
@@ -241,9 +300,12 @@ $content = Display::return_introduction_section(TOOL_COURSE_HOMEPAGE, array(
 	the setting homepage_view is adjustable through
 	the platform administration section */
 
-if ($show_autolunch_lp_warning) {
+if ($show_autolaunch_lp_warning) {
     $show_message .= Display::return_message(get_lang('TheLPAutoLaunchSettingIsONStudentsWillBeRedirectToAnSpecificLP'),'warning');
 }
+if ($show_autolaunch_exercise_warning) {
+    $show_message .= Display::return_message(get_lang('TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToAnSpecificExercise'),'warning');
+}
 if (api_get_setting('homepage_view') == 'activity' || api_get_setting('homepage_view') == 'activity_big') {
 	require 'activity.php';
 } elseif (api_get_setting('homepage_view') == '2column') {

+ 10 - 0
main/course_info/infocours.php

@@ -325,6 +325,15 @@ $group[]=$form->createElement('radio', 'allow_fast_exercise_edition', get_lang('
 
 $form->addGroup($group, '', array(get_lang("AllowFastExerciseEdition")), '');
 
+//Auto-launch Exercise LP
+$form->addElement('html', '<div><h3>'.Display::return_icon('quiz.png', get_lang('ConfigExercise'),'',ICON_SIZE_SMALL).' '.Security::remove_XSS(get_lang('ConfigExercise')).'</h3><div>');
+$group = array();
+$group[]=$form->createElement('radio', 'enable_exercise_auto_launch', get_lang('ExerciseAutoLaunch'), get_lang('RedirectToExercise'), 1);
+$group[]=$form->createElement('radio', 'enable_exercise_auto_launch', get_lang('ExerciseAutoLaunch'), get_lang('RedirectToTheExerciseList'), 2);
+$group[]=$form->createElement('radio', 'enable_exercise_auto_launch', null, get_lang('Deactivate'), 0);
+$form->addGroup($group, '', array(get_lang("ExerciseAutoLaunch")), '');
+
+
 $form->addElement('style_submit_button', null, get_lang('SaveSettings'), 'class="save"');
 $form->addElement('html', '</div></div>');
 
@@ -408,6 +417,7 @@ $values['display_info_advance_inside_homecourse']   = api_get_course_setting('di
 $values['email_alert_students_on_new_homework']     = api_get_course_setting('email_alert_students_on_new_homework');
 
 $values['enable_lp_auto_launch']                    = api_get_course_setting('enable_lp_auto_launch');
+$values['enable_exercise_auto_launch']              = api_get_course_setting('enable_exercise_auto_launch');
 $values['pdf_export_watermark_text']                = api_get_course_setting('pdf_export_watermark_text');
 $values['allow_public_certificates']                = api_get_course_setting('allow_public_certificates');
 $values['allow_fast_exercise_edition']              = api_get_course_setting('allow_fast_exercise_edition') == 1 ? 1 : 0;

+ 2 - 1
main/coursecopy/classes/CourseArchiver.class.php

@@ -12,7 +12,8 @@ require_once 'Course.class.php';
  *
  * @todo Use archive-folder of Chamilo?
  */
-class CourseArchiver {
+class CourseArchiver 
+{
 
     /**
      * Delete old temp-dirs

+ 26 - 16
main/coursecopy/classes/CourseBuilder.class.php

@@ -445,8 +445,12 @@ class CourseBuilder {
 				$doc = Database::fetch_object($res);
 				$obj->sound = $doc->id;
 			}
+            $exercise_obj = new Exercise($course_id);
+            $exercise_obj->read($obj->iid);
+            $categories = $exercise_obj->get_categories_with_name_in_exercise();
+            $obj->categories = $categories;
             $quiz = new Quiz($obj);
-			$sql = 'SELECT * FROM '.$table_rel.' WHERE c_id = '.$course_id.' AND exercice_id = '.$obj->id;
+			$sql = 'SELECT * FROM '.$table_rel.' WHERE c_id = '.$course_id.' AND exercice_id = '.$obj->iid;
 			$db_result2 = Database::query($sql);
 			while ($obj2 = Database::fetch_object($db_result2)) {
 				$quiz->add_question($obj2->question_id, $obj2->question_order);
@@ -477,14 +481,21 @@ class CourseBuilder {
 		$sql = "SELECT * FROM $table_que WHERE c_id = $course_id ";
         $db_result = Database::query($sql);
 		while ($obj = Database::fetch_object($db_result)) {
-			$question = new QuizQuestion($obj->id, $obj->question, $obj->description, $obj->ponderation, $obj->type, $obj->position, $obj->picture, $obj->level, $obj->extra);
-			$sql = 'SELECT * FROM '.$table_ans.' WHERE c_id = '.$course_id.' AND question_id = '.$obj->id;
+            $categories = Testcategory::getCategoryForQuestionWithCategoryData($obj->iid, $course_id, true);
+            $parent_info = array();
+
+            if (isset($obj->parent_id) && !empty($obj->parent_id)) {
+                $parent_info = (array) Question::read($obj->parent_id, $course_id);
+            }
+
+			$question = new QuizQuestion($obj->iid, $obj->question, $obj->description, $obj->ponderation, $obj->type, $obj->position, $obj->picture, $obj->level, $obj->extra, $parent_info, $categories);
+			$sql = 'SELECT * FROM '.$table_ans.' WHERE c_id = '.$course_id.' AND question_id = '.$obj->iid;
 			$db_result2 = Database::query($sql);
 			while ($obj2 = Database::fetch_object($db_result2)) {
-				$question->add_answer($obj2->id, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
+				$question->add_answer($obj2->iid, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
                 if ($obj->type == MULTIPLE_ANSWER_TRUE_FALSE) {
                     $table_options	= Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
-                    $sql = 'SELECT * FROM '.$table_options.' WHERE c_id = '.$course_id.' AND question_id = '.$obj->id;
+                    $sql = 'SELECT * FROM '.$table_options.' WHERE c_id = '.$course_id.' AND question_id = '.$obj->iid;
                     $db_result3 = Database::query($sql);
                     while ($obj3 = Database::fetch_object($db_result3)) {
                         $question_option = new QuizQuestionOption($obj3);
@@ -497,7 +508,7 @@ class CourseBuilder {
 
 		// Building a fictional test for collecting orphan questions.
 		$build_orphan_questions = !empty($_POST['recycle_option']); // When a course is emptied this option should be activated (true).
-
+        //@todo fix query in order to use iid
         /*$sql = "SELECT questions.*
                 FROM $table_que as questions
                 LEFT JOIN $table_rel as quizz_questions
@@ -545,8 +556,8 @@ class CourseBuilder {
                     $db_result2 = Database::query($sql);
                     if (Database::num_rows($db_result2)) {
                         while ($obj2 = Database::fetch_object($db_result2)) {
-                            $question->add_answer($obj2->id, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
-                            $orphanQuestionIds[] = $obj2->id;
+                            $question->add_answer($obj2->iid, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
+                            $orphanQuestionIds[] = $obj2->iid;
                         }
                     }
 				    $this->course->add_resource($question);
@@ -594,16 +605,15 @@ class CourseBuilder {
                         exercices.active = -1)';
 		$db_result = Database::query($sql);
 		if (Database::num_rows($db_result) > 0) {
-			$orphan_questions = new Quiz(-1, get_lang('OrphanQuestions', ''), '', 0, 0, 1, '', 0); // Tjis is the fictional test for collecting orphan questions.
+            // This is the fictional test for collecting orphan questions.
+			$orphan_questions = new Quiz(-1, get_lang('OrphanQuestions', ''), '', 0, 0, 1, '', 0);
 			$this->course->add_resource($orphan_questions);
-			while ($obj = Database::fetch_object($db_result))
-			{
-				$question = new QuizQuestion($obj->id, $obj->question, $obj->description, $obj->ponderation, $obj->type, $obj->position, $obj->picture,$obj->level,$obj->extra);
-				$sql = 'SELECT * FROM '.$table_ans.' WHERE question_id = '.$obj->id;
+			while ($obj = Database::fetch_object($db_result)) {
+				$question = new QuizQuestion($obj->iid, $obj->question, $obj->description, $obj->ponderation, $obj->type, $obj->position, $obj->picture,$obj->level,$obj->extra);
+				$sql = 'SELECT * FROM '.$table_ans.' WHERE question_id = '.$obj->iid;
 				$db_result2 = Database::query($sql);
-				while ($obj2 = Database::fetch_object($db_result2))
-				{
-					$question->add_answer($obj2->id, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
+				while ($obj2 = Database::fetch_object($db_result2)) {
+					$question->add_answer($obj2->iid, $obj2->answer, $obj2->correct, $obj2->comment, $obj2->ponderation, $obj2->position, $obj2->hotspot_coordinates, $obj2->hotspot_type);
 				}
 				$this->course->add_resource($question);
 			}

+ 5 - 3
main/coursecopy/classes/CourseRecycler.class.php

@@ -314,6 +314,7 @@ class CourseRecycler
             $table_qui_que_rel_cat = Database :: get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
 
             $ids = array_keys($this->course->resources[RESOURCE_QUIZ]);
+            $ids = array_filter($ids);
             // If the value "-1" is in the ids of elements (questions) to
             // be deleted, then consider all orphan questions should be deleted
             // This value is set in CourseBuilder::quiz_build_questions()
@@ -323,7 +324,7 @@ class CourseRecycler
             if (!empty($ids)) {
                 // Deletion of the tests first. Questions in these tests are
                 //   not deleted and become orphan at this point
-                $sql = "DELETE FROM ".$table_qui." WHERE c_id = ".$this->course_id." AND id IN(".$ids.")";
+                $sql = "DELETE FROM ".$table_qui." WHERE c_id = ".$this->course_id." AND iid IN(".$ids.")";
                 Database::query($sql);
                 $sql = "DELETE FROM ".$table_rel." WHERE c_id = ".$this->course_id." AND exercice_id IN(".$ids.")";
                 Database::query($sql);
@@ -347,6 +348,7 @@ class CourseRecycler
                        // active = -1 means "deleted" test.
                 */
 
+                //@todo fix query in order to use iid
                 $sql = " (
                     SELECT q.id FROM $table_qui_que q
                     INNER JOIN $table_rel r
@@ -374,14 +376,14 @@ class CourseRecycler
                 if (Database::num_rows($db_result) > 0) {
                     $orphan_ids = array();
                     while ($obj = Database::fetch_object($db_result)) {
-                        $orphan_ids[] = $obj->id;
+                        $orphan_ids[] = $obj->iid;
                     }
                     $orphan_ids = implode(',', $orphan_ids);
                     $sql = "DELETE FROM ".$table_rel." WHERE c_id = ".$this->course_id." AND question_id IN(".$orphan_ids.")";
                     Database::query($sql);
                     $sql = "DELETE FROM ".$table_qui_ans." WHERE c_id = ".$this->course_id." AND question_id IN(".$orphan_ids.")";
                     Database::query($sql);
-                    $sql = "DELETE FROM ".$table_qui_que." WHERE c_id = ".$this->course_id." AND id IN(".$orphan_ids.")";
+                    $sql = "DELETE FROM ".$table_qui_que." WHERE c_id = ".$this->course_id." AND iid IN(".$orphan_ids.")";
                     Database::query($sql);
                 }
                 // Also delete questions categories and options

+ 108 - 68
main/coursecopy/classes/CourseRestorer.class.php

@@ -1508,7 +1508,7 @@ class CourseRestorer
                         $exercise->add_exercise_to_order_table();
                     }
 
-                    $this->course->resources[RESOURCE_QUIZ][$id]->destination_id = $new_id;
+                    $this->course->resources[RESOURCE_QUIZ][$id]->obj->destination_id = $new_id;
                     $order = 0;
                     if (!empty($quiz->question_ids)) {
                         foreach ($quiz->question_ids as $index => $question_id) {
@@ -1518,6 +1518,35 @@ class CourseRestorer
                             Database::query($sql);
                         }
                     }
+
+                    if ($quiz->categories) {
+                        $exercise_obj = new Exercise($this->destination_course_id);
+                        $exercise_obj->read($new_id);
+                        $cats = array();
+                        foreach ($quiz->categories as $cat) {
+                            $cat_from_db = new Testcategory($cat['category_id']);
+                            if ($cat_from_db && $cat_from_db->title == $cat['title']) {
+                                //use the same id
+                                $cats[$cat_from_db->id] = $cat['count_questions'];
+                            } else {
+                                $cat_from_db = $cat_from_db->get_category_by_title($cat['title'], $this->destination_course_id);
+                                if (empty($cat_from_db)) {
+                                    //Create a new category in this portal
+                                    if ($cat['category_id'] == 0) {
+                                        $category_c_id = 0;
+                                    } else {
+                                        $category_c_id = $this->destination_course_id;
+                                    }
+                                    $new_cat = new Testcategory(null, $cat['title'], $cat['description'], null, 'simple', $category_c_id);
+                                    $new_cat_id = $new_cat->addCategoryInBDD();
+                                    $cats[$new_cat_id] = $cat['count_questions'];
+                                } else {
+                                    $cats[$cat_from_db['iid']] = $cat['count_questions'];
+                                }
+                            }
+                        }
+                        $exercise_obj->save_categories_in_exercise($cats);
+                    }
                 }
             }
         }
@@ -1543,13 +1572,26 @@ class CourseRestorer
             $table_options = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
 
             // check resources inside html from fckeditor tool and copy correct urls into recipient course
-            $question->description = DocumentManager::replace_urls_inside_content_html_from_copy_course(
-                $question->description,
-                $this->course->code,
-                $this->course->destination_path,
-                $this->course->backup_path,
-                $this->course->info['path']
-            );
+			$question->description = DocumentManager::replace_urls_inside_content_html_from_copy_course($question->description, $this->course->code, $this->course->destination_path);
+
+            $parent_id = 0;
+
+            if (isset($question->parent_info)) {
+                $question_obj = Question::readByTitle($question->parent_info['question'], $this->destination_course_id);
+
+                if ($question_obj) {
+                    //Reuse media
+                    $parent_id = $question_obj->selectId();
+                } else {
+                    //Create media
+                    $question_obj = new MediaQuestion();
+                    $question_obj->updateCourse($this->destination_course_id);
+                    $parent_id = $question_obj->save_media(array(
+                        'questionName'  => $question->parent_info['question'],
+                        'questionDescription' => $question->parent_info['description']
+                    ));
+                }
+            }
 
             $sql = "INSERT INTO ".$table_que." SET
                     c_id = ".$this->destination_course_id." ,
@@ -1560,6 +1602,7 @@ class CourseRestorer
                     type='".self::DBUTF8escapestring($question->quiz_type)."',
                     picture='".self::DBUTF8escapestring($question->picture)."',
                     level='".self::DBUTF8escapestring($question->level)."',
+                    parent_id ='".$parent_id."',
                     extra='".self::DBUTF8escapestring($question->extra)."'";
 
             Database::query($sql);
@@ -1573,9 +1616,7 @@ class CourseRestorer
                     // picture path
                     $picturePath = $documentPath.'/images';
 
-                    $old_picture = api_get_path(
-                        SYS_COURSE_PATH
-                    ).$this->course->info['path'].'/document/images/'.$question->picture;
+                    $old_picture = api_get_path(SYS_COURSE_PATH).$this->course->info['path'].'/document/images/'.$question->picture;
                     if (file_exists($old_picture)) {
                         $picture_name = 'quiz-'.$new_id.'.jpg';
 
@@ -1590,14 +1631,18 @@ class CourseRestorer
 
             if ($question->quiz_type == MATCHING) {
                 $temp = array();
+                $matching_list = array();
+                $matching_to_update = array();
                 foreach ($question->answers as $index => $answer) {
                     $temp[$answer['position']] = $answer;
-                }
 
+                    if (!empty($answer['correct'])) {
+                        $matching_to_update[$answer['iid']] = $answer['correct'];
+                    }
+                }
                 foreach ($temp as $index => $answer) {
                     $sql = "INSERT INTO ".$table_ans." SET
                             c_id = ".$this->destination_course_id." ,
-                            id = '".$index."',
                             question_id = '".$new_id."',
                             answer = '".self::DBUTF8escapestring($answer['answer'])."',
                             correct = '".$answer['correct']."',
@@ -1606,6 +1651,15 @@ class CourseRestorer
                             position = '".$answer['position']."',
                             hotspot_coordinates = '".$answer['hotspot_coordinates']."',
                             hotspot_type = '".$answer['hotspot_type']."'";
+					Database::query($sql);
+                    $new_answer_id = Database::insert_id();
+                    $matching_list[$answer['iid']] = $new_answer_id;
+				}
+                //var_dump($matching_list, $matching_to_update);
+                foreach ($matching_to_update as $old_answer_id => $old_correct_id) {
+                    $new_correct = $matching_list[$old_correct_id];
+                    $new_fixed_id = $matching_list[$old_answer_id];
+                    $sql = "UPDATE $table_ans SET correct = '$new_correct' WHERE iid = $new_fixed_id ";
                     Database::query($sql);
                 }
             } else {
@@ -1613,24 +1667,11 @@ class CourseRestorer
                 foreach ($question->answers as $index => $answer) {
 
                     // check resources inside html from fckeditor tool and copy correct urls into recipient course
-                    $answer['answer'] = DocumentManager::replace_urls_inside_content_html_from_copy_course(
-                        $answer['answer'],
-                        $this->course->code,
-                        $this->course->destination_path,
-                        $this->course->backup_path,
-                        $this->course->info['path']
-                    );
-                    $answer['comment'] = DocumentManager::replace_urls_inside_content_html_from_copy_course(
-                        $answer['comment'],
-                        $this->course->code,
-                        $this->course->destination_path,
-                        $this->course->backup_path,
-                        $this->course->info['path']
-                    );
+					$answer['answer']  = DocumentManager::replace_urls_inside_content_html_from_copy_course($answer['answer'], $this->course->code, $this->course->destination_path);
+					$answer['comment'] = DocumentManager::replace_urls_inside_content_html_from_copy_course($answer['comment'], $this->course->code, $this->course->destination_path);
 
                     $sql = "INSERT INTO ".$table_ans." SET
                                 c_id = ".$this->destination_course_id." ,
-                                id = '".($index + 1)."',
                                 question_id = '".$new_id."',
                                 answer = '".self::DBUTF8escapestring($answer['answer'])."',
                                 correct = '".$answer['correct']."',
@@ -1640,7 +1681,8 @@ class CourseRestorer
                                 hotspot_coordinates = '".$answer['hotspot_coordinates']."',
                                 hotspot_type = '".$answer['hotspot_type']."'";
                     Database::query($sql);
-                    $correct_answers[$index + 1] = $answer['correct'];
+                    $new_answer_id = Database::insert_id();
+                    $correct_answers[$new_answer_id] = $answer['correct'];
                 }
             }
 
@@ -1655,41 +1697,19 @@ class CourseRestorer
                 if ($question_option_list) {
                     $old_option_ids = array();
                     foreach ($question_option_list as $item) {
-                        $old_id = $item['id'];
-                        unset($item['id']);
+                        $old_id = $item['iid'];
+                        unset($item['iid']);
                         $item['question_id'] = $new_id;
                         $item['c_id'] = $this->destination_course_id;
                         $question_option_id = Database::insert($table_options, $item);
                         $old_option_ids[$old_id] = $question_option_id;
                     }
                     if ($old_option_ids) {
-                        $new_answers = Database::select(
-                            'id, correct',
-                            $table_ans,
-                            array(
-                                'WHERE' => array(
-                                    'question_id = ? AND c_id = ? ' => array(
-                                        $new_id,
-                                        $this->destination_course_id
-                                    )
-                                )
-                            )
-                        );
+                        $new_answers = Database::select('iid, correct', $table_ans, array('WHERE' => array('question_id = ? AND c_id = ? '=> array($new_id, $this->destination_course_id))));
                         foreach ($new_answers as $answer_item) {
                             $params = array();
                             $params['correct'] = $old_option_ids[$answer_item['correct']];
-                            $question_option_id = Database::update(
-                                $table_ans,
-                                $params,
-                                array(
-                                    'id = ? AND c_id = ? AND question_id = ? ' => array(
-                                        $answer_item['id'],
-                                        $this->destination_course_id,
-                                        $new_id
-                                    )
-                                ),
-                                false
-                            );
+                            $question_option_id = Database::update($table_ans, $params, array('iid = ? AND c_id = ? AND question_id = ? '=> array($answer_item['iid'], $this->destination_course_id, $new_id)), false);
                         }
                     }
                 } else {
@@ -1704,26 +1724,46 @@ class CourseRestorer
                             $item['position'] = $obj->obj->position;
 
                             $question_option_id = Database::insert($table_options, $item);
-                            $new_options[$obj->obj->id] = $question_option_id;
+                            $new_options[$obj->obj->iid] = $question_option_id;
                         }
+                        //var_dump($new_options, $correct_answers);
                         foreach ($correct_answers as $answer_id => $correct_answer) {
                             $params = array();
                             $params['correct'] = $new_options[$correct_answer];
-                            Database::update(
-                                $table_ans,
-                                $params,
-                                array(
-                                    'id = ? AND c_id = ? AND question_id = ? ' => array(
-                                        $answer_id,
-                                        $this->destination_course_id,
-                                        $new_id
-                                    )
-                                ),
-                                false
-                            );
+                            Database::update($table_ans, $params, array('iid = ? AND c_id = ? AND question_id = ? '=> array($answer_id, $this->destination_course_id, $new_id)), false);
+                        }
+                    }
+                }
+            }
+
+            if ($question->categories) {
+                $cats = array();
+                foreach ($question->categories as $cat) {
+
+                    $new_category = new Testcategory($cat['category_id']);
+                    if ($new_category && $new_category->title == $cat['title']) {
+                        $cats[] = $cat['category_id'];
+                    } else {
+                        $new_category = $new_category->get_category_by_title($cat['title'], $this->destination_course_id);
+                        if (empty($new_category)) {
+                            //Create a new category in this portal
+                            if ($cat['category_id'] == 0) {
+                                $category_c_id = 0;
+                            } else {
+                                $category_c_id = $this->destination_course_id;
+                            }
+                            $new_cat = new Testcategory(null, $cat['title'], $cat['description'], null, 'simple', $category_c_id);
+                            $new_cat_id = $new_cat->addCategoryInBDD();
+                            $cats[] = $new_cat_id;
+                        } else {
+                            $cats[] = $new_category['iid'];
                         }
                     }
                 }
+                $question = Question::read($new_id, $this->destination_course_id);
+                if (!empty($cats)) {
+                    $question->saveCategories($cats);
+                }
             }
             $this->course->resources[RESOURCE_QUIZQUESTION][$id]->destination_id = $new_id;
         }

+ 14 - 11
main/coursecopy/classes/QuizQuestion.class.php

@@ -21,43 +21,44 @@ class QuizQuestion extends Resource
     /**
      * The question
      */
-    var $question;
+    public $question;
 
     /**
      * The description
      */
-    var $description;
+    public $description;
 
     /**
      * Ponderation
      */
-    var $ponderation;
+    public $ponderation;
 
     /**
      * Type
      */
-    var $quiz_type;
+    public $quiz_type;
 
     /**
      * Position
      */
-    var $position;
+    public $position;
 
     /**
      * Level
      */
-    var $level;
+    public $level;
 
     /**
      * Answers
      */
-    var $answers;
+    public $answers;
 
     /**
      * Picture
      */
-    var $picture;
-    var $extra;
+    public $picture;
+    public $extra;
+    public $categories;
 
     /**
      * Create a new QuizQuestion
@@ -67,7 +68,7 @@ class QuizQuestion extends Resource
      * @param int $type
      * @param int $position
      */
-    function QuizQuestion($id, $question, $description, $ponderation, $type, $position, $picture, $level, $extra) {
+    function QuizQuestion($id, $question, $description, $ponderation, $type, $position, $picture, $level, $extra, $parent_info, $categories) {
         parent::Resource($id, RESOURCE_QUIZQUESTION);
         $this->question = $question;
         $this->description = $description;
@@ -78,6 +79,8 @@ class QuizQuestion extends Resource
         $this->level = $level;
         $this->answers = array();
         $this->extra = $extra;
+        $this->parent_info = $parent_info;
+        $this->categories = $categories;
     }
 
     /**
@@ -85,7 +88,7 @@ class QuizQuestion extends Resource
      */
     function add_answer($answer_id, $answer_text, $correct, $comment, $ponderation, $position, $hotspot_coordinates, $hotspot_type) {
         $answer = array();
-        $answer['id'] = $answer_id;
+        $answer['iid'] = $answer_id;
         $answer['answer'] = $answer_text;
         $answer['correct'] = $correct;
         $answer['comment'] = $comment;

+ 1 - 1
main/coursecopy/classes/QuizQuestionOption.class.php

@@ -28,7 +28,7 @@ class QuizQuestionOption extends Resource
      * @param int $position
      */
     function QuizQuestionOption($obj) {
-        parent::Resource($obj->id, RESOURCE_QUIZQUESTION);
+        parent::Resource($obj->iid, RESOURCE_QUIZQUESTION);
         $this->obj = $obj;
     }
 }

+ 39 - 0
main/css/base.css

@@ -2866,6 +2866,7 @@ div.admin_section h4 {
 
 /* Fixes questions list */
 .question_options {
+    margin-top:16px;
     min-height: 150px;
 }
 
@@ -2938,6 +2939,7 @@ div.admin_section h4 {
 #question_description {
     padding-left: 5px;
     padding-top: 10px;
+    padding-bottom:10px;
 }
 
 #question_score {
@@ -5147,6 +5149,43 @@ i.size-32.icon-new-work {
     margin-bottom: 15px;
 }
 
+
+.pagination .current a {
+    font-size: 120%;
+    cursor: default;
+}
+
+.pagination .before a, .pagination .before a:hover {
+    background-color: #028DC2;
+    color:white;
+}
+
+.pagination .after a, .pagination .after a:hover {
+    background-color: #F5F5F5;
+    color:#999999;
+}
+
+.pagination .remind a {
+    text-decoration: underline;
+}
+
+.pagination .not_answered a , .pagination .not_answered a:hover {
+    background-color: #f89406;
+}
+
+.pagination .answered a , .pagination .answered a:hover {
+    background-color: green;
+}
+
+.exercise_reminder_item {
+    font-size: 80%;
+}
+
+.exercise_reminder_item .radio, .exercise_reminder_item .checkbox {
+    padding-left: 0px;
+}
+
+
 .exercise_save_mini_message {
     margin-left: 5px;
 }

+ 1 - 1
main/exercice/GC.php

@@ -13,7 +13,7 @@
 // working recursively, flag[0,1] print or delete the HotPotatoes temp files (.t.html)
 
 echo "Garbage Collector<BR>";
-HotPotGC($_configuration['root_sys'], 1, 1);
+HotPotGC(api_get_path(SYS_PATH), 1, 1);
 /**
  * Garbage collector caller function
  */

+ 1 - 2
main/exercice/addlimits.php

@@ -185,5 +185,4 @@ if (isset($_POST['ok'])) {
 		$query = "UPDATE ".$TBL_EXERCICES." SET num_attempts = 0 WHERE id= $exercise_id";
 		Database::query($query);
 	}
-}
-?>
+}

+ 6 - 5
main/exercice/admin.php

@@ -248,11 +248,11 @@ if (!empty($cancelQuestion)) {
 }
 
 if (!empty($clone_question) && !empty($objExercise->id)) {
-    $old_question_obj = Question::read($clone_question);
+    $old_question_obj = Question::read($clone_question, api_get_course_int_id());
     $old_question_obj->question = $old_question_obj->question.' - '.get_lang('Copy');
 
     $new_id = $old_question_obj->duplicate();
-    $new_question_obj = Question::read($new_id);
+    $new_question_obj = Question::read($new_id, api_get_course_int_id());
     $new_question_obj->addToList($exerciseId);
 
     // This should be moved to the duplicate function
@@ -329,10 +329,10 @@ function multiple_answer_true_false_onchange(variable) {
     }
     document.getElementById(weight_id).value = array_result[result];
 }
-
-
 </script>';
 
+$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/jquery.fcbkcomplete.js" type="text/javascript" language="javascript"></script>';
+$htmlHeadXtra[] = '<link href="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/style.css" rel="stylesheet" type="text/css" />';
 $htmlHeadXtra[] = "<script type=\"text/javascript\" src=\"../plugin/hotspot/JavaScriptFlashGateway.js\"></script>
 <script src=\"../plugin/hotspot/hotspot.js\" type=\"text/javascript\"></script>
 <script language=\"JavaScript\" type=\"text/javascript\">
@@ -558,6 +558,7 @@ if ($newQuestion || $editQuestion) {
         $type = Security::remove_XSS($_REQUEST['answerType']);
     }
     echo '<input type="hidden" name="Type" value="'.$type.'" />';
+    //Create/Edit question
     require 'question_admin.inc.php';
 }
 
@@ -572,7 +573,7 @@ if (isset($_GET['hotspotadmin'])) {
 }
 
 if (!$newQuestion && !$modifyQuestion && !$editQuestion && !isset($_GET['hotspotadmin'])) {
-    // question list management
+    // Question list (drag n drop view)
     if ($fastEdition) {
         require 'question_list_pagination_admin.inc.php';
     } else {

+ 135 - 61
main/exercice/answer.class.php

@@ -116,15 +116,21 @@ class Answer
         $TBL_ANSWER = Database::get_course_table(TABLE_QUIZ_ANSWER);
         $questionId = $this->questionId;
 
-        $sql = "SELECT id, id_auto, answer,correct,comment,ponderation, position, hotspot_coordinates, hotspot_type, destination  FROM
+        /*$sql = "SELECT id, id_auto, answer,correct,comment,ponderation, position, hotspot_coordinates, hotspot_type, destination  FROM
 		      $TBL_ANSWER WHERE c_id = {$this->course_id} AND question_id ='".$questionId."' ORDER BY position";
+        */
+        $sql = "SELECT iid, answer,correct,comment,ponderation, position, hotspot_coordinates, hotspot_type, destination
+                FROM $TBL_ANSWER
+                WHERE question_id ='".$questionId."' ORDER BY position";
 
         $result = Database::query($sql);
-        $i      = 1;
+        $counter = 1;
 
         // while a record is found
         while ($object = Database::fetch_object($result)) {
-            $this->id[$i]                  = $object->id;
+            $i = $object->iid;
+
+            $this->id[$i]                  = $object->iid;
             $this->answer[$i]              = $object->answer;
             $this->correct[$i]             = $object->correct;
             $this->comment[$i]             = $object->comment;
@@ -133,10 +139,10 @@ class Answer
             $this->hotspot_coordinates[$i] = $object->hotspot_coordinates;
             $this->hotspot_type[$i]        = $object->hotspot_type;
             $this->destination[$i]         = $object->destination;
-            $this->autoId[$i]              = $object->id_auto;
-            $i++;
+                //$this->autoId[$i]            = $object->id_auto;
+            $counter++;
         }
-        $this->nbrAnswers = $i - 1;
+        $this->nbrAnswers = $counter - 1;
     }
 
     /**
@@ -161,16 +167,20 @@ class Answer
         $TBL_QUIZ   = Database::get_course_table(TABLE_QUIZ_QUESTION);
         $questionId = intval($this->questionId);
 
-        $sql             = "SELECT type FROM $TBL_QUIZ WHERE c_id = {$this->course_id} AND id = $questionId";
+        //$sql             = "SELECT type FROM $TBL_QUIZ WHERE c_id = {$this->course_id} AND id = $questionId";
+        $sql = "SELECT type FROM $TBL_QUIZ WHERE c_id = {$this->course_id} AND iid = $questionId";
         $result_question = Database::query($sql);
         $question_type   = Database::fetch_array($result_question);
 
-        $sql    = "SELECT answer,correct,comment,ponderation,position, hotspot_coordinates, hotspot_type, destination, id_auto ".
+        /*$sql    = "SELECT answer,correct,comment,ponderation,position, hotspot_coordinates, hotspot_type, destination, id_auto ".
             "FROM $TBL_ANSWER WHERE c_id = {$this->course_id} AND question_id='".$questionId."'   ".
             "ORDER BY $field $order";
+        */
+        $sql = "SELECT * FROM $TBL_ANSWER
+                WHERE c_id = {$this->course_id} AND question_id = '".$questionId."'
+				ORDER BY $field $order";
         $result = Database::query($sql);
 
-        $i = 1;
         // while a record is found
         $doubt_data = null;
         while ($object = Database::fetch_object($result)) {
@@ -178,33 +188,37 @@ class Answer
                 $doubt_data = $object;
                 continue;
             }
+            $i = $object->iid;
+            $this->id[$i] = $object->iid;
+
             $this->answer[$i]      = $object->answer;
             $this->correct[$i]     = $object->correct;
             $this->comment[$i]     = $object->comment;
             $this->weighting[$i]   = $object->ponderation;
             $this->position[$i]    = $object->position;
             $this->destination[$i] = $object->destination;
-            $this->autoId[$i]      = $object->id_auto;
-            $i++;
+            //$this->autoId[$i]      = $object->id_auto;
         }
 
         if ($question_type['type'] == UNIQUE_ANSWER_NO_OPTION && !empty($doubt_data)) {
+            $i = $doubt_data->iid;
+            $this->id[$i] = $doubt_data->iid;
+
             $this->answer[$i]      = $doubt_data->answer;
             $this->correct[$i]     = $doubt_data->correct;
             $this->comment[$i]     = $doubt_data->comment;
             $this->weighting[$i]   = $doubt_data->ponderation;
             $this->position[$i]    = $doubt_data->position;
             $this->destination[$i] = $doubt_data->destination;
-            $this->autoId[$i]      = $doubt_data->id_auto;
-            $i++;
+            //$this->autoId[$i]      = $doubt_data->id_auto;            
         }
-        $this->nbrAnswers = $i - 1;
+        $this->nbrAnswers = count($this->answer);
     }
 
 
     /**
      * returns the autoincrement id identificator
-     *
+     * @deprecated Should not be used anymore
      * @author - Juan Carlos Ra�a
      * @return - integer - answer num
      */
@@ -320,7 +334,7 @@ class Answer
                 }
 
                 $list[] = array(
-                    'id'            => $i,
+                    'iid'            => $i,
                     'answer'        => $this->answer[$i],
                     'comment'       => $this->comment[$i],
                     'grade'         => $this->weighting[$i],
@@ -358,8 +372,8 @@ class Answer
     function getQuestionType()
     {
         $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
-        $sql           = "SELECT type FROM $TBL_QUESTIONS WHERE c_id = {$this->course_id} AND id = '".$this->questionId."'";
-        $res           = Database::query($sql);
+        $sql = "SELECT type FROM $TBL_QUESTIONS WHERE c_id = {$this->course_id} AND iid = '".$this->questionId."'";
+        $res = Database::query($sql);
         if (Database::num_rows($res) <= 0) {
             return null;
         }
@@ -367,7 +381,6 @@ class Answer
         return $row['type'];
     }
 
-
     /**
      * tells if answer is correct or not
      *
@@ -380,6 +393,36 @@ class Answer
         return $this->correct[$id];
     }
 
+    function getAnswerIdFromList($answer_id) {
+        $counter = 1;
+        foreach ($this->answer as $my_answer_id => $item) {
+            if ($answer_id == $my_answer_id) {
+                return $counter;
+            }
+            $counter++;
+        }
+    }
+
+    function getRealAnswerIdFromList($answer_id) {
+        $counter = 1;
+        foreach ($this->answer as $my_answer_id => $item) {
+            if ($answer_id == $counter) {
+                return $my_answer_id;
+            }
+            $counter++;
+        }
+    }
+
+    function getCorrectAnswerPosition($correct_id) {
+        $counter = 1;
+        foreach ($this->correct as $my_correct_id => $item) {
+            if ($correct_id == $my_correct_id) {
+                return $counter;
+            }
+            $counter++;
+        }
+    }
+
     /**
      * returns answer comment
      *
@@ -488,13 +531,13 @@ class Answer
         $TBL_REPONSES = Database :: get_course_table(TABLE_QUIZ_ANSWER);
 
         $questionId = $this->questionId;
-        $sql        = "UPDATE $TBL_REPONSES SET
+        $sql = "UPDATE $TBL_REPONSES SET
                 answer = '".Database::escape_string($answer)."',
 				comment = '".Database::escape_string($comment)."',
 				ponderation = '".Database::escape_string($weighting)."',
 				position = '".Database::escape_string($position)."',
 				destination = '".Database::escape_string($destination)."'
-				WHERE c_id = {$this->course_id} AND id = '".Database::escape_string($position)."'
+				WHERE c_id = {$this->course_id} AND iid = '".Database::escape_string($position)."'
 				AND question_id = '".Database::escape_string($questionId)."'";
         Database::query($sql);
     }
@@ -506,30 +549,52 @@ class Answer
      */
     function save()
     {
-        $TBL_REPONSES = Database :: get_course_table(TABLE_QUIZ_ANSWER);
-        $questionId   = intval($this->questionId);
+        $table_quiz_answer = Database :: get_course_table(TABLE_QUIZ_ANSWER);
+        $questionId = intval($this->questionId);
 
-        // removes old answers before inserting of new ones
-        $sql = "DELETE FROM $TBL_REPONSES WHERE c_id = {$this->course_id} AND question_id = '".($questionId)."'";
+        // Removes old answers before inserting of new ones
+        /*$sql = "DELETE FROM $TBL_REPONSES WHERE c_id = {$this->course_id} AND question_id = '".($questionId)."'";
+        Database::query($sql);*/
+
+        // @todo don't do this!
+        $sql = "DELETE FROM $table_quiz_answer WHERE question_id = '".($questionId)."'";
         Database::query($sql);
 
         $c_id = $this->course['real_id'];
-        // inserts new answers into data base
-        $sql = "INSERT INTO $TBL_REPONSES (c_id, id, question_id, answer, correct, comment, ponderation, position, hotspot_coordinates,hotspot_type, destination) VALUES ";
+
+        // Inserts new answers into database
+        $real_correct_ids = array();
         for ($i = 1; $i <= $this->new_nbrAnswers; $i++) {
-            $answer              = Database::escape_string($this->new_answer[$i]);
-            $correct             = Database::escape_string($this->new_correct[$i]);
-            $comment             = Database::escape_string($this->new_comment[$i]);
-            $weighting           = Database::escape_string($this->new_weighting[$i]);
-            $position            = Database::escape_string($this->new_position[$i]);
+
+            $answer = Database::escape_string($this->new_answer[$i]);
+            $correct = Database::escape_string($this->new_correct[$i]);
+            $comment = Database::escape_string($this->new_comment[$i]);
+            $weighting = Database::escape_string($this->new_weighting[$i]);
+            $position = Database::escape_string($this->new_position[$i]);
             $hotspot_coordinates = Database::escape_string($this->new_hotspot_coordinates[$i]);
-            $hotspot_type        = Database::escape_string($this->new_hotspot_type[$i]);
-            $destination         = Database::escape_string($this->new_destination[$i]);
+            $hotspot_type = Database::escape_string($this->new_hotspot_type[$i]);
+            $destination = Database::escape_string($this->new_destination[$i]);
 
-            $sql .= "($c_id, '$i','$questionId','$answer','$correct','$comment','$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination'),";
+            $sql = "INSERT INTO $table_quiz_answer (c_id, question_id, answer, correct, comment, ponderation, position, hotspot_coordinates,hotspot_type, destination) VALUES ";
+            $sql.= "($c_id, '$questionId','$answer','$correct','$comment','$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination')";
+            Database::query($sql);
+            $latest_insert_id = Database::insert_id();
+            $real_correct_ids[$i] = $latest_insert_id;
+        }
+
+        $question_info = Question::read($questionId);
+        if ($question_info->type == MATCHING) {
+
+            //Fixing real answer id
+            for ($i = 1; $i <= $this->new_nbrAnswers; $i++) {
+                if (isset($this->new_correct[$i]) && !empty($this->new_correct[$i])) {
+                    $real_correct_id = $real_correct_ids[$this->new_correct[$i]];
+                    $current_answer_id = $real_correct_ids[$i];
+                    $sql = "UPDATE $table_quiz_answer SET correct = '$real_correct_id' WHERE iid = $current_answer_id";
+                    Database::query($sql);
+                }
+            }
         }
-        $sql = api_substr($sql, 0, -1);
-        Database::query($sql);
 
         // moves $new_* arrays
         $this->answer              = $this->new_answer;
@@ -570,7 +635,6 @@ class Answer
             //Selecting origin options
             $origin_options = Question::readQuestionOption($this->selectQuestionId(), $this->course['real_id']);
 
-
             if (!empty($origin_options)) {
                 foreach ($origin_options as $item) {
                     $new_option_list[] = $item['id'];
@@ -591,41 +655,51 @@ class Answer
         // if at least one answer
         if ($this->nbrAnswers) {
             // inserts new answers into data base
-            $sql  = "INSERT INTO $TBL_REPONSES (c_id, id,question_id,answer,correct,comment, ponderation,position,hotspot_coordinates,hotspot_type,destination) VALUES";
+
             $c_id = $course_info['real_id'];
+            $correct_answers = array();
+            $new_ids = array();
 
-            for ($i = 1; $i <= $this->nbrAnswers; $i++) {
+            foreach ($this->answer as $answer_id => $answer_item) {
+                $i = $answer_id;
                 if ($this->course['id'] != $course_info['id']) {
-                    $this->answer[$i]  = DocumentManager::replace_urls_inside_content_html_from_copy_course(
-                        $this->answer[$i],
-                        $this->course['id'],
-                        $course_info['id']
-                    );
-                    $this->comment[$i] = DocumentManager::replace_urls_inside_content_html_from_copy_course(
-                        $this->comment[$i],
-                        $this->course['id'],
-                        $course_info['id']
-                    );
+                    $this->answer[$i] = DocumentManager::replace_urls_inside_content_html_from_copy_course($this->answer[$i], $this->course['id'], $course_info['id']);
+                    $this->comment[$i] = DocumentManager::replace_urls_inside_content_html_from_copy_course($this->comment[$i], $this->course['id'], $course_info['id']);
                 }
 
-                $answer  = Database::escape_string($this->answer[$i]);
+                $answer = Database::escape_string($this->answer[$i]);
                 $correct = Database::escape_string($this->correct[$i]);
 
-                if (self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE || self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE
-                ) {
+
+
+                if (self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE || self::getQuestionType() == MULTIPLE_ANSWER_TRUE_FALSE) {
                     $correct = $fixed_list[intval($correct)];
                 }
 
-                $comment             = Database::escape_string($this->comment[$i]);
-                $weighting           = Database::escape_string($this->weighting[$i]);
-                $position            = Database::escape_string($this->position[$i]);
+                $comment = Database::escape_string($this->comment[$i]);
+                $weighting = Database::escape_string($this->weighting[$i]);
+                $position = Database::escape_string($this->position[$i]);
                 $hotspot_coordinates = Database::escape_string($this->hotspot_coordinates[$i]);
-                $hotspot_type        = Database::escape_string($this->hotspot_type[$i]);
-                $destination         = Database::escape_string($this->destination[$i]);
-                $sql .= "($c_id, '$i','$newQuestionId','$answer','$correct','$comment',"."'$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination'),";
+                $hotspot_type = Database::escape_string($this->hotspot_type[$i]);
+                $destination = Database::escape_string($this->destination[$i]);
+                $sql = "INSERT INTO $TBL_REPONSES (c_id, question_id, answer, correct, comment, ponderation, position, hotspot_coordinates, hotspot_type ,destination) VALUES";
+                $sql.= "($c_id, '$newQuestionId','$answer','$correct','$comment', '$weighting','$position','$hotspot_coordinates','$hotspot_type','$destination')";
+                Database::query($sql);
+                $new_id = Database::insert_id();
+                $new_ids[$answer_id] = $new_id;
+                if ($correct) {
+                    $correct_answers[$new_id] = $correct;
+                }
+            }
+            if (self::getQuestionType() == MATCHING) {
+                if (!empty($correct_answers)) {
+                    foreach($correct_answers as $new_id => $correct_id) {
+                        $correct = $new_ids[$correct_id];
+                        $sql = "UPDATE $TBL_REPONSES SET correct = $correct WHERE iid = $new_id";
+                        Database::query($sql);
+                    }
+                }
             }
-            $sql = api_substr($sql, 0, -1);
-            Database::query($sql);
         }
     }
 

+ 71 - 140
main/exercice/exercice.php

@@ -1,7 +1,7 @@
 <?php
 /* For licensing terms, see /license.txt */
 /**
- *    Exercise list: This script shows the list of exercises for administrators and students.
+ * Exercise list: This script shows the list of exercises for administrators and students.
  * @package chamilo.exercise
  * @author Olivier Brouckaert, original author
  * @author Denes Nagy, HotPotatoes integration
@@ -178,7 +178,7 @@ $htmlHeadXtra[] = '<script>
 if ($origin != 'learnpath') {
     //so we are not in learnpath tool
     Display :: display_header($nameTools, get_lang('Exercise'));
-    if (isset ($_GET['message'])) {
+    if (isset($_GET['message'])) {
         if (in_array($_GET['message'], array('ExerciseEdited'))) {
             Display :: display_confirmation_message(get_lang($_GET['message']));
         }
@@ -194,12 +194,11 @@ Display :: display_introduction_section(TOOL_QUIZ);
 
 HotPotGCt($documentPath, 1, api_get_user_id());
 
-// only for administrator
+// Only for administrator
 
 if ($is_allowedToEdit) {
     if (!empty($choice)) {
-        // construction of Exercise
-
+        // Construction of Exercise
         $objExerciseTmp = new Exercise();
         $check = Security::check_token('get');
         $exercise_action_locked = api_resource_is_locked_by_gradebook($exerciseId, LINK_EXERCISE);
@@ -266,15 +265,19 @@ if ($is_allowedToEdit) {
                             );
                         }
                         break;
-                    case 'copy_exercise' : //copy an exercise
+                    case 'copy_exercise' :
                         $objExerciseTmp->copy_exercise();
                         Display :: display_confirmation_message(get_lang('ExerciseCopied'));
                         break;
+                    case 'set_autolaunch':
+                        $objExerciseTmp->set_autolaunch();
+                        Display :: display_confirmation_message(get_lang('ItemUpdated'));
+                        break;
                 }
             }
         }
         // destruction of Exercise
-        unset ($objExerciseTmp);
+        unset($objExerciseTmp);
         Security::clear_token();
     }
 
@@ -332,7 +335,6 @@ if ($is_allowedToEdit) {
     echo '<div class="actions">';
 }
 
-
 // Selects $limit exercises at the same time
 // maximum number of exercises on a same page
 $limit = 50;
@@ -350,16 +352,15 @@ $condition_session = api_get_session_condition($session_id, true, true);
 
 // Only for administrators
 if ($is_allowedToEdit) {
-    $total_sql = "SELECT count(id) as count FROM $TBL_EXERCICES WHERE c_id = $course_id AND active<>'-1' $condition_session ";
+    $total_sql = "SELECT count(iid) as count FROM $TBL_EXERCICES WHERE c_id = $course_id AND active<>'-1' $condition_session ";
     $sql = "SELECT * FROM $TBL_EXERCICES WHERE c_id = $course_id AND active<>'-1' $condition_session ORDER BY title LIMIT ".$from.",".$limit;
 } else {
     // Only for students
-    $total_sql = "SELECT count(id) as count FROM $TBL_EXERCICES WHERE c_id = $course_id AND active = '1' $condition_session ";
+    $total_sql = "SELECT count(iid) as count FROM $TBL_EXERCICES WHERE c_id = $course_id AND active = '1' $condition_session ";
     $sql = "SELECT * FROM $TBL_EXERCICES
             WHERE c_id = $course_id AND
                   active='1' $condition_session
             ORDER BY title LIMIT ".$from.",".$limit;
-
 }
 
 $result = Database::query($sql);
@@ -410,6 +411,11 @@ if ($is_allowedToEdit && $origin != 'learnpath') {
     echo '<a href="tests_category.php">';
     echo Display::return_icon('question_category_show.gif', get_lang('QuestionCategory'));
     echo '</a>';
+    if (api_is_platform_admin()) {
+        echo '<a href="tests_category.php?type=global">';
+        echo Display::return_icon('folder_global_category.png', get_lang('QuestionGlobalCategory'), array(), ICON_SIZE_MEDIUM);
+        echo '</a>';
+    }
     echo '<a href="question_pool.php">';
     echo Display::return_icon('database.png', get_lang('QuestionPool'), array('style' => 'width:32px'));
     echo '</a>';
@@ -476,7 +482,7 @@ $exercise_obj = new Exercise();
 $list_ordered = $exercise_obj->get_exercise_list_ordered();
 
 while ($row = Database :: fetch_array($result, 'ASSOC')) {
-    $exercise_list[$row['id']] = $row;
+    $exercise_list[$row['iid']] = $row;
 }
 
 if (isset($list_ordered) && !empty($list_ordered)) {
@@ -495,9 +501,9 @@ echo '<table class="'.Display::return_default_table_class().'">';
 if (!empty($exercise_list)) {
     if ($origin != 'learnpath') {
         //avoid sending empty parameters
-        $myorigin = (empty ($origin) ? '' : '&origin='.$origin);
-        $mylpid = (empty ($learnpath_id) ? '' : '&learnpath_id='.$learnpath_id);
-        $mylpitemid = (empty ($learnpath_item_id) ? '' : '&learnpath_item_id='.$learnpath_item_id);
+        $myorigin = (empty($origin) ? '' : '&origin='.$origin);
+        $mylpid = (empty($learnpath_id) ? '' : '&learnpath_id='.$learnpath_id);
+        $mylpitemid = (empty($learnpath_item_id) ? '' : '&learnpath_item_id='.$learnpath_item_id);
 
         $token = Security::get_token();
         $i = 1;
@@ -522,12 +528,15 @@ if (!empty($exercise_list)) {
         }
         echo Display::tag('tr', $header_list);
 
+        $autolaunch_setting_on = api_get_course_setting('enable_exercise_auto_launch') == 1;
+
         $count = 0;
         if (!empty($exercise_list)) {
             foreach ($exercise_list as $row) {
-                $my_exercise_id = $row['id'];
+                $my_exercise_id = $row['iid'];
 
                 $exercise_obj = new Exercise();
+                //$exercise_obj->setCategoriesGrouping(true);
                 $exercise_obj->read($my_exercise_id);
 
                 $locked = $exercise_obj->is_gradebook_locked;
@@ -627,8 +636,7 @@ if (!empty($exercise_list)) {
                         $class_tip = 'link_tooltip';
                     }
                     //$class_tip = 'exercise_link';
-                    $url = $move.'<a '.$alt_title.' class="'.$class_tip.'" id="tooltip_'.$row['id'].'" href="overview.php?'.api_get_cidreq(
-                    ).$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'"><img src="../img/quiz.gif" /> '.$title.' </a>';
+                    $url = $move.'<a '.$alt_title.' class="'.$class_tip.'" id="tooltip_'.$my_exercise_id.'" href="overview.php?'.api_get_cidreq().$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$my_exercise_id.'"><img src="../img/quiz.gif" /> '.$title.' </a>';
 
                     $item = Display::tag('td', $url.' '.$session_img.$lp_blocked);
 
@@ -639,132 +647,56 @@ if (!empty($exercise_list)) {
 
                     if ($session_id == $row['session_id']) {
                         //Settings
-                        $actions = Display::url(
-                            Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL),
-                            'admin.php?'.api_get_cidreq().'&exerciseId='.$row['id']
-                        );
+                        $actions = Display::url(Display::return_icon('edit.png', get_lang('Edit'), '', ICON_SIZE_SMALL), 'admin.php?'.api_get_cidreq().'&exerciseId='.$my_exercise_id);
 
                         //Exercise results
-                        $actions .= '<a href="exercise_report.php?'.api_get_cidreq(
-                        ).'&exerciseId='.$row['id'].'">'.Display :: return_icon(
-                            'test_results.png',
-                            get_lang('Results'),
-                            '',
-                            ICON_SIZE_SMALL
-                        ).'</a>';
+                        $actions .='<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$my_exercise_id.'">'.Display :: return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
 
                         //Export
-                        $actions .= Display::url(
-                            Display::return_icon('cd.gif', get_lang('CopyExercise')),
-                            '',
-                            array(
-                                'onclick' => "javascript:if(!confirm('".addslashes(
-                                    api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset)
-                                )." ".addslashes($row['title'])."?"."')) return false;",
-                                'href' => 'exercice.php?'.api_get_cidreq(
-                                ).'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id']
-                            )
-                        );
+                        $actions .= Display::url(Display::return_icon('cd.gif', get_lang('CopyExercise')), '', array('onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", 'href' => 'exercice.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$my_exercise_id));
+
+                        if ($autolaunch_setting_on) {
+                            $icon = Display::return_icon('launch.png', get_lang('AutoLaunch'));
+                            if ($row['autolaunch'] == 0) {
+                                $icon = Display::return_icon('launch_na.png', get_lang('AutoLaunch'));
+                            }
+                            $actions .= Display::url($icon, 'exercice.php?'.api_get_cidreq().'&choice=set_autolaunch&sec_token='.$token.'&exerciseId='.$my_exercise_id);
+                        }
 
                         //Clean exercise
                         if ($locked == false) {
-                            $actions .= Display::url(
-                                Display::return_icon('clean.png', get_lang('CleanStudentResults'), '', ICON_SIZE_SMALL),
-                                '',
-                                array(
-                                    'onclick' => "javascript:if(!confirm('".addslashes(
-                                        api_htmlentities(get_lang('AreYouSureToDeleteResults'), ENT_QUOTES, $charset)
-                                    )." ".addslashes($row['title'])."?"."')) return false;",
-                                    'href' => 'exercice.php?'.api_get_cidreq(
-                                    ).'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['id']
-                                )
-                            );
+                            $actions .= Display::url(Display::return_icon('clean.png', get_lang('CleanStudentResults'), '', ICON_SIZE_SMALL), '', array('onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteResults'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", 'href' => 'exercice.php?'.api_get_cidreq().'&choice=clean_results&sec_token='.$token.'&exerciseId='.$my_exercise_id));
                         } else {
-                            $actions .= Display::return_icon(
-                                'clean_na.png',
-                                get_lang('ResourceLockedByGradebook'),
-                                '',
-                                ICON_SIZE_SMALL
-                            );
+                            $actions .= Display::return_icon('clean_na.png', get_lang('ResourceLockedByGradebook'), '', ICON_SIZE_SMALL);
                         }
 
                         //Visible / invisible
                         //Check if this exercise was added in a LP
                         if ($exercise_obj->exercise_was_added_in_lp == true) {
-                            $actions .= Display::return_icon(
-                                'invisible.png',
-                                get_lang('AddedToLPCannotBeAccessed'),
-                                '',
-                                ICON_SIZE_SMALL
-                            );
+                            $actions .= Display::return_icon('invisible.png', get_lang('AddedToLPCannotBeAccessed'), '', ICON_SIZE_SMALL);
                         } else {
                             if ($row['active'] == 0 || $visibility == 0) {
-                                $actions .= Display::url(
-                                    Display::return_icon('invisible.png', get_lang('Activate'), '', ICON_SIZE_SMALL),
-                                    'exercice.php?'.api_get_cidreq(
-                                    ).'&choice=enable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
-                                );
+                                $actions .= Display::url(Display::return_icon('invisible.png', get_lang('Activate'), '', ICON_SIZE_SMALL), 'exercice.php?'.api_get_cidreq().'&choice=enable&sec_token='.$token.'&page='.$page.'&exerciseId='.$my_exercise_id);
                             } else {
                                 // else if not active
-                                $actions .= Display::url(
-                                    Display::return_icon('visible.png', get_lang('Deactivate'), '', ICON_SIZE_SMALL),
-                                    'exercice.php?'.api_get_cidreq(
-                                    ).'&choice=disable&sec_token='.$token.'&page='.$page.'&exerciseId='.$row['id']
-                                );
+                                $actions .= Display::url(Display::return_icon('visible.png', get_lang('Deactivate'), '', ICON_SIZE_SMALL), 'exercice.php?'.api_get_cidreq().'&choice=disable&sec_token='.$token.'&page='.$page.'&exerciseId='.$my_exercise_id);
                             }
                         }
                         // Export qti ...
-                        $actions .= Display::url(
-                            Display::return_icon('export_qti2.png', 'IMS/QTI', '', ICON_SIZE_SMALL),
-                            'exercice.php?choice=exportqti2&exerciseId='.$row['id']
-                        );
+                        $actions .= Display::url(Display::return_icon('export_qti2.png', 'IMS/QTI', '', ICON_SIZE_SMALL), 'exercice.php?choice=exportqti2&exerciseId='.$my_exercise_id);
                     } else {
                         // not session
-                        $actions = Display::return_icon(
-                            'edit_na.png',
-                            get_lang('ExerciseEditionNotAvailableInSession')
-                        );
-                        $actions .= '<a href="exercise_report.php?'.api_get_cidreq(
-                        ).'&exerciseId='.$row['id'].'">'.Display :: return_icon(
-                            'test_results.png',
-                            get_lang('Results'),
-                            '',
-                            ICON_SIZE_SMALL
-                        ).'</a>';
-                        $actions .= Display::url(
-                            Display::return_icon('cd.gif', get_lang('CopyExercise')),
-                            '',
-                            array(
-                                'onclick' => "javascript:if(!confirm('".addslashes(
-                                    api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset)
-                                )." ".addslashes($row['title'])."?"."')) return false;",
-                                'href' => 'exercice.php?'.api_get_cidreq(
-                                ).'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id']
-                            )
-                        );
+                        $actions = Display::return_icon('edit_na.png', get_lang('ExerciseEditionNotAvailableInSession'));
+                        $actions .='<a href="exercise_report.php?'.api_get_cidreq().'&exerciseId='.$my_exercise_id.'">'.Display :: return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).'</a>';
+                        $actions .= Display::url(Display::return_icon('cd.gif', get_lang('CopyExercise')), '', array('onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", 'href' => 'exercice.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$my_exercise_id));
                     }
 
                     //Delete
                     if ($session_id == $row['session_id']) {
                         if ($locked == false) {
-                            $actions .= Display::url(
-                                Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL),
-                                '',
-                                array(
-                                    'onclick' => "javascript:if(!confirm('".addslashes(
-                                        api_htmlentities(get_lang('AreYouSureToDelete'), ENT_QUOTES, $charset)
-                                    )." ".addslashes($row['title'])."?"."')) return false;",
-                                    'href' => 'exercice.php?'.api_get_cidreq(
-                                    ).'&choice=delete&sec_token='.$token.'&exerciseId='.$row['id']
-                                )
-                            );
+                            $actions .= Display::url(Display::return_icon('delete.png', get_lang('Delete'), '', ICON_SIZE_SMALL), '', array('onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDelete'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", 'href' => 'exercice.php?'.api_get_cidreq().'&choice=delete&sec_token='.$token.'&exerciseId='.$my_exercise_id));
                         } else {
-                            $actions .= Display::return_icon(
-                                'delete_na.png',
-                                get_lang('ResourceLockedByGradebook'),
-                                '',
-                                ICON_SIZE_SMALL
-                            );
+                            $actions .= Display::return_icon('delete_na.png', get_lang('ResourceLockedByGradebook'), '', ICON_SIZE_SMALL);
                         }
                         /*if ($rowi > 50) {
                             // Showing the direct link to exercise settings only
@@ -782,20 +714,24 @@ if (!empty($exercise_list)) {
                         if ($random_number_of_question == -1) {
                             $random_number_of_question = $rowi;
                         }
+
                         if ($row['random_by_category'] > 0) {
-                            $nbQuestionsTotal = Testcategory::getNumberOfQuestionRandomByCategory(
-                                $my_exercise_id,
-                                $random_number_of_question
-                            );
-                            $number_of_questions = $nbQuestionsTotal." ";
-                            $number_of_questions .= ($nbQuestionsTotal > 1) ? get_lang("QuestionsLowerCase") : get_lang(
-                                "QuestionLowerCase"
-                            );
+                            $nbQuestionsTotal = $exercise_obj->getNumberQuestionExerciseCategory();
+                            $real_question_count = count($exercise_obj->selectQuestionList());
+                            $warning = null;
+                            if ($nbQuestionsTotal != $real_question_count) {
+                                $warning = Display::return_icon('warning.png', get_lang('CheckThatYouHaveEnoughQuestionsInYourCategories'));
+                            }
+
+                            //$nbQuestionsTotal = Testcategory::getNumberOfQuestionRandomByCategory($my_exercise_id, $random_number_of_question);
+
+                            $number_of_questions = $nbQuestionsTotal." ".$warning;
+                            //$number_of_questions .= ($nbQuestionsTotal > 1) ? get_lang("QuestionsLowerCase") : get_lang("QuestionLowerCase");
+                            /*
                             $number_of_questions .= " - ";
-                            $number_of_questions .= min(
-                                Testcategory::getNumberMaxQuestionByCat($my_exercise_id),
-                                $random_number_of_question
-                            ).' '.get_lang('QuestionByCategory');
+                            $number_of_questions .= min(Testcategory::getNumberMaxQuestionByCat($my_exercise_id), $random_number_of_question).' '.get_lang('QuestionByCategory');
+                             *
+                             */
                         } else {
                             $random_label = ' ('.get_lang('Random').') ';
                             $number_of_questions = $random_number_of_question.' '.$random_label;
@@ -809,8 +745,7 @@ if (!empty($exercise_list)) {
                     }
 
                     //Attempts
-                    //$attempts = get_count_exam_results($row['id']).' '.get_lang('Attempts');
-
+                    //$attempts = get_count_exam_results($my_exercise_id).' '.get_lang('Attempts');
                     //$item .=  Display::tag('td',$attempts);
                     $item .= Display::tag('td', $number_of_questions);
 
@@ -827,21 +762,19 @@ if (!empty($exercise_list)) {
 
                     if ($time_limits) {
                         if ($is_actived_time) {
-                            $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq(
-                            ).$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">'.$cut_title.'</a>';
+                            $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq().$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$my_exercise_id.'">'.$cut_title.'</a>';
                         } else {
                             $url = $row['title'];
                         }
                     } else {
-                        $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq(
-                        ).$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$row['id'].'">'.$cut_title.'</a>';
+                        $url = '<a '.$alt_title.'  href="overview.php?'.api_get_cidreq().$myorigin.$mylpid.$mylpitemid.'&exerciseId='.$my_exercise_id.'">'.$cut_title.'</a>';
                     }
 
                     //Link of the exercise
                     $item = Display::tag('td', $url.' '.$session_img);
 
                     //count number exercise questions
-                    $sqlquery = "SELECT count(*) FROM $TBL_EXERCICE_QUESTION WHERE c_id = $course_id AND exercice_id = ".$row['id'];
+                    $sqlquery = "SELECT count(*) FROM $TBL_EXERCICE_QUESTION WHERE c_id = $course_id AND exercice_id = ".$my_exercise_id;
                     $sqlresult = Database::query($sqlquery);
                     $rowi = Database::result($sqlresult, 0);
 
@@ -855,7 +788,7 @@ if (!empty($exercise_list)) {
                     //This query might be improved later on by ordering by the new "tms" field rather than by exe_id
                     //Don't remove this marker: note-query-exe-results
                     $qry = "SELECT * FROM $TBL_TRACK_EXERCICES
-                        WHERE   exe_exo_id      = ".$row['id']." AND
+                        WHERE   exe_exo_id      = ".$my_exercise_id." AND
                                 exe_user_id     = ".api_get_user_id()." AND
                                 c_id    = '".api_get_course_int_id()."' AND
                                 status          <> 'incomplete' AND
@@ -944,9 +877,6 @@ if (!empty($exercise_list)) {
                         $num = '<span class="tooltip" style="display: none;">'.$num.'</span>';
                     }
                     $item .= Display::tag('td', $attempt_text);
-
-                    //See results
-                    //$actions = '<a class="'.$class_tip.'" id="tooltip_'.$row['id'].'" href="exercise_report.php?' . api_get_cidreq() . '&exerciseId='.$row['id'].'">'.$num.Display::return_icon('test_results.png', get_lang('Results'),'',ICON_SIZE_SMALL).' </a>';
                 }
                 $class = 'row_even';
                 if ($count % 2) {
@@ -1065,7 +995,8 @@ if (isset($attribute['path']) && is_array($attribute['path'])) {
             //$actions .='<img src="../img/lp_quiz_na.gif" border="0" title="'.get_lang('NotMarkActivity').'" alt="" />';
             $item .= Display::tag('td', $actions);
             echo Display::tag('tr', $item, array('class' => $class));
-        } else { // student only
+        } else {
+            // Student only
             if ($active == 1) {
                 $nbrActiveTests = $nbrActiveTests + 1;
                 $item .= Display::tag(

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 356 - 184
main/exercice/exercise.class.php


+ 41 - 28
main/exercice/exercise.lib.php

@@ -23,20 +23,19 @@
  * @param int   current item from the list of questions
  * @param int   number of total questions
  * */
-function showQuestion($questionId, $only_questions = false, $origin = false, $current_item = '', $show_title = true, $freeze = false, $user_choice = array(), $show_comment = false, $exercise_feedback = null, $show_answers = false)
-{
-
+function showQuestion($objQuestionTmp, $only_questions = false, $origin = false, $current_item = '', $show_title = true, $freeze = false, $user_choice = array(), $show_comment = false, $exercise_feedback = null, $show_answers = false) {
     // Text direction for the current language
-    $is_ltr_text_direction = api_get_text_direction() != 'rtl';
-
+    //$is_ltr_text_direction = api_get_text_direction() != 'rtl';
     // Change false to true in the following line to enable answer hinting
     $debug_mark_answer = $show_answers; //api_is_allowed_to_edit() && false;
     // Reads question information
-    if (!$objQuestionTmp = Question::read($questionId)) {
+    if (!$objQuestionTmp) {
         // Question not found
         return false;
     }
 
+    $questionId = $objQuestionTmp->id;
+
     if ($exercise_feedback != EXERCISE_FEEDBACK_TYPE_END) {
         $show_comment = false;
     }
@@ -91,23 +90,25 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
                 $s .= '<table class="data_table">';
             }
 
-            $x = 1; //iterate through answers
+            $j = 1; //iterate through answers
             $letter = 'A'; //mark letters for each answer
-            $answer_matching = $cpt1 = array();
-
-            for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
+            $answer_matching = array();
+            $capital_letter = array();
+            //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
+            foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
                 $answerCorrect = $objAnswerTmp->isCorrect($answerId);
-                $numAnswer = $objAnswerTmp->selectAutoId($answerId);
                 $answer = $objAnswerTmp->selectAnswer($answerId);
                 if ($answerCorrect == 0) {
                     // options (A, B, C, ...) that will be put into the list-box
                     // have the "correct" field set to 0 because they are answer
-                    $cpt1[$x] = $letter;
-                    $answer_matching[$x] = $objAnswerTmp->selectAnswerByAutoId($numAnswer);
-                    $x++;
+                    $capital_letter[$j] = $letter;
+                    //$answer_matching[$j]=$objAnswerTmp->selectAnswerByAutoId($numAnswer);
+                    $answer_matching[$j] = array('id' => $answerId, 'answer' => $answer);
+                    $j++;
                     $letter++;
                 }
             }
+
             $i = 1;
 
             $select_items[0]['id'] = 0;
@@ -116,11 +117,11 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
 
             foreach ($answer_matching as $id => $value) {
                 $select_items[$i]['id'] = $value['id'];
-                $select_items[$i]['letter'] = $cpt1[$id];
+                $select_items[$i]['letter'] = $capital_letter[$id];
                 $select_items[$i]['answer'] = $value['answer'];
                 $i++;
             }
-            $num_suggestions = ($nbrAnswers - $x) + 1;
+            $num_suggestions = ($nbrAnswers - $j) + 1;
         } elseif ($answerType == FREE_ANSWER) {
             $fck_content = isset($user_choice[0]) && !empty($user_choice[0]['answer']) ? $user_choice[0]['answer'] : null;
 
@@ -134,8 +135,9 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
         } elseif ($answerType == ORAL_EXPRESSION) {
             //Add nanog
             if (api_get_setting('enable_nanogong') == 'true') {
+
                 //@todo pass this as a parameter
-                global $exercise_stat_info, $exerciseId, $exe_id;
+                global $exercise_stat_info, $exerciseId;
 
                 if (!empty($exercise_stat_info)) {
                     $params = array(
@@ -199,12 +201,16 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
             }
         }
 
-        for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) {
+        //for ($answerId=1; $answerId <= $nbrAnswers; $answerId++) {
+
+        foreach ($objAnswerTmp->answer as $answerId => $answer_item) {
             $answer = $objAnswerTmp->selectAnswer($answerId);
             $answerCorrect = $objAnswerTmp->isCorrect($answerId);
-            $numAnswer = $objAnswerTmp->selectAutoId($answerId);
             $comment = $objAnswerTmp->selectComment($answerId);
 
+            //$numAnswer       = $objAnswerTmp->selectAutoId($answerId);
+            $numAnswer = $answerId;
+
             $attributes = array();
             // Unique answer
             if (in_array($answerType, array(UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION))) {
@@ -252,8 +258,7 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
                 } else {
                     $s .= $answer_input;
                 }
-
-            } elseif ($answerType == MULTIPLE_ANSWER || $answerType == MULTIPLE_ANSWER_TRUE_FALSE || $answerType == GLOBAL_MULTIPLE_ANSWER) {
+            } elseif (in_array($answerType, array(MULTIPLE_ANSWER, MULTIPLE_ANSWER_TRUE_FALSE, GLOBAL_MULTIPLE_ANSWER))) {
                 $input_id = 'choice-'.$questionId.'-'.$answerId;
                 $answer = Security::remove_XSS($answer, STUDENT);
 
@@ -304,7 +309,7 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
 
                     if (!empty($quiz_question_options)) {
                         foreach ($quiz_question_options as $id => $item) {
-
+                            $id = $item['iid'];
                             if (isset($my_choice[$numAnswer]) && $id == $my_choice[$numAnswer]) {
                                 $attributes = array('checked' => 1, 'selected' => 1);
                             } else {
@@ -329,6 +334,7 @@ function showQuestion($questionId, $only_questions = false, $origin = false, $cu
                     $s.='</tr>';
                 }
             } elseif ($answerType == MULTIPLE_ANSWER_COMBINATION) {
+
                 // multiple answers
                 $input_id = 'choice-'.$questionId.'-'.$answerId;
 
@@ -871,7 +877,7 @@ function get_exercise_track_exercise_info($exe_id)
     $exe_id = intval($exe_id);
     $result = array();
     if (!empty($exe_id)) {
-        $sql_fb_type = "SELECT q.*, tee.*
+        $sql = "SELECT q.*, tee.*
 	                    FROM $TBL_EXERCICES as q
 	                    INNER JOIN $TBL_TRACK_EXERCICES as tee
 	                    ON q.id=tee.exe_exo_id
@@ -880,10 +886,10 @@ function get_exercise_track_exercise_info($exe_id)
 	                    WHERE tee.exe_id = $exe_id
 	                    AND q.c_id=c.id";
 
-        $res_fb_type = Database::query($sql_fb_type);
-        $result = Database::fetch_array($res_fb_type, 'ASSOC');
+        $result = Database::query($sql);
+        $result_array = Database::fetch_array($result, 'ASSOC');
     }
-    return $result;
+    return $result_array;
 }
 
 /**
@@ -2323,11 +2329,18 @@ function display_question_list_by_attempt($objExercise, $exe_id, $save_user_resu
         if ($debug) {
             error_log('Looping question_list '.print_r($question_list, 1));
         }
+
+        $media_questions = $objExercise->get_media_list();
+        $media_is_activated = $objExercise->media_is_activated($media_questions);
+
+        $medias_showed = array();
+
         foreach ($question_list as $questionId) {
 
             // creates a temporary Question object
-            $objQuestionTmp = Question :: read($questionId);
+            $objQuestionTmp = Question::read($questionId);
 
+            //this variable commes from exercise_submit_modal.php
             ob_start();
             $hotspot_delineation_result = null;
 
@@ -2454,7 +2467,7 @@ function display_question_list_by_attempt($objExercise, $exe_id, $save_user_resu
         $learnpath_item_view_id = $exercise_stat_info['orig_lp_item_view_id'];
 
         if (api_is_allowed_to_session_edit()) {
-            update_event_exercise($exercise_stat_info['exe_id'], $objExercise->selectId(), $total_score, $total_weight, api_get_session_id(), $learnpath_id, $learnpath_item_id, $learnpath_item_view_id, $exercise_stat_info['exe_duration']);
+            update_event_exercise($exercise_stat_info['exe_id'], $objExercise->selectId(), $total_score, $total_weight, api_get_session_id(), $learnpath_id, $learnpath_item_id, $learnpath_item_view_id, $exercise_stat_info['exe_duration'], '', array(), $end_date);
         }
 
         // Send notification ..

+ 62 - 9
main/exercice/exercise_admin.php

@@ -27,16 +27,67 @@ if (!api_is_allowed_to_edit(null,true)) {
 	api_not_allowed(true);
 }
 
+$url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?1=1';
+
+$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/jquery.fcbkcomplete.js" type="text/javascript" language="javascript"></script>';
+$htmlHeadXtra[] = '<link href="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/style.css" rel="stylesheet" type="text/css" />';
+
 $htmlHeadXtra[] = '<script>
-		function advanced_parameters() {
-			if(document.getElementById(\'options\').style.display == \'none\') {
-				document.getElementById(\'options\').style.display = \'block\';
-				document.getElementById(\'img_plus_and_minus\').innerHTML=\' <img style="vertical-align:middle;" src="../img/div_hide.gif" alt="" /> '.addslashes(api_htmlentities(get_lang('AdvancedParameters'))).'\';
-			} else {
-				document.getElementById(\'options\').style.display = \'none\';
-				document.getElementById(\'img_plus_and_minus\').innerHTML=\' <img style="vertical-align:middle;" src="../img/div_show.gif" alt="" /> '.addslashes(api_htmlentities(get_lang('AdvancedParameters'))).'\';
-			}
+    function check() {
+        $("#category_id option:selected").each(function() {
+            var id = $(this).val();
+            var name = $(this).text();
+            if (id != "" ) {
+                $.ajax({
+                    async: false,
+                    url: "'.$url.'&a=exercise_category_exists",
+                    data: "id="+id,
+                    success: function(return_value) {
+                        if (return_value == 0 ) {
+                            alert("'.get_lang('CategoryDoesNotExists').'");
+                            //Deleting select option tag
+                            $("#category_id").find("option").remove();
+
+                            $(".holder li").each(function () {
+                                if ($(this).attr("rel") == id) {
+                                    $(this).remove();
+                                }
+                            });
+                        } else {
+                            option_text = $("#category_id").find("option").text();
+                            $("#category_inputs").append( option_text + "<input type=text>");
+                        }
+                    },
+                });
+            }
+        });
+    }
+
+    $(function() {
+        $("#category_id").fcbkcomplete({
+            json_url: "'.$url.'&a=search_category_parent",
+            maxitems: 20,
+            addontab: false,
+            input_min_size: 1,
+            cache: false,
+            complete_text:"'.get_lang('StartToType').'",
+            firstselected: false,
+            onselect: check,
+            oncreate: add_item,
+            filter_selected: true,
+            newel: true
+        });
+    });
+
+	function advanced_parameters() {
+		if(document.getElementById(\'options\').style.display == \'none\') {
+			document.getElementById(\'options\').style.display = \'block\';
+			document.getElementById(\'img_plus_and_minus\').innerHTML=\' <img style="vertical-align:middle;" src="../img/div_hide.gif" alt="" /> '.addslashes(api_htmlentities(get_lang('AdvancedParameters'))).'\';
+		} else {
+			document.getElementById(\'options\').style.display = \'none\';
+			document.getElementById(\'img_plus_and_minus\').innerHTML=\' <img style="vertical-align:middle;" src="../img/div_show.gif" alt="" /> '.addslashes(api_htmlentities(get_lang('AdvancedParameters'))).'\';
 		}
+	}
 
 		function FCKeditor_OnComplete( editorInstance ) {
 			   if (document.getElementById ( \'HiddenFCK\' + editorInstance.Name )) {
@@ -147,6 +198,8 @@ window.onload=advanced_parameters;
 // INIT EXERCISE
 
 $objExercise = new Exercise();
+$objExercise->setCategoriesGrouping(false);
+
 $course_id = api_get_course_int_id();
 
 //INIT FORM
@@ -221,4 +274,4 @@ if ($form->validate()) {
     }
 	$form->display();
 }
-Display::display_footer();
+Display::display_footer();

+ 75 - 72
main/exercice/exercise_reminder.php

@@ -17,13 +17,13 @@ $language_file = 'exercice';
 require_once '../inc/global.inc.php';
 require_once 'exercise.lib.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';
+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';
 }
 
 $this_section = SECTION_COURSES;
@@ -69,7 +69,6 @@ if ($time_control) {
 	$htmlHeadXtra[] = $objExercise->show_time_control_js($time_left);
 }
 
-
 if (isset($_SESSION['exe_id'])) {
 	$exe_id = intval($_SESSION['exe_id']);
 }
@@ -107,7 +106,7 @@ if ($time_control) {
     echo $objExercise->return_time_left_div();
 }
 
-echo Display::div('', array('id'=>'message'));
+echo Display::div('', array('id' => 'message'));
 
 echo '<script>
 		lp_data = $.param({"learnpath_id": '.$learnpath_id.', "learnpath_item_id" : '.$learnpath_item_id.', "learnpath_item_view_id": '.$learnpath_item_view_id.'});
@@ -149,88 +148,92 @@ echo '<script>
 		}
 </script>';
 
-$attempt_list       = get_all_exercise_event_by_exe_id($exe_id);
+$exercise_result = get_answered_questions_from_attempt($exe_id);
 
-$remind_list        = $exercise_stat_info['questions_to_check'];
-$remind_list        = explode(',', $remind_list);
+$remind_list = $exercise_stat_info['questions_to_check'];
+$remind_list = explode(',', $remind_list);
 
-$exercise_result    = array();
 
-foreach ($attempt_list as $question_id => $options) {
-	//echo $question_id.'<br />';
-	foreach($options as $item) {
+echo Display::label(get_lang('QuestionWithNoAnswer'), 'warning');
+echo '<div class="clear"></div><br />';
 
-		$question_obj = Question::read($item['question_id']);
+$table = '<div class="row">';
 
-		switch($question_obj->type) {
-			case FILL_IN_BLANKS:
-				$item['answer'] = $objExercise->fill_in_blank_answer_to_string($item['answer']);
-				break;
-			case HOT_SPOT:
+$counter = 0;
+$split_by = 25;
 
-				break;
-		}
+//$count_cols = round(count($question_list)/$split_by);
+$count_cols = 3;
+$span_size = 12/$count_cols;
+$span_class = "span$span_size";
 
-		if ($item['answer'] != '0' && !empty($item['answer'])) {
-    		$exercise_result[] = $question_id;
-    		break;
-		}
-	}
-}
-echo Display::label(get_lang('QuestionWithNoAnswer'), 'warning');
-echo '<div class="clear"></div><br />';
+$table .= '<div class="'.$span_class.'">';
 
-$table = '';
-$counter = 0;
-// Loop over all question to show results for each of them, one by one
+$cols = 1;
 
+// Loop over all question to show results for each of them, one by one
 foreach ($question_list as $questionId) {
-    // destruction of the Question object
-	unset($objQuestionTmp);
-
-	// creates a temporary Question object
-	$objQuestionTmp        = Question :: read($questionId);
-	// initialize question information
-
-	$quesId         = $objQuestionTmp->selectId();
-	$check_id 		= 'remind_list['.$questionId.']';
-	$attributes     = array('id'=>$check_id, 'onclick'=>"save_remind_item(this, '$questionId');");
-
-	if (in_array($questionId, $remind_list)) {
-	    $attributes['checked'] = 1;
-	}
-	$label_attributes = array();
-	$label_attributes['class'] = 'checkbox';
-	$label_attributes['for'] = $check_id;
+
+    // creates a temporary Question object
+    $objQuestionTmp = Question::read($questionId);
+    // initialize question information
+    $check_id = 'remind_list['.$questionId.']';
+    $attributes = array('id' => $check_id, 'onclick' => "save_remind_item(this, '$questionId');");
+
+    if (in_array($questionId, $remind_list)) {
+        $attributes['checked'] = 1;
+    }
+    $label_attributes = array();
+    $label_attributes['class'] = 'checkbox';
+    $label_attributes['for'] = $check_id;
     $label_attributes['class'] = "checkbox";
 
-	$checkbox          = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes);
-	$url               = 'exercise_submit.php?exerciseId='.$objExercise->id.'&num='.$counter.'&reminder=1';
+    if (in_array($objQuestionTmp->type, Question::question_type_no_review())) {
+        $attributes['disabled'] = 'disabled';
+    }
+
+    $checkbox = Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes);
+    $url = 'exercise_submit.php?exerciseId='.$objExercise->id.'&num='.$counter.'&reminder=1';
 
-	$counter++;
-	if ($objExercise->type == ONE_PER_PAGE) {
-	    $question_title = Display::url($counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40), $url);
-	    $question_title = $counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40);
-	} else {
-	    $question_title = $counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40);
-	}
+    $counter++;
+    if ($objExercise->type == ONE_PER_PAGE) {
+        $question_title = Display::url($counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40), $url);
+        $question_title = $counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40);
+    } else {
+        $question_title = $counter.'. '.Text::cut($objQuestionTmp->selectTitle(), 40);
+    }
     //Check if the question doesn't have an answer
     if (!in_array($questionId, $exercise_result)) {
         $question_title = Display::label($question_title, 'warning');
     }
-	$question_title    = Display::tag('label', $checkbox.$question_title, $label_attributes);
-	$table            .= Display::div($question_title, array('class'=>'exercise_reminder_item'));
-} // end foreach() block that loops over all questions
+    $question_title = Display::tag('label', $checkbox.$question_title, $label_attributes);
+    $table .= Display::div($question_title, array('class' => 'exercise_reminder_item'));
+
+    if (($counter % $split_by) == 0) {
+        if ($counter > 1 ) {
+            $table .= '</div>';
+            if ($cols % $count_cols == 0) {
+                $table .= '</div>';
+                $table .= '<hr>';
+                $table .= '<div class="row">';
+            }
+            $cols++;
+        }
+        $table .= '<div class="'.$span_class.'">';
+    }
+}
+$table .= "</div>";
+$table .= "</div>";
 
-echo Display::div($table, array('class'=>'span10'));
+echo Display::div($table, array('class' => 'span12'));
 
-$exercise_actions = Display::url(get_lang('EndTest'), 'javascript://', array('onclick'=>'final_submit();', 'class'=>'btn btn-warning'));
-$exercise_actions .=  '&nbsp;'.Display::url(get_lang('ReviewQuestions'), 'javascript://', array('onclick'=>'review_questions();','class'=>'btn btn-success'));
+$exercise_actions = Display::url(get_lang('EndTest'), 'javascript://', array('onclick' => 'final_submit();', 'class' => 'btn btn-warning'));
+$exercise_actions .= '&nbsp;'.Display::url(get_lang('ReviewQuestions'), 'javascript://', array('onclick' => 'review_questions();', 'class' => 'btn btn-success'));
 
-echo Display::div(' ', array('class'=>'clear'));
-echo Display::div($exercise_actions, array('class'=>'form-actions'));
+echo Display::div('', array('class' => 'clear'));
+echo Display::div($exercise_actions, array('class' => 'form-actions'));
 
 if ($origin != 'learnpath') {
-	//we are not in learnpath tool
-	Display::display_footer();
-}
+    //we are not in learnpath tool
+    Display::display_footer();
+}

+ 12 - 13
main/exercice/exercise_report.php

@@ -497,17 +497,16 @@ $extra_params['height'] = 'auto';
 <script>
 
     function setSearchSelect(columnName) {
-        $("#results").jqGrid('setColProp', columnName,
-                {
-                    searchoptions:{
-                        dataInit:function (el) {
-                            $("option[value='1']", el).attr("selected", "selected");
-                            setTimeout(function () {
-                                $(el).trigger('change');
-                            }, 1000);
-                        }
-                    }
-                });
+        $("#results").jqGrid('setColProp', columnName, {
+            searchoptions:{
+                dataInit:function (el) {
+                    $("option[value='1']", el).attr("selected", "selected");
+                    setTimeout(function () {
+                        $(el).trigger('change');
+                    }, 1000);
+                }
+            }
+        });
     }
 
     function exportExcel() {
@@ -575,8 +574,8 @@ jQuery("#results").jqGrid('navButtonAdd','#results_pager',{
 
         //Adding search options
         var options = {
-            'stringResult':true,
-            'autosearch':true,
+            'stringResult': true,
+            'autosearch' : true,
             'searchOnEnter':false
         }
         jQuery("#results").jqGrid('filterToolbar', options);

+ 3 - 4
main/exercice/exercise_result.class.php

@@ -11,7 +11,6 @@
 /**
  * Code
  */
-if(!class_exists('ExerciseResult')):
 /**
  * Exercise results class
  * @package chamilo.exercise
@@ -439,7 +438,8 @@ class ExerciseResult
 		foreach ($this->results as $row) {
 			$column = 0;
 
-            if ($with_column_user) {
+            if ($with_column_user) {               
+
                 if (api_is_western_name_order()) {
                     $worksheet->write($line,$column,api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset));
                     $column++;
@@ -486,5 +486,4 @@ class ExerciseResult
 		$workbook->close();
 		return true;
 	}
-}
-endif;
+}

+ 1 - 0
main/exercice/exercise_show.php

@@ -333,6 +333,7 @@ foreach ($questionList as $questionId) {
         $questionScore   = $question_result['score'];
         $totalScore     += $question_result['score'];
 	} elseif ($answerType == HOT_SPOT) {
+        //@todo remove this HTML and move in the function
 	    if ($show_results) {
 		    echo '<table width="500" border="0"><tr>
                     <td valign="top" align="center" style="padding-left:0px;" >

+ 63 - 59
main/exercice/exercise_submit.php

@@ -448,7 +448,7 @@ if (!is_object($objExercise)) {
     exit;
 }
 
-//if reminder ends we jump to the exercise_reminder
+// If reminder ends we jump to the exercise_reminder
 if ($objExercise->review_answers) {
 	if ($remind_question_id == -1) {
 		header('Location: exercise_reminder.php?origin='.$origin.'&exerciseId='.$exerciseId);
@@ -466,6 +466,7 @@ if ($objExercise->expired_time != 0) {
 
 //Generating the time control key for the user
 $current_expired_time_key = get_time_control_key($objExercise->id, $learnpath_id, $learnpath_item_id);
+if ($debug)  error_log("4. current_expired_time_key: $current_expired_time_key ");
 
 $_SESSION['duration_time'][$current_expired_time_key] = $current_timestamp;
 
@@ -477,7 +478,7 @@ if ($time_control) {
 $show_clock = true;
 $user_id = api_get_user_id();
 if ($objExercise->selectAttempts() > 0) {
-	$attempt_html = '';
+	$attempt_html = null;
 	$attempt_count = get_attempt_count($user_id, $exerciseId, $learnpath_id, $learnpath_item_id, $learnpath_item_view_id);
 
 	if ($attempt_count >= $objExercise->selectAttempts()) {
@@ -516,7 +517,6 @@ if ($objExercise->selectAttempts() > 0) {
 			}
 		} else {
 			$attempt_html .= Display :: return_message(sprintf(get_lang('ReachedMaxAttempts'), $exercise_title, $objExercise->selectAttempts()), 'warning', false);
-			//Display :: display_warning_message(sprintf(get_lang('ReachedMaxAttemptsAdmin'), $exercise_title, $objExercise->selectAttempts()), false);
 		}
 
 		if ($origin == 'learnpath') {
@@ -532,21 +532,42 @@ if ($objExercise->selectAttempts() > 0) {
 	}
 }
 
+//5. Getting user exercise info (if the user took the exam before) - generating exe_id
+$exercise_stat_info = $objExercise->get_stat_track_exercise_info($learnpath_id, $learnpath_item_id, $learnpath_item_view_id);
 
-if ($debug) { error_log("4. Setting the exe_id: $exe_id");} ;
+//Media questions
+$media_questions = $objExercise->get_media_list();
+$media_is_activated = $objExercise->media_is_activated($media_questions);
 
-//5. Getting user exercise info (if the user took the exam before) - generating exe_id
+//In LP's is enabled the "remember question" feature?
 
-$exercise_stat_info = $objExercise->get_stat_track_exercise_info($learnpath_id, $learnpath_item_id, $learnpath_item_view_id);
+if (!isset($_SESSION['questionList'])) {
+    // Selects the list of question ID
+    $questionList = $objExercise->get_question_list(false);
+
+    //Getting order from random
+    if ($media_is_activated == false && $objExercise->isRandom() && isset($exercise_stat_info) && !empty($exercise_stat_info['data_tracking'])) {
+    	$questionList = explode(',', $exercise_stat_info['data_tracking']);
+    }
+    Session::write('questionList', $questionList);
+    if ($debug > 0) { error_log('$_SESSION[questionList] was set'); }
+} else {
+	if (isset($objExercise) && isset($_SESSION['objExercise'])) {
+        $questionList = Session::read('questionList');
+	}
+}
+
+//Fix in order to get the correct question list
+$question_list_flatten = $objExercise->transform_question_list_with_medias($questionList, true);
+Session::write('question_list_flatten', $question_list_flatten);
 
 $clock_expired_time = null;
 if (empty($exercise_stat_info)) {
-    if ($debug)  error_log("learnpath_id: $learnpath_id, learnpath_item_id: $learnpath_item_id, learnpath_item_view_id: $learnpath_item_view_id");
     if ($debug)  error_log('5  $exercise_stat_info is empty ');
 	$total_weight = 0;
-	$questionList = $objExercise->get_question_list(true);
+	//$questionList = $objExercise->get_question_list(true);
 
-	foreach ($questionList as $question_id) {
+	foreach ($question_list_flatten as $question_id) {
 		$objQuestionTmp = Question::read($question_id);
 		$total_weight += floatval($objQuestionTmp->weighting);
 	}
@@ -564,12 +585,12 @@ if (empty($exercise_stat_info)) {
 		$_SESSION['expired_time'][$current_expired_time_key] 	 = $clock_expired_time;
 		if ($debug) { error_log('5.4. Setting the $_SESSION[expired_time]: '.$_SESSION['expired_time'][$current_expired_time_key] ); };
 	}
-	$exe_id = $objExercise->save_stat_track_exercise_info($clock_expired_time, $learnpath_id, $learnpath_item_id, $learnpath_item_view_id, $questionList, $total_weight);
+	$exe_id = $objExercise->save_stat_track_exercise_info($clock_expired_time, $learnpath_id, $learnpath_item_id, $learnpath_item_view_id, $question_list_flatten, $total_weight);
 	$exercise_stat_info = $objExercise->get_stat_track_exercise_info($learnpath_id, $learnpath_item_id, $learnpath_item_view_id);
-    if ($debug)  error_log("5.5  exercise_stat_info[] created. exe_id: $exe_id");
+    if ($debug)  error_log("5.5  Creating a new attempt exercise_stat_info[] exe_id : $exe_id");
 } else {
 	$exe_id = $exercise_stat_info['exe_id'];
-    if ($debug)  error_log("5  exercise_stat_info[] exists getting exe_id $exe_id ");
+    if ($debug)  error_log("5  exercise_stat_info[] exists getting exe_id: $exe_id ");
 }
 
 //Array to check in order to block the chat
@@ -582,9 +603,10 @@ if (!empty($exercise_stat_info['questions_to_check'])) {
 	$my_remind_list = explode(',', $my_remind_list);
 	$my_remind_list = array_filter($my_remind_list);
 }
+if ($debug) { error_log("6.0 my_remind_list array: ".print_r($my_remind_list, 1)); }
 
 $params = "exe_id=$exe_id&exerciseId=$exerciseId&origin=$origin&learnpath_id=$learnpath_id&learnpath_item_id=$learnpath_item_id&learnpath_item_view_id=$learnpath_item_view_id&".api_get_cidreq();
-if ($debug) { error_log("6.1 params: ->  $params"); };
+if ($debug) { error_log("6.1 params: $params"); };
 
 if ($reminder == 2 && empty($my_remind_list)) {
     if ($debug) { error_log("6.2 calling the exercise_reminder.php "); };
@@ -634,10 +656,7 @@ if ($time_control) {
 
 			// First we update the attempt to today
 			// How the expired time is changed into "track_e_exercices" table,then the last attempt for this student should be changed too,so
-	        $sql_track_e_exe = "UPDATE $exercice_attemp_table SET tms = '".api_get_utc_datetime()."'
-	                            WHERE exe_id = '".$exercise_stat_info['exe_id']."' AND tms = '".$last_attempt_date."' ";
-	        if ($debug) {error_log('7.10. $sql_track_e_exe2: '.$sql_track_e_exe); }
-	        Database::query($sql_track_e_exe);
+            update_attempt_date($exercise_stat_info['exe_id'], $last_attempt_date);
 
 	        //Sessions  that contain the expired time
 	        $_SESSION['expired_time'][$current_expired_time_key] 		= $clock_expired_time;
@@ -647,7 +666,7 @@ if ($time_control) {
         $clock_expired_time =  $_SESSION['expired_time'][$current_expired_time_key];
     }
 } else {
-    if ($debug) { error_log("7 No time control"); };
+    if ($debug) { error_log("7. No time control"); };
 }
 
 // Get time left for exipiring time
@@ -661,40 +680,16 @@ if ($time_control) { //Sends the exercice form when the expired time is finished
 	$htmlHeadXtra[] = $objExercise->show_time_control_js($time_left);
 }
 
-// if the user has submitted the form
-
 $exercise_title	= $objExercise->selectTitle();
 $exercise_sound = $objExercise->selectSound();
 
-//Media questions
-$media_questions = $objExercise->get_media_list();
-$media_is_activated = $objExercise->media_is_activated($media_questions);
-
-
-//in LP's is enabled the "remember question" feature?
-
-if (!isset($_SESSION['questionList'])) {
-    // selects the list of question ID
-    $questionList = $objExercise->get_question_list(false);
-    //Getting order from random
-    if ($media_is_activated == false && $objExercise->isRandom() && !empty($exercise_stat_info['data_tracking'])) {
-    	$questionList = explode(',', $exercise_stat_info['data_tracking']);
-    }
-    Session::write('questionList', $questionList);
-    if ($debug > 0) { error_log('$_SESSION[questionList] was set'); }
-} else {
-	if (isset($objExercise) && isset($_SESSION['objExercise'])) {
-        $questionList = Session::read('questionList');
-	}
-}
-
 if ($debug) error_log('8. Question list loaded '.print_r($questionList, 1));
-if ($debug) error_log('8.1 Media list loaded '.print_r($media_questions, 1));
+if ($debug) error_log('8.1. Media list loaded '.print_r($media_questions, 1));
 
 $question_count = $objExercise->get_count_question_list();
 
 if ($formSent && isset($_POST)) {
-    if ($debug) { error_log('9. $formSent was set'); }
+    if ($debug) { error_log('9. $formSent was sent'); }
 
     // Initializing
     if (!is_array($exerciseResult)) {
@@ -733,7 +728,7 @@ if ($formSent && isset($_POST)) {
                 $exerciseResult[$key] = $choice[$key];
                 //saving each question
                 if ($objExercise->feedback_type != EXERCISE_FEEDBACK_TYPE_DIRECT) {
-                    $nro_question = $current_question; // - 1;
+                    //$nro_question = $current_question; // - 1;
                  	$questionId   = $key;
                     // gets the student choice for this question
                     $choice = $exerciseResult[$questionId];
@@ -753,7 +748,7 @@ if ($formSent && isset($_POST)) {
 
     // the script "exercise_result.php" will take the variable $exerciseResult from the session
     Session::write('exerciseResult',$exerciseResult);
-    Session::write('remind_list', $remind_list);
+    //Session::write('remind_list', $remind_list);
     Session::write('exerciseResultCoordinates',$exerciseResultCoordinates);
 
     // if all questions on one page OR if it is the last question (only for an exercise with one question per page)
@@ -912,6 +907,16 @@ if (api_is_course_admin() && $origin != 'learnpath') {
     echo '</div>';
 }
 
+if ($objExercise->type == ONE_PER_PAGE) {
+    $exercise_result = get_answered_questions_from_attempt($exe_id, $objExercise);
+    $conditions = array();
+    $conditions[] = array("class" => 'remind', 'items' => $my_remind_list);
+    //$conditions[] = array("class" => 'not_answered', 'items' => $exercise_result, 'type' => 'negative');
+    $conditions[] = array("class" => 'answered', 'items' => $exercise_result);
+    $link = api_get_self().'?'.$params.'&num=';
+    echo Display::progress_pagination_bar($questionList, $current_question, $conditions, $link);
+}
+
 $is_visible_return = $objExercise->is_visible($learnpath_id, $learnpath_item_id, $learnpath_item_view_id);
 if ($is_visible_return['value'] == false) {
     echo $is_visible_return['message'];
@@ -979,8 +984,6 @@ if (!empty($objExercise->description)) {
         $(function() {
             $('#description_content').accordion({
                 changestart: function(event, ui) {
-                    //var clicked = $(this).find('.ui-state-active').attr('id');
-                    //$('#'+clicked).load('/widgets/'+clicked);
                     $('#collapse1').html('".addslashes($objExercise->description)."');
                 }
             });
@@ -1277,8 +1280,6 @@ if (!empty($error)) {
          <input type="hidden" name="learnpath_item_view_id" value="'.$learnpath_item_view_id . '" />';
 
 	//Show list of questions
-    $i = 1;
-
     $attempt_list = array();
     if (isset($exe_id)) {
         $attempt_list = get_all_exercise_event_by_exe_id($exe_id);
@@ -1342,13 +1343,14 @@ function render_question_list($objExercise, $questionList, $current_question, $e
                 }
             } else {
                 render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question);
+                $i++;
             }
         } else {
             //Normal question render
             render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question);
+            $i++;
         }
 
-        $i++;
         // for sequential exercises
         if ($objExercise->type == ONE_PER_PAGE) {
             // quits the loop
@@ -1365,6 +1367,7 @@ function render_question_list($objExercise, $questionList, $current_question, $e
 
 function render_question($objExercise, $questionId, $attempt_list, $remind_list, $i, $current_question, $questions_in_media = array(), $last_question_in_media = false) {
     global $origin;
+    $question_obj = Question::read($questionId);
     $user_choice = isset($attempt_list[$questionId]) ? $attempt_list[$questionId] : null;
 
     $remind_highlight = null;
@@ -1372,6 +1375,9 @@ function render_question($objExercise, $questionId, $attempt_list, $remind_list,
     //Hides questions when reviewing a ALL_ON_ONE_PAGE exercise see #4542 no_remind_highlight class hide with jquery
     if ($objExercise->type == ALL_ON_ONE_PAGE && isset($_GET['reminder']) && $_GET['reminder'] == 2) {
         $remind_highlight = 'no_remind_highlight';
+        if (in_array($question_obj->type, Question::question_type_no_review())) {
+            return null;
+        }
     }
 
     $is_remind_on = false;
@@ -1389,12 +1395,10 @@ function render_question($objExercise, $questionId, $attempt_list, $remind_list,
     $exercise_actions  = null;
 
     echo '<div id="question_div_'.$questionId.'" class="main_question '.$remind_highlight.'" >';
+        // Shows the question + possible answers
+        showQuestion($question_obj, false, $origin, $i, true, false, $user_choice, false);
 
-        //Shows the question and possible answers
-
-        showQuestion($questionId, false, $origin, $i, true, false, $user_choice, false);
-
-        //Button save and continue
+        // Button save and continue
         switch ($objExercise->type) {
             case ONE_PER_PAGE:
                 $exercise_actions .= $objExercise->show_button($questionId, $current_question);
@@ -1418,13 +1422,13 @@ function render_question($objExercise, $questionId, $attempt_list, $remind_list,
                 $exercise_actions  = Display::div($button, array('class'=>'exercise_save_now_button'));
             }
 
-            if ($last_question_in_media) {
+            if ($last_question_in_media && $objExercise->type == ONE_PER_PAGE) {
                 $exercise_actions = $objExercise->show_button($questionId, $current_question, $questions_in_media);
             }
         }
 
         //Checkbox review answers
-        if ($objExercise->review_answers) {
+        if ($objExercise->review_answers && !in_array($question_obj->type, Question::question_type_no_review())) {
             $remind_question_div = Display::tag('label', Display::input('checkbox', 'remind_list['.$questionId.']', '', $attributes).get_lang('ReviewQuestionLater'), array('class' => 'checkbox', 'for' =>'remind_list['.$questionId.']'));
             $exercise_actions   .= Display::div($remind_question_div, array('class'=>'exercise_save_now_button'));
         }
@@ -1439,4 +1443,4 @@ if ($origin != 'learnpath') {
     Display :: display_footer();
 } else {
     echo '</body></html>';
-}
+}

+ 3 - 2
main/exercice/fill_blanks.class.php

@@ -45,7 +45,7 @@ class FillBlanks extends Question
 		$defaults = array();
 
 		if (!empty($this->id)) {
-			$objAnswer = new answer($this->id);
+			$objAnswer = new Answer($this->id);
 
 			// the question is encoded like this
 		    // [A] B [C] D [E] F::10,10,10@1
@@ -53,7 +53,8 @@ class FillBlanks extends Question
 		    // [A] B [C] D [E] F::10,10,10@ or  [A] B [C] D [E] F::10,10,10
 		    // means that is a normal fill blank question
 
-			$pre_array = explode('::', $objAnswer->selectAnswer(1));
+            $answer_id = $objAnswer->getRealAnswerIdFromList(1);
+			$pre_array = explode('::', $objAnswer->selectAnswer($answer_id));
 
 			//make sure we only take the last bit to find special marks
 			$sz = count($pre_array);

+ 49 - 51
main/exercice/freeanswer.class.php

@@ -12,64 +12,62 @@
 /**
  * Code
  */
-if (!class_exists('FreeAnswer')):
 
-    /**
-     * @package chamilo.exercise
-     */
-    class FreeAnswer extends Question {
+/**
+ * @package chamilo.exercise
+ */
+class FreeAnswer extends Question {
 
-        static $typePicture = 'open_answer.gif';
-        static $explanationLangVar = 'FreeAnswer';
+    static $typePicture = 'open_answer.gif';
+    static $explanationLangVar = 'FreeAnswer';
 
-        /**
-         * Constructor
-         */
-        function FreeAnswer() {
-            parent::question();
-            $this->type = FREE_ANSWER;
-            $this->isContent = $this->getIsContent();
-        }
+    /**
+     * Constructor
+     */
+    function FreeAnswer() {
+        parent::question();
+        $this->type = FREE_ANSWER;
+        $this->isContent = $this->getIsContent();
+    }
 
-        /**
-         * function which redifines Question::createAnswersForm
-         * @param the formvalidator instance
-         */
-        function createAnswersForm($form) {
-            $form->addElement('text', 'weighting', get_lang('Weighting'), array('class' => 'span1'));
-            global $text, $class;
-            // setting the save button here and not in the question class.php
-            $form->addElement('style_submit_button', 'submitQuestion', $text, 'class="' . $class . '"');
-            if (!empty($this->id)) {
-                $form->setDefaults(array('weighting' => Text::float_format($this->weighting, 1)));
-            } else {
-                if ($this->isContent == 1) {
-                    $form->setDefaults(array('weighting' => '10'));
-                }
+    /**
+     * function which redifines Question::createAnswersForm
+     * @param the formvalidator instance
+     */
+    function createAnswersForm($form) {
+        $form->addElement('text', 'weighting', get_lang('Weighting'), array('class' => 'span1'));
+        global $text, $class;
+        // setting the save button here and not in the question class.php
+        $form->addElement('style_submit_button', 'submitQuestion', $text, 'class="' . $class . '"');
+        if (!empty($this->id)) {
+            $form->setDefaults(array('weighting' => Text::float_format($this->weighting, 1)));
+        } else {
+            if ($this->isContent == 1) {
+                $form->setDefaults(array('weighting' => '10'));
             }
         }
+    }
 
-        /**
-         * abstract function which creates the form to create / edit the answers of the question
-         * @param the formvalidator instance
-         */
-        function processAnswersCreation($form) {
-            $this->weighting = $form->getSubmitValue('weighting');
-            $this->save();
-        }
+    /**
+     * abstract function which creates the form to create / edit the answers of the question
+     * @param the formvalidator instance
+     */
+    function processAnswersCreation($form) {
+        $this->weighting = $form->getSubmitValue('weighting');
+        $this->save();
+    }
 
-        function return_header($feedback_type = null, $counter = null, $score = null, $show_media = false) {
-            if (!empty($score['comments']) || $score['score'] > 0) {
-                $score['revised'] = true;
-            } else {
-                $score['revised'] = false;
-            }
-            $header = parent::return_header($feedback_type, $counter, $score, $show_media);
-            $header .= '<table class="' . $this->question_table_class . '" >
-	        <tr>
-			<th>' . get_lang("Answer") . '</th>
-			</tr>';
-            return $header;
+    function return_header($feedback_type = null, $counter = null, $score = null, $show_media = false) {
+        if (!empty($score['comments']) || $score['score'] > 0) {
+            $score['revised'] = true;
+        } else {
+            $score['revised'] = false;
         }
+        $header = parent::return_header($feedback_type, $counter, $score, $show_media);
+        $header .= '<table class="' . $this->question_table_class . '" >
+        <tr>
+		<th>' . get_lang("Answer") . '</th>
+		</tr>';
+        return $header;
     }
-endif;
+}

+ 8 - 6
main/exercice/global_multiple_answer.class.php

@@ -1,7 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
-class GlobalMultipleAnswer extends Question {
+class GlobalMultipleAnswer extends Question
+{
 
     static $typePicture = 'mcmagl.gif';
     static $explanationLangVar = 'GlobalMultipleAnswer';
@@ -81,12 +82,14 @@ class GlobalMultipleAnswer extends Question {
         for ($i = 1; $i <= $nb_answers; ++$i) {
             /* si la reponse est de type objet */
             if (is_object($answer)) {
-                $defaults['answer[' . $i . ']'] = $answer->answer[$i];
-                $defaults['comment[' . $i . ']'] = $answer->comment[$i];
-                $defaults['correct[' . $i . ']'] = $answer->correct[$i];
+                $answer_id = $answer->getRealAnswerIdFromList($i);
+
+                $defaults['answer['.$i.']'] = $answer->answer[$answer_id];
+                $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
+                $defaults['correct['.$i.']'] = $answer->correct[$answer_id];
 
                 //------------- D�but
-                $scoreA = $answer->weighting[$i];
+                $scoreA = $answer->weighting[$answer_id];
             }
             if ($scoreA > 0) {
                 $scoreG = $scoreG + $scoreA;
@@ -207,7 +210,6 @@ class GlobalMultipleAnswer extends Question {
 
         //$answer_score �quivaut � la valeur d'une bonne r�ponse
         // cr�ation variable pour r�cuperer la valeur de la coche pour la prise en compte des n�gatifs
-        $test = "";
         $test = $form->getSubmitValue('pts');
 
         for ($i = 1; $i <= $nb_answers; $i++) {

+ 1 - 3
main/exercice/hotpotatoes_exercise_result.class.php

@@ -11,7 +11,6 @@
 /**
  * Code
  */
-if(!class_exists('HotpotatoesExerciseResult')):
 /**
  * Exercise results class
  * @package chamilo.exercise
@@ -377,5 +376,4 @@ class HotpotatoesExerciseResult
 		$workbook->close();
 		return true;
 	}
-}
-endif;
+}

+ 1 - 4
main/exercice/hotspot.class.php

@@ -9,8 +9,6 @@
 /**
  * Code
  */
-if(!class_exists('HotSpot')):
-
 /**
 	CLASS HotSpot
  *
@@ -114,5 +112,4 @@ class HotSpotDelineation extends HotSpot {
 	function processAnswersCreation ($form) {
 		parent::processAnswersCreation ($form);
 	}
-}
-endif;
+}

+ 24 - 29
main/exercice/hotspot_actionscript.as.php

@@ -1,4 +1,4 @@
-<?php //$id:$
+<?php
 /* For licensing terms, see /license.txt */
 /**
 *	This file generates the ActionScript variables code used by the HotSpot .swf
@@ -28,7 +28,6 @@ $pictureWidth  = $pictureSize[0];
 $pictureHeight = $pictureSize[1];
 
 $courseLang = $_course['language'];
-$courseCode = $_course['sysCode'];
 $coursePath = $_course['path'];
 
 
@@ -36,54 +35,50 @@ $course_id = api_get_course_int_id();
 
 // Query db for answers
 if ($answer_type==HOT_SPOT_DELINEATION) {
-	$sql = "SELECT id, answer, hotspot_coordinates, hotspot_type, ponderation FROM $TBL_ANSWERS 
-	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' AND hotspot_type = 'delineation' ORDER BY id";		
+    $sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type, ponderation FROM $TBL_ANSWERS
+	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' AND hotspot_type = 'delineation'
+            ORDER BY iid";
 } else {
-	$sql = "SELECT id, answer, hotspot_coordinates, hotspot_type, ponderation FROM $TBL_ANSWERS 
-	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' ORDER BY id";
+    $sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type, ponderation FROM $TBL_ANSWERS
+	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' ORDER BY iid";
 }
 $result = Database::query($sql);
 // Init
 $output = "hotspot_lang=$courseLang&hotspot_image=$pictureName&hotspot_image_width=$pictureWidth&hotspot_image_height=$pictureHeight&courseCode=$coursePath";
-$i = 0;
+$i = 1;
 $nmbrTries = 0;
 
-while ($hotspot = Database::fetch_assoc($result))
-{
-   	$output .= "&hotspot_".$hotspot['id']."=true";
-	$output .= "&hotspot_".$hotspot['id']."_answer=".str_replace('&','{amp}',$hotspot['answer']);
+while ($hotspot = Database::fetch_assoc($result)) {
+    $hotspot_id = $i;
+    //$hotspot_id = $hotspot['iid'];
+    $output .= "&hotspot_".$hotspot_id."=true";
+    $output .= "&hotspot_".$hotspot_id."_answer=".str_replace('&', '{amp}', $hotspot['answer']);
 	// Square or rectancle
-	if ($hotspot['hotspot_type'] == 'square' )
-	{
-		$output .= "&hotspot_".$hotspot['id']."_type=square";
+    if ($hotspot['hotspot_type'] == 'square') {
+        $output .= "&hotspot_".$hotspot_id."_type=square";
 	}
 	// Circle or ovale
-	if ($hotspot['hotspot_type'] == 'circle')
-	{
-		$output .= "&hotspot_".$hotspot['id']."_type=circle";
+    if ($hotspot['hotspot_type'] == 'circle') {
+        $output .= "&hotspot_".$hotspot_id."_type=circle";
 	}
 	// Polygon
-	if ($hotspot['hotspot_type'] == 'poly')
-	{
-		$output .= "&hotspot_".$hotspot['id']."_type=poly";
+    if ($hotspot['hotspot_type'] == 'poly') {
+        $output .= "&hotspot_".$hotspot_id."_type=poly";
 	}
 	// Delineation
-	if ($hotspot['hotspot_type'] == 'delineation')
-	{
-		$output .= "&hotspot_".$hotspot['id']."_type=delineation";
+    if ($hotspot['hotspot_type'] == 'delineation') {
+        $output .= "&hotspot_".$hotspot_id."_type=delineation";
 	}
 	// No error
-	if ($hotspot['hotspot_type'] == 'noerror')
-	{
-		$output .= "&hotspot_".$hotspot['id']."_type=noerror";
+    if ($hotspot['hotspot_type'] == 'noerror') {
+        $output .= "&hotspot_".$hotspot_id."_type=noerror";
 	}	
 
 	// This is a good answer, count + 1 for nmbr of clicks
-	if ($hotspot['hotspot_type'] > 0)
-	{
+    if ($hotspot['hotspot_type'] > 0) {
 		$nmbrTries++;
 	}
-	$output .= "&hotspot_".$hotspot['id']."_coord=".$hotspot['hotspot_coordinates']."";
+    $output .= "&hotspot_".$hotspot_id."_coord=".$hotspot['hotspot_coordinates']."";
 	$i++;
 }
 

+ 5 - 9
main/exercice/hotspot_actionscript_admin.as.php

@@ -1,4 +1,4 @@
-<?php //$id:$
+<?php
 /* For licensing terms, see /license.txt */
 /**
 *	This file generates the ActionScript variables code used by the HotSpot .swf
@@ -50,20 +50,17 @@ for($i=1;$i <= $nbrAnswers;$i++) {
 			$output .= "&hotspot_".$i."_type=oar";
 	} else {
 		// Square or rectancle
-		if ($answers['hotspot_type'][$i] == 'square' )
-		{
+        if ($answers['hotspot_type'][$i] == 'square') {
 			$output .= "&hotspot_".$i."_type=square";
 		}
 
 		// Circle or ovale
-		if ($answers['hotspot_type'][$i] == 'circle')
-		{
+        if ($answers['hotspot_type'][$i] == 'circle') {
 			$output .= "&hotspot_".$i."_type=circle";
 		}
 
 		// Polygon
-		if ($answers['hotspot_type'][$i] == 'poly')
-		{
+        if ($answers['hotspot_type'][$i] == 'poly') {
 			$output .= "&hotspot_".$i."_type=poly";
 		}
 		/*// Delineation
@@ -74,8 +71,7 @@ for($i=1;$i <= $nbrAnswers;$i++) {
 	}
 
 	// This is a good answer, count + 1 for nmbr of clicks
-	if ($answers['weighting'][$i] > 0)
-	{
+    if ($answers['weighting'][$i] > 0) {
 		$nmbrTries++;
 	}
 

+ 10 - 6
main/exercice/hotspot_admin.inc.php

@@ -344,14 +344,17 @@ if ($modifyAnswers) {
         $destination = array();
 
 
-        for ($i = 1; $i <= $nbrAnswers; $i++) {
-            $reponse[$i] = $objAnswer->selectAnswer($i);
+        //for ($i = 1; $i <= $nbrAnswers; $i++) {
+        $i = 1;
+        foreach ($objAnswer->answer as $answer_id =>  $answer_item) {
+            //$i = $answer_id;
+            $reponse[$i] = $objAnswer->selectAnswer($answer_id);
             if ($objExercise->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                $comment[$i] = $objAnswer->selectComment($i);
+                $comment[$i] = $objAnswer->selectComment($answer_id);
             }
-            $weighting[$i] = $objAnswer->selectWeighting($i);
-            $hotspot_coordinates[$i] = $objAnswer->selectHotspotCoordinates($i);
-            $hotspot_type[$i] = $objAnswer->selectHotspotType($i);
+            $weighting[$i] = $objAnswer->selectWeighting($answer_id);
+            $hotspot_coordinates[$i] = $objAnswer->selectHotspotCoordinates($answer_id);
+            $hotspot_type[$i] = $objAnswer->selectHotspotType($answer_id);
 
             if ($answerType == HOT_SPOT_DELINEATION) {
                 $destination[$i] = $objAnswer->selectDestination($i);
@@ -368,6 +371,7 @@ if ($modifyAnswers) {
                 $select_question[$i] = $destination_items[3];
                 $url[$i] = $destination_items[4];
             }
+            $i++;
         }
     }
 

+ 15 - 12
main/exercice/hotspot_answers.as.php

@@ -38,43 +38,46 @@ $course_id     = api_get_course_int_id();
 
 if ($answer_type == HOT_SPOT_DELINEATION) {
 	// Query db for answers
-	$sql = "SELECT id, answer, hotspot_coordinates, hotspot_type FROM $TBL_ANSWERS 
-	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' AND hotspot_type <> 'noerror' ORDER BY id";
+	$sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type FROM $TBL_ANSWERS
+	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' AND hotspot_type <> 'noerror'
+            ORDER BY iid";
 } else {
-	$sql = "SELECT id, answer, hotspot_coordinates, hotspot_type FROM $TBL_ANSWERS 
-	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' ORDER BY id";
+	$sql = "SELECT iid, answer, hotspot_coordinates, hotspot_type FROM $TBL_ANSWERS
+	        WHERE c_id = $course_id AND question_id = '".Database::escape_string($questionId)."' ORDER BY iid";
 }
 $result = Database::query($sql);
 // Init
 $output = "hotspot_lang=$courseLang&hotspot_image=$pictureName&hotspot_image_width=$pictureWidth&hotspot_image_height=$pictureHeight&courseCode=$coursePath";
-$i = 0;
+$i = 1;
 
 while ($hotspot = Database::fetch_array($result)) {
-	$output .= "&hotspot_".$hotspot['id']."=true";
+    $hotspot_id = $i;
+    //$hotspot_id = $hotspot['iid'];
+	$output .= "&hotspot_".$hotspot_id."=true";
 	// Square or rectancle
 	if ($hotspot['hotspot_type'] == 'square' ) {
-		$output .= "&hotspot_".$hotspot['id']."_type=square";
+		$output .= "&hotspot_".$hotspot_id."_type=square";
 	}
 
 	// Circle or ovale
 	if ($hotspot['hotspot_type'] == 'circle') {
-		$output .= "&hotspot_".$hotspot['id']."_type=circle";
+		$output .= "&hotspot_".$hotspot_id."_type=circle";
 	}
 
 	// Polygon
 	if ($hotspot['hotspot_type'] == 'poly') {
-		$output .= "&hotspot_".$hotspot['id']."_type=poly";
+		$output .= "&hotspot_".$hotspot_id."_type=poly";
 	}
 
 	// Delineation
 	if ($hotspot['hotspot_type'] == 'delineation') {
-		$output .= "&hotspot_".$hotspot['id']."_type=delineation";
+		$output .= "&hotspot_".$hotspot_id."_type=delineation";
 	}	
 	// oar
 	if ($hotspot['hotspot_type'] == 'oar') {
-		$output .= "&hotspot_".$hotspot['id']."_type=delineation";	 
+		$output .= "&hotspot_".$hotspot_id."_type=delineation";
 	}	
-	$output .= "&hotspot_".$hotspot['id']."_coord=".$hotspot['hotspot_coordinates']."";
+	$output .= "&hotspot_".$hotspot_id."_coord=".$hotspot['hotspot_coordinates']."";
 	$i++;
 }
 

+ 7 - 5
main/exercice/matching.class.php

@@ -33,7 +33,7 @@ class Matching extends Question
     }
 
     /**
-     * function which redifines Question::createAnswersForm
+     * Redefines Question::createAnswersForm
      * @param the formvalidator instance
      */
     function createAnswersForm($form)
@@ -65,12 +65,13 @@ class Matching extends Question
                 if (count($answer->nbrAnswers) > 0) {
                     $a_matches  = $a_options = array();
                     $nb_matches = $nb_options = 0;
-                    for ($i = 1; $i <= $answer->nbrAnswers; $i++) {
+                    foreach ($answer->answer as $i => $answer_item) {
                         if ($answer->isCorrect($i)) {
                             $nb_matches++;
                             $defaults['answer['.$nb_matches.']']    = $answer->selectAnswer($i);
-                            $defaults['weighting['.$nb_matches.']'] = Text::float_format($answer->selectWeighting($i), 1);
-                            $defaults['matches['.$nb_matches.']']   = $answer->correct[$i];
+                            $defaults['weighting['.$nb_matches.']'] = float_format($answer->selectWeighting($i), 1);
+                            $correct_answer_id = $answer->correct[$i];
+                            $defaults['matches['.$nb_matches.']'] = $answer->getCorrectAnswerPosition($correct_answer_id);
                         } else {
                             $nb_options++;
                             $defaults['option['.$nb_options.']'] = $answer->selectAnswer($i);
@@ -88,7 +89,8 @@ class Matching extends Question
         }
         $a_matches = array();
         for ($i = 1; $i <= $nb_options; ++$i) {
-            $a_matches[$i] = chr(64 + $i); // fill the array with A, B, C.....
+            // fill the array with A, B, C.....
+            $a_matches[$i] = chr(64 + $i);
         }
 
         $form->addElement('hidden', 'nb_matches', $nb_matches);

+ 7 - 7
main/exercice/media.php

@@ -37,10 +37,9 @@ switch ($action) {
             $objQuestion->processCreation($form, null);
             // answers
             $objQuestion->processAnswersCreation($form, null);
-            header('Location: '.$page_url);
-            exit;
+        } else {
+            $form->display();
         }
-        $form->display();
         break;
     case 'edit':
         $url  = $page_url.'&action='.Security::remove_XSS($_GET['action']).'&id='.intval($_GET['id']);
@@ -58,13 +57,14 @@ switch ($action) {
 		$defaults['questionCategory']       = $objQuestion->category_list;
         $defaults['parent_id']              = $objQuestion->parent_id;
         $form->setDefaults($defaults);
-        $form->display();
 
         if ($form->validate()) {
             // question
             $objQuestion->processCreation($form, null);
             // answers
             $objQuestion->processAnswersCreation($form, null);
+        } else {
+            $form->display();
         }
         break;
     case 'delete':
@@ -81,9 +81,9 @@ $columns        = array(get_lang('Name'), get_lang('Actions'));
 
 //Column config
 $column_model   = array(
-                        array('name'=>'name',           'index'=>'name',        'width'=>'80',   'align'=>'left'),
-                        array('name'=>'actions',        'index'=>'actions',     'width'=>'100',  'align'=>'left','formatter'=>'action_formatter','sortable'=>'false')
-                       );
+    array('name'=>'name',    'index'=>'name',        'width'=>'200',   'align'=>'left'),
+    array('name'=>'actions', 'index'=>'actions',     'width'=>'50',  'align'=>'left','formatter'=>'action_formatter','sortable'=>'false')
+);
 //Autowidth
 $extra_params['autowidth'] = 'true';
 //height auto

+ 7 - 5
main/exercice/media_question.class.php

@@ -1,12 +1,14 @@
 <?php
+/* For licensing terms, see /license.txt */
 
-class MediaQuestion extends Question {
+class MediaQuestion extends Question
+{
 	static $typePicture = 'looknfeel.png';
 	static $explanationLangVar = 'MediaQuestion';
     
     function __construct(){
         parent::question();
-		$this -> type = MEDIA_QUESTION;		
+		$this->type = MEDIA_QUESTION;		
     }
     
     function processAnswersCreation($form) {
@@ -17,16 +19,16 @@ class MediaQuestion extends Question {
     function save_media($params) {        
         $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION);         
         $new_params = array(
-            'c_id'          => api_get_course_int_id(),
+            'c_id' => $this->course['real_id'],
             'question'      => $params['questionName'],
             'description'   => $params['questionDescription'],
             'parent_id'     => 0,
             'type'          => MEDIA_QUESTION
         );
         if (isset($params['id'])) {
-            Database::update($table_question, $new_params, array('id = ? and c_id = ?' => array($params['id'], api_get_course_int_id())));
+            Database::update($table_question, $new_params, array('iid = ? and c_id = ?' => array($params['id'], $this->course['real_id'])));
         } else {
-            Database::insert($table_question, $new_params);
+            return Database::insert($table_question, $new_params);
         }
     }
     

+ 8 - 10
main/exercice/multiple_answer.class.php

@@ -8,8 +8,6 @@
 /**
  * Code
  */
-if(!class_exists('MultipleAnswer')):
-
 /**
 	CLASS MultipleAnswer
  *
@@ -58,7 +56,7 @@ class MultipleAnswer extends Question {
 							'.get_lang('Answer').'
 						</th>';
 				// show column comment when feedback is enable
-				if ($obj_ex->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM ) {
+                if (!empty($obj_ex) && $obj_ex->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
                     $html .='<th>
                                 '.get_lang('Comment').'
                             </th>';
@@ -89,10 +87,11 @@ class MultipleAnswer extends Question {
 
 		for ($i = 1 ; $i <= $nb_answers ; ++$i) {
 			if(is_object($answer)) {
-				$defaults['answer['.$i.']'] = $answer -> answer[$i];
-				$defaults['comment['.$i.']'] = $answer -> comment[$i];
-				$defaults['weighting['.$i.']'] = Text::float_format($answer -> weighting[$i], 1);
-				$defaults['correct['.$i.']'] = $answer -> correct[$i];
+                $answer_id = $answer->getRealAnswerIdFromList($i);
+                $defaults['answer['.$i.']'] = $answer->answer[$answer_id];
+                $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
+                $defaults['weighting['.$i.']'] = float_format($answer->weighting[$answer_id], 1);
+                $defaults['correct['.$i.']'] = $answer->correct[$answer_id];
 			} else {
 				$defaults['answer[1]']  = get_lang('DefaultMultipleAnswer2');
 				$defaults['comment[1]'] = get_lang('DefaultMultipleComment2');
@@ -122,7 +121,7 @@ class MultipleAnswer extends Question {
 			$form->addRule('answer['.$i.']', get_lang('ThisFieldIsRequired'), 'required');
 
 			// show comment when feedback is enable
-			if ($obj_ex->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            if (!empty($obj_ex) && $obj_ex->selectFeedbackType() != EXERCISE_FEEDBACK_TYPE_EXAM) {
 				$form->addElement('html_editor', 'comment['.$i.']',null, 'style="vertical-align:middle"', array('ToolbarSet' => 'TestProposedAnswer', 'Width' => '100%', 'Height' => '100'));
 			}
 
@@ -221,5 +220,4 @@ class MultipleAnswer extends Question {
         $header .= '</tr>';
         return $header;
 	}
-}
-endif;
+}

+ 9 - 11
main/exercice/multiple_answer_combination.class.php

@@ -9,8 +9,6 @@
 /**
  * Code
  */
-if(class_exists('MultipleAnswerCombination')) { return true; }
-
 /**
 	CLASS MultipleAnswer
  *
@@ -21,7 +19,8 @@ if(class_exists('MultipleAnswerCombination')) { return true; }
  *	@package chamilo.exercise
  **/
 
-class MultipleAnswerCombination extends Question {
+class MultipleAnswerCombination extends Question
+{
 
 	static $typePicture = 'mcmac.gif';
 	static $explanationLangVar = 'MultipleSelectCombination';
@@ -82,10 +81,11 @@ class MultipleAnswerCombination extends Question {
 
 		for($i = 1 ; $i <= $nb_answers ; ++$i) {
 			if(is_object($answer)) {
-				$defaults['answer['.$i.']'] = $answer -> answer[$i];
-				$defaults['comment['.$i.']'] = $answer -> comment[$i];
-				$defaults['weighting['.$i.']'] = Text::float_format($answer -> weighting[$i], 1);
-				$defaults['correct['.$i.']'] = $answer -> correct[$i];
+                $answer_id = $answer->getRealAnswerIdFromList($i);
+                $defaults['answer['.$i.']'] = $answer->answer[$answer_id];
+                $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
+                $defaults['weighting['.$i.']'] = float_format($answer->weighting[$answer_id], 1);
+                $defaults['correct['.$i.']'] = $answer->correct[$answer_id];
 			} else {
 				$defaults['answer[1]']  = get_lang('DefaultMultipleAnswer2');
 				$defaults['comment[1]'] = get_lang('DefaultMultipleComment2');
@@ -177,8 +177,7 @@ class MultipleAnswerCombination extends Question {
 
 		$nb_answers = $form -> getSubmitValue('nb_answers');
 
-		for($i=1 ; $i <= $nb_answers ; $i++)
-        {
+        for ($i = 1; $i <= $nb_answers; $i++) {
         	$answer = trim($form -> getSubmitValue('answer['.$i.']'));
             $comment = trim($form -> getSubmitValue('comment['.$i.']'));
             if ($i == 1)
@@ -194,8 +193,7 @@ class MultipleAnswerCombination extends Question {
 				$weighting = abs($weighting);
 			//	$weighting = -$weighting;
 			}
-    		if($weighting > 0)
-            {
+            if ($weighting > 0) {
                 $questionWeighting += $weighting;
             }
         	$objAnswer -> createAnswer($answer,$goodAnswer,$comment,$weighting,$i);

+ 2 - 3
main/exercice/multiple_answer_combination_true_false.class.php

@@ -9,8 +9,6 @@
 /**
  * Code
  */
-if(class_exists('MultipleAnswerCombinationTrueFalse')) { return true; }
-require_once 'multiple_answer_combination.class.php';
 /**
 	CLASS MultipleAnswer
  *
@@ -20,7 +18,8 @@ require_once 'multiple_answer_combination.class.php';
  *	@author Eric Marguin
  *	@package chamilo.exercise
  **/
-class MultipleAnswerCombinationTrueFalse extends MultipleAnswerCombination {
+class MultipleAnswerCombinationTrueFalse extends MultipleAnswerCombination
+{
 
 	static $typePicture = 'mcmaco.gif';
 	static $explanationLangVar = 'MultipleAnswerCombinationTrueFalse';

+ 10 - 11
main/exercice/multiple_answer_true_false.class.php

@@ -11,12 +11,12 @@
 /**
  * Code
  */
-if(!class_exists('MultipleAnswerTrueFalse')):
 /**
  * Class
  * @package chamilo.exercise
  */
-class MultipleAnswerTrueFalse extends Question {
+class MultipleAnswerTrueFalse extends Question
+{
 
 	static $typePicture = 'mcmao.gif';
 	static $explanationLangVar = 'MultipleAnswerTrueFalse';
@@ -101,11 +101,12 @@ class MultipleAnswerTrueFalse extends Question {
             $answer_number->freeze();
 
 			if (is_object($answer)) {
-				$defaults['answer['.$i.']']     = $answer -> answer[$i];
-				$defaults['comment['.$i.']']    = $answer -> comment[$i];
 				      
-                $correct = $answer->correct[$i];
+                $answer_id = $answer->getRealAnswerIdFromList($i);
 
+                $defaults['answer['.$i.']'] = $answer->answer[$answer_id];
+                $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
+                $correct = $answer->correct[$answer_id];
                 $defaults['correct['.$i.']']    = $correct;
 
                 $j = 1;
@@ -128,7 +129,6 @@ class MultipleAnswerTrueFalse extends Question {
                 $defaults['correct['.$i.']']    = '';
 			}
 
-            //$form->addElement('select', 'correct['.$i.']',null, $this->options, array('id'=>$i,'onchange'=>'multiple_answer_true_false_onchange(this)'));
 
 			$boxes_names[] = 'correct['.$i.']';
 
@@ -229,8 +229,8 @@ class MultipleAnswerTrueFalse extends Question {
 
         if (!empty($options)) {
             foreach ($options as $option_data) {
-                $id = $option_data['id'];
-                unset($option_data['id']);
+                $id = $option_data['iid'];
+                unset($option_data['iid']);
                 Question::updateQuestionOption($id, $option_data, $course_id);
             }
         } else {
@@ -262,7 +262,7 @@ class MultipleAnswerTrueFalse extends Question {
             $goodAnswer = trim($form -> getSubmitValue('correct['.$i.']'));
             if (empty($options)) {
                 //If this is the first time that the question is created when change the default values from the form 1 and 2 by the correct "option id" registered
-                $goodAnswer = $sorted_by_position[$goodAnswer]['id'];
+                $goodAnswer = $sorted_by_position[$goodAnswer]['iid'];
             }
     	    $questionWeighting += $extra_values[0]; //By default 0 has the correct answers
         	$objAnswer->createAnswer($answer, $goodAnswer, $comment,'',$i);
@@ -292,5 +292,4 @@ class MultipleAnswerTrueFalse extends Question {
         $header .= '</tr>';
         return $header;
 	}
-}
-endif;
+}

+ 3 - 4
main/exercice/oral_expression.class.php

@@ -11,11 +11,11 @@
 /**
  * Code
  */
-if(!class_exists('OralExpression')):
 /**
  * @package chamilo.exercise
  */
-class OralExpression extends Question {
+class OralExpression extends Question
+{
 
 	static $typePicture = 'audio_question.png';
 	static $explanationLangVar = 'OralExpression';
@@ -70,5 +70,4 @@ class OralExpression extends Question {
 			</tr>';
         return $header;
 	}
-}
-endif;
+}

+ 167 - 108
main/exercice/question.class.php

@@ -121,6 +121,24 @@ abstract class Question
         return $this->isContent = $isContent;
     }
 
+    public function readByTitle($title, $course_id = null) {
+        if (!empty($course_id)) {
+            $course_info = api_get_course_info_by_id($course_id);
+        } else {
+            $course_info = api_get_course_info();
+        }
+        $course_id = $course_info['real_id'];
+
+        $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
+        $title = Database::escape_string($title);
+        $sql = "SELECT iid FROM $TBL_QUESTIONS WHERE question = '$title' AND c_id = $course_id ";
+        $result = Database::query($sql);
+        if (Database::num_rows($result)) {
+            $row = Database::fetch_array($result);
+            return self::read($row['iid'], $course_id);
+        }
+        return false;
+    }
     /**
      * Reads question informations from the data base
      *
@@ -147,7 +165,7 @@ abstract class Question
         $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
         $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
 
-        $sql = "SELECT question,description,ponderation,position,type,picture,level, extra, parent_id FROM $TBL_QUESTIONS WHERE c_id = $course_id AND id = $id ";
+        $sql = "SELECT * FROM $TBL_QUESTIONS WHERE iid = $id ";
 
         $result = Database::query($sql);
 
@@ -319,6 +337,9 @@ abstract class Question
         return sizeof($this->exerciseList);
     }
 
+    function updateCourse($course_id) {
+        $this->course = api_get_course_info_by_id($course_id);
+    }
     /**
      * changes the question title
      *
@@ -393,19 +414,22 @@ abstract class Question
      * @param  - int $in_positive
      * @author - Julio Montoya - Adding multiple cat support
      */
-    function saveCategories($category_list)
-    {
+    function saveCategories($category_list) {
+        $course_id = $this->course['real_id'];
 
         if (!empty($category_list)) {
             $this->deleteCategory();
             $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
+            $category_list = array_filter($category_list);
 
             // update or add category for a question
             foreach ($category_list as $category_id) {
+                if (empty($category_id)) {
+                    continue;
+                }
                 $category_id = intval($category_id);
                 $question_id = Database::escape_string($this->id);
-                $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE category_id = $category_id AND question_id = $question_id AND c_id=".api_get_course_int_id(
-                );
+                $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE category_id = $category_id AND question_id = $question_id AND c_id=".$course_id;
                 $res = Database::query($sql);
                 $row = Database::fetch_array($res);
                 if ($row['nb'] > 0) {
@@ -413,8 +437,7 @@ abstract class Question
                     //$sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id = $category_id WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
                     //$res = Database::query($sql);
                 } else {
-                    $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id) VALUES (".api_get_course_int_id(
-                    ).", $question_id, $category_id)";
+                    $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY (c_id, question_id, category_id) VALUES (".$course_id.", $question_id, $category_id)";
                     $res = Database::query($sql);
                 }
             }
@@ -437,17 +460,14 @@ abstract class Question
             $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
             $category_id = Database::escape_string($in_category);
             $question_id = Database::escape_string($this->id);
-            $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id=$question_id AND c_id=".api_get_course_int_id(
-            );
+            $sql = "SELECT count(*) AS nb FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id= $question_id AND c_id=".api_get_course_int_id();
             $res = Database::query($sql);
             $row = Database::fetch_array($res);
             if ($row['nb'] > 0) {
-                $sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id=$category_id WHERE question_id=$question_id AND c_id=".api_get_course_int_id(
-                );
+                $sql = "UPDATE $TBL_QUESTION_REL_CATEGORY SET category_id= $category_id WHERE question_id=$question_id AND c_id=".api_get_course_int_id();
                 $res = Database::query($sql);
             } else {
-                $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY VALUES (".api_get_course_int_id(
-                ).", $question_id, $category_id)";
+                $sql = "INSERT INTO $TBL_QUESTION_REL_CATEGORY VALUES (".api_get_course_int_id().", $question_id, $category_id)";
                 $res = Database::query($sql);
             }
         }
@@ -459,12 +479,11 @@ abstract class Question
      * @param : none
      * delte the category for question
      */
-    function deleteCategory()
-    {
+    function deleteCategory() {
+        $course_id = $this->course['real_id'];
         $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
         $question_id = Database::escape_string($this->id);
-        $sql = "DELETE FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id = $question_id AND c_id = ".api_get_course_int_id(
-        );
+        $sql = "DELETE FROM $TBL_QUESTION_REL_CATEGORY WHERE question_id = $question_id AND c_id = ".$course_id;
         Database::query($sql);
     }
 
@@ -802,7 +821,7 @@ abstract class Question
         $c_id = $this->course['real_id'];
         $category_list = $this->category_list;
 
-        // question already exists
+        // Question already exists
         if (!empty($id)) {
             $sql = "UPDATE $TBL_QUESTIONS SET
 					question 	='".Database::escape_string($question)."',
@@ -814,7 +833,7 @@ abstract class Question
                     extra       ='".Database::escape_string($extra)."',
 					level		='".Database::escape_string($level)."',
                     parent_id   =  ".$this->parent_id."
-				WHERE c_id = $c_id AND id='".Database::escape_string($id)."'";
+				WHERE iid = '".Database::escape_string($id)."'";
 
             Database::query($sql);
             $this->saveCategories($category_list);
@@ -825,15 +844,10 @@ abstract class Question
             if (api_get_setting('search_enabled') == 'true') {
                 if ($exerciseId != 0) {
                     $this->search_engine_edit($exerciseId);
-                } else {
-                    /**
-                     * actually there is *not* an user interface for
-                     * creating questions without a relation with an exercise
-                     */
                 }
             }
         } else {
-            // creates a new question
+            // Creates a new question
             $sql = "SELECT max(position) FROM $TBL_QUESTIONS as question, $TBL_EXERCICE_QUESTION as test_question
 					   WHERE 	question.id					= test_question.question_id AND
 								test_question.exercice_id	= '".Database::escape_string($exerciseId)."' AND
@@ -864,19 +878,15 @@ abstract class Question
             // If hotspot, create first answer
             if ($type == HOT_SPOT || $type == HOT_SPOT_ORDER) {
                 $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
-                $sql = "INSERT INTO $TBL_ANSWERS (c_id, id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
-					    VALUES (".$c_id.", '1', '".Database::escape_string(
-                    $this->id
-                )."', '', NULL , '', '10' , '1', '0;0|0|0', 'square')";
+                $sql = "INSERT INTO $TBL_ANSWERS (c_id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
+					    VALUES (".$c_id.", '".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'square')";
                 Database::query($sql);
             }
 
             if ($type == HOT_SPOT_DELINEATION) {
                 $TBL_ANSWERS = Database::get_course_table(TABLE_QUIZ_ANSWER);
-                $sql = "INSERT INTO $TBL_ANSWERS (c_id, id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
-					  VALUES (".$c_id.", '1', '".Database::escape_string(
-                    $this->id
-                )."', '', NULL , '', '10' , '1', '0;0|0|0', 'delineation')";
+                $sql = "INSERT INTO $TBL_ANSWERS (c_id, question_id , answer , correct , comment , ponderation , position , hotspot_coordinates , hotspot_type )
+					  VALUES (".$c_id.", '".Database::escape_string($this->id)."', '', NULL , '', '10' , '1', '0;0|0|0', 'delineation')";
                 Database::query($sql);
             }
 
@@ -894,13 +904,6 @@ abstract class Question
 
         // if the question is created in an exercise
         if ($exerciseId) {
-            /*
-            $sql = 'UPDATE '.Database::get_course_table(TABLE_LP_ITEM).'
-                    SET max_score = '.intval($weighting).'
-                    WHERE item_type = "'.TOOL_QUIZ.'"
-                    AND path='.intval($exerciseId);
-            Database::query($sql);
-            */
             // adds the exercise into the exercise list of this question
             $this->addToList($exerciseId, true);
         }
@@ -1228,27 +1231,29 @@ abstract class Question
         $options = self::readQuestionOption($this->id, $this->course['real_id']);
 
         //Inserting in the new course db / or the same course db
-        $sql = "INSERT INTO $TBL_QUESTIONS (c_id, question, description, ponderation, position, type, level, extra )
-				VALUES ('$course_id', '".Database::escape_string($question)."','".Database::escape_string(
-            $description
-        )."','".Database::escape_string($weighting)."','".Database::escape_string(
-            $position
-        )."','".Database::escape_string($type)."' ,'".Database::escape_string($level)."' ,'".Database::escape_string(
-            $extra
-        )."'  )";
-        Database::query($sql);
-
-        $new_question_id = Database::insert_id();
+        $params = array(
+            'c_id' => $course_id,
+            'question' => $question,
+            'description' => $description,
+            'ponderation' => $this->weighting,
+            'position' => $this->position,
+            'type' => $this->type,
+            'level' => $this->level,
+            'extra' => $this->extra,
+            'parent_id' => $this->parent_id,
+        );
+        $new_question_id = Database::insert($TBL_QUESTIONS, $params);
 
         if (!empty($options)) {
             //Saving the quiz_options
             foreach ($options as $item) {
                 $item['question_id'] = $new_question_id;
                 $item['c_id'] = $course_id;
-                unset($item['id']);
+                unset($item['iid']);
                 Database::insert($TBL_QUESTION_OPTIONS, $item);
             }
         }
+        $this->duplicate_category_question($new_question_id, $course_id);
 
         // Duplicates the picture of the hotspot
         $this->exportPicture($new_question_id, $course_info);
@@ -1256,10 +1261,20 @@ abstract class Question
         return $new_question_id;
     }
 
-    function get_question_type_name()
-    {
-        $key = self::$questionTypes[$this->type];
+    function get_categories_from_question() {
+        return Testcategory::getCategoryForQuestion($this->id);
+    }
+
+    function duplicate_category_question($question_id, $course_id) {
+        $question = Question::read($question_id, $course_id);
+        $categories = $this->get_categories_from_question();
+        if (!empty($categories)) {
+            $question->saveCategories($categories);
+        }
+    }
 
+    function get_question_type_name() {
+        $key = self::$questionTypes[$this->type];
         return get_lang($key[1]);
     }
 
@@ -1309,11 +1324,56 @@ abstract class Question
      * A subclass can redifine this function to add fields...
      * @param FormValidator $form the formvalidator instance (by reference)
      */
-    function createForm(&$form, $fck_config = 0)
-    {
+    function createForm(&$form, $fck_config = 0) {
+        $url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?1=1';
         echo '	<style>
 					.media { display:none;}
 				</style>';
+        if ($this->type != MEDIA_QUESTION) {
+            echo '<script>
+            function check() {
+                $("#category_id option:selected").each(function() {
+                    var id = $(this).val();
+                    var name = $(this).text();
+                    if (id != "" ) {
+                        $.ajax({
+                            async: false,
+                            url: "'.$url.'&a=exercise_category_exists",
+                            data: "id="+id,
+                            success: function(return_value) {
+                                if (return_value == 0 ) {
+                                    alert("'.get_lang('CategoryDoesNotExists').'");
+                                    //Deleting select option tag
+                                    $("#parent_id").find("option").remove();
+
+                                    $(".holder li").each(function () {
+                                        if ($(this).attr("rel") == id) {
+                                            $(this).remove();
+                                        }
+                                    });
+                                }
+                            },
+                        });
+                    }
+                });
+            }
+
+            $(function() {
+                $("#category_id").fcbkcomplete({
+                       json_url: "'.$url.'&a=search_category_parent&type=all&",
+                       maxitems: 20,
+                       addontab: false,
+                       input_min_size: 1,
+                       cache: false,
+                       complete_text:"'.get_lang('StartToType').'",
+                       firstselected: false,
+                       onselect: check,
+                       filter_selected: true,
+                       newel: true
+                   });
+            });
+            </script>';
+        }
         echo '<script>
 			// hack to hide http://cksource.com/forums/viewtopic.php?f=6&t=8700
 
@@ -1415,23 +1475,29 @@ abstract class Question
             $select_level = Question::get_default_levels();
             $form->addElement('select', 'questionLevel', get_lang('Difficulty'), $select_level);
 
-            // Categories
-            $category_list = Testcategory::getCategoriesIdAndName();
-            $form->addElement(
-                'select',
-                'questionCategory',
-                get_lang('Category'),
-                $category_list,
-                array('multiple' => 'multiple')
-            );
+            if (!empty($this->category_list)) {
+                $trigger = '';
+                foreach ($this->category_list as $category_id) {
+                    if (!empty($category_id)) {
+                        $cat = new Testcategory($category_id);
+                        if ($cat->id) {
+                            $trigger .= '$("#category_id").trigger("addItem",[{"title": "'.$cat->parent_path.'", "value": "'.$cat->id.'"}]);';
+                        }
+                    }
+                }
+                echo '<script>$(function() { '.$trigger.'  });</script>';
+            }
+
+            $form->addElement('select', 'questionCategory', get_lang('Category'), array(), array('id' => 'category_id'));
 
             // Categories
             //$tabCat = Testcategory::getCategoriesIdAndName();
             //$form->addElement('select', 'questionCategory', get_lang('Category'), $tabCat);
 
             //Medias
-            //$course_medias = Question::prepare_course_media_select(api_get_course_int_id());
-            //$form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
+            $course_medias = Question::prepare_course_media_select(api_get_course_int_id());
+
+            $form->addElement('select', 'parent_id', get_lang('AttachToMedia'), $course_medias);
 
             $form->addElement('html', '</div>');
         }
@@ -1559,12 +1625,9 @@ abstract class Question
             if ($objExercise->exercise_was_added_in_lp == true) {
                 $img = pathinfo($img);
                 $img = $img['filename'].'_na.'.$img['extension'];
-                echo Display::return_icon($img, $explanation);
+                echo Display::return_icon($img, $explanation, array(), ICON_SIZE_BIG);
             } else {
-                echo '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'">'.Display::return_icon(
-                    $img,
-                    $explanation
-                ).'</a>';
+                echo '<a href="admin.php?'.api_get_cidreq().'&newQuestion=yes&answerType='.$i.'&exerciseId='.$exerciseId.'">'.Display::return_icon($img, $explanation, array(), ICON_SIZE_BIG).'</a>';
             }
             echo '</div>';
             echo '</li>';
@@ -1595,7 +1658,7 @@ abstract class Question
         $params['name'] = $name;
         $params['position'] = $position;
         $params['c_id'] = $course_id;
-        $result = self::readQuestionOption($question_id, $course_id);
+        //$result  = self::readQuestionOption($question_id, $course_id);
         $last_id = Database::insert($TBL_EXERCICE_QUESTION_OPTION, $params);
 
         return $last_id;
@@ -1622,19 +1685,17 @@ abstract class Question
         return $result;
     }
 
-    static function readQuestionOption($question_id, $course_id)
-    {
+    static function readQuestionOption($question_id, $course_id) {
         $TBL_EXERCICE_QUESTION_OPTION = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION);
-        $result = Database::select(
-            '*',
-            $TBL_EXERCICE_QUESTION_OPTION,
-            array(
-                'where' => array('c_id = ? AND question_id = ?' => array($course_id, $question_id)),
-                'order' => 'id ASC'
-            )
-        );
-
-        return $result;
+        $result = Database::select('*', $TBL_EXERCICE_QUESTION_OPTION, array('where' => array('c_id = ? AND question_id = ?' => array($course_id, $question_id)), 'order' => 'iid ASC'));
+        if (!empty($result)) {
+            $new_result = array();
+            foreach ($result as $item) {
+                $new_result[$item['iid']] = $item;
+            }
+            return $new_result;
+        }
+        return array();
     }
 
     /**
@@ -1646,7 +1707,7 @@ abstract class Question
      *
      * @return string
      */
-    function return_header($feedback_type = null, $counter = null, $score = null)
+    function return_header($feedback_type = null, $counter = null, $score = null, $show_media)
     {
         $counter_label = '';
         if (!empty($counter)) {
@@ -1670,18 +1731,13 @@ abstract class Question
         }
         $question_title = $this->question;
 
-        // display question category, if any
-        //$header = Testcategory::returnCategoryAndTitle($this->id);
-        $show_media = null;
-        $header = null;
+        // Display question category, if any
         if ($show_media) {
             $header .= $this->show_media_content();
         }
         $header .= Display::page_subheader2($counter_label.". ".$question_title);
-        $header .= Display::div(
-            '<div class="rib rib-'.$class.'"><h3>'.$score_label.'</h3></div> <h4>'.$score['result'].' </h4>',
-            array('class' => 'ribbon')
-        );
+        //$header .=  Display::div('<div class="rib rib-'.$class.'"><h3>'.$score_label.'</h3></div> <h4>'.($score['result']).' </h4><h5 class="'.$class.'">'.$score['result'].' </h5>', array('class'=>'ribbon'));
+        $header .= Display::div('<div class="rib rib-'.$class.'"><h3>'.$score_label.'</h3></div> <h4>'.$score['result'].' </h4>', array('class' => 'ribbon'));
         $header .= Display::div($this->description, array('id' => 'question_description'));
 
         return $header;
@@ -1710,18 +1766,16 @@ abstract class Question
         // Get the max position
         $sql = "SELECT max(position) as max_position"
             ." FROM $tbl_quiz_question q INNER JOIN $tbl_quiz_rel_question r"
-            ." ON q.id = r.question_id"
+            ." ON q.iid = r.question_id"
             ." AND exercice_id = $quiz_id AND q.c_id = $course_id AND r.c_id = $course_id";
-        $rs_max = Database::query($sql, __FILE__, __LINE__);
+        $rs_max = Database::query($sql);
         $row_max = Database::fetch_object($rs_max);
         $max_position = $row_max->max_position + 1;
 
         // Insert the new question
         $sql = "INSERT INTO $tbl_quiz_question (c_id, question, ponderation, position, type, level)
-                VALUES ($course_id, '".Database::escape_string(
-            $question_name
-        )."', '$max_score', $max_position, $type, $level)";
-        $rs = Database::query($sql);
+                VALUES ($course_id, '".Database::escape_string($question_name)."', '$max_score', $max_position, $type, $level)";
+        Database::query($sql);
         // Get the question ID
         $question_id = Database::get_last_insert_id();
 
@@ -1732,10 +1786,9 @@ abstract class Question
         $row_max_order = Database::fetch_object($rs_max_order);
         $max_order = $row_max_order->max_order + 1;
         // Attach questions to quiz
-        $sql = "INSERT INTO $tbl_quiz_rel_question "
-            ."(c_id, question_id,exercice_id,question_order)"
+        $sql = "INSERT INTO $tbl_quiz_rel_question (c_id, question_id, exercice_id, question_order)"
             ." VALUES($course_id, $question_id, $quiz_id, $max_order)";
-        $rs = Database::query($sql);
+        Database::query($sql);
 
         return $question_id;
     }
@@ -1747,8 +1800,8 @@ abstract class Question
     public function get_type_icon_html()
     {
         $type = $this->selectType();
-        $tabQuestionList = Question::get_question_type_list(); // [0]=file to include [1]=type name
-
+        // [0]=file to include [1]=type name
+        $tabQuestionList = Question::get_question_type_list();
         require_once $tabQuestionList[$type][0];
         eval('$img = '.$tabQuestionList[$type][1].'::$typePicture;');
         eval('$explanation = get_lang('.$tabQuestionList[$type][1].'::$explanationLangVar);');
@@ -1815,7 +1868,7 @@ abstract class Question
 
         if (!empty($medias)) {
             foreach ($medias as $media) {
-                $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
+                $media_list[$media['iid']] = empty($media['question']) ? get_lang('Untitled') : $media['question'];
             }
         }
 
@@ -1848,4 +1901,10 @@ abstract class Question
 
         return $html;
     }
-}
+
+    static function question_type_no_review() {
+        return array(
+            HOT_SPOT, HOT_SPOT_ORDER, HOT_SPOT_DELINEATION
+        );
+    }
+}

+ 6 - 6
main/exercice/question_admin.inc.php

@@ -2,10 +2,10 @@
 /* For licensing terms, see /license.txt */
 /**
 *	This script allows to manage the statements of questions.
-* 	It is included from the script admin.php
-*	@package chamilo.exercise
-* 	@author Olivier Brouckaert
-*  @author Julio Montoya
+*   It is included from the script admin.php
+*   @package chamilo.exercise
+*   @author Olivier Brouckaert
+*   @author Julio Montoya
 */
 /**
  * Code
@@ -16,10 +16,10 @@ $course_id = api_get_course_int_id();
 // INIT QUESTION
 if (isset($_GET['editQuestion'])) {
 	$objQuestion = Question::read ($_GET['editQuestion']);
-	$action = api_get_self()."?".api_get_cidreq()."&myid=1&modifyQuestion=".$modifyQuestion."&editQuestion=".$objQuestion->id;
+	$action = api_get_self()."?".api_get_cidreq()."&myid=1&modifyQuestion=".$modifyQuestion."&editQuestion=".$objQuestion->id."&exerciseId=$exerciseId";
 } else {
 	$objQuestion = Question :: getInstance($_REQUEST['answerType']);
-	$action = api_get_self()."?".api_get_cidreq()."&modifyQuestion=".$modifyQuestion."&newQuestion=".$newQuestion;
+	$action = api_get_self()."?".api_get_cidreq()."&modifyQuestion=".$modifyQuestion."&newQuestion=".$newQuestion."&exerciseId=$exerciseId";
 }
 
 if (is_object($objQuestion)) {

+ 6 - 10
main/exercice/question_list_admin.inc.php

@@ -3,17 +3,12 @@
 /**
 *	Code library for HotPotatoes integration.
 *	@package chamilo.exercise
-* 	@author
-*/
+*	@author Olivier Brouckaert & Julio Montoya & Hubert Borderiou 21-10-2011 (Question by category)
 
-/**
 *	QUESTION LIST ADMINISTRATION
 *
 *	This script allows to manage the question list
 *	It is included from the script admin.php
-*
-*	@author Olivier Brouckaert
-* Modified by Hubert Borderiou 21-10-2011 (Question by category)
 */
 
 // deletes a question from the exercise (not from the data base)
@@ -156,6 +151,7 @@ if (!$inATest) {
     echo '<div id="question_list">';
 	if ($nbrQuestions) {
         //Always getting list from DB
+        $objExercise->setCategoriesGrouping(false);
         $questionList = $objExercise->selectQuestionList(true);
 
         // Style for columns
@@ -166,7 +162,7 @@ if (!$inATest) {
         $styleLevel = "width:6%; float:left; padding-top:8px; text-align:center;";
         $styleScore = "width:4%; float:left; padding-top:8px; text-align:center;";
 
-        $category_list = Testcategory::getCategoryListName();
+        $category_list = Testcategory::getListOfCategoriesNameForTest($objExercise->id, false);
 
         if (is_array($questionList)) {
 			foreach ($questionList as $id) {
@@ -202,7 +198,7 @@ if (!$inATest) {
                     $question_media  = Display::label($objQuestionMedia->question, 'info');
                 }
 
-				$questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media, array('style' => $styleType));
+				$questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM), array('style' => $styleType));
 
 				// Question category
                 $category_labels = Testcategory::return_category_labels($objQuestionTmp->category_list, $category_list);
@@ -210,7 +206,7 @@ if (!$inATest) {
 				if (empty($category_labels)) {
 					$category_labels = "-";
 				}
-				$questionCategory = Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.'</a>', array('style'=>$styleCat));
+				$questionCategory = Display::tag('div', '<a href="#" style="padding:0px; margin:0px;">'.$category_labels.$question_media.'</a>', array('style'=>$styleCat));
 
 				// Question level
 				$txtQuestionLevel = $objQuestionTmp->level;
@@ -239,7 +235,7 @@ if (!$inATest) {
                         echo '<br />';
                         //echo get_lang('Level').': '.$objQuestionTmp->selectLevel();
                         echo '<br />';
-                        showQuestion($id, false, null, null, false, true, false, true, $objExercise->feedback_type, true);
+                        showQuestion($objQuestionTmp, false, null, null, false, true, false, true, $objExercise->feedback_type, true);
                         echo '</p>';
                     echo '</div>';
                 echo '</div>';

+ 66 - 105
main/exercice/question_pool.php

@@ -150,10 +150,7 @@ if ($is_allowedToEdit) {
 		$objExercise->addToList($recup);
 		Session::write('objExercise',$objExercise);
 
-//		header("Location: admin.php?".api_get_cidreq()."&exerciseId=$fromExercise");
-//		exit();
-	}
-	else if( isset($_POST['recup']) && is_array($_POST['recup']) && $fromExercise) {
+    } else if (isset($_POST['recup']) && is_array($_POST['recup']) && $fromExercise) {
 		$list_recup 		= $_POST['recup'];
 
 		foreach ($list_recup as $course_id => $question_data) {
@@ -163,22 +160,6 @@ if ($is_allowedToEdit) {
 			$current_course     = api_get_course_info();
 
 			foreach ($question_data as $old_question_id) {
-				/*
-				$recup = intval($recup);
-				// if the question exists
-				if($objQuestionTmp = Question :: read($recup)) {
-					// adds the exercise ID represented by $fromExercise into the list of exercises for the current question
-					$objQuestionTmp->addToList($fromExercise);
-				}
-				// destruction of the Question object
-				unset($objQuestionTmp);
-		        if(!$objExcercise instanceOf Exercise) {
-		        	$objExercise = new Exercise();
-		            $objExercise->read($fromExercise);
-		        }
-				// adds the question ID represented by $recup into the list of questions for the current exercise
-				$objExercise->addToList($recup);
-				*/
 
 				//Reading the source question
 				$old_question_obj = Question::read($old_question_id, $origin_course_id);
@@ -211,8 +192,6 @@ if ($is_allowedToEdit) {
 			}
 		}
 		Session::write('objExercise',$objExercise);
-//		header("Location: admin.php?".api_get_cidreq()."&exerciseId=$fromExercise");
-//		exit();
 	}
 }
 
@@ -262,8 +241,7 @@ echo '<div class="actions">';
 	if (isset($fromExercise) && $fromExercise > 0) {
 		echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$fromExercise.'">'.Display::return_icon('back.png', get_lang('GoBackToQuestionList'),'',ICON_SIZE_MEDIUM).'</a>';
 		$titleAdd = get_lang('AddQuestionToTest');
-	}
-	else {
+    } else {
 		echo '<a href="exercice.php?'.api_get_cidReq().'">'.Display::return_icon('back.png', get_lang('BackToExercisesList'),'',ICON_SIZE_MEDIUM).'</a>';
 		echo "<a href='admin.php?exerciseId=0'>".Display::return_icon('add_question.gif', get_lang('NewQu'), '', 32)."</a>";
 		$titleAdd = get_lang('ManageAllQuestions');
@@ -316,8 +294,7 @@ echo Display::form_row(get_lang('Course'), $select_course_html);
 if (empty($selected_course) || $selected_course == '-1') {
     $course_info = api_get_course_info();
     reset_menu_exo_lvl_type();    // no course selected, reset menu test / difficult� / type de reponse // hub 13-10-2011
-}
-else {
+} else {
 	$course_info = CourseManager::get_course_information_by_id($selected_course);
 }
 // If course has changed, reset the menu default
@@ -365,7 +342,9 @@ echo Display::form_row(get_lang('Exercise'), $select_exercise_html);
 
 // Difficulty list (only from 0 to 5)
 
-$select_difficulty_html = Display::select('exerciseLevel', array(-1 => get_lang('All'), 0=>0, 1=>1, 2=>2, 3=>3, 4=>4, 5=>5), $exerciseLevel, array('class'=>'chzn-select', 'onchange'=>'submit_form(this);'), false);
+$levels = Question::get_default_levels();
+$levels['-1'] = get_lang('All');
+$select_difficulty_html = Display::select('exerciseLevel', $levels, $exerciseLevel, array('class' => 'chzn-select', 'onchange' => 'submit_form(this);'), false);
 echo Display::form_row(get_lang('Difficulty'), $select_difficulty_html);
 
 
@@ -413,7 +392,7 @@ if ($exerciseId > 0) {
 	$from = '';
 	if (isset($courseCategoryId) && $courseCategoryId > 0) {
 		$from = ", $TBL_COURSE_REL_CATEGORY crc ";
-		$where .= " AND crc.c_id = $selected_course AND crc.question_id=qu.id AND crc.category_id=$courseCategoryId";
+            $where .= " AND crc.c_id = $selected_course AND crc.question_id = qu.iid AND crc.category_id = $courseCategoryId";
 	}
 	if (isset($exerciseLevel) && $exerciseLevel != -1) {
 		$where .= ' AND level='.$exerciseLevel;
@@ -421,22 +400,18 @@ if ($exerciseId > 0) {
 	if (isset($answerType) && $answerType > 0) {
 		$where .= ' AND type='.$answerType;
 	}
-	$sql = "SELECT DISTINCT
-	            id,question,
-	            type,
-	            level
+        $sql = "SELECT DISTINCT iid, question, type, level
 	        FROM
 	            $TBL_EXERCICE_QUESTION qt,
 	            $TBL_QUESTIONS qu
 	            $from
 	        WHERE
-	            qt.question_id=qu.id
+                qt.question_id = qu.iid
 	            AND qt.exercice_id = $exerciseId
 	            AND qt.c_id = $selected_course
 	            AND qu.c_id = $selected_course
 	            $where
-	        ORDER BY
-	            question_order";
+            ORDER BY question_order";
     $result=Database::query($sql);
     while($row = Database::fetch_array($result, 'ASSOC')) {
         $main_question_list[] = $row;
@@ -448,14 +423,14 @@ if ($exerciseId > 0) {
 	$from = '';
 	if (isset($courseCategoryId) && $courseCategoryId > 0) {
 		$from = ", $TBL_COURSE_REL_CATEGORY crc ";
-		$level_where .= " AND crc.c_id=$selected_course AND crc.question_id=qu.id AND crc.category_id = $courseCategoryId";
+            $level_where .= " AND crc.c_id= $selected_course AND crc.question_id = qu.iid AND crc.category_id = $courseCategoryId";
 	}
 	if (isset($exerciseLevel) && $exerciseLevel!= -1 ) {
 		$level_where = ' AND level = '.$exerciseLevel;
 	}
 	$answer_where = '';
 	if (isset($answerType) && $answerType >0 -1 ) {
-		$answer_where = ' AND q.type = '.$answerType;
+        $answer_where = ' AND type = '.$answerType;
 	}
 
 	/*$sql = "SELECT DISTINCT * FROM $TBL_QUESTIONS qu $from
@@ -464,6 +439,8 @@ if ($exerciseId > 0) {
               WHERE c_id=$selected_course
             ) $level_where $answer_where";*/
 
+    // @todo fix this query with the new iid field
+
     $sql = " (
                 SELECT q.* FROM $TBL_QUESTIONS q INNER JOIN $TBL_EXERCICE_QUESTION r
                 ON (q.c_id = r.c_id AND q.id = r.question_id)
@@ -491,16 +468,14 @@ if ($exerciseId > 0) {
         $main_question_list[] = $row;
     }
 } else {
-	//
 	// All tests for selected course
-	//
-	// if we have not selected any option in the list-box 'Filter'
+    // If we have not selected any option in the list-box 'Filter'
 	$filter = '';
 	$from = '';
 
 	if (isset($courseCategoryId) && $courseCategoryId > 0) {
 		$from = ", $TBL_COURSE_REL_CATEGORY crc ";
-		$filter .= " AND crc.c_id=$selected_course AND crc.question_id=qu.id AND crc.category_id=$courseCategoryId";
+            $filter .= " AND crc.c_id = $selected_course AND crc.question_id = qu.iid AND crc.category_id = $courseCategoryId";
 	}
 	if (isset($exerciseLevel) && $exerciseLevel != -1) {
 		$filter .= ' AND level='.$exerciseLevel.' ';
@@ -544,12 +519,13 @@ if ($exerciseId > 0) {
                      continue;
                    }
                 }
-                $question_row = array(	'id'			=> $question_obj->id,
-                						'question'		=> $question_obj->question,
-                						'type'			=> $question_obj->type,
-                						'level'			=> $question_obj->level,
-                						'exercise_id'	=> $exercise['id'],
-                						'course_id'		=> $course_item['id'],
+                $question_row = array(
+                    'id' => $question_obj->id,
+					'question'		=> $question_obj->question,
+					'type'			=> $question_obj->type,
+					'level'			=> $question_obj->level,
+					'exercise_id'	=> $exercise['id'],
+					'course_id'		=> $course_item['id'],
                 );
                 $main_question_list[]    = $question_row;
               }
@@ -558,12 +534,16 @@ if ($exerciseId > 0) {
         }
       }
     }
-  }
-  else {
-  	//
+} else {
     // All tests for the course selected, not in session
-    //
-  	$sql = "SELECT DISTINCT qu.id, question, qu.type, level, q.session_id FROM $TBL_QUESTIONS as qu, $TBL_EXERCICE_QUESTION as qt, $TBL_EXERCICES as q $from WHERE qu.c_id=$selected_course AND qt.c_id=$selected_course AND q.c_id=$selected_course AND qu.id = qt.question_id AND q.id = qt.exercice_id $filter ORDER BY session_id ASC";
+            $sql = "SELECT DISTINCT qu.iid, question, qu.type, level, q.session_id
+                    FROM $TBL_QUESTIONS as qu, $TBL_EXERCICE_QUESTION as qt, $TBL_EXERCICES as q $from
+                    WHERE   qu.c_id = $selected_course AND
+                            qt.c_id = $selected_course AND
+                            q.c_id = $selected_course AND
+                            qu.iid = qt.question_id AND
+                            q.iid = qt.exercice_id $filter
+                    ORDER BY session_id ASC";
   	$result = Database::query($sql);
   	while($row = Database::fetch_array($result, 'ASSOC')) {
   		$main_question_list[] = $row;
@@ -599,31 +579,26 @@ if ($fromExercise <= 0) { // NOT IN A TEST - IN THE COURSE
 		$actionIcon1 = "edit";
 		$actionIcon2 = "delete";
 		$questionTagA = 1;	// we are in the course, question title can be a link to the question edit page
-	}
-	else { // NOT IN A TEST - NOT IN THE COURSE
+    } else { // NOT IN A TEST - NOT IN THE COURSE
 		$actionLabel = get_lang('Reuse');
 		$actionIcon1 = get_lang('MustBeInATest');
 		$actionIcon2 = "";
 		$questionTagA = 0;	// we are not in this course, to messy if we link to the question in another course
 	}
-}
-else { // IN A TEST - IN THE COURSE
+} else { // IN A TEST - IN THE COURSE
 	if ($selected_course == api_get_course_int_id()) {
 		$actionLabel = get_lang('Reuse');
 		$actionIcon1 = "add";
 		$actionIcon2 = "";
 		$questionTagA = 1;
-	}
-	else { // IN A TEST - NOT IN THE COURSE
+    } else { // IN A TEST - NOT IN THE COURSE
 		$actionLabel = get_lang('Reuse');
 		$actionIcon1 = "clone";
 		$actionIcon2 = "";
 		$questionTagA = 0;
 	}
 }
-//
-// display table
-//
+// Display table
 $header = array();
 $header[] = array(get_lang('QuestionUpperCaseFirstLetter'), false, array("style"=>"text-align:center"), '');
 $header[] = array(get_lang('Type'), false, array("style"=>"text-align:center"), array("style"=>"text-align:center"), '');
@@ -635,34 +610,24 @@ $data = array();
 
 if (is_array($main_question_list)) {
     $all_category_list = Testcategory::getCategoryListName($selected_course);
-    foreach ($main_question_list as $tabQuestion) {
+    foreach ($main_question_list as $question_item) {
         $row = array();
+            $question_id_item = $question_item['iid'];
 
         //This function checks if the question can be read
-        $question_type = get_question_type_for_question($selected_course, $tabQuestion['id']);
+            $question_type = get_question_type_for_question($selected_course, $question_id_item);
 
         if (empty($question_type)) {
             continue;
         }
 
-        $row[] = get_a_tag_for_question($questionTagA, $fromExercise, $tabQuestion['id'], $tabQuestion['type'], $tabQuestion['question']);
+        $row[] = get_a_tag_for_question($questionTagA, $fromExercise, $question_id_item, $question_item['type'], $question_item['question']);
         $row[] = $question_type;
-        $row[] = get_question_categorie_for_question($selected_course, $tabQuestion['id']);
-        $row[] = $tabQuestion['level'];
-        $row[] = get_action_icon_for_question($actionIcon1,
-                    $fromExercise,
-                    $tabQuestion['id'],
-                    $tabQuestion['type'],
-                    $tabQuestion['question'],
-                    $selected_course,
-                    $courseCategoryId,
-                    $exerciseLevel,
-                    $answerType,
-                    $session_id, $exerciseId).
-                    "&nbsp;".
-                    get_action_icon_for_question($actionIcon2, $fromExercise, $tabQuestion['id'], $tabQuestion['type'],
-                    $tabQuestion['question'], $selected_course, $courseCategoryId, $exerciseLevel, $answerType,
-                    $session_id, $exerciseId);
+        $row[] = get_question_category_for_question($selected_course, $question_id_item);
+        $row[] = $question_item['level'];
+        $row[] = get_action_icon_for_question($actionIcon1, $fromExercise, $question_id_item, $question_item['type'], $question_item['question'], $selected_course, $courseCategoryId, $exerciseLevel, $answerType, $session_id, $exerciseId).
+                "&nbsp;".
+            get_action_icon_for_question($actionIcon2, $fromExercise, $question_id_item, $question_item['type'], $question_item['question'], $selected_course, $courseCategoryId, $exerciseLevel, $answerType, $session_id, $exerciseId);
         $data[] = $row;
     }
 }
@@ -706,7 +671,6 @@ function reset_menu_exo_lvl_type() {
 	$courseCategoryId = 0;
 }
 
-//
 // return the <a> link to admin question, if needed
 // hubert.borderiou 13-10-2011
 function get_a_tag_for_question($in_addA, $in_fromex, $in_questionid, $in_questiontype, $in_questionname) {
@@ -740,23 +704,21 @@ $session_id, $exerciseId);
 
 */
 
-//
-// return the <a> html code for delete, add, clone, edit a question
-// hubert.borderiou 13-10-2011
-// in_action = the code of the action triggered by the button
-// from_exercice = the id of the current exercice from which we click on question pool
-// in_questionid = the id of the current question
-// in_questiontype = the code of the type of the current question
-// in_questionname = the name of the question
-// in_selected_course = the if of the course chosen in the FILTERING MENU
-// in_courseCategoryId = the id of the category chosen in the FILTERING MENU
-// in_exerciseLevel = the level of the exercice chosen in the FILTERING MENU
-// in_answerType = the code of the type of the question chosen in the FILTERING MENU
-// in_session_id = the id of the session_id chosen in the FILTERING MENU
-// in_exercice_id = the id of the exercice chosen in the FILTERING MENU
-function get_action_icon_for_question($in_action, $from_exercice, $in_questionid, $in_questiontype, $in_questionname,
-    $in_selected_course, $in_courseCategoryId, $in_exerciseLevel, $in_answerType, $in_session_id, $in_exercice_id
-) {
+/*
+    Return the <a> html code for delete, add, clone, edit a question hubert.borderiou 13-10-2011
+    in_action = the code of the action triggered by the button
+    from_exercice = the id of the current exercice from which we click on question pool
+    in_questionid = the id of the current question
+    in_questiontype = the code of the type of the current question
+    in_questionname = the name of the question
+    in_selected_course = the if of the course chosen in the FILTERING MENU
+    in_courseCategoryId = the id of the category chosen in the FILTERING MENU
+    in_exerciseLevel = the level of the exercice chosen in the FILTERING MENU
+    in_answerType = the code of the type of the question chosen in the FILTERING MENU
+    in_session_id = the id of the session_id chosen in the FILTERING MENU
+    in_exercice_id = the id of the exercice chosen in the FILTERING MENU
+ */
+function get_action_icon_for_question($in_action, $from_exercice, $in_questionid, $in_questiontype, $in_questionname, $in_selected_course, $in_courseCategoryId, $in_exerciseLevel, $in_answerType, $in_session_id, $in_exercice_id) {
 
 	$res = "";
 	$getParams = "&selected_course=$in_selected_course&courseCategoryId=$in_courseCategoryId&exerciseId=$in_exercice_id&exerciseLevel=$in_exerciseLevel&answerType=$in_answerType&session_id=$in_session_id";
@@ -773,13 +735,13 @@ function get_action_icon_for_question($in_action, $from_exercice, $in_questionid
 		case "add":
 			// add if question is not already in test
 			$myObjEx = new Exercise();
+            $myObjEx->setCategoriesGrouping(false);
 			$myObjEx->read($from_exercice);
 			if (!$myObjEx->isInList($in_questionid)) {
 				$res = "<a href='".api_get_self()."?".api_get_cidreq()."&recup=$in_questionid&fromExercise=$from_exercice$getParams'>";
 				$res .= Display::return_icon("view_more_stats.gif", get_lang('InsertALinkToThisQuestionInTheExercise'));
 				$res .= "</a>";
-			}
-			else {
+            } else {
 				$res = "-";
 			}
 			unset($myObjEx);
@@ -796,8 +758,8 @@ function get_action_icon_for_question($in_action, $from_exercice, $in_questionid
 	return $res;
 }
 
-// return the icon for the question type
-// hubert.borderiou 13-10-2011
+/* return the icon for the question type
+ hubert.borderiou 13-10-2011*/
 function get_question_type_for_question($in_selectedcourse, $in_questionid) {
 	$myObjQuestion = Question::read($in_questionid, $in_selectedcourse);
     $questionType = null;
@@ -809,9 +771,8 @@ function get_question_type_for_question($in_selectedcourse, $in_questionid) {
 	return $questionType;
 }
 
-// return the name of the category for the question in a course
-// hubert.borderiou 13-10-2011
-function get_question_categorie_for_question($course_id, $question_id) {
+/* return the name of the category for the question in a course hubert.borderiou 13-10-2011*/
+function get_question_category_for_question($course_id, $question_id) {
     global $all_category_list;
     $objQuestionTmp = Question :: read($question_id, $course_id);
     return Testcategory::return_category_labels($objQuestionTmp->category_list, $all_category_list);

+ 1 - 1
main/exercice/result.php

@@ -75,4 +75,4 @@ if ($show_headers) {
 
 display_question_list_by_attempt($objExercise, $id, false);
 
-Display::display_footer();
+Display::display_footer();

+ 1 - 0
main/exercice/savescores.php

@@ -36,6 +36,7 @@ $full_file_path = $documentPath.$test;
 FileManager::my_delete($full_file_path.$_user['user_id'].".t.html");
 
 $TABLETRACK_HOTPOTATOES = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_HOTPOTATOES);
+$tbl_learnpath_user = Database::get_course_table(TABLE_LEARNPATH_USER);
 $TABLE_LP_ITEM_VIEW = Database::get_course_table(TABLE_LP_ITEM_VIEW);
 
 $jscript2run = '';

+ 422 - 117
main/exercice/testcategory.class.php

@@ -1,73 +1,166 @@
 <?php
 /* For licensing terms, see /license.txt */
-/** @author hubert.borderiou  **/
-
-if (!class_exists('Testcategory')):
-
-class Testcategory {
+/**
+ * @author hubert.borderiou
+ */
+class Testcategory
+{
 	public $id;
-	public $name;
+    public $name; //why using name?? Use title as in the db!
+    public $title;
 	public $description;
+    public $parent_id;
+    public $parent_path;
+    public $category_array_tree;
+    public $type;
+    public $course_id;
 
 	/**
 	 * Constructor of the class Category
 	 * @author - Hubert Borderiou
 	 If you give an in_id and no in_name, you get info concerning the category of id=in_id
 	 otherwise, you've got an category objet avec your in_id, in_name, in_descr
+     * @todo fix this function
 	 */
-	function Testcategory($in_id=0, $in_name = '', $in_description="") {
+    public function Testcategory($in_id = 0, $in_name = '', $in_description = "", $parent_id = 0, $type = 'simple', $course_id = null)
+    {
 		if ($in_id != 0 && $in_name == "") {
 			$tmpobj = new Testcategory();
 			$tmpobj->getCategory($in_id);
 			$this->id = $tmpobj->id;
 			$this->name = $tmpobj->name;
+            $this->title = $tmpobj->name;
 			$this->description = $tmpobj->description;
+            $this->parent_id = $tmpobj->parent_id;
+            $this->parent_path = $this->name;
+
+            if (!empty($tmpobj->parent_id)) {
+                $category = new Testcategory($tmpobj->parent_id);
+                $this->parent_path = $category->parent_path.' > '.$this->name;
 		}
-		else {
+        } else {
 			$this->id = $in_id;
 			$this->name = $in_name;
 			$this->description = $in_description;
-		}
+            $this->parent_id = $parent_id;
+        }
+        $this->type = $type;
+
+        if (!empty($course_id))  {
+            $this->course_id = $course_id;
+		} else {
+            $this->course_id = api_get_course_int_id();
+        }
 	}
 
-	/** return the Testcategory object with id=in_id
+    /**
+     * Return the Testcategory object with id=in_id
+     *
+     * @assert () === false
 	 */
 	function getCategory($in_id) {
 		$t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
 		$in_id = Database::escape_string($in_id);
-		$sql = "SELECT * FROM $t_cattable WHERE id=$in_id AND c_id=".api_get_course_int_id();
+        $sql = "SELECT * FROM $t_cattable WHERE iid = $in_id ";
 		$res = Database::query($sql);
 		$numrows = Database::num_rows($res);
 		if ($numrows > 0) {
 			$row = Database::fetch_array($res);
-			$this->id = $row['id'];
-			$this->name = $row['title'];
+            $this->id = $row['iid'];
+            $this->title = $this->name = $row['title'];
 			$this->description  = $row['description'];
+            $this->parent_id = $row['parent_id'];
+        } else {
+            return false;
 		}
 	}
 
-	/** add Testcategory in the database if name doesn't already exists
-		*/
+    /**
+     * Add Testcategory in the database if name doesn't already exists
+     *
+	 */
 	function addCategoryInBDD() {
 		$t_cattable = Database :: get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-		$v_name = $this->name;
-		$v_name = Database::escape_string($v_name);
-		$v_description = $this->description;
-		$v_description = Database::escape_string($v_description);
-		// check if name already exists
-		$sql_verif = "SELECT count(*) AS nb FROM $t_cattable WHERE title = '$v_name' AND c_id=".api_get_course_int_id();
-		$result_verif = Database::query($sql_verif);
-		$data_verif = Database::fetch_array($result_verif);
-		// lets add in BDD if not the same name
-		if ($data_verif['nb'] <= 0) {
-			$c_id = api_get_course_int_id();
-			$sql = "INSERT INTO $t_cattable VALUES ('$c_id', '', '$v_name', '$v_description')";
-			$res = Database::query($sql);
-			return true;
-		}
-		else {
-			return false;
+        $v_name = Database::escape_string($this->name);
+        $v_description = Database::escape_string($this->description);
+        $parent_id = intval($this->parent_id);
+
+        $course_id = $this->course_id;
+        $courseCondition = " AND c_id = $course_id ";
+        if ($this->type == 'global') {
+            $course_id = '';
+            $courseCondition = null;
+        }
+		// Check if name already exists
+        $sql = "SELECT count(*) AS nb FROM $t_cattable WHERE title = '$v_name' $courseCondition";
+		$result = Database::query($sql);
+		$data = Database::fetch_array($result);
+        // lets add in BD if not the same name
+		if ($data['nb'] <= 0) {
+            $sql = "INSERT INTO $t_cattable (c_id, title, description, parent_id) VALUES ('$course_id', '$v_name', '$v_description', '$parent_id')";
+            Database::query($sql);
+            return Database::insert_id();
+        } else {
+            return false;
 		}
+    }
+
+    function get_category_by_title($title , $course_id = 0) {
+        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $course_id = intval($course_id);
+        $sql = "SELECT * FROM $table WHERE title = '$title' AND c_id IN ('0', '$course_id')LIMIT 1";
+        $title = Database::escape_string($title);
+        $result = Database::query($sql);
+        if (Database::num_rows($result)) {
+            $result = Database::store_result($result, 'ASSOC');
+            return $result[0];
+        }
+		return false;
+	}
+    /**
+     *
+     * Get categories by title for json calls
+     *
+     * @assert() === false
+     */
+    function get_categories_by_keyword($tag) {
+        if (empty($tag)) {
+            return false;
+        }
+        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $sql = "SELECT iid, title, c_id FROM $table WHERE 1=1 ";
+        $tag = Database::escape_string($tag);
+
+        $where_condition = array();
+        if (!empty($tag)) {
+            $condition = ' LIKE "%'.$tag.'%"';
+            $where_condition = array(
+                "title $condition",
+            );
+            $where_condition = ' AND  ('.implode(' OR ',  $where_condition).') ';
+        }
+
+        switch ($this->type) {
+            case 'simple':
+                $course_condition = " AND c_id = '".api_get_course_int_id()."' ";
+                break;
+            case 'global':
+                $course_condition = " AND c_id = '0' ";
+                break;
+            case 'all':
+                $course_condition = " AND c_id IN ('0', '".api_get_course_int_id()."')";
+                break;
+        }
+
+        $where_condition .= $course_condition;
+        $order_clause = " ORDER BY title";
+        $sql .= $where_condition.$order_clause;
+
+        $result = Database::query($sql);
+        if (Database::num_rows($result)) {
+            return Database::store_result($result, 'ASSOC');
+        }
+        return false;
 	}
 
 	/**
@@ -75,12 +168,11 @@ class Testcategory {
      * @todo I'm removing the $in_id parameter because it seems that you're using $this->id instead of $in_id after confirmation delete this
      * jmontoya
 	 */
-	//function removeCategory($in_id) {
     function removeCategory() {
 		$t_cattable = Database :: get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
 		$v_id = Database::escape_string($this->id);
-		$sql = "DELETE FROM $t_cattable WHERE id=$v_id AND c_id=".api_get_course_int_id();
-		$res = Database::query($sql);
+        $sql = "DELETE FROM $t_cattable WHERE iid = $v_id";
+        Database::query($sql);
 		if (Database::affected_rows() <= 0) {
 			return false;
 		} else {
@@ -89,20 +181,29 @@ class Testcategory {
 	}
 
 
-	/** modify category name or description of category with id=in_id
+    /**
+     * Modify category name or description of category with id=in_id
 	 */
-	//function modifyCategory($in_id, $in_name, $in_description) {
     function modifyCategory() {
 		$t_cattable = Database :: get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
 		$v_id = Database::escape_string($this->id);
 		$v_name = Database::escape_string($this->name);
 		$v_description = Database::escape_string($this->description);
-		$sql = "UPDATE $t_cattable SET title='$v_name', description='$v_description' WHERE id='$v_id' AND c_id=".api_get_course_int_id();
-		$res = Database::query($sql);
+        $parent_id = intval($this->parent_id);
+
+        //Avoid recursive categories
+        if ($parent_id == $v_id) {
+            $parent_id = 0;
+        }
+        $sql = "UPDATE $t_cattable SET
+                    title = '$v_name',
+                    description = '$v_description',
+                    parent_id = '$parent_id'
+                WHERE iid = '$v_id'";
+        Database::query($sql);
 		if (Database::affected_rows() <= 0) {
 			return false;
-		}
-		else {
+        } else {
 			return true;
 		}
 	}
@@ -116,7 +217,7 @@ class Testcategory {
 	function getCategoryQuestionsNumber() {
 		$t_reltable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
 		$in_id = Database::escape_string($this->id);
-		$sql = "SELECT count(*) AS nb FROM $t_reltable WHERE category_id=$in_id AND c_id=".api_get_course_int_id();
+        $sql = "SELECT count(*) AS nb FROM $t_reltable WHERE category_id = $in_id";
 		$res = Database::query($sql);
 		$row = Database::fetch_array($res);
 		return $row['nb'];
@@ -145,11 +246,10 @@ class Testcategory {
 			$sql = "SELECT * FROM $t_cattable WHERE c_id=$in_courseid ORDER BY title ASC";
 			$res = Database::query($sql);
 			while ($row = Database::fetch_array($res)) {
-				$tmpcat = new Testcategory($row['id'], $row['title'], $row['description']);
+                $tmpcat = new Testcategory($row['iid'], $row['title'], $row['description'], $row['parent_id']);
 				$tabres[] = $tmpcat;
 			}
-		}
-		else {
+        } else {
 			$sql = "SELECT $in_field FROM $t_cattable WHERE c_id=$in_courseid ORDER BY $in_field ASC";
 			$res = Database::query($sql);
 			while ($row = Database::fetch_array($res)) {
@@ -164,39 +264,68 @@ class Testcategory {
 	 Return the testcategory id for question with question_id = $in_questionid
 	 In this version, a question has only 1 testcategory.
 	 Return the testcategory id, 0 if none
+     * @assert () === false
 	 */
-	public static function getCategoryForQuestion($in_questionid, $in_courseid = null) {
+    public static function getCategoryForQuestion($question_id, $in_courseid = null) {
 		$result = array();	// result
 		if (empty($in_courseid) || $in_courseid=="") {
 			$in_courseid = api_get_course_int_id();
 		}
 		$t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-		$question_id = Database::escape_string($in_questionid);
+        $question_id = Database::escape_string($question_id);
 		$sql = "SELECT category_id FROM $t_cattable WHERE question_id = '$question_id' AND c_id = $in_courseid";
 		$res = Database::query($sql);
 		if (Database::num_rows($res) > 0) {
-            while ($row = Database::fetch_array($res)) {
+            while ($row = Database::fetch_array($res, 'ASSOC')) {
                 $result[] = $row['category_id'];
             }
 		}
 		return $result;
 	}
 
-    public static function getCategoryNamesForQuestion($in_questionid, $in_courseid = null, $display_into_labels = true) {
+    public static function getCategoryForQuestionWithCategoryData($question_id, $in_courseid = null) {
 		$result = array();	// result
 		if (empty($in_courseid) || $in_courseid=="") {
 			$in_courseid = api_get_course_int_id();
 		}
 		$t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
         $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-		$question_id = Database::escape_string($in_questionid);
-		$sql = "SELECT c.title FROM $t_cattable qc INNER JOIN $table_category c
-                        ON (qc.category_id = c.id AND qc.c_id = $in_courseid AND c.c_id = $in_courseid)
+        $question_id = Database::escape_string($question_id);
+        $sql = "SELECT * FROM $t_cattable qc INNER JOIN $table_category c ON (category_id = c.iid) WHERE question_id = '$question_id' AND qc.c_id = $in_courseid";
+        $res = Database::query($sql);
+        if (Database::num_rows($res) > 0) {
+            while ($row = Database::fetch_array($res, 'ASSOC')) {
+                $result[] = $row;
+            }
+        }
+        return $result;
+    }
+
+    /**
+     *
+     * @param int $question_id
+     * @param int $course_id
+     * @param bool $display_into_labels
+     * @return string
+     */
+    public static function getCategoryNamesForQuestion($question_id, $course_id = null, $display_into_labels = true) {
+        $result = array(); // result
+        if (empty($course_id) || $course_id == "") {
+            $course_id = api_get_course_int_id();
+        }
+        $t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
+        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $question_id = intval($question_id);
+        $course_id = intval($course_id);
+
+        $sql = "SELECT c.title, c.iid FROM $t_cattable qc INNER JOIN $table_category c
+                    ON (qc.category_id = c.iid AND qc.c_id = $course_id)
                      WHERE question_id = '$question_id' ";
 		$res = Database::query($sql);
 		if (Database::num_rows($res) > 0) {
             while ($row = Database::fetch_array($res)) {
-                $result[] = $row['title'];
+                $cat = new Testcategory($row['iid']);
+                $result[] = $cat->parent_path;
             }
 		}
 
@@ -209,9 +338,12 @@ class Testcategory {
 
 	/**
 	 * true if question id has a category
+     * @assert () === false
+     * @assert (null) === false
+     * @assert (-1) === false
 	 */
-	public static function isQuestionHasCategory($in_questionid) {
-        $category_list = Testcategory::getCategoryForQuestion($in_questionid);
+    public static function isQuestionHasCategory($question_id) {
+        $category_list = Testcategory::getCategoryForQuestion($question_id);
 		if (!empty($category_list)) {
 			return true;
 		}
@@ -225,18 +357,16 @@ class Testcategory {
 	 In this version, a question has only 1 category.
 	 Return the category id, "" if none
 	 */
-	public static function getCategoryNameForQuestion($question_id, $course_id = null) {
+    public static function getCategoryNameForQuestion($catid, $course_id = null) {
 		if (empty($course_id) || $course_id == "") {
 			$course_id = api_get_course_int_id();
 		}
         $course_id = intval($course_id);
 
-		$category_list = Testcategory::getCategoryForQuestion($question_id, $course_id);
-
-		$result = array();	// result
+        $result = array();
 		$t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
 		$catid = Database::escape_string($catid);
-		$sql = "SELECT title FROM $t_cattable WHERE id='$catid' AND c_id = $course_id";
+        $sql = "SELECT title FROM $t_cattable WHERE iid = '$catid' AND c_id = $course_id";
 		$res = Database::query($sql);
 		$data = Database::fetch_array($res);
 		if (Database::num_rows($res) > 0) {
@@ -251,15 +381,34 @@ class Testcategory {
 	 * return : array of category id (integer)
 	 * @author hubert.borderiou 07-04-2011, Julio Montoya
 	 */
-	public static function getListOfCategoriesIDForTest($exercise_id) {
+    public static function getListOfCategoriesIDForTest($exercise_id, $grouped_by_category = true) {
 		// parcourir les questions d'un test, recup les categories uniques dans un tableau
 		$categories_in_exercise = array();
-		$quiz = new Exercise();
-		$quiz->read($exercise_id);
-		$question_list = $quiz->selectQuestionList();
+        $exercise = new Exercise();
+        $exercise->setCategoriesGrouping($grouped_by_category);
+        $exercise->read($exercise_id);
+        $question_list = $exercise->selectQuestionList();
 		// the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
-		for ($i=1; $i <= count($question_list); $i++) {
-            $category_list = Testcategory::getCategoryForQuestion($question_list[$i]);
+        foreach ($question_list as $question_id) {
+            $category_list = Testcategory::getCategoryForQuestion($question_id);
+            if (!empty($category_list)) {
+                $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
+            }
+        }
+        if (!empty($categories_in_exercise)) {
+            $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
+        }
+        return $categories_in_exercise;
+    }
+
+    public static function getListOfCategoriesIDForTestObject($exercise_obj) {
+        // parcourir les questions d'un test, recup les categories uniques dans un tableau
+        $categories_in_exercise = array();
+        $question_list = $exercise_obj->selectQuestionList();
+
+        // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
+        foreach ($question_list as $question_id) {
+            $category_list = Testcategory::getCategoryForQuestion($question_id);
             if (!empty($category_list)) {
 				$categories_in_exercise = array_merge($categories_in_exercise, $category_list);
             }
@@ -277,14 +426,29 @@ class Testcategory {
 	 * hubert.borderiou 07-04-2011
      * @author function rewrote by jmontoya
 	 */
-	public static function getListOfCategoriesNameForTest($in_testid) {
-		$tabcatName = array();
-		$tabcatID = self::getListOfCategoriesIDForTest($in_testid);
-		for ($i=0; $i < count($tabcatID); $i++) {
-			$cat = new Testcategory($tabcatID[$i]);
-			$tabcatName[$cat->id] = $cat->name;
+    public static function getListOfCategoriesNameForTest($exercise_id, $grouped_by_category = true) {
+        $result = array();
+        $categories = self::getListOfCategoriesIDForTest($exercise_id, $grouped_by_category);
+        foreach ($categories as $cat_id) {
+            $cat = new Testcategory($cat_id);
+            if (!empty($cat->id)) {
+                $result[$cat->id] = $cat->name;
 		}
-		return $tabcatName;
+        }
+        return $result;
+    }
+
+    public static function getListOfCategoriesForTest($exercise_obj) {
+        $result = array();
+        $categories = self::getListOfCategoriesIDForTestObject($exercise_obj);
+        foreach ($categories as $cat_id) {
+            $cat = new Testcategory($cat_id);
+            $cat = (array)$cat;
+            $cat['iid'] = $cat['id'];
+            $cat['title'] = $cat['name'];
+            $result[$cat['id']] = $cat;
+        }
+        return $result;
 	}
 
 	/**
@@ -293,8 +457,8 @@ class Testcategory {
 	 * return : integer
 	 * hubert.borderiou 07-04-2011
 	 */
-	public static function getNumberOfCategoriesForTest($in_testid) {
-		return count(Testcategory::getListOfCategoriesIDForTest($in_testid));
+    public static function getNumberOfCategoriesForTest($exercise_id) {
+        return count(Testcategory::getListOfCategoriesIDForTest($exercise_id));
 	}
 
 	/**
@@ -309,8 +473,8 @@ class Testcategory {
 		$exercise->read($exercise_id);
 		$question_list = $exercise->selectQuestionList();
 		// the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
-		for ($i=1; $i <= count($question_list); $i++) {
-            $category_in_question = Testcategory::getCategoryForQuestion($question_list[$i]);
+        foreach ($question_list as $question_id) {
+            $category_in_question = Testcategory::getCategoryForQuestion($question_id);
 			if (in_array($category_id, $category_in_question)) {
 				$number_questions_in_category++;
 			}
@@ -329,9 +493,10 @@ class Testcategory {
 		$list_categories = Testcategory::getListOfCategoriesIDForTest($exercise_id);
 
         if (!empty($list_categories)) {
-            for ($i=0; $i < count($list_categories); $i++) {
-                if ($list_categories[$i] > 0) {	// 0 = no category for this question
-                    $nbQuestionInThisCat = Testcategory::getNumberOfQuestionsInCategoryForTest($exercise_id, $list_categories[$i]);
+            foreach ($list_categories as $category_item) {
+                if ($category_item > 0) {
+                    // 0 = no category for this question
+                    $nbQuestionInThisCat = Testcategory::getNumberOfQuestionsInCategoryForTest($exercise_id, $category_item);
 
                     if ($nbQuestionInThisCat > $in_nbrandom) {
                         $nbquestionresult += $in_nbrandom;
@@ -363,39 +528,48 @@ class Testcategory {
 	}
 
 	/**
-		* return an array of question_id for each category
-		* tabres[0] = array of question id with category id = 0 (i.e. no category)
-		* tabres[24] = array of question id with category id = 24
-		* In this version, a question has 0 or 1 category
+     * Returns an array of question ids for each category
+     * $categories[1][30] = 10, array with category id = 1 and question_id = 10
+     * A question has "n" categories
 	 */
-	static function getQuestionsByCat($in_exerciceId) {
-		$tabres = array();
+    static function getQuestionsByCat($exerciceId, $check_in_question_list = array()) {
+        $categories = array();
 		$TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
 		$TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
-        $in_exerciceId = intval($in_exerciceId);
-		$sql = "SELECT qrc.question_id, qrc.category_id FROM $TBL_QUESTION_REL_CATEGORY qrc, $TBL_EXERCICE_QUESTION eq
-                WHERE exercice_id=$in_exerciceId AND eq.question_id=qrc.question_id AND eq.c_id=".api_get_course_int_id()." AND eq.c_id=qrc.c_id ORDER BY category_id, question_id";
+        $exerciceId = intval($exerciceId);
+        $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
+                FROM $TBL_QUESTION_REL_CATEGORY qrc, $TBL_EXERCICE_QUESTION eq
+                WHERE   exercice_id = $exerciceId AND
+                        eq.question_id = qrc.question_id AND
+                        eq.c_id =".api_get_course_int_id()." AND
+                        eq.c_id = qrc.c_id
+                ORDER BY category_id, question_id";
 		$res = Database::query($sql);
 		while ($data = Database::fetch_array($res)) {
-			if (!is_array($tabres[$data['category_id']])) {
-				$tabres[$data['category_id']] = array();
-			}
-			$tabres[$data['category_id']][] = $data['question_id'];
-		}
-		return $tabres;
-	}
+            if (!empty($check_in_question_list)) {
+                if (!in_array($data['question_id'], $check_in_question_list)) {
+                    continue;
+			    }
+		    }
+
+            if (!is_array($categories[$data['category_id']])) {
+                $categories[$data['category_id']] = array();
+            }
 
+            $categories[$data['category_id']][] = $data['question_id'];
+        }
+        return $categories;
+    }
 
 	/**
-	 * return a tab of $in_number random elements of $in_tab
+     * Return an array of X elements of an array
 	 */
-	static function getNElementsFromArray($in_tab, $in_number) {
-		$tabres = $in_tab;
-		shuffle($tabres);
-		if ($in_number < count($tabres)) {
-			$tabres = array_slice($tabres, 0, $in_number);
+    static function getNElementsFromArray($array, $random_number) {
+        shuffle($array);
+        if ($random_number < count($array)) {
+            $array = array_slice($array, 0, $random_number);
 		}
-		return $tabres;
+        return $array;
 	}
 
 	/**
@@ -412,24 +586,24 @@ class Testcategory {
 	}
 
 	/**
-	 * sortTabByBracketLabel ($tabCategoryQuestions)
-	 * key of $tabCategoryQuestions are the categopy id (0 for not in a category)
+     * key of $array are the categopy id (0 for not in a category)
 	 * value is the array of question id of this category
 	 * Sort question by Category
 	*/
-	static function sortTabByBracketLabel($in_tab) {
+    static function sortCategoriesQuestionByName($array) {
 		$tabResult = array();
-		$tabCatName = array();	// tab of category name
-		while (list($cat_id, $tabquestion) = each($in_tab)) {
-			$catTitle = new Testcategory($cat_id);
-			$tabCatName[$cat_id] = $catTitle->name;
+        $category_array = array();
+        // tab of category name
+        while (list($cat_id, $tabquestion) = each($array)) {
+            $cat = new Testcategory($cat_id);
+            $category_array[$cat_id] = $cat->title;
 		}
-		reset($in_tab);
+        reset($array);
 		// sort table by value, keeping keys as they are
-		asort($tabCatName);
+        asort($category_array);
 		// keys of $tabCatName are keys order for $in_tab
-		while (list($key, $val) = each($tabCatName)) {
-			$tabResult[$key] = $in_tab[$key];
+        while (list($key, $val) = each($category_array)) {
+            $tabResult[$key] = $array[$key];
 		}
 		return $tabResult;
 	}
@@ -538,7 +712,7 @@ class Testcategory {
         if (empty($category_list)) {
             return null;
         }
-        $category_name_list = Testcategory::getListOfCategoriesNameForTest($exercise_id);
+        $category_name_list = Testcategory::getListOfCategoriesNameForTest($exercise_id, false);
 
         $table = new HTML_Table(array('class' => 'data_table'));
         $table->setHeaderContents(0, 0, get_lang('Categories'));
@@ -581,7 +755,138 @@ class Testcategory {
         return null;
     }
 
+    function get_all_categories() {
+        $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $sql = "SELECT * FROM $table ORDER BY title ASC";
+        $res = Database::query($sql);
+        while ($row = Database::fetch_array($res,'ASSOC')) {
+            $array[] = $row;
+        }
+        return $array;
+    }
+
+    function get_category_exercise_tree($exercise_id, $course_id, $order = null) {
+        $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
+        $table_category = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+        $sql = "SELECT * FROM $table qc INNER JOIN $table_category c ON (category_id = c.iid) WHERE exercise_id = {$exercise_id} AND qc.c_id = {$course_id} ";
+
+        if (!empty($order)) {
+            $sql .= "ORDER BY $order";
+        }
+
+        $result = Database::query($sql);
+        if (Database::num_rows($result)) {
+             while ($row = Database::fetch_array($result, 'ASSOC')) {
+                $list[$row['category_id']] = $row;
+            }
+            if (!empty($list)) {
+                $array = $this->sort_tree_array($list);
+                $this->create_tree_array($array);
+            }
+            return $this->category_array_tree;
+        }
+        return false;
+    }
+
+
+
+    function get_category_tree_by_type($type) {
+        $course_id = api_get_course_int_id();
+        if ($type == 'global') {
+            $course_condition = 'c_id = 0';
+        } else {
+            $course_condition = " c_id IN ('$course_id', '0') ";
+        }
+        $t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
+
+        $sql = "SELECT * FROM $t_cattable WHERE $course_condition";
+        $res = Database::query($sql);
+        $categories = array();
+        while ($row = Database::fetch_array($res, 'ASSOC')) {
+            $row['id'] = $row['iid'];
+            $categories[] = $row;
+        }
+        if (!empty($categories)) {
+            $array = $this->sort_tree_array($categories);
+            $this->create_tree_array($array);
+        }
+        return $this->category_array_tree;
+    }
+
 
+    function return_category_form($exercise_obj) {
+
+        $categories = $this->getListOfCategoriesForTest($exercise_obj);
+
+        if (!empty($categories)) {
+            $array = $this->sort_tree_array($categories);
+            $this->create_tree_array($array);
+        }
+
+        $saved_categories = $exercise_obj->get_categories_in_exercise();
+
+        if (!empty($this->category_array_tree)) {
+            $nbQuestionsTotal = $exercise_obj->getNumberQuestionExerciseCategory();
+            $original_grouping = $exercise_obj->categories_grouping;
+            $exercise_obj->setCategoriesGrouping(true);
+            $real_question_count = count($exercise_obj->selectQuestionList(true));
+            $exercise_obj->setCategoriesGrouping($original_grouping);
+
+            $warning = null;
+            if ($nbQuestionsTotal != $real_question_count) {
+                $warning = Display::return_message(get_lang('CheckThatYouHaveEnoughQuestionsInYourCategories'), 'warning');
+            }
 
-}
-endif;
+            $return .= $warning;
+            $return .= '<table class="data_table">';
+            $return .= '<tr>';
+            $return .= '<th height="24">' . get_lang('Categories') . '</th>';
+            $return .= '<th width="70" height="24">' . get_lang('Number') . '</th></tr>';
+            for ($i = 0; $i < count($this->category_array_tree); $i++) {
+                $cat_id = $this->category_array_tree[$i]['id'];
+                $return .= '<tr>';
+                $return .= '<td>';
+                $style = 'margin-left:' . $this->category_array_tree[$i]['depth'] * 20 . 'px; margin-right:10px;';
+                $return .= Display::div($this->category_array_tree[$i]['title'], array('style' => $style));
+                $return .= '</td>';
+                $return .= '<td>';
+                $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : 0;
+                $return .= '<input name="category['.$cat_id.']" value="' .$value.'" />';
+                $return .= '</td>';
+                $return .= '</tr>';
+            }
+            $return .= '</table>';
+            return $return;
+        }
+    }
+
+    public function sort_tree_array($array) {
+      foreach ($array as $key => $row) {
+            $parent[$key] = $row['parent_id'];
+        }
+        if (count($array) > 0) {
+            array_multisort($parent, SORT_ASC, $array);
+        }
+        return $array;
+    }
+
+    public function create_tree_array($array, $parent = 0, $depth = -1, $tmp = array()) {
+        if (is_array($array)) {
+            for ($i = 0; $i < count($array); $i++) {
+                //var_dump($array[$i], $parent, $tmp);
+                if ($array[$i]['parent_id'] == $parent) {
+                    if (!in_array($array[$i]['parent_id'], $tmp)) {
+                        $tmp[] = $array[$i]['parent_id'];
+                        $depth++;
+                    }
+                    $row = $array[$i];
+                    $row['id'] = $array[$i]['iid'];
+                    $row['depth'] = $depth;
+                    $this->category_array_tree[] = $row;
+                    $this->create_tree_array($array, $array[$i]['iid'], $depth, $tmp);
+                }
+            }
+
+        }
+    }
+}

+ 149 - 66
main/exercice/tests_category.php

@@ -6,6 +6,27 @@
   hubert.borderiou
   Manage tests category page
  */
+// name of the language file that needs to be included
+$language_file = 'exercice';
+$nameTools = "";
+
+require_once '../inc/global.inc.php';
+require_once 'exercise.lib.php';
+require_once 'question.class.php';
+
+$this_section = SECTION_COURSES;
+
+if (!api_is_allowed_to_edit()) {
+    api_not_allowed(true);
+}
+
+$type = isset($_GET['type']) ? Security::remove_XSS($_GET['type']) : 'simple';
+
+if ($type == 'global' && !api_is_platform_admin()) {
+    api_not_allowed(true);
+}
+
+$url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?type='.$type;
 $htmlHeadXtra[] = '
 <script>
 	function confirmDelete(in_txt, in_id) {
@@ -19,71 +40,112 @@ $htmlHeadXtra[] = '
 			return false;
 		}
 	}
-</script>
-';
 
-// name of the language file that needs to be included
-$language_file = 'exercice';
-$nameTools = "";
+    function check() {
+        $("#parent_id option:selected").each(function() {
+            var id = $(this).val();
+            var name = $(this).text();
+            if (id != "" ) {
+                $.ajax({
+                    async: false,
+                    url: "'.$url.'&a=exercise_category_exists",
+                    data: "id="+id,
+                    success: function(return_value) {
+                        if (return_value == 0 ) {
+                            alert("'.get_lang('CategoryDoesNotExists').'");
+                            //Deleting select option tag
+                            $("#parent_id").find("option").remove();
 
-require_once '../inc/global.inc.php';
-require_once 'exercise.lib.php';
-require_once 'question.class.php';
-require_once 'testcategory.class.php';
+                            $(".holder li").each(function () {
+                                if ($(this).attr("rel") == id) {
+                                    $(this).remove();
+                                }
+                            });
+                        }
+                    },
+                });
+            }
+        });
+    }
 
-$this_section = SECTION_COURSES;
+    $(function() {
+        $("#parent_id").fcbkcomplete({
+            json_url: "'.$url.'&a=search_category_parent",
+            maxitems: 1,
+            addontab: false,
+            input_min_size: 1,
+            cache: false,
+            complete_text:"'.get_lang('StartToType').'",
+            firstselected: false,
+            onselect: check,
+            filter_selected: true,
+            newel: true
+        });
+    });
+</script>';
 
-if (!api_is_allowed_to_edit()) {
-    api_not_allowed(true);
-}
+$htmlHeadXtra[] = '<script src="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/jquery.fcbkcomplete.js" type="text/javascript" language="javascript"></script>';
+$htmlHeadXtra[] = '<link href="'.api_get_path(WEB_LIBRARY_PATH).'javascript/tag/style.css" rel="stylesheet" type="text/css" />';
 
-// breadcrumbs
+// Breadcrumbs
 $interbreadcrumb[] = array("url" => "exercice.php", "name" => get_lang('Exercices'));
 Display::display_header(get_lang('Category'));
 
 // Action handling: add, edit and remove
 if (isset($_GET['action']) && $_GET['action'] == 'addcategory') {
-    add_category_form($_GET['action']);
+    add_category_form($_GET['action'], $type);
+} else if (isset($_GET['action']) && $_GET['action'] == 'addcategoryglobal') {
+    add_category_form($_GET['action'], $type);
 } else if (isset($_GET['action']) && $_GET['action'] == 'editcategory') {
-    edit_category_form($_GET['action']);
+    edit_category_form($_GET['action'], $type);
 } else if (isset($_GET['action']) && $_GET['action'] == 'deletecategory') {
-    delete_category_form($_GET['action']);
+    delete_category_form($_GET['action'], $type);
 } else {
-    display_add_category();
-    display_categories();
+    display_add_category($type);
+    display_categories($type);
 }
 
 Display::display_footer();
 
 // FUNCTIONS
 // form to edit a category
-function edit_category_form($in_action) {
+function edit_category_form($in_action, $type = 'simple') {
     $in_action = Security::remove_XSS($in_action);
     if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) {
         $category_id = Security::remove_XSS($_GET['category_id']);
         $objcat = new Testcategory($category_id);
 
-        // initiate the object		
-        $form = new FormValidator('note', 'post', api_get_self() . '?action=' . $in_action . '&category_id=' . $category_id);
+        // initiate the object
+        $form = new FormValidator('note', 'post', api_get_self().'?action='.$in_action.'&category_id='.$category_id."&type=".$type);
 
-        // settting the form elements			
+        // settting the form elements
         $form->addElement('header', get_lang('EditCategory'));
         $form->addElement('hidden', 'category_id');
-        $form->addElement('text', 'category_name', get_lang('CategoryName'), array('size' => '95'));
+        $form->addElement('text', 'category_name', get_lang('CategoryName'), array('class' => 'span6'));
         $form->add_html_editor('category_description', get_lang('CategoryDescription'), false, false, array('ToolbarSet' => 'test_category', 'Width' => '90%', 'Height' => '200'));
+        $category_parent_list = array();
+
+        if (!empty($objcat->parent_id)) {
+            $parent_cat = new Testcategory($objcat->parent_id);
+            $category_parent_list = array($parent_cat->id => $parent_cat->name);
+            echo '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
+        }
+
+        $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, array('id' => 'parent_id'));
         $form->addElement('style_submit_button', 'SubmitNote', get_lang('ModifyCategory'), 'class="add"');
 
-        // setting the defaults		
+        // setting the defaults
         $defaults = array();
         $defaults["category_id"] = $objcat->id;
         $defaults["category_name"] = $objcat->name;
         $defaults["category_description"] = $objcat->description;
+        $defaults["parent_id"] = $objcat->parent_id;
         $form->setDefaults($defaults);
 
-        // setting the rules		
+        // setting the rules
         $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
 
-        // The validation or display		
+        // The validation or display
         if ($form->validate()) {
             $check = Security::check_token('post');
             if ($check) {
@@ -91,7 +153,8 @@ function edit_category_form($in_action) {
                 $v_id = Security::remove_XSS($values['category_id']);
                 $v_name = Security::remove_XSS($values['category_name'], COURSEMANAGER);
                 $v_description = Security::remove_XSS($values['category_description'], COURSEMANAGER);
-                $objcat = new Testcategory($v_id, $v_name, $v_description);
+                $parent_id = isset($values['parent_id']) ? $values['parent_id'] : null;
+                $objcat = new Testcategory($v_id, $v_name, $v_description, $parent_id, $type);
                 if ($objcat->modifyCategory()) {
                     Display::display_confirmation_message(get_lang('MofidfyCategoryDone'));
                 } else {
@@ -99,15 +162,15 @@ function edit_category_form($in_action) {
                 }
             }
             Security::clear_token();
-            display_add_category();
-            display_categories();
+            display_add_category($type);
+            display_categories($type);
         } else {
-            display_goback();
+            display_goback($type);
             $token = Security::get_token();
             $form->addElement('hidden', 'sec_token');
             $form->setConstants(array('sec_token' => $token));
             $form->display();
-            display_categories();
+            display_categories($type);
         }
     } else {
         Display::display_error_message(get_lang('CannotEditCategory'));
@@ -115,7 +178,7 @@ function edit_category_form($in_action) {
 }
 
 // process to delete a category
-function delete_category_form($in_action) {
+function delete_category_form($in_action, $type = 'simple') {
     $in_action = Security::remove_XSS($in_action);
     if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) {
         $category_id = Security::remove_XSS($_GET['category_id']);
@@ -132,30 +195,33 @@ function delete_category_form($in_action) {
     } else {
         Display::display_error_message(get_lang('CannotDeleteCategoryError'));
     }
-    display_add_category();
-    display_categories();
+    display_add_category($type);
+    display_categories($type);
 }
 
-// form to add a category
-function add_category_form($in_action) {
+// Form to add a category
+function add_category_form($in_action, $type = 'simple') {
     $in_action = Security::remove_XSS($in_action);
-    // initiate the object
-    $form = new FormValidator('note', 'post', api_get_self() . '?action=' . $in_action);
-    // settting the form elements	
+    // Initiate the object
+    $form = new FormValidator('note', 'post', api_get_self().'?action='.$in_action."&type=".$type);
+    // Setting the form elements
     $form->addElement('header', get_lang('AddACategory'));
-    $form->addElement('text', 'category_name', get_lang('CategoryName'), array('size' => '95'));
+    $form->addElement('text', 'category_name', get_lang('CategoryName'), array('class' => 'span6'));
     $form->add_html_editor('category_description', get_lang('CategoryDescription'), false, false, array('ToolbarSet' => 'test_category', 'Width' => '90%', 'Height' => '200'));
+    $form->addElement('select', 'parent_id', get_lang('Parent'), array(), array('id' => 'parent_id'));
     $form->addElement('style_submit_button', 'SubmitNote', get_lang('AddTestCategory'), 'class="add"');
-    // setting the rules
+
+    // Setting the rules
     $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
     // The validation or display
     if ($form->validate()) {
         $check = Security::check_token('post');
         if ($check) {
-            $values = $form->exportValues();
+            $values = $form->getSubmitValues();
             $v_name = Security::remove_XSS($values['category_name'], COURSEMANAGER);
             $v_description = Security::remove_XSS($values['category_description'], COURSEMANAGER);
-            $objcat = new Testcategory(0, $v_name, $v_description);
+            $parent_id = isset($values['parent_id']) && isset($values['parent_id'][0]) ? $values['parent_id'][0] : null;
+            $objcat = new Testcategory(0, $v_name, $v_description, $parent_id, $type, api_get_course_int_id());
             if ($objcat->addCategoryInBDD()) {
                 Display::display_confirmation_message(get_lang('AddCategoryDone'));
             } else {
@@ -163,59 +229,75 @@ function add_category_form($in_action) {
             }
         }
         Security::clear_token();
-        display_add_category();
-        display_categories();
+        display_add_category($type);
+        display_categories($type);
     } else {
-        display_goback();
+        display_goback($type);
         $token = Security::get_token();
         $form->addElement('hidden', 'sec_token');
         $form->setConstants(array('sec_token' => $token));
         $form->display();
-        display_categories();
     }
 }
 
 // Display add category button
 
-function display_add_category() {
+function display_add_category($type) {
     echo '<div class="actions">';
     echo '<a href="exercice.php?' . api_get_cidreq() . '">' . Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM) . '</a>';
-    echo '<a href="' . api_get_self() . '?action=addcategory">' . Display::return_icon('question_category.gif', get_lang('AddACategory')) . '</a>';
+    $icon = "question_category.gif";
+    if ($type == 'global') {
+        $icon = "folder_global_category_new.png";
+    }
+    echo '<a href="'.api_get_self().'?action=addcategory&type='.$type.'">'.Display::return_icon($icon, get_lang('AddACategory'), array(), ICON_SIZE_MEDIUM).'</a>';
     echo '</div>';
     echo "<br/>";
+    if ($type == 'simple') {
     echo "<fieldset><legend>" . get_lang('QuestionCategory') . "</legend></fieldset>";
+    } else {
+        echo "<fieldset><legend>".get_lang('QuestionGlobalCategory')."</legend></fieldset>";
+    }
 }
 
 // Display category list
 
-function display_categories() {
-    $course_id = api_get_course_int_id();
-    $t_cattable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
-    $sql = "SELECT * FROM $t_cattable WHERE c_id = $course_id ORDER BY title";
-    $res = Database::query($sql);
+function display_categories($type = 'simple') {
+    $cat = new Testcategory();
+    $categories = $cat->get_category_tree_by_type($type);
 
-    while ($row = Database::fetch_array($res)) {
-        // le titre avec le nombre de questions qui sont dans cette cat�gorie
-        $tmpobj = new Testcategory($row['id']);
+    foreach ($categories as $row) {
+        // Title with number of questions in the category
+        $category_id = $row['id'];
+        $tmpobj = new Testcategory($category_id);
         $nb_question = $tmpobj->getCategoryQuestionsNumber();
-        echo '<div class="sectiontitle" id="id_cat' . $row['id'] . '">';
+        echo '<div class="sectiontitle" id="id_cat'.$category_id.'">';
         $nb_question_label = $nb_question == 1 ? $nb_question . ' ' . get_lang('Question') : $nb_question . ' ' . get_lang('Questions');
+        $global_label = null;
+        if ($row['c_id'] == 0) {
+            $global_label = ' '.Display::label(get_lang('Global'), 'info');
+        }
         echo "<span style='float:right'>" . $nb_question_label . "</span>";
-        echo $row['title'];
+        $style = 'margin-left:' . $row['depth'] * 20 . 'px; margin-right:10px;';
+        echo $title = Display::div($row['title'].$global_label, array('style' => $style));
         echo '</div>';
         echo '<div class="sectioncomment">';
         echo $row['description'];
         echo '</div>';
         echo '<div>';
-        echo '<a href="' . api_get_self() . '?action=editcategory&amp;category_id=' . $row['id'] . '">' . Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL) . '</a>';
-        if ($nb_question > 0) {
+        if ($row['c_id'] == 0 && $type == 'simple') {
+            echo Display::return_icon('edit_na.png', get_lang('Edit'), array(), ICON_SIZE_SMALL);
+        } else {
+            echo '<a href="'.api_get_self().'?action=editcategory&category_id='.$category_id.'&type='.$type.'">'.Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>';
+        }
+
+        if ($nb_question > 0 || ($row['c_id'] == 0 && $type == 'simple')){
             echo '<a href="javascript:void(0)" onclick="alert(\'' . protectJSDialogQuote(get_lang('CannotDeleteCategory')) . '\')">';
             echo Display::return_icon('delete_na.png', get_lang('CannotDeleteCategory'), array(), ICON_SIZE_SMALL);
             echo '</a>';
         } else {
             $rowname = protectJSDialogQuote($row['title']);
-            echo ' <a href="' . api_get_self() . '?action=deletecategory&amp;category_id=' . $row['id'] . '" ';
-            echo 'onclick="return confirmDelete(\'' . protectJSDialogQuote(get_lang('DeleteCategoryAreYouSure') . '[' . $rowname) . '] ?\', \'id_cat' . $row['id'] . '\');">';
+            echo ' <a href="'.api_get_self().'?action=deletecategory&amp;category_id='.$category_id.'&type='.$type.'"';
+            echo 'onclick="return confirmDelete(\''.protectJSDialogQuote(get_lang('DeleteCategoryAreYouSure').'['.$rowname).'] ?\', \'id_cat'.$category_id.'\');">';
             echo Display::return_icon('delete.png', get_lang('Delete'), array(), ICON_SIZE_SMALL) . '</a>';
         }
         echo '</div>';
@@ -223,9 +305,10 @@ function display_categories() {
 }
 
 // display goback to category list page link
-function display_goback() {
+function display_goback($type) {
+    $type = Security::remove_XSS($type);
     echo '<div class="actions">';
-    echo '<a href="' . api_get_self() . '">' . Display::return_icon('back.png', get_lang('BackToCategoryList'), array(), 32) . '</a>';
+    echo '<a href="'.api_get_self().'?type='.$type.'">'.Display::return_icon('back.png', get_lang('BackToCategoryList'), array(), 32).'</a>';
     echo '</div>';
 }
 

+ 19 - 22
main/exercice/unique_answer.class.php

@@ -9,9 +9,6 @@
 /**
  * Code
  */
-
-if(!class_exists('UniqueAnswer')):
-
 /**
 	CLASS UNIQUE_ANSWER
  *
@@ -23,7 +20,8 @@ if(!class_exists('UniqueAnswer')):
  *	@package chamilo.exercise
  **/
 
-class UniqueAnswer extends Question {
+class UniqueAnswer extends Question
+{
 
 	static $typePicture = 'mcua.gif';
 	static $explanationLangVar = 'UniqueSelect';
@@ -119,7 +117,7 @@ class UniqueAnswer extends Question {
 				$question = Question::read($questionid);
 
                 if($question) {
-                    $select_question[$questionid]='Q'.$key.' :'.Text::cut($question->selectTitle(),20);
+                    $select_question[$questionid] = 'Q'.$key.' :'.Text::cut($question->selectTitle(),20);
                 }
 			}
 		}
@@ -131,7 +129,7 @@ class UniqueAnswer extends Question {
 		$select_lp_id[0]=get_lang('SelectTargetLP');
 
 		foreach ($flat_list as $id => $details) {
-			$select_lp_id[$id] = Text::cut($details['lp_name'],20);
+			$select_lp_id[$id] = Text::cut($details['lp_name'], 20);
 		}
 
 		$temp_scenario = array();
@@ -144,14 +142,16 @@ class UniqueAnswer extends Question {
         for ($i = 1 ; $i <= $nb_answers ; ++$i) {
             $form -> addElement ('html', '<tr>');
             if (isset($answer) && is_object($answer)) {
-                if ($answer -> correct[$i]) {
+                $answer_id = $answer->getRealAnswerIdFromList($i);
+
+                if ($answer->correct[$answer_id]) {
                     $correct = $i;
                 }
-                $defaults['answer['.$i.']']    = $answer->answer[$i];
-                $defaults['comment['.$i.']']   = $answer->comment[$i];
-                $defaults['weighting['.$i.']'] = Text::float_format($answer->weighting[$i], 1);
+                $defaults['answer['.$i.']'] = $answer->answer[$answer_id];
+                $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
+                $defaults['weighting['.$i.']'] = float_format($answer->weighting[$answer_id], 1);
 
-                $item_list=explode('@@',$answer -> destination[$i]);
+                $item_list = explode('@@', $answer->destination[$answer_id]);
 
                 $try       = $item_list[0];
                 $lp        = $item_list[1];
@@ -306,8 +306,7 @@ class UniqueAnswer extends Question {
 			url= an url
 
  			$destination_str='';
- 			foreach ($list_destination as $destination_id)
- 			{
+              foreach ($list_destination as $destination_id) {
  				$destination_str.=$destination_id.';';
  			}*/
 
@@ -379,13 +378,14 @@ class UniqueAnswer extends Question {
    * @assert (1,1,'','',1,1,null) === false
    */
   function create_answer($id=1, $question_id, $answer_title, $comment = '', $score = 0, $correct = 0, $course_id = null) {
-    if (empty($question_id) or empty($answer_title)) { return false; }
+        if (empty($question_id) or empty($answer_title)) {
+            return false;
+        }
     $tbl_quiz_answer = Database::get_course_table(TABLE_QUIZ_ANSWER);
     $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION);
     if (empty($course_id)) {
         $course_id = api_get_course_int_id();
     }
-    $position = 1;
     $question_id = filter_var($question_id,FILTER_SANITIZE_NUMBER_INT);
     $score = filter_var($score,FILTER_SANITIZE_NUMBER_FLOAT);
     $correct = filter_var($correct,FILTER_SANITIZE_NUMBER_INT);
@@ -396,18 +396,15 @@ class UniqueAnswer extends Question {
     $row_max = Database::fetch_object($rs_max);
     $position = $row_max->max_position + 1;
     // Insert a new answer
-    $sql = "INSERT INTO $tbl_quiz_answer "
-    ."(c_id, id, question_id,answer,correct,comment,ponderation,position,destination)"
-    ."VALUES ($course_id, $id,$question_id,'".Database::escape_string($answer_title)."',"
+        $sql = "INSERT INTO $tbl_quiz_answer (c_id, question_id,answer,correct,comment,ponderation,position,destination)"
+            ."VALUES ($course_id, $question_id,'".Database::escape_string($answer_title)."',"
     ."$correct,'".Database::escape_string($comment)."',$score,$position, "
     ." '0@@0@@0@@0')";
     $rs = Database::query($sql);
     if ($correct) {
-        $sql = "UPDATE $tbl_quiz_question "
-        ." SET ponderation = (ponderation + $score) WHERE c_id = $course_id AND id = ".$question_id;
+            $sql = "UPDATE $tbl_quiz_question SET ponderation = (ponderation + $score) WHERE c_id = $course_id AND iid = ".$question_id;
         $rs = Database::query($sql);
         return $rs;
     }
   }
-}
-endif;
+}

+ 12 - 13
main/exercice/unique_answer_no_option.class.php

@@ -10,9 +10,6 @@
 /**
  * Code
  */
-
-if(!class_exists('UniqueAnswerNoOption')):
-
 /**
 	CLASS UNIQUE_ANSWER
  *
@@ -24,7 +21,8 @@ if(!class_exists('UniqueAnswerNoOption')):
  *	@package chamilo.exercise
  **/
 
-class UniqueAnswerNoOption extends Question {
+class UniqueAnswerNoOption extends Question
+{
 
 	static $typePicture = 'mcuao.gif';
 	static $explanationLangVar = 'UniqueAnswerNoOption';
@@ -121,7 +119,8 @@ class UniqueAnswerNoOption extends Question {
                 }
             }
             for ($k = 1 ; $k <= $nb_answers; ++$k) {
-                if ($answer->position[$k] != '666') {
+                $answer_id = $answer->getRealAnswerIdFromList($k);
+                if ($answer->position[$answer_id] != '666') {
                     $new_list[$count] = $count;
                     $count++;
                 }
@@ -137,25 +136,26 @@ class UniqueAnswerNoOption extends Question {
         foreach ($new_list as $key) {
             $i = $key;
 			$form -> addElement ('html', '<tr>');
+            $answer_id = $answer->getRealAnswerIdFromList($i);
 
 			if (is_object($answer)) {
-			    if($answer->position[$i] == 666) {
+                if ($answer->position[$answer_id] == 666) {
 			        //we set nothing
 			    } else {
-    				if ($answer->correct[$i]) {
+                    if ($answer->correct[$answer_id]) {
     					$correct = $i;
     				}
-                    $answer_result = $answer->answer[$i];
-                    $weight_result = Text::float_format($answer->weighting[$i], 1);
+                    $answer_result = $answer->answer[$answer_id];
+                    $weight_result = float_format($answer->weighting[$answer_id], 1);
                     if ($nb_answers == $i) {
                         $weight_result = '0';
                     }
 
     				$defaults['answer['.$i.']']    = $answer_result;
-    				$defaults['comment['.$i.']']   = $answer->comment[$i];
+                    $defaults['comment['.$i.']'] = $answer->comment[$answer_id];
     				$defaults['weighting['.$i.']'] = $weight_result;
 
-    				$item_list=explode('@@',$answer -> destination[$i]);
+                    $item_list = explode('@@', $answer->destination[$answer_id]);
 
     				$try       = $item_list[0];
     				$lp        = $item_list[1];
@@ -413,5 +413,4 @@ class UniqueAnswerNoOption extends Question {
         $header .= '</tr>';
         return $header;
 	}
-}
-endif;
+}

+ 6 - 37
main/exercice/upload_exercise.php

@@ -32,14 +32,8 @@ if (!$is_allowed_to_edit) {
     api_not_allowed(true);
 }
 
-// setting the tabs
+// Setting the tabs
 $this_section = SECTION_COURSES;
-$htmlHeadXtra[] = "<script type='text/javascript'>
-  $(document).ready( function(){
-  $(\"div.formw\").attr(\"style\",\"width: 73%;\");
-  $(\"#img_plus_and_minus\").hide();
-});
-</script>";
 
 // Action handling
 lp_upload_quiz_action_handling();
@@ -55,50 +49,32 @@ if ($origin != 'learnpath') {
             Display :: display_confirmation_message(get_lang($_GET['message']));
         }
     }
-} else {
-    echo '<link rel="stylesheet" type="text/css" href="'.api_get_path(WEB_CODE_PATH).'css/default.css"/>';
 }
 // display the actions
 echo '<div class="actions">';
 echo lp_upload_quiz_actions();
 echo '</div>';
 
-// start the content div
-echo '<div id="content_with_secondary_actions" class="gradient">';
 // the main content
 lp_upload_quiz_main();
 
-// close the content div
-echo '</div>';
-
-function lp_upload_quiz_actions()
-{
+function lp_upload_quiz_actions() {
     $lp_id = Security::remove_XSS($_GET['lp_id']);
     $return = "";
-    $return .= '<a href="exercice.php?'.api_get_cidReq().'">'.Display::return_icon(
-        'back.png',
-        get_lang('BackToExercisesList'),
-        '',
-        ICON_SIZE_MEDIUM
-    ).'</a>';
+    $return .= '<a href="exercice.php?'.api_get_cidReq().'">'.Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
 
     return $return;
 }
 
-function lp_upload_quiz_secondary_actions()
-{
+function lp_upload_quiz_secondary_actions() {
     $lp_id = Security::remove_XSS($_GET['lp_id']);
     $return .= '';
-    $return .= '<a href="exercise_report.php?'.api_get_cidreq().'">'.Display :: return_icon(
-        'reporting32.png',
-        get_lang('Tracking')
-    ).get_lang('Tracking').'</a>';
+    $return.='<a href="exercise_report.php?'.api_get_cidreq().'">'.Display :: return_icon('reporting32.png', get_lang('Tracking')).get_lang('Tracking').'</a>';
 
     return $return;
 }
 
-function lp_upload_quiz_main()
-{
+function lp_upload_quiz_main() {
 
     // variable initialisation
     $lp_id = Security::remove_XSS($_GET['lp_id']);
@@ -286,13 +262,6 @@ function lp_upload_quiz_action_handling()
                         } else {
                             $comment = $feedback_false_list[$i][2];
                         }
-                        /*
-                                            if ($id == 1) {
-                                                $comment = $feedback_true_list[$i][2];
-                                            } elseif ($id == 2) {
-                                                $comment = $feedback_false_list[$i][2];
-                                            }
-                        */
                         // Create answer
                         $unique_answer->create_answer($id, $question_id, $answer, $comment, $score, $correct);
                         $id++;

BIN
main/img/icons/32/folder_category.gif


BIN
main/img/icons/32/folder_global_category.png


BIN
main/img/icons/32/folder_global_category_new.gif


BIN
main/img/icons/32/folder_global_category_new.png


BIN
main/img/icons/64/looknfeel.png


+ 54 - 18
main/inc/ajax/exercise.ajax.php

@@ -21,6 +21,35 @@ $session_id = isset($_REQUEST['session_id']) ? intval($_REQUEST['session_id']) :
 $course_code = isset($_REQUEST['cidReq']) ? $_REQUEST['cidReq'] : api_get_course_id();
 
 switch ($action) {
+    case 'exercise_category_exists':
+        $category = new Testcategory();
+        $category->getCategory($_REQUEST['id']);
+        if (empty($category->id)) {
+            echo 0;
+        } else {
+            echo 1;
+        }
+        break;
+    case 'search_category_parent':
+        $type = isset($_REQUEST['type']) ? $_REQUEST['type'] : 'simple';
+
+        $cat = new Testcategory(null, null, null, null, $type);
+        $items = $cat->get_categories_by_keyword($_REQUEST['tag']);
+
+        $json_items = array();
+        if (!empty($items)) {
+            foreach ($items as $item) {
+                if ($item['c_id'] == 0) {
+                    $item['title'] .= " [".get_lang('Global')."]";
+                }
+                $json_items[] = array(
+                    'key' => $item['iid'],
+                    'value' => $item['title']
+                );
+            }
+        }
+        echo json_encode($json_items);
+        break;
     case 'get_live_stats':
         if (!api_is_allowed_to_edit(null, true)) {
             break;
@@ -212,18 +241,21 @@ switch ($action) {
             //Attempt id
             $exe_id = $_REQUEST['exe_id'];
 
-            if ($debug) error_log("exe_id = $exe_id ");
-            if ($debug) error_log("type = $type ");
-            if ($debug) error_log("choice = ".print_r($choice, 1)." ");
-            if ($debug) error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates,1));
-            if ($debug) error_log("remind_list = ".print_r($remind_list));
+            if ($debug) {
+                error_log("exe_id = $exe_id ");
+                error_log("type = $type ");
+                error_log("choice = ".print_r($choice, 1)." ");
+                error_log("hot_spot_coordinates = ".print_r($hot_spot_coordinates,1));
+                error_log("remind_list = ".print_r($remind_list));
+            }
 
             //Exercise information
             $objExercise             = isset($_SESSION['objExercise']) ? $_SESSION['objExercise'] : null;
 
             //Question info
             $question_id             = intval($_REQUEST['question_id']);
-            $question_list           = $_SESSION['questionList'];
+            //$question_list           = $_SESSION['questionList'];
+            $question_list           = $_SESSION['question_list_flatten'];
 
             //If exercise or question is not set then exit
             if (empty($question_list) || empty($objExercise)) {
@@ -297,12 +329,14 @@ switch ($action) {
 
             //Looping the question list
 
+            if ($debug) error_log("Looping question list".print_r($question_list, 1));
+            if ($debug) error_log("Trying to save question: $question_id ");
             foreach ($question_list as $my_question_id) {
-                if ($debug) error_log("Saving question_id = $my_question_id ");
 
                 if ($type == 'simple' && $question_id != $my_question_id) {
                     continue;
                 }
+                if ($debug) error_log("Saving question_id = $my_question_id ");
 
                 $my_choice = $choice[$my_question_id];
 
@@ -380,17 +414,19 @@ switch ($action) {
 
                 $_SESSION['duration_time'][$key] = time();
 
-                update_event_exercise(  $exe_id,
-                                        $objExercise->selectId(),
-                                        $total_score,
-                                        $total_weight,
-                                        $session_id,
-                                        $exercise_stat_info['orig_lp_id'],
-                                        $exercise_stat_info['orig_lp_item_id'],
-                                        $exercise_stat_info['orig_lp_item_view_id'],
-                                        $duration,
-                                        'incomplete',
-                                        $remind_list);
+                update_event_exercise(
+                    $exe_id,
+                    $objExercise->selectId(),
+                    $total_score,
+                    $total_weight,
+                    $session_id,
+                    $exercise_stat_info['orig_lp_id'],
+                    $exercise_stat_info['orig_lp_item_id'],
+                    $exercise_stat_info['orig_lp_item_view_id'],
+                    $duration,
+                    'incomplete',
+                    $remind_list
+                );
 
                  // Destruction of the Question object
             	unset($objQuestionTmp);

+ 25 - 3
main/inc/ajax/model.ajax.php

@@ -38,12 +38,20 @@ if (!in_array($action, array(
     'get_timelines',
     'get_user_skill_ranking',
     'get_usergroups_teacher',
-    'get_question_list'
+    'get_question_list',
+    'get_user_list_plugin_widescale'
 ))
     ) {
 	api_protect_admin_script(true);
 }
 
+if ($action == 'get_user_list_plugin_widescale') {
+    $allowed = api_is_drh() || api_is_platform_admin();
+    if (!$allowed) {
+        api_not_allowed();
+    }
+}
+
 //Search features
 
 $ops = array(
@@ -181,6 +189,9 @@ if (!$sidx) $sidx = 1;
 //@todo rework this
 
 switch ($action) {
+    case 'get_user_list_plugin_widescale':
+        $count = UserManager::get_user_data(null, null, null, null, true);
+        break;
     case 'get_question_list':
         require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.class.php';
         $exerciseId = isset($_REQUEST['exerciseId']) ? $_REQUEST['exerciseId'] : null;
@@ -320,6 +331,11 @@ $is_allowedToEdit = api_is_allowed_to_edit(null,true) || api_is_allowed_to_edit(
 $columns = array();
 
 switch ($action) {
+    case 'get_user_list_plugin_widescale':
+        $columns = array('username', 'firstname', 'lastname', 'exam_password');
+        $column_names = array(get_lang('Username'), get_lang('Firstname'), get_lang('Lastname'), get_lang('Password'));
+        $result = UserManager::get_user_data($start, $limit, $sidx, $sord);
+        break;
     case 'get_question_list':
         if (isset($exercise) && !empty($exercise)) {
             $columns = array('question', 'type', 'category', 'level', 'score', 'actions');
@@ -333,6 +349,11 @@ switch ($action) {
     case 'get_course_exercise_medias':
         $columns = array('question');
         $result = Question::get_course_medias($course_id, $start, $limit, $sidx, $sord, $where_condition);
+        if (!empty($result)) {
+            foreach ($result as &$media) {
+                $media['id'] = $media['iid'];
+            }
+        }
         break;
     case 'get_user_course_report_resumed':
         $columns = array('extra_ruc', 'training_hours', 'count_users', 'count_users_registered', 'average_hours_per_user', 'count_certificates');
@@ -553,7 +574,7 @@ switch ($action) {
                 $group['sessions']   = count($obj->get_sessions_by_usergroup($group['id']));
                 $group['courses']    = count($obj->get_courses_by_usergroup($group['id']));
                 $group['users']      = count($obj->get_users_by_usergroup($group['id']));
-                switch($group['group_type']) {
+                switch ($group['group_type']) {
                     case '0':
                         $group['group_type'] = Display::label(get_lang('Class'), 'info');
                         break;
@@ -668,7 +689,8 @@ $allowed_actions = array(
     'get_user_course_report',
     'get_user_course_report_resumed',
     'get_group_reporting',
-    'get_question_list'
+    'get_question_list',
+    'get_user_list_plugin_widescale'
 );
 
 //5. Creating an obj to return a json

+ 115 - 110
main/inc/global.inc.php

@@ -100,6 +100,12 @@ require_once $includePath.'/lib/internationalization_internal.lib.php';
 // Do not over-use this variable. It is only for this script's local use.
 $libPath = $includePath.'/lib/';
 
+// Database constants
+require_once $libPath.'database.constants.inc.php';
+
+// @todo Rewrite the events.lib.inc.php in a class
+require_once $libPath.'events.lib.inc.php';
+
 /** Loading config files */
 if ($alreadyInstalled) {
     $configPath = $includePath.'/../../config/';
@@ -333,6 +339,115 @@ $app['form.extensions'] = $app->share($app->extend('form.extensions', function (
     return $extensions;
 }));*/
 
+
+// Setting Doctrine service provider (DBAL)
+if (isset($app['configuration']['main_database'])) {
+
+    $app->register(new Silex\Provider\DoctrineServiceProvider(), array(
+            'db.options' => array(
+                'driver' => 'pdo_mysql',
+                'dbname' => $app['configuration']['main_database'],
+                'user' => $app['configuration']['db_user'],
+                'password' => $app['configuration']['db_password'],
+                'host' => $app['configuration']['db_host'],
+                'charset'   => 'utf8',
+                /*'driverOptions' => array(
+                    1002 => 'SET NAMES utf8'
+                )*/
+            )
+        ));
+
+    // Setting Doctrine ORM
+    $app->register(new Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider, array(
+            'orm.auto_generate_proxies' => true,
+            "orm.proxies_dir" => $app['db.orm.proxies_dir'],
+            //'orm.proxies_namespace' => '\Doctrine\ORM\Proxy\Proxy',
+            "orm.em.options" => array(
+                "mappings" => array(
+                    array(
+                        //If true, only simple notations like @Entity will work. If false, more advanced notations and aliasing via use will work. (Example: use Doctrine\ORM\Mapping AS ORM, @ORM\Entity)
+                        'use_simple_annotation_reader' => false,
+                        "type" => "annotation",
+                        "namespace" => "Entity",
+                        "path" => api_get_path(INCLUDE_PATH).'Entity',
+                    )
+                ),
+            ),
+        ));
+
+    // Temporal fix to load gedmo libs
+    $sortableGroup = new Gedmo\Mapping\Annotation\SortableGroup(array());
+    $sortablePosition = new Gedmo\Mapping\Annotation\SortablePosition(array());
+
+    // Setting Doctrine2 extensions
+    $timestampableListener = new \Gedmo\Timestampable\TimestampableListener();
+    $app['db.event_manager']->addEventSubscriber($timestampableListener);
+
+    $sluggableListener = new \Gedmo\Sluggable\SluggableListener();
+    $app['db.event_manager']->addEventSubscriber($sluggableListener);
+
+    $sortableListener = new Gedmo\Sortable\SortableListener();
+    $app['db.event_manager']->addEventSubscriber($sortableListener);
+}
+
+// Connect to the server database and select the main chamilo database.
+if (!($conn_return = @Database::connect(
+    array(
+        'server' => $app['configuration']['db_host'],
+        'username' => $app['configuration']['db_user'],
+        'password' => $app['configuration']['db_password'],
+        'persistent' => isset($app['configuration']['db_persistent_connection']) ? $app['configuration']['db_persistent_connection'] : null
+        // When $app['configuration']['db_persistent_connection'] is set, it is expected to be a boolean type.
+    )
+))
+) {
+    //$app->abort(500, "Database is unavailable"); //error 3
+}
+
+/*
+if (!$app['configuration']['db_host']) {
+    //$app->abort(500, "Database is unavailable"); //error 3
+}*/
+
+$charset = 'UTF-8';
+$checkConnection = false;
+
+if (isset($app['configuration']['main_database'])) {
+    // The system has not been designed to use special SQL modes that were introduced since MySQL 5.
+    Database::query("set session sql_mode='';");
+
+    $checkConnection = @Database::select_db($app['configuration']['main_database'], $conn_return);
+
+    if ($checkConnection) {
+
+        // Initialization of the database encoding to be used.
+        Database::query("SET SESSION character_set_server='utf8';");
+        Database::query("SET SESSION collation_server='utf8_general_ci';");
+
+        /*   Initialization of the default encodings */
+
+        // The platform's character set must be retrieved at this early moment.
+        /*$sql = "SELECT selected_value FROM settings_current WHERE variable = 'platform_charset';";
+
+        $result = Database::query($sql);
+        while ($row = @Database::fetch_array($result)) {
+            $charset = $row[0];
+        }
+        if (empty($charset)) {
+            $charset = 'UTF-8';
+        }*/
+        //Charset is UTF-8
+
+        if (api_is_utf8($charset)) {
+            // See Bug #1802: For UTF-8 systems we prefer to use "SET NAMES 'utf8'" statement in order to avoid a bizarre problem with Chinese language.
+            Database::query("SET NAMES 'utf8';");
+        } else {
+            Database::query("SET CHARACTER SET '".Database::to_db_encoding($charset)."';");
+        }
+        Database::query("SET NAMES 'utf8';");
+    }
+}
+
 // The script is allowed? This setting is modified when calling api_is_not_allowed()
 $app['allowed'] = true;
 
@@ -430,54 +545,6 @@ if (is_writable($app['temp.path'])) {
     );
 }
 
-// Setting Doctrine service provider (DBAL)
-if (isset($app['configuration']['main_database'])) {
-
-    $app->register(new Silex\Provider\DoctrineServiceProvider(), array(
-        'db.options' => array(
-            'driver' => 'pdo_mysql',
-            'dbname' => $app['configuration']['main_database'],
-            'user' => $app['configuration']['db_user'],
-            'password' => $app['configuration']['db_password'],
-            'host' => $app['configuration']['db_host'],
-            'driverOptions' => array(
-                1002 => 'SET NAMES utf8'
-            )
-        )
-    ));
-
-    // Setting Doctrine ORM
-    $app->register(new Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider, array(
-        'orm.auto_generate_proxies' => true,
-        "orm.proxies_dir" => $app['db.orm.proxies_dir'],
-        //'orm.proxies_namespace' => '\Doctrine\ORM\Proxy\Proxy',
-        "orm.em.options" => array(
-            "mappings" => array(
-                array(
-                    //If true, only simple notations like @Entity will work. If false, more advanced notations and aliasing via use will work. (Example: use Doctrine\ORM\Mapping AS ORM, @ORM\Entity)
-                    'use_simple_annotation_reader' => false,
-                    "type" => "annotation",
-                    "namespace" => "Entity",
-                    "path" => api_get_path(INCLUDE_PATH).'Entity',
-                )
-            ),
-        ),
-    ));
-
-    // Temporal fix to load gedmo libs
-    $sortableGroup = new Gedmo\Mapping\Annotation\SortableGroup(array());
-    $sortablePosition = new Gedmo\Mapping\Annotation\SortablePosition(array());
-
-    // Setting Doctrine2 extensions
-    $timestampableListener = new \Gedmo\Timestampable\TimestampableListener();
-    $app['db.event_manager']->addEventSubscriber($timestampableListener);
-
-    $sluggableListener = new \Gedmo\Sluggable\SluggableListener();
-    $app['db.event_manager']->addEventSubscriber($sluggableListener);
-
-    $sortableListener = new Gedmo\Sortable\SortableListener();
-    $app['db.event_manager']->addEventSubscriber($sortableListener);
-}
 
 define('IMAGE_PROCESSOR', 'gd'); // imagick or gd strings
 
@@ -534,68 +601,6 @@ if ($app['debug'] && isset($app['configuration']['main_database'])) {
     });
 }
 
-// Database constants
-require_once $libPath.'database.constants.inc.php';
-// @todo Rewrite the events.lib.inc.php in a class
-require_once $libPath.'events.lib.inc.php';
-
-// Connect to the server database and select the main chamilo database.
-if (!($conn_return = @Database::connect(
-    array(
-        'server' => $app['configuration']['db_host'],
-        'username' => $app['configuration']['db_user'],
-        'password' => $app['configuration']['db_password'],
-        'persistent' => isset($app['configuration']['db_persistent_connection']) ? $app['configuration']['db_persistent_connection'] : null
-        // When $app['configuration']['db_persistent_connection'] is set, it is expected to be a boolean type.
-    )
-))
-) {
-    //$app->abort(500, "Database is unavailable"); //error 3
-}
-
-/*
-if (!$app['configuration']['db_host']) {
-    //$app->abort(500, "Database is unavailable"); //error 3
-}*/
-
-$charset = 'UTF-8';
-$checkConnection = false;
-
-if (isset($app['configuration']['main_database'])) {
-    // The system has not been designed to use special SQL modes that were introduced since MySQL 5.
-    Database::query("set session sql_mode='';");
-
-    $checkConnection = @Database::select_db($app['configuration']['main_database'], $conn_return);
-
-    if ($checkConnection) {
-
-        // Initialization of the database encoding to be used.
-        Database::query("SET SESSION character_set_server='utf8';");
-        Database::query("SET SESSION collation_server='utf8_general_ci';");
-
-        /*   Initialization of the default encodings */
-
-        // The platform's character set must be retrieved at this early moment.
-        /*$sql = "SELECT selected_value FROM settings_current WHERE variable = 'platform_charset';";
-
-        $result = Database::query($sql);
-        while ($row = @Database::fetch_array($result)) {
-            $charset = $row[0];
-        }
-        if (empty($charset)) {
-            $charset = 'UTF-8';
-        }*/
-        //Charset is UTF-8
-
-        if (api_is_utf8($charset)) {
-            // See Bug #1802: For UTF-8 systems we prefer to use "SET NAMES 'utf8'" statement in order to avoid a bizarre problem with Chinese language.
-            Database::query("SET NAMES 'utf8';");
-        } else {
-            Database::query("SET CHARACTER SET '".Database::to_db_encoding($charset)."';");
-        }
-        Database::query("SET NAMES 'utf8';");
-    }
-}
 
 // Preserving the value of the global variable $charset.
 $charset_initial_value = $charset;

+ 3 - 3
main/inc/lib/database.constants.inc.php

@@ -198,8 +198,9 @@ define('TABLE_QUIZ_ORDER',                  'quiz_order');
 define('TABLE_QUIZ_ANSWER',                 'quiz_answer');
 define('TABLE_QUIZ_TEST_QUESTION',          'quiz_rel_question');
 define('TABLE_QUIZ_QUESTION_OPTION',        'quiz_question_option');
-define('TABLE_QUIZ_QUESTION_CATEGORY',      'quiz_question_category');
+define('TABLE_QUIZ_QUESTION_CATEGORY',      'quiz_category');
 define('TABLE_QUIZ_QUESTION_REL_CATEGORY',  'quiz_question_rel_category');
+define('TABLE_QUIZ_REL_CATEGORY',           'quiz_rel_category');
 
 // Linked resource table
 //@todo table exists?
@@ -303,13 +304,13 @@ define('TABLE_THEMATIC_ADVANCE','thematic_advance');
 //User groups
 //Needed for migration
 define('TABLE_MAIN_GROUP',              'groups');
+
 /*
 define('TABLE_MAIN_USER_REL_GROUP',     'group_rel_user');
 define('TABLE_MAIN_GROUP_REL_TAG',      'group_rel_tag');
 define('TABLE_MAIN_GROUP_REL_GROUP',    'group_rel_group');
 */
 
-
 // Careers, promotions, Usergroups
 define('TABLE_CAREER',      'career');
 define('TABLE_PROMOTION',   'promotion');
@@ -353,4 +354,3 @@ define('TABLE_BRANCH_SYNC_LOG',              'branch_sync_log');
 
 define('TABLE_BRANCH_TRANSACTION',           'branch_transaction');
 define('TABLE_BRANCH_TRANSACTION_STATUS',    'branch_transaction_status');
-

+ 5 - 0
main/inc/lib/database.lib.php

@@ -723,6 +723,8 @@ class Database
      */
     public static function query($query, $connection = null, $file = null, $line = null)
     {
+        //bad idea ...
+        //global $app;return $app['db']->executeQuery($query);
         $use_default_connection = self::use_default_connection($connection);
         if ($use_default_connection) {
             // Let us do parameter shifting, thus the method would be similar
@@ -771,6 +773,9 @@ class Database
                 strpos($query, 'ALTER') === false &&
                 strpos($query, 'alter') === false &&
                 strpos($query, 'c_id') === false &&
+                strpos($query, 'c_quiz_question_rel_category') === false &&
+                strpos($query, 'c_quiz_category') === false &&
+
                 strpos($query, 'create table') === false &&
                 strpos($query, 'CREATE TABLE') === false &&
                 strpos($query, 'AUTO_INCREMENT') === false

+ 59 - 1
main/inc/lib/display.lib.php

@@ -1718,4 +1718,62 @@ class Display
             </div> </div>';
         return $html;
     }
-} //end class Display
+
+    static function progress_pagination_bar($list, $current, $conditions = array(), $link = null) {
+        $counter = 1;
+        $pagination_size = '';
+        $total = count($list);
+        if ($total > 25) {
+            $pagination_size = 'pagination-small';
+        }
+        if ($total > 50) {
+            $pagination_size = 'pagination-mini';
+        }
+        $html = '<div class="pagination '.$pagination_size.' pagination-centered"><ul>';
+
+        foreach ($list as $item_id) {
+            $class = "active";
+
+            if ($counter < $current) {
+                $class = "before";
+            }
+
+            foreach ($conditions as $condition) {
+                $array = $condition['items'];
+                $class_to_applied = $condition['class'];
+                $type = isset($condition['type']) ? $condition['type'] : 'positive';
+                switch ($type) {
+                    case 'positive':
+                        if (in_array($item_id, $array)) {
+                            $class .= " $class_to_applied";
+                        }
+                        break;
+                    case 'negative':
+                        if (!in_array($item_id, $array)) {
+                            $class .= " $class_to_applied";
+                        }
+                        break;
+                }
+            }
+
+            if ($current == $counter) {
+                $class = "before current";
+            }
+
+            if ($counter > $current) {
+                $class = "after";
+            }
+
+            if (empty($link)) {
+                $link_to_show = "#";
+            } else {
+                $link_counter = $counter -1;
+                $link_to_show = $link.$link_counter;
+            }
+            $html .= '<li class = "'.$class.'"><a href="'.$link_to_show.'">'.$counter.'</a></li>';
+            $counter++;
+        }
+        $html .= '</ul></div>';
+        return $html;
+    }
+}

+ 29 - 3
main/inc/lib/events.lib.inc.php

@@ -328,8 +328,7 @@ function event_link($link_id)
  * @author Julio Montoya Armas <gugli100@gmail.com> Reworked 2010
  * @desc Record result of user when an exercice was done
  */
-function update_event_exercise($exeid, $exo_id, $score, $weight, $session_id, $learnpath_id = 0, $learnpath_item_id = 0, $learnpath_item_view_id = 0, $duration = 0, $status = '', $remind_list = array(), $end_date = null)
-{
+function update_event_exercise($exeid, $exo_id, $score, $weigh, $session_id, $learnpath_id = 0, $learnpath_item_id = 0, $learnpath_item_view_id = 0, $duration = 0, $status = '', $remind_list = array() , $end_date = null) {
     require_once api_get_path(SYS_CODE_PATH).'exercice/exercise.lib.php';
     global $debug;
     if ($debug) {
@@ -1571,6 +1570,32 @@ function delete_attempt_hotspot($exe_id, $user_id, $courseId, $question_id)
     Database::query($sql);
 }
 
+function get_answered_questions_from_attempt($exe_id, $objExercise) {
+    $attempt_list = get_all_exercise_event_by_exe_id($exe_id);
+    $exercise_result = array();
+    if (!empty($attempt_list)) {
+        foreach ($attempt_list as $question_id => $options) {
+            foreach ($options as $item) {
+                $question_obj = Question::read($item['question_id']);
+                switch ($question_obj->type) {
+                    case FILL_IN_BLANKS:
+                        $item['answer'] = $objExercise->fill_in_blank_answer_to_string($item['answer']);
+                        break;
+                    case HOT_SPOT:
+                        //var_dump($item['answer']);
+                        break;
+                }
+
+                if ($item['answer'] != '0' && !empty($item['answer'])) {
+                    $exercise_result[] = $question_id;
+                    break;
+                }
+            }
+        }
+    }
+    return $exercise_result;
+}
+
 /**
  * User logs in for the first time to a course
  * @param $course_code
@@ -1626,7 +1651,8 @@ function check_if_mail_already_sent($event_name, $user_from, $user_to = null)
     $result = Database::store_result(Database::query($sql), 'ASSOC');
     return $result[0]["total"];
 }
-/*
+
+/**
  *
  * Filter EventEmailTemplate Filters see the main/inc/conf/events.conf.dist.php
  *

+ 23 - 25
main/inc/lib/plugin.lib.php

@@ -1,28 +1,23 @@
 <?php
 /* See license terms in /license.txt */
 
-class AppPlugin {
-    var $plugin_regions = array (
- //           'loginpage_main',
-            'login_top',
-            'login_bottom',
-            'menu_top',
-            'menu_bottom',
-/*            'campushomepage_main',
-            'campushomepage_menu',
-            'mycourses_main',
-            'mycourses_menu',*/
-            'content_top',
-            'content_bottom',
-            'header_main',
-            'header_center',
-            'header_left',
-            'header_right',
-            //'footer',
-            'footer_left',
-            'footer_center',
-            'footer_right',
-            'course_tool_plugin'
+class AppPlugin
+{
+    public $plugin_regions = array(
+        'login_top',
+        'login_bottom',
+        'menu_top',
+        'menu_bottom',
+        'content_top',
+        'content_bottom',
+        'header_main',
+        'header_center',
+        'header_left',
+        'header_right',
+        'footer_left',
+        'footer_center',
+        'footer_right',
+        'course_tool_plugin'
     );
 
     function __construct() {
@@ -79,7 +74,7 @@ class AppPlugin {
         $pluginpath = api_get_path(SYS_PLUGIN_PATH).$plugin_name.'/install.php';
 
         if (is_file($pluginpath) && is_readable($pluginpath)) {
-            //execute the install procedure
+            // Executes the install procedure
             require $pluginpath;
         }
     }
@@ -412,7 +407,9 @@ class AppPlugin {
      */ 
     public function get_plugin_course_settings($plugin_name) {
         $settings = array();
-        if (empty($plugin_name)) { return $settings; }
+        if (empty($plugin_name)) {
+            return $settings;
+        }
         $plugin_info = $this->get_plugin_info($plugin_name);
         
         if (isset($plugin_info['plugin_class'])) {
@@ -428,7 +425,8 @@ class AppPlugin {
                     }
                 }
             }
-            unset($obj); unset($plugin_info);
+            unset($obj);
+            unset($plugin_info);
         }
         return $settings;
     }

+ 1 - 1
main/inc/local.inc.php

@@ -478,6 +478,7 @@ $_user = Session::read('_user');
 $app['current_user'] = $_user;
 
 /* USER INIT */
+
 if (isset($uidReset) && $uidReset) {    // session data refresh requested
     unset($_SESSION['_user']['uidReset']);
     $is_platformAdmin = false;
@@ -506,7 +507,6 @@ if (isset($uidReset) && $uidReset) {    // session data refresh requested
             $uData = Database::fetch_array($result);
             $_user = _api_format_user($uData, false);
             $_user['lastLogin']        = api_strtotime($uData['login_date'], 'UTC');
-
             $is_platformAdmin           = (bool) (! is_null( $uData['is_admin']));
             $is_allowedCreateCourse     = (bool) (($uData ['status'] == COURSEMANAGER) or (api_get_setting('drhCourseManagerRights') and $uData['status'] == DRH));
             ConditionalLogin::check_conditions($uData);

+ 25 - 25
main/install/1.10.0/db_course.sql

@@ -1280,8 +1280,8 @@ DROP TABLE IF EXISTS c_quiz;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz (
-  c_id int(11) NOT NULL,
-  id int(10) unsigned NOT NULL AUTO_INCREMENT,
+  iid int unsigned NOT NULL auto_increment,
+  c_id int(11) NOT NULL,  
   title varchar(255) NOT NULL,
   description text,
   sound varchar(255) DEFAULT NULL,
@@ -1303,8 +1303,8 @@ CREATE TABLE c_quiz (
   text_when_finished text,
   display_category_name int(11) NOT NULL DEFAULT '1',
   pass_percentage int(11) DEFAULT NULL,
-  PRIMARY KEY (c_id,id),
-  KEY session_id (session_id)
+  autolaunch INT DEFAULT 0,
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1316,9 +1316,8 @@ DROP TABLE IF EXISTS c_quiz_answer;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_answer (
-  c_id int(11) NOT NULL,
-  id int(10) unsigned NOT NULL,
-  id_auto int(11) NOT NULL AUTO_INCREMENT,
+  iid bigint unsigned NOT NULL AUTO_INCREMENT,
+  c_id int(11) NOT NULL, 
   question_id int(10) unsigned NOT NULL,
   answer text NOT NULL,
   correct mediumint(8) unsigned DEFAULT NULL,
@@ -1329,8 +1328,7 @@ CREATE TABLE c_quiz_answer (
   hotspot_type enum('square','circle','poly','delineation','oar') DEFAULT NULL,
   destination text NOT NULL,
   answer_code char(10) DEFAULT '',
-  PRIMARY KEY (c_id,id_auto),
-  KEY idx_quiz_answer_c_q (c_id,question_id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1342,12 +1340,12 @@ DROP TABLE IF EXISTS c_quiz_order;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_order (
-  id int(10) unsigned NOT NULL AUTO_INCREMENT,
+  iid bigint unsigned NOT NULL auto_increment,  
   c_id int(10) unsigned NOT NULL,
   session_id int(10) unsigned NOT NULL,
   exercise_id int(11) NOT NULL,
   exercise_order int(11) NOT NULL,
-  PRIMARY KEY (id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1359,8 +1357,8 @@ DROP TABLE IF EXISTS c_quiz_question;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_question (
-  c_id int(11) NOT NULL,
-  id int(10) unsigned NOT NULL AUTO_INCREMENT,
+  iid bigint unsigned NOT NULL auto_increment,
+  c_id int(11) NOT NULL,  
   question text NOT NULL,
   description text,
   ponderation float(6,2) NOT NULL DEFAULT '0.00',
@@ -1371,24 +1369,24 @@ CREATE TABLE c_quiz_question (
   extra varchar(255) DEFAULT NULL,
   question_code char(10) DEFAULT '',
   parent_id int(10) unsigned NOT NULL DEFAULT '0',
-  PRIMARY KEY (c_id,id),
-  KEY position (position)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
 --
--- Table structure for table c_quiz_question_category
+-- Table structure for table c_quiz_category
 --
 
-DROP TABLE IF EXISTS c_quiz_question_category;
+DROP TABLE IF EXISTS c_quiz_category;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
-CREATE TABLE c_quiz_question_category (
-  c_id int(11) NOT NULL,
-  id int(11) NOT NULL AUTO_INCREMENT,
+CREATE TABLE c_quiz_category (
+  iid bigint unsigned NOT NULL AUTO_INCREMENT,
+  parent_id int unsigned NOT NULL default 0,
+  c_id int(11) NOT NULL,  
   title varchar(255) NOT NULL,
   description text NOT NULL,
-  PRIMARY KEY (c_id,id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1400,12 +1398,12 @@ DROP TABLE IF EXISTS c_quiz_question_option;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_question_option (
+  iid bigint unsigned NOT NULL auto_increment,
   c_id int(11) NOT NULL,
-  id int(11) NOT NULL AUTO_INCREMENT,
   question_id int(11) NOT NULL,
   name varchar(255) DEFAULT NULL,
   position int(10) unsigned NOT NULL,
-  PRIMARY KEY (c_id,id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1417,10 +1415,11 @@ DROP TABLE IF EXISTS c_quiz_question_rel_category;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_question_rel_category (
+  iid bigint unsigned NOT NULL AUTO_INCREMENT,
   c_id int(11) NOT NULL,
   question_id int(11) NOT NULL,
   category_id int(11) NOT NULL,
-  PRIMARY KEY (c_id,question_id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 
@@ -1432,11 +1431,12 @@ DROP TABLE IF EXISTS c_quiz_rel_question;
 /*!40101 SET @saved_cs_client     = @@character_set_client */;
 /*!40101 SET character_set_client = utf8 */;
 CREATE TABLE c_quiz_rel_question (
+  iid bigint unsigned NOT NULL AUTO_INCREMENT,
   c_id int(11) NOT NULL,
   question_id int(10) unsigned NOT NULL,
   exercice_id int(10) unsigned NOT NULL,
   question_order int(10) unsigned NOT NULL DEFAULT '1',
-  PRIMARY KEY (c_id,question_id,exercice_id)
+  PRIMARY KEY (iid)
 ) ENGINE=MyISAM DEFAULT CHARSET=utf8;
 /*!40101 SET character_set_client = @saved_cs_client */;
 

+ 9 - 8
main/install/1.10.0/db_main.sql

@@ -2643,18 +2643,18 @@ CREATE TABLE IF NOT EXISTS user_rel_tag (
 );
 
 
---DROP TABLE IF EXISTS group_rel_group;
---CREATE TABLE IF NOT EXISTS group_rel_group (
+-- DROP TABLE IF EXISTS group_rel_group;
+-- CREATE TABLE IF NOT EXISTS group_rel_group (
 --	id int NOT NULL AUTO_INCREMENT,
 --	group_id int NOT NULL,
 --	subgroup_id int NOT NULL,
 --	relation_type int NOT NULL,
 --	PRIMARY KEY (id)
---);
+-- );
 
---ALTER TABLE group_rel_group ADD INDEX ( group_id );
---ALTER TABLE group_rel_group ADD INDEX ( subgroup_id );
---ALTER TABLE group_rel_group ADD INDEX ( relation_type );
+-- ALTER TABLE group_rel_group ADD INDEX ( group_id );
+-- ALTER TABLE group_rel_group ADD INDEX ( subgroup_id );
+-- ALTER TABLE group_rel_group ADD INDEX ( relation_type );
 
 DROP TABLE IF EXISTS announcement_rel_group;
 CREATE TABLE IF NOT EXISTS announcement_rel_group (
@@ -2663,6 +2663,7 @@ CREATE TABLE IF NOT EXISTS announcement_rel_group (
 	announcement_id int NOT NULL,
 	PRIMARY KEY (id)
 );
+
 --
 -- Table structure for table message attachment
 --
@@ -3263,7 +3264,7 @@ CREATE TABLE track_e_links (
   links_cours_id varchar(40) NOT NULL default '' ,
   links_link_id int NOT NULL default 0,
   links_session_id INT NOT NULL DEFAULT 0,
-  PRIMARY KEY  (links_id),
+  PRIMARY KEY (links_id),
   KEY links_cours_id (links_cours_id),
   KEY links_user_id (links_user_id)
 );
@@ -3275,7 +3276,7 @@ CREATE TABLE track_e_login (
   login_date datetime NOT NULL default '0000-00-00 00:00:00',
   login_ip varchar(39) NOT NULL default '',
   logout_date datetime NULL default NULL,
-  PRIMARY KEY  (login_id),
+  PRIMARY KEY (login_id),
   KEY login_user_id (login_user_id)
 );
 

+ 8 - 1
main/install/1.10.0/migrate-db-1.9.0-1.10.0-pre.sql

@@ -74,7 +74,7 @@ ALTER TABLE c_item_property ADD INDEX idx_itemprop_id_tool (c_id, tool(8));
 ALTER TABLE c_tool_intro MODIFY COLUMN intro_text MEDIUMTEXT NOT NULL;
 
 ALTER TABLE c_quiz_answer ADD INDEX idx_quiz_answer_c_q (c_id, question_id);
-CREATE TABLE c_quiz_order( id int unsigned NOT NULL auto_increment, c_id int unsigned NOT NULL, session_id int unsigned NOT NULL, exercise_id int NOT NULL, exercise_order INT NOT NULL, PRIMARY KEY (id));
+CREATE TABLE c_quiz_order( iid bigint unsigned NOT NULL auto_increment, c_id int unsigned NOT NULL, session_id int unsigned NOT NULL, exercise_id int NOT NULL, exercise_order INT NOT NULL, PRIMARY KEY (iid));
 
 ALTER TABLE c_quiz_question_rel_category ADD COLUMN id int unsigned NOT NULL;
 ALTER TABLE c_quiz_question_rel_category DROP PRIMARY KEY;
@@ -195,5 +195,12 @@ ALTER TABLE track_e_hotpotatoes ADD COLUMN c_id int unsigned NOT NULL default 0;
 ALTER TABLE track_e_exercices ADD COLUMN c_id int unsigned NOT NULL default 0;
 ALTER TABLE track_e_attempt ADD COLUMN c_id int unsigned NOT NULL default 0;
 
+ALTER TABLE c_quiz ADD COLUMN autolaunch int DEFAULT 0;
+RENAME TABLE c_quiz_question_category TO c_quiz_category;
+ALTER TABLE c_quiz_category ADD COLUMN parent_id int unsigned NOT NULL default 0;
+
+CREATE TABLE c_quiz_rel_category (iid bigint unsigned NOT NULL auto_increment, c_id INT unsigned default 0, category_id int unsigned NOT NULL, exercise_id int unsigned NOT NULL, count_questions int NOT NULL default 0, PRIMARY KEY(iid));
+
+
 -- Do not move this
 UPDATE settings_current SET selected_value = '1.10.0.c9a93d7' WHERE variable = 'chamilo_database_version';

+ 2 - 1
main/newscorm/learnpath_functions.inc.php

@@ -1422,7 +1422,8 @@ function export_exercise($item_id)
 
         echo $s = "<tr bgcolor='#e6e6e6'><td valign='top' colspan='2'>".get_lang('Question')." ";
         // Call the showQuestion() function from exercise.lib.php. This basically displays the question in a table.
-        $test .= showQuestion($questionId, false, 'export', $i);
+        $question_obj = Question::read($questionId);
+        $test .= showQuestion($question_obj, false, 'export', $i);
 
     } // end foreach()
 

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно