diff --git a/main/admin/resource_sequence.php b/main/admin/resource_sequence.php index 1c727230f36..12c99493b8e 100644 --- a/main/admin/resource_sequence.php +++ b/main/admin/resource_sequence.php @@ -133,7 +133,7 @@ 'content' => get_lang('Courses'), ]; -$tabs = Display::tabsOnlyLink($headers,$type === SequenceResource::COURSE_TYPE ? 2 : 1); +$tabs = Display::tabsOnlyLink($headers, $type === SequenceResource::COURSE_TYPE ? 2 : 1); $tpl->assign('create_sequence', $formSequence->returnForm()); $tpl->assign('select_sequence', $selectSequence->returnForm()); diff --git a/main/admin/user_move_stats.php b/main/admin/user_move_stats.php index 57de700bf1b..c2ad2827281 100755 --- a/main/admin/user_move_stats.php +++ b/main/admin/user_move_stats.php @@ -270,7 +270,7 @@ function compare_data($result_message) while ($row = Database::fetch_array($res, 'ASSOC')) { // Checking if the LP exist in the new session //if (in_array($row['lp_id'], array_keys($flat_list))) { - $list[$row['id']] = $row; + $list[$row['id']] = $row; //} } @@ -323,7 +323,7 @@ function compare_data($result_message) while ($row = Database::fetch_array($res, 'ASSOC')) { //Checking if the LP exist in the new session //if (in_array($row['lp_id'], array_keys($flat_list))) { - $list[$row['id']] = $row; + $list[$row['id']] = $row; //} } diff --git a/main/auth/courses_controller.php b/main/auth/courses_controller.php index fb1596292e2..54f3532e591 100755 --- a/main/auth/courses_controller.php +++ b/main/auth/courses_controller.php @@ -361,7 +361,7 @@ public function getSessionIcon($sessionName) /** * Return Session catalog rendered view. * - * @param array $limit + * @param array $limit */ public function sessionList($limit = []) { diff --git a/main/exercise/admin.php b/main/exercise/admin.php index 14c8a93b5a8..8b05b0fe6b0 100755 --- a/main/exercise/admin.php +++ b/main/exercise/admin.php @@ -160,6 +160,13 @@ api_not_allowed(true); } +if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/ptest_admin.php?'. + 'exerciseId='.$exerciseId.'&'.api_get_cidreq() + ); + exit(); +} + // doesn't select the exercise ID if we come from the question pool if (!$fromExercise) { // gets the right exercise ID, and if 0 creates a new exercise diff --git a/main/exercise/answer.class.php b/main/exercise/answer.class.php index 754010e79a3..0851b36a1c1 100755 --- a/main/exercise/answer.class.php +++ b/main/exercise/answer.class.php @@ -25,6 +25,7 @@ class Answer public $hotspot_coordinates; public $hotspot_type; public $destination; + public $ptest_category; // these arrays are used to save temporarily new answers // then they are moved into the arrays above or deleted in the event of cancellation public $new_answer; @@ -39,6 +40,7 @@ class Answer public $nbrAnswers; public $new_nbrAnswers; public $new_destination; // id of the next question if feedback option is set to Directfeedback + public $new_ptest_category; // id c_quiz_category_test public $course; //Course information public $iid; public $questionJSId; @@ -67,6 +69,7 @@ public function __construct($questionId, $course_id = 0, $exercise = null, $read $this->hotspot_coordinates = []; $this->hotspot_type = []; $this->destination = []; + $this->ptest_category = []; // clears $new_* arrays $this->cancel(); @@ -114,6 +117,7 @@ public function cancel() $this->new_hotspot_type = []; $this->new_nbrAnswers = 0; $this->new_destination = []; + $this->new_ptest_category = []; } /** @@ -146,6 +150,7 @@ public function read() $this->hotspot_coordinates[$i] = $object->hotspot_coordinates; $this->hotspot_type[$i] = $object->hotspot_type; $this->destination[$i] = $object->destination; + $this->ptest_category[$i] = $object->ptest_category; $this->autoId[$i] = $object->id_auto; $this->iid[$i] = $object->iid; $i++; @@ -249,6 +254,7 @@ public function readOrderedBy($field, $order = 'ASC') hotspot_coordinates, hotspot_type, destination, + ptest_category, id_auto, iid FROM $TBL_ANSWER @@ -274,6 +280,7 @@ public function readOrderedBy($field, $order = 'ASC') $this->hotspot_coordinates[$i] = $object->hotspot_coordinates; $this->hotspot_type[$i] = $object->hotspot_type; $this->destination[$i] = $object->destination; + $this->ptest_category[$i] = $object->ptest_category; $this->autoId[$i] = $object->id_auto; $this->iid[$i] = $object->iid; $i++; @@ -288,6 +295,7 @@ public function readOrderedBy($field, $order = 'ASC') $this->hotspot_coordinates[$i] = isset($object->hotspot_coordinates) ? $object->hotspot_coordinates : 0; $this->hotspot_type[$i] = isset($object->hotspot_type) ? $object->hotspot_type : 0; $this->destination[$i] = $doubt_data->destination; + $this->ptest_category[$i] = $object->ptest_category; $this->autoId[$i] = $doubt_data->id_auto; $this->iid[$i] = $doubt_data->iid; $i++; @@ -347,6 +355,20 @@ public function selectDestination($id) return isset($this->destination[$id]) ? $this->destination[$id] : null; } + /** + * returns the question ID of the personality test question. + * + * @author Jose Angel Ruiz (NOSOLORED) + * + * @param int $id + * + * @return int - the question ID + */ + public function selectPtCategory($id) + { + return isset($this->ptest_category[$id]) ? $this->ptest_category[$id] : null; + } + /** * returns the answer title. * @@ -445,6 +467,7 @@ public function getAnswersList($decode = false) 'hotspot_type' => $this->hotspot_type[$i], 'correct' => $this->correct[$i], 'destination' => $this->destination[$i], + 'ptest_category' => $this->ptest_category[$i], ]; } } @@ -598,7 +621,8 @@ public function createAnswer( $position, $new_hotspot_coordinates = null, $new_hotspot_type = null, - $destination = '' + $destination = '', + $ptest_category = null ) { $this->new_nbrAnswers++; $id = $this->new_nbrAnswers; @@ -610,6 +634,7 @@ public function createAnswer( $this->new_hotspot_coordinates[$id] = $new_hotspot_coordinates; $this->new_hotspot_type[$id] = $new_hotspot_type; $this->new_destination[$id] = $destination; + $this->new_ptest_category[$id] = $ptest_category; } /** @@ -626,6 +651,7 @@ public function createAnswer( * @param string $destination * @param string $hotSpotCoordinates * @param string $hotSpotType + * @param int $ptestCategory * * @return CQuizAnswer */ @@ -638,7 +664,8 @@ public function updateAnswers( $position, $destination, $hotSpotCoordinates, - $hotSpotType + $hotSpotType, + $ptestCategory ) { $em = Database::getManager(); @@ -653,7 +680,8 @@ public function updateAnswers( ->setPosition($position) ->setDestination($destination) ->setHotspotCoordinates($hotSpotCoordinates) - ->setHotspotType($hotSpotType); + ->setHotspotType($hotSpotType) + ->setPtestCategory($ptestCategory); $em->merge($quizAnswer); $em->flush(); @@ -687,6 +715,7 @@ public function save() $hotspot_coordinates = isset($this->new_hotspot_coordinates[$i]) ? $this->new_hotspot_coordinates[$i] : ''; $hotspot_type = isset($this->new_hotspot_type[$i]) ? $this->new_hotspot_type[$i] : ''; $destination = isset($this->new_destination[$i]) ? $this->new_destination[$i] : ''; + $ptestCategory = isset($this->new_ptest_category[$i]) ? $this->new_ptest_category[$i] : ''; $autoId = $this->selectAutoId($i); $iid = isset($this->iid[$i]) ? $this->iid[$i] : 0; @@ -703,7 +732,8 @@ public function save() ->setPosition($position) ->setHotspotCoordinates($hotspot_coordinates) ->setHotspotType($hotspot_type) - ->setDestination($destination); + ->setDestination($destination) + ->setPtestCategory($ptestCategory); $em->persist($quizAnswer); $em->flush(); @@ -750,7 +780,8 @@ public function save() $this->new_position[$i], $this->new_destination[$i], $this->new_hotspot_coordinates[$i], - $this->new_hotspot_type[$i] + $this->new_hotspot_type[$i], + $this->new_ptest_category[$i] ); } @@ -818,6 +849,7 @@ public function save() $this->hotspot_type = $this->new_hotspot_type; $this->nbrAnswers = $this->new_nbrAnswers; $this->destination = $this->new_destination; + $this->ptest_category = $this->new_ptest_category; $this->cancel(); } @@ -893,6 +925,7 @@ public function duplicate($newQuestion, $course_info = null) 'hotspot_coordinates' => $this->hotspot_coordinates[$i], 'hotspot_type' => $this->hotspot_type[$i], 'destination' => $this->destination[$i], + 'ptest_category' => $this->ptest_category[$i], ]; $temp[$answer['position']] = $answer; $allAnswers[$this->id[$i]] = $this->answer[$i]; @@ -977,7 +1010,8 @@ public function duplicate($newQuestion, $course_info = null) ->setPosition($this->position[$i]) ->setHotspotCoordinates($this->hotspot_coordinates[$i]) ->setHotspotType($this->hotspot_type[$i]) - ->setDestination($this->destination[$i]); + ->setDestination($this->destination[$i]) + ->setPtestCategory($this->ptest_category[$i]); $em->persist($quizAnswer); $em->flush(); diff --git a/main/exercise/exercise.class.php b/main/exercise/exercise.class.php index 3579195f64f..8430c777046 100755 --- a/main/exercise/exercise.class.php +++ b/main/exercise/exercise.class.php @@ -30,6 +30,7 @@ class Exercise public $exercise; public $description; public $sound; + public $pt_type; //EXERCISE_PT_TYPE_CLASSIC public $type; //ALL_ON_ONE_PAGE or ONE_PER_PAGE public $random; public $random_answers; @@ -104,6 +105,7 @@ public function __construct($courseId = 0) $this->exercise = ''; $this->description = ''; $this->sound = ''; + $this->pt_type = EXERCISE_PT_TYPE_CLASSIC; $this->type = ALL_ON_ONE_PAGE; $this->random = 0; $this->random_answers = 0; @@ -177,6 +179,7 @@ public function read($id, $parseQuestionList = true) $this->title = $object->title; $this->description = $object->description; $this->sound = $object->sound; + $this->pt_type = $object->pt_type; $this->type = $object->type; if (empty($this->type)) { $this->type = ONE_PER_PAGE; @@ -363,6 +366,18 @@ public function selectSound() return $this->sound; } + /** + * returns the exercise type. + * + * @author Olivier Brouckaert + * + * @return int - exercise type + */ + public function selectPtType() + { + return $this->pt_type; + } + /** * returns the exercise type. * @@ -1420,6 +1435,18 @@ public function updateSound($sound, $delete) } } + /** + * changes the exercise type. + * + * @author Olivier Brouckaert + * + * @param int $type - exercise type + */ + public function updatePtType($ptType) + { + $this->pt_type = $ptType; + } + /** * changes the exercise type. * @@ -1518,6 +1545,7 @@ public function save($type_e = '') $exercise = $this->exercise; $description = $this->description; $sound = $this->sound; + $ptType = $this->pt_type; $type = $this->type; $attempts = isset($this->attempts) ? $this->attempts : 0; $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0; @@ -1562,6 +1590,7 @@ public function save($type_e = '') if ($type_e != 'simple') { $paramsExtra = [ 'sound' => $sound, + 'pt_type' => $ptType, 'type' => $type, 'random' => $random, 'random_answers' => $random_answers, @@ -1652,6 +1681,7 @@ public function save($type_e = '') 'title' => $exercise, 'description' => $description, 'sound' => $sound, + 'pt_type' => $ptType, 'type' => $type, 'random' => $random, 'random_answers' => $random_answers, @@ -2415,6 +2445,314 @@ public function createForm($form, $type = 'full') $form->addElement('html', ''); } + if ($type == 'ptest') { + $form->addElement('hidden', 'exercisePtType', EXERCISE_PT_TYPE_PTEST); + $form->addElement('hidden', 'results_disabled', RESULT_DISABLE_PT_TYPE_PTEST); + + // Type of questions disposition on page + $radios = []; + $radios[] = $form->createElement( + 'radio', + 'exerciseType', + null, + get_lang('SimpleExercise'), + '1', + [ + 'onclick' => 'check_per_page_all()', + 'id' => 'option_page_all', + ] + ); + $radios[] = $form->createElement( + 'radio', + 'exerciseType', + null, + get_lang('SequentialExercise'), + '2', + [ + 'onclick' => 'check_per_page_one()', + 'id' => 'option_page_one', + ] + ); + + $form->addGroup($radios, null, get_lang('QuestionsPerPage')); + + $option = [ + EX_Q_SELECTION_ORDERED => get_lang('OrderedByUser'), + // Defined by user + EX_Q_SELECTION_RANDOM => get_lang('Random'), + // 1-10, All + 'per_categories' => '--------'.get_lang('UsingCategories').'----------', + // Base (A 123 {3} B 456 {3} C 789{2} D 0{0}) --> Matrix {3, 3, 2, 0} + EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED => get_lang('OrderedCategoriesAlphabeticallyWithQuestionsOrdered'), + // A 123 B 456 C 78 (0, 1, all) + EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED => get_lang('RandomCategoriesWithQuestionsOrdered'), + // C 78 B 456 A 123 + EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM => get_lang('OrderedCategoriesAlphabeticallyWithRandomQuestions'), + // A 321 B 654 C 87 + EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM => get_lang('RandomCategoriesWithRandomQuestions'), + // C 87 B 654 A 321 + //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED => get_lang('RandomCategoriesWithQuestionsOrderedNoQuestionGrouped'), + /* B 456 C 78 A 123 + 456 78 123 + 123 456 78 + */ + //EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED => get_lang('RandomCategoriesWithRandomQuestionsNoQuestionGrouped'), + /* + A 123 B 456 C 78 + B 456 C 78 A 123 + B 654 C 87 A 321 + 654 87 321 + 165 842 73 + */ + //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED => get_lang('OrderedCategoriesByParentWithQuestionsOrdered'), + //EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM => get_lang('OrderedCategoriesByParentWithQuestionsRandom'), + ]; + + $form->addElement( + 'select', + 'question_selection_type', + [get_lang('QuestionSelection')], + $option, + [ + 'id' => 'questionSelection', + 'onchange' => 'checkQuestionSelection()', + ] + ); + + $pageConfig = api_get_configuration_value('allow_quiz_results_page_config'); + if ($pageConfig) { + $group = [ + $form->createElement( + 'checkbox', + 'hide_expected_answer', + null, + get_lang('HideExpectedAnswer') + ), + $form->createElement( + 'checkbox', + 'hide_total_score', + null, + get_lang('HideTotalScore') + ), + $form->createElement( + 'checkbox', + 'hide_question_score', + null, + get_lang('HideQuestionScore') + ), + ]; + $form->addGroup($group, null, get_lang('ResultsConfigurationPage')); + } + + $displayMatrix = 'none'; + $displayRandom = 'none'; + $selectionType = $this->getQuestionSelectionType(); + switch ($selectionType) { + case EX_Q_SELECTION_RANDOM: + $displayRandom = 'block'; + break; + case $selectionType >= EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: + $displayMatrix = 'block'; + break; + } + + $form->addElement( + 'html', + '
' + ); + // Number of random question. + $max = ($this->id > 0) ? $this->getQuestionCount() : 10; + $option = range(0, $max); + $option[0] = get_lang('No'); + $option[-1] = get_lang('AllQuestionsShort'); + $form->addElement( + 'select', + 'randomQuestions', + [ + get_lang('RandomQuestions'), + get_lang('RandomQuestionsHelp'), + ], + $option, + ['id' => 'randomQuestions'] + ); + $form->addElement('html', '
'); + + $form->addElement( + 'html', + '
' + ); + + // Category selection. + $cat = new TestCategory(); + $cat_form = $cat->returnCategoryForm($this); + if (empty($cat_form)) { + $cat_form = ''.get_lang('NoCategoriesDefined').''; + } + $form->addElement('label', null, $cat_form); + $form->addElement('html', '
'); + + // Random answers. + $radios_random_answers = [ + $form->createElement('radio', 'randomAnswers', null, get_lang('Yes'), '1'), + $form->createElement('radio', 'randomAnswers', null, get_lang('No'), '0'), + ]; + $form->addGroup($radios_random_answers, null, get_lang('RandomAnswers')); + + // Category name. + $radio_display_cat_name = [ + $form->createElement('radio', 'display_category_name', null, get_lang('Yes'), '1'), + $form->createElement('radio', 'display_category_name', null, get_lang('No'), '0'), + ]; + $form->addGroup($radio_display_cat_name, null, get_lang('QuestionDisplayCategoryName')); + + // Hide question title. + $group = [ + $form->createElement('radio', 'hide_question_title', null, get_lang('Yes'), '1'), + $form->createElement('radio', 'hide_question_title', null, get_lang('No'), '0'), + ]; + $form->addGroup($group, null, get_lang('HideQuestionTitle')); + + $allow = api_get_configuration_value('allow_quiz_show_previous_button_setting'); + + if ($allow === true) { + // Hide question title. + $group = [ + $form->createElement( + 'radio', + 'show_previous_button', + null, + get_lang('Yes'), + '1' + ), + $form->createElement( + 'radio', + 'show_previous_button', + null, + get_lang('No'), + '0' + ), + ]; + $form->addGroup($group, null, get_lang('ShowPreviousButton')); + } + + // Attempts + $attempt_option = range(0, 10); + $attempt_option[0] = get_lang('Infinite'); + + $form->addElement( + 'select', + 'exerciseAttempts', + get_lang('ExerciseAttempts'), + $attempt_option, + ['id' => 'exerciseAttempts'] + ); + + // Exercise time limit + $form->addElement( + 'checkbox', + 'activate_start_date_check', + null, + get_lang('EnableStartTime'), + ['onclick' => 'activate_start_date()'] + ); + + if (!empty($this->start_time)) { + $form->addElement('html', '
'); + } else { + $form->addElement('html', ''); + $form->addElement( + 'checkbox', + 'activate_end_date_check', + null, + get_lang('EnableEndTime'), + ['onclick' => 'activate_end_date()'] + ); + + if (!empty($this->end_time)) { + $form->addElement('html', '
'); + } else { + $form->addElement('html', ''); + + $display = 'block'; + + $form->addElement('html', '
 
'); + $form->addElement('checkbox', 'review_answers', null, get_lang('ReviewAnswers')); + $form->addElement('html', '
'); + + // Timer control + $form->addElement( + 'checkbox', + 'enabletimercontrol', + null, + get_lang('EnableTimerControl'), + [ + 'onclick' => 'option_time_expired()', + 'id' => 'enabletimercontrol', + 'onload' => 'check_load_time()', + ] + ); + + $expired_date = (int) $this->selectExpiredTime(); + + if (($expired_date != '0')) { + $form->addElement('html', '
'); + } else { + $form->addElement('html', ''); + + // add the text_when_finished textbox + $form->addHtmlEditor( + 'text_when_finished', + get_lang('TextWhenFinished'), + false, + false, + $editor_config + ); + + $allow = api_get_configuration_value('allow_notification_setting_per_exercise'); + if ($allow === true) { + $settings = ExerciseLib::getNotificationSettings(); + $group = []; + foreach ($settings as $itemId => $label) { + $group[] = $form->createElement( + 'checkbox', + 'notifications[]', + null, + $label, + ['value' => $itemId] + ); + } + $form->addGroup($group, '', [get_lang('EmailNotifications')]); + } + + $form->addCheckBox( + 'update_title_in_lps', + null, + get_lang('UpdateTitleInLps') + ); + + $form->addElement('html', '
'); //End advanced setting + $form->addElement('html', '
'); + } + // submit if (isset($_GET['exerciseId'])) { $form->addButtonSave(get_lang('ModifyExercise'), 'submitExercise'); @@ -2492,6 +2830,66 @@ public function createForm($form, $type = 'full') $defaults['on_success_message'] = null; $defaults['on_failed_message'] = null; } + } elseif ($type == 'ptest') { + // rules + $form->addRule('exerciseAttempts', get_lang('Numeric'), 'numeric'); + $form->addRule('start_time', get_lang('InvalidDate'), 'datetime'); + $form->addRule('end_time', get_lang('InvalidDate'), 'datetime'); + + if ($this->id > 0) { + $defaults['randomQuestions'] = $this->random; + $defaults['randomAnswers'] = $this->getRandomAnswers(); + $defaults['exerciseType'] = $this->selectType(); + $defaults['exerciseTitle'] = $this->get_formated_title(); + $defaults['exerciseDescription'] = $this->selectDescription(); + $defaults['exerciseAttempts'] = $this->selectAttempts(); + $defaults['review_answers'] = $this->review_answers; + $defaults['randomByCat'] = $this->getRandomByCategory(); + $defaults['text_when_finished'] = $this->getTextWhenFinished(); + $defaults['display_category_name'] = $this->selectDisplayCategoryName(); + $defaults['question_selection_type'] = $this->getQuestionSelectionType(); + $defaults['hide_question_title'] = $this->getHideQuestionTitle(); + $defaults['show_previous_button'] = $this->showPreviousButton(); + $defaults['exercise_category_id'] = $this->getExerciseCategoryId(); + + if (!empty($this->start_time)) { + $defaults['activate_start_date_check'] = 1; + } + if (!empty($this->end_time)) { + $defaults['activate_end_date_check'] = 1; + } + + $defaults['start_time'] = !empty($this->start_time) ? api_get_local_time($this->start_time) : date('Y-m-d 12:00:00'); + $defaults['end_time'] = !empty($this->end_time) ? api_get_local_time($this->end_time) : date('Y-m-d 12:00:00', time() + 84600); + + // Get expired time + if ($this->expired_time != '0') { + $defaults['enabletimercontrol'] = 1; + $defaults['enabletimercontroltotalminutes'] = $this->expired_time; + } else { + $defaults['enabletimercontroltotalminutes'] = 0; + } + } else { + $defaults['exerciseType'] = 2; + $defaults['exerciseAttempts'] = 0; + $defaults['randomQuestions'] = 0; + $defaults['randomAnswers'] = 0; + $defaults['exerciseDescription'] = ''; + $defaults['exerciseFeedbackType'] = 0; + $defaults['results_disabled'] = RESULT_DISABLE_PT_TYPE_PTEST; + $defaults['randomByCat'] = 0; + $defaults['text_when_finished'] = ''; + $defaults['start_time'] = date('Y-m-d 12:00:00'); + $defaults['display_category_name'] = 1; + $defaults['end_time'] = date('Y-m-d 12:00:00', time() + 84600); + $defaults['pass_percentage'] = ''; + $defaults['end_button'] = $this->selectEndButton(); + $defaults['question_selection_type'] = 1; + $defaults['hide_question_title'] = 0; + $defaults['show_previous_button'] = 1; + $defaults['on_success_message'] = null; + $defaults['on_failed_message'] = null; + } } else { $defaults['exerciseTitle'] = $this->selectTitle(); $defaults['exerciseDescription'] = $this->selectDescription(); @@ -2602,6 +3000,7 @@ public function processCreation($form, $type = '') $this->updateDescription($form->getSubmitValue('exerciseDescription')); $this->updateAttempts($form->getSubmitValue('exerciseAttempts')); $this->updateFeedbackType($form->getSubmitValue('exerciseFeedbackType')); + $this->updatePtType($form->getSubmitValue('exercisePtType')); $this->updateType($form->getSubmitValue('exerciseType')); $this->setRandom($form->getSubmitValue('randomQuestions')); $this->updateRandomAnswers($form->getSubmitValue('randomAnswers')); @@ -3503,9 +3902,14 @@ public function manage_answer( $table_ans = Database::get_course_table(TABLE_QUIZ_ANSWER); $studentChoiceDegree = null; + $ptest = false; + if ($this->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $ptest = true; + } + // Creates a temporary Question object $course_id = $this->course_id; - $objQuestionTmp = Question::read($questionId, $this->course); + $objQuestionTmp = Question::read($questionId, $this->course, true, $ptest); if ($objQuestionTmp === false) { return false; @@ -4767,6 +5171,70 @@ public function manage_answer( $questionScore = 0; } break; + case QUESTION_PT_TYPE_CATEGORY_RANKING: + if ($from_database) { + $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT + WHERE + exe_id = $exeId AND + question_id = $questionId"; + $result = Database::query($sql); + $choice = Database::result($result, 0, 'answer'); + + $studentChoice = $choice == $answerAutoId ? 1 : 0; + } else { + $studentChoice = $choice == $answerAutoId ? 1 : 0; + } + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $studentChoice = 0; + if ($from_database) { + $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT + WHERE + exe_id = $exeId AND + question_id = $questionId"; + $result = Database::query($sql); + $choice = Database::result($result, 0, 'answer'); + $aux = explode(',', $choice); + if ($aux[0] == $answerAutoId) { + $studentChoice = ANSWER_AGREE; + } + if ($aux[1] == $answerAutoId) { + $studentChoice = ANSWER_DISAGREE; + } + } + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + $studentChoice = 0; + if ($from_database) { + $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT + WHERE + exe_id = $exeId AND + question_id = $questionId"; + $result = Database::query($sql); + $choice = Database::result($result, 0, 'answer'); + $aux = json_decode($choice, true); + if (!empty($aux[$answerAutoId])) { + $studentChoice = $aux[$answerAutoId]; + } + } + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $studentChoice = 0; + if ($from_database) { + $sql = "SELECT answer FROM $TBL_TRACK_ATTEMPT + WHERE + exe_id = $exeId AND + question_id = $questionId"; + $result = Database::query($sql); + $choice = Database::result($result, 0, 'answer'); + $aux = json_decode($choice, true); + foreach ($aux as $key => $value) { + if ($value == $answerAutoId) { + $studentChoice = $key; + } + } + } + break; } if ($show_result) { @@ -5087,6 +5555,22 @@ public function manage_answer( $questionScore, $results_disabled ); + } elseif (in_array( + $answerType, + [ + QUESTION_PT_TYPE_CATEGORY_RANKING, + QUESTION_PT_TYPE_AGREE_OR_DISAGREE, + QUESTION_PT_TYPE_AGREE_SCALE, + QUESTION_PT_TYPE_AGREE_REORDER, + ] + )) { + ExerciseShowFunctions::display_ptest_answer( + $this, + $answerType, + $studentChoice, + $answer, + $this->export + ); } } } else { @@ -5799,10 +6283,28 @@ public function manage_answer( false, $objQuestionTmp->getAbsoluteFilePath() ); + } elseif ($answerType == QUESTION_PT_TYPE_AGREE_OR_DISAGREE) { + $answer = implode(',', $choice); + Event::saveQuestionAttempt( + $questionScore, + $answer, + $quesId, + $exeId, + 0, + $this->id + ); } elseif ( in_array( $answerType, - [UNIQUE_ANSWER, UNIQUE_ANSWER_IMAGE, UNIQUE_ANSWER_NO_OPTION, READING_COMPREHENSION] + [ + UNIQUE_ANSWER, + UNIQUE_ANSWER_IMAGE, + UNIQUE_ANSWER_NO_OPTION, + READING_COMPREHENSION, + QUESTION_PT_TYPE_CATEGORY_RANKING, + QUESTION_PT_TYPE_AGREE_SCALE, + QUESTION_PT_TYPE_AGREE_REORDER, + ] ) ) { $answer = $choice; @@ -6157,6 +6659,214 @@ public function showExerciseResultHeader( return $content; } + /** + * Returns the exercise charts. + * + * @param int $exeId + * @param int $courseId + * @param int $sessionId + * + * @return string + */ + public function showPtestCharts($exeId, $courseId = 0, $sessionId = 0) + { + $exeId = (int) $exeId; + $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId; + $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId; + + $cList = PTestCategory::getCategoryListInfo($this->id); + + $categoryList = []; + foreach ($cList as $item) { + $categoryList[$item->id]['label'] = $item->name; + $categoryList[$item->id]['num'] = 0; + $categoryList[$item->id]['color'] = $item->color; + } + + $answerTable = Database::get_course_table(TABLE_QUIZ_ANSWER); + $questionTable = Database::get_course_table(TABLE_QUIZ_QUESTION); + $attemptsTable = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); + + // QUESTION_PT_TYPE_CATEGORY_RANKING + $sql = "SELECT an.ptest_category AS id, COUNT(*) AS rep FROM $attemptsTable att + INNER JOIN $answerTable an ON att.answer = an.id + INNER JOIN $questionTable q ON att.question_id = q.id + WHERE + att.exe_id = $exeId AND + att.c_id = $courseId AND + att.session_id = $sessionId AND + q.type = ".QUESTION_PT_TYPE_CATEGORY_RANKING." + GROUP BY an.ptest_category"; + $res = Database::query($sql); + while ($row = Database::fetch_assoc($res)) { + $categoryList[$row['id']]['num'] = $row['rep']; + } + + // QUESTION_PT_TYPE_AGREE_OR_DISAGREE + $sql = "SELECT att.answer AS answer FROM $attemptsTable att + INNER JOIN $questionTable q ON att.question_id = q.id + WHERE + att.exe_id = $exeId AND + att.c_id = $courseId AND + att.session_id = $sessionId AND + q.type = ".QUESTION_PT_TYPE_AGREE_OR_DISAGREE; + $res = Database::query($sql); + while ($row = Database::fetch_assoc($res)) { + $answer = explode(',', $row['answer']); + // ANSWER_AGREE + $sql = "SELECT * FROM $answerTable WHERE id = ".$answer[0]; + $result = Database::query($sql); + $object = Database::fetch_object($result); + $ptestCategoryId = $object->ptest_category; + $categoryList[$ptestCategoryId]['num']++; + // ANSWER_DISAGREE + $sql = "SELECT * FROM $answerTable WHERE id = ".$answer[1]; + $result = Database::query($sql); + $object = Database::fetch_object($result); + $ptestCategoryId = $object->ptest_category; + $categoryList[$ptestCategoryId]['num']--; + } + + // QUESTION_PT_TYPE_AGREE_SCALE + $sql = "SELECT att.answer AS answer FROM $attemptsTable att + INNER JOIN $questionTable q ON att.question_id = q.id + WHERE + att.exe_id = $exeId AND + att.c_id = $courseId AND + att.session_id = $sessionId AND + q.type = ".QUESTION_PT_TYPE_AGREE_SCALE; + $res = Database::query($sql); + while ($row = Database::fetch_assoc($res)) { + $answer = json_decode($row['answer'], 1); + foreach ($answer as $key => $value) { + $sql = "SELECT * FROM $answerTable WHERE id = ".$key; + $result = Database::query($sql); + $object = Database::fetch_object($result); + $ptestCategoryId = $object->ptest_category; + $categoryList[$ptestCategoryId]['num'] += $value; + } + } + + // QUESTION_PT_TYPE_AGREE_REORDER + $sql = "SELECT att.answer AS answer FROM $attemptsTable att + INNER JOIN $questionTable q ON att.question_id = q.id + WHERE + att.exe_id = $exeId AND + att.c_id = $courseId AND + att.session_id = $sessionId AND + q.type = ".QUESTION_PT_TYPE_AGREE_REORDER; + $res = Database::query($sql); + while ($row = Database::fetch_assoc($res)) { + $answer = json_decode($row['answer'], 1); + foreach ($answer as $key => $value) { + $sql = "SELECT * FROM $answerTable WHERE id = ".$value; + $result = Database::query($sql); + $object = Database::fetch_object($result); + $ptestCategoryId = $object->ptest_category; + $categoryList[$ptestCategoryId]['num'] += $key; + } + } + + $labels = []; + $num = []; + $backgroundColor = $borderColor = []; + foreach ($categoryList as $item) { + $labels[] = $item['label']; + $data[] = (int) $item['num']; + $bgColor = 'rgba(0, 0, 0, 0.1)'; + $brColor = 'rgba(0, 0, 0, 0.1)'; + + if (!empty($item['color'])) { + $hex = $item['color']; + list($r, $g, $b) = sscanf($hex, "#%02x%02x%02x"); + $brColor = 'rgb('.$r.', '.$g.', '.$b.')'; + $bgColor = 'rgba('.$r.', '.$g.', '.$b.', 0.6)'; + } + + $backgroundColor[] = $bgColor; + $borderColor[] = $brColor; + } + + $html = ''; + $html .= '
'; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '
'; + $html .= ''; + $html .= '
'; + $html .= '

'; + + $html .= ''; + + return $html; + } + /** * Returns the exercise result. * @@ -8084,6 +8794,10 @@ public function showExpectedChoiceColumn() return false; } + if ($this->pt_type == EXERCISE_PT_TYPE_PTEST) { + return false; + } + return true; } @@ -8727,6 +9441,11 @@ public static function exerciseGrid($categoryId, $keyword = '') $url .= Display::div($embeddableIcon, ['class' => 'pull-right']); } + if ($row['pt_type'] == EXERCISE_PT_TYPE_PTEST) { + $embeddableIcon = Display::return_icon('new_personality_test.png', get_lang('PtestType')); + $url .= Display::div($embeddableIcon, ['class' => 'pull-right']); + } + $currentRow['title'] = $url.' '.$session_img.$lp_blocked; // Count number exercise - teacher @@ -8753,83 +9472,99 @@ public static function exerciseGrid($categoryId, $keyword = '') } $actions .= $settings; - // Exercise results - $resultsLink = ''. - Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).''; + if ($exercise->pt_type != EXERCISE_PT_TYPE_PTEST) { + // Exercise results + $resultsLink = ''. + Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).''; - if ($limitTeacherAccess) { - if (api_is_platform_admin()) { + if ($limitTeacherAccess) { + if (api_is_platform_admin()) { + $actions .= $resultsLink; + } + } else { + // Exercise results $actions .= $resultsLink; } - } else { - // Exercise results - $actions .= $resultsLink; - } - // Auto launch - if ($autoLaunchAvailable) { - $autoLaunch = $exercise->getAutoLaunch(); - if (empty($autoLaunch)) { - $actions .= Display::url( + // Auto launch + if ($autoLaunchAvailable) { + $autoLaunch = $exercise->getAutoLaunch(); + if (empty($autoLaunch)) { + $actions .= Display::url( + Display::return_icon( + 'launch_na.png', + get_lang('Enable'), + '', + ICON_SIZE_SMALL + ), + 'exercise.php?'.api_get_cidreq().'&choice=enable_launch&sec_token='.$token.'&exerciseId='.$row['id'] + ); + } else { + $actions .= Display::url( + Display::return_icon( + 'launch.png', + get_lang('Disable'), + '', + ICON_SIZE_SMALL + ), + 'exercise.php?'.api_get_cidreq().'&choice=disable_launch&sec_token='.$token.'&exerciseId='.$row['id'] + ); + } + } + + // Export + $actions .= Display::url( + Display::return_icon('cd.png', get_lang('CopyExercise')), + '', + [ + 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", + 'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'], + ] + ); + + // Clean exercise + if ($locked == false) { + $clean = Display::url( Display::return_icon( - 'launch_na.png', - get_lang('Enable'), + 'clean.png', + get_lang('CleanStudentResults'), '', ICON_SIZE_SMALL ), - 'exercise.php?'.api_get_cidreq().'&choice=enable_launch&sec_token='.$token.'&exerciseId='.$row['id'] + '', + [ + 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteResults'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", + 'href' => 'exercise.php?'.api_get_cidreq().'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['id'], + ] ); } else { - $actions .= Display::url( - Display::return_icon( - 'launch.png', - get_lang('Disable'), - '', - ICON_SIZE_SMALL - ), - 'exercise.php?'.api_get_cidreq().'&choice=disable_launch&sec_token='.$token.'&exerciseId='.$row['id'] + $clean = Display::return_icon( + 'clean_na.png', + get_lang('ResourceLockedByGradebook'), + '', + ICON_SIZE_SMALL ); } - } - - // Export - $actions .= Display::url( - Display::return_icon('cd.png', get_lang('CopyExercise')), - '', - [ - 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToCopy'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", - 'href' => 'exercise.php?'.api_get_cidreq().'&choice=copy_exercise&sec_token='.$token.'&exerciseId='.$row['id'], - ] - ); - // Clean exercise - if ($locked == false) { - $clean = Display::url( - Display::return_icon( - 'clean.png', - get_lang('CleanStudentResults'), - '', - ICON_SIZE_SMALL - ), - '', - [ - 'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDeleteResults'), ENT_QUOTES, $charset))." ".addslashes($row['title'])."?"."')) return false;", - 'href' => 'exercise.php?'.api_get_cidreq().'&choice=clean_results&sec_token='.$token.'&exerciseId='.$row['id'], - ] - ); + if ($limitTeacherAccess && !api_is_platform_admin()) { + $clean = ''; + } + $actions .= $clean; } else { - $clean = Display::return_icon( - 'clean_na.png', - get_lang('ResourceLockedByGradebook'), - '', - ICON_SIZE_SMALL - ); - } + // Exercise results + $resultsLink = ''. + Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_SMALL).''; - if ($limitTeacherAccess && !api_is_platform_admin()) { - $clean = ''; + if ($limitTeacherAccess) { + if (api_is_platform_admin()) { + $actions .= $resultsLink; + } + } else { + // Exercise results + $actions .= $resultsLink; + } } - $actions .= $clean; + // Visible / invisible // Check if this exercise was added in a LP if ($exercise->exercise_was_added_in_lp == true) { @@ -8870,22 +9605,24 @@ public static function exerciseGrid($categoryId, $keyword = '') $actions .= $visibility; - // Export qti ... - $export = Display::url( - Display::return_icon( - 'export_qti2.png', - 'IMS/QTI', - '', - ICON_SIZE_SMALL - ), - 'exercise.php?action=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq() - ); + if ($exercise->pt_type != EXERCISE_PT_TYPE_PTEST) { + // Export qti ... + $export = Display::url( + Display::return_icon( + 'export_qti2.png', + 'IMS/QTI', + '', + ICON_SIZE_SMALL + ), + 'exercise.php?action=exportqti2&exerciseId='.$row['id'].'&'.api_get_cidreq() + ); - if ($limitTeacherAccess && !api_is_platform_admin()) { - $export = ''; - } + if ($limitTeacherAccess && !api_is_platform_admin()) { + $export = ''; + } - $actions .= $export; + $actions .= $export; + } } else { // not session $actions = Display::return_icon( @@ -9141,6 +9878,9 @@ public static function exerciseGrid($categoryId, $keyword = '') $attempt_text = get_lang('NotAttempted'); } } + if ($my_result_disabled == RESULT_DISABLE_PT_TYPE_PTEST) { + $attempt_text = $num.' '.get_lang('Attempts'); + } } } diff --git a/main/exercise/exercise.php b/main/exercise/exercise.php index dadeab4ec3f..5b9fd5d2da9 100644 --- a/main/exercise/exercise.php +++ b/main/exercise/exercise.php @@ -525,7 +525,12 @@ Display::return_icon('new_exercice.png', get_lang('NewEx'), '', ICON_SIZE_MEDIUM).''; $actionsLeft .= ''. Display::return_icon('new_question.png', get_lang('AddQ'), '', ICON_SIZE_MEDIUM).''; - + if (api_get_configuration_value('show_ptest_quiz')) { + $actionsLeft .= ''. + Display::return_icon('new_personality_test.png', get_lang('AddPT'), '', ICON_SIZE_MEDIUM). + ''; + } if (api_get_configuration_value('allow_exercise_categories')) { $actionsLeft .= ''; $actionsLeft .= Display::return_icon('folder.png', get_lang('Category'), '', ICON_SIZE_MEDIUM); diff --git a/main/exercise/exercise_admin.php b/main/exercise/exercise_admin.php index 8b4bd361def..bd2b9a9464a 100755 --- a/main/exercise/exercise_admin.php +++ b/main/exercise/exercise_admin.php @@ -130,6 +130,13 @@ function setFocus(){ $course_id = api_get_course_int_id(); //INIT FORM +$ptest = false; +$ptestUrl = ''; +if (!empty($_GET['ptest'])) { + $ptest = true; + $ptestUrl = '&ptest=1'; +} + if (isset($_GET['exerciseId'])) { $form = new FormValidator( 'exercise_admin', @@ -142,12 +149,16 @@ function setFocus(){ $form = new FormValidator( 'exercise_admin', 'post', - api_get_self().'?'.api_get_cidreq() + api_get_self().'?'.api_get_cidreq().$ptestUrl ); $form->addElement('hidden', 'edit', 'false'); } -$objExercise->createForm($form); +if ($ptest || $objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $objExercise->createForm($form, 'ptest'); +} else { + $objExercise->createForm($form); +} // VALIDATE FORM if ($form->validate()) { diff --git a/main/exercise/exercise_result.php b/main/exercise/exercise_result.php index 644628eaa81..69075aff1cb 100755 --- a/main/exercise/exercise_result.php +++ b/main/exercise/exercise_result.php @@ -71,6 +71,7 @@ $htmlHeadXtra[] = ''; $htmlHeadXtra[] = ''; $htmlHeadXtra[] = ''; +$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js'); if (api_get_configuration_value('quiz_prevent_copy_paste')) { $htmlHeadXtra[] = ''; } diff --git a/main/exercise/exercise_show.php b/main/exercise/exercise_show.php index bc24988aa18..9a2f46194fc 100755 --- a/main/exercise/exercise_show.php +++ b/main/exercise/exercise_show.php @@ -498,7 +498,7 @@ function getFCK(vals, marksid) { $(function() { new HotspotQuestion({ questionId: $questionId, - exerciseId: {$objExercise->id}, + exerciseId: {$objExercise->id}, exeId: $id, selector: '#hotspot-solution-$questionId-$id', for: 'solution', diff --git a/main/exercise/exercise_submit.php b/main/exercise/exercise_submit.php index 4f604c12235..8a7f4b6cbcd 100755 --- a/main/exercise/exercise_submit.php +++ b/main/exercise/exercise_submit.php @@ -168,6 +168,10 @@ exit; } +$ptest = false; +if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $ptest = true; +} // if the user has submitted the form $exercise_title = $objExercise->selectTitle(); $exercise_sound = $objExercise->selectSound(); @@ -1049,7 +1053,7 @@ if (!empty($questionList)) { foreach ($questionList as $questionId) { $i++; - $objQuestionTmp = Question::read($questionId); + $objQuestionTmp = Question::read($questionId, null, true, $ptest); // for sequential exercises if ($objExercise->type == ONE_PER_PAGE) { @@ -1243,6 +1247,16 @@ function save_now(question_id, url_extra, validate) { // 4. choice for degree of certainty var my_choiceDc = $(\'*[name*="choiceDegreeCertainty[\'+question_id+\']"]\').serialize(); + // 5. Agree o Disagree choice inputs + var my_choice_agree = $(\'*[name*="choice-agree[\'+question_id+\']"]\').serialize(); + var my_choice_disagree = $(\'*[name*="choice-disagree[\'+question_id+\']"]\').serialize(); + + // 6. Agree scale choice inputs + var my_choice_agree_scale = $(\'*[name*="choice[\'+question_id+\']"]\').serialize(); + + // 7. Agree reorder choice inputs + var my_choice_agree_reorder = $(\'*[name*="choice[\'+question_id+\']*"]\').serialize(); + // Checking CkEditor if (question_id) { if (CKEDITOR.instances["choice["+question_id+"]"]) { @@ -1261,7 +1275,8 @@ function save_now(question_id, url_extra, validate) { // Only for the first time var dataparam = "'.$params.'&type=simple&question_id="+question_id; - dataparam += "&"+my_choice+"&"+hotspot+"&"+remind_list+"&"+my_choiceDc; + dataparam += "&"+my_choice+"&"+hotspot+"&"+remind_list+"&"+my_choiceDc+"&"+my_choice_agree+"&" + +my_choice_disagree+"&"+my_choice_agree_scale+"&"+my_choice_agree_reorder; $("#save_for_now_"+question_id).html(\''. Display::returnFontAwesomeIcon('spinner', null, true, 'fa-spin').'\'); diff --git a/main/exercise/overview.php b/main/exercise/overview.php index b15f710a0de..3157ddf5699 100755 --- a/main/exercise/overview.php +++ b/main/exercise/overview.php @@ -96,11 +96,21 @@ api_get_path(WEB_CODE_PATH).'exercise/admin.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id ); } - $editLink .= Display::url( - Display::return_icon('test_results.png', get_lang('Results'), [], ICON_SIZE_SMALL), - api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'.api_get_cidreq().'&exerciseId='.$objExercise->id, - ['title' => get_lang('Results')] - ); + if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $editLink .= Display::url( + Display::return_icon('test_results.png', get_lang('Results'), [], ICON_SIZE_SMALL), + api_get_path(WEB_CODE_PATH).'exercise/ptest_exercise_report.php?'. + api_get_cidreq().'&exerciseId='.$objExercise->id, + ['title' => get_lang('Results')] + ); + } else { + $editLink .= Display::url( + Display::return_icon('test_results.png', get_lang('Results'), [], ICON_SIZE_SMALL), + api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'. + api_get_cidreq().'&exerciseId='.$objExercise->id, + ['title' => get_lang('Results')] + ); + } } $iconExercise = Display::return_icon('test-quiz.png', null, [], ICON_SIZE_MEDIUM); @@ -244,6 +254,9 @@ if ($attempt_result['attempt_revised'] == 0) { $teacher_revised = Display::label(get_lang('NotValidated'), 'info'); } + if ($objExercise->results_disabled == RESULT_DISABLE_PT_TYPE_PTEST) { + $teacher_revised = ''; + } $row = [ 'count' => $i, 'date' => api_convert_and_format_date( @@ -280,6 +293,7 @@ RESULT_DISABLE_DONT_SHOW_SCORE_ONLY_IF_USER_FINISHES_ATTEMPTS_SHOW_ALWAYS_FEEDBACK, RESULT_DISABLE_RANKING, RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, + RESULT_DISABLE_PT_TYPE_PTEST, ] ) || ( $objExercise->results_disabled == RESULT_DISABLE_SHOW_SCORE_ONLY && @@ -370,6 +384,9 @@ ]; } break; + case RESULT_DISABLE_PT_TYPE_PTEST: + $header_names = [get_lang('Attempt'), get_lang('StartDate'), get_lang('IP'), get_lang('Details')]; + break; } $column = 0; foreach ($header_names as $item) { diff --git a/main/exercise/ptest_admin.php b/main/exercise/ptest_admin.php new file mode 100644 index 00000000000..b76b2159095 --- /dev/null +++ b/main/exercise/ptest_admin.php @@ -0,0 +1,392 @@ + $val) { + if (is_string($val)) { + $_POST[$key] = stripslashes($val); + } elseif (is_array($val)) { + foreach ($val as $key2 => $val2) { + $_POST[$key][$key2] = stripslashes($val2); + } + } + $GLOBALS[$key] = $_POST[$key]; + } +} + +$newQuestion = isset($_GET['newQuestion']) ? $_GET['newQuestion'] : 0; +$modifyAnswers = isset($_GET['modifyAnswers']) ? $_GET['modifyAnswers'] : 0; +$editQuestion = isset($_GET['editQuestion']) ? $_GET['editQuestion'] : 0; +$page = isset($_GET['page']) && !empty($_GET['page']) ? (int) $_GET['page'] : 1; +$modifyQuestion = isset($_GET['modifyQuestion']) ? $_GET['modifyQuestion'] : 0; +$deleteQuestion = isset($_GET['deleteQuestion']) ? $_GET['deleteQuestion'] : 0; +if (empty($questionId)) { + $questionId = Session::read('questionId'); +} +if (empty($modifyExercise)) { + $modifyExercise = isset($_GET['modifyExercise']) ? $_GET['modifyExercise'] : null; +} + +$fromExercise = isset($fromExercise) ? $fromExercise : null; +$cancelExercise = isset($cancelExercise) ? $cancelExercise : null; +$cancelAnswers = isset($cancelAnswers) ? $cancelAnswers : null; +$modifyIn = isset($modifyIn) ? $modifyIn : null; +$cancelQuestion = isset($cancelQuestion) ? $cancelQuestion : null; + +/* Cleaning all incomplete attempts of the admin/teacher to avoid weird problems + when changing the exercise settings, number of questions, etc */ +Event::delete_all_incomplete_attempts( + api_get_user_id(), + $exerciseId, + api_get_course_int_id(), + api_get_session_id() +); + +// get from session +$objExercise = Session::read('objExercise'); +$objQuestion = Session::read('objQuestion'); + +if (isset($_REQUEST['convertAnswer'])) { + $objQuestion = $objQuestion->swapSimpleAnswerTypes(); + Session::write('objQuestion', $objQuestion); +} +$objAnswer = Session::read('objAnswer'); +$_course = api_get_course_info(); + +// document path +$documentPath = api_get_path(SYS_COURSE_PATH).$_course['path'].'/document'; + +// picture path +$picturePath = $documentPath.'/images'; + +// audio path +$audioPath = $documentPath.'/audio'; + +// tables used in the exercise tool +if (!empty($_GET['action']) && $_GET['action'] == 'exportqti2' && !empty($_GET['questionId'])) { + require_once 'export/qti2/qti2_export.php'; + $export = export_question_qti($_GET['questionId'], true); + $qid = (int) $_GET['questionId']; + $archive_path = api_get_path(SYS_ARCHIVE_PATH); + $temp_dir_short = uniqid(); + $temp_zip_dir = $archive_path."/".$temp_dir_short; + if (!is_dir($temp_zip_dir)) { + mkdir($temp_zip_dir, api_get_permissions_for_new_directories()); + } + $temp_zip_file = $temp_zip_dir."/".api_get_unique_id().".zip"; + $temp_xml_file = $temp_zip_dir."/qti2export_".$qid.'.xml'; + file_put_contents($temp_xml_file, $export); + $zip_folder = new PclZip($temp_zip_file); + $zip_folder->add($temp_xml_file, PCLZIP_OPT_REMOVE_ALL_PATH); + $name = 'qti2_export_'.$qid.'.zip'; + + DocumentManager::file_send_for_download($temp_zip_file, true, $name); + unlink($temp_zip_file); + unlink($temp_xml_file); + rmdir($temp_zip_dir); + exit; //otherwise following clicks may become buggy +} + +// Exercise object creation. +if (!is_object($objExercise) || $objExercise->selectPtType() != EXERCISE_PT_TYPE_PTEST) { + // construction of the Exercise object + $objExercise = new Exercise(); + + // creation of a new exercise if wrong or not specified exercise ID + if ($exerciseId) { + $parseQuestionList = $showPagination > 0 ? false : true; + if ($editQuestion) { + $parseQuestionList = false; + $showPagination = true; + } + $objExercise->read($exerciseId, $parseQuestionList); + } + // saves the object into the session + Session::write('objExercise', $objExercise); +} + +// Exercise can be edited in their course. +if ($objExercise->sessionId != $sessionId) { + api_not_allowed(true); +} + +// doesn't select the exercise ID if we come from the question pool +if (!$fromExercise) { + // gets the right exercise ID, and if 0 creates a new exercise + if (!$exerciseId = $objExercise->selectId()) { + $modifyExercise = 'yes'; + } +} + +$nbrQuestions = $objExercise->getQuestionCount(); + +// Question object creation. +if ($editQuestion || $newQuestion || $modifyQuestion || $modifyAnswers) { + if ($editQuestion || $newQuestion) { + // reads question data + if ($editQuestion) { + // question not found + if (!$objQuestion = Question::read($editQuestion, null, true, true)) { + api_not_allowed(true); + } + // saves the object into the session + Session::write('objQuestion', $objQuestion); + } + } + + // checks if the object exists + if (is_object($objQuestion)) { + // gets the question ID + $questionId = $objQuestion->selectId(); + } +} + +// if cancelling an exercise +if ($cancelExercise) { + // existing exercise + if ($exerciseId) { + unset($modifyExercise); + } else { + // new exercise + // goes back to the exercise list + header('Location: '.api_get_path(WEB_CODE_PATH).'exercise/exercise.php?'.api_get_cidreq()); + exit(); + } +} + +// if cancelling question creation/modification +if ($cancelQuestion) { + // if we are creating a new question from the question pool + if (!$exerciseId && !$questionId) { + // goes back to the question pool + header('Location: question_pool.php?'.api_get_cidreq()); + exit(); + } else { + // goes back to the question viewing + $editQuestion = $modifyQuestion; + unset($newQuestion, $modifyQuestion); + } +} + +// if cancelling answer creation/modification +if ($cancelAnswers) { + // goes back to the question viewing + $editQuestion = $modifyAnswers; + unset($modifyAnswers); +} + +$nameTools = ''; +// modifies the query string that is used in the link of tool name +if ($editQuestion || $modifyQuestion || $newQuestion || $modifyAnswers) { + $nameTools = get_lang('QuestionManagement'); +} + +if (api_is_in_gradebook()) { + $interbreadcrumb[] = [ + 'url' => Category::getUrl(), + 'name' => get_lang('ToolGradebook'), + ]; +} + +$interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')]; +if (isset($_GET['newQuestion']) || isset($_GET['editQuestion'])) { + $interbreadcrumb[] = [ + 'url' => 'admin.php?exerciseId='.$objExercise->id.'&'.api_get_cidreq(), + 'name' => $objExercise->selectTitle(true), + ]; +} else { + $interbreadcrumb[] = [ + 'url' => '#', + 'name' => $objExercise->selectTitle(true), + ]; +} + +// if the question is duplicated, disable the link of tool name +if ($modifyIn === 'thisExercise') { + if ($buttonBack) { + $modifyIn = 'allExercises'; + } +} + +$htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js'); +$htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js'); + +$template = new Template(); +$templateName = $template->get_template('exercise/submit.js.tpl'); +$htmlHeadXtra[] = $template->fetch($templateName); +$htmlHeadXtra[] = api_get_js('d3/jquery.xcolor.js'); +$htmlHeadXtra[] = ''; +$htmlHeadXtra[] = ''; + +if (isset($_GET['message'])) { + if (in_array($_GET['message'], ['ExerciseStored', 'ItemUpdated', 'ItemAdded'])) { + Display::addFlash(Display::return_message(get_lang($_GET['message']), 'confirmation')); + } +} + +Display::display_header($nameTools, 'Exercise'); + +// If we are in a test +$inATest = isset($exerciseId) && $exerciseId > 0; + +if ($inATest) { + echo '
'; + if (isset($_GET['hotspotadmin']) || isset($_GET['newQuestion'])) { + echo ''. + Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).''; + } + + echo ''. + Display::return_icon('preview_view.png', get_lang('Preview'), '', ICON_SIZE_MEDIUM).''; + echo Display::url( + Display::return_icon('test_results.png', get_lang('Results'), '', ICON_SIZE_MEDIUM), + api_get_path(WEB_CODE_PATH).'exercise/exercise_report.php?'. + api_get_cidreq().'&exerciseId='.$objExercise->id + ); + echo ''. + Display::return_icon('settings.png', get_lang('ModifyExercise'), '', ICON_SIZE_MEDIUM).''; + echo '
'; + if ($objExercise->added_in_lp()) { + echo Display::return_message(get_lang('AddedToLPCannotBeAccessed'), 'warning'); + } + if ($editQuestion && $objQuestion->existsInAnotherExercise()) { + echo Display::return_message( + Display::returnFontAwesomeIcon('exclamation-triangle"') + .get_lang('ThisQuestionExistsInAnotherExercisesWarning'), + 'warning', + false + ); + } +} elseif (isset($_GET['newQuestion'])) { + // we are in create a new question from question pool not in a test + echo ''; +} else { + // If we are in question_pool but not in an test, go back to question create in pool + echo ''; +} + +if ($newQuestion || $editQuestion) { + // Question management + $type = isset($_REQUEST['answerType']) ? Security::remove_XSS($_REQUEST['answerType']) : null; + echo ''; + + if ($newQuestion === 'yes') { + $objExercise->edit_exercise_in_lp = true; + require 'question_ptest_admin.inc.php'; + } + if ($editQuestion) { + // Question preview if teacher clicked the "switch to student" + if ($studentViewActive && $is_allowedToEdit) { + echo '
'; + echo Display::div($objQuestion->selectTitle(), ['class' => 'question_title']); + ExerciseLib::showQuestion( + $objExercise, + $editQuestion, + false, + null, + null, + false, + true, + false, + true, + true + ); + echo '
'; + } else { + require 'question_ptest_admin.inc.php'; + } + } +} + +if (!$newQuestion && !$modifyQuestion && !$editQuestion && !isset($_GET['hotspotadmin'])) { + // question list management + require 'question_list_ptest_admin.inc.php'; +} + +// if we are in question authoring, display warning to user is feedback not shown at the end of the test -ref #6619 +// this test to display only message in the question authoring page and not in the question list page too +if ($objExercise->getFeedbackType() == EXERCISE_FEEDBACK_TYPE_EXAM) { + echo Display::return_message(get_lang('TestFeedbackNotShown'), 'normal'); +} + +Session::write('objExercise', $objExercise); +Session::write('objQuestion', $objQuestion); +Session::write('objAnswer', $objAnswer); +Display::display_footer(); diff --git a/main/exercise/ptest_agree_disagree.class.php b/main/exercise/ptest_agree_disagree.class.php new file mode 100644 index 00000000000..9dbd7aabcc1 --- /dev/null +++ b/main/exercise/ptest_agree_disagree.class.php @@ -0,0 +1,296 @@ +type = QUESTION_PT_TYPE_AGREE_OR_DISAGREE; + $this->isContent = $this->getIsContent(); + } + + /** + * {@inheritdoc} + */ + public function createAnswersForm($form) + { + // Getting the exercise list + /** @var Exercise $objEx */ + $objEx = Session::read('objExercise'); + + $editorConfig = [ + 'ToolbarSet' => 'TestProposedAnswer', + 'Width' => '100%', + 'Height' => '125', + ]; + + // Categories options select + $category = new PTestCategory(); + $categoriesList = $category->getCategoryListInfo($objEx->selectId()); + $categoriesOptions = [null => get_lang('None')]; + foreach ($categoriesList as $categoryItem) { + $categoriesOptions[$categoryItem->id] = (string) $categoryItem->name; + } + + //this line defines how many questions by default appear when creating a choice question + // The previous default value was 2. See task #1759. + $nbAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : count($categoriesList); + $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0)); + + $html = ' + + + + + + + + '; + + $form->addHeader(get_lang('Answers')); + $form->addHtml($html); + + $defaults = []; + if (!empty($this->id)) { + $answer = new Answer($this->id); + $answer->read(); + if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) { + $nbAnswers = $answer->nbrAnswers; + } + } + $form->addElement('hidden', 'nb_answers'); + + //$temp_scenario = []; + if ($nbAnswers < 1) { + $nbAnswers = 1; + echo Display::return_message( + get_lang('YouHaveToCreateAtLeastOneAnswer') + ); + } + + for ($i = 1; $i <= $nbAnswers; $i++) { + $form->addHtml(''); + if (isset($answer) && is_object($answer)) { + $defaults['answer['.$i.']'] = isset($answer->answer[$i]) ? $answer->answer[$i] : ''; + $defaults['ptest_category['.$i.']'] = 0; + if (isset($answer->ptest_category[$i])) { + $defaults['ptest_category['.$i.']'] = $answer->ptest_category[$i]; + } + } + + $renderer = $form->defaultRenderer(); + $renderer->setElementTemplate( + '', + 'counter['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'answer['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'ptest_category['.$i.']' + ); + $answerNumber = $form->addElement( + 'text', + 'counter['.$i.']', + null, + ' value = "'.$i.'"' + ); + $answerNumber->freeze(); + + $form->addHtmlEditor('answer['.$i.']', null, null, false, $editorConfig); + + $form->addRule( + 'answer['.$i.']', + get_lang('ThisFieldIsRequired'), + 'required' + ); + + $form->addSelect( + 'ptest_category['.$i.']', + null, + $categoriesOptions + ); + + $form->addHtml(''); + } + + $form->addHtml(''); + $form->addHtml('
'.get_lang('Number').''.get_lang('Answer').''.get_lang('PtestCategory').'
{error}
{element}
{error}
{element}
{error}
{element}
'); + + global $text; + $buttonGroup = []; + + if ($objEx->edit_exercise_in_lp == true || + (empty($this->exerciseList) && empty($objEx->id)) + ) { + //setting the save button here and not in the question class.php + $buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true); + $buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true); + $buttonGroup[] = $form->addButton( + 'submitQuestion', + $text, + 'check', + 'primary', + 'default', + null, + ['id' => 'submit-question'], + true + ); + $form->addGroup($buttonGroup); + } + + if (!empty($this->id)) { + $form->setDefaults($defaults); + } else { + if ($this->isContent == 1) { + // Default sample content. + $form->setDefaults($defaults); + } + } + $form->setConstants(['nb_answers' => $nbAnswers]); + } + + /** + * {@inheritdoc} + */ + public function processAnswersCreation($form, $exercise) + { + $objAnswer = new Answer($this->id); + $nbAnswers = $form->getSubmitValue('nb_answers'); + + for ($i = 1; $i <= $nbAnswers; $i++) { + $answer = trim($form->getSubmitValue('answer['.$i.']')); + $goodAnswer = false; + $comment = ''; + $weighting = 0; + $ptestCategory = (int) $form->getSubmitValue('ptest_category['.$i.']'); + $dest = ''; + + $objAnswer->createAnswer( + $answer, + $goodAnswer, + $comment, + $weighting, + $i, + null, + null, + $dest, + $ptestCategory + ); + } + + // saves the answers into the data base + $objAnswer->save(); + + $this->save($exercise); + } + + /** + * {@inheritdoc} + */ + public function return_header(Exercise $exercise, $counter = null, $score = []) + { + $header = parent::return_header($exercise, $counter); //, $score); + $header .= ''; + + $header .= ''; + $header .= ''; + $header .= ''; + + return $header; + } + + /** + * Saves one answer to the database. + * + * @param int $id The ID of the answer (has to be calculated for this course) + * @param int $questionId The question ID (to which the answer is attached) + * @param string $title The text of the answer + * @param string $comment The feedback for the answer + * @param float $score The score you get when picking this answer + * @param int $correct Whether this answer is considered *the* correct one (this is the unique answer type) + */ + public function addAnswer( + $id, + $questionId, + $title, + $comment, + $score = 0.0, + $correct = 0 + ) { + $em = Database::getManager(); + $tblQuizAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER); + $tblQuizQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); + $courseId = api_get_course_int_id(); + $questionId = intval($questionId); + $score = floatval($score); + $correct = intval($correct); + $title = Database::escape_string($title); + $comment = Database::escape_string($comment); + // Get the max position. + $sql = "SELECT max(position) as max_position + FROM $tblQuizAnswer + WHERE + c_id = $courseId AND + question_id = $questionId"; + $rsMax = Database::query($sql); + $rowMax = Database::fetch_object($rsMax); + $position = $rowMax->max_position + 1; + + // Insert a new answer + $quizAnswer = new CQuizAnswer(); + $quizAnswer + ->setCId($courseId) + ->setId($id) + ->setQuestionId($questionId) + ->setAnswer($title) + ->setCorrect($correct) + ->setComment($comment) + ->setPonderation($score) + ->setPosition($position) + ->setDestination('0@@0@@0@@0'); + + $em->persist($quizAnswer); + $em->flush(); + + $id = $quizAnswer->getIid(); + + if ($id) { + $quizAnswer + ->setId($id); + + $em->merge($quizAnswer); + $em->flush(); + } + + if ($correct) { + $sql = "UPDATE $tblQuizQuestion + SET ponderation = (ponderation + $score) + WHERE c_id = $courseId AND id = ".$questionId; + Database::query($sql); + } + } +} diff --git a/main/exercise/ptest_agree_reorder.class.php b/main/exercise/ptest_agree_reorder.class.php new file mode 100644 index 00000000000..1906ebe8a7c --- /dev/null +++ b/main/exercise/ptest_agree_reorder.class.php @@ -0,0 +1,296 @@ +type = QUESTION_PT_TYPE_AGREE_REORDER; + $this->isContent = $this->getIsContent(); + } + + /** + * {@inheritdoc} + */ + public function createAnswersForm($form) + { + // Getting the exercise list + /** @var Exercise $objEx */ + $objEx = Session::read('objExercise'); + + $editorConfig = [ + 'ToolbarSet' => 'TestProposedAnswer', + 'Width' => '100%', + 'Height' => '125', + ]; + + // Categories options select + $category = new PTestCategory(); + $categoriesList = $category->getCategoryListInfo($objEx->selectId()); + $categoriesOptions = [null => get_lang('None')]; + foreach ($categoriesList as $categoryItem) { + $categoriesOptions[$categoryItem->id] = (string) $categoryItem->name; + } + + //this line defines how many questions by default appear when creating a choice question + // The previous default value was 2. See task #1759. + $nbAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : count($categoriesList); + $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0)); + + $html = '
'.get_lang('Choice').''.get_lang('Answer').'
+ + + + + + + + '; + + $form->addHeader(get_lang('Answers')); + $form->addHtml($html); + + $defaults = []; + if (!empty($this->id)) { + $answer = new Answer($this->id); + $answer->read(); + if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) { + $nbAnswers = $answer->nbrAnswers; + } + } + $form->addElement('hidden', 'nb_answers'); + + //$temp_scenario = []; + if ($nbAnswers < 1) { + $nbAnswers = 1; + echo Display::return_message( + get_lang('YouHaveToCreateAtLeastOneAnswer') + ); + } + + for ($i = 1; $i <= $nbAnswers; $i++) { + $form->addHtml(''); + if (isset($answer) && is_object($answer)) { + $defaults['answer['.$i.']'] = isset($answer->answer[$i]) ? $answer->answer[$i] : ''; + $defaults['ptest_category['.$i.']'] = 0; + if (isset($answer->ptest_category[$i])) { + $defaults['ptest_category['.$i.']'] = $answer->ptest_category[$i]; + } + } + + $renderer = $form->defaultRenderer(); + $renderer->setElementTemplate( + '', + 'counter['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'answer['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'ptest_category['.$i.']' + ); + $answerNumber = $form->addElement( + 'text', + 'counter['.$i.']', + null, + ' value = "'.$i.'"' + ); + $answerNumber->freeze(); + + $form->addHtmlEditor('answer['.$i.']', null, null, false, $editorConfig); + + $form->addRule( + 'answer['.$i.']', + get_lang('ThisFieldIsRequired'), + 'required' + ); + + $form->addSelect( + 'ptest_category['.$i.']', + null, + $categoriesOptions + ); + + $form->addHtml(''); + } + + $form->addHtml(''); + $form->addHtml('
'.get_lang('Number').''.get_lang('Answer').''.get_lang('PtestCategory').'
{error}
{element}
{error}
{element}
{error}
{element}
'); + + global $text; + $buttonGroup = []; + + if ($objEx->edit_exercise_in_lp == true || + (empty($this->exerciseList) && empty($objEx->id)) + ) { + //setting the save button here and not in the question class.php + $buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true); + $buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true); + $buttonGroup[] = $form->addButton( + 'submitQuestion', + $text, + 'check', + 'primary', + 'default', + null, + ['id' => 'submit-question'], + true + ); + $form->addGroup($buttonGroup); + } + + if (!empty($this->id)) { + $form->setDefaults($defaults); + } else { + if ($this->isContent == 1) { + // Default sample content. + $form->setDefaults($defaults); + } + } + $form->setConstants(['nb_answers' => $nbAnswers]); + } + + /** + * {@inheritdoc} + */ + public function processAnswersCreation($form, $exercise) + { + $objAnswer = new Answer($this->id); + $nbAnswers = $form->getSubmitValue('nb_answers'); + + for ($i = 1; $i <= $nbAnswers; $i++) { + $answer = trim($form->getSubmitValue('answer['.$i.']')); + $goodAnswer = false; + $comment = ''; + $weighting = 0; + $ptestCategory = (int) $form->getSubmitValue('ptest_category['.$i.']'); + $dest = ''; + + $objAnswer->createAnswer( + $answer, + $goodAnswer, + $comment, + $weighting, + $i, + null, + null, + $dest, + $ptestCategory + ); + } + + // saves the answers into the data base + $objAnswer->save(); + + $this->save($exercise); + } + + /** + * {@inheritdoc} + */ + public function return_header(Exercise $exercise, $counter = null, $score = []) + { + $header = parent::return_header($exercise, $counter); //, $score); + $header .= ''; + + $header .= ''; + $header .= ''; + $header .= ''; + + return $header; + } + + /** + * Saves one answer to the database. + * + * @param int $id The ID of the answer (has to be calculated for this course) + * @param int $questionId The question ID (to which the answer is attached) + * @param string $title The text of the answer + * @param string $comment The feedback for the answer + * @param float $score The score you get when picking this answer + * @param int $correct Whether this answer is considered *the* correct one (this is the unique answer type) + */ + public function addAnswer( + $id, + $questionId, + $title, + $comment, + $score = 0.0, + $correct = 0 + ) { + $em = Database::getManager(); + $tblQuizAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER); + $tblQuizQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); + $courseId = api_get_course_int_id(); + $questionId = intval($questionId); + $score = floatval($score); + $correct = intval($correct); + $title = Database::escape_string($title); + $comment = Database::escape_string($comment); + // Get the max position. + $sql = "SELECT max(position) as max_position + FROM $tblQuizAnswer + WHERE + c_id = $courseId AND + question_id = $questionId"; + $rsMax = Database::query($sql); + $rowMax = Database::fetch_object($rsMax); + $position = $rowMax->max_position + 1; + + // Insert a new answer + $quizAnswer = new CQuizAnswer(); + $quizAnswer + ->setCId($courseId) + ->setId($id) + ->setQuestionId($questionId) + ->setAnswer($title) + ->setCorrect($correct) + ->setComment($comment) + ->setPonderation($score) + ->setPosition($position) + ->setDestination('0@@0@@0@@0'); + + $em->persist($quizAnswer); + $em->flush(); + + $id = $quizAnswer->getIid(); + + if ($id) { + $quizAnswer + ->setId($id); + + $em->merge($quizAnswer); + $em->flush(); + } + + if ($correct) { + $sql = "UPDATE $tblQuizQuestion + SET ponderation = (ponderation + $score) + WHERE c_id = $courseId AND id = ".$questionId; + Database::query($sql); + } + } +} diff --git a/main/exercise/ptest_agree_scale.class.php b/main/exercise/ptest_agree_scale.class.php new file mode 100644 index 00000000000..2105f29f40c --- /dev/null +++ b/main/exercise/ptest_agree_scale.class.php @@ -0,0 +1,348 @@ +type = QUESTION_PT_TYPE_AGREE_SCALE; + $this->isContent = $this->getIsContent(); + } + + /** + * {@inheritdoc} + */ + public function createAnswersForm($form) + { + // Getting the exercise list + /** @var Exercise $objEx */ + $objEx = Session::read('objExercise'); + + $editorConfig = [ + 'ToolbarSet' => 'TestProposedAnswer', + 'Width' => '100%', + 'Height' => '125', + ]; + + // Categories options select + $category = new PTestCategory(); + $categoriesList = $category->getCategoryListInfo($objEx->selectId()); + $categoriesOptions = [null => get_lang('None')]; + foreach ($categoriesList as $categoryItem) { + $categoriesOptions[$categoryItem->id] = (string) $categoryItem->name; + } + + //this line defines how many questions by default appear when creating a choice question + // The previous default value was 2. See task #1759. + $nbAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : count($categoriesList); + $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0)); + + $html = '
'.get_lang('Choice').''.get_lang('Answer').'
+ + + + + + + + '; + + $form->addHeader(get_lang('Answers')); + $form->addHtml($html); + + $defaults = []; + if (!empty($this->id)) { + $answer = new Answer($this->id); + $answer->read(); + if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) { + $nbAnswers = $answer->nbrAnswers; + } + } + $form->addElement('hidden', 'nb_answers'); + + if ($nbAnswers < 1) { + $nbAnswers = 1; + echo Display::return_message( + get_lang('YouHaveToCreateAtLeastOneAnswer') + ); + } + + for ($i = 1; $i <= $nbAnswers; $i++) { + $form->addHtml(''); + if (isset($answer) && is_object($answer)) { + $defaults['answer['.$i.']'] = isset($answer->answer[$i]) ? $answer->answer[$i] : ''; + $defaults['ptest_category['.$i.']'] = 0; + if (isset($answer->ptest_category[$i])) { + $defaults['ptest_category['.$i.']'] = $answer->ptest_category[$i]; + } + } + + $renderer = $form->defaultRenderer(); + $renderer->setElementTemplate( + '', + 'counter['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'answer['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'ptest_category['.$i.']' + ); + $answerNumber = $form->addElement( + 'text', + 'counter['.$i.']', + null, + ' value = "'.$i.'"' + ); + $answerNumber->freeze(); + + $form->addHtmlEditor('answer['.$i.']', null, null, false, $editorConfig); + + $form->addRule( + 'answer['.$i.']', + get_lang('ThisFieldIsRequired'), + 'required' + ); + + $form->addSelect( + 'ptest_category['.$i.']', + null, + $categoriesOptions + ); + + $form->addHtml(''); + } + + $form->addHtml(''); + $form->addHtml('
'.get_lang('Number').''.get_lang('Answer').''.get_lang('PtestCategory').'
{error}
{element}
{error}
{element}
{error}
{element}
'); + + global $text; + $buttonGroup = []; + + if ($objEx->edit_exercise_in_lp == true || + (empty($this->exerciseList) && empty($objEx->id)) + ) { + //setting the save button here and not in the question class.php + $buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true); + $buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true); + $buttonGroup[] = $form->addButton( + 'submitQuestion', + $text, + 'check', + 'primary', + 'default', + null, + ['id' => 'submit-question'], + true + ); + $form->addGroup($buttonGroup); + } + + if (!empty($this->id)) { + $form->setDefaults($defaults); + } else { + if ($this->isContent == 1) { + // Default sample content. + $form->setDefaults($defaults); + } + } + $form->setConstants(['nb_answers' => $nbAnswers]); + } + + public function setDirectOptions($i, FormValidator $form, $renderer, $selectLpId, $selectQuestion) + { + $editorConfig = [ + 'ToolbarSet' => 'TestProposedAnswer', + 'Width' => '100%', + 'Height' => '125', + ]; + + $form->addHtmlEditor( + 'comment['.$i.']', + null, + null, + false, + $editorConfig + ); + // Direct feedback + //Adding extra feedback fields + $group = []; + $group['try'.$i] = $form->createElement( + 'checkbox', + 'try'.$i, + null, + get_lang('TryAgain') + ); + $group['lp'.$i] = $form->createElement( + 'select', + 'lp'.$i, + get_lang('SeeTheory').': ', + $selectLpId + ); + $group['destination'.$i] = $form->createElement( + 'select', + 'destination'.$i, + get_lang('GoToQuestion').': ', + $selectQuestion + ); + $group['url'.$i] = $form->createElement( + 'text', + 'url'.$i, + get_lang('Other').': ', + [ + 'class' => 'col-md-2', + 'placeholder' => get_lang('Other'), + ] + ); + $form->addGroup($group, 'scenario'); + + $renderer->setElementTemplate( + '{error}
{element}', + 'scenario' + ); + } + + /** + * {@inheritdoc} + */ + public function processAnswersCreation($form, $exercise) + { + $objAnswer = new Answer($this->id); + $nbAnswers = $form->getSubmitValue('nb_answers'); + + for ($i = 1; $i <= $nbAnswers; $i++) { + $answer = trim($form->getSubmitValue('answer['.$i.']')); + $goodAnswer = false; + $comment = ''; + $weighting = 0; + $ptestCategory = (int) $form->getSubmitValue('ptest_category['.$i.']'); + $dest = ''; + + $objAnswer->createAnswer( + $answer, + $goodAnswer, + $comment, + $weighting, + $i, + null, + null, + $dest, + $ptestCategory + ); + } + + // saves the answers into the data base + $objAnswer->save(); + + $this->save($exercise); + } + + /** + * {@inheritdoc} + */ + public function return_header(Exercise $exercise, $counter = null, $score = []) + { + $header = parent::return_header($exercise, $counter); //, $score); + $header .= ''; + + $header .= ''; + $header .= ''; + $header .= ''; + + return $header; + } + + /** + * Saves one answer to the database. + * + * @param int $id The ID of the answer (has to be calculated for this course) + * @param int $questionId The question ID (to which the answer is attached) + * @param string $title The text of the answer + * @param string $comment The feedback for the answer + * @param float $score The score you get when picking this answer + * @param int $correct Whether this answer is considered *the* correct one (this is the unique answer type) + */ + public function addAnswer( + $id, + $questionId, + $title, + $comment, + $score = 0.0, + $correct = 0 + ) { + $em = Database::getManager(); + $tblQuizAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER); + $tblQuizQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); + $courseId = api_get_course_int_id(); + $questionId = intval($questionId); + $score = floatval($score); + $correct = intval($correct); + $title = Database::escape_string($title); + $comment = Database::escape_string($comment); + // Get the max position. + $sql = "SELECT max(position) as max_position + FROM $tblQuizAnswer + WHERE + c_id = $courseId AND + question_id = $questionId"; + $rsMax = Database::query($sql); + $rowMax = Database::fetch_object($rsMax); + $position = $rowMax->max_position + 1; + + // Insert a new answer + $quizAnswer = new CQuizAnswer(); + $quizAnswer + ->setCId($courseId) + ->setId($id) + ->setQuestionId($questionId) + ->setAnswer($title) + ->setCorrect($correct) + ->setComment($comment) + ->setPonderation($score) + ->setPosition($position) + ->setDestination('0@@0@@0@@0'); + + $em->persist($quizAnswer); + $em->flush(); + + $id = $quizAnswer->getIid(); + + if ($id) { + $quizAnswer + ->setId($id); + + $em->merge($quizAnswer); + $em->flush(); + } + + if ($correct) { + $sql = "UPDATE $tblQuizQuestion + SET ponderation = (ponderation + $score) + WHERE c_id = $courseId AND id = ".$questionId; + Database::query($sql); + } + } +} diff --git a/main/exercise/ptest_category.class.php b/main/exercise/ptest_category.class.php new file mode 100644 index 00000000000..9737887c290 --- /dev/null +++ b/main/exercise/ptest_category.class.php @@ -0,0 +1,476 @@ +name = ''; + $this->description = ''; + $this->exercise_id = 0; + $this->color = '#000000'; + $this->position = 0; + } + + /** + * return the PTestCategory object with id=in_id. + * + * @param int $id + * @param int $courseId + * + * @return PTestCategory + */ + public function getCategory($id, $courseId = 0) + { + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $id = (int) $id; + $exerciseId = (int) $exerciseId; + $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId; + $sql = "SELECT * FROM $table + WHERE id = $id AND c_id = ".$courseId; + $res = Database::query($sql); + + if (Database::num_rows($res)) { + $row = Database::fetch_array($res); + + $this->id = $row['id']; + $this->name = $row['title']; + $this->description = $row['description']; + $this->exercise_id = $row['exercise_id']; + $this->color = $row['color']; + $this->position = $row['position']; + + return $this; + } + + return false; + } + + /** + * Save PTestCategory in the database if name doesn't exists. + * + * @param int $exerciseId + * @param int $courseId + * + * @return bool + */ + public function save($exerciseId, $courseId = 0) + { + $exerciseId = (int) $exerciseId; + $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId; + $courseInfo = api_get_course_info_by_id($courseId); + if (empty($courseInfo)) { + return false; + } + + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + + // check if name already exists + $sql = "SELECT count(*) AS nb FROM $table + WHERE + title = '".Database::escape_string($this->name)."' AND + c_id = $courseId AND + exercise_id = $exerciseId"; + $result = Database::query($sql); + $row = Database::fetch_array($result); + // lets add in BDD if not the same name + if ($row['nb'] <= 0) { + $params = [ + 'c_id' => $courseId, + 'exercise_id' => $exerciseId, + 'title' => $this->name, + 'description' => $this->description, + 'session_id' => api_get_session_id(), + 'color' => $this->color, + 'position' => $this->position, + ]; + $newId = Database::insert($table, $params); + + if ($newId) { + api_item_property_update( + $courseInfo, + TOOL_PTEST_CATEGORY, + $newId, + 'TestCategoryAdded', + api_get_user_id() + ); + } + + return $newId; + } else { + return false; + } + } + + /** + * Removes the category from the database + * if there were question in this category, the link between question and category is removed. + * + * @param int $id + * + * @return bool + */ + public function removeCategory($id) + { + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $id = (int) $id; + $courseId = api_get_course_int_id(); + $category = $this->getCategory($id); + + if ($category) { + $sql = "DELETE FROM $table + WHERE id= $id AND c_id=".$courseId; + Database::query($sql); + + return true; + } + + return false; + } + + /** + * Modify category name or description of category with id=in_id. + * + * @param int $courseId + * + * @return bool + */ + public function modifyCategory($courseId = 0) + { + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $id = (int) $this->id; + $name = Database::escape_string($this->name); + $description = Database::escape_string($this->description); + $color = Database::escape_string($this->color); + $position = Database::escape_string($this->position); + $cat = $this->getCategory($id, $courseId); + $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId; + $courseInfo = api_get_course_info_by_id($courseId); + if (empty($courseInfo)) { + return false; + } + + if ($cat) { + $sql = "UPDATE $table SET + title = '$name', + description = '$description', + color = '$color', + position = $position + WHERE id = $id AND c_id = ".$courseId; + Database::query($sql); + + // item_property update + api_item_property_update( + $courseInfo, + TOOL_PTEST_CATEGORY, + $this->id, + 'TestCategoryModified', + api_get_user_id() + ); + + return true; + } + + return false; + } + + /** + * Gets the number of categories of exercise id=in_id. + * + * @param int $exerciseId + * + * @return int + */ + public function getCategoriesExerciseNumber($exerciseId) + { + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $exerciseId = (int) $exerciseId; + $sql = "SELECT count(*) AS nb + FROM $table + WHERE exercise_id = $exerciseId AND c_id=".api_get_course_int_id(); + $res = Database::query($sql); + $row = Database::fetch_array($res); + + return $row['nb']; + } + + /** + * Return an array of all Category objects of exercise in the database + * If $field=="" Return an array of all category objects in the database + * Otherwise, return an array of all in_field value + * in the database (in_field = id or name or description). + * + * @param int $exerciseId + * @param string $field + * @param int $courseId + * + * @return array + */ + public static function getCategoryListInfo($exerciseId, $field = '', $courseId = 0) + { + $exerciseId = (int) $exerciseId; + $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId; + + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $categories = []; + if (empty($field)) { + $sql = "SELECT id FROM $table + WHERE c_id = $courseId AND exercise_id = $exerciseId + ORDER BY position ASC"; + $res = Database::query($sql); + while ($row = Database::fetch_array($res)) { + $category = new PTestCategory(); + $categories[] = $category->getCategory($row['id'], $courseId); + } + } else { + $field = Database::escape_string($field); + $sql = "SELECT $field FROM $table + WHERE c_id = $courseId AND exercise_id = $exerciseId + ORDER BY $field ASC"; + $res = Database::query($sql); + while ($row = Database::fetch_array($res)) { + $categories[] = $row[$field]; + } + } + + return $categories; + } + + /** + * @param FormValidator $form + * @param string $action + */ + public function getForm(&$form, $action = 'new') + { + switch ($action) { + case 'new': + $header = get_lang('AddACategory'); + $submit = get_lang('AddTestCategory'); + break; + case 'edit': + $header = get_lang('EditCategory'); + $submit = get_lang('ModifyCategory'); + break; + } + + // Setting the form elements + $form->addElement('header', $header); + $form->addElement('hidden', 'category_id'); + $form->addElement( + 'text', + 'category_name', + get_lang('CategoryName'), + ['class' => 'span6'] + ); + $form->add_html_editor( + 'category_description', + get_lang('CategoryDescription'), + false, + false, + [ + 'ToolbarSet' => 'test_category', + 'Width' => '90%', + 'Height' => '200', + ] + ); + $categoryParentList = []; + + $options = [ + '1' => get_lang('Visible'), + '0' => get_lang('Hidden'), + ]; + $form->addElement( + 'select', + 'visibility', + get_lang('Visibility'), + $options + ); + $script = null; + if (!empty($this->parent_id)) { + $parentCat = new PTestCategory(); + $parentCat = $parentCat->getCategory($this->parent_id); + $categoryParentList = [$parentCat->id => $parentCat->name]; + $script .= ''; + } + $form->addElement('html', $script); + + $form->addElement('select', 'parent_id', get_lang('Parent'), $categoryParentList, ['id' => 'parent_id']); + $form->addElement('style_submit_button', 'SubmitNote', $submit, 'class="add"'); + + // setting the defaults + $defaults = []; + $defaults["category_id"] = $this->id; + $defaults["category_name"] = $this->name; + $defaults["category_description"] = $this->description; + $defaults["parent_id"] = $this->parent_id; + $defaults["visibility"] = $this->visibility; + $form->setDefaults($defaults); + + // setting the rules + $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required'); + } + + /** + * Return true if a category already exists with the same name. + * + * @param string $name + * @param int $courseId + * + * @return bool + */ + public static function categoryTitleExists($name, $courseId = 0) + { + $categories = self::getCategoryListInfo('title', $courseId); + foreach ($categories as $title) { + if ($title == $name) { + return true; + } + } + + return false; + } + + /** + * @param int $exerciseId + * @param int $courseId + * @param int $sessionId + * + * @return array + */ + public function getCategories($exerciseId, $courseId, $sessionId = 0) + { + $table = Database::get_course_table(TABLE_QUIZ_CATEGORY_PTEST); + $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY); + $sessionId = (int) $sessionId; + $courseId = (int) $courseId; + $exerciseId = (int) $exerciseId; + + $sessionCondition = api_get_session_condition( + $sessionId + ); + + if (empty($courseId)) { + return []; + } + + $sql = "SELECT * FROM $table + WHERE + exercise_id = $exerciseId AND + c_id = $courseId + $sessionCondition + ORDER BY position ASC"; + $result = Database::query($sql); + + return Database::store_result($result, 'ASSOC'); + } + + /** + * @param int $courseId + * @param int $sessionId + * + * @return string + */ + public function displayCategories($exerciseId, $courseId, $sessionId = 0) + { + $exerciseId = (int) $exerciseId; + $sessionId = (int) $sessionId; + $categories = $this->getCategories($exerciseId, $courseId, $sessionId); + $html = ''; + foreach ($categories as $category) { + $tmpobj = new PTestCategory(); + $tmpobj = $tmpobj->getCategory($category['id']); + $rowname = self::protectJSDialogQuote($category['title']); + $content = ''; + $content .= '
'; + $content .= '
'.get_lang('Choice').''.get_lang('Answer').'
'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= '
'.get_lang('PtestCategoryPosition').''.$category['position'].'
'; + $content .= get_lang('PtestCategoryColor'); + $content .= ''; + $content .= Display::tag( + 'span', + null, + [ + 'class' => 'form-control', + 'style' => 'background:'.$category['color'].'; + width:100px; + vertical-align:middle; + display:inline-block; + margin-right:20px;', + ] + ); + $content .= $category['color']; + $content .= '
'.get_lang('Description').''.$category['description'].'
'; + $content .= '
'; + $links = ''; + + $links .= ''. + Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).''; + $links .= ' '; + $links .= Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).''; + + $html .= Display::panel($content, $category['title'].$links); + } + + return $html; + } + + /** + * To allowed " in javascript dialog box without bad surprises + * replace " with two '. + * + * @param string $text + * + * @return mixed + */ + public function protectJSDialogQuote($text) + { + $res = $text; + $res = str_replace("'", "\'", $res); + // super astuce pour afficher les " dans les boite de dialogue + $res = str_replace('"', "\'\'", $res); + + return $res; + } +} diff --git a/main/exercise/ptest_category_ranking.class.php b/main/exercise/ptest_category_ranking.class.php new file mode 100644 index 00000000000..e08c3dd22d8 --- /dev/null +++ b/main/exercise/ptest_category_ranking.class.php @@ -0,0 +1,298 @@ +type = QUESTION_PT_TYPE_CATEGORY_RANKING; + $this->isContent = $this->getIsContent(); + } + + /** + * {@inheritdoc} + */ + public function createAnswersForm($form) + { + // Getting the exercise list + /** @var Exercise $objEx */ + $objEx = Session::read('objExercise'); + + $editorConfig = [ + 'ToolbarSet' => 'TestProposedAnswer', + 'Width' => '100%', + 'Height' => '125', + ]; + + // Categories options select + $category = new PTestCategory(); + $categoriesList = $category->getCategoryListInfo($objEx->selectId()); + $categoriesOptions = [null => get_lang('None')]; + foreach ($categoriesList as $categoryItem) { + $categoriesOptions[$categoryItem->id] = (string) $categoryItem->name; + } + + //this line defines how many questions by default appear when creating a choice question + // The previous default value was 2. See task #1759. + $nbAnswers = isset($_POST['nb_answers']) ? (int) $_POST['nb_answers'] : count($categoriesList); + $nbAnswers += (isset($_POST['lessAnswers']) ? -1 : (isset($_POST['moreAnswers']) ? 1 : 0)); + + $html = ' + + + + + + + + '; + + $form->addHeader(get_lang('Answers')); + $form->addHtml($html); + + $defaults = []; + if (!empty($this->id)) { + $answer = new Answer($this->id); + $answer->read(); + if ($answer->nbrAnswers > 0 && !$form->isSubmitted()) { + $nbAnswers = $answer->nbrAnswers; + } + } + $form->addElement('hidden', 'nb_answers'); + + //$temp_scenario = []; + if ($nbAnswers < 1) { + $nbAnswers = 1; + echo Display::return_message( + get_lang('YouHaveToCreateAtLeastOneAnswer') + ); + } + + for ($i = 1; $i <= $nbAnswers; $i++) { + $form->addHtml(''); + if (isset($answer) && is_object($answer)) { + $defaults['answer['.$i.']'] = isset($answer->answer[$i]) ? $answer->answer[$i] : ''; + $defaults['ptest_category['.$i.']'] = 0; + if (isset($answer->ptest_category[$i])) { + $defaults['ptest_category['.$i.']'] = $answer->ptest_category[$i]; + } + } + + $renderer = $form->defaultRenderer(); + + $renderer->setElementTemplate( + '', + 'counter['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'answer['.$i.']' + ); + $renderer->setElementTemplate( + '', + 'ptest_category['.$i.']' + ); + + $answerNumber = $form->addElement( + 'text', + 'counter['.$i.']', + null, + ' value = "'.$i.'"' + ); + $answerNumber->freeze(); + + $form->addHtmlEditor('answer['.$i.']', null, null, false, $editorConfig); + + $form->addRule( + 'answer['.$i.']', + get_lang('ThisFieldIsRequired'), + 'required' + ); + + $form->addSelect( + 'ptest_category['.$i.']', + null, + $categoriesOptions + ); + + $form->addHtml(''); + } + + $form->addHtml(''); + $form->addHtml('
'.get_lang('Number').''.get_lang('Answer').''.get_lang('PtestCategory').'
{error}
{element}
{error}
{element}
{error}
{element}
'); + + global $text; + $buttonGroup = []; + + if ($objEx->edit_exercise_in_lp == true || + (empty($this->exerciseList) && empty($objEx->id)) + ) { + //setting the save button here and not in the question class.php + $buttonGroup[] = $form->addButtonDelete(get_lang('LessAnswer'), 'lessAnswers', true); + $buttonGroup[] = $form->addButtonCreate(get_lang('PlusAnswer'), 'moreAnswers', true); + $buttonGroup[] = $form->addButton( + 'submitQuestion', + $text, + 'check', + 'primary', + 'default', + null, + ['id' => 'submit-question'], + true + ); + $form->addGroup($buttonGroup); + } + + if (!empty($this->id)) { + $form->setDefaults($defaults); + } else { + if ($this->isContent == 1) { + // Default sample content. + $form->setDefaults($defaults); + } + } + $form->setConstants(['nb_answers' => $nbAnswers]); + } + + /** + * {@inheritdoc} + */ + public function processAnswersCreation($form, $exercise) + { + $objAnswer = new Answer($this->id); + $nbAnswers = $form->getSubmitValue('nb_answers'); + + for ($i = 1; $i <= $nbAnswers; $i++) { + $answer = trim($form->getSubmitValue('answer['.$i.']')); + $goodAnswer = false; + $comment = ''; + $weighting = 0; + $ptestCategory = (int) $form->getSubmitValue('ptest_category['.$i.']'); + $dest = ''; + + $objAnswer->createAnswer( + $answer, + $goodAnswer, + $comment, + $weighting, + $i, + null, + null, + $dest, + $ptestCategory + ); + } + + // saves the answers into the data base + $objAnswer->save(); + + $this->save($exercise); + } + + /** + * {@inheritdoc} + */ + public function return_header(Exercise $exercise, $counter = null, $score = []) + { + $header = parent::return_header($exercise, $counter); //, $score); + $header .= ''; + + $header .= ''; + $header .= ''; + $header .= ''; + + return $header; + } + + /** + * Saves one answer to the database. + * + * @param int $id The ID of the answer (has to be calculated for this course) + * @param int $questionId The question ID (to which the answer is attached) + * @param string $title The text of the answer + * @param string $comment The feedback for the answer + * @param float $score The score you get when picking this answer + * @param int $correct Whether this answer is considered *the* correct one (this is the unique answer type) + */ + public function addAnswer( + $id, + $questionId, + $title, + $comment, + $score = 0.0, + $correct = 0 + ) { + $em = Database::getManager(); + $tblQuizAnswer = Database::get_course_table(TABLE_QUIZ_ANSWER); + $tblQuizQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); + $courseId = api_get_course_int_id(); + $questionId = intval($questionId); + $score = floatval($score); + $correct = intval($correct); + $title = Database::escape_string($title); + $comment = Database::escape_string($comment); + // Get the max position. + $sql = "SELECT max(position) as max_position + FROM $tblQuizAnswer + WHERE + c_id = $courseId AND + question_id = $questionId"; + $rsMax = Database::query($sql); + $rowMax = Database::fetch_object($rsMax); + $position = $rowMax->max_position + 1; + + // Insert a new answer + $quizAnswer = new CQuizAnswer(); + $quizAnswer + ->setCId($courseId) + ->setId($id) + ->setQuestionId($questionId) + ->setAnswer($title) + ->setCorrect($correct) + ->setComment($comment) + ->setPonderation($score) + ->setPosition($position) + ->setDestination('0@@0@@0@@0'); + + $em->persist($quizAnswer); + $em->flush(); + + $id = $quizAnswer->getIid(); + + if ($id) { + $quizAnswer + ->setId($id); + + $em->merge($quizAnswer); + $em->flush(); + } + + if ($correct) { + $sql = "UPDATE $tblQuizQuestion + SET ponderation = (ponderation + $score) + WHERE c_id = $courseId AND id = ".$questionId; + Database::query($sql); + } + } +} diff --git a/main/exercise/ptest_exercise_report.php b/main/exercise/ptest_exercise_report.php new file mode 100644 index 00000000000..c69831e5e83 --- /dev/null +++ b/main/exercise/ptest_exercise_report.php @@ -0,0 +1,530 @@ +read($exerciseId); + +$actions = null; +if ($isAllowedToEdit && $origin != 'learnpath') { + // the form + if (api_is_platform_admin() || api_is_course_admin() || + api_is_course_tutor() || api_is_session_general_coach() + ) { + $actions .= ''. + Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).''; + $actions .= ''. + Display::return_icon('statistics.png', get_lang('ReportByQuestion'), '', ICON_SIZE_MEDIUM).''; + $actions .= ''. + Display::return_icon('survey_reporting_question.png', get_lang('ExerciseGraph'), '', ICON_SIZE_MEDIUM); + $actions .= ''; + // clean result before a selected date icon + $actions .= Display::url( + Display::return_icon( + 'clean_before_date.png', + get_lang('CleanStudentsResultsBeforeDate'), + '', + ICON_SIZE_MEDIUM + ), + '#', + ['onclick' => 'javascript:display_date_picker()'] + ); + // clean result before a selected date datepicker popup + $actions .= Display::span( + Display::input( + 'input', + 'datepicker_start', + get_lang('SelectADateOnTheCalendar'), + [ + 'onmouseover' => 'datepicker_input_mouseover()', + 'id' => 'datepicker_start', + 'onchange' => 'datepicker_input_changed()', + 'readonly' => 'readonly', + ] + ). + Display::button( + 'delete', + get_lang('Delete'), + ['onclick' => 'submit_datepicker()'] + ), + ['style' => 'display:none', 'id' => 'datepicker_span'] + ); + } +} else { + $actions .= ''. + Display::return_icon( + 'back.png', + get_lang('GoBackToQuestionList'), + '', + ICON_SIZE_MEDIUM + ). + ''; +} + +// Deleting an attempt +if (($isAllowedToEdit || $isTutor || api_is_coach()) && + isset($_GET['delete']) && $_GET['delete'] === 'delete' && + !empty($_GET['did']) && $locked == false +) { + $exeId = (int) $_GET['did']; + if (!empty($exeId)) { + $sql = 'DELETE FROM '.$TBL_TRACK_EXERCISES.' WHERE exe_id = '.$exeId; + Database::query($sql); + $sql = 'DELETE FROM '.$TBL_TRACK_ATTEMPT.' WHERE exe_id = '.$exeId; + Database::query($sql); + + Event::addEvent( + LOG_EXERCISE_ATTEMPT_DELETE, + LOG_EXERCISE_ATTEMPT, + $exeId, + api_get_utc_datetime() + ); + header('Location: ptest_exercise_report.php?'.api_get_cidreq().'&exerciseId='.$exerciseId); + exit; + } +} + +if ($isAllowedToEdit || $isTutor) { + $interbreadcrumb[] = [ + 'url' => 'exercise.php?'.api_get_cidreq(), + 'name' => get_lang('Exercises'), + ]; + + $nameTools = get_lang('Stats'); + if ($exerciseExists) { + $interbreadcrumb[] = [ + 'url' => '#', + 'name' => $objExerciseTmp->selectTitle(true), + ]; + } +} else { + $interbreadcrumb[] = [ + 'url' => 'exercise.php?'.api_get_cidreq(), + 'name' => get_lang('Exercises'), + ]; + if ($exerciseExists) { + $nameTools = get_lang('Results').': '.$objExerciseTmp->selectTitle(true); + } +} + +if (($isAllowedToEdit || $isTutor || api_is_coach()) && + isset($_GET['a']) && $_GET['a'] === 'close' && + !empty($_GET['id']) && $locked == false +) { + // Close the user attempt otherwise left pending + $exeId = (int) $_GET['id']; + $sql = "UPDATE $TBL_TRACK_EXERCISES SET status = '' + WHERE exe_id = $exeId AND status = 'incomplete'"; + Database::query($sql); +} + +Display::display_header($nameTools); + +// Clean all results for this test before the selected date +if (($isAllowedToEdit || $isTutor || api_is_coach()) && + isset($_GET['delete_before_date']) && $locked == false +) { + // ask for the date + $check = Security::check_token('get'); + if ($check) { + $objExerciseTmp = new Exercise(); + if ($objExerciseTmp->read($exerciseId)) { + $count = $objExerciseTmp->cleanResults( + true, + $_GET['delete_before_date'].' 23:59:59' + ); + echo Display::return_message( + sprintf(get_lang('XResultsCleaned'), $count), + 'confirm' + ); + } + } +} + +// Security token to protect deletion +$token = Security::get_token(); +$actions = Display::div($actions, ['class' => 'actions']); + +echo $actions; +$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?'; +$url .= 'a=get_ptest_exercise_results&exerciseId='.$exerciseId.'&filter_by_user='.$filterUser.'&'.api_get_cidreq(); +$actionLinks = ''; +// Generating group list +$group_list = GroupManager::get_group_list(); +$groupParameters = [ + 'group_all:'.get_lang('All'), + 'group_none:'.get_lang('None'), +]; + +foreach ($group_list as $group) { + $groupParameters[] = $group['id'].':'.$group['name']; +} +if (!empty($groupParameters)) { + $groupParameters = implode(';', $groupParameters); +} + +$officialCodeInList = api_get_setting('show_official_code_exercise_result_list'); + +if ($isAllowedToEdit || $isTutor) { + // The order is important you need to check the the $column variable in the model.ajax.php file + $columns = [ + get_lang('FirstName'), + get_lang('LastName'), + get_lang('LoginName'), + get_lang('Group'), + get_lang('Duration').' ('.get_lang('MinMinute').')', + get_lang('StartDate'), + get_lang('EndDate'), + get_lang('IP'), + get_lang('ToolLearnpath'), + get_lang('Actions'), + ]; + + if ($officialCodeInList === 'true') { + $columns = array_merge([get_lang('OfficialCode')], $columns); + } + + // Column config + $columnModel = [ + ['name' => 'firstname', 'index' => 'firstname', 'width' => '50', 'align' => 'left', 'search' => 'true'], + [ + 'name' => 'lastname', + 'index' => 'lastname', + 'width' => '50', + 'align' => 'left', + 'formatter' => 'action_formatter', + 'search' => 'true' + ], + [ + 'name' => 'login', + 'index' => 'username', + 'width' => '40', + 'align' => 'left', + 'search' => 'true', + 'hidden' => api_get_configuration_value('exercise_attempts_report_show_username') ? 'false' : 'true', + ], + [ + 'name' => 'group_name', + 'index' => 'group_id', + 'width' => '40', + 'align' => 'left', + 'search' => 'true', + 'stype' => 'select', + //for the bottom bar + 'searchoptions' => [ + 'defaultValue' => 'group_all', + 'value' => $groupParameters, + ], + //for the top bar + 'editoptions' => ['value' => $groupParameters], + ], + ['name' => 'duration', 'index' => 'exe_duration', 'width' => '30', 'align' => 'left', 'search' => 'true'], + ['name' => 'start_date', 'index' => 'start_date', 'width' => '60', 'align' => 'left', 'search' => 'true'], + ['name' => 'exe_date', 'index' => 'exe_date', 'width' => '60', 'align' => 'left', 'search' => 'true'], + ['name' => 'ip', 'index' => 'user_ip', 'width' => '40', 'align' => 'center', 'search' => 'true'], + ['name' => 'lp', 'index' => 'orig_lp_id', 'width' => '60', 'align' => 'left', 'search' => 'false'], + [ + 'name' => 'actions', + 'index' => 'actions', + 'width' => '60', + 'align' => 'left', + 'search' => 'false', + 'sortable' => 'false' + ], + ]; + + if ($officialCodeInList === 'true') { + $officialCodeRow = [ + 'name' => 'official_code', + 'index' => 'official_code', + 'width' => '50', + 'align' => 'left', + 'search' => 'true' + ]; + $columnModel = array_merge([$officialCodeRow], $columnModel); + } + + $actionLinks = ' + // add username as title in lastname filed - ref 4226 + function action_formatter(cellvalue, options, rowObject) { + // rowObject is firstname,lastname,login,... get the third word + var loginx = "'.api_htmlentities(sprintf(get_lang('LoginX'), ':::'), ENT_QUOTES).'"; + var tabLoginx = loginx.split(/:::/); + // tabLoginx[0] is before and tabLoginx[1] is after ::: + // may be empty string but is defined + return ""+cellvalue+""; + }'; +} + +$extraParams['autowidth'] = 'true'; +$extraParams['height'] = 'auto'; +$extraParams['gridComplete'] = " + defaultGroupId = Cookies.get('default_group_".$exerciseId."'); + if (typeof defaultGroupId !== 'undefined') { + $('#gs_group_name').val(defaultGroupId); + } +"; + +$extraParams['beforeRequest'] = " +var defaultGroupId = $('#gs_group_name').val(); + +// Load from group menu +if (typeof defaultGroupId !== 'undefined') { + Cookies.set('default_group_".$exerciseId."', defaultGroupId); +} else { + // get from cookies + defaultGroupId = Cookies.get('default_group_".$exerciseId."'); + $('#gs_group_name').val(defaultGroupId); +} + +if (typeof defaultGroupId !== 'undefined') { + var posted_data = $(\"#results\").jqGrid('getGridParam', 'postData'); + var extraFilter = ',{\"field\":\"group_id\",\"op\":\"eq\",\"data\":\"'+ defaultGroupId +'\"}]}'; + var filters = posted_data.filters; + var stringObj = new String(filters); + stringObj.replace(']}', extraFilter); + + posted_data['group_id_in_toolbar'] = defaultGroupId; + $(this).jqGrid('setGridParam', 'postData', posted_data); +} +"; + +$gridJs = Display::grid_js( + 'results', + $url, + $columns, + $columnModel, + $extraParams, + [], + $actionLinks, + true +); + +?> + + + + + + + +read($exerciseId); +} + +// Only users can see their own results +if (!$isAllowedToEdit) { + if ($studentId != $currentUserId) { + api_not_allowed($printHeaders); + } +} + +$js = ''; +$htmlHeadXtra[] = $js; + +if (api_is_in_gradebook()) { + $interbreadcrumb[] = [ + 'url' => Category::getUrl(), + 'name' => get_lang('ToolGradebook'), + ]; +} + +$interbreadcrumb[] = [ + 'url' => 'exercise.php?'.api_get_cidreq(), + 'name' => get_lang('Exercises'), +]; +$interbreadcrumb[] = [ + 'url' => 'ptest_exercise_report.php?exerciseId='.$exerciseId.'&'.api_get_cidreq(), + 'name' => $objExercise->selectTitle(true), +]; +$interbreadcrumb[] = ['url' => '#', 'name' => get_lang('Result')]; + +$this_section = SECTION_COURSES; + +$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js'); + +if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + Display::display_header(''); + + $message = Session::read('attempt_remaining'); + Session::erase('attempt_remaining'); + + ExerciseLib::displayQuestionListByAttempt( + $objExercise, + $id, + false, + $message + ); + Display::display_footer(); + exit; +} diff --git a/main/exercise/ptest_stats.php b/main/exercise/ptest_stats.php new file mode 100644 index 00000000000..6e72ad4f729 --- /dev/null +++ b/main/exercise/ptest_stats.php @@ -0,0 +1,255 @@ +read($exerciseId); + +if (!$result) { + api_not_allowed(true); +} + +$sessionId = api_get_session_id(); +$courseCode = api_get_course_id(); + +if (empty($sessionId)) { + $students = CourseManager:: get_student_list_from_course_code( + $courseCode, + false + ); +} else { + $students = CourseManager:: get_student_list_from_course_code( + $courseCode, + true, + $sessionId + ); +} +$countStudents = count($students); +$questionList = $objExercise->get_validated_question_list(); +$content = ''; + +if (!empty($questionList)) { + $id = 0; + $counterLabel = 0; + foreach ($questionList as $questionId) { + $counterLabel++; + $data = []; + $questionObj = Question::read($questionId, null, null, true); + $exerciseStats = ExerciseLib::get_student_stats_by_question( + $questionId, + $exerciseId, + $courseCode, + $sessionId + ); + $content .= Display::page_subheader2($counterLabel.'. '.$questionObj->question); + $content .= '

'.get_lang('QuestionType').': '.$questionObj->get_question_type_name(true).'

'; + + $answer = new Answer($questionId); + $answerCount = $answer->selectNbrAnswers(); + + for ($answerId = 1; $answerId <= $answerCount; $answerId++) { + $answerInfo = $answer->selectAnswer($answerId); + $realAnswerId = $answer->selectAutoId($answerId); + + // Overwriting values depending of the question + switch ($questionObj->type) { + case QUESTION_PT_TYPE_CATEGORY_RANKING: + $headers = [ + get_lang('Answer'), + get_lang('NumberStudentWhoSelectedIt'), + ]; + + $data[$id]['answer'] = $answerInfo; + $count = ExerciseLib::get_number_students_answer_count( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $percentage = 0; + if (!empty($countStudents)) { + $percentage = $count / $countStudents * 100; + } + $data[$id]['attempts'] = Display::bar_progress( + $percentage, + false, + $count.' / '.$countStudents + ); + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $headers = [ + get_lang('Answer'), + get_lang('MostAgree'), + get_lang('LeastAgree'), + ]; + $data[$id]['answer'] = $realAnswerId.' - '.$answerInfo; + $count = ExerciseLib::get_number_students_answer_count( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $percentageAgree = 0; + $percentageDisagree = 0; + + if (!empty($countStudents)) { + $percentageAgree = $count[0] / $countStudents * 100; + $percentageDisagree = $count[1] / $countStudents * 100; + } + $data[$id]['agree'] = Display::bar_progress( + $percentageAgree, + false, + $count[0].' / '.$countStudents + ); + $data[$id]['disagree'] = Display::bar_progress( + $percentageDisagree, + false, + $count[1].' / '.$countStudents + ); + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + $headers = [ + get_lang('Answer'), + get_lang('AverageScore'), + ]; + + $data[$id]['answer'] = $answerInfo; + $count = ExerciseLib::get_number_students_answer_count( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $percentage = 0; + if (!empty($countStudents)) { + $percentage = $count / 5 * 100; + } + $data[$id]['attempts'] = Display::bar_progress( + $percentage, + false, + $count.' / 5' + ); + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $headers = [ + get_lang('Answer'), + get_lang('AverageScore'), + ]; + + $data[$id]['answer'] = $answerInfo; + $count = ExerciseLib::get_number_students_answer_count( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $percentage = 0; + if (!empty($countStudents)) { + $percentage = $count / 5 * 100; + } + $data[$id]['attempts'] = Display::bar_progress( + $percentage, + false, + $count.' / 5' + ); + break; + default: + if ($answerId == 1) { + $data[$id]['name'] = cut($questionObj->question, 100); + } else { + $data[$id]['name'] = '-'; + } + $data[$id]['answer'] = $answerInfo; + $data[$id]['correct'] = $correct_answer; + + $count = ExerciseLib::get_number_students_answer_count( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId + ); + $percentage = 0; + if (!empty($countStudents)) { + $percentage = $count / $countStudents * 100; + } + $data[$id]['attempts'] = Display::bar_progress( + $percentage, + false, + $count.' / '.$countStudents + ); + } + $id++; + } + + // Format A table + $table = new HTML_Table(['class' => 'data_table']); + $row = 0; + $column = 0; + foreach ($headers as $header) { + $table->setHeaderContents($row, $column, $header); + $column++; + } + $row++; + foreach ($data as $rowTable) { + $column = 0; + foreach ($rowTable as $cell) { + $table->setCellContents($row, $column, $cell); + $table->updateCellAttributes($row, $column, 'align="center"'); + $column++; + } + $table->updateRowAttributes($row, $row % 2 ? 'class="row_even"' : 'class="row_odd"', true); + $row++; + } + $content .= $table->toHtml(); + } +} + +$interbreadcrumb[] = [ + "url" => "exercise.php?".api_get_cidreq(), + "name" => get_lang('Exercises'), +]; +$interbreadcrumb[] = [ + "url" => "admin.php?exerciseId=$exerciseId&".api_get_cidreq(), + "name" => $objExercise->selectTitle(true), +]; + +$tpl = new Template(get_lang('ReportByQuestion')); +$actions = ''. + Display:: return_icon( + 'back.png', + get_lang('GoBackToQuestionList'), + '', + ICON_SIZE_MEDIUM + ) + .''; +$actions = Display::div($actions, ['class' => 'actions']); +$content = $actions.$content; +$tpl->assign('content', $content); +$tpl->display_one_col_template(); diff --git a/main/exercise/ptest_stats_graph.php b/main/exercise/ptest_stats_graph.php new file mode 100644 index 00000000000..f667491e0d0 --- /dev/null +++ b/main/exercise/ptest_stats_graph.php @@ -0,0 +1,223 @@ +read($exerciseId); + +if (!$result) { + api_not_allowed(true); +} + +$sessionId = api_get_session_id(); +$courseCode = api_get_course_id(); + +if (empty($sessionId)) { + $students = CourseManager:: get_student_list_from_course_code( + $courseCode, + false + ); +} else { + $students = CourseManager:: get_student_list_from_course_code( + $courseCode, + true, + $sessionId + ); +} +$countStudents = count($students); +$questionList = $objExercise->get_validated_question_list(); + +$cList = PTestCategory::getCategoryListInfo($objExercise->id); + +$categoryList = []; +foreach ($cList as $item) { + $categoryList[$item->id]['label'] = $item->name; + $categoryList[$item->id]['num'] = 0; +} + +if (!empty($questionList)) { + foreach ($questionList as $questionId) { + $questionObj = Question::read($questionId, null, null, true); + + $answer = new Answer($questionId); + $answerCount = $answer->selectNbrAnswers(); + + for ($answerId = 1; $answerId <= $answerCount; $answerId++) { + $realAnswerId = $answer->selectAutoId($answerId); + $categoryId = $answer->selectPtCategory($answerId); + + // Overwriting values depending of the question + switch ($questionObj->type) { + case QUESTION_PT_TYPE_CATEGORY_RANKING: + $count = ExerciseLib::getNumberStudentsAnswerCountGraph( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $categoryList[$categoryId]['num'] += $count; + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $count = ExerciseLib::getNumberStudentsAnswerCountGraph( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $categoryList[$categoryId]['num'] += $count[0]; + $categoryList[$categoryId]['num'] -= $count[1]; + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + $count = ExerciseLib::getNumberStudentsAnswerCountGraph( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $categoryList[$categoryId]['num'] += $count; + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $count = ExerciseLib::getNumberStudentsAnswerCountGraph( + $realAnswerId, + $questionId, + $exerciseId, + $courseCode, + $sessionId, + $questionObj->type + ); + $categoryList[$categoryId]['num'] += $count; + break; + } + } + } +} + +$labels = []; +$num = []; +foreach ($categoryList as $item) { + $labels[] = $item['label']; + $data[] = (int) $item['num']; +} + +$html = ''; +$html .= '
'; +$html .= '
'; +$html .= ''; +$html .= '
'; +$html .= '
'; +$html .= ''; +$html .= '
'; +$html .= '

'; + +$html .= ''; + +$interbreadcrumb[] = [ + "url" => "exercise.php?".api_get_cidreq(), + "name" => get_lang('Exercises'), +]; +$interbreadcrumb[] = [ + "url" => "admin.php?exerciseId=$exerciseId&".api_get_cidreq(), + "name" => $objExercise->selectTitle(true), +]; + +$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js'); + +$tpl = new Template(get_lang('ReportByQuestion')); +$actions = ''. + Display:: return_icon( + 'back.png', + get_lang('GoBackToQuestionList'), + '', + ICON_SIZE_MEDIUM + ) + .''; +$actions = Display::div($actions, ['class' => 'actions']); +$content = $actions.$html; +$tpl->assign('content', $content); +$tpl->display_one_col_template(); diff --git a/main/exercise/ptests_category.php b/main/exercise/ptests_category.php new file mode 100644 index 00000000000..1a44bf1603f --- /dev/null +++ b/main/exercise/ptests_category.php @@ -0,0 +1,271 @@ + + function confirmDelete(in_txt, in_id) { + var oldbgcolor = document.getElementById(in_id).style.backgroundColor; + document.getElementById(in_id).style.backgroundColor="#AAFFB0"; + if (confirm(in_txt)) { + return true; + } else { + document.getElementById(in_id).style.backgroundColor = oldbgcolor; + return false; + } + } +'; + +$nameTools = ''; + +require_once __DIR__.'/../inc/global.inc.php'; + +$this_section = SECTION_COURSES; + +api_protect_course_script(true); + +if (!api_is_allowed_to_edit()) { + api_not_allowed(true); +} + +$category = new PTestCategory(); +$courseId = api_get_course_int_id(); +$sessionId = api_get_session_id(); +$exerciseId = (int) $_GET['exerciseId']; + +// get from session +$objExercise = Session::read('objExercise'); + +// Exercise object creation. +if (!is_object($objExercise) || $objExercise->selectPtType() != EXERCISE_PT_TYPE_PTEST) { + // construction of the Exercise object + $objExercise = new Exercise(); + + // creation of a new exercise if wrong or not specified exercise ID + if ($exerciseId) { + $parseQuestionList = $showPagination > 0 ? false : true; + if ($editQuestion) { + $parseQuestionList = false; + $showPagination = true; + } + $objExercise->read($exerciseId, $parseQuestionList); + } + // saves the object into the session + Session::write('objExercise', $objExercise); +} + +// Exercise can be edited in their course. +if ($objExercise->sessionId != $sessionId) { + api_not_allowed(true); +} + +// breadcrumbs +$interbreadcrumb[] = [ + "url" => "ptest_admin.php?exercise_id=".$objExercise->selectId()."&".api_get_cidreq(), + "name" => get_lang('Exercises'), +]; + +$action = isset($_GET['action']) ? $_GET['action'] : ''; +$content = ''; + +switch ($action) { + case 'addcategory': + $content = add_category_form('addcategory'); + break; + case 'editcategory': + $content = edit_category_form('editcategory'); + break; + case 'deletecategory': + delete_category_form(); + break; +} + +Display::display_header(get_lang('Category')); +displayActionBar(); +echo $content; +echo $category->displayCategories($exerciseId, $courseId, $sessionId); +Display::display_footer(); + +/** + * Form to edit a category. + * + * @todo move to PTestCategory.class.php + * + * @param string $action + */ +function edit_category_form($action) +{ + $exerciseId = (int) $_GET['exerciseId']; + $action = Security::remove_XSS($action); + if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) { + $categoryId = intval($_GET['category_id']); + $objcat = new PTestCategory(); + $objcat = $objcat->getCategory($categoryId); + + $params = [ + 'exerciseId' => $exerciseId, + 'action' => $action, + 'category_id' => $categoryId, + ]; + $form = new FormValidator( + 'note', + 'post', + api_get_self().'?'.http_build_query($params).'&'.api_get_cidreq() + ); + + // Setting the form elements + $form->addElement('header', get_lang('EditCategory')); + $form->addElement('hidden', 'category_id'); + $form->addElement('text', 'category_name', get_lang('PtestCategoryName'), ['size' => '95']); + $form->addElement('color', 'category_color', get_lang('PtestCategoryColor'), ['size' => '95']); + $form->addElement('number', 'category_position', get_lang('PtestCategoryPosition'), ['size' => '95']); + $form->addHtmlEditor( + 'category_description', + get_lang('PtestCategoryDescription'), + false, + false, + ['ToolbarSet' => 'TestQuestionDescription', 'Height' => '200'] + ); + $form->addButtonCreate(get_lang('ModifyPTestFeature'), 'SubmitNote'); + + // setting the rules + $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required'); + + // setting the defaults + $defaults = []; + $defaults['category_id'] = $objcat->id; + $defaults['category_name'] = $objcat->name; + $defaults['category_color'] = $objcat->color; + $defaults['category_position'] = $objcat->position; + $defaults['category_description'] = $objcat->description; + $form->setDefaults($defaults); + + // The validation or display + if ($form->validate()) { + $check = Security::check_token('post'); + if ($check) { + $values = $form->exportValues(); + $category = new PTestCategory(); + $category = $category->getCategory($values['category_id']); + + if ($category) { + $category->name = $values['category_name']; + $category->description = $values['category_description']; + $category->color = $values['category_color']; + $category->position = $values['category_position']; + $category->modifyCategory(); + Display::addFlash(Display::return_message(get_lang('Updated'))); + } else { + Display::addFlash(Display::return_message(get_lang('ModifyCategoryError'), 'error')); + } + } + Security::clear_token(); + } else { + $token = Security::get_token(); + $form->addElement('hidden', 'sec_token'); + $form->setConstants(['sec_token' => $token]); + + return $form->returnForm(); + } + } else { + Display::addFlash( + Display::return_message(get_lang('CannotEditCategory'), 'error') + ); + } +} + +// process to delete a category +function delete_category_form() +{ + if (isset($_GET['category_id']) && is_numeric($_GET['category_id'])) { + $category = new PTestCategory(); + if ($category->removeCategory($_GET['category_id'])) { + Display::addFlash(Display::return_message(get_lang('DeleteCategoryDone'))); + } else { + Display::addFlash(Display::return_message(get_lang('CannotDeleteCategoryError'), 'error')); + } + } else { + Display::addFlash(Display::return_message(get_lang('CannotDeleteCategoryError'), 'error')); + } +} + +/** + * form to add a category. + * + * @todo move to PTestCategory.class.php + * + * @param string $action + */ +function add_category_form($action) +{ + $exerciseId = (int) $_GET['exerciseId']; + $action = Security::remove_XSS($action); + // initiate the object + $form = new FormValidator( + 'note', + 'post', + api_get_self().'?'.http_build_query([ + 'exerciseId' => $exerciseId, + 'action' => $action, + ]).'&'.api_get_cidreq() + ); + // Setting the form elements + $form->addElement('header', get_lang('AddACategory')); + $form->addElement('text', 'category_name', get_lang('PtestCategoryName'), ['size' => '95']); + $form->addElement('color', 'category_color', get_lang('PtestCategoryColor'), ['size' => '95']); + $form->addElement('number', 'category_position', get_lang('PtestCategoryPosition'), ['size' => '95']); + $form->addHtmlEditor( + 'category_description', + get_lang('PtestCategoryDescription'), + false, + false, + ['ToolbarSet' => 'TestQuestionDescription', 'Height' => '200'] + ); + $form->addButtonCreate(get_lang('AddPTestFeature'), 'SubmitNote'); + // 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(); + $category = new PTestCategory(); + $category->name = $values['category_name']; + $category->description = $values['category_description']; + $category->color = $values['category_color']; + $category->position = $values['category_position']; + if ($category->save($exerciseId)) { + Display::addFlash(Display::return_message(get_lang('AddCategoryDone'))); + } else { + Display::addFlash(Display::return_message(get_lang('AddCategoryNameAlreadyExists'), 'warning')); + } + } + Security::clear_token(); + } else { + $token = Security::get_token(); + $form->addElement('hidden', 'sec_token'); + $form->setConstants(['sec_token' => $token]); + + return $form->returnForm(); + } +} + +// Display add category button +function displayActionBar() +{ + $exerciseId = (int) $_GET['exerciseId']; + echo '
'; + $urlParams = 'exerciseId='.$exerciseId.'&'.api_get_cidreq(); + echo ''. + Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM). + ''; + + echo ''. + Display::return_icon('new_folder.png', get_lang('AddACategory'), null, ICON_SIZE_MEDIUM). + ''; + + echo '
'; + echo "
"; + echo "
".get_lang('PtestCategoryList')."
"; +} diff --git a/main/exercise/question.class.php b/main/exercise/question.class.php index 19c2e932f3a..e3a8cc5ea23 100755 --- a/main/exercise/question.class.php +++ b/main/exercise/question.class.php @@ -69,6 +69,12 @@ abstract class Question ANNOTATION => ['Annotation.php', 'Annotation'], READING_COMPREHENSION => ['ReadingComprehension.php', 'ReadingComprehension'], ]; + public static $questionPtTypes = [ + QUESTION_PT_TYPE_CATEGORY_RANKING => ['ptest_category_ranking.class.php', 'PtestCategoryRanking'], + QUESTION_PT_TYPE_AGREE_OR_DISAGREE => ['ptest_agree_disagree.class.php', 'PtestAgreeOrDisagree'], + QUESTION_PT_TYPE_AGREE_SCALE => ['ptest_agree_scale.class.php', 'PtestAgreeScale'], + QUESTION_PT_TYPE_AGREE_REORDER => ['ptest_agree_reorder.class.php', 'PtestAgreeReorder'], + ]; /** * constructor of the class. @@ -123,22 +129,22 @@ public function getIsContent() * Reads question information from the data base. * * @param int $id - question ID - * @param array $course_info + * @param array $courseInfo * @param bool $getExerciseList * * @return Question * * @author Olivier Brouckaert */ - public static function read($id, $course_info = [], $getExerciseList = true) + public static function read($id, $courseInfo = [], $getExerciseList = true, $ptest = false) { $id = (int) $id; - if (empty($course_info)) { - $course_info = api_get_course_info(); + if (empty($courseInfo)) { + $courseInfo = api_get_course_info(); } - $course_id = $course_info['real_id']; + $courseId = $courseInfo['real_id']; - if (empty($course_id) || $course_id == -1) { + if (empty($courseId) || $courseId == -1) { return false; } @@ -147,12 +153,12 @@ public static function read($id, $course_info = [], $getExerciseList = true) $sql = "SELECT * FROM $TBL_QUESTIONS - WHERE c_id = $course_id AND id = $id "; + WHERE c_id = $courseId AND id = $id "; $result = Database::query($sql); // if the question has been found if ($object = Database::fetch_object($result)) { - $objQuestion = self::getInstance($object->type); + $objQuestion = self::getInstance($object->type, $ptest); if (!empty($objQuestion)) { $objQuestion->id = (int) $id; $objQuestion->iid = (int) $object->iid; @@ -164,9 +170,9 @@ public static function read($id, $course_info = [], $getExerciseList = true) $objQuestion->picture = $object->picture; $objQuestion->level = (int) $object->level; $objQuestion->extra = $object->extra; - $objQuestion->course = $course_info; + $objQuestion->course = $courseInfo; $objQuestion->feedback = isset($object->feedback) ? $object->feedback : ''; - $objQuestion->category = TestCategory::getCategoryForQuestion($id, $course_id); + $objQuestion->category = TestCategory::getCategoryForQuestion($id, $courseId); $objQuestion->code = isset($object->code) ? $object->code : ''; if ($getExerciseList) { @@ -176,7 +182,7 @@ public static function read($id, $course_info = [], $getExerciseList = true) INNER JOIN $tblQuiz e ON e.c_id = q.c_id AND e.id = q.exercice_id WHERE - q.c_id = $course_id AND + q.c_id = $courseId AND q.question_id = $id AND e.active >= 0"; @@ -508,12 +514,12 @@ public function saveCategories($category_list) // update or add category for a question foreach ($category_list as $category_id) { $category_id = (int) $category_id; - $question_id = (int) $this->id; + $questionId = (int) $this->id; $sql = "SELECT count(*) AS nb FROM $table WHERE category_id = $category_id - AND question_id = $question_id + AND question_id = $questionId AND c_id=".api_get_course_int_id(); $res = Database::query($sql); $row = Database::fetch_array($res); @@ -521,7 +527,7 @@ public function saveCategories($category_list) // DO nothing } else { $sql = "INSERT INTO $table (c_id, question_id, category_id) - VALUES (".api_get_course_int_id().", $question_id, $category_id)"; + VALUES (".api_get_course_int_id().", $questionId, $category_id)"; Database::query($sql); } } @@ -553,10 +559,10 @@ public function saveCategory($categoryId, $courseId = 0) // update or add category for a question $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY); $categoryId = (int) $categoryId; - $question_id = (int) $this->id; + $questionId = (int) $this->id; $sql = "SELECT count(*) AS nb FROM $table WHERE - question_id = $question_id AND + question_id = $questionId AND c_id = ".$courseId; $res = Database::query($sql); $row = Database::fetch_array($res); @@ -564,12 +570,12 @@ public function saveCategory($categoryId, $courseId = 0) $sql = "UPDATE $table SET category_id = $categoryId WHERE - question_id = $question_id AND + question_id = $questionId AND c_id = ".$courseId; Database::query($sql); } else { $sql = "INSERT INTO $table (c_id, question_id, category_id) - VALUES (".$courseId.", $question_id, $categoryId)"; + VALUES (".$courseId.", $questionId, $categoryId)"; Database::query($sql); } @@ -638,10 +644,10 @@ public function updateLevel($level) public function updateType($type) { $table = Database::get_course_table(TABLE_QUIZ_ANSWER); - $course_id = $this->course['real_id']; + $courseId = $this->course['real_id']; - if (empty($course_id)) { - $course_id = api_get_course_int_id(); + if (empty($courseId)) { + $courseId = api_get_course_int_id(); } // if we really change the type if ($type != $this->type) { @@ -651,7 +657,7 @@ public function updateType($type) ) { // removes old answers $sql = "DELETE FROM $table - WHERE c_id = $course_id AND question_id = ".intval($this->id); + WHERE c_id = $courseId AND question_id = ".intval($this->id); Database::query($sql); } @@ -715,7 +721,7 @@ public function uploadPicture($picture) $pictureFilename = self::generatePictureName(); $img = new Image($picture); $img->send_image($picturePath.'/'.$pictureFilename, -1, 'jpg'); - $document_id = add_document( + $documentId = add_document( $this->course, '/images/'.$pictureFilename, 'file', @@ -723,8 +729,8 @@ public function uploadPicture($picture) $pictureFilename ); - if ($document_id) { - $this->picture = $document_id; + if ($documentId) { + $this->picture = $documentId; if (!file_exists($picturePath.'/'.$pictureFilename)) { return false; @@ -733,7 +739,7 @@ public function uploadPicture($picture) api_item_property_update( $this->course, TOOL_DOCUMENT, - $document_id, + $documentId, 'DocumentAdded', api_get_user_id() ); @@ -802,7 +808,7 @@ public function exportPicture($questionId, $courseInfo) return false; } - $course_id = $courseInfo['real_id']; + $courseId = $courseInfo['real_id']; $destination_path = $this->getHotSpotFolderInCourse($courseInfo); if (empty($destination_path)) { @@ -816,7 +822,7 @@ public function exportPicture($questionId, $courseInfo) return false; } - $sourcePictureName = $this->getPictureFilename($course_id); + $sourcePictureName = $this->getPictureFilename($courseId); $picture = $this->generatePictureName(); $result = false; if (file_exists($source_path.'/'.$sourcePictureName)) { @@ -846,7 +852,7 @@ public function exportPicture($questionId, $courseInfo) $table = Database::get_course_table(TABLE_QUIZ_QUESTION); $sql = "UPDATE $table SET picture = '".Database::escape_string($picture)."' - WHERE c_id = $course_id AND id='".intval($questionId)."'"; + WHERE c_id = $courseId AND id='".intval($questionId)."'"; Database::query($sql); $documentId = add_document( @@ -989,8 +995,8 @@ public function save($exercise) question.c_id = $c_id AND test_question.c_id = $c_id "; $result = Database::query($sql); - $current_position = Database::result($result, 0, 0); - $this->updatePosition($current_position + 1); + $currentPosition = Database::result($result, 0, 0); + $this->updatePosition($currentPosition + 1); $position = $this->position; $params = [ @@ -1108,50 +1114,50 @@ public function search_engine_edit( if (!empty($exerciseId) && api_get_setting('search_enabled') == 'true' && extension_loaded('xapian') ) { - $course_id = api_get_course_id(); + $courseId = api_get_course_id(); // get search_did $tbl_se_ref = Database::get_main_table(TABLE_MAIN_SEARCH_ENGINE_REF); if ($addQs || $rmQs) { //there's only one row per question on normal db and one document per question on search engine db $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_second_level=%s LIMIT 1'; - $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id); + $sql = sprintf($sql, $tbl_se_ref, $courseId, TOOL_QUIZ, $this->id); } else { $sql = 'SELECT * FROM %s WHERE course_code=\'%s\' AND tool_id=\'%s\' AND ref_id_high_level=%s AND ref_id_second_level=%s LIMIT 1'; - $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id); + $sql = sprintf($sql, $tbl_se_ref, $courseId, TOOL_QUIZ, $exerciseId, $this->id); } $res = Database::query($sql); if (Database::num_rows($res) > 0 || $addQs) { $di = new ChamiloIndexer(); if ($addQs) { - $question_exercises = [(int) $exerciseId]; + $questionExercises = [(int) $exerciseId]; } else { - $question_exercises = []; + $questionExercises = []; } isset($_POST['language']) ? $lang = Database::escape_string($_POST['language']) : $lang = 'english'; $di->connectDb(null, null, $lang); // retrieve others exercise ids - $se_ref = Database::fetch_array($res); - $se_doc = $di->get_document((int) $se_ref['search_did']); - if ($se_doc !== false) { - if (($se_doc_data = $di->get_document_data($se_doc)) !== false) { - $se_doc_data = UnserializeApi::unserialize( + $seRef = Database::fetch_array($res); + $seDoc = $di->get_document((int) $seRef['search_did']); + if ($seDoc !== false) { + if (($seDocData = $di->get_document_data($seDoc)) !== false) { + $seDocData = UnserializeApi::unserialize( 'not_allowed_classes', - $se_doc_data + $seDocData ); - if (isset($se_doc_data[SE_DATA]['type']) && - $se_doc_data[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION + if (isset($seDocData[SE_DATA]['type']) && + $seDocData[SE_DATA]['type'] == SE_DOCTYPE_EXERCISE_QUESTION ) { - if (isset($se_doc_data[SE_DATA]['exercise_ids']) && - is_array($se_doc_data[SE_DATA]['exercise_ids']) + if (isset($seDocData[SE_DATA]['exercise_ids']) && + is_array($seDocData[SE_DATA]['exercise_ids']) ) { - foreach ($se_doc_data[SE_DATA]['exercise_ids'] as $old_value) { - if (!in_array($old_value, $question_exercises)) { - $question_exercises[] = $old_value; + foreach ($seDocData[SE_DATA]['exercise_ids'] as $oldValue) { + if (!in_array($oldValue, $questionExercises)) { + $questionExercises[] = $oldValue; } } } @@ -1159,36 +1165,36 @@ public function search_engine_edit( } } if ($rmQs) { - while (($key = array_search($exerciseId, $question_exercises)) !== false) { - unset($question_exercises[$key]); + while (($key = array_search($exerciseId, $questionExercises)) !== false) { + unset($questionExercises[$key]); } } // build the chunk to index - $ic_slide = new IndexableChunk(); - $ic_slide->addValue("title", $this->question); - $ic_slide->addCourseId($course_id); - $ic_slide->addToolId(TOOL_QUIZ); + $icSlide = new IndexableChunk(); + $icSlide->addValue("title", $this->question); + $icSlide->addCourseId($courseId); + $icSlide->addToolId(TOOL_QUIZ); $xapian_data = [ - SE_COURSE_ID => $course_id, + SE_COURSE_ID => $courseId, SE_TOOL_ID => TOOL_QUIZ, SE_DATA => [ 'type' => SE_DOCTYPE_EXERCISE_QUESTION, - 'exercise_ids' => $question_exercises, + 'exercise_ids' => $questionExercises, 'question_id' => (int) $this->id, ], SE_USER => (int) api_get_user_id(), ]; - $ic_slide->xapian_data = serialize($xapian_data); - $ic_slide->addValue("content", $this->description); + $icSlide->xapian_data = serialize($xapian_data); + $icSlide->addValue("content", $this->description); //TODO: index answers, see also form validation on question_admin.inc.php - $di->remove_document($se_ref['search_did']); - $di->addChunk($ic_slide); + $di->remove_document($seRef['search_did']); + $di->addChunk($icSlide); //index and return search engine document id - if (!empty($question_exercises)) { // if empty there is nothing to index + if (!empty($questionExercises)) { // if empty there is nothing to index $did = $di->index(); unset($di); } @@ -1197,7 +1203,7 @@ public function search_engine_edit( if ($addQs || $rmQs) { $sql = "DELETE FROM %s WHERE course_code = '%s' AND tool_id = '%s' AND ref_id_second_level = '%s'"; - $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $this->id); + $sql = sprintf($sql, $tbl_se_ref, $courseId, TOOL_QUIZ, $this->id); } else { $sql = "DELETE FROM %S WHERE @@ -1206,11 +1212,11 @@ public function search_engine_edit( AND tool_id = '%s' AND ref_id_high_level = '%s' AND ref_id_second_level = '%s'"; - $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id); + $sql = sprintf($sql, $tbl_se_ref, $courseId, TOOL_QUIZ, $exerciseId, $this->id); } Database::query($sql); if ($rmQs) { - if (!empty($question_exercises)) { + if (!empty($questionExercises)) { $sql = "INSERT INTO %s ( id, course_code, tool_id, ref_id_high_level, ref_id_second_level, search_did ) @@ -1220,9 +1226,9 @@ public function search_engine_edit( $sql = sprintf( $sql, $tbl_se_ref, - $course_id, + $courseId, TOOL_QUIZ, - array_shift($question_exercises), + array_shift($questionExercises), $this->id, $did ); @@ -1235,7 +1241,7 @@ public function search_engine_edit( VALUES ( NULL , '%s', '%s', %s, %s, %s )"; - $sql = sprintf($sql, $tbl_se_ref, $course_id, TOOL_QUIZ, $exerciseId, $this->id, $did); + $sql = sprintf($sql, $tbl_se_ref, $courseId, TOOL_QUIZ, $exerciseId, $this->id, $did); Database::query($sql); } } @@ -1484,14 +1490,14 @@ public function duplicate($courseInfo = []) ); } - $course_id = $courseInfo['real_id']; + $courseId = $courseInfo['real_id']; // Read the source options $options = self::readQuestionOption($this->id, $this->course['real_id']); // Inserting in the new course db / or the same course db $params = [ - 'c_id' => $course_id, + 'c_id' => $courseId, 'question' => $question, 'description' => $description, 'ponderation' => $weighting, @@ -1512,7 +1518,7 @@ public function duplicate($courseInfo = []) // Saving the quiz_options foreach ($options as $item) { $item['question_id'] = $newQuestionId; - $item['c_id'] = $course_id; + $item['c_id'] = $courseId; unset($item['id']); unset($item['iid']); $id = Database::insert($TBL_QUESTION_OPTIONS, $item); @@ -1535,9 +1541,23 @@ public function duplicate($courseInfo = []) /** * @return string */ - public function get_question_type_name() + public function get_question_type_name($ptest = false) { - $key = self::$questionTypes[$this->type]; + if ($ptest) { + $key = self::$questionPtTypes[$this->type]; + } else { + $key = self::$questionTypes[$this->type]; + } + + return get_lang($key[1]); + } + + /** + * @return string + */ + public function get_question_ptest_type_name() + { + $key = self::$questionPtTypes[$this->type]; return get_lang($key[1]); } @@ -1554,6 +1574,14 @@ public static function get_question_type($type) return self::$questionTypes[$type]; } + /** + * @param string $type + */ + public static function get_question_ptest_type($type) + { + return self::$questionPtTypes[$type]; + } + /** * @return array */ @@ -1571,6 +1599,14 @@ public static function getQuestionTypeList() return self::$questionTypes; } + /** + * @return array + */ + public static function getQuestionPtTypeList() + { + return self::$questionPtTypes; + } + /** * Returns an instance of the class corresponding to the type. * @@ -1578,10 +1614,14 @@ public static function getQuestionTypeList() * * @return $this instance of a Question subclass (or of Questionc class by default) */ - public static function getInstance($type) + public static function getInstance($type, $ptest = false) { if (!is_null($type)) { - list($fileName, $className) = self::get_question_type($type); + if ($ptest) { + list($fileName, $className) = self::get_question_ptest_type($type); + } else { + list($fileName, $className) = self::get_question_type($type); + } if (!empty($fileName)) { include_once $fileName; if (class_exists($className)) { @@ -1654,12 +1694,12 @@ public function createForm(&$form, $exercise) if ($this->type != MEDIA_QUESTION) { // Advanced parameters - $select_level = self::get_default_levels(); + $selectLevel = self::get_default_levels(); $form->addElement( 'select', 'questionLevel', get_lang('Difficulty'), - $select_level + $selectLevel ); // Categories @@ -1778,6 +1818,111 @@ public function createForm(&$form, $exercise) }*/ } + /** + * Creates the form to create / edit a question + * A subclass can redefine this function to add fields... + * + * @param FormValidator $form + */ + public function createPtForm(&$form) + { + echo ''; + + // question name + if (api_get_configuration_value('save_titles_as_html')) { + $editorConfig = ['ToolbarSet' => 'TitleAsHtml']; + $form->addHtmlEditor( + 'questionName', + get_lang('Question'), + false, + false, + $editorConfig, + true + ); + } else { + $form->addElement('text', 'questionName', get_lang('Question')); + } + + $form->addRule('questionName', get_lang('GiveQuestion'), 'required'); + + // default content + $isContent = isset($_REQUEST['isContent']) ? (int) $_REQUEST['isContent'] : null; + + // Question type + $answerType = isset($_REQUEST['answerType']) ? (int) $_REQUEST['answerType'] : null; + $form->addElement('hidden', 'answerType', $answerType); + + // html editor + $editorConfig = [ + 'ToolbarSet' => 'TestQuestionDescription', + 'Height' => '150', + ]; + + if (!api_is_allowed_to_edit(null, true)) { + $editorConfig['UserStatus'] = 'student'; + } + + $form->addButtonAdvancedSettings('advanced_params'); + $form->addElement('html', ''); + + // default values + $defaults = []; + $defaults['questionName'] = $this->question; + $defaults['questionDescription'] = $this->description; + $defaults['questionLevel'] = $this->level; + $defaults['questionCategory'] = $this->category; + + // Came from he question pool + if (isset($_GET['fromExercise'])) { + $form->setDefaults($defaults); + } + + if (!isset($_GET['newQuestion']) || $isContent) { + $form->setDefaults($defaults); + } + } + /** * function which process the creation of questions. * @@ -1912,43 +2057,93 @@ public static function displayTypeMenu($objExercise) } /** - * @param int $question_id + * Displays the menu of question types. + * + * @param Exercise $objExercise + */ + public static function displayPtTypeMenu($objExercise) + { + // 1. by default we show all the question types + $questionTypeList = self::getQuestionPtTypeList(); + + echo '
'; + echo '
'; + echo ''; + echo '
'; + echo '
'; + } + + /** + * @param int $questionId * @param string $name - * @param int $course_id + * @param int $courseId * @param int $position * * @return false|string */ - public static function saveQuestionOption($question_id, $name, $course_id, $position = 0) + public static function saveQuestionOption($questionId, $name, $courseId, $position = 0) { $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION); - $params['question_id'] = (int) $question_id; + $params['question_id'] = (int) $questionId; $params['name'] = $name; $params['position'] = $position; - $params['c_id'] = $course_id; - $result = self::readQuestionOption($question_id, $course_id); - $last_id = Database::insert($table, $params); - if ($last_id) { - $sql = "UPDATE $table SET id = iid WHERE iid = $last_id"; + $params['c_id'] = $courseId; + $result = self::readQuestionOption($questionId, $courseId); + $lastId = Database::insert($table, $params); + if ($lastId) { + $sql = "UPDATE $table SET id = iid WHERE iid = $lastId"; Database::query($sql); } - return $last_id; + return $lastId; } /** - * @param int $question_id - * @param int $course_id + * @param int $questionId + * @param int $courseId */ - public static function deleteAllQuestionOptions($question_id, $course_id) + public static function deleteAllQuestionOptions($questionId, $courseId) { $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION); Database::delete( $table, [ 'c_id = ? AND question_id = ?' => [ - $course_id, - $question_id, + $courseId, + $questionId, ], ] ); @@ -1957,29 +2152,29 @@ public static function deleteAllQuestionOptions($question_id, $course_id) /** * @param int $id * @param array $params - * @param int $course_id + * @param int $courseId * * @return bool|int */ - public static function updateQuestionOption($id, $params, $course_id) + public static function updateQuestionOption($id, $params, $courseId) { $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION); $result = Database::update( $table, $params, - ['c_id = ? AND id = ?' => [$course_id, $id]] + ['c_id = ? AND id = ?' => [$courseId, $id]] ); return $result; } /** - * @param int $question_id - * @param int $course_id + * @param int $questionId + * @param int $courseId * * @return array */ - public static function readQuestionOption($question_id, $course_id) + public static function readQuestionOption($questionId, $courseId) { $table = Database::get_course_table(TABLE_QUIZ_QUESTION_OPTION); $result = Database::select( @@ -1988,8 +2183,8 @@ public static function readQuestionOption($question_id, $course_id) [ 'where' => [ 'c_id = ? AND question_id = ?' => [ - $course_id, - $question_id, + $courseId, + $questionId, ], ], 'order' => 'id ASC', @@ -2085,8 +2280,8 @@ public function return_header(Exercise $exercise, $counter = null, $score = []) if ($exercise->display_category_name) { $header = TestCategory::returnCategoryAndTitle($this->id); } - $show_media = ''; - if ($show_media) { + $showMedia = ''; + if ($showMedia) { $header .= $this->show_media_content(); } @@ -2165,7 +2360,7 @@ public function create_question( $type = 1, $level = 1 ) { - $course_id = api_get_course_int_id(); + $courseId = api_get_course_int_id(); $tbl_quiz_question = Database::get_course_table(TABLE_QUIZ_QUESTION); $tbl_quiz_rel_question = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); @@ -2181,14 +2376,14 @@ public function create_question( ON q.id = r.question_id AND exercice_id = $quiz_id AND - q.c_id = $course_id AND - r.c_id = $course_id"; + q.c_id = $courseId AND + r.c_id = $courseId"; $rs_max = Database::query($sql); $row_max = Database::fetch_object($rs_max); $max_position = $row_max->max_position + 1; $params = [ - 'c_id' => $course_id, + 'c_id' => $courseId, 'question' => $question_name, 'description' => $question_description, 'ponderation' => $max_score, @@ -2196,27 +2391,27 @@ public function create_question( 'type' => $type, 'level' => $level, ]; - $question_id = Database::insert($tbl_quiz_question, $params); + $questionId = Database::insert($tbl_quiz_question, $params); - if ($question_id) { + if ($questionId) { $sql = "UPDATE $tbl_quiz_question - SET id = iid WHERE iid = $question_id"; + SET id = iid WHERE iid = $questionId"; Database::query($sql); // Get the max question_order $sql = "SELECT max(question_order) as max_order FROM $tbl_quiz_rel_question - WHERE c_id = $course_id AND exercice_id = $quiz_id "; + WHERE c_id = $courseId AND exercice_id = $quiz_id "; $rs_max_order = Database::query($sql); $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) - VALUES($course_id, $question_id, $quiz_id, $max_order)"; + VALUES($courseId, $questionId, $quiz_id, $max_order)"; Database::query($sql); } - return $question_id; + return $questionId; } /** @@ -2238,28 +2433,28 @@ public function getExplanation() /** * Get course medias. * - * @param int $course_id + * @param int $courseId * * @return array */ public static function get_course_medias( - $course_id, + $courseId, $start = 0, $limit = 100, $sidx = "question", $sord = "ASC", $where_condition = [] ) { - $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION); + $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); $default_where = [ 'c_id = ? AND parent_id = 0 AND type = ?' => [ - $course_id, + $courseId, MEDIA_QUESTION, ], ]; $result = Database::select( '*', - $table_question, + $tableQuestion, [ 'limit' => " $start, $limit", 'where' => $default_where, @@ -2277,16 +2472,16 @@ public static function get_course_medias( * * @return int */ - public static function get_count_course_medias($course_id) + public static function get_count_course_medias($courseId) { - $table_question = Database::get_course_table(TABLE_QUIZ_QUESTION); + $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION); $result = Database::select( 'count(*) as count', - $table_question, + $tableQuestion, [ 'where' => [ 'c_id = ? AND parent_id = 0 AND type = ?' => [ - $course_id, + $courseId, MEDIA_QUESTION, ], ], @@ -2302,23 +2497,23 @@ public static function get_count_course_medias($course_id) } /** - * @param int $course_id + * @param int $courseId * * @return array */ - public static function prepare_course_media_select($course_id) + public static function prepare_course_media_select($courseId) { - $medias = self::get_course_medias($course_id); - $media_list = []; - $media_list[0] = get_lang('NoMedia'); + $medias = self::get_course_medias($courseId); + $mediaList = []; + $mediaList[0] = get_lang('NoMedia'); if (!empty($medias)) { foreach ($medias as $media) { - $media_list[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question']; + $mediaList[$media['id']] = empty($media['question']) ? get_lang('Untitled') : $media['question']; } } - return $media_list; + return $mediaList; } /** @@ -2344,8 +2539,8 @@ public function show_media_content() { $html = ''; if ($this->parent_id != 0) { - $parent_question = self::read($this->parent_id); - $html = $parent_question->show_media_content(); + $parentQuestion = self::read($this->parent_id); + $html = $parentQuestion->show_media_content(); } else { $html .= Display::page_subheader($this->selectTitle()); $html .= $this->selectDescription(); @@ -2507,22 +2702,22 @@ private function resizePicture($Dimension, $Max) $picturePath = $this->getHotSpotFolderInCourse().'/'.$this->getPictureFilename(); // Get dimensions from current image. - $my_image = new Image($picturePath); + $myImage = new Image($picturePath); - $current_image_size = $my_image->get_image_size(); - $current_width = $current_image_size['width']; - $current_height = $current_image_size['height']; + $currentImageSize = $myImage->get_image_size(); + $currentWidth = $currentImageSize['width']; + $currentHeight = $currentImageSize['height']; - if ($current_width < $Max && $current_height < $Max) { + if ($currentWidth < $Max && $currentHeight < $Max) { return true; - } elseif ($current_height == '') { + } elseif ($currentHeight == '') { return false; } // Resize according to height. if ($Dimension == "height") { - $resize_scale = $current_height / $Max; - $new_width = ceil($current_width / $resize_scale); + $resize_scale = $currentHeight / $Max; + $new_width = ceil($currentWidth / $resize_scale); } // Resize according to width @@ -2532,17 +2727,17 @@ private function resizePicture($Dimension, $Max) // Resize according to height or width, both should not be larger than $Max after resizing. if ($Dimension == "any") { - if ($current_height > $current_width || $current_height == $current_width) { - $resize_scale = $current_height / $Max; - $new_width = ceil($current_width / $resize_scale); + if ($currentHeight > $currentWidth || $currentHeight == $currentWidth) { + $resize_scale = $currentHeight / $Max; + $new_width = ceil($currentWidth / $resize_scale); } - if ($current_height < $current_width) { + if ($currentHeight < $currentWidth) { $new_width = $Max; } } - $my_image->resize($new_width); - $result = $my_image->send_image($picturePath); + $myImage->resize($new_width); + $result = $myImage->send_image($picturePath); if ($result) { return true; diff --git a/main/exercise/question_list_ptest_admin.inc.php b/main/exercise/question_list_ptest_admin.inc.php new file mode 100644 index 00000000000..9ea0b3e5414 --- /dev/null +++ b/main/exercise/question_list_ptest_admin.inc.php @@ -0,0 +1,317 @@ +delete($exerciseId); + + // if the question has been removed from the exercise + if ($objExercise->removeFromList($deleteQuestion)) { + $nbrQuestions--; + } + } + // destruction of the Question object + unset($objQuestionTmp); +} +$ajax_url = api_get_path(WEB_AJAX_PATH).'exercise.ajax.php?'.api_get_cidreq().'&exercise_id='.intval($exerciseId); +?> + + +'; +$token = Security::get_token(); +//deletes a session when using don't know question type (ugly fix) +Session::erase('less_answer'); + +// Define categories for question +$category = new PTestCategory(); +if ($category->getCategoriesExerciseNumber($objExercise->selectId()) == 0) { + echo Display::return_message(get_lang('NoDefinedCategories'), 'warning'); +} + +// If we are in a test +$inATest = isset($exerciseId) && $exerciseId > 0; +if (!$inATest) { + echo Display::return_message(get_lang('ChoiceQuestionType'), 'warning'); +} else { + if ($nbrQuestions) { + // In the building exercise mode show question list ordered as is. + $objExercise->setCategoriesGrouping(false); + + // In building mode show all questions not render by teacher order. + $objExercise->questionSelectionType = EX_Q_SELECTION_ORDERED; + $allowQuestionOrdering = true; + $showPagination = api_get_configuration_value('show_question_pagination'); + if (!empty($showPagination) && $nbrQuestions > $showPagination) { + $length = api_get_configuration_value('question_pagination_length'); + $url = api_get_self().'?'.api_get_cidreq(); + // Use pagination for exercise with more than 200 questions. + $allowQuestionOrdering = false; + $start = ($page - 1) * $length; + $questionList = $objExercise->getQuestionForTeacher($start, $length); + $paginator = new Knp\Component\Pager\Paginator(); + $pagination = $paginator->paginate([]); + + $pagination->setTotalItemCount($nbrQuestions); + $pagination->setItemNumberPerPage($length); + $pagination->setCurrentPageNumber($page); + $pagination->renderer = function ($data) use ($url) { + $render = ''; + + return $render; + }; + echo $pagination; + } else { + // Classic order + $questionList = $objExercise->selectQuestionList(true, true); + } + + echo ' + +
+ '; + + $categoryList = TestCategory::getListOfCategoriesNameForTest($objExercise->id, false); + + if (is_array($questionList)) { + foreach ($questionList as $id) { + // To avoid warning messages. + if (!is_numeric($id)) { + continue; + } + /** @var Question $objQuestionTmp */ + $objQuestionTmp = Question::read($id, null, true, true); + + if (empty($objQuestionTmp)) { + continue; + } + + $editLink = Display::url( + Display::return_icon( + 'edit.png', + get_lang('Modify'), + [], + ICON_SIZE_TINY + ), + api_get_self().'?exerciseId='.$objExercise->id.'&'.api_get_cidreq().'&' + .http_build_query([ + 'type' => $objQuestionTmp->selectType(), + 'editQuestion' => $id, + 'page' => $page, + ]), + ['class' => 'btn btn-default btn-sm'] + ); + $deleteLink = null; + if ($objExercise->edit_exercise_in_lp == true) { + $deleteLink = Display::url( + Display::return_icon( + 'delete.png', + get_lang('RemoveFromTest'), + [], + ICON_SIZE_TINY + ), + api_get_self().'?exerciseId='.$objExercise->id.'&'.api_get_cidreq().'&' + .http_build_query([ + 'exerciseId' => $exerciseId, + 'deleteQuestion' => $id, + 'page' => $page, + ]), + [ + 'id' => "delete_$id", + 'class' => 'opener btn btn-default btn-sm', + ] + ); + } + + if ($limitTeacherAccess && !api_is_platform_admin()) { + $deleteLink = ''; + } + + $btnActions = implode( + PHP_EOL, + [$editLink, $deleteLink] + ); + + $title = Security::remove_XSS($objQuestionTmp->selectTitle()); + $title = strip_tags($title); + $move = ' '; + if ($allowQuestionOrdering) { + $move = Display::returnFontAwesomeIcon('arrows moved', 1, true); + } + + // Question name + $questionName = + ' + '.$move.' '.cut($title, 42).' + '; + + // Question type + $typeImg = $objQuestionTmp->getTypePicture(); + $typeExpl = $objQuestionTmp->getExplanation(); + + $questionType = Display::return_icon($typeImg, get_lang($typeExpl)); + + // Question category + $txtQuestionCat = Security::remove_XSS( + TestCategory::getCategoryNameForQuestion($objQuestionTmp->id) + ); + if (empty($txtQuestionCat)) { + $txtQuestionCat = '-'; + } + + // Question level + $txtQuestionLevel = $objQuestionTmp->getLevel(); + if (empty($objQuestionTmp->level)) { + $txtQuestionLevel = '-'; + } + $questionLevel = $txtQuestionLevel; + + echo '
+
+
+
' + .$questionName.' +
+
+ '.get_lang('Type').' ' + .$questionType.' +
+
+ '.get_lang('Category').' ' + .cut($txtQuestionCat, 42).' +
+
+ '.get_lang('Difficulty').' ' + .$questionLevel.' +
+
+
'.$btnActions.'
+
+
+
+
+
+
+ '; + unset($objQuestionTmp); + } + } + + echo '
'; //question list div + } else { + echo Display::return_message(get_lang('NoQuestion'), 'warning'); + } +} diff --git a/main/exercise/question_pool.php b/main/exercise/question_pool.php index 0ced93c4c07..7a63c4c007b 100755 --- a/main/exercise/question_pool.php +++ b/main/exercise/question_pool.php @@ -521,8 +521,6 @@ function confirm_your_choice() { $objExercise->id, + 'modifyQuestion' => $modifyQuestion, + 'editQuestion' => $objQuestion->id, + 'page' => $page, + ]).api_get_cidreq(); +} else { + $objQuestion = Question::getInstance($_REQUEST['answerType'], true); + $action = api_get_self().'?'.http_build_query([ + 'exerciseId' => $objExercise->id, + 'modifyQuestion' => $modifyQuestion, + 'newQuestion' => $newQuestion, + ]).'&'.api_get_cidreq(); +} + +if (is_object($objQuestion)) { + // FORM CREATION + $form = new FormValidator('question_admin_form', 'post', $action); + if (isset($_GET['editQuestion'])) { + $class = 'btn btn-default'; + $text = get_lang('ModifyQuestion'); + $type = isset($_GET['type']) ? Security::remove_XSS($_GET['type']) : null; + } else { + $class = 'btn btn-default'; + $text = get_lang('AddQuestionToExercise'); + $type = $_REQUEST['answerType']; + } + + $typesInformation = Question::getQuestionPtTypeList(); + $formTitleExtra = isset($typesInformation[$type][1]) ? get_lang($typesInformation[$type][1]) : null; + + $code = ''; + if (isset($objQuestion->code) && !empty($objQuestion->code)) { + $code = ' ('.$objQuestion->code.')'; + } + + // form title + $form->addHeader($text.': '.$formTitleExtra.$code); + + // question form elements + $objQuestion->createPtForm($form); + + // answer form elements + $objQuestion->createAnswersForm($form); + + // this variable $show_quiz_edition comes from admin.php blocks the exercise/quiz modifications + if (!empty($objExercise->id) && $objExercise->edit_exercise_in_lp == false) { + $form->freeze(); + } + + // FORM VALIDATION + if (isset($_POST['submitQuestion']) && $form->validate()) { + // Question + $objQuestion->processCreation($form, $objExercise); + $objQuestion->processAnswersCreation($form, $objExercise); + + if (isset($_GET['editQuestion'])) { + if (empty($exerciseId)) { + Display::addFlash(Display::return_message(get_lang('ItemUpdated'))); + $url = 'ptest_admin.php?'.http_build_query([ + 'exerciseId' => $exerciseId, + 'editQuestion' => $objQuestion->id, + ]).'&'.api_get_cidreq(); + echo ''; + exit; + } + echo ''; + } else { + // New question + $page = 1; + $length = api_get_configuration_value('question_pagination_length'); + if (!empty($length)) { + $page = round($objExercise->getQuestionCount() / $length); + } + echo ''; + } + } else { + if (isset($questionName)) { + echo '

'.$questionName.'

'; + } + if (!empty($pictureName)) { + echo ''; + } + if (!empty($msgErr)) { + echo Display::return_message($msgErr); + } + // display the form + $form->display(); + } +} diff --git a/main/exercise/result.php b/main/exercise/result.php index 91611f62f1c..9874d27f007 100755 --- a/main/exercise/result.php +++ b/main/exercise/result.php @@ -57,6 +57,7 @@ $htmlHeadXtra[] = ''; $htmlHeadXtra[] = ''; $htmlHeadXtra[] = ''; +$htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js'); if (!empty($objExercise->getResultAccess())) { $htmlHeadXtra[] = api_get_css(api_get_path(WEB_LIBRARY_PATH).'javascript/epiclock/renderers/minute/epiclock.minute.css'); diff --git a/main/img/icons/22/new_personality_test.png b/main/img/icons/22/new_personality_test.png new file mode 100644 index 00000000000..6e80bd0995e Binary files /dev/null and b/main/img/icons/22/new_personality_test.png differ diff --git a/main/img/icons/22/ptaod.png b/main/img/icons/22/ptaod.png new file mode 100644 index 00000000000..cf09a853906 Binary files /dev/null and b/main/img/icons/22/ptaod.png differ diff --git a/main/img/icons/22/ptorder.png b/main/img/icons/22/ptorder.png new file mode 100644 index 00000000000..5533565c2c3 Binary files /dev/null and b/main/img/icons/22/ptorder.png differ diff --git a/main/img/icons/22/ptscale.png b/main/img/icons/22/ptscale.png new file mode 100644 index 00000000000..38772c7897c Binary files /dev/null and b/main/img/icons/22/ptscale.png differ diff --git a/main/img/icons/32/new_personality_test.png b/main/img/icons/32/new_personality_test.png new file mode 100644 index 00000000000..4578986febe Binary files /dev/null and b/main/img/icons/32/new_personality_test.png differ diff --git a/main/img/icons/64/ptaod.png b/main/img/icons/64/ptaod.png new file mode 100644 index 00000000000..aea3374df10 Binary files /dev/null and b/main/img/icons/64/ptaod.png differ diff --git a/main/img/icons/64/ptorder.png b/main/img/icons/64/ptorder.png new file mode 100644 index 00000000000..3c9c45e42c4 Binary files /dev/null and b/main/img/icons/64/ptorder.png differ diff --git a/main/img/icons/64/ptscale.png b/main/img/icons/64/ptscale.png new file mode 100644 index 00000000000..d1a7b510f3e Binary files /dev/null and b/main/img/icons/64/ptscale.png differ diff --git a/main/inc/ajax/exercise.ajax.php b/main/inc/ajax/exercise.ajax.php index 74c07eb698b..1c2ecab21fa 100755 --- a/main/inc/ajax/exercise.ajax.php +++ b/main/inc/ajax/exercise.ajax.php @@ -445,6 +445,11 @@ exit; } + $ptest = false; + if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $ptest = true; + } + // Getting information of the current exercise. $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); $exercise_id = $exercise_stat_info['exe_exo_id']; @@ -498,7 +503,7 @@ $total_weight = 0; if ($type == 'simple') { foreach ($question_list as $my_question_id) { - $objQuestionTmp = Question::read($my_question_id, $objExercise->course); + $objQuestionTmp = Question::read($my_question_id, $objExercise->course, true, $ptest); $total_weight += $objQuestionTmp->selectWeighting(); } } @@ -521,7 +526,7 @@ } // Creates a temporary Question object - $objQuestionTmp = Question::read($my_question_id, $objExercise->course); + $objQuestionTmp = Question::read($my_question_id, $objExercise->course, true, $ptest); $myChoiceDegreeCertainty = null; if ($objQuestionTmp->type === MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { @@ -612,6 +617,60 @@ $objExercise->selectPropagateNeg(), $hotspot_delineation_result ); + } elseif ($objQuestionTmp->type === QUESTION_PT_TYPE_AGREE_OR_DISAGREE) { + $myChoiceTmp = []; + $myChoiceTmp['agree'] = ( + isset($_REQUEST['choice-agree'][$my_question_id]) && + !empty($_REQUEST['choice-agree'][$my_question_id]) + ) ? (int) $_REQUEST['choice-agree'][$my_question_id] : 0; + $myChoiceTmp['disagree'] = ( + isset($_REQUEST['choice-disagree'][$my_question_id]) && + !empty($_REQUEST['choice-disagree'][$my_question_id]) + ) ? (int) $_REQUEST['choice-disagree'][$my_question_id] : 0; + + $result = $objExercise->manage_answer( + $exeId, + $my_question_id, + $myChoiceTmp, + 'exercise_result', + $hot_spot_coordinates, + true, + false, + false, + $objExercise->selectPropagateNeg(), + $hotspot_delineation_result + ); + } elseif ($objQuestionTmp->type === QUESTION_PT_TYPE_AGREE_SCALE) { + $result = $objExercise->manage_answer( + $exeId, + $my_question_id, + json_encode($my_choice), + 'exercise_result', + $hot_spot_coordinates, + true, + false, + false, + $objExercise->selectPropagateNeg(), + $hotspot_delineation_result + ); + } elseif ($objQuestionTmp->type === QUESTION_PT_TYPE_AGREE_REORDER) { + $myChoiceTmp = []; + foreach ($_REQUEST['choice'][$my_question_id] as $key => $value) { + $myChoiceTmp[$key] = $value; + } + + $result = $objExercise->manage_answer( + $exeId, + $my_question_id, + json_encode($myChoiceTmp), + 'exercise_result', + $hot_spot_coordinates, + true, + false, + false, + $objExercise->selectPropagateNeg(), + $hotspot_delineation_result + ); } else { $result = $objExercise->manage_answer( $exeId, diff --git a/main/inc/ajax/model.ajax.php b/main/inc/ajax/model.ajax.php index f7712c79a07..6048734b27e 100755 --- a/main/inc/ajax/model.ajax.php +++ b/main/inc/ajax/model.ajax.php @@ -43,6 +43,7 @@ $action, [ 'get_exercise_results', + 'get_ptest_exercise_results', 'get_exercise_results_report', 'get_work_student_list_overview', 'get_hotpotatoes_exercise_results', @@ -597,6 +598,7 @@ function getWhereClause($col, $oper, $val) ); break; case 'get_exercise_results': + case 'get_ptest_exercise_results': $exercise_id = $_REQUEST['exerciseId']; if (isset($_GET['filter_by_user']) && !empty($_GET['filter_by_user'])) { @@ -1398,6 +1400,48 @@ function getWhereClause($col, $oper, $val) $whereCondition ); break; + case 'get_ptest_exercise_results': + $is_allowedToEdit = api_is_allowed_to_edit(null, true) || + api_is_drh() || + api_is_student_boss() || + api_is_session_admin(); + if ($is_allowedToEdit || api_is_student_boss()) { + $columns = [ + 'firstname', + 'lastname', + 'username', + 'group_name', + 'exe_duration', + 'start_date', + 'exe_date', + 'user_ip', + 'lp', + 'actions', + ]; + $officialCodeInList = api_get_setting('show_official_code_exercise_result_list'); + if ($officialCodeInList === 'true') { + $columns = array_merge(['official_code'], $columns); + } + } + + $result = ExerciseLib::get_exam_results_data( + $start, + $limit, + $sidx, + $sord, + $exercise_id, + $whereCondition, + false, + null, + false, + false, + [], + false, + false, + false, + true + ); + break; case 'get_exercise_results_report': $columns = [ 'firstname', @@ -2322,6 +2366,7 @@ function getWhereClause($col, $oper, $val) 'get_session_progress', 'get_exercise_progress', 'get_exercise_results', + 'get_ptest_exercise_results', 'get_exercise_results_report', 'get_work_student_list_overview', 'get_hotpotatoes_exercise_results', diff --git a/main/inc/ajax/statistics.ajax.php b/main/inc/ajax/statistics.ajax.php index 33cfef7bd3b..f462bddbaa9 100644 --- a/main/inc/ajax/statistics.ajax.php +++ b/main/inc/ajax/statistics.ajax.php @@ -428,13 +428,13 @@ $years = (int) $interval->y; if ($years >= 16 && $years <= 17) { - $all['16-17'] += 1; + $all['16-17']++; } if ($years >= 18 && $years <= 25) { - $all['18-25'] += 1; + $all['18-25']++; } if ($years >= 26 && $years <= 30) { - $all['26-30'] += 1; + $all['26-30']++; } /*if ($years >= 31) { $all[get_lang('N/A')] += 1; @@ -658,7 +658,7 @@ if (!isset($all[$language])) { $all[$language] = 0; } - $all[$language] += 1; + $all[$language]++; } break; case 'course_in_session': diff --git a/main/inc/lib/api.lib.php b/main/inc/lib/api.lib.php index 53abb967409..a61df20f947 100644 --- a/main/inc/lib/api.lib.php +++ b/main/inc/lib/api.lib.php @@ -123,6 +123,7 @@ define('TOOL_DROPBOX', 'dropbox'); define('TOOL_QUIZ', 'quiz'); define('TOOL_TEST_CATEGORY', 'test_category'); +define('TOOL_PTEST_CATEGORY', 'ptest_category'); define('TOOL_USER', 'user'); define('TOOL_GROUP', 'group'); define('TOOL_BLOGS', 'blog_management'); @@ -470,6 +471,9 @@ define('EXERCISE_FEEDBACK_TYPE_EXAM', 2); // NoFeedback - Show score only define('EXERCISE_FEEDBACK_TYPE_POPUP', 3); // Popup BT#15827 +define('EXERCISE_PT_TYPE_CLASSIC', 0); // Default quiz +define('EXERCISE_PT_TYPE_PTEST', 1); // Personality test quiz + define('RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS', 0); //show score and expected answers define('RESULT_DISABLE_NO_SCORE_AND_EXPECTED_ANSWERS', 1); //Do not show score nor answers define('RESULT_DISABLE_SHOW_SCORE_ONLY', 2); //Show score only @@ -479,6 +483,7 @@ define('RESULT_DISABLE_RANKING', 6); define('RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER', 7); define('RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING', 8); +define('RESULT_DISABLE_PT_TYPE_PTEST', 9); define('EXERCISE_MAX_NAME_SIZE', 80); @@ -507,6 +512,15 @@ define('READING_COMPREHENSION', 21); define('MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY', 22); +// Question Personality Test types +define('QUESTION_PT_TYPE_CATEGORY_RANKING', 23); // count how many times the user clicked in different categories +define('QUESTION_PT_TYPE_AGREE_OR_DISAGREE', 24); // Select two options out of 5 (most agree and least agree) +define('QUESTION_PT_TYPE_AGREE_SCALE', 25); // On a scale of 5 set a value - Most agree = 5, least agree is 1 +define('QUESTION_PT_TYPE_AGREE_REORDER', 26); // Re-order these 5 words by most agree to least agree. + +define('ANSWER_AGREE', 1); +define('ANSWER_DISAGREE', 2); + define('EXERCISE_CATEGORY_RANDOM_SHUFFLED', 1); define('EXERCISE_CATEGORY_RANDOM_ORDERED', 2); define('EXERCISE_CATEGORY_RANDOM_DISABLED', 0); diff --git a/main/inc/lib/database.constants.inc.php b/main/inc/lib/database.constants.inc.php index cb1757009f9..10528c7751f 100755 --- a/main/inc/lib/database.constants.inc.php +++ b/main/inc/lib/database.constants.inc.php @@ -197,6 +197,7 @@ define('TABLE_QUIZ_QUESTION_CATEGORY', 'quiz_question_category'); define('TABLE_QUIZ_QUESTION_REL_CATEGORY', 'quiz_question_rel_category'); define('TABLE_QUIZ_REL_CATEGORY', 'quiz_rel_category'); +define('TABLE_QUIZ_CATEGORY_PTEST', 'quiz_category_ptest'); // New SCORM tables define('TABLE_LP_MAIN', 'lp'); diff --git a/main/inc/lib/display.lib.php b/main/inc/lib/display.lib.php index 5f777b82b26..8724c3b731e 100755 --- a/main/inc/lib/display.lib.php +++ b/main/inc/lib/display.lib.php @@ -2583,7 +2583,7 @@ public static function getVCardUserLink($userId) * @param string $content * @param string $title * @param string $footer - * @param string $type primary|success|info|warning|danger + * @param string $type primary|success|info|warning|danger * @param string $extra * @param string $id * @param string $backgroundColor diff --git a/main/inc/lib/exercise.lib.php b/main/inc/lib/exercise.lib.php index 3d3a538ce17..86b60c02c49 100644 --- a/main/inc/lib/exercise.lib.php +++ b/main/inc/lib/exercise.lib.php @@ -55,10 +55,14 @@ public static function showQuestion( } $course = $exercise->course; + $ptest = false; + if ($exercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $ptest = true; + } // Change false to true in the following line to enable answer hinting $debug_mark_answer = $show_answers; // Reads question information - if (!$objQuestionTmp = Question::read($questionId, $course)) { + if (!$objQuestionTmp = Question::read($questionId, $course, true, $ptest)) { // Question not found return false; } @@ -472,6 +476,149 @@ function handleRadioRow(event, question_id, answer_id) { ); } + if ($answerType == QUESTION_PT_TYPE_AGREE_OR_DISAGREE) { + echo " + "; + $header = Display::tag( + 'th', + get_lang('Option'), + ['class' => 'text-center', 'style' => 'width:1px;white-space:nowrap;'] + ); + $header .= Display::tag('th', get_lang('Answer')); + + $s .= '
'.get_lang('Choice').''.get_lang('Answer').'
'; + $s .= Display::tag( + 'tr', + $header, + ['style' => 'text-align:left;'] + ); + + $optionsList = []; + $agreeOption = 0; + $disagreeOption = 0; + } + + if ($answerType == QUESTION_PT_TYPE_AGREE_SCALE) { + $dataList = ' + + + + + + '; + + $header = Display::tag( + 'th', + get_lang('Answer') + ); + $header .= Display::tag('th', get_lang('QuestionWeighting')); + + $s .= '
'; + $s .= Display::tag( + 'tr', + $header, + ['style' => 'text-align:left;'] + ); + } + + if ($answerType == QUESTION_PT_TYPE_AGREE_REORDER) { + echo " + "; + + $header = Display::tag( + 'th', + get_lang('Option'), + ['class' => 'text-center', 'style' => 'width:1px;white-space:nowrap;'] + ); + $header .= Display::tag('th', get_lang('Answer')); + + $s .= '
'; + $s .= Display::tag( + 'tr', + $header, + ['style' => 'text-align:left;'] + ); + + $optionsList = []; + $selectValue = []; + } + for ($answerId = 1; $answerId <= $nbrAnswers; $answerId++) { $answer = $objAnswerTmp->selectAnswer($answerId); $answerCorrect = $objAnswerTmp->isCorrect($answerId); @@ -484,6 +631,7 @@ function handleRadioRow(event, question_id, answer_id) { case UNIQUE_ANSWER_NO_OPTION: case UNIQUE_ANSWER_IMAGE: case READING_COMPREHENSION: + case QUESTION_PT_TYPE_CATEGORY_RANKING: $input_id = 'choice-'.$questionId.'-'.$answerId; if (isset($user_choice[0]['answer']) && $user_choice[0]['answer'] == $numAnswer) { $attributes = [ @@ -1326,6 +1474,74 @@ class="window window_left_question window{$questionId}_question"> } $matching_correct_answer++; } + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $optionsList[$numAnswer] = $answerId; + + if (isset($user_choice[0]['answer'])) { + $answerOption = explode(',', $user_choice[0]['answer']); + if ($answerOption[0] == $numAnswer) { + $agreeOption = $numAnswer; + } + if ($answerOption[1] == $numAnswer) { + $disagreeOption = $numAnswer; + } + } + + $answer = Security::remove_XSS($answer, STUDENT); + + $s .= ''; + + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + $input_id = 'choice-'.$questionId.'-'.$answerId; + $value = 3; + if (!empty($user_choice[0]['answer'])) { + $answerList = json_decode($user_choice[0]['answer'], true); + $value = $answerList[$numAnswer]; + } + + $attributes = []; + $attributes['id'] = $input_id; + $attributes['min'] = '1'; + $attributes['max'] = '5'; + $attributes['step'] = '1'; + $attributes['style'] = 'max-width:70%; margin:0 15px; display:inline;'; + $attributes['list'] = 'steplist'; + + $answer_input = Display::input( + 'range', + 'choice['.$questionId.']['.$numAnswer.']', + $value, + $attributes + ); + + $answer = Security::remove_XSS($answer, STUDENT); + + $s .= ''; + $s .= ''; + $s .= ''; + $s .= ''; + + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $optionsList[$numAnswer] = $answerId; + + if (isset($user_choice[0]['answer'])) { + $answerOption = json_decode($user_choice[0]['answer'], true); + //error_log("answerOption"); + //error_log(print_r($answerOption,1)); + for ($i = 1; $i <= 5; $i++) { + if ($answerOption[$i] == $numAnswer) { + $selectValue[$i] = $numAnswer; + } + } + } + + $answer = Security::remove_XSS($answer, STUDENT); + + $s .= ''; + break; } } @@ -1341,11 +1557,79 @@ class="window window_left_question window{$questionId}_question"> MULTIPLE_ANSWER_TRUE_FALSE, MULTIPLE_ANSWER_COMBINATION_TRUE_FALSE, MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY, + QUESTION_PT_TYPE_AGREE_OR_DISAGREE, + QUESTION_PT_TYPE_AGREE_SCALE, + QUESTION_PT_TYPE_AGREE_REORDER, ] )) { $s .= '
'.$answerId.''.$answer.'
'.$answer.''.$answer_input.'
'.$answerId.''.$answer.'
'; } + if ($answerType == QUESTION_PT_TYPE_AGREE_OR_DISAGREE) { + $s .= '
'; + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= '
'; + $s .= '
'; + + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= '
'; + $s .= '
'; + $s .= '
'; + } + + if ($answerType == QUESTION_PT_TYPE_AGREE_REORDER) { + $s .= '
'; + for ($i = 5; $i > 0; $i--) { + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= ''; + $s .= '
'; + $s .= '
'; + $s .= '
'; + } + $s .= '
'; + } + if ($answerType == DRAGGABLE) { $isVertical = $objQuestionTmp->extra == 'v'; $s .= ""; @@ -1972,7 +2256,8 @@ public static function get_exam_results_data( $userExtraFieldsToAdd = [], $useCommaAsDecimalPoint = false, $roundValues = false, - $getOnyIds = false + $getOnyIds = false, + $ptest = false ) { //@todo replace all this globals global $filter; @@ -2394,67 +2679,76 @@ public static function get_exam_results_data( $actions .= Display::return_icon('teacher.png', get_lang('Teacher')); } } - $revisedLabel = ''; - switch ($revised) { - case 0: - $actions .= "". - Display:: return_icon( - 'quiz.png', - get_lang('Qualify') - ); - $actions .= ''; - $revisedLabel = Display::label( - get_lang('NotValidated'), - 'info' + if ($ptest) { + $actions .= "". + Display:: return_icon( + 'quiz.png', + get_lang('Qualify') ); - break; - case 1: - $actions .= "". - Display:: return_icon( - 'edit.png', - get_lang('Edit'), - [], - ICON_SIZE_SMALL + $actions .= ''; + } else { + $revisedLabel = ''; + switch ($revised) { + case 0: + $actions .= "". + Display:: return_icon( + 'quiz.png', + get_lang('Qualify') + ); + $actions .= ''; + $revisedLabel = Display::label( + get_lang('NotValidated'), + 'info' ); - $actions .= ''; - $revisedLabel = Display::label( - get_lang('Validated'), - 'success' - ); - break; - case 2: //finished but not marked as such - $actions .= ''. - Display:: return_icon( - 'lock.png', - get_lang('MarkAttemptAsClosed'), + break; + case 1: + $actions .= "". + Display:: return_icon( + 'edit.png', + get_lang('Edit'), + [], + ICON_SIZE_SMALL + ); + $actions .= ''; + $revisedLabel = Display::label( + get_lang('Validated'), + 'success' + ); + break; + case 2: //finished but not marked as such + $actions .= ''. + Display:: return_icon( + 'lock.png', + get_lang('MarkAttemptAsClosed'), + [], + ICON_SIZE_SMALL + ); + $actions .= ''; + $revisedLabel = Display::label( + get_lang('Unclosed'), + 'warning' + ); + break; + case 3: //still ongoing + $actions .= Display:: return_icon( + 'clock.png', + get_lang('AttemptStillOngoingPleaseWait'), [], ICON_SIZE_SMALL ); - $actions .= ''; - $revisedLabel = Display::label( - get_lang('Unclosed'), - 'warning' - ); - break; - case 3: //still ongoing - $actions .= Display:: return_icon( - 'clock.png', - get_lang('AttemptStillOngoingPleaseWait'), - [], - ICON_SIZE_SMALL - ); - $actions .= ''; - $revisedLabel = Display::label( - get_lang('Ongoing'), - 'danger' - ); - break; + $actions .= ''; + $revisedLabel = Display::label( + get_lang('Ongoing'), + 'danger' + ); + break; + } } if ($filter == 2) { @@ -2476,23 +2770,25 @@ public static function get_exam_results_data( .Display::return_icon('info.png', $ip) .''; - $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'. - api_get_cidreq().'&'. - http_build_query([ - 'id' => $id, - 'exercise' => $exercise_id, - 'user' => $results[$i]['exe_user_id'], - ]); - $actions .= Display::url( - Display::return_icon('reload.png', get_lang('RecalculateResults')), - $recalculateUrl, - [ - 'data-exercise' => $exercise_id, - 'data-user' => $results[$i]['exe_user_id'], - 'data-id' => $id, - 'class' => 'exercise-recalculate', - ] - ); + if (!$ptest) { + $recalculateUrl = api_get_path(WEB_CODE_PATH).'exercise/recalculate.php?'. + api_get_cidreq().'&'. + http_build_query([ + 'id' => $id, + 'exercise' => $exercise_id, + 'user' => $results[$i]['exe_user_id'], + ]); + $actions .= Display::url( + Display::return_icon('reload.png', get_lang('RecalculateResults')), + $recalculateUrl, + [ + 'data-exercise' => $exercise_id, + 'data-user' => $results[$i]['exe_user_id'], + 'data-id' => $id, + 'class' => 'exercise-recalculate', + ] + ); + } $filterByUser = isset($_GET['filter_by_user']) ? (int) $_GET['filter_by_user'] : 0; $delete_link = ' 0) { + return round(($sum / $count), 1); + } + + return 0; + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $sum = 0; + $count = 0; + $userListUse = []; + while ($row = Database::fetch_array($result, 'ASSOC')) { + if (in_array($row['exe_user_id'], $userListUse)) { + // It is not the last attempt + continue; + } + $userListUse[] = $row['exe_user_id']; + $answerValue = json_decode($row['answer'], true); + foreach ($answerValue as $key => $value) { + if ($value == $answer_id) { + $sum += $key; + $count++; + break; + } + } + } + + if ($count > 0) { + return round(($sum / $count), 1); + } + + return 0; + break; case MATCHING: case MATCHING_DRAGGABLE: default: @@ -4123,6 +4512,166 @@ public static function get_number_students_answer_count( return $return; } + /** + * @param int $answer_id + * @param int $question_id + * @param int $exercise_id + * @param string $course_code + * @param int $session_id + * @param string $question_type + * @param string $correct_answer + * @param string $current_answer + * + * @return int + */ + public static function getNumberStudentsAnswerCountGraph( + $answer_id, + $question_id, + $exercise_id, + $course_code, + $session_id, + $question_type = null, + $correct_answer = null, + $current_answer = null + ) { + $track_exercises = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES); + $track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT); + $courseTable = Database::get_main_table(TABLE_MAIN_COURSE); + $courseUser = Database::get_main_table(TABLE_MAIN_COURSE_USER); + $courseUserSession = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER); + + $question_id = (int) $question_id; + $answer_id = (int) $answer_id; + $exercise_id = (int) $exercise_id; + $courseId = api_get_course_int_id($course_code); + $session_id = (int) $session_id; + $orderByCondition = ''; + + switch ($question_type) { + case QUESTION_PT_TYPE_CATEGORY_RANKING: + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + case QUESTION_PT_TYPE_AGREE_SCALE: + case QUESTION_PT_TYPE_AGREE_REORDER: + $answer_condition = ''; + $select_condition = ' DISTINCT exe_user_id, e.exe_id, answer '; + $orderByCondition = ' ORDER BY a.tms DESC'; + break; + } + + if (empty($session_id)) { + $courseCondition = " + INNER JOIN $courseUser cu + ON cu.c_id = c.id AND cu.user_id = exe_user_id"; + $courseConditionWhere = " AND relation_type <> 2 AND cu.status = ".STUDENT; + } else { + $courseCondition = " + INNER JOIN $courseUserSession cu + ON cu.c_id = a.c_id AND cu.user_id = exe_user_id"; + $courseConditionWhere = ' AND cu.status = 0 '; + } + + $sql = "SELECT $select_condition + FROM $track_exercises e + INNER JOIN $track_attempt a + ON ( + a.exe_id = e.exe_id AND + e.c_id = a.c_id AND + e.session_id = a.session_id + ) + INNER JOIN $courseTable c + ON c.id = a.c_id + $courseCondition + WHERE + exe_exo_id = $exercise_id AND + a.c_id = $courseId AND + e.session_id = $session_id AND + $answer_condition + question_id = $question_id AND + e.status = '' + $courseConditionWhere + $orderByCondition"; + + $result = Database::query($sql); + $return = 0; + if ($result) { + switch ($question_type) { + case QUESTION_PT_TYPE_CATEGORY_RANKING: + $return = 0; + $userListUse = []; + while ($row = Database::fetch_array($result, 'ASSOC')) { + if (in_array($row['exe_user_id'], $userListUse)) { + // It is not the last attempt + continue; + } + $userListUse[] = $row['exe_user_id']; + if ($row['answer'] == $answer_id) { + $return++; + } + } + + return $return; + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $return = [0, 0]; + $userListUse = []; + while ($row = Database::fetch_array($result, 'ASSOC')) { + if (in_array($row['exe_user_id'], $userListUse)) { + // It is not the last attempt + continue; + } + $userListUse[] = $row['exe_user_id']; + $answerValue = explode(',', $row['answer']); + if ($answerValue[0] == $answer_id) { + $return[0]++; + } + if ($answerValue[1] == $answer_id) { + $return[1]++; + } + } + + return $return; + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + $sum = 0; + $userListUse = []; + while ($row = Database::fetch_array($result, 'ASSOC')) { + if (in_array($row['exe_user_id'], $userListUse)) { + // It is not the last attempt + continue; + } + $userListUse[] = $row['exe_user_id']; + $answerValue = json_decode($row['answer'], true); + $sum += $answerValue[$answer_id]; + } + + return $sum; + break; + case QUESTION_PT_TYPE_AGREE_REORDER: + $sum = 0; + $userListUse = []; + while ($row = Database::fetch_array($result, 'ASSOC')) { + if (in_array($row['exe_user_id'], $userListUse)) { + // It is not the last attempt + continue; + } + $userListUse[] = $row['exe_user_id']; + $answerValue = json_decode($row['answer'], true); + foreach ($answerValue as $key => $value) { + if ($value == $answer_id) { + $sum += $key; + break; + } + } + } + + return $sum; + break; + } + } + + return $return; + } + /** * @param array $answer * @param string $user_answer @@ -4373,11 +4922,18 @@ public static function displayQuestionListByAttempt( ) { $origin = api_get_origin(); $courseCode = api_get_course_id(); + $courseId = api_get_course_int_id(); $sessionId = api_get_session_id(); // Getting attempt info $exercise_stat_info = $objExercise->get_stat_track_exercise_info_by_exe_id($exeId); + // Get type + $ptest = false; + if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $ptest = true; + } + // Getting question list $question_list = []; $studentInfo = []; @@ -4428,6 +4984,7 @@ public static function displayQuestionListByAttempt( RESULT_DISABLE_SHOW_ONLY_IN_CORRECT_ANSWER, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS, RESULT_DISABLE_SHOW_SCORE_AND_EXPECTED_ANSWERS_AND_RANKING, + RESULT_DISABLE_PT_TYPE_PTEST, ] )) { $show_results = true; @@ -4523,6 +5080,10 @@ public static function displayQuestionListByAttempt( } } + if ($objExercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + echo $objExercise->showPtestCharts($exeId, $courseId, $sessionId); + } + // Display text when test is finished #4074 and for LP #4227 $endOfMessage = $objExercise->getTextWhenFinished(); if (!empty($endOfMessage)) { @@ -4557,7 +5118,7 @@ public static function displayQuestionListByAttempt( if (!empty($question_list)) { foreach ($question_list as $questionId) { // Creates a temporary Question object - $objQuestionTmp = Question::read($questionId, $objExercise->course); + $objQuestionTmp = Question::read($questionId, $objExercise->course, true, $ptest); // This variable came from exercise_submit_modal.php ob_start(); $choice = null; @@ -4732,7 +5293,7 @@ public static function displayQuestionListByAttempt( $totalScoreText = null; $certificateBlock = ''; - if (($show_results || $show_only_score) && $showTotalScore) { + if (($show_results || $show_only_score) && $showTotalScore && !$ptest) { if ($result['answer_type'] == MULTIPLE_ANSWER_TRUE_FALSE_DEGREE_CERTAINTY) { echo '

