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 = ''; $this->start_time = ''; $this->results_disabled = 1; $this->expired_time = 0; $this->propagate_neg = 0; $this->saveCorrectAnswers = 0; $this->review_answers = false; $this->randomByCat = 0; $this->text_when_finished = ''; $this->display_category_name = 0; $this->pass_percentage = ''; $this->modelType = 1; $this->questionSelectionType = EX_Q_SELECTION_ORDERED; $this->endButton = 0; $this->scoreTypeModel = 0; $this->globalCategoryId = 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; // ALTER TABLE c_quiz_question ADD COLUMN feedback text; $this->questionFeedbackEnabled = api_get_configuration_value('allow_quiz_question_feedback'); } /** * Reads exercise information from the data base * * @author Olivier Brouckaert * @param integer $id - exercise Id * @param bool $parseQuestionList * * @return boolean - true if exercise exists, otherwise false */ public function read($id, $parseQuestionList = true) { $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST); $table_lp_item = Database::get_course_table(TABLE_LP_ITEM); $id = (int) $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->saveCorrectAnswers = $object->save_correct_answers; $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; $this->globalCategoryId = isset($object->global_category_id) ? $object->global_category_id : null; $this->questionSelectionType = isset($object->question_selection_type) ? $object->question_selection_type : null; $this->hideQuestionTitle = isset($object->hide_question_title) ? (int) $object->hide_question_title : 0; $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 = api_get_configuration_value('force_edit_exercise_in_lp'); 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 (!empty($object->end_time)) { $this->end_time = $object->end_time; } if (!empty($object->start_time)) { $this->start_time = $object->start_time; } // Control time $this->expired_time = $object->expired_time; // Checking if question_order is correctly set if ($parseQuestionList) { $this->setQuestionList(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() { $title = $this->getUnformattedTitle(); return cut($title, 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 * @param bool $unformattedText Optional. Get the title without HTML tags * @return string - exercise title */ public function selectTitle($unformattedText = false) { if ($unformattedText) { return $this->getUnformattedTitle(); } 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 * @return int */ 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 int - exercise type */ public function selectType() { return $this->type; } /** * @return int */ public function getModelType() { return $this->modelType; } /** * @return int */ public function selectEndButton() { return $this->endButton; } /** * @return string */ public function getOnSuccessMessage() { return $this->onSuccessMessage; } /** * @return string */ public function getOnFailedMessage() { return $this->onFailedMessage; } /** * @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 = EXERCISE_CATEGORY_RANDOM_DISABLED; if ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_SHUFFLED) { $res = EXERCISE_CATEGORY_RANDOM_SHUFFLED; } elseif ($this->randomByCat == EXERCISE_CATEGORY_RANDOM_ORDERED) { $res = EXERCISE_CATEGORY_RANDOM_ORDERED; } return $res; } /** * return nothing * update randomByCat value for object * @param int $random * * @author hubert borderiou */ public function updateRandomByCat($random) { if (in_array( $random, array( EXERCISE_CATEGORY_RANDOM_SHUFFLED, EXERCISE_CATEGORY_RANDOM_ORDERED, EXERCISE_CATEGORY_RANDOM_DISABLED, ) )) { $this->randomByCat = $random; } else { $this->randomByCat = EXERCISE_CATEGORY_RANDOM_DISABLED; } } /** * 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 * @return int */ 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; } /** * If false the question list will be managed as always if true the question will be filtered * depending of the exercise settings (table c_quiz_rel_category) * @param bool $status active or inactive grouping **/ public function setCategoriesGrouping($status) { $this->categories_grouping = (bool) $status; } /** * @return int */ public function getHideQuestionTitle() { return $this->hideQuestionTitle; } /** * @param $value */ public function setHideQuestionTitle($value) { $this->hideQuestionTitle = (int) $value; } /** * @return int */ public function getScoreTypeModel() { return $this->scoreTypeModel; } /** * @param int $value */ public function setScoreTypeModel($value) { $this->scoreTypeModel = (int) $value; } /** * @return int */ public function getGlobalCategoryId() { return $this->globalCategoryId; } /** * @param int $value */ public function setGlobalCategoryId($value) { if (is_array($value) && isset($value[0])) { $value = $value[0]; } $this->globalCategoryId = (int) $value; } /** * * @param int $start * @param int $limit * @param int $sidx * @param string $sord * @param array $where_condition * @param array $extraFields * * @return array */ public function getQuestionListPagination( $start, $limit, $sidx, $sord, $where_condition = array(), $extraFields = array() ) { if (!empty($this->id)) { $category_list = TestCategory::getListOfCategoriesNameForTest( $this->id, false ); $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); $sql = "SELECT q.iid FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id = q.id AND e.c_id = ".$this->course_id." ) WHERE e.exercice_id = '".Database::escape_string($this->id)."' "; $orderCondition = "ORDER BY question_order"; if (!empty($sidx) && !empty($sord)) { if ($sidx == 'question') { if (in_array(strtolower($sord), array('desc', 'asc'))) { $orderCondition = " ORDER BY q.$sidx $sord"; } } } $sql .= $orderCondition; $limitCondition = null; if (isset($start) && isset($limit)) { $start = intval($start); $limit = intval($limit); $limitCondition = " LIMIT $start, $limit"; } $sql .= $limitCondition; $result = Database::query($sql); $questions = array(); if (Database::num_rows($result)) { if (!empty($extraFields)) { $extraFieldValue = new ExtraFieldValue('question'); } while ($question = Database::fetch_array($result, 'ASSOC')) { /** @var Question $objQuestionTmp */ $objQuestionTmp = Question::read($question['iid']); $category_labels = TestCategory::return_category_labels( $objQuestionTmp->category_list, $category_list ); if (empty($category_labels)) { $category_labels = "-"; } // Question type list($typeImg, $typeExpl) = $objQuestionTmp->get_type_icon_html(); $question_media = null; if (!empty($objQuestionTmp->parent_id)) { $objQuestionMedia = Question::read($objQuestionTmp->parent_id); $question_media = Question::getMediaLabel($objQuestionMedia->question); } $questionType = Display::tag( 'div', Display::return_icon($typeImg, $typeExpl, array(), ICON_SIZE_MEDIUM).$question_media ); $question = array( 'id' => $question['iid'], 'question' => $objQuestionTmp->selectTitle(), 'type' => $questionType, 'category' => Display::tag('div', ''.$category_labels.''), 'score' => $objQuestionTmp->selectWeighting(), 'level' => $objQuestionTmp->level ); if (!empty($extraFields)) { foreach ($extraFields as $extraField) { $value = $extraFieldValue->get_values_by_handler_and_field_id($question['id'], $extraField['id']); $stringValue = null; if ($value) { $stringValue = $value['field_value']; } $question[$extraField['field_variable']] = $stringValue; } } $questions[] = $question; } } return $questions; } } /** * Get question count per exercise from DB (any special treatment) * @return int */ public function getQuestionCount() { $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); $sql = "SELECT count(q.id) as count FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id = q.id AND e.c_id = q.c_id) WHERE e.c_id = {$this->course_id} AND e.exercice_id = ".Database::escape_string($this->id); $result = Database::query($sql); $count = 0; if (Database::num_rows($result)) { $row = Database::fetch_array($result); $count = $row['count']; } return $count; } /** * @return array */ public function getQuestionOrderedListByName() { $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); // Getting question list from the order (question list drag n drop interface ). $sql = "SELECT e.question_id FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id= q.id AND e.c_id = q.c_id) WHERE e.c_id = {$this->course_id} AND e.exercice_id = '".Database::escape_string($this->id)."' ORDER BY q.question"; $result = Database::query($sql); $list = array(); if (Database::num_rows($result)) { $list = Database::store_result($result, 'ASSOC'); } return $list; } /** * Gets the question list ordered by the question_order setting (drag and drop) * @return array */ private function getQuestionOrderedList() { $questionList = array(); $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); // Getting question_order to verify that the question // list is correct and all question_order's were set $sql = "SELECT DISTINCT e.question_order FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id = q.id AND e.c_id = q.c_id) WHERE e.c_id = {$this->course_id} AND e.exercice_id = ".Database::escape_string($this->id); $result = Database::query($sql); $count_question_orders = Database::num_rows($result); // Getting question list from the order (question list drag n drop interface ). $sql = "SELECT DISTINCT e.question_id, e.question_order FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id = q.id AND e.c_id = q.c_id) WHERE e.c_id = {$this->course_id} AND e.exercice_id = '".Database::escape_string($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; while ($new_object = Database::fetch_object($result)) { // Correct order. $questionList[$new_object->question_order] = $new_object->question_id; // Just in case we save the order in other array $temp_question_list[$counter] = $new_object->question_id; $counter++; } if (!empty($temp_question_list)) { /* If both array don't match it means that question_order was not correctly set for all questions using the default mysql order */ if (count($temp_question_list) != $count_question_orders) { $questionList = $temp_question_list; } } return $questionList; } /** * Select N values from the questions per category array * * @param array $categoriesAddedInExercise * @param array $question_list * @param array $questions_by_category per category * @param bool $flatResult * @param bool $randomizeQuestions * * @return array */ private function pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, & $questions_by_category, $flatResult = true, $randomizeQuestions = false ) { $addAll = true; $categoryCountArray = array(); // Getting how many questions will be selected per category. if (!empty($categoriesAddedInExercise)) { $addAll = false; // Parsing question according the category rel exercise settings foreach ($categoriesAddedInExercise as $category_info) { $category_id = $category_info['category_id']; if (isset($questions_by_category[$category_id])) { // How many question will be picked from this category. $count = $category_info['count_questions']; // -1 means all questions if ($count == -1) { $categoryCountArray[$category_id] = 999; } else { $categoryCountArray[$category_id] = $count; } } } } if (!empty($questions_by_category)) { $temp_question_list = array(); foreach ($questions_by_category as $category_id => & $categoryQuestionList) { if (isset($categoryCountArray) && !empty($categoryCountArray)) { if (isset($categoryCountArray[$category_id])) { $numberOfQuestions = $categoryCountArray[$category_id]; } else { $numberOfQuestions = 0; } } if ($addAll) { $numberOfQuestions = 999; } if (!empty($numberOfQuestions)) { $elements = TestCategory::getNElementsFromArray( $categoryQuestionList, $numberOfQuestions, $randomizeQuestions ); if (!empty($elements)) { $temp_question_list[$category_id] = $elements; $categoryQuestionList = $elements; } } } if (!empty($temp_question_list)) { if ($flatResult) { $temp_question_list = array_flatten($temp_question_list); } $question_list = $temp_question_list; } } return $question_list; } /** * Selecting question list depending in the exercise-category * relationship (category table in exercise settings) * * @param array $question_list * @param int $questionSelectionType * @return array */ public function getQuestionListWithCategoryListFilteredByCategorySettings( $question_list, $questionSelectionType ) { $result = array( 'question_list' => array(), 'category_with_questions_list' => array() ); // Order/random categories $cat = new TestCategory(); // Setting category order. switch ($questionSelectionType) { case EX_Q_SELECTION_ORDERED: // 1 case EX_Q_SELECTION_RANDOM: // 2 // This options are not allowed here. break; case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED: // 3 $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], 'title ASC', false, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, false ); break; case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED: // 4 case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7 $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], null, true, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, false ); break; case EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_RANDOM: // 5 $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], 'title DESC', false, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, true ); break; case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM: // 6 case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], null, true, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, true ); break; case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_ORDERED_NO_GROUPED: // 7 break; case EX_Q_SELECTION_CATEGORIES_RANDOM_QUESTIONS_RANDOM_NO_GROUPED: // 8 break; case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_ORDERED: // 9 $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], 'root ASC, lft ASC', false, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, false ); break; case EX_Q_SELECTION_CATEGORIES_ORDERED_BY_PARENT_QUESTIONS_RANDOM: // 10 $categoriesAddedInExercise = $cat->getCategoryExerciseTree( $this, $this->course['real_id'], 'root, lft ASC', false, true ); $questions_by_category = TestCategory::getQuestionsByCat( $this->id, $question_list, $categoriesAddedInExercise ); $question_list = $this->pickQuestionsPerCategory( $categoriesAddedInExercise, $question_list, $questions_by_category, true, true ); break; } $result['question_list'] = isset($question_list) ? $question_list : array(); $result['category_with_questions_list'] = isset($questions_by_category) ? $questions_by_category : array(); // Adding category info in the category list with question list: if (!empty($questions_by_category)) { $newCategoryList = array(); foreach ($questions_by_category as $categoryId => $questionList) { $cat = new TestCategory(); $cat = $cat->getCategory($categoryId); $cat = (array) $cat; $cat['iid'] = $cat['id']; $categoryParentInfo = null; // Parent is not set no loop here if (!empty($cat['parent_id'])) { if (!isset($parentsLoaded[$cat['parent_id']])) { $categoryEntity = $em->find('ChamiloCoreBundle:CQuizCategory', $cat['parent_id']); $parentsLoaded[$cat['parent_id']] = $categoryEntity; } else { $categoryEntity = $parentsLoaded[$cat['parent_id']]; } $path = $repo->getPath($categoryEntity); $index = 0; if ($this->categoryMinusOne) { //$index = 1; } /** @var \Chamilo\CourseBundle\Entity\CQuizCategory $categoryParent*/ foreach ($path as $categoryParent) { $visibility = $categoryParent->getVisibility(); if ($visibility == 0) { $categoryParentId = $categoryId; $categoryTitle = $cat['title']; if (count($path) > 1) { continue; } } else { $categoryParentId = $categoryParent->getIid(); $categoryTitle = $categoryParent->getTitle(); } $categoryParentInfo['id'] = $categoryParentId; $categoryParentInfo['iid'] = $categoryParentId; $categoryParentInfo['parent_path'] = null; $categoryParentInfo['title'] = $categoryTitle; $categoryParentInfo['name'] = $categoryTitle; $categoryParentInfo['parent_id'] = null; break; } } $cat['parent_info'] = $categoryParentInfo; $newCategoryList[$categoryId] = array( 'category' => $cat, 'question_list' => $questionList ); } $result['category_with_questions_list'] = $newCategoryList; } return $result; } /** * returns the array with the question ID list * @param bool $from_db Whether the results should be fetched in the database or just from memory * @param bool $adminView Whether we should return all questions (admin view) or * just a list limited by the max number of random questions * @author Olivier Brouckaert * @return array - question ID list */ public function selectQuestionList($from_db = false, $adminView = false) { if ($from_db && !empty($this->id)) { $nbQuestions = $this->getQuestionCount(); $questionSelectionType = $this->getQuestionSelectionType(); switch ($questionSelectionType) { case EX_Q_SELECTION_ORDERED: $questionList = $this->getQuestionOrderedList(); break; case EX_Q_SELECTION_RANDOM: // Not a random exercise, or if there are not at least 2 questions if ($this->random == 0 || $nbQuestions < 2) { $questionList = $this->getQuestionOrderedList(); } else { $questionList = $this->selectRandomList($adminView); } break; default: $questionList = $this->getQuestionOrderedList(); $result = $this->getQuestionListWithCategoryListFilteredByCategorySettings( $questionList, $questionSelectionType ); $this->categoryWithQuestionList = $result['category_with_questions_list']; $questionList = $result['question_list']; break; } return $questionList; } 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; } /** * @return int */ public function selectSaveCorrectAnswers() { return $this->saveCorrectAnswers; } /** * Selects questions randomly in the question list * * @author Olivier Brouckaert * @author Hubert Borderiou 15 nov 2011 * @param bool $adminView Whether we should return all * questions (admin view) or just a list limited by the max number of random questions * @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($adminView = false) { $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION); $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION); $random = isset($this->random) && !empty($this->random) ? $this->random : 0; $randomLimit = "ORDER BY RAND() LIMIT $random"; // Random all questions so no limit if ($random == -1 or $adminView === true) { // If viewing it as admin for edition, don't show it randomly, use title + id $randomLimit = 'ORDER BY e.question_order'; } $sql = "SELECT e.question_id FROM $TBL_EXERCISE_QUESTION e INNER JOIN $TBL_QUESTIONS q ON (e.question_id= q.id AND e.c_id = q.c_id) WHERE e.c_id = {$this->course_id} AND e.exercice_id = '".Database::escape_string($this->id)."' $randomLimit "; $result = Database::query($sql); $questionList = array(); while ($row = Database::fetch_object($result)) { $questionList[] = $row->question_id; } return $questionList; } /** * 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->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 int */ public function updateSaveCorrectAnswers($value) { $this->saveCorrectAnswers = $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; } /** * @param string $text */ public function updateEmailNotificationTemplate($text) { $this->emailNotificationTemplate = $text; } /** * @param string $text */ public function updateEmailNotificationTemplateToUser($text) { $this->emailNotificationTemplateToUser = $text; } /** * @param string $value */ public function setNotifyUserByEmail($value) { $this->notifyUserByEmail = $value; } /** * @param int $value */ public function updateEndButton($value) { $this->endButton = (int) $value; } /** * @param string $value */ public function setOnSuccessMessage($value) { $this->onSuccessMessage = $value; } /** * @param string $value */ public function setOnFailedMessage($value) { $this->onFailedMessage = $value; } /** * @param $value */ public function setModelType($value) { $this->modelType = (int) $value; } /** * @param int $value */ public function setQuestionSelectionType($value) { $this->questionSelectionType = (int) $value; } /** * @return int */ public function getQuestionSelectionType() { return $this->questionSelectionType; } /** * @param array $categories */ public function updateCategories($categories) { if (!empty($categories)) { $categories = array_map('intval', $categories); $this->categories = $categories; } } /** * 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)) { $sql = "SELECT 1 FROM $TBL_DOCUMENT WHERE c_id = ".$this->course_id." AND path = '".str_replace($documentPath, '', $audioPath).'/'.$this->sound."'"; $result = Database::query($sql); 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 = (int) $results_disabled; } /** * updates the exercise in the data base * @param string $type_e * @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 = (int) $this->propagate_neg; $saveCorrectAnswers = isset($this->saveCorrectAnswers) && $this->saveCorrectAnswers ? 1 : 0; $review_answers = isset($this->review_answers) && $this->review_answers ? 1 : 0; $randomByCat = intval($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)) { $start_time = $this->start_time; } else { $start_time = null; } if (!empty($this->end_time)) { $end_time = $this->end_time; } else { $end_time = null; } $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, 'save_correct_answers' => $saveCorrectAnswers, '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, 'question_selection_type' => $this->getQuestionSelectionType(), 'hide_question_title' => $this->getHideQuestionTitle() ]; } $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)) { $start_time = $this->start_time; } else { $start_time = null; } if (!empty($this->end_time)) { $end_time = $this->end_time; } else { $end_time = null; } $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, 'save_correct_answers' => (int) $saveCorrectAnswers, 'propagate_neg' => $propagate_neg, 'hide_question_title' => $this->getHideQuestionTitle() ]; $this->id = Database::insert($TBL_EXERCISES, $params); if ($this->id) { $sql = "UPDATE $TBL_EXERCISES SET id = iid WHERE iid = {$this->id} "; Database::query($sql); $sql = "UPDATE $TBL_EXERCISES SET question_selection_type= ".intval($this->getQuestionSelectionType())." WHERE id = ".$this->id." AND c_id = ".$this->course_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(); } } } $this->save_categories_in_exercise($this->categories); // Updates the question position $this->update_question_positions(); } /** * Updates question position */ public function update_question_positions() { $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 $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') { 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. if (api_get_configuration_value('save_titles_as_html')) { $form->addHtmlEditor( 'exerciseTitle', get_lang('ExerciseName'), false, false, ['ToolbarSet' => 'Minimal'] ); } else { $form->addElement( 'text', 'exerciseTitle', get_lang('ExerciseName'), array('id' => 'exercise_title') ); } $form->addElement('advanced_settings', 'advanced_params', get_lang('AdvancedParameters')); $form->addElement('html', '