TestCategory.php 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. /**
  5. * Class TestCategory
  6. * @author hubert.borderiou
  7. * @author Julio Montoya - several fixes
  8. * @todo rename to ExerciseCategory
  9. */
  10. class TestCategory
  11. {
  12. public $id;
  13. public $name;
  14. public $description;
  15. /**
  16. * Constructor of the class Category
  17. */
  18. public function __construct()
  19. {
  20. }
  21. /**
  22. * return the TestCategory object with id=in_id
  23. * @param int $id
  24. * @param int $courseId
  25. * @return TestCategory
  26. */
  27. public function getCategory($id, $courseId = 0)
  28. {
  29. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  30. $id = intval($id);
  31. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  32. $sql = "SELECT * FROM $table
  33. WHERE id = $id AND c_id = ".$courseId;
  34. $res = Database::query($sql);
  35. if (Database::num_rows($res)) {
  36. $row = Database::fetch_array($res);
  37. $this->id = $row['id'];
  38. $this->name = $row['title'];
  39. $this->description = $row['description'];
  40. return $this;
  41. }
  42. return false;
  43. }
  44. /**
  45. * Save TestCategory in the database if name doesn't exists
  46. * @param int $courseId
  47. * @return bool
  48. */
  49. public function save($courseId = 0)
  50. {
  51. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  52. $courseInfo = api_get_course_info_by_id($courseId);
  53. if (empty($courseInfo)) {
  54. return false;
  55. }
  56. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  57. // check if name already exists
  58. $sql = "SELECT count(*) AS nb FROM $table
  59. WHERE title = '".$this->name."' AND c_id=$courseId";
  60. $result = Database::query($sql);
  61. $row = Database::fetch_array($result);
  62. // lets add in BDD if not the same name
  63. if ($row['nb'] <= 0) {
  64. $params = [
  65. 'c_id' => $courseId,
  66. 'title' => (string) $this->name,
  67. 'description' => (string) $this->description
  68. ];
  69. $newId = Database::insert($table, $params);
  70. if ($newId) {
  71. $sql = "UPDATE $table SET id = iid WHERE iid = $newId";
  72. Database::query($sql);
  73. api_item_property_update(
  74. $courseInfo,
  75. TOOL_TEST_CATEGORY,
  76. $newId,
  77. 'TestCategoryAdded',
  78. api_get_user_id()
  79. );
  80. }
  81. return $newId;
  82. } else {
  83. return false;
  84. }
  85. }
  86. /**
  87. * Removes the category from the database
  88. * if there were question in this category, the link between question and category is removed
  89. */
  90. public function removeCategory($id)
  91. {
  92. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  93. $tbl_question_rel_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  94. $id = intval($id);
  95. $course_id = api_get_course_int_id();
  96. $category = $this->getCategory($id);
  97. if ($category) {
  98. $sql = "DELETE FROM $table
  99. WHERE id= $id AND c_id=".$course_id;
  100. Database::query($sql);
  101. // remove link between question and category
  102. $sql = "DELETE FROM $tbl_question_rel_cat
  103. WHERE category_id = $id AND c_id=".$course_id;
  104. Database::query($sql);
  105. // item_property update
  106. $course_info = api_get_course_info_by_id($course_id);
  107. api_item_property_update(
  108. $course_info,
  109. TOOL_TEST_CATEGORY,
  110. $this->id,
  111. 'TestCategoryDeleted',
  112. api_get_user_id()
  113. );
  114. return true;
  115. }
  116. return false;
  117. }
  118. /**
  119. * Modify category name or description of category with id=in_id
  120. * @param int $courseId
  121. * @return bool
  122. */
  123. public function modifyCategory($courseId = 0)
  124. {
  125. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  126. $id = intval($this->id);
  127. $name = Database::escape_string($this->name);
  128. $description = Database::escape_string($this->description);
  129. $cat = $this->getCategory($id);
  130. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  131. $courseInfo = api_get_course_info_by_id($courseId);
  132. if (empty($courseInfo)) {
  133. return false;
  134. }
  135. if ($cat) {
  136. $sql = "UPDATE $table SET
  137. title = '$name',
  138. description = '$description'
  139. WHERE id = $id AND c_id = ".$courseId;
  140. Database::query($sql);
  141. // item_property update
  142. api_item_property_update(
  143. $courseInfo,
  144. TOOL_TEST_CATEGORY,
  145. $this->id,
  146. 'TestCategoryModified',
  147. api_get_user_id()
  148. );
  149. return true;
  150. }
  151. return false;
  152. }
  153. /**
  154. * Gets the number of question of category id=in_id
  155. */
  156. public function getCategoryQuestionsNumber()
  157. {
  158. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  159. $id = intval($this->id);
  160. $sql = "SELECT count(*) AS nb
  161. FROM $table
  162. WHERE category_id = $id AND c_id=".api_get_course_int_id();
  163. $res = Database::query($sql);
  164. $row = Database::fetch_array($res);
  165. return $row['nb'];
  166. }
  167. /**
  168. * Return an array of all Category objects in the database
  169. * If $field=="" Return an array of all category objects in the database
  170. * Otherwise, return an array of all in_field value
  171. * in the database (in_field = id or name or description)
  172. *
  173. * @param string $field
  174. * @param int $courseId
  175. * @return array
  176. */
  177. public static function getCategoryListInfo($field = '', $courseId = 0)
  178. {
  179. $courseId = empty($courseId) ? api_get_course_int_id() : (int) $courseId;
  180. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  181. $field = Database::escape_string($field);
  182. $categories = array();
  183. if (empty($field)) {
  184. $sql = "SELECT id FROM $table
  185. WHERE c_id = $courseId
  186. ORDER BY title ASC";
  187. $res = Database::query($sql);
  188. while ($row = Database::fetch_array($res)) {
  189. $category = new TestCategory();
  190. $categories[] = $category->getCategory($row['id']);
  191. }
  192. } else {
  193. $sql = "SELECT $field FROM $table
  194. WHERE c_id = $courseId
  195. ORDER BY $field ASC";
  196. $res = Database::query($sql);
  197. while ($row = Database::fetch_array($res)) {
  198. $categories[] = $row[$field];
  199. }
  200. }
  201. return $categories;
  202. }
  203. /**
  204. * Return the TestCategory id for question with question_id = $questionId
  205. * In this version, a question has only 1 TestCategory.
  206. * Return the TestCategory id, 0 if none
  207. * @param int $questionId
  208. * @param int $courseId
  209. *
  210. * @return int
  211. */
  212. public static function getCategoryForQuestion($questionId, $courseId = 0)
  213. {
  214. $courseId = (int) $courseId;
  215. if (empty($courseId)) {
  216. $courseId = api_get_course_int_id();
  217. }
  218. if (empty($courseId) || empty($questionId)) {
  219. return 0;
  220. }
  221. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  222. $questionId = intval($questionId);
  223. $sql = "SELECT category_id
  224. FROM $table
  225. WHERE question_id = $questionId AND c_id = $courseId";
  226. $res = Database::query($sql);
  227. $result = 0;
  228. if (Database::num_rows($res) > 0) {
  229. $data = Database::fetch_array($res);
  230. $result = (int) $data['category_id'];
  231. }
  232. return $result;
  233. }
  234. /**
  235. * true if question id has a category
  236. *
  237. * @param int $questionId
  238. * @return bool
  239. */
  240. public static function isQuestionHasCategory($questionId)
  241. {
  242. if (self::getCategoryForQuestion($questionId) > 0) {
  243. return true;
  244. }
  245. return false;
  246. }
  247. /**
  248. * Return the category name for question with question_id = $questionId
  249. * In this version, a question has only 1 category.
  250. * @param $questionId
  251. * @param int $courseId
  252. * @return string
  253. */
  254. public static function getCategoryNameForQuestion(
  255. $questionId,
  256. $courseId = 0
  257. ) {
  258. if (empty($courseId)) {
  259. $courseId = api_get_course_int_id();
  260. }
  261. $courseId = (int) $courseId;
  262. $categoryId = self::getCategoryForQuestion($questionId, $courseId);
  263. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  264. $sql = "SELECT title
  265. FROM $table
  266. WHERE id = $categoryId AND c_id = $courseId";
  267. $res = Database::query($sql);
  268. $data = Database::fetch_array($res);
  269. $result = '';
  270. if (Database::num_rows($res) > 0) {
  271. $result = $data['title'];
  272. }
  273. return $result;
  274. }
  275. /**
  276. * Return the list of differents categories ID for a test in the current course
  277. * input : test_id
  278. * return : array of category id (integer)
  279. * hubert.borderiou 07-04-2011
  280. * @param int $exerciseId
  281. *
  282. * @return array
  283. */
  284. public static function getListOfCategoriesIDForTest($exerciseId)
  285. {
  286. // parcourir les questions d'un test, recup les categories uniques dans un tableau
  287. $exercise = new Exercise();
  288. $exercise->read($exerciseId, false);
  289. $categoriesInExercise = $exercise->getQuestionWithCategories();
  290. // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
  291. $categories = array();
  292. if (!empty($categoriesInExercise)) {
  293. foreach ($categoriesInExercise as $category) {
  294. $categories[$category['id']] = $category;
  295. }
  296. }
  297. return $categories;
  298. }
  299. /**
  300. * @param Exercise $exercise_obj
  301. * @return array
  302. */
  303. public static function getListOfCategoriesIDForTestObject(Exercise $exercise_obj)
  304. {
  305. // parcourir les questions d'un test, recup les categories uniques dans un tableau
  306. $categories_in_exercise = array();
  307. $question_list = $exercise_obj->getQuestionOrderedListByName();
  308. // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ???
  309. foreach ($question_list as $questionInfo) {
  310. $question_id = $questionInfo['question_id'];
  311. $category_list = self::getCategoryForQuestion($question_id);
  312. if (is_numeric($category_list)) {
  313. $category_list = array($category_list);
  314. }
  315. if (!empty($category_list)) {
  316. $categories_in_exercise = array_merge($categories_in_exercise, $category_list);
  317. }
  318. }
  319. if (!empty($categories_in_exercise)) {
  320. $categories_in_exercise = array_unique(array_filter($categories_in_exercise));
  321. }
  322. return $categories_in_exercise;
  323. }
  324. /**
  325. * Return the list of different categories NAME for a test
  326. * @param int exercise id
  327. * @param bool
  328. * @return integer of string
  329. *
  330. * @author function rewrote by jmontoya
  331. */
  332. public static function getListOfCategoriesNameForTest($exercise_id, $grouped_by_category = true)
  333. {
  334. $result = array();
  335. $categories = self::getListOfCategoriesIDForTest(
  336. $exercise_id,
  337. $grouped_by_category
  338. );
  339. foreach ($categories as $catInfo) {
  340. $categoryId = $catInfo['id'];
  341. if (!empty($categoryId)) {
  342. $result[$categoryId] = array(
  343. 'title' => $catInfo['title'],
  344. //'parent_id' => $catInfo['parent_id'],
  345. 'parent_id' => '',
  346. 'c_id' => $catInfo['c_id']
  347. );
  348. }
  349. }
  350. return $result;
  351. }
  352. /**
  353. * @param Exercise $exercise_obj
  354. * @return array
  355. */
  356. public static function getListOfCategoriesForTest(Exercise $exercise_obj)
  357. {
  358. $result = array();
  359. $categories = self::getListOfCategoriesIDForTestObject($exercise_obj);
  360. foreach ($categories as $cat_id) {
  361. $cat = new TestCategory();
  362. $cat = (array) $cat->getCategory($cat_id);
  363. $cat['iid'] = $cat['id'];
  364. $cat['title'] = $cat['name'];
  365. $result[$cat['id']] = $cat;
  366. }
  367. return $result;
  368. }
  369. /**
  370. * return the number of differents categories for a test
  371. * input : test_id
  372. * return : integer
  373. * hubert.borderiou 07-04-2011
  374. */
  375. public static function getNumberOfCategoriesForTest($id)
  376. {
  377. return count(self::getListOfCategoriesIDForTest($id));
  378. }
  379. /**
  380. * return the number of question of a category id in a test
  381. * @param int $exerciseId
  382. * @param int $categoryId
  383. *
  384. * @return integer
  385. *
  386. * @author hubert.borderiou 07-04-2011
  387. */
  388. public static function getNumberOfQuestionsInCategoryForTest($exerciseId, $categoryId)
  389. {
  390. $nbCatResult = 0;
  391. $quiz = new Exercise();
  392. $quiz->read($exerciseId);
  393. $questionList = $quiz->selectQuestionList();
  394. // the array given by selectQuestionList start at indice 1 and not at indice 0 !!! ? ? ?
  395. for ($i = 1; $i <= count($questionList); $i++) {
  396. if (self::getCategoryForQuestion($questionList[$i]) == $categoryId) {
  397. $nbCatResult++;
  398. }
  399. }
  400. return $nbCatResult;
  401. }
  402. /**
  403. * return the number of question for a test using random by category
  404. * input : test_id, number of random question (min 1)
  405. * hubert.borderiou 07-04-2011
  406. * question without categories are not counted
  407. */
  408. public static function getNumberOfQuestionRandomByCategory($exerciseId, $in_nbrandom)
  409. {
  410. $count = 0;
  411. $categories = self::getListOfCategoriesIDForTest($exerciseId);
  412. foreach ($categories as $category) {
  413. if (empty($category['id'])) {
  414. continue;
  415. }
  416. $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
  417. $exerciseId,
  418. $category['id']
  419. );
  420. if ($nbQuestionInThisCat > $in_nbrandom) {
  421. $count += $in_nbrandom;
  422. } else {
  423. $count += $nbQuestionInThisCat;
  424. }
  425. }
  426. return $count;
  427. }
  428. /**
  429. * Return an array (id=>name)
  430. * tabresult[0] = get_lang('NoCategory');
  431. *
  432. * @param int $courseId
  433. *
  434. * @return array
  435. *
  436. */
  437. public static function getCategoriesIdAndName($courseId = 0)
  438. {
  439. if (empty($courseId)) {
  440. $courseId = api_get_course_int_id();
  441. }
  442. $categories = self::getCategoryListInfo('', $courseId);
  443. $result = array('0' => get_lang('NoCategorySelected'));
  444. for ($i = 0; $i < count($categories); $i++) {
  445. $result[$categories[$i]->id] = $categories[$i]->name;
  446. }
  447. return $result;
  448. }
  449. /**
  450. * Returns an array of question ids for each category
  451. * $categories[1][30] = 10, array with category id = 1 and question_id = 10
  452. * A question has "n" categories
  453. * @param int exercise
  454. * @param array $check_in_question_list
  455. * @param array $categoriesAddedInExercise
  456. *
  457. * @param int $exerciseId
  458. * @return array
  459. */
  460. public static function getQuestionsByCat(
  461. $exerciseId,
  462. $check_in_question_list = array(),
  463. $categoriesAddedInExercise = array()
  464. ) {
  465. $tableQuestion = Database::get_course_table(TABLE_QUIZ_QUESTION);
  466. $TBL_EXERCICE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  467. $TBL_QUESTION_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  468. $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  469. $exerciseId = intval($exerciseId);
  470. $courseId = api_get_course_int_id();
  471. $sql = "SELECT DISTINCT qrc.question_id, qrc.category_id
  472. FROM $TBL_QUESTION_REL_CATEGORY qrc
  473. INNER JOIN $TBL_EXERCICE_QUESTION eq
  474. ON (eq.question_id = qrc.question_id AND qrc.c_id = eq.c_id)
  475. INNER JOIN $categoryTable c
  476. ON (c.id = qrc.category_id AND c.c_id = eq.c_id)
  477. INNER JOIN $tableQuestion q
  478. ON (q.id = qrc.question_id AND q.c_id = eq.c_id)
  479. WHERE
  480. exercice_id = $exerciseId AND
  481. qrc.c_id = $courseId
  482. ";
  483. $res = Database::query($sql);
  484. $categories = array();
  485. while ($data = Database::fetch_array($res)) {
  486. if (!empty($check_in_question_list)) {
  487. if (!in_array($data['question_id'], $check_in_question_list)) {
  488. continue;
  489. }
  490. }
  491. if (!isset($categories[$data['category_id']]) ||
  492. !is_array($categories[$data['category_id']])
  493. ) {
  494. $categories[$data['category_id']] = array();
  495. }
  496. $categories[$data['category_id']][] = $data['question_id'];
  497. }
  498. if (!empty($categoriesAddedInExercise)) {
  499. $newCategoryList = array();
  500. foreach ($categoriesAddedInExercise as $category) {
  501. $categoryId = $category['category_id'];
  502. if (isset($categories[$categoryId])) {
  503. $newCategoryList[$categoryId] = $categories[$categoryId];
  504. }
  505. }
  506. $checkQuestionsWithNoCategory = false;
  507. foreach ($categoriesAddedInExercise as $category) {
  508. if (empty($category['category_id'])) {
  509. // Check
  510. $checkQuestionsWithNoCategory = true;
  511. break;
  512. }
  513. }
  514. // Select questions that don't have any category related
  515. if ($checkQuestionsWithNoCategory) {
  516. $originalQuestionList = $check_in_question_list;
  517. foreach ($originalQuestionList as $questionId) {
  518. $categoriesFlatten = array_flatten($categories);
  519. if (!in_array($questionId, $categoriesFlatten)) {
  520. $newCategoryList[0][] = $questionId;
  521. }
  522. }
  523. }
  524. $categories = $newCategoryList;
  525. }
  526. return $categories;
  527. }
  528. /**
  529. * Returns an array of $numberElements from $array
  530. * @param array
  531. * @param int
  532. *
  533. * @return array
  534. */
  535. public static function getNElementsFromArray($array, $numberElements)
  536. {
  537. $list = $array;
  538. shuffle($list);
  539. if ($numberElements < count($list)) {
  540. $list = array_slice($list, 0, $numberElements);
  541. }
  542. return $list;
  543. }
  544. /**
  545. * @param int $questionId
  546. * @param int $displayCategoryName
  547. */
  548. public static function displayCategoryAndTitle($questionId, $displayCategoryName = 1)
  549. {
  550. echo self::returnCategoryAndTitle($questionId, $displayCategoryName);
  551. }
  552. /**
  553. * @param int $questionId
  554. * @param int $in_display_category_name
  555. * @return null|string
  556. */
  557. public static function returnCategoryAndTitle($questionId, $in_display_category_name = 1)
  558. {
  559. $is_student = !(api_is_allowed_to_edit(null, true) || api_is_session_admin());
  560. // @todo fix $_SESSION['objExercise']
  561. $objExercise = Session::read('objExercise');
  562. if (!empty($objExercise)) {
  563. $in_display_category_name = $objExercise->display_category_name;
  564. }
  565. $content = null;
  566. if (self::getCategoryNameForQuestion($questionId) != '' &&
  567. ($in_display_category_name == 1 || !$is_student)
  568. ) {
  569. $content .= '<div class="page-header">';
  570. $content .= '<h4>'.get_lang('Category').": ".self::getCategoryNameForQuestion($questionId).'</h4>';
  571. $content .= "</div>";
  572. }
  573. return $content;
  574. }
  575. /**
  576. * Display signs [+] and/or (>0) after question title if question has options
  577. * scoreAlwaysPositive and/or uncheckedMayScore
  578. */
  579. public function displayQuestionOption($in_objQuestion)
  580. {
  581. if ($in_objQuestion->type == MULTIPLE_ANSWER && $in_objQuestion->scoreAlwaysPositive) {
  582. echo "<span style='font-size:75%'> (>0)</span>";
  583. }
  584. if ($in_objQuestion->type == MULTIPLE_ANSWER && $in_objQuestion->uncheckedMayScore) {
  585. echo "<span style='font-size:75%'> [+]</span>";
  586. }
  587. }
  588. /**
  589. * sortTabByBracketLabel ($tabCategoryQuestions)
  590. * key of $tabCategoryQuestions are the category id (0 for not in a category)
  591. * value is the array of question id of this category
  592. * Sort question by Category
  593. */
  594. public static function sortTabByBracketLabel($in_tab)
  595. {
  596. $tabResult = array();
  597. $tabCatName = array(); // tab of category name
  598. while (list($cat_id, $tabquestion) = each($in_tab)) {
  599. $category = new TestCategory();
  600. $category = $category->getCategory($cat_id);
  601. $tabCatName[$cat_id] = $category->name;
  602. }
  603. reset($in_tab);
  604. // sort table by value, keeping keys as they are
  605. asort($tabCatName);
  606. // keys of $tabCatName are keys order for $in_tab
  607. while (list($key, $val) = each($tabCatName)) {
  608. $tabResult[$key] = $in_tab[$key];
  609. }
  610. return $tabResult;
  611. }
  612. /**
  613. * return total score for test exe_id for all question in the category $in_cat_id for user
  614. * If no question for this category, return ""
  615. */
  616. public static function getCatScoreForExeidForUserid($in_cat_id, $in_exe_id, $in_user_id)
  617. {
  618. $tbl_track_attempt = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT);
  619. $tbl_question_rel_category = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  620. $in_cat_id = intval($in_cat_id);
  621. $in_exe_id = intval($in_exe_id);
  622. $in_user_id = intval($in_user_id);
  623. $query = "SELECT DISTINCT
  624. marks,
  625. exe_id,
  626. user_id,
  627. ta.question_id,
  628. category_id
  629. FROM $tbl_track_attempt ta
  630. INNER JOIN $tbl_question_rel_category qrc
  631. ON (ta.question_id=qrc.question_id)
  632. WHERE
  633. qrc.category_id = $in_cat_id AND
  634. exe_id = $in_exe_id AND
  635. user_id = $in_user_id";
  636. $res = Database::query($query);
  637. $score = '';
  638. while ($data = Database::fetch_array($res)) {
  639. $score += $data['marks'];
  640. }
  641. return $score;
  642. }
  643. /**
  644. * Return the number max of question in a category
  645. * count the number of questions in all categories, and return the max
  646. * @param int $exerciseId
  647. * @author - hubert borderiou
  648. * @return int
  649. */
  650. public static function getNumberMaxQuestionByCat($exerciseId)
  651. {
  652. $res_num_max = 0;
  653. // foreach question
  654. $categories = self::getListOfCategoriesIDForTest($exerciseId);
  655. foreach ($categories as $category) {
  656. if (empty($category['id'])) {
  657. continue;
  658. }
  659. $nbQuestionInThisCat = self::getNumberOfQuestionsInCategoryForTest(
  660. $exerciseId,
  661. $category['id']
  662. );
  663. if ($nbQuestionInThisCat > $res_num_max) {
  664. $res_num_max = $nbQuestionInThisCat;
  665. }
  666. }
  667. return $res_num_max;
  668. }
  669. /**
  670. * Returns a category summary report
  671. * @params int $exerciseId
  672. * @params array $category_list
  673. * pre filled array with the category_id, score, and weight
  674. * example: array(1 => array('score' => '10', 'total' => 20));
  675. *
  676. * @return string
  677. */
  678. public static function get_stats_table_by_attempt(
  679. $exerciseId,
  680. $category_list = array()
  681. ) {
  682. if (empty($category_list)) {
  683. return null;
  684. }
  685. $category_name_list = self::getListOfCategoriesNameForTest($exerciseId);
  686. $table = new HTML_Table(array('class' => 'data_table'));
  687. $table->setHeaderContents(0, 0, get_lang('Categories'));
  688. $table->setHeaderContents(0, 1, get_lang('AbsoluteScore'));
  689. $table->setHeaderContents(0, 2, get_lang('RelativeScore'));
  690. $row = 1;
  691. $none_category = array();
  692. if (isset($category_list['none'])) {
  693. $none_category = $category_list['none'];
  694. unset($category_list['none']);
  695. }
  696. $total = array();
  697. if (isset($category_list['total'])) {
  698. $total = $category_list['total'];
  699. unset($category_list['total']);
  700. }
  701. if (count($category_list) > 1) {
  702. foreach ($category_list as $category_id => $category_item) {
  703. $table->setCellContents($row, 0, $category_name_list[$category_id]);
  704. $table->setCellContents(
  705. $row,
  706. 1,
  707. ExerciseLib::show_score(
  708. $category_item['score'],
  709. $category_item['total'],
  710. false
  711. )
  712. );
  713. $table->setCellContents(
  714. $row,
  715. 2,
  716. ExerciseLib::show_score(
  717. $category_item['score'],
  718. $category_item['total'],
  719. true,
  720. false,
  721. true
  722. )
  723. );
  724. $row++;
  725. }
  726. if (!empty($none_category)) {
  727. $table->setCellContents($row, 0, get_lang('None'));
  728. $table->setCellContents(
  729. $row,
  730. 1,
  731. ExerciseLib::show_score(
  732. $none_category['score'],
  733. $none_category['total'],
  734. false
  735. )
  736. );
  737. $table->setCellContents(
  738. $row,
  739. 2,
  740. ExerciseLib::show_score(
  741. $none_category['score'],
  742. $none_category['total'],
  743. true,
  744. false,
  745. true
  746. )
  747. );
  748. $row++;
  749. }
  750. if (!empty($total)) {
  751. $table->setCellContents($row, 0, get_lang('Total'));
  752. $table->setCellContents(
  753. $row,
  754. 1,
  755. ExerciseLib::show_score(
  756. $total['score'],
  757. $total['total'],
  758. false
  759. )
  760. );
  761. $table->setCellContents(
  762. $row,
  763. 2,
  764. ExerciseLib::show_score(
  765. $total['score'],
  766. $total['total'],
  767. true,
  768. false,
  769. true
  770. )
  771. );
  772. }
  773. return $table->toHtml();
  774. }
  775. return '';
  776. }
  777. /**
  778. * @return array
  779. */
  780. public static function get_all_categories()
  781. {
  782. $table = Database::get_course_table(TABLE_QUIZ_CATEGORY);
  783. $sql = "SELECT * FROM $table ORDER BY title ASC";
  784. $res = Database::query($sql);
  785. $array = [];
  786. while ($row = Database::fetch_array($res, 'ASSOC')) {
  787. $array[] = $row;
  788. }
  789. return $array;
  790. }
  791. /**
  792. * @param Exercise $exercise
  793. * @param int $courseId
  794. * @param string $order
  795. * @param bool $shuffle
  796. * @param bool $excludeCategoryWithNoQuestions
  797. * @return array|bool
  798. */
  799. public function getCategoryExerciseTree(
  800. $exercise,
  801. $courseId,
  802. $order = null,
  803. $shuffle = false,
  804. $excludeCategoryWithNoQuestions = true
  805. ) {
  806. if (empty($exercise)) {
  807. return array();
  808. }
  809. $courseId = (int) $courseId;
  810. $table = Database::get_course_table(TABLE_QUIZ_REL_CATEGORY);
  811. $categoryTable = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  812. $sql = "SELECT * FROM $table qc
  813. LEFT JOIN $categoryTable c
  814. ON (qc.c_id = c.c_id AND c.id = qc.category_id)
  815. WHERE qc.c_id = $courseId AND exercise_id = {$exercise->id} ";
  816. if (!empty($order)) {
  817. $sql .= "ORDER BY $order";
  818. }
  819. $categories = array();
  820. $result = Database::query($sql);
  821. if (Database::num_rows($result)) {
  822. while ($row = Database::fetch_array($result, 'ASSOC')) {
  823. if ($excludeCategoryWithNoQuestions) {
  824. if ($row['count_questions'] == 0) {
  825. continue;
  826. }
  827. }
  828. if (empty($row['title']) && empty($row['category_id'])) {
  829. $row['title'] = get_lang('NoCategory');
  830. }
  831. $categories[$row['category_id']] = $row;
  832. }
  833. }
  834. if ($shuffle) {
  835. shuffle_assoc($categories);
  836. }
  837. return $categories;
  838. }
  839. /**
  840. * @param $form
  841. * @param string $action
  842. */
  843. public function getForm(& $form, $action = 'new')
  844. {
  845. switch ($action) {
  846. case 'new':
  847. $header = get_lang('AddACategory');
  848. $submit = get_lang('AddTestCategory');
  849. break;
  850. case 'edit':
  851. $header = get_lang('EditCategory');
  852. $submit = get_lang('ModifyCategory');
  853. break;
  854. }
  855. // Setting the form elements
  856. $form->addElement('header', $header);
  857. $form->addElement('hidden', 'category_id');
  858. $form->addElement(
  859. 'text',
  860. 'category_name',
  861. get_lang('CategoryName'),
  862. array('class' => 'span6')
  863. );
  864. $form->add_html_editor(
  865. 'category_description',
  866. get_lang('CategoryDescription'),
  867. false,
  868. false,
  869. array(
  870. 'ToolbarSet' => 'test_category',
  871. 'Width' => '90%',
  872. 'Height' => '200',
  873. )
  874. );
  875. $category_parent_list = array();
  876. $options = array(
  877. '1' => get_lang('Visible'),
  878. '0' => get_lang('Hidden')
  879. );
  880. $form->addElement(
  881. 'select',
  882. 'visibility',
  883. get_lang('Visibility'),
  884. $options
  885. );
  886. $script = null;
  887. if (!empty($this->parent_id)) {
  888. $parent_cat = new TestCategory();
  889. $parent_cat = $parent_cat->getCategory($this->parent_id);
  890. $category_parent_list = array($parent_cat->id => $parent_cat->name);
  891. $script .= '<script>$(function() { $("#parent_id").trigger("addItem",[{"title": "'.$parent_cat->name.'", "value": "'.$parent_cat->id.'"}]); });</script>';
  892. }
  893. $form->addElement('html', $script);
  894. $form->addElement('select', 'parent_id', get_lang('Parent'), $category_parent_list, array('id' => 'parent_id'));
  895. $form->addElement('style_submit_button', 'SubmitNote', $submit, 'class="add"');
  896. // setting the defaults
  897. $defaults = array();
  898. $defaults["category_id"] = $this->id;
  899. $defaults["category_name"] = $this->name;
  900. $defaults["category_description"] = $this->description;
  901. $defaults["parent_id"] = $this->parent_id;
  902. $defaults["visibility"] = $this->visibility;
  903. $form->setDefaults($defaults);
  904. // setting the rules
  905. $form->addRule('category_name', get_lang('ThisFieldIsRequired'), 'required');
  906. }
  907. /**
  908. * Returns the category form.
  909. * @param Exercise $exercise_obj
  910. * @return string
  911. */
  912. public function returnCategoryForm(Exercise $exercise_obj)
  913. {
  914. $categories = $this->getListOfCategoriesForTest($exercise_obj);
  915. $saved_categories = $exercise_obj->get_categories_in_exercise();
  916. $return = null;
  917. if (!empty($categories)) {
  918. $nbQuestionsTotal = $exercise_obj->getNumberQuestionExerciseCategory();
  919. $exercise_obj->setCategoriesGrouping(true);
  920. $real_question_count = count($exercise_obj->getQuestionList());
  921. $warning = null;
  922. if ($nbQuestionsTotal != $real_question_count) {
  923. $warning = Display::return_message(
  924. get_lang('CheckThatYouHaveEnoughQuestionsInYourCategories'),
  925. 'warning'
  926. );
  927. }
  928. $return .= $warning;
  929. $return .= '<table class="data_table">';
  930. $return .= '<tr>';
  931. $return .= '<th height="24">'.get_lang('Categories').'</th>';
  932. $return .= '<th width="70" height="24">'.get_lang('Number').'</th></tr>';
  933. $emptyCategory = array(
  934. 'id' => '0',
  935. 'name' => get_lang('NoCategory'),
  936. 'description' => '',
  937. 'iid' => '0',
  938. 'title' => get_lang('NoCategory')
  939. );
  940. $categories[] = $emptyCategory;
  941. foreach ($categories as $category) {
  942. $cat_id = $category['iid'];
  943. $return .= '<tr>';
  944. $return .= '<td>';
  945. //$return .= Display::div(isset($category['parent_path']) ? $category['parent_path'] : '');
  946. $return .= Display::div($category['name']);
  947. $return .= '</td>';
  948. $return .= '<td>';
  949. $value = isset($saved_categories) && isset($saved_categories[$cat_id]) ? $saved_categories[$cat_id]['count_questions'] : -1;
  950. $return .= '<input name="category['.$cat_id.']" value="'.$value.'" />';
  951. $return .= '</td>';
  952. $return .= '</tr>';
  953. }
  954. $return .= '</table>';
  955. $return .= get_lang('ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected');
  956. }
  957. return $return;
  958. }
  959. /**
  960. * Sorts an array
  961. * @param $array
  962. * @return mixed
  963. */
  964. public function sort_tree_array($array)
  965. {
  966. foreach ($array as $key => $row) {
  967. $parent[$key] = $row['parent_id'];
  968. }
  969. if (count($array) > 0) {
  970. array_multisort($parent, SORT_ASC, $array);
  971. }
  972. return $array;
  973. }
  974. /**
  975. * Return true if a category already exists with the same name
  976. * @param string $name
  977. * @param int $courseId
  978. *
  979. * @return bool
  980. */
  981. public static function categoryTitleExists($name, $courseId = 0)
  982. {
  983. $categories = self::getCategoryListInfo('title', $courseId);
  984. foreach ($categories as $title) {
  985. if ($title == $name) {
  986. return true;
  987. }
  988. }
  989. return false;
  990. }
  991. /**
  992. * Return the id of the test category with title = $in_title
  993. * @param string $title
  994. * @param int $courseId
  995. *
  996. * @return int is id of test category
  997. */
  998. public static function get_category_id_for_title($title, $courseId = 0)
  999. {
  1000. $out_res = 0;
  1001. if (empty($courseId)) {
  1002. $courseId = api_get_course_int_id();
  1003. }
  1004. $courseId = intval($courseId);
  1005. $tbl_cat = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  1006. $sql = "SELECT id FROM $tbl_cat
  1007. WHERE c_id = $courseId AND title = '".Database::escape_string($title)."'";
  1008. $res = Database::query($sql);
  1009. if (Database::num_rows($res) > 0) {
  1010. $data = Database::fetch_array($res);
  1011. $out_res = $data['id'];
  1012. }
  1013. return $out_res;
  1014. }
  1015. /**
  1016. * Add a relation between question and category in table c_quiz_question_rel_category
  1017. * @param int $categoryId
  1018. * @param int $questionId
  1019. * @param int $courseId
  1020. *
  1021. * @return string|false
  1022. */
  1023. public static function addCategoryToQuestion($categoryId, $questionId, $courseId)
  1024. {
  1025. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  1026. // if question doesn't have a category
  1027. // @todo change for 1.10 when a question can have several categories
  1028. if (self::getCategoryForQuestion($questionId, $courseId) == 0 &&
  1029. $questionId > 0 &&
  1030. $courseId > 0
  1031. ) {
  1032. $sql = "INSERT INTO $table (c_id, question_id, category_id)
  1033. VALUES (".intval($courseId).", ".intval($questionId).", ".intval($categoryId).")";
  1034. Database::query($sql);
  1035. $id = Database::insert_id();
  1036. return $id;
  1037. }
  1038. return false;
  1039. }
  1040. /**
  1041. * @param int $courseId
  1042. * @param int $sessionId
  1043. *
  1044. * @return array
  1045. */
  1046. public function getCategories($courseId, $sessionId = 0)
  1047. {
  1048. $table = Database::get_course_table(TABLE_QUIZ_QUESTION_CATEGORY);
  1049. $itemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
  1050. $sessionId = intval($sessionId);
  1051. $courseId = intval($courseId);
  1052. if (empty($sessionId)) {
  1053. $sessionCondition = api_get_session_condition(
  1054. $sessionId,
  1055. true,
  1056. false,
  1057. 'i.session_id'
  1058. );
  1059. } else {
  1060. $sessionCondition = api_get_session_condition(
  1061. $sessionId,
  1062. true,
  1063. true,
  1064. 'i.session_id'
  1065. );
  1066. }
  1067. if (empty($courseId)) {
  1068. return array();
  1069. }
  1070. $sql = "SELECT c.* FROM $table c
  1071. INNER JOIN $itemProperty i
  1072. ON c.c_id = i.c_id AND i.ref = c.id
  1073. WHERE
  1074. c.c_id = $courseId AND
  1075. i.tool = '".TOOL_TEST_CATEGORY."'
  1076. $sessionCondition
  1077. ORDER BY title";
  1078. $result = Database::query($sql);
  1079. return Database::store_result($result, 'ASSOC');
  1080. }
  1081. /**
  1082. * @param int $courseId
  1083. * @param int $sessionId
  1084. * @return string
  1085. */
  1086. public function displayCategories($courseId, $sessionId = 0)
  1087. {
  1088. $categories = $this->getCategories($courseId, $sessionId);
  1089. $html = '';
  1090. foreach ($categories as $category) {
  1091. $tmpobj = new TestCategory();
  1092. $tmpobj = $tmpobj->getCategory($category['id']);
  1093. $nb_question = $tmpobj->getCategoryQuestionsNumber();
  1094. $rowname = self::protectJSDialogQuote($category['title']);
  1095. $nb_question_label = $nb_question == 1 ? $nb_question.' '.get_lang('Question') : $nb_question.' '.get_lang('Questions');
  1096. $content = "<span style='float:right'>".$nb_question_label."</span>";
  1097. $content .= '<div class="sectioncomment">';
  1098. $content .= $category['description'];
  1099. $content .= '</div>';
  1100. $links = '<a href="'.api_get_self().'?action=editcategory&category_id='.$category['id'].'&'.api_get_cidreq().'">'.
  1101. Display::return_icon('edit.png', get_lang('Edit'), array(), ICON_SIZE_SMALL).'</a>';
  1102. $links .= ' <a href="'.api_get_self().'?'.api_get_cidreq().'&action=deletecategory&category_id='.$category['id'].'" ';
  1103. $links .= 'onclick="return confirmDelete(\''.self::protectJSDialogQuote(get_lang('DeleteCategoryAreYouSure').'['.$rowname).'] ?\', \'id_cat'.$category['id'].'\');">';
  1104. $links .= Display::return_icon('delete.png', get_lang('Delete'), array(), ICON_SIZE_SMALL).'</a>';
  1105. $html .= Display::panel($content, $category['title'].$links);
  1106. }
  1107. return $html;
  1108. }
  1109. // To allowed " in javascript dialog box without bad surprises
  1110. // replace " with two '
  1111. public function protectJSDialogQuote($in_txt)
  1112. {
  1113. $res = $in_txt;
  1114. $res = str_replace("'", "\'", $res);
  1115. $res = str_replace('"', "\'\'", $res); // super astuce pour afficher les " dans les boite de dialogue
  1116. return $res;
  1117. }
  1118. }