'.get_lang('YourResults').'


'; } diff --git a/main/inc/lib/exercise_show_functions.lib.php b/main/inc/lib/exercise_show_functions.lib.php index 6cfc8289e24..1a7dff36e93 100755 --- a/main/inc/lib/exercise_show_functions.lib.php +++ b/main/inc/lib/exercise_show_functions.lib.php @@ -364,7 +364,10 @@ public static function display_unique_or_multiple_answer( break; } - $icon = in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION]) ? 'radio' : 'checkbox'; + $icon = in_array( + $answerType, + [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION, QUESTION_PT_TYPE_CATEGORY_RANKING] + ) ? 'radio' : 'checkbox'; $icon .= $studentChoice ? '_on' : '_off'; $icon .= '.png'; $iconAnswer = in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION]) ? 'radio' : 'checkbox'; @@ -424,6 +427,10 @@ public static function display_unique_or_multiple_answer( $showComment = true; } + if ($exercise->selectPtType() == EXERCISE_PT_TYPE_PTEST) { + $showComment = false; + } + if ($showComment) { echo ''; $color = 'black'; @@ -445,6 +452,86 @@ public static function display_unique_or_multiple_answer( echo ''; } + /** + * Display the answers to a ptest question. + * + * @param Exercise $exercise + * @param int $answerType Answer type + * @param int $studentChoice Student choice + * @param string $answer Textual answer + * @param string $answerComment Comment on answer + * @param bool $export + */ + public static function display_ptest_answer( + $exercise, + $answerType, + $studentChoice, + $answer, + $export = false + ) { + if ($export) { + $answer = strip_tags_blacklist($answer, ['title', 'head']); + // Fix answers that contains this tags + $tags = [ + '', + '', + '', + '', + ]; + $answer = str_replace($tags, '', $answer); + } + + $studentChoiceInt = (int) $studentChoice; + + switch ($answerType) { + case QUESTION_PT_TYPE_CATEGORY_RANKING: + $iconOff = ''; + $iconOn = ''; + $icon .= $studentChoice ? $iconOn : $iconOff; + break; + case QUESTION_PT_TYPE_AGREE_OR_DISAGREE: + $icon = ''; + if ($studentChoice == ANSWER_AGREE) { + $icon = ''; + } + + if ($studentChoice == ANSWER_DISAGREE) { + $icon = ''; + } + break; + case QUESTION_PT_TYPE_AGREE_SCALE: + case QUESTION_PT_TYPE_AGREE_REORDER: + $icon = ''; + $color = 'text-primary'; + switch ($studentChoiceInt) { + case 1: + case 2: + $color = 'text-danger'; + break; + case 3: + $color = 'text-warning'; + break; + case 4: + case 5: + $color = 'text-success'; + break; + } + for ($i = 0; $i < 5; $i++) { + if ($i < $studentChoiceInt) { + $icon .= ' '; + } else { + $icon .= ' '; + } + } + break; + } + + echo ''; + echo ''.$icon.''; + echo ''.$answer.''; + echo ''; + } + /** * Display the answers to a multiple choice question. * diff --git a/main/inc/lib/sessionmanager.lib.php b/main/inc/lib/sessionmanager.lib.php index 3bd64a7f6bb..6aa2ee40d08 100755 --- a/main/inc/lib/sessionmanager.lib.php +++ b/main/inc/lib/sessionmanager.lib.php @@ -129,27 +129,27 @@ public static function fetch($id) * * @author Carlos Vargas , from existing code * - * @param string $name - * @param string $startDate (YYYY-MM-DD hh:mm:ss) - * @param string $endDate (YYYY-MM-DD hh:mm:ss) - * @param string $displayStartDate (YYYY-MM-DD hh:mm:ss) - * @param string $displayEndDate (YYYY-MM-DD hh:mm:ss) - * @param string $coachStartDate (YYYY-MM-DD hh:mm:ss) - * @param string $coachEndDate (YYYY-MM-DD hh:mm:ss) - * @param mixed $coachId If int, this is the session coach id, - * if string, the coach ID will be looked for from the user table - * @param int $sessionCategoryId ID of the session category in which this session is registered - * @param int $visibility Visibility after end date (0 = read-only, 1 = invisible, 2 = accessible) - * @param bool $fixSessionNameIfExists - * @param string $duration - * @param string $description Optional. The session description - * @param int $showDescription Optional. Whether show the session description - * @param array $extraFields - * @param int $sessionAdminId Optional. If this sessions was created by a session admin, assign it to him - * @param bool $sendSubscriptionNotification Optional. - * Whether send a mail notification to users being subscribed - * @param int $accessUrlId Optional. - * @param int $status + * @param string $name + * @param string $startDate (YYYY-MM-DD hh:mm:ss) + * @param string $endDate (YYYY-MM-DD hh:mm:ss) + * @param string $displayStartDate (YYYY-MM-DD hh:mm:ss) + * @param string $displayEndDate (YYYY-MM-DD hh:mm:ss) + * @param string $coachStartDate (YYYY-MM-DD hh:mm:ss) + * @param string $coachEndDate (YYYY-MM-DD hh:mm:ss) + * @param mixed $coachId If int, this is the session coach id, + * if string, the coach ID will be looked for from the user table + * @param int $sessionCategoryId ID of the session category in which this session is registered + * @param int $visibility Visibility after end date (0 = read-only, 1 = invisible, 2 = accessible) + * @param bool $fixSessionNameIfExists + * @param string $duration + * @param string $description Optional. The session description + * @param int $showDescription Optional. Whether show the session description + * @param array $extraFields + * @param int $sessionAdminId Optional. If this sessions was created by a session admin, assign it to him + * @param bool $sendSubscriptionNotification Optional. + * Whether send a mail notification to users being subscribed + * @param int $accessUrlId Optional. + * @param int $status * * @return mixed Session ID on success, error message otherwise * @@ -9355,6 +9355,27 @@ public static function convertSessionDateToString($startDate, $endDate, $showTim return $result; } + public static function getStatusList() + { + return [ + self::STATUS_PLANNED => get_lang('Planned'), + self::STATUS_PROGRESS => get_lang('InProgress'), + self::STATUS_FINISHED => get_lang('Finished'), + self::STATUS_CANCELLED => get_lang('Cancelled'), + ]; + } + + public static function getStatusLabel($status) + { + $list = self::getStatusList(); + + if (!isset($list[$status])) { + return get_lang('NoStatus'); + } + + return $list[$status]; + } + /** * @param int $id * @@ -9492,26 +9513,4 @@ private static function compareByCourse($listA, $listB) return -1; } } - - public static function getStatusList() - { - return [ - self::STATUS_PLANNED => get_lang('Planned'), - self::STATUS_PROGRESS => get_lang('InProgress'), - self::STATUS_FINISHED => get_lang('Finished'), - self::STATUS_CANCELLED => get_lang('Cancelled'), - ]; - } - - public static function getStatusLabel($status) - { - $list = self::getStatusList(); - - if (!isset($list[$status])) { - - return get_lang('NoStatus'); - } - - return $list[$status]; - } } diff --git a/main/inc/lib/usergroup.lib.php b/main/inc/lib/usergroup.lib.php index 23d0cde9d94..ff26430907f 100755 --- a/main/inc/lib/usergroup.lib.php +++ b/main/inc/lib/usergroup.lib.php @@ -125,7 +125,7 @@ public function getUserGroupUsers($id, $getCount = false, $start = 0, $limit = 0 $limitCondition = " LIMIT $start, $limit"; } - $sql.= $limitCondition; + $sql .= $limitCondition; $result = Database::query($sql); diff --git a/main/inc/lib/webservices/WebService.class.php b/main/inc/lib/webservices/WebService.class.php index 46727e2e3d0..5acab5e71f5 100644 --- a/main/inc/lib/webservices/WebService.class.php +++ b/main/inc/lib/webservices/WebService.class.php @@ -86,12 +86,13 @@ public static function isValidUser($username, $password) if (empty($username) || empty($password)) { return false; } - + error_log("89"); $user = UserManager::getManager()->findUserByUsername($username); - + error_log("90"); if (!$user) { return false; } + error_log("95"); return UserManager::isPasswordValid( $user->getPassword(), diff --git a/main/install/configuration.dist.php b/main/install/configuration.dist.php index 4dec20f1107..f5871416083 100755 --- a/main/install/configuration.dist.php +++ b/main/install/configuration.dist.php @@ -952,8 +952,8 @@ // menu // $_configuration['disable_gdpr'] = true; -// GDPR requires users to be informed of the Data Protection Officer name and -// contact point. These can only be defined here for now, but will be moved to +// GDPR requires users to be informed of the Data Protection Officer name and +// contact point. These can only be defined here for now, but will be moved to // web settings in the future. // Name of the person or organization that is responsible for the treatment of // personal info @@ -1349,6 +1349,19 @@ // LP view menu location. Options: "left" or "right" // $_configuration['lp_menu_location'] = 'left'; +// Show personality test +// CREATE TABLE c_quiz_category_ptest ( +// id INT AUTO_INCREMENT NOT NULL, +// c_id INT NOT NULL, +// exercise_id INT NOT NULL, +// title VARCHAR(255) NOT NULL, +// description LONGTEXT, +// session_id INT NOT NULL, +// PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB; +// ALTER TABLE c_quiz ADD COLUMN pt_type INT DEFAULT 0 NOT NULL; +// ALTER TABLE c_quiz_answer ADD COLUMN ptest_category INT DEFAULT 0 NOT NULL; +// $_configuration['show_ptest_quiz'] = true; + // Show notification events /*CREATE TABLE IF NOT EXISTS notification_event ( id INT unsigned NOT NULL auto_increment PRIMARY KEY, @@ -1378,7 +1391,7 @@ // Catalog search settings visibility //$_configuration['catalog_settings'] = ['sessions' => ['by_title' => true, 'by_date' => true, 'by_tag' => true, 'show_session_info' => true, 'show_session_date' => true]]; -// Enable learning paths with only one SCO item to use the score returned by +// Enable learning paths with only one SCO item to use the score returned by // the SCO as an indicator of progress of the whole learning path // $_configuration['lp_score_as_progress_enable'] = false; @@ -1388,7 +1401,6 @@ // In Scorm comunication use the username instead of the user_id //$_configuration['scorm_api_username_as_student_id'] = false; - // KEEP THIS AT THE END // -------- Custom DB changes // Add user activation by confirmation email diff --git a/main/lang/english/trad4all.inc.php b/main/lang/english/trad4all.inc.php index 62575451beb..3bed62de1bf 100644 --- a/main/lang/english/trad4all.inc.php +++ b/main/lang/english/trad4all.inc.php @@ -8446,6 +8446,33 @@ $CompilatioNonToAnalyse = "Your selection contains no jobs to analyze. Only jobs managed by Compilatio and not already scanned can be sent."; $CompilatioComunicationAjaxImpossible = "AJAX communication with the Compilatio server impossible. Please retry later."; $UserClassExplanation = "Information: The list of classes below contains the list of classes you have already registered in your course. If this list is empty, use the + green above to add classes."; +$PtestCategoryRanking = "Choose a word (or sentence) from a set of possible answers"; +$PtestAgreeOrDisagree = "Select the most agree and disagree options"; +$PtestAgreeScale = "Set a value for each category"; +$PtestAgreeReorder = "Re-order words by most agree to least agree"; +$PtestType = "Type of exercise Personality Test"; +$PtCategories = "Define personal characteristics for used in questions"; +$NoDefinedCategories = "No defined personal characteristics for questions"; +$PtestCategory = "Personal characteristics"; +$PtestCategoryList = "Personal characteristics list"; +$PtestCategoryName = "Title"; +$PtestCategoryPonderation = "Points"; +$PtestCategoryDescription = "Description"; +$AddPTestFeature = "Add personal characteristics"; +$ModifyPTestFeature = "Modify personal characteristics"; +$BarGraphRepresentation = "Bar graph representation"; +$RadarPlotRepresentation = "Radar plot representation"; +$MostAgree = "Most Agree"; +$LeastAgree = "Least Agree"; +$AgreeScale5 = "Top"; +$AgreeScale4 = "2nd highest"; +$AgreeScale3 = "Middle"; +$AgreeScale2 = "2nd lowest"; +$AgreeScale1 = "Bottom"; +$AddPT = "New personality test"; +$ExerciseGraph = "Attemps graph"; +$PtestCategoryPosition = "Position"; +$PtestCategoryColor = "Colour"; $InsertTwoNames = "Insert your two names"; $AddRightLogo = "Add right logo"; $LearnpathUseScoreAsProgress = "Use score as progress"; diff --git a/main/lang/spanish/trad4all.inc.php b/main/lang/spanish/trad4all.inc.php index 563ae7ef7fc..b9c4193e2e9 100644 --- a/main/lang/spanish/trad4all.inc.php +++ b/main/lang/spanish/trad4all.inc.php @@ -8459,4 +8459,31 @@ $EnterYourNewPassword = "Introduzca su nueva contraseña aquí."; $RepeatYourNewPassword = "Introduzca su nueva contraseña una vez más, para reducir la probabilidad de errores."; $UserClassExplanation = "Información: La lista de clases a continuación contiene la lista de clases que ya ha registrado en su clase. Si esta lista está vacía, use el + verde arriba para agregar clases."; +$PtestCategoryRanking = "Elige una palabra (o sentencia) de un conjunto de posibles respuestas"; +$PtestAgreeOrDisagree = "Seleccionar las opciones más de acuerdo y en desacuerdo"; +$PtestAgreeScale = "Establecer un valor para cada categoría"; +$PtestAgreeReorder = "Reordenar sentencias por más de acuerdo a menos de acuerdo"; +$PtestType = "Tipo de ejercicio Test de Personalidad"; +$PtCategories = "Definir característica personal para usar en las preguntas"; +$NoDefinedCategories = "Sin características personales definidas para las preguntas"; +$PtestCategory = "Características personales"; +$PtestCategoryList = "Listado de características personales"; +$PtestCategoryName = "Nombre"; +$PtestCategoryPonderation = "Puntos"; +$PtestCategoryDescription = "Descripción"; +$AddPTestFeature = "Añadir característica personal"; +$ModifyPTestFeature = "Modificar característica personal"; +$BarGraphRepresentation = "Representación en gráfico de barras"; +$RadarPlotRepresentation = "Representación en gráfico de Radar"; +$MostAgree = "Más de acuerdo"; +$LeastAgree = "Menos de acuerdo"; +$AgreeScale5 = "Superior"; +$AgreeScale4 = "2º superior"; +$AgreeScale3 = "Intermedio"; +$AgreeScale2 = "2º inferior"; +$AgreeScale1 = "Inferior"; +$AddPT = "Add personality test"; +$ExerciseGraph = "Gráficos de los intentos"; +$PtestCategoryPosition = "Posición"; +$PtestCategoryColor = "Color"; ?> \ No newline at end of file diff --git a/main/lp/learnpath.class.php b/main/lp/learnpath.class.php index 9d642571feb..ae6de20da45 100755 --- a/main/lp/learnpath.class.php +++ b/main/lp/learnpath.class.php @@ -2571,6 +2571,7 @@ public function get_progress_bar_text($mode = '', $add = 0) $percentage = $score; $text = '/'.$maxScore; } + return [$percentage, $text]; } } @@ -13671,6 +13672,48 @@ public function getItemsForForm($addParentCondition = false) return $arrLP; } + /** + * Gets whether this SCORM learning path has been marked to use the score + * as progress. Takes into account whether the learnpath matches (SCORM + * content + less than 2 items). + * + * @return bool True if the score should be used as progress, false otherwise + */ + public function getUseScoreAsProgress() + { + // If not a SCORM, we don't care about the setting + if ($this->get_type() != 2) { + return false; + } + // If more than one step in the SCORM, we don't care about the setting + if ($this->get_total_items_count() > 1) { + return false; + } + $extraFieldValue = new ExtraFieldValue('lp'); + $doUseScore = false; + $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress'); + if (!empty($useScore) && isset($useScore['value'])) { + $doUseScore = $useScore['value']; + } + + return $doUseScore; + } + + /** + * Get the user identifier (user_id or username + * Depends on scorm_api_username_as_student_id in app/config/configuration.php. + * + * @return string + */ + public function getUserIdentifierForExternalServices() + { + if (api_get_configuration_value('scorm_api_username_as_student_id')) { + return api_get_user_info(api_get_user_id())['username']; + } else { + return api_get_user_id(); + } + } + /** * Get the depth level of LP item. * @@ -13775,43 +13818,4 @@ private function getSavedFinalItem() return ''; } - /** - * Gets whether this SCORM learning path has been marked to use the score - * as progress. Takes into account whether the learnpath matches (SCORM - * content + less than 2 items). - * @return bool True if the score should be used as progress, false otherwise - */ - public function getUseScoreAsProgress() - { - // If not a SCORM, we don't care about the setting - if ($this->get_type() != 2) { - return false; - } - // If more than one step in the SCORM, we don't care about the setting - if ($this->get_total_items_count() > 1) { - return false; - } - $extraFieldValue = new ExtraFieldValue('lp'); - $doUseScore = false; - $useScore = $extraFieldValue->get_values_by_handler_and_field_variable($this->get_id(), 'use_score_as_progress'); - if (!empty($useScore) && isset($useScore['value'])) { - $doUseScore = $useScore['value']; - } - - return $doUseScore; - } - /** - * Get the user identifier (user_id or username - * Depends on scorm_api_username_as_student_id in app/config/configuration.php - * - * @return string - */ - public function getUserIdentifierForExternalServices() - { - if (api_get_configuration_value('scorm_api_username_as_student_id')) { - return api_get_user_info(api_get_user_id())['username']; - } else { - return api_get_user_id(); - } - } } diff --git a/main/lp/lp_ajax_switch_item_toc.php b/main/lp/lp_ajax_switch_item_toc.php index 65b18fc313d..e1159daccf1 100755 --- a/main/lp/lp_ajax_switch_item_toc.php +++ b/main/lp/lp_ajax_switch_item_toc.php @@ -168,8 +168,6 @@ function switch_item_toc($lpId, $userId, $viewId, $currentItem, $nextItem) $return .= "update_progress_bar('$completedItems','$totalItems','$progressMode');"; } - - $myLP->set_error_msg(''); $myLP->prerequisites_match(); // Check the prerequisites are all complete. if ($debug > 1) { diff --git a/main/lp/lp_edit.php b/main/lp/lp_edit.php index 64f9167680c..43cfb9e2600 100755 --- a/main/lp/lp_edit.php +++ b/main/lp/lp_edit.php @@ -273,7 +273,6 @@ function activate_end_date() { $skillList = Skill::addSkillsToForm($form, ITEM_TYPE_LEARNPATH, $lpId); - // Submit button $form->addButtonSave(get_lang('SaveLPSettings')); diff --git a/main/lp/scorm_api.php b/main/lp/scorm_api.php index 372784b82e0..a2f763cc2df 100755 --- a/main/lp/scorm_api.php +++ b/main/lp/scorm_api.php @@ -54,7 +54,7 @@ ?>var scorm_logs=scorm_debug) or (!api_is_course_admin() && !api_is_platform_admin())) ? '0' : '3'; ?>; //debug log level for SCORM. 0 = none, 1=light, 2=a lot, 3=all - displays logs in log frame var lms_logs = 0; //debug log level for LMS actions. 0=none, 1=light, 2=a lot, 3=all -var score_as_progress = getUseScoreAsProgress())? 'false':'true'); ?>; +var score_as_progress = getUseScoreAsProgress()) ? 'false' : 'true'; ?>; // API Object initialization (eases access later on) function APIobject() { diff --git a/main/session/resume_session.php b/main/session/resume_session.php index a5b9dcb8821..8e4b1646076 100644 --- a/main/session/resume_session.php +++ b/main/session/resume_session.php @@ -12,7 +12,6 @@ /** * @author Bart Mollet, Julio Montoya lot of fixes */ - $cidReset = true; require_once __DIR__.'/../inc/global.inc.php'; diff --git a/main/session/session_list.php b/main/session/session_list.php index 8ea89394f15..b6169cc5304 100644 --- a/main/session/session_list.php +++ b/main/session/session_list.php @@ -293,7 +293,8 @@ function show_cols(grid, added_cols) { // Sortable rows grid.jqGrid('sortableRows', options); - + grid.jqGrid('navGrid','#sessions_pager', {edit:false,add:false,del:false}, diff --git a/main/session/session_list_simple.php b/main/session/session_list_simple.php index 0e63d8152d3..793445071c8 100644 --- a/main/session/session_list_simple.php +++ b/main/session/session_list_simple.php @@ -249,7 +249,7 @@ function show_cols(grid, added_cols) { original_cols = grid.jqGrid('getGridParam', 'colModel'); + ?> options = { update: function (e, ui) { var rowNum = jQuery("#sessions").getGridParam('rowNum'); diff --git a/main/tracking/lp_report.php b/main/tracking/lp_report.php index 14b4ca75dbc..bbf1a2a8e87 100644 --- a/main/tracking/lp_report.php +++ b/main/tracking/lp_report.php @@ -25,7 +25,6 @@ }); '; - $lps = learnpath::getLpList($courseId); Session::write('lps', $lps); @@ -274,7 +273,6 @@ function getCount() } return $count; - } /** diff --git a/src/Chamilo/CoreBundle/Entity/Repository/SequenceResourceRepository.php b/src/Chamilo/CoreBundle/Entity/Repository/SequenceResourceRepository.php index 4217efaea60..627842ddc18 100644 --- a/src/Chamilo/CoreBundle/Entity/Repository/SequenceResourceRepository.php +++ b/src/Chamilo/CoreBundle/Entity/Repository/SequenceResourceRepository.php @@ -10,7 +10,7 @@ use Fhaculty\Graph\Vertex; /** - * Class SequenceResourceRepository + * Class SequenceResourceRepository. */ class SequenceResourceRepository extends EntityRepository { @@ -407,4 +407,31 @@ public function checkSequenceAreCompleted(array $sequences) return false; } + + /** + * Get sessions from vertices. + * + * @param Vertices $verticesEdges The vertices + * + * @return array + */ + protected function findSessionFromVerticesEdges(Vertices $verticesEdges) + { + $sessionVertices = []; + foreach ($verticesEdges as $supVertex) { + $vertexId = $supVertex->getId(); + $session = $this->getEntityManager()->getReference( + 'ChamiloCoreBundle:Session', + $vertexId + ); + + if (empty($session)) { + continue; + } + + $sessionVertices[$vertexId] = $session; + } + + return $sessionVertices; + } } diff --git a/src/Chamilo/CourseBundle/Entity/CQuizAnswer.php b/src/Chamilo/CourseBundle/Entity/CQuizAnswer.php index 2cc83deb4a0..9281ea521df 100644 --- a/src/Chamilo/CourseBundle/Entity/CQuizAnswer.php +++ b/src/Chamilo/CourseBundle/Entity/CQuizAnswer.php @@ -112,6 +112,13 @@ class CQuizAnswer */ protected $destination; + /** + * @var string + * + * @ORM\Column(name="ptest_category", type="text", nullable=true) + */ + protected $ptestCategory; + /** * @var string * @@ -129,6 +136,7 @@ public function __construct() $this->hotspotCoordinates = null; $this->hotspotType = null; $this->destination = null; + $this->ptestCategory = null; $this->answerCode = null; } @@ -372,6 +380,30 @@ public function getDestination() return $this->destination; } + /** + * Set personality test category. + * + * @param string $ptestCategory + * + * @return CQuizAnswer + */ + public function setPtestCategory($ptestCategory) + { + $this->ptestCategory = empty($ptestCategory) ? null : $ptestCategory; + + return $this; + } + + /** + * Get ptestCategory. + * + * @return string + */ + public function getPtestCategory() + { + return $this->ptestCategory; + } + /** * Set answerCode. *