exercise_result.class.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class ExerciseResult
  5. * which allows you to export exercises results in multiple presentation forms.
  6. *
  7. * @package chamilo.exercise
  8. *
  9. * @author Yannick Warnier
  10. */
  11. class ExerciseResult
  12. {
  13. public $includeAllUsers = false;
  14. public $onlyBestAttempts = false;
  15. private $results = [];
  16. /**
  17. * @param bool $includeAllUsers
  18. */
  19. public function setIncludeAllUsers($includeAllUsers)
  20. {
  21. $this->includeAllUsers = $includeAllUsers;
  22. }
  23. /**
  24. * @param bool $value
  25. */
  26. public function setOnlyBestAttempts($value)
  27. {
  28. $this->onlyBestAttempts = $value;
  29. }
  30. /**
  31. * Gets the results of all students (or just one student if access is limited).
  32. *
  33. * @param string $document_path The document path (for HotPotatoes retrieval)
  34. * @param int $user_id User ID. Optional. If no user ID is provided, we take all the results. Defauts to null
  35. * @param int $filter
  36. * @param int $exercise_id
  37. *
  38. * @return bool
  39. */
  40. public function getExercisesReporting(
  41. $document_path,
  42. $user_id = null,
  43. $filter = 0,
  44. $exercise_id = 0
  45. ) {
  46. $return = [];
  47. $TBL_EXERCISES = Database::get_course_table(TABLE_QUIZ_TEST);
  48. $TBL_TABLE_LP_MAIN = Database::get_course_table(TABLE_LP_MAIN);
  49. $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
  50. $TBL_TRACK_EXERCISES = Database::get_main_table(TABLE_STATISTIC_TRACK_E_EXERCISES);
  51. $TBL_TRACK_ATTEMPT_RECORDING = Database::get_main_table(TABLE_STATISTIC_TRACK_E_ATTEMPT_RECORDING);
  52. $cid = api_get_course_id();
  53. $course_id = api_get_course_int_id();
  54. $user_id = intval($user_id);
  55. $sessionId = api_get_session_id();
  56. $session_id_and = ' AND te.session_id = '.$sessionId.' ';
  57. $exercise_id = intval($exercise_id);
  58. if (!empty($exercise_id)) {
  59. $session_id_and .= " AND exe_exo_id = $exercise_id ";
  60. }
  61. if (empty($user_id)) {
  62. $user_id_and = null;
  63. $sql = "SELECT ".(api_is_western_name_order() ? "firstname as userpart1, lastname userpart2" : "lastname as userpart1, firstname as userpart2").",
  64. official_code,
  65. ce.title as extitle,
  66. te.score as exresult ,
  67. te.max_score as exweight,
  68. te.exe_date as exdate,
  69. te.exe_id as exid,
  70. email as exemail,
  71. te.start_date as exstart,
  72. steps_counter as exstep,
  73. exe_user_id as excruid,
  74. te.exe_duration as duration,
  75. te.orig_lp_id as orig_lp_id,
  76. tlm.name as lp_name,
  77. user.username
  78. FROM $TBL_EXERCISES AS ce
  79. INNER JOIN $TBL_TRACK_EXERCISES AS te
  80. ON (te.exe_exo_id = ce.id)
  81. INNER JOIN $TBL_USER AS user
  82. ON (user.user_id = exe_user_id)
  83. LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm
  84. ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
  85. WHERE
  86. ce.c_id = $course_id AND
  87. te.status != 'incomplete' AND
  88. te.c_id = ce.c_id $user_id_and $session_id_and AND
  89. ce.active <>-1";
  90. } else {
  91. $user_id_and = ' AND te.exe_user_id = '.api_get_user_id().' ';
  92. // get only this user's results
  93. $sql = "SELECT ".(api_is_western_name_order() ? "firstname as userpart1, lastname userpart2" : "lastname as userpart1, firstname as userpart2").",
  94. official_code,
  95. ce.title as extitle,
  96. te.score as exresult,
  97. te.max_score as exweight,
  98. te.exe_date as exdate,
  99. te.exe_id as exid,
  100. email as exemail,
  101. te.start_date as exstart,
  102. steps_counter as exstep,
  103. exe_user_id as excruid,
  104. te.exe_duration as duration,
  105. ce.results_disabled as exdisabled,
  106. te.orig_lp_id as orig_lp_id,
  107. tlm.name as lp_name,
  108. user.username
  109. FROM $TBL_EXERCISES AS ce
  110. INNER JOIN $TBL_TRACK_EXERCISES AS te
  111. ON (te.exe_exo_id = ce.id)
  112. INNER JOIN $TBL_USER AS user
  113. ON (user.user_id = exe_user_id)
  114. LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm
  115. ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
  116. WHERE
  117. ce.c_id = $course_id AND
  118. te.status != 'incomplete' AND
  119. te.c_id = ce.c_id $user_id_and $session_id_and AND
  120. ce.active <>-1 AND
  121. ORDER BY userpart2, te.c_id ASC, ce.title ASC, te.exe_date DESC";
  122. }
  123. $results = [];
  124. $resx = Database::query($sql);
  125. $bestAttemptPerUser = [];
  126. while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
  127. if ($this->onlyBestAttempts) {
  128. if (!isset($bestAttemptPerUser[$rowx['excruid']])) {
  129. $bestAttemptPerUser[$rowx['excruid']] = $rowx;
  130. } else {
  131. if ($rowx['exresult'] > $bestAttemptPerUser[$rowx['excruid']]['exresult']) {
  132. $bestAttemptPerUser[$rowx['excruid']] = $rowx;
  133. }
  134. }
  135. } else {
  136. $results[] = $rowx;
  137. }
  138. }
  139. if ($this->onlyBestAttempts) {
  140. // Remove userId indexes to avoid issues in output
  141. foreach ($bestAttemptPerUser as $attempt) {
  142. $results[] = $attempt;
  143. }
  144. }
  145. $filter_by_not_revised = false;
  146. $filter_by_revised = false;
  147. if ($filter) {
  148. switch ($filter) {
  149. case 1:
  150. $filter_by_not_revised = true;
  151. break;
  152. case 2:
  153. $filter_by_revised = true;
  154. break;
  155. default:
  156. null;
  157. }
  158. }
  159. if (empty($sessionId)) {
  160. $students = CourseManager::get_user_list_from_course_code($cid);
  161. } else {
  162. $students = CourseManager::get_user_list_from_course_code($cid, $sessionId);
  163. }
  164. $studentsUserIdList = array_keys($students);
  165. // Print the results of tests
  166. $userWithResults = [];
  167. if (is_array($results)) {
  168. $i = 0;
  169. foreach ($results as $result) {
  170. $revised = false;
  171. //revised or not
  172. $sql_exe = "SELECT exe_id
  173. FROM $TBL_TRACK_ATTEMPT_RECORDING
  174. WHERE
  175. author != '' AND
  176. exe_id = ".Database::escape_string($result['exid'])."
  177. LIMIT 1";
  178. $query = Database::query($sql_exe);
  179. if (Database:: num_rows($query) > 0) {
  180. $revised = true;
  181. }
  182. if ($filter_by_not_revised && $revised) {
  183. continue;
  184. }
  185. if ($filter_by_revised && !$revised) {
  186. continue;
  187. }
  188. $return[$i] = [];
  189. if (empty($user_id)) {
  190. $return[$i]['official_code'] = $result['official_code'];
  191. if (api_is_western_name_order()) {
  192. $return[$i]['first_name'] = $results[$i]['userpart1'];
  193. $return[$i]['last_name'] = $results[$i]['userpart2'];
  194. } else {
  195. $return[$i]['first_name'] = $results[$i]['userpart2'];
  196. $return[$i]['last_name'] = $results[$i]['userpart1'];
  197. }
  198. $return[$i]['user_id'] = $results[$i]['excruid'];
  199. $return[$i]['email'] = $results[$i]['exemail'];
  200. $return[$i]['username'] = $results[$i]['username'];
  201. }
  202. $return[$i]['title'] = $result['extitle'];
  203. $return[$i]['start_date'] = api_get_local_time($result['exstart']);
  204. $return[$i]['end_date'] = api_get_local_time($result['exdate']);
  205. $return[$i]['duration'] = $result['duration'];
  206. $return[$i]['result'] = $result['exresult'];
  207. $return[$i]['max'] = $result['exweight'];
  208. $return[$i]['status'] = $revised ? get_lang('Validated') : get_lang('Not validated');
  209. $return[$i]['lp_id'] = $result['orig_lp_id'];
  210. $return[$i]['lp_name'] = $result['lp_name'];
  211. if (in_array($result['excruid'], $studentsUserIdList)) {
  212. $return[$i]['is_user_subscribed'] = get_lang('Yes');
  213. } else {
  214. $return[$i]['is_user_subscribed'] = get_lang('No');
  215. }
  216. $userWithResults[$result['excruid']] = 1;
  217. $i++;
  218. }
  219. }
  220. if ($this->includeAllUsers) {
  221. $latestId = count($return);
  222. $userWithResults = array_keys($userWithResults);
  223. if (!empty($students)) {
  224. foreach ($students as $student) {
  225. if (!in_array($student['user_id'], $userWithResults)) {
  226. $i = $latestId;
  227. $isWestern = api_is_western_name_order();
  228. if (empty($user_id)) {
  229. $return[$i]['official_code'] = $student['official_code'];
  230. if ($isWestern) {
  231. $return[$i]['first_name'] = $student['firstname'];
  232. $return[$i]['last_name'] = $student['lastname'];
  233. } else {
  234. $return[$i]['first_name'] = $student['lastname'];
  235. $return[$i]['last_name'] = $student['firstname'];
  236. }
  237. $return[$i]['user_id'] = $student['user_id'];
  238. $return[$i]['email'] = $student['email'];
  239. $return[$i]['username'] = $student[$i]['username'];
  240. }
  241. $return[$i]['title'] = null;
  242. $return[$i]['start_date'] = null;
  243. $return[$i]['end_date'] = null;
  244. $return[$i]['duration'] = null;
  245. $return[$i]['result'] = null;
  246. $return[$i]['max'] = null;
  247. $return[$i]['status'] = get_lang('Not attempted');
  248. $return[$i]['lp_id'] = null;
  249. $return[$i]['lp_name'] = null;
  250. $return[$i]['is_user_subscribed'] = get_lang('Yes');
  251. $latestId++;
  252. }
  253. }
  254. }
  255. }
  256. $this->results = $return;
  257. return true;
  258. }
  259. /**
  260. * Exports the complete report as a CSV file.
  261. *
  262. * @param string $document_path Document path inside the document tool
  263. * @param int $user_id Optional user ID
  264. * @param bool $export_user_fields Whether to include user fields or not
  265. * @param int $export_filter
  266. * @param int $exercise_id
  267. *
  268. * @return bool False on error
  269. */
  270. public function exportCompleteReportCSV(
  271. $document_path = '',
  272. $user_id = null,
  273. $export_user_fields = false,
  274. $export_filter = 0,
  275. $exercise_id = 0
  276. ) {
  277. global $charset;
  278. $this->getExercisesReporting(
  279. $document_path,
  280. $user_id,
  281. $export_filter,
  282. $exercise_id
  283. );
  284. $now = api_get_local_time();
  285. $filename = 'exercise_results_'.$now.'.csv';
  286. if (!empty($user_id)) {
  287. $filename = 'exercise_results_user_'.$user_id.'_'.$now.'.csv';
  288. }
  289. $filename = api_replace_dangerous_char($filename);
  290. $data = '';
  291. if (api_is_western_name_order()) {
  292. if (!empty($this->results[0]['first_name'])) {
  293. $data .= get_lang('First name').';';
  294. }
  295. if (!empty($this->results[0]['last_name'])) {
  296. $data .= get_lang('Last name').';';
  297. }
  298. } else {
  299. if (!empty($this->results[0]['last_name'])) {
  300. $data .= get_lang('Last name').';';
  301. }
  302. if (!empty($this->results[0]['first_name'])) {
  303. $data .= get_lang('First name').';';
  304. }
  305. }
  306. $officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
  307. if ($officialCodeInList === 'true') {
  308. $data .= get_lang('Code').';';
  309. }
  310. $data .= get_lang('Login').';';
  311. $data .= get_lang('e-mail').';';
  312. $data .= get_lang('Groups').';';
  313. if ($export_user_fields) {
  314. //show user fields section with a big th colspan that spans over all fields
  315. $extra_user_fields = UserManager::get_extra_fields(
  316. 0,
  317. 1000,
  318. 5,
  319. 'ASC',
  320. false,
  321. 1
  322. );
  323. if (!empty($extra_user_fields)) {
  324. foreach ($extra_user_fields as $field) {
  325. $data .= '"'.str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($field[3]), ENT_QUOTES, $charset)).'";';
  326. }
  327. }
  328. }
  329. $data .= get_lang('Title').';';
  330. $data .= get_lang('Start Date').';';
  331. $data .= get_lang('End Date').';';
  332. $data .= get_lang('Duration').' ('.get_lang('minutes').') ;';
  333. $data .= get_lang('Score').';';
  334. $data .= get_lang('Total').';';
  335. $data .= get_lang('Status').';';
  336. $data .= get_lang('Learning path').';';
  337. $data .= get_lang('The user is currently subscribed').';';
  338. $data .= "\n";
  339. //results
  340. foreach ($this->results as $row) {
  341. if (api_is_western_name_order()) {
  342. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
  343. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['last_name']), ENT_QUOTES, $charset)).';';
  344. } else {
  345. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['last_name']), ENT_QUOTES, $charset)).';';
  346. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
  347. }
  348. // Official code
  349. if ($officialCodeInList === 'true') {
  350. $data .= $row['official_code'].';';
  351. }
  352. // e-mail
  353. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['username']), ENT_QUOTES, $charset)).';';
  354. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['email']), ENT_QUOTES, $charset)).';';
  355. $data .= str_replace("\r\n", ' ', implode(", ", GroupManager::get_user_group_name($row['user_id']))).';';
  356. if ($export_user_fields) {
  357. //show user fields data, if any, for this user
  358. $user_fields_values = UserManager::get_extra_user_data(
  359. $row['user_id'],
  360. false,
  361. false,
  362. false,
  363. true
  364. );
  365. if (!empty($user_fields_values)) {
  366. foreach ($user_fields_values as $value) {
  367. $data .= '"'.str_replace('"', '""', api_html_entity_decode(strip_tags($value), ENT_QUOTES, $charset)).'";';
  368. }
  369. }
  370. }
  371. $duration = !empty($row['duration']) ? round($row['duration'] / 60) : 0;
  372. $data .= str_replace("\r\n", ' ', api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset)).';';
  373. $data .= str_replace("\r\n", ' ', $row['start_date']).';';
  374. $data .= str_replace("\r\n", ' ', $row['end_date']).';';
  375. $data .= str_replace("\r\n", ' ', $duration).';';
  376. $data .= str_replace("\r\n", ' ', $row['result']).';';
  377. $data .= str_replace("\r\n", ' ', $row['max']).';';
  378. $data .= str_replace("\r\n", ' ', $row['status']).';';
  379. $data .= str_replace("\r\n", ' ', $row['lp_name']).';';
  380. $data .= str_replace("\r\n", ' ', $row['is_user_subscribed']).';';
  381. $data .= "\n";
  382. }
  383. // output the results
  384. $len = strlen($data);
  385. header('Content-type: application/octet-stream');
  386. header('Content-Type: application/force-download');
  387. header('Content-length: '.$len);
  388. if (preg_match("/MSIE 5.5/", $_SERVER['HTTP_USER_AGENT'])) {
  389. header('Content-Disposition: filename= '.$filename);
  390. } else {
  391. header('Content-Disposition: attachment; filename= '.$filename);
  392. }
  393. if (strpos($_SERVER['HTTP_USER_AGENT'], 'MSIE')) {
  394. header('Pragma: ');
  395. header('Cache-Control: ');
  396. header('Cache-Control: public'); // IE cannot download from sessions without a cache
  397. }
  398. header('Content-Description: '.$filename);
  399. header('Content-transfer-encoding: binary');
  400. echo $data;
  401. return true;
  402. }
  403. /**
  404. * Exports the complete report as an XLS file.
  405. *
  406. * @param string $document_path
  407. * @param null $user_id
  408. * @param bool $export_user_fields
  409. * @param int $export_filter
  410. * @param int $exercise_id
  411. * @param null $hotpotato_name
  412. *
  413. * @return bool
  414. */
  415. public function exportCompleteReportXLS(
  416. $document_path = '',
  417. $user_id = null,
  418. $export_user_fields = false,
  419. $export_filter = 0,
  420. $exercise_id = 0,
  421. $hotpotato_name = null
  422. ) {
  423. global $charset;
  424. $this->getExercisesReporting(
  425. $document_path,
  426. $user_id,
  427. $export_filter,
  428. $exercise_id,
  429. $hotpotato_name
  430. );
  431. $now = api_get_local_time();
  432. $filename = 'exercise_results_'.$now;
  433. if (!empty($user_id)) {
  434. $filename = 'exercise_results_user_'.$user_id.'_'.$now;
  435. }
  436. // check if exists column 'user'
  437. $withColumnUser = false;
  438. foreach ($this->results as $result) {
  439. if (!empty($result['last_name']) && !empty($result['first_name'])) {
  440. $withColumnUser = true;
  441. break;
  442. }
  443. }
  444. $officialCodeInList = api_get_setting('show_official_code_exercise_result_list');
  445. $list = [];
  446. if ($withColumnUser) {
  447. if (api_is_western_name_order()) {
  448. $list[0][] = get_lang('First name');
  449. $list[0][] = get_lang('Last name');
  450. } else {
  451. $list[0][] = get_lang('Last name');
  452. $list[0][] = get_lang('First name');
  453. }
  454. if ($officialCodeInList === 'true') {
  455. $list[0][] = get_lang('Code');
  456. }
  457. $list[0][] = get_lang('Login');
  458. $list[0][] = get_lang('e-mail');
  459. }
  460. $list[0][] = get_lang('Groups');
  461. if ($export_user_fields) {
  462. //show user fields section with a big th colspan that spans over all fields
  463. $extra_user_fields = UserManager::get_extra_fields(
  464. 0,
  465. 1000,
  466. 5,
  467. 'ASC',
  468. false,
  469. 1
  470. );
  471. //show the fields names for user fields
  472. foreach ($extra_user_fields as $field) {
  473. $list[0][] = api_html_entity_decode(
  474. strip_tags($field[3]),
  475. ENT_QUOTES,
  476. $charset
  477. );
  478. }
  479. }
  480. $list[0][] = get_lang('Title');
  481. $list[0][] = get_lang('Start Date');
  482. $list[0][] = get_lang('End Date');
  483. $list[0][] = get_lang('Duration').' ('.get_lang('minutes').')';
  484. $list[0][] = get_lang('Score');
  485. $list[0][] = get_lang('Total');
  486. $list[0][] = get_lang('Status');
  487. $list[0][] = get_lang('Learning path');
  488. $list[0][] = get_lang('The user is currently subscribed');
  489. $column = 1;
  490. foreach ($this->results as $row) {
  491. if ($withColumnUser) {
  492. if (api_is_western_name_order()) {
  493. $list[$column][] = api_html_entity_decode(
  494. strip_tags($row['first_name']),
  495. ENT_QUOTES,
  496. $charset
  497. );
  498. $list[$column][] = api_html_entity_decode(
  499. strip_tags($row['last_name']),
  500. ENT_QUOTES,
  501. $charset
  502. );
  503. } else {
  504. $list[$column][] = api_html_entity_decode(
  505. strip_tags($row['last_name']),
  506. ENT_QUOTES,
  507. $charset
  508. );
  509. $list[$column][] = api_html_entity_decode(
  510. strip_tags($row['first_name']),
  511. ENT_QUOTES,
  512. $charset
  513. );
  514. }
  515. if ($officialCodeInList === 'true') {
  516. $list[$column][] = api_html_entity_decode(
  517. strip_tags($row['official_code']),
  518. ENT_QUOTES,
  519. $charset
  520. );
  521. }
  522. $list[$column][] = api_html_entity_decode(
  523. strip_tags($row['username']),
  524. ENT_QUOTES,
  525. $charset
  526. );
  527. $list[$column][] = api_html_entity_decode(
  528. strip_tags($row['email']),
  529. ENT_QUOTES,
  530. $charset
  531. );
  532. }
  533. $list[$column][] = api_html_entity_decode(
  534. strip_tags(
  535. implode(
  536. ', ',
  537. GroupManager:: get_user_group_name($row['user_id'])
  538. )
  539. ),
  540. ENT_QUOTES,
  541. $charset
  542. );
  543. if ($export_user_fields) {
  544. // show user fields data, if any, for this user
  545. $values = UserManager::get_extra_user_data(
  546. $row['user_id'],
  547. false,
  548. false,
  549. false,
  550. true
  551. );
  552. foreach ($values as $value) {
  553. $list[$column][] = api_html_entity_decode(
  554. strip_tags($value),
  555. ENT_QUOTES,
  556. $charset
  557. );
  558. }
  559. }
  560. $list[$column][] = api_html_entity_decode(
  561. strip_tags($row['title']),
  562. ENT_QUOTES,
  563. $charset
  564. );
  565. $list[$column][] = $row['start_date'];
  566. $list[$column][] = $row['end_date'];
  567. $duration = !empty($row['duration']) ? round($row['duration'] / 60) : 0;
  568. $list[$column][] = $duration;
  569. $list[$column][] = $row['result'];
  570. $list[$column][] = $row['max'];
  571. $list[$column][] = $row['status'];
  572. $list[$column][] = $row['lp_name'];
  573. $list[$column][] = $row['is_user_subscribed'];
  574. $column++;
  575. }
  576. Export::arrayToXls($list, $filename);
  577. return true;
  578. }
  579. }