id = 0; $this->exercise = ''; $this->description = ''; $this->sound = ''; $this->type = ALL_ON_ONE_PAGE; $this->random = 0; $this->random_answers = 0; $this->active = 1; $this->questionList = array(); $this->timeLimit = 0; $this->end_time = '0000-00-00 00:00:00'; $this->start_time = '0000-00-00 00:00:00'; $this->results_disabled = 1; $this->expired_time = '0000-00-00 00:00:00'; $this->propagate_neg = 0; $this->review_answers = false; $this->randomByCat = 0; // $this->text_when_finished = ""; // $this->display_category_name = 0; $this->pass_percentage = null; if (!empty($course_id)) { $course_info = api_get_course_info_by_id($course_id); } else { $course_info = api_get_course_info(); } $this->course_id = $course_info['real_id']; $this->course = $course_info; $this->sessionId = api_get_session_id(); } /** * Reads exercise information from the data base * * @author Olivier Brouckaert * @param integer $id - exercise Id * * @return boolean - true if exercise exists, otherwise false */ public function read($id) { global $_configuration; $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); $table_lp_item = Database::get_course_table(TABLE_LP_ITEM); $id = intval($id); if (empty($this->course_id)) { return false; } $sql = "SELECT * FROM $TBL_EXERCISES WHERE c_id = ".$this->course_id." AND id = ".$id; $result = Database::query($sql); // if the exercise has been found if ($object = Database::fetch_object($result)) { $this->id = $id; $this->exercise = $object->title; $this->name = $object->title; $this->title = $object->title; $this->description = $object->description; $this->sound = $object->sound; $this->type = $object->type; if (empty($this->type)) { $this->type = ONE_PER_PAGE; } $this->random = $object->random; $this->random_answers = $object->random_answers; $this->active = $object->active; $this->results_disabled = $object->results_disabled; $this->attempts = $object->max_attempt; $this->feedback_type = $object->feedback_type; $this->propagate_neg = $object->propagate_neg; $this->randomByCat = $object->random_by_category; $this->text_when_finished = $object->text_when_finished; $this->display_category_name = $object->display_category_name; $this->pass_percentage = $object->pass_percentage; $this->sessionId = $object->session_id; $this->is_gradebook_locked = api_resource_is_locked_by_gradebook($id, LINK_EXERCISE); $this->review_answers = (isset($object->review_answers) && $object->review_answers == 1) ? true : false; $sql = "SELECT lp_id, max_score FROM $table_lp_item WHERE c_id = {$this->course_id} AND item_type = '".TOOL_QUIZ."' AND path = '".$id."'"; $result = Database::query($sql); if (Database::num_rows($result) > 0) { $this->exercise_was_added_in_lp = true; $this->lpList = Database::store_result($result, 'ASSOC'); } $this->force_edit_exercise_in_lp = isset($_configuration['force_edit_exercise_in_lp']) ? $_configuration['force_edit_exercise_in_lp'] : false; if ($this->exercise_was_added_in_lp) { $this->edit_exercise_in_lp = $this->force_edit_exercise_in_lp == true; } else { $this->edit_exercise_in_lp = true; } if ($object->end_time != '0000-00-00 00:00:00') { $this->end_time = $object->end_time; } if ($object->start_time != '0000-00-00 00:00:00') { $this->start_time = $object->start_time; } //control time $this->expired_time = $object->expired_time; //Checking if question_order is correctly set $this->questionList = $this->selectQuestionList(true); //overload questions list with recorded questions list //load questions only for exercises of type 'one question per page' //this is needed only is there is no questions /* // @todo not sure were in the code this is used somebody mess with the exercise tool // @todo don't know who add that config and why $_configuration['live_exercise_tracking'] global $_configuration, $questionList; if ($this->type == ONE_PER_PAGE && $_SERVER['REQUEST_METHOD'] != 'POST' && defined('QUESTION_LIST_ALREADY_LOGGED') && isset($_configuration['live_exercise_tracking']) && $_configuration['live_exercise_tracking']) { $this->questionList = $questionList; }*/ return true; } return false; } /** * @return string */ public function getCutTitle() { return cut($this->exercise, EXERCISE_MAX_NAME_SIZE); } /** * returns the exercise ID * * @author Olivier Brouckaert * @return int - exercise ID */ public function selectId() { return $this->id; } /** * returns the exercise title * * @author Olivier Brouckaert * @return string - exercise title */ public function selectTitle() { return $this->exercise; } /** * returns the number of attempts setted * * @return int - exercise attempts */ public function selectAttempts() { return $this->attempts; } /** returns the number of FeedbackType * * 0=>Feedback , 1=>DirectFeedback, 2=>NoFeedback * @return int - exercise attempts */ public function selectFeedbackType() { return $this->feedback_type; } /** * returns the time limit */ public function selectTimeLimit() { return $this->timeLimit; } /** * returns the exercise description * * @author Olivier Brouckaert * @return string - exercise description */ public function selectDescription() { return $this->description; } /** * returns the exercise sound file * * @author Olivier Brouckaert * @return string - exercise description */ public function selectSound() { return $this->sound; } /** * returns the exercise type * * @author Olivier Brouckaert * @return integer - exercise type */ public function selectType() { return $this->type; } /** * @author hubert borderiou 30-11-11 * @return integer : do we display the question category name for students */ public function selectDisplayCategoryName() { return $this->display_category_name; } /** * @return int */ public function selectPassPercentage() { return $this->pass_percentage; } /** * * Modify object to update the switch display_category_name * @author hubert borderiou 30-11-11 * @param int $in_txt is an integer 0 or 1 */ public function updateDisplayCategoryName($in_txt) { $this->display_category_name = $in_txt; } /** * @author hubert borderiou 28-11-11 * @return string html text : the text to display ay the end of the test. */ public function selectTextWhenFinished() { return $this->text_when_finished; } /** * @author hubert borderiou 28-11-11 * @return string html text : update the text to display ay the end of the test. */ public function updateTextWhenFinished($in_txt) { $this->text_when_finished = $in_txt; } /** * return 1 or 2 if randomByCat * @author hubert borderiou * @return integer - quiz random by category */ public function selectRandomByCat() { return $this->randomByCat; } /** * return 0 if no random by cat * return 1 if random by cat, categories shuffled * return 2 if random by cat, categories sorted by alphabetic order * @author hubert borderiou * @return integer - quiz random by category */ public function isRandomByCat() { $res = 0; if ($this->randomByCat == 1) { $res = 1; } else if ($this->randomByCat == 2) { $res = 2; } return $res; } /** * return nothing * update randomByCat value for object * @author hubert borderiou */ public function updateRandomByCat($in_randombycat) { if ($in_randombycat == 1) { $this->randomByCat = 1; } else if ($in_randombycat == 2) { $this->randomByCat = 2; } else { $this->randomByCat = 0; } } /** * Tells if questions are selected randomly, and if so returns the draws * * @author Carlos Vargas * @return integer - results disabled exercise */ public function selectResultsDisabled() { return $this->results_disabled; } /** * tells if questions are selected randomly, and if so returns the draws * * @author Olivier Brouckaert * @return integer - 0 if not random, otherwise the draws */ public function isRandom() { if($this->random > 0 || $this->random == -1) { return true; } else { return false; } } /** * returns random answers status. * * @author Juan Carlos Rana */ public function selectRandomAnswers() { return $this->random_answers; } /** * Same as isRandom() but has a name applied to values different than 0 or 1 */ public function getShuffle() { return $this->random; } /** * returns the exercise status (1 = enabled ; 0 = disabled) * * @author Olivier Brouckaert * @return boolean - true if enabled, otherwise false */ public function selectStatus() { return $this->active; } /** * returns the array with the question ID list * * @author Olivier Brouckaert * @return array - question ID list */ public function selectQuestionList($from_db = false) { if ($from_db && !empty($this->id)) { $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); $sql = "SELECT DISTINCT e.question_order FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id = q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.") WHERE e.exercice_id = ".intval($this->id).""; $result = Database::query($sql); $count_question_orders = Database::num_rows($result); $sql = "SELECT e.question_id, e.question_order FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id= q.id AND e.c_id = ".$this->course_id." AND q.c_id = ".$this->course_id.") WHERE e.exercice_id = ".intval($this->id)." ORDER BY question_order"; $result = Database::query($sql); // fills the array with the question ID for this exercise // the key of the array is the question position $temp_question_list = array(); $counter = 1; $question_list = array(); while ($new_object = Database::fetch_object($result)) { $question_list[$new_object->question_order]= $new_object->question_id; $temp_question_list[$counter] = $new_object->question_id; $counter++; } if (!empty($temp_question_list)) { if (count($temp_question_list) != $count_question_orders) { $question_list = $temp_question_list; } } return $question_list; } return $this->questionList; } /** * returns the number of questions in this exercise * * @author Olivier Brouckaert * @return integer - number of questions */ public function selectNbrQuestions() { return sizeof($this->questionList); } /** * @return int */ public function selectPropagateNeg() { return $this->propagate_neg; } /** * Selects questions randomly in the question list * * @author Olivier Brouckaert * @author Hubert Borderiou 15 nov 2011 * @return array - if the exercise is not set to take questions randomly, returns the question list * without randomizing, otherwise, returns the list with questions selected randomly */ public function selectRandomList() { $nbQuestions = $this->selectNbrQuestions(); $temp_list = $this->questionList; //Not a random exercise, or if there are not at least 2 questions if($this->random == 0 || $nbQuestions < 2) { return $this->questionList; } if ($nbQuestions != 0) { shuffle($temp_list); $my_random_list = array_combine(range(1,$nbQuestions),$temp_list); $my_question_list = array(); // $this->random == -1 if random with all questions if ($this->random > 0) { $i = 0; foreach ($my_random_list as $item) { if ($i < $this->random) { $my_question_list[$i] = $item; } else { break; } $i++; } } else { $my_question_list = $my_random_list; } return $my_question_list; } } /** * returns 'true' if the question ID is in the question list * * @author Olivier Brouckaert * @param integer $questionId - question ID * @return boolean - true if in the list, otherwise false */ public function isInList($questionId) { if (is_array($this->questionList)) return in_array($questionId,$this->questionList); else return false; } /** * changes the exercise title * * @author Olivier Brouckaert * @param string $title - exercise title */ public function updateTitle($title) { $this->exercise=$title; } /** * changes the exercise max attempts * * @param int $attempts - exercise max attempts */ public function updateAttempts($attempts) { $this->attempts=$attempts; } /** * changes the exercise feedback type * * @param int $feedback_type */ public function updateFeedbackType($feedback_type) { $this->feedback_type=$feedback_type; } /** * changes the exercise description * * @author Olivier Brouckaert * @param string $description - exercise description */ public function updateDescription($description) { $this->description=$description; } /** * changes the exercise expired_time * * @author Isaac flores * @param int $expired_time The expired time of the quiz */ public function updateExpiredTime($expired_time) { $this->expired_time = $expired_time; } /** * @param $value */ public function updatePropagateNegative($value) { $this->propagate_neg = $value; } /** * @param $value */ public function updateReviewAnswers($value) { $this->review_answers = isset($value) && $value ? true : false; } /** * @param $value */ public function updatePassPercentage($value) { $this->pass_percentage = $value; } /** * changes the exercise sound file * * @author Olivier Brouckaert * @param string $sound - exercise sound file * @param string $delete - ask to delete the file */ public function updateSound($sound,$delete) { global $audioPath, $documentPath; $TBL_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT); if ($sound['size'] && (strstr($sound['type'],'audio') || strstr($sound['type'],'video'))) { $this->sound=$sound['name']; if (@move_uploaded_file($sound['tmp_name'],$audioPath.'/'.$this->sound)) { $query = "SELECT 1 FROM $TBL_DOCUMENT WHERE c_id = ".$this->course_id." AND path='".str_replace($documentPath,'',$audioPath).'/'.$this->sound."'"; $result=Database::query($query); if (!Database::num_rows($result)) { $id = add_document( $this->course, str_replace($documentPath,'',$audioPath).'/'.$this->sound, 'file', $sound['size'], $sound['name'] ); api_item_property_update( $this->course, TOOL_DOCUMENT, $id, 'DocumentAdded', api_get_user_id() ); item_property_update_on_folder( $this->course, str_replace($documentPath, '', $audioPath), api_get_user_id() ); } } } elseif($delete && is_file($audioPath.'/'.$this->sound)) { $this->sound=''; } } /** * changes the exercise type * * @author Olivier Brouckaert * @param integer $type - exercise type */ public function updateType($type) { $this->type=$type; } /** * sets to 0 if questions are not selected randomly * if questions are selected randomly, sets the draws * * @author Olivier Brouckaert * @param integer $random - 0 if not random, otherwise the draws */ public function setRandom($random) { /*if ($random == 'all') { $random = $this->selectNbrQuestions(); }*/ $this->random = $random; } /** * sets to 0 if answers are not selected randomly * if answers are selected randomly * @author Juan Carlos Rana * @param integer $random_answers - random answers */ public function updateRandomAnswers($random_answers) { $this->random_answers = $random_answers; } /** * enables the exercise * * @author Olivier Brouckaert */ public function enable() { $this->active=1; } /** * disables the exercise * * @author Olivier Brouckaert */ public function disable() { $this->active=0; } /** * Set disable results */ public function disable_results() { $this->results_disabled = true; } /** * Enable results */ public function enable_results() { $this->results_disabled = false; } /** * @param int $results_disabled */ public function updateResultsDisabled($results_disabled) { $this->results_disabled = intval($results_disabled); } /** * updates the exercise in the data base * * @author Olivier Brouckaert */ public function save($type_e = '') { $_course = $this->course; $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); $id = $this->id; $exercise = $this->exercise; $description = $this->description; $sound = $this->sound; $type = $this->type; $attempts = isset($this->attempts) ? $this->attempts : 0; $feedback_type = isset($this->feedback_type) ? $this->feedback_type : 0; $random = $this->random; $random_answers = $this->random_answers; $active = $this->active; $propagate_neg = $this->propagate_neg; $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0; $randomByCat = $this->randomByCat; $text_when_finished = $this->text_when_finished; $display_category_name = intval($this->display_category_name); $pass_percentage = intval($this->pass_percentage); $session_id = $this->sessionId; //If direct we do not show results if ($feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) { $results_disabled = 0; } else { $results_disabled = intval($this->results_disabled); } $expired_time = intval($this->expired_time); // Exercise already exists if ($id) { // we prepare date in the database using the api_get_utc_datetime() function if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') { $start_time = Database::escape_string($this->start_time); } else { $start_time = '0000-00-00 00:00:00'; } if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') { $end_time = Database::escape_string($this->end_time); } else { $end_time = '0000-00-00 00:00:00'; } $params = [ 'title' => $exercise, 'description' => $description, ]; $paramsExtra = []; if ($type_e != 'simple') { $paramsExtra = [ 'sound' => $sound, 'type' => $type, 'random' => $random, 'random_answers' => $random_answers, 'active' => $active, 'feedback_type' => $feedback_type, 'start_time' => $start_time, 'end_time' => $end_time, 'max_attempt' => $attempts, 'expired_time' => $expired_time, 'propagate_neg' => $propagate_neg, 'review_answers' => $review_answers, 'random_by_category' => $randomByCat, 'text_when_finished' => $text_when_finished, 'display_category_name' => $display_category_name, 'pass_percentage' => $pass_percentage, 'results_disabled' => $results_disabled, ]; } $params = array_merge($params, $paramsExtra); Database::update( $TBL_EXERCISES, $params, ['c_id = ? AND id = ?' => [$this->course_id, $id]] ); // update into the item_property table api_item_property_update( $_course, TOOL_QUIZ, $id, 'QuizUpdated', api_get_user_id() ); if (api_get_setting('search_enabled')=='true') { $this->search_engine_edit(); } } else { // Creates a new exercise // In this case of new exercise, we don't do the api_get_utc_datetime() // for date because, bellow, we call function api_set_default_visibility() // In this function, api_set_default_visibility, // the Quiz is saved too, with an $id and api_get_utc_datetime() is done. // If we do it now, it will be done twice (cf. https://support.chamilo.org/issues/6586) if (!empty($this->start_time) && $this->start_time != '0000-00-00 00:00:00') { $start_time = $this->start_time; } else { $start_time = '0000-00-00 00:00:00'; } if (!empty($this->end_time) && $this->end_time != '0000-00-00 00:00:00') { $end_time = $this->end_time; } else { $end_time = '0000-00-00 00:00:00'; } $params = [ 'c_id' => $this->course_id, 'start_time' => $start_time, 'end_time' => $end_time, 'title' => $exercise, 'description' => $description, 'sound' => $sound, 'type' => $type, 'random' => $random, 'random_answers' => $random_answers, 'active' => $active, 'results_disabled' => $results_disabled, 'max_attempt' => $attempts, 'feedback_type' => $feedback_type, 'expired_time' => $expired_time, 'session_id' => $session_id, 'review_answers' => $review_answers, 'random_by_category' => $randomByCat, 'text_when_finished' => $text_when_finished, 'display_category_name' => $display_category_name, 'pass_percentage' => $pass_percentage ]; $this->id = Database::insert($TBL_EXERCISES, $params); if ($this->id) { $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} "; Database::query($sql); // insert into the item_property table api_item_property_update( $this->course, TOOL_QUIZ, $this->id, 'QuizAdded', api_get_user_id() ); // This function save the quiz again, carefull about start_time // and end_time if you remove this line (see above) api_set_default_visibility( $this->id, TOOL_QUIZ, null, $this->course ); if (api_get_setting('search_enabled') == 'true' && extension_loaded('xapian')) { $this->search_engine_save(); } } } // Updates the question position $this->update_question_positions(); } /** * Updates question position */ public function update_question_positions() { $quiz_question_table = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); //Fixes #3483 when updating order $question_list = $this->selectQuestionList(true); if (!empty($question_list)) { foreach ($question_list as $position => $questionId) { $sql = "UPDATE $quiz_question_table SET question_order ='".intval($position)."' WHERE c_id = ".$this->course_id." AND question_id = ".intval($questionId)." AND exercice_id=".intval($this->id); Database::query($sql); } } } /** * Adds a question into the question list * * @author Olivier Brouckaert * @param integer $questionId - question ID * @return boolean - true if the question has been added, otherwise false */ public function addToList($questionId) { // checks if the question ID is not in the list if (!$this->isInList($questionId)) { // selects the max position if (!$this->selectNbrQuestions()) { $pos = 1; } else { if (is_array($this->questionList)) { $pos = max(array_keys($this->questionList)) + 1; } } $this->questionList[$pos] = $questionId; return true; } return false; } /** * removes a question from the question list * * @author Olivier Brouckaert * @param integer $questionId - question ID * @return boolean - true if the question has been removed, otherwise false */ public function removeFromList($questionId) { // searches the position of the question ID in the list $pos = array_search($questionId,$this->questionList); // question not found if ($pos === false) { return false; } else { // dont reduce the number of random question if we use random by category option, or if // random all questions if ($this->isRandom() && $this->isRandomByCat() == 0) { if (count($this->questionList) >= $this->random && $this->random > 0) { $this->random -= 1; $this->save(); } } // deletes the position from the array containing the wanted question ID unset($this->questionList[$pos]); return true; } } /** * deletes the exercise from the database * Notice : leaves the question in the data base * * @author Olivier Brouckaert */ public function delete() { $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); $sql = "UPDATE $TBL_EXERCISES SET active='-1' WHERE c_id = ".$this->course_id." AND id = ".intval($this->id).""; Database::query($sql); api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'QuizDeleted', api_get_user_id()); api_item_property_update($this->course, TOOL_QUIZ, $this->id, 'delete', api_get_user_id()); if (api_get_setting('search_enabled')=='true' && extension_loaded('xapian') ) { $this->search_engine_delete(); } } /** * Creates the form to create / edit an exercise * @param FormValidator $form */ public function createForm($form, $type='full') { global $id; if (empty($type)){ $type='full'; } // form title if (!empty($_GET['exerciseId'])) { $form_title = get_lang('ModifyExercise'); } else { $form_title = get_lang('NewEx'); } $form->addElement('header', $form_title); // Title. $form->addElement('text', 'exerciseTitle', get_lang('ExerciseName'), array('class' => 'span6','id'=>'exercise_title')); $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters')); $form->addElement('html', '