question_pool.php 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. use ChamiloSession as Session;
  4. use Knp\Component\Pager\Paginator;
  5. /**
  6. * Question Pool
  7. * This script allows administrators to manage questions and add them into their exercises.
  8. * One question can be in several exercises.
  9. *
  10. * @package chamilo.exercise
  11. *
  12. * @author Olivier Brouckaert
  13. * @author Julio Montoya adding support to query all questions from all session, courses, exercises
  14. * @author Modify by hubert borderiou 2011-10-21 Question's category
  15. */
  16. require_once __DIR__.'/../inc/global.inc.php';
  17. api_protect_course_script(true);
  18. $this_section = SECTION_COURSES;
  19. $is_allowedToEdit = api_is_allowed_to_edit(null, true);
  20. $delete = isset($_GET['delete']) ? intval($_GET['delete']) : null;
  21. $recup = isset($_GET['recup']) ? intval($_GET['recup']) : null;
  22. $fromExercise = isset($_REQUEST['fromExercise']) ? intval($_REQUEST['fromExercise']) : null;
  23. $exerciseId = isset($_REQUEST['exerciseId']) ? intval($_REQUEST['exerciseId']) : null;
  24. $courseCategoryId = isset($_REQUEST['courseCategoryId']) ? intval($_REQUEST['courseCategoryId']) : null;
  25. $exerciseLevel = isset($_REQUEST['exerciseLevel']) ? intval($_REQUEST['exerciseLevel']) : -1;
  26. $answerType = isset($_REQUEST['answerType']) ? intval($_REQUEST['answerType']) : null;
  27. $question_copy = isset($_REQUEST['question_copy']) ? intval($_REQUEST['question_copy']) : 0;
  28. $session_id = isset($_REQUEST['session_id']) ? intval($_REQUEST['session_id']) : null;
  29. $selected_course = isset($_GET['selected_course']) ? intval($_GET['selected_course']) : null;
  30. // save the id of the previous course selected by user to reset menu if we detect that user change course hub 13-10-2011
  31. $course_id_changed = isset($_GET['course_id_changed']) ? intval($_GET['course_id_changed']) : null;
  32. // save the id of the previous exercise selected by user to reset menu if we detect that user change course hub 13-10-2011
  33. $exercise_id_changed = isset($_GET['exercise_id_changed']) ? intval($_GET['exercise_id_changed']) : null;
  34. $questionId = isset($_GET['question_id']) && !empty($_GET['question_id']) ? (int) $_GET['question_id'] : '';
  35. $description = isset($_GET['description']) ? Database::escape_string($_GET['description']) : '';
  36. $page = isset($_GET['page']) ? (int) $_GET['page'] : 1;
  37. // by default when we go to the page for the first time, we select the current course
  38. if (!isset($_GET['selected_course']) && !isset($_GET['exerciseId'])) {
  39. $selected_course = api_get_course_int_id();
  40. }
  41. $_course = api_get_course_info();
  42. $objExercise = new Exercise();
  43. if (!empty($fromExercise)) {
  44. $objExercise->read($fromExercise, false);
  45. }
  46. $nameTools = get_lang('QuestionPool');
  47. $interbreadcrumb[] = ['url' => 'exercise.php?'.api_get_cidreq(), 'name' => get_lang('Exercises')];
  48. if (!empty($objExercise->id)) {
  49. $interbreadcrumb[] = [
  50. 'url' => 'admin.php?exerciseId='.$objExercise->id.'&'.api_get_cidreq(),
  51. 'name' => $objExercise->selectTitle(true),
  52. ];
  53. }
  54. // message to be displayed if actions successful
  55. $displayMessage = '';
  56. if ($is_allowedToEdit) {
  57. // Duplicating a Question
  58. if (!isset($_POST['recup']) && $question_copy != 0 && isset($fromExercise)) {
  59. $origin_course_id = (int) $_GET['course_id'];
  60. $origin_course_info = api_get_course_info_by_id($origin_course_id);
  61. $current_course = api_get_course_info();
  62. $old_question_id = $question_copy;
  63. // Reading the source question
  64. $old_question_obj = Question::read($old_question_id, $origin_course_info);
  65. $courseId = $current_course['real_id'];
  66. if ($old_question_obj) {
  67. $old_question_obj->updateTitle($old_question_obj->selectTitle().' - '.get_lang('Copy'));
  68. //Duplicating the source question, in the current course
  69. $new_id = $old_question_obj->duplicate($current_course);
  70. //Reading new question
  71. $new_question_obj = Question::read($new_id);
  72. $new_question_obj->addToList($fromExercise);
  73. //Reading Answers obj of the current course
  74. $new_answer_obj = new Answer($old_question_id, $origin_course_id);
  75. $new_answer_obj->read();
  76. //Duplicating the Answers in the current course
  77. $new_answer_obj->duplicate($new_question_obj, $current_course);
  78. // destruction of the Question object
  79. unset($new_question_obj);
  80. unset($old_question_obj);
  81. $objExercise = new Exercise($courseId);
  82. $objExercise->read($fromExercise);
  83. Session::write('objExercise', $objExercise);
  84. }
  85. $displayMessage = get_lang('ItemAdded');
  86. }
  87. // Deletes a question from the database and all exercises
  88. if ($delete) {
  89. $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
  90. if ($limitTeacherAccess && !api_is_platform_admin()) {
  91. api_not_allowed(true);
  92. }
  93. // Construction of the Question object
  94. $objQuestionTmp = Question::read($delete);
  95. // if the question exists
  96. if ($objQuestionTmp) {
  97. // deletes the question from all exercises
  98. $objQuestionTmp->delete();
  99. }
  100. // destruction of the Question object
  101. unset($objQuestionTmp);
  102. } elseif ($recup && $fromExercise) {
  103. // gets an existing question and copies it into a new exercise
  104. $objQuestionTmp = Question :: read($recup);
  105. // if the question exists
  106. if ($objQuestionTmp) {
  107. /* Adds the exercise ID represented by $fromExercise into the list
  108. of exercises for the current question */
  109. $objQuestionTmp->addToList($fromExercise);
  110. }
  111. // destruction of the Question object
  112. unset($objQuestionTmp);
  113. if (!$objExercise instanceof Exercise) {
  114. $objExercise = new Exercise();
  115. $objExercise->read($fromExercise);
  116. }
  117. // Adds the question ID represented by $recup into the list of questions for the current exercise
  118. $objExercise->addToList($recup);
  119. Session::write('objExercise', $objExercise);
  120. Display::addFlash(
  121. Display::return_message(get_lang('ItemAdded'), 'success')
  122. );
  123. } elseif (isset($_POST['recup']) && is_array($_POST['recup']) && $fromExercise) {
  124. $list_recup = $_POST['recup'];
  125. foreach ($list_recup as $course_id => $question_data) {
  126. $origin_course_id = (int) $course_id;
  127. $origin_course_info = api_get_course_info_by_id($origin_course_id);
  128. $current_course = api_get_course_info();
  129. foreach ($question_data as $old_question_id) {
  130. //Reading the source question
  131. $old_question_obj = Question::read($old_question_id, $origin_course_info);
  132. if ($old_question_obj) {
  133. $old_question_obj->updateTitle(
  134. $old_question_obj->selectTitle().' - '.get_lang('Copy')
  135. );
  136. // Duplicating the source question, in the current course
  137. $new_id = $old_question_obj->duplicate($current_course);
  138. // Reading new question
  139. $new_question_obj = Question::read($new_id);
  140. $new_question_obj->addToList($fromExercise);
  141. //Reading Answers obj of the current course
  142. $new_answer_obj = new Answer($old_question_id, $origin_course_id);
  143. $new_answer_obj->read();
  144. //Duplicating the Answers in the current course
  145. $new_answer_obj->duplicate($new_question_obj, $current_course);
  146. // destruction of the Question object
  147. unset($new_question_obj);
  148. unset($old_question_obj);
  149. if (!$objExercise instanceof Exercise) {
  150. $objExercise = new Exercise();
  151. $objExercise->read($fromExercise);
  152. }
  153. }
  154. }
  155. }
  156. Session::write('objExercise', $objExercise);
  157. }
  158. }
  159. if (api_is_in_gradebook()) {
  160. $interbreadcrumb[] = [
  161. 'url' => Category::getUrl(),
  162. 'name' => get_lang('ToolGradebook'),
  163. ];
  164. }
  165. // if admin of course
  166. if (!$is_allowedToEdit) {
  167. api_not_allowed(true);
  168. }
  169. $confirmYourChoice = addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset));
  170. $htmlHeadXtra[] = "
  171. <script>
  172. function submit_form(obj) {
  173. document.question_pool.submit();
  174. }
  175. function mark_course_id_changed() {
  176. $('#course_id_changed').val('1');
  177. }
  178. function mark_exercise_id_changed() {
  179. $('#exercise_id_changed').val('1');
  180. }
  181. function confirm_your_choice() {
  182. return confirm('$confirmYourChoice');
  183. }
  184. </script>";
  185. Display::display_header($nameTools, 'Exercise');
  186. // Menu
  187. echo '<div class="actions">';
  188. $url = api_get_self().'?'.api_get_cidreq().'&'.http_build_query(
  189. [
  190. 'fromExercise' => $fromExercise,
  191. 'session_id' => $session_id,
  192. 'selected_course' => $selected_course,
  193. 'courseCategoryId' => $courseCategoryId,
  194. 'exerciseId' => $exerciseId,
  195. 'exerciseLevel' => $exerciseLevel,
  196. 'answerType' => $answerType,
  197. 'question_id' => $questionId,
  198. 'description' => Security::remove_XSS($description),
  199. 'course_id_changed' => $course_id_changed,
  200. 'exercise_id_changed' => $exercise_id_changed,
  201. ]
  202. );
  203. if (isset($fromExercise) && $fromExercise > 0) {
  204. echo '<a href="admin.php?'.api_get_cidreq().'&exerciseId='.$fromExercise.'">'.
  205. Display::return_icon('back.png', get_lang('GoBackToQuestionList'), '', ICON_SIZE_MEDIUM).'</a>';
  206. $titleAdd = get_lang('AddQuestionToTest');
  207. } else {
  208. echo '<a href="exercise.php?'.api_get_cidreq().'">'.
  209. Display::return_icon('back.png', get_lang('BackToExercisesList'), '', ICON_SIZE_MEDIUM).'</a>';
  210. echo "<a href='admin.php?exerciseId=0'>".
  211. Display::return_icon('add_question.gif', get_lang('NewQu'), '', ICON_SIZE_MEDIUM)."</a>";
  212. $titleAdd = get_lang('ManageAllQuestions');
  213. }
  214. echo '</div>';
  215. if ($displayMessage != '') {
  216. echo Display::return_message($displayMessage, 'confirm');
  217. $displayMessage = '';
  218. }
  219. // Form
  220. echo '<form class="form-horizontal" name="question_pool" method="GET" action="'.$url.'">';
  221. // Title
  222. echo '<legend>'.$nameTools.' - '.$titleAdd.'</legend>';
  223. echo '<input type="hidden" name="fromExercise" value="'.$fromExercise.'">';
  224. // Session list, if sessions are used.
  225. $sessionList = SessionManager::get_sessions_by_user(api_get_user_id(), api_is_platform_admin());
  226. $session_select_list = [];
  227. foreach ($sessionList as $item) {
  228. $session_select_list[$item['session_id']] = $item['session_name'];
  229. }
  230. $select_session_html = Display::select(
  231. 'session_id',
  232. $session_select_list,
  233. $session_id,
  234. ['onchange' => 'submit_form(this)']
  235. );
  236. echo Display::form_row(get_lang('Session'), $select_session_html);
  237. // Course list, get course list of session, or for course where user is admin
  238. if (!empty($session_id) && $session_id != '-1' && !empty($sessionList)) {
  239. $sessionInfo = [];
  240. foreach ($sessionList as $session) {
  241. if ($session['session_id'] == $session_id) {
  242. $sessionInfo = $session;
  243. }
  244. }
  245. $course_list = $sessionInfo['courses'];
  246. } else {
  247. if (api_is_platform_admin()) {
  248. $course_list = CourseManager::get_courses_list(0, 0, 'title');
  249. } else {
  250. $course_list = CourseManager::get_course_list_of_user_as_course_admin(api_get_user_id());
  251. }
  252. // Admin fix, add the current course in the question pool.
  253. if (api_is_platform_admin()) {
  254. $courseInfo = api_get_course_info();
  255. if (!empty($course_list)) {
  256. if (!in_array($courseInfo['real_id'], $course_list)) {
  257. $course_list = array_merge($course_list, [$courseInfo]);
  258. }
  259. } else {
  260. $course_list = [$courseInfo];
  261. }
  262. }
  263. }
  264. $course_select_list = [];
  265. foreach ($course_list as $item) {
  266. $courseItemId = $item['real_id'];
  267. $courseInfo = api_get_course_info_by_id($courseItemId);
  268. $course_select_list[$courseItemId] = '';
  269. if ($courseItemId == api_get_course_int_id()) {
  270. $course_select_list[$courseItemId] = ">&nbsp;&nbsp;&nbsp;&nbsp;";
  271. }
  272. $course_select_list[$courseItemId] .= $courseInfo['title'];
  273. }
  274. $select_course_html = Display::select(
  275. 'selected_course',
  276. $course_select_list,
  277. $selected_course,
  278. ['onchange' => 'mark_course_id_changed(); submit_form(this);']
  279. );
  280. echo Display::form_row(get_lang('Course'), $select_course_html);
  281. if (empty($selected_course) || $selected_course == '-1') {
  282. $course_info = api_get_course_info();
  283. // no course selected, reset menu test / difficult� / type de reponse
  284. reset_menu_exo_lvl_type();
  285. } else {
  286. $course_info = api_get_course_info_by_id($selected_course);
  287. }
  288. // If course has changed, reset the menu default
  289. if ($course_id_changed) {
  290. reset_menu_exo_lvl_type();
  291. }
  292. $course_id = $course_info['real_id'];
  293. // Get category list for the course $selected_course
  294. $categoryList = TestCategory::getCategoriesIdAndName($selected_course);
  295. $selectCourseCategory = Display::select(
  296. 'courseCategoryId',
  297. $categoryList,
  298. $courseCategoryId,
  299. ['onchange' => 'submit_form(this);'],
  300. false
  301. );
  302. echo Display::form_row(get_lang('QuestionCategory'), $selectCourseCategory);
  303. // Get exercise list for this course
  304. $exercise_list = ExerciseLib::get_all_exercises_for_course_id(
  305. $course_info,
  306. $session_id,
  307. $selected_course,
  308. false
  309. );
  310. // Exercise List
  311. $my_exercise_list = [];
  312. $my_exercise_list['0'] = get_lang('AllExercises');
  313. $my_exercise_list['-1'] = get_lang('OrphanQuestions');
  314. if (is_array($exercise_list)) {
  315. foreach ($exercise_list as $row) {
  316. $my_exercise_list[$row['id']] = '';
  317. if ($row['id'] == $fromExercise && $selected_course == api_get_course_int_id()) {
  318. $my_exercise_list[$row['id']] = ">&nbsp;&nbsp;&nbsp;&nbsp;";
  319. }
  320. $my_exercise_list[$row['id']] .= $row['title'];
  321. }
  322. }
  323. if ($exercise_id_changed == 1) {
  324. reset_menu_lvl_type();
  325. }
  326. $select_exercise_html = Display::select(
  327. 'exerciseId',
  328. $my_exercise_list,
  329. $exerciseId,
  330. ['onchange' => 'mark_exercise_id_changed(); submit_form(this);'],
  331. false
  332. );
  333. echo Display::form_row(get_lang('Exercise'), $select_exercise_html);
  334. // Difficulty list (only from 0 to 5)
  335. $levels = [
  336. -1 => get_lang('All'),
  337. 0 => 0,
  338. 1 => 1,
  339. 2 => 2,
  340. 3 => 3,
  341. 4 => 4,
  342. 5 => 5,
  343. ];
  344. $select_difficulty_html = Display::select(
  345. 'exerciseLevel',
  346. $levels,
  347. $exerciseLevel,
  348. ['onchange' => 'submit_form(this);'],
  349. false
  350. );
  351. echo Display::form_row(get_lang('Difficulty'), $select_difficulty_html);
  352. // Answer type
  353. $question_list = Question::get_question_type_list();
  354. $new_question_list = [];
  355. $new_question_list['-1'] = get_lang('All');
  356. if (!empty($_course)) {
  357. foreach ($question_list as $key => $item) {
  358. if ($objExercise->feedback_type == EXERCISE_FEEDBACK_TYPE_DIRECT) {
  359. if (!in_array($key, [HOT_SPOT_DELINEATION, UNIQUE_ANSWER])) {
  360. continue;
  361. }
  362. $new_question_list[$key] = get_lang($item[1]);
  363. } else {
  364. if ($key == HOT_SPOT_DELINEATION) {
  365. continue;
  366. }
  367. $new_question_list[$key] = get_lang($item[1]);
  368. }
  369. }
  370. }
  371. // Answer type list
  372. $select_answer_html = Display::select(
  373. 'answerType',
  374. $new_question_list,
  375. $answerType,
  376. ['onchange' => 'submit_form(this);'],
  377. false
  378. );
  379. echo Display::form_row(get_lang('AnswerType'), $select_answer_html);
  380. echo Display::form_row(get_lang('Id'), Display::input('text', 'question_id', $questionId));
  381. echo Display::form_row(
  382. get_lang('Description'),
  383. Display::input('text', 'description', Security::remove_XSS($description))
  384. );
  385. $button = '<button class="btn btn-primary save" type="submit" name="name" value="'.get_lang('Filter').'">'.
  386. get_lang('Filter').'</button>';
  387. echo Display::form_row('', $button);
  388. echo "<input type='hidden' id='course_id_changed' name='course_id_changed' value='0' />";
  389. echo "<input type='hidden' id='exercise_id_changed' name='exercise_id_changed' value='0' />";
  390. ?>
  391. </form>
  392. <div class="clear"></div>
  393. <?php
  394. function getQuestions(
  395. $getCount,
  396. $start,
  397. $length,
  398. $exerciseId,
  399. $courseCategoryId,
  400. $selected_course,
  401. $session_id,
  402. $exerciseLevel,
  403. $answerType,
  404. $questionId,
  405. $description
  406. ) {
  407. $start = (int) $start;
  408. $length = (int) $length;
  409. $exerciseId = (int) $exerciseId;
  410. $courseCategoryId = (int) $courseCategoryId;
  411. $selected_course = (int) $selected_course;
  412. $session_id = (int) $session_id;
  413. $exerciseLevel = (int) $exerciseLevel;
  414. $answerType = (int) $answerType;
  415. $questionId = (int) $questionId;
  416. $description = Database::escape_string($description);
  417. $TBL_EXERCISE_QUESTION = Database::get_course_table(TABLE_QUIZ_TEST_QUESTION);
  418. $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
  419. $TBL_QUESTIONS = Database::get_course_table(TABLE_QUIZ_QUESTION);
  420. $TBL_COURSE_REL_CATEGORY = Database::get_course_table(TABLE_QUIZ_QUESTION_REL_CATEGORY);
  421. // if we have selected an exercise in the list-box 'Filter'
  422. if ($exerciseId > 0) {
  423. $where = '';
  424. $from = '';
  425. if (isset($courseCategoryId) && $courseCategoryId > 0) {
  426. $from = ", $TBL_COURSE_REL_CATEGORY crc ";
  427. $where .= " AND
  428. crc.c_id = $selected_course AND
  429. crc.question_id = qu.id AND
  430. crc.category_id = $courseCategoryId";
  431. }
  432. if (isset($exerciseLevel) && $exerciseLevel != -1) {
  433. $where .= ' AND level='.$exerciseLevel;
  434. }
  435. if (isset($answerType) && $answerType > 0) {
  436. $where .= ' AND type='.$answerType;
  437. }
  438. if (!empty($questionId)) {
  439. $where .= ' AND qu.iid='.$questionId;
  440. }
  441. if (!empty($description)) {
  442. $where .= " AND qu.description LIKE '%$description%'";
  443. }
  444. $select = 'DISTINCT
  445. id,
  446. question,
  447. type,
  448. level,
  449. qt.exercice_id exerciseId';
  450. if ($getCount) {
  451. $select = 'count(qu.iid) as count';
  452. }
  453. $sql = "SELECT $select
  454. FROM
  455. $TBL_EXERCISE_QUESTION qt,
  456. $TBL_QUESTIONS qu
  457. $from
  458. WHERE
  459. qt.question_id = qu.id AND
  460. qt.exercice_id = $exerciseId AND
  461. qt.c_id = $selected_course AND
  462. qu.c_id = $selected_course
  463. $where
  464. ORDER BY question_order";
  465. } elseif ($exerciseId == -1) {
  466. // If we have selected the option 'Orphan questions' in the list-box 'Filter'
  467. $level_where = '';
  468. $from = '';
  469. if (isset($courseCategoryId) && $courseCategoryId > 0) {
  470. $from = " INNER JOIN $TBL_COURSE_REL_CATEGORY crc
  471. ON crc.question_id = q.id AND crc.c_id = q.c_id ";
  472. $level_where .= " AND
  473. crc.c_id = $selected_course AND
  474. crc.category_id = $courseCategoryId";
  475. }
  476. if (isset($exerciseLevel) && $exerciseLevel != -1) {
  477. $level_where = ' AND level='.$exerciseLevel;
  478. }
  479. $answer_where = '';
  480. if (isset($answerType) && $answerType > 0 - 1) {
  481. $answer_where = ' AND type='.$answerType;
  482. }
  483. if (!empty($questionId)) {
  484. $answer_where .= ' AND q.iid='.$questionId;
  485. }
  486. if (!empty($description)) {
  487. $answer_where .= " AND q.description LIKE '%$description%'";
  488. }
  489. $select = ' q.*, r.exercice_id exerciseId ';
  490. if ($getCount) {
  491. $select = 'count(q.iid) as count';
  492. }
  493. // @todo fix this query with the new id field
  494. $sql = " (
  495. SELECT $select
  496. FROM $TBL_QUESTIONS q
  497. INNER JOIN $TBL_EXERCISE_QUESTION r
  498. ON (q.c_id = r.c_id AND q.id = r.question_id)
  499. INNER JOIN $TBL_EXERCISES ex
  500. ON (ex.id = r.exercice_id AND ex.c_id = r.c_id)
  501. $from
  502. WHERE
  503. ex.c_id = '$selected_course' AND
  504. ex.active = '-1'
  505. $level_where
  506. $answer_where
  507. )
  508. UNION
  509. (
  510. SELECT $select
  511. FROM $TBL_QUESTIONS q
  512. LEFT OUTER JOIN $TBL_EXERCISE_QUESTION r
  513. ON (q.c_id = r.c_id AND q.id = r.question_id)
  514. $from
  515. WHERE
  516. q.c_id = '$selected_course' AND
  517. r.question_id is null
  518. $level_where
  519. $answer_where
  520. )
  521. UNION
  522. (
  523. SELECT $select
  524. FROM $TBL_QUESTIONS q
  525. INNER JOIN $TBL_EXERCISE_QUESTION r
  526. ON (q.c_id = r.c_id AND q.id = r.question_id)
  527. $from
  528. WHERE
  529. r.c_id = '$selected_course' AND
  530. (r.exercice_id = '-1' OR r.exercice_id = '0')
  531. $level_where
  532. $answer_where
  533. )
  534. ";
  535. if ($getCount) {
  536. $sql = "SELECT SUM(count) count FROM ($sql) as total";
  537. }
  538. } else {
  539. // All tests for selected course
  540. // If we have not selected any option in the list-box 'Filter'
  541. $filter = '';
  542. $from = '';
  543. if (isset($courseCategoryId) && $courseCategoryId > 0) {
  544. $from = ", $TBL_COURSE_REL_CATEGORY crc ";
  545. $filter .= " AND
  546. crc.c_id = $selected_course AND
  547. crc.question_id = qu.id AND
  548. crc.category_id = $courseCategoryId";
  549. }
  550. if (isset($exerciseLevel) && $exerciseLevel != -1) {
  551. $filter .= ' AND level='.$exerciseLevel.' ';
  552. }
  553. if (isset($answerType) && $answerType > 0) {
  554. $filter .= ' AND qu.type='.$answerType.' ';
  555. }
  556. if (!empty($questionId)) {
  557. $filter .= ' AND qu.iid='.$questionId;
  558. }
  559. if (!empty($description)) {
  560. $filter .= " AND qu.description LIKE '%$description%'";
  561. }
  562. if ($session_id == -1 || empty($session_id)) {
  563. $session_id = 0;
  564. }
  565. $sessionCondition = api_get_session_condition($session_id, true, 'q.session_id');
  566. $select = 'qu.id, question, qu.type, level, q.session_id, qt.exercice_id exerciseId ';
  567. if ($getCount) {
  568. $select = 'count(qu.iid) as count';
  569. }
  570. // All tests for the course selected, not in session
  571. $sql = "SELECT DISTINCT
  572. $select
  573. FROM
  574. $TBL_QUESTIONS as qu,
  575. $TBL_EXERCISE_QUESTION as qt,
  576. $TBL_EXERCISES as q
  577. $from
  578. WHERE
  579. qu.c_id = $selected_course AND
  580. qt.c_id = $selected_course AND
  581. q.c_id = $selected_course AND
  582. qu.id = qt.question_id
  583. $sessionCondition AND
  584. q.id = qt.exercice_id
  585. $filter
  586. ORDER BY session_id ASC";
  587. }
  588. if ($getCount) {
  589. $result = Database::query($sql);
  590. $row = Database::fetch_array($result, 'ASSOC');
  591. return (int) $row['count'];
  592. }
  593. $sql .= " LIMIT $start, $length";
  594. $result = Database::query($sql);
  595. $mainQuestionList = [];
  596. while ($row = Database::fetch_array($result, 'ASSOC')) {
  597. $mainQuestionList[] = $row;
  598. }
  599. return $mainQuestionList;
  600. }
  601. $nbrQuestions = getQuestions(
  602. true,
  603. null,
  604. null,
  605. $exerciseId,
  606. $courseCategoryId,
  607. $selected_course,
  608. $session_id,
  609. $exerciseLevel,
  610. $answerType,
  611. $questionId,
  612. $description
  613. );
  614. $length = api_get_configuration_value('question_pagination_length');
  615. if (empty($length)) {
  616. $length = 20;
  617. }
  618. $start = ($page - 1) * $length;
  619. $paginator = new Paginator();
  620. $pagination = $paginator->paginate([]);
  621. $pagination->setTotalItemCount($nbrQuestions);
  622. $pagination->setItemNumberPerPage($length);
  623. $pagination->setCurrentPageNumber($page);
  624. $pagination->renderer = function ($data) use ($url) {
  625. $render = '';
  626. if ($data['pageCount'] > 1) {
  627. $render = '<ul class="pagination">';
  628. for ($i = 1; $i <= $data['pageCount']; $i++) {
  629. $pageContent = '<li><a href="'.$url.'&page='.$i.'">'.$i.'</a></li>';
  630. if ($data['current'] == $i) {
  631. $pageContent = '<li class="active"><a href="#" >'.$i.'</a></li>';
  632. }
  633. $render .= $pageContent;
  634. }
  635. $render .= '</ul>';
  636. }
  637. return $render;
  638. };
  639. $mainQuestionList = getQuestions(
  640. false,
  641. $start,
  642. $length,
  643. $exerciseId,
  644. $courseCategoryId,
  645. $selected_course,
  646. $session_id,
  647. $exerciseLevel,
  648. $answerType,
  649. $questionId,
  650. $description
  651. );
  652. // build the line of the array to display questions
  653. // Actions are different if you launch the question_pool page
  654. // They are different too if you have displayed questions from your course
  655. // Or from another course you are the admin(or session admin)
  656. // from a test or not
  657. /*
  658. +--------------------------------------------+--------------------------------------------+
  659. | NOT IN A TEST | IN A TEST |
  660. +----------------------+---------------------+---------------------+----------------------+
  661. |IN THE COURSE (*) "x | NOT IN THE COURSE o | IN THE COURSE + | NOT IN THE COURSE o |
  662. +----------------------+---------------------+---------------------+----------------------+
  663. |Edit the question | Do nothing | Add question to test|Clone question in test|
  664. |Delete the question | | | |
  665. |(true delete) | | | |
  666. +----------------------+---------------------+---------------------+----------------------+
  667. (*) this is the only way to delete or modify orphan questions
  668. */
  669. if ($fromExercise <= 0) {
  670. // NOT IN A TEST - NOT IN THE COURSE
  671. $actionLabel = get_lang('Reuse');
  672. $actionIcon1 = get_lang('MustBeInATest');
  673. $actionIcon2 = '';
  674. // We are not in this course, to messy if we link to the question in another course
  675. $questionTagA = 0;
  676. if ($selected_course == api_get_course_int_id()) {
  677. // NOT IN A TEST - IN THE COURSE
  678. $actionLabel = get_lang('Modify');
  679. $actionIcon1 = 'edit';
  680. $actionIcon2 = 'delete';
  681. // We are in the course, question title can be a link to the question edit page
  682. $questionTagA = 1;
  683. }
  684. } else {
  685. // IN A TEST - NOT IN THE COURSE
  686. $actionLabel = get_lang('Reuse');
  687. $actionIcon1 = 'clone';
  688. $actionIcon2 = '';
  689. $questionTagA = 0;
  690. if ($selected_course == api_get_course_int_id()) {
  691. // IN A TEST - IN THE COURSE
  692. $actionLabel = get_lang('Reuse');
  693. $actionIcon1 = 'add';
  694. $actionIcon2 = '';
  695. $questionTagA = 1;
  696. }
  697. }
  698. $data = [];
  699. if (is_array($mainQuestionList)) {
  700. foreach ($mainQuestionList as $question) {
  701. $row = [];
  702. // This function checks if the question can be read
  703. $question_type = get_question_type_for_question($selected_course, $question['id']);
  704. if (empty($question_type)) {
  705. continue;
  706. }
  707. $sessionId = isset($question['session_id']) ? $question['session_id'] : null;
  708. $exerciseName = isset($question['exercise_name']) ? '<br />('.$question['exercise_id'].') ' : null;
  709. $row[] = getLinkForQuestion(
  710. $questionTagA,
  711. $fromExercise,
  712. $question['id'],
  713. $question['type'],
  714. $question['question'],
  715. $sessionId,
  716. $question['exerciseId']
  717. ).$exerciseName;
  718. $row[] = $question_type;
  719. $row[] = TestCategory::getCategoryNameForQuestion($question['id'], $selected_course);
  720. $row[] = $question['level'];
  721. $row[] = get_action_icon_for_question(
  722. $actionIcon1,
  723. $fromExercise,
  724. $question['id'],
  725. $question['type'],
  726. $question['question'],
  727. $selected_course,
  728. $courseCategoryId,
  729. $exerciseLevel,
  730. $answerType,
  731. $session_id,
  732. $question['exerciseId'],
  733. $objExercise
  734. ).'&nbsp;'.
  735. get_action_icon_for_question(
  736. $actionIcon2,
  737. $fromExercise,
  738. $question['id'],
  739. $question['type'],
  740. $question['question'],
  741. $selected_course,
  742. $courseCategoryId,
  743. $exerciseLevel,
  744. $answerType,
  745. $session_id,
  746. $question['exerciseId'],
  747. $objExercise
  748. );
  749. $data[] = $row;
  750. }
  751. }
  752. // Display table
  753. $header = [
  754. [
  755. get_lang('QuestionUpperCaseFirstLetter'),
  756. false,
  757. ['style' => 'text-align:center'],
  758. '',
  759. ],
  760. [
  761. get_lang('Type'),
  762. false,
  763. ['style' => 'text-align:center'],
  764. ['style' => 'text-align:center'],
  765. '',
  766. ],
  767. [
  768. get_lang('QuestionCategory'),
  769. false,
  770. ['style' => 'text-align:center'],
  771. ['style' => 'text-align:center'],
  772. '',
  773. ],
  774. [
  775. get_lang('Difficulty'),
  776. false,
  777. ['style' => 'text-align:center'],
  778. ['style' => 'text-align:center'],
  779. '',
  780. ],
  781. [
  782. $actionLabel,
  783. false,
  784. ['style' => 'text-align:center'],
  785. ['style' => 'text-align:center'],
  786. '',
  787. ],
  788. ];
  789. echo $pagination;
  790. Display::display_sortable_table(
  791. $header,
  792. $data,
  793. '',
  794. ['per_page_default' => 999, 'per_page' => 999, 'page_nr' => 1]
  795. );
  796. Display::display_footer();
  797. /**
  798. * Put the menu entry for level and type to default "Choice"
  799. * It is useful if you change the exercise, you need to reset the other menus.
  800. *
  801. * @author hubert.borderiou 13-10-2011
  802. */
  803. function reset_menu_lvl_type()
  804. {
  805. global $exerciseLevel, $answerType;
  806. $answerType = -1;
  807. $exerciseLevel = -1;
  808. }
  809. /**
  810. * Put the menu entry for exercise and level and type to default "Choice"
  811. * It is useful if you change the course, you need to reset the other menus.
  812. *
  813. * @author hubert.borderiou 13-10-2011
  814. */
  815. function reset_menu_exo_lvl_type()
  816. {
  817. global $exerciseId, $courseCategoryId;
  818. reset_menu_lvl_type();
  819. $exerciseId = 0;
  820. $courseCategoryId = 0;
  821. }
  822. /**
  823. * return the <a> link to admin question, if needed.
  824. *
  825. * @param int $in_addA
  826. * @param int $in_fromex
  827. * @param int $questionId
  828. * @param int $questiontype
  829. * @param string $questionName
  830. * @param int $sessionId
  831. * @param int $exerciseId
  832. *
  833. * @return string
  834. *
  835. * @author hubert.borderiou
  836. */
  837. function getLinkForQuestion(
  838. $in_addA,
  839. $fromExercise,
  840. $questionId,
  841. $questionType,
  842. $questionName,
  843. $sessionId,
  844. $exerciseId
  845. ) {
  846. $result = $questionName;
  847. if ($in_addA) {
  848. $sessionIcon = '';
  849. if (!empty($sessionId) && $sessionId != -1) {
  850. $sessionIcon = ' '.Display::return_icon('star.png', get_lang('Session'));
  851. }
  852. $exerciseId = (int) $exerciseId;
  853. $questionId = (int) $questionId;
  854. $questionType = (int) $questionType;
  855. $fromExercise = (int) $fromExercise;
  856. $result = Display::url(
  857. $questionName.$sessionIcon,
  858. "admin.php?".api_get_cidreq().
  859. "&exerciseId=$exerciseId&editQuestion=$questionId&type=$questionType&fromExercise=$fromExercise"
  860. );
  861. }
  862. return $result;
  863. }
  864. /**
  865. Return the <a> html code for delete, add, clone, edit a question
  866. in_action = the code of the action triggered by the button
  867. from_exercise = the id of the current exercise from which we click on question pool
  868. in_questionid = the id of the current question
  869. in_questiontype = the code of the type of the current question
  870. in_questionname = the name of the question
  871. in_selected_course = the if of the course chosen in the FILTERING MENU
  872. in_courseCategoryId = the id of the category chosen in the FILTERING MENU
  873. in_exerciseLevel = the level of the exercise chosen in the FILTERING MENU
  874. in_answerType = the code of the type of the question chosen in the FILTERING MENU
  875. in_session_id = the id of the session_id chosen in the FILTERING MENU
  876. in_exercise_id = the id of the exercise chosen in the FILTERING MENU
  877. */
  878. function get_action_icon_for_question(
  879. $in_action,
  880. $from_exercise,
  881. $in_questionid,
  882. $in_questiontype,
  883. $in_questionname,
  884. $in_selected_course,
  885. $in_courseCategoryId,
  886. $in_exerciseLevel,
  887. $in_answerType,
  888. $in_session_id,
  889. $in_exercise_id,
  890. Exercise $myObjEx
  891. ) {
  892. $limitTeacherAccess = api_get_configuration_value('limit_exercise_teacher_access');
  893. $getParams = "&selected_course=$in_selected_course&courseCategoryId=$in_courseCategoryId&exerciseId=$in_exercise_id&exerciseLevel=$in_exerciseLevel&answerType=$in_answerType&session_id=$in_session_id";
  894. $res = '';
  895. switch ($in_action) {
  896. case 'delete':
  897. if ($limitTeacherAccess && !api_is_platform_admin()) {
  898. break;
  899. }
  900. $res = "<a href='".api_get_self()."?".
  901. api_get_cidreq().$getParams."&delete=$in_questionid' onclick='return confirm_your_choice()'>";
  902. $res .= Display::return_icon('delete.png', get_lang('Delete'));
  903. $res .= "</a>";
  904. break;
  905. case 'edit':
  906. $res = getLinkForQuestion(
  907. 1,
  908. $from_exercise,
  909. $in_questionid,
  910. $in_questiontype,
  911. Display::return_icon('edit.png', get_lang('Modify')),
  912. $in_session_id,
  913. $in_exercise_id
  914. );
  915. break;
  916. case 'add':
  917. $res = '-';
  918. if (!$myObjEx->hasQuestion($in_questionid)) {
  919. $res = "<a href='".api_get_self()."?".
  920. api_get_cidreq().$getParams."&recup=$in_questionid&fromExercise=$from_exercise'>";
  921. $res .= Display::return_icon('view_more_stats.gif', get_lang('InsertALinkToThisQuestionInTheExercise'));
  922. $res .= "</a>";
  923. }
  924. break;
  925. case 'clone':
  926. $url = api_get_self().'?'.api_get_cidreq().$getParams.
  927. "&question_copy=$in_questionid&course_id=$in_selected_course&fromExercise=$from_exercise";
  928. $res = Display::url(
  929. Display::return_icon('cd.png', get_lang('ReUseACopyInCurrentTest')),
  930. $url
  931. );
  932. break;
  933. default:
  934. $res = $in_action;
  935. break;
  936. }
  937. return $res;
  938. }
  939. /**
  940. * Return the icon for the question type.
  941. *
  942. * @author hubert.borderiou 13-10-2011
  943. */
  944. function get_question_type_for_question($in_selectedcourse, $in_questionid)
  945. {
  946. $courseInfo = api_get_course_info_by_id($in_selectedcourse);
  947. $myObjQuestion = Question::read($in_questionid, $courseInfo);
  948. $questionType = null;
  949. if (!empty($myObjQuestion)) {
  950. list($typeImg, $typeExpl) = $myObjQuestion->get_type_icon_html();
  951. $questionType = Display::tag('div', Display::return_icon($typeImg, $typeExpl, [], 32), []);
  952. }
  953. return $questionType;
  954. }