';
+
+ $header .= ''.get_lang('Choice').' ';
+ $header .= ''.get_lang('Answer').' ';
+ $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('Number').'
+ '.get_lang('Answer').'
+ '.get_lang('PtestCategory').'
+
+
+ ';
+
+ $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(
+ '{error} {element} ',
+ 'counter['.$i.']'
+ );
+ $renderer->setElementTemplate(
+ '{error} {element} ',
+ 'answer['.$i.']'
+ );
+ $renderer->setElementTemplate(
+ '{error} {element} ',
+ '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('
');
+
+ 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 .= ''.get_lang('Choice').' ';
+ $header .= ''.get_lang('Answer').' ';
+ $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 .= '';
+ $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 = '
+
+
+ '.get_lang('Number').'
+ '.get_lang('Answer').'
+ '.get_lang('PtestCategory').'
+
+
+ ';
+
+ $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(
+ '{error} {element} ',
+ 'counter['.$i.']'
+ );
+ $renderer->setElementTemplate(
+ '{error} {element} ',
+ 'answer['.$i.']'
+ );
+ $renderer->setElementTemplate(
+ '{error} {element} ',
+ '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('
');
+
+ 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 .= ''.get_lang('Choice').' ';
+ $header .= ''.get_lang('Answer').' ';
+ $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 '';
+ 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', '');
+ $form->addHtmlEditor(
+ 'questionDescription',
+ get_lang('QuestionDescription'),
+ false,
+ false,
+ $editorConfig
+ );
+
+ if ($this->type != MEDIA_QUESTION) {
+ // Advanced parameters
+ $selectLevel = self::get_default_levels();
+ $form->addElement(
+ 'select',
+ 'questionLevel',
+ get_lang('Difficulty'),
+ $selectLevel
+ );
+
+ // Categories
+ $tabCat = TestCategory::getCategoriesIdAndName();
+ $form->addElement(
+ 'select',
+ 'questionCategory',
+ get_lang('Category'),
+ $tabCat
+ );
+
+ global $text;
+
+ $buttonGroup = [];
+ $buttonGroup[] = $form->addButtonSave(
+ $text,
+ 'submitQuestion',
+ true
+ );
+ $form->addGroup($buttonGroup);
+ }
+
+ $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 '
+
+
'.get_lang('Questions').'
+
'.get_lang('Type').'
+
'.get_lang('Category').'
+
'.get_lang('Difficulty').'
+
'.get_lang('Actions').'
+
+
+ ';
+
+ $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 '
+ ';
+ 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 .= '';
+ $s .= Display::tag(
+ 'tr',
+ $header,
+ ['style' => 'text-align:left;']
+ );
+
+ $optionsList = [];
+ $agreeOption = 0;
+ $disagreeOption = 0;
+ }
+
+ if ($answerType == QUESTION_PT_TYPE_AGREE_SCALE) {
+ $dataList = '
+ 1
+ 2
+ 3
+ 4
+ 5
+ ';
+
+ $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 .= ''.$answerId.' '.$answer.' ';
+
+ 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 .= ''.$answer.' ';
+ $s .= ' '.$answer_input.' ';
+ $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 .= ''.$answerId.' '.$answer.' ';
+
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 .= '
';
}
+ if ($answerType == QUESTION_PT_TYPE_AGREE_OR_DISAGREE) {
+ $s .= '';
+ }
+
+ if ($answerType == QUESTION_PT_TYPE_AGREE_REORDER) {
+ $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.
*