gradebooktable.class.php 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277
  1. <?php
  2. /* For licensing terms, see license.txt */
  3. use ChamiloSession as Session;
  4. use CpChart\Cache as pCache;
  5. use CpChart\Data as pData;
  6. use CpChart\Image as pImage;
  7. /**
  8. * GradebookTable Class
  9. * Table to display categories, evaluations and links.
  10. *
  11. * @author Stijn Konings
  12. * @author Bert Steppé (refactored, optimised)
  13. *
  14. * @package chamilo.gradebook
  15. */
  16. class GradebookTable extends SortableTable
  17. {
  18. public $cats;
  19. public $exportToPdf;
  20. public $teacherView;
  21. public $userId;
  22. public $studentList = [];
  23. private $currentcat;
  24. private $datagen;
  25. private $evals_links;
  26. private $dataForGraph;
  27. /**
  28. * @var array Indicates which columns should be shown in gradebook
  29. *
  30. * @example [1] For add Ranking column
  31. * [2] For add Best Score column
  32. * [3] For add Average column
  33. */
  34. private $loadStats = [];
  35. /**
  36. * GradebookTable constructor.
  37. *
  38. * @param Category $currentcat
  39. * @param array $cats
  40. * @param array $evals
  41. * @param array $links
  42. * @param null $addparams
  43. * @param bool $exportToPdf
  44. * @param null $showTeacherView
  45. * @param int $userId
  46. * @param array $studentList
  47. * @param array $loadStats
  48. */
  49. public function __construct(
  50. $currentcat,
  51. $cats = [],
  52. $evals = [],
  53. $links = [],
  54. $addparams = null,
  55. $exportToPdf = false,
  56. $showTeacherView = null,
  57. $userId = null,
  58. $studentList = [],
  59. array $loadStats = []
  60. ) {
  61. $this->teacherView = is_null($showTeacherView) ? api_is_allowed_to_edit(null, true) : $showTeacherView;
  62. $this->userId = is_null($userId) ? api_get_user_id() : $userId;
  63. $this->exportToPdf = $exportToPdf;
  64. $this->studentList = $studentList;
  65. parent::__construct(
  66. 'gradebooklist',
  67. null,
  68. null,
  69. api_is_allowed_to_edit() ? 1 : 0,
  70. 20,
  71. 'ASC',
  72. 'gradebook_list'
  73. );
  74. $this->evals_links = array_merge($evals, $links);
  75. $this->currentcat = $currentcat;
  76. $this->cats = $cats;
  77. $this->loadStats = $loadStats;
  78. $this->datagen = new GradebookDataGenerator($cats, $evals, $links);
  79. $this->datagen->preLoadDataKey = $this->getPreloadDataKey();
  80. $this->datagen->hidePercentage = api_get_configuration_value('hide_gradebook_percentage_user_result');
  81. if (!empty($userId)) {
  82. $this->datagen->userId = $userId;
  83. }
  84. if (isset($addparams)) {
  85. $this->set_additional_parameters($addparams);
  86. }
  87. $column = 0;
  88. if ($this->teacherView) {
  89. if ($this->exportToPdf == false) {
  90. $this->set_header($column++, '', '', 'width="25px"');
  91. }
  92. }
  93. $this->set_header($column++, get_lang('Type'), '', 'width="35px"');
  94. $this->set_header($column++, get_lang('Name'), false);
  95. if ($this->exportToPdf == false) {
  96. $this->set_header($column++, get_lang('Description'), false);
  97. }
  98. $model = ExerciseLib::getCourseScoreModel();
  99. $this->set_header(
  100. $column++,
  101. get_lang('Weight'),
  102. '',
  103. 'width="100px"'
  104. );
  105. if (!$this->teacherView) {
  106. $this->set_header($column++, get_lang('Result'), false);
  107. }
  108. if (empty($model)) {
  109. if (in_array(1, $this->loadStats)) {
  110. $this->set_header($column++, get_lang('Ranking'), false);
  111. }
  112. if (in_array(2, $this->loadStats)) {
  113. $this->set_header($column++, get_lang('BestScore'), false);
  114. }
  115. if (in_array(3, $this->loadStats)) {
  116. $this->set_header($column++, get_lang('Average'), false);
  117. }
  118. }
  119. if ($this->teacherView) {
  120. } else {
  121. if (!empty($cats)) {
  122. if ($this->exportToPdf == false) {
  123. $this->set_header($column++, get_lang('Actions'), false);
  124. }
  125. }
  126. }
  127. // Deactivates the odd/even alt rows in order that the +/- buttons work see #4047
  128. $this->odd_even_rows_enabled = false;
  129. // Admins get an edit column.
  130. if ($this->teacherView) {
  131. $this->set_header($column++, get_lang('Modify'), false, 'width="195px"');
  132. // Actions on multiple selected documents.
  133. $this->set_form_actions(
  134. [
  135. 'setvisible' => get_lang('SetVisible'),
  136. 'setinvisible' => get_lang('SetInvisible'),
  137. 'deleted' => get_lang('DeleteSelected'),
  138. ]
  139. );
  140. } else {
  141. if (empty($_GET['selectcat']) && !$this->teacherView) {
  142. if ($this->exportToPdf == false) {
  143. $this->set_header(
  144. $column++,
  145. get_lang('Certificates'),
  146. false
  147. );
  148. }
  149. }
  150. }
  151. }
  152. /**
  153. * @return GradebookDataGenerator
  154. */
  155. public function get_data()
  156. {
  157. return $this->datagen;
  158. }
  159. /**
  160. * Function used by SortableTable to get total number of items in the table.
  161. *
  162. * @return int
  163. */
  164. public function get_total_number_of_items()
  165. {
  166. return $this->datagen->get_total_items_count();
  167. }
  168. /**
  169. * @return string
  170. */
  171. public function getPreloadDataKey()
  172. {
  173. return 'default_data_'.api_get_course_id().'_'.api_get_session_id();
  174. }
  175. public function preloadData()
  176. {
  177. $allitems = $this->datagen->items;
  178. usort($allitems, ['GradebookDataGenerator', 'sort_by_name']);
  179. $visibleItems = array_merge($this->datagen->items, $this->evals_links);
  180. $defaultDataFromSession = Session::read($this->getPreloadDataKey());
  181. if (empty($defaultDataFromSession)) {
  182. $defaultData = [];
  183. /** @var GradebookItem $item */
  184. foreach ($visibleItems as $item) {
  185. $item->setStudentList($this->studentList);
  186. $itemType = get_class($item);
  187. switch ($itemType) {
  188. case 'Evaluation':
  189. // Best
  190. $best = $this->datagen->buildBestResultColumn($item);
  191. $defaultData[$item->get_id()]['best'] = $best;
  192. // Average
  193. $average = $this->datagen->buildAverageResultColumn($item);
  194. $defaultData[$item->get_id()]['average'] = $average;
  195. break;
  196. case 'ExerciseLink':
  197. /** @var ExerciseLink $item */
  198. // Best
  199. $best = $this->datagen->buildBestResultColumn($item);
  200. $defaultData[$item->get_id()]['best'] = $best;
  201. // Average
  202. $average = $this->datagen->buildAverageResultColumn($item);
  203. $defaultData[$item->get_id()]['average'] = $average;
  204. // Ranking
  205. /*if (!empty($this->studentList)) {
  206. $invalidateRanking = true;
  207. foreach ($this->studentList as $user) {
  208. $score = $this->datagen->build_result_column(
  209. $user['user_id'],
  210. $item,
  211. false,
  212. true
  213. );
  214. if (!empty($score['score'])) {
  215. $invalidateRanking = false;
  216. }
  217. $rankingStudentList[$user['user_id']] = $score['score'][0];
  218. $defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
  219. $defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateRanking;
  220. }
  221. }*/
  222. break;
  223. default:
  224. // Best
  225. $best = $this->datagen->buildBestResultColumn($item);
  226. $defaultData[$item->get_id()]['best'] = $best;
  227. // Average
  228. $average = $this->datagen->buildAverageResultColumn($item);
  229. $defaultData[$item->get_id()]['average'] = $average;
  230. // Ranking
  231. if (!empty($this->studentList)) {
  232. $invalidateRanking = true;
  233. foreach ($this->studentList as $user) {
  234. $score = $this->datagen->build_result_column(
  235. $user['user_id'],
  236. $item,
  237. false,
  238. true
  239. );
  240. if (!empty($score['score'])) {
  241. $invalidateRanking = false;
  242. }
  243. $rankingStudentList[$user['user_id']] = $score['score'][0];
  244. $defaultData[$item->get_id()]['ranking'] = $rankingStudentList;
  245. $defaultData[$item->get_id()]['ranking_invalidate'] = $invalidateRanking;
  246. }
  247. //exit;
  248. }
  249. break;
  250. }
  251. }
  252. Session::write($this->getPreloadDataKey(), $defaultData);
  253. } else {
  254. $defaultData = $defaultDataFromSession;
  255. }
  256. return $defaultData;
  257. }
  258. /**
  259. * Function used by SortableTable to generate the data to display.
  260. *
  261. * @param int $from
  262. * @param int $per_page
  263. * @param int $column
  264. * @param string $direction
  265. * @param int $sort
  266. *
  267. * @return array|mixed
  268. */
  269. public function get_table_data($from = 1, $per_page = null, $column = null, $direction = null, $sort = null)
  270. {
  271. //variables load in index.php
  272. global $certificate_min_score;
  273. $isAllowedToEdit = api_is_allowed_to_edit();
  274. // determine sorting type
  275. $col_adjust = $isAllowedToEdit ? 1 : 0;
  276. // By id
  277. $this->column = 5;
  278. switch ($this->column) {
  279. // Type
  280. case 0 + $col_adjust:
  281. $sorting = GradebookDataGenerator::GDG_SORT_TYPE;
  282. break;
  283. case 1 + $col_adjust:
  284. $sorting = GradebookDataGenerator::GDG_SORT_NAME;
  285. break;
  286. case 2 + $col_adjust:
  287. $sorting = GradebookDataGenerator::GDG_SORT_DESCRIPTION;
  288. break;
  289. case 3 + $col_adjust:
  290. $sorting = GradebookDataGenerator::GDG_SORT_WEIGHT;
  291. break;
  292. case 4 + $col_adjust:
  293. $sorting = GradebookDataGenerator::GDG_SORT_DATE;
  294. break;
  295. case 5 + $col_adjust:
  296. $sorting = GradebookDataGenerator::GDG_SORT_ID;
  297. break;
  298. }
  299. if ($this->direction == 'DESC') {
  300. $sorting |= GradebookDataGenerator::GDG_SORT_DESC;
  301. } else {
  302. $sorting |= GradebookDataGenerator::GDG_SORT_ASC;
  303. }
  304. // Status of user in course.
  305. $user_id = $this->userId;
  306. $course_code = api_get_course_id();
  307. $session_id = api_get_session_id();
  308. $statusToFilter = 0;
  309. if (empty($session_id)) {
  310. $statusToFilter = STUDENT;
  311. }
  312. if (empty($this->studentList) && $this->loadStats) {
  313. $studentList = CourseManager::get_user_list_from_course_code(
  314. $course_code,
  315. $session_id,
  316. null,
  317. null,
  318. $statusToFilter
  319. );
  320. $this->studentList = $studentList;
  321. }
  322. $this->datagen->userId = $this->userId;
  323. $data_array = $this->datagen->get_data(
  324. $sorting,
  325. $from,
  326. $this->per_page,
  327. false,
  328. $this->studentList,
  329. $this->loadStats
  330. );
  331. // generate the data to display
  332. $sortable_data = [];
  333. $weight_total_links = 0;
  334. $main_cat = Category::load(
  335. null,
  336. null,
  337. $course_code,
  338. null,
  339. null,
  340. $session_id,
  341. 'ORDER BY id'
  342. );
  343. $total_categories_weight = 0;
  344. $scoredisplay = ScoreDisplay::instance();
  345. $totalUserResult = [0, 0];
  346. $totalBest = [0, 0];
  347. $totalAverage = [0, 0];
  348. $type = 'detail';
  349. if ($this->exportToPdf) {
  350. $type = 'simple';
  351. }
  352. $model = ExerciseLib::getCourseScoreModel();
  353. $userExerciseScoreInCategory = api_get_configuration_value(
  354. 'gradebook_use_exercise_score_settings_in_categories'
  355. );
  356. $course_code = api_get_course_id();
  357. $session_id = api_get_session_id();
  358. $defaultData = Session::read($this->getPreloadDataKey());
  359. // Categories.
  360. if (!empty($data_array)) {
  361. foreach ($data_array as $data) {
  362. // list of items inside the gradebook (exercises, lps, forums, etc)
  363. $row = [];
  364. /** @var AbstractLink $item */
  365. $item = $data[0];
  366. // If the item is invisible, wrap it in a span with class invisible
  367. $invisibility_span_open = $isAllowedToEdit && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
  368. $invisibility_span_close = $isAllowedToEdit && $item->is_visible() == '0' ? '</span>' : '';
  369. // Id
  370. if ($this->teacherView) {
  371. if ($this->exportToPdf == false) {
  372. $row[] = $this->build_id_column($item);
  373. }
  374. }
  375. // Type.
  376. $row[] = $this->build_type_column($item);
  377. // Name.
  378. if (get_class($item) === 'Category') {
  379. $row[] = $invisibility_span_open.'<strong>'.$item->get_name().'</strong>'.$invisibility_span_close;
  380. $main_categories[$item->get_id()]['name'] = $item->get_name();
  381. } else {
  382. $name = $this->build_name_link($item, $type);
  383. $row[] = $invisibility_span_open.$name.$invisibility_span_close;
  384. $main_categories[$item->get_id()]['name'] = $name;
  385. }
  386. $this->dataForGraph['categories'][] = $item->get_name();
  387. $main_categories[$item->get_id()]['weight'] = $item->get_weight();
  388. $total_categories_weight += $item->get_weight();
  389. // Description.
  390. if ($this->exportToPdf == false) {
  391. $row[] = $invisibility_span_open.$data[2].$invisibility_span_close;
  392. }
  393. // Weight.
  394. $weight = $scoredisplay->display_score(
  395. [
  396. $data['3'],
  397. $this->currentcat->get_weight(),
  398. ],
  399. SCORE_SIMPLE,
  400. SCORE_BOTH,
  401. true
  402. );
  403. if ($this->teacherView) {
  404. $row[] = $invisibility_span_open.
  405. Display::tag('p', $weight, ['class' => 'score']).
  406. $invisibility_span_close;
  407. } else {
  408. $row[] = $invisibility_span_open.$weight.$invisibility_span_close;
  409. }
  410. $category_weight = $item->get_weight();
  411. $mainCategoryWeight = $main_cat[0]->get_weight();
  412. if ($this->teacherView) {
  413. $weight_total_links += $data[3];
  414. }
  415. // Edit (for admins).
  416. if ($this->teacherView) {
  417. $cat = new Category();
  418. $show_message = $cat->show_message_resource_delete($item->get_course_code());
  419. if ($show_message === false) {
  420. $row[] = $this->build_edit_column($item);
  421. }
  422. } else {
  423. $score = $item->calc_score($this->userId);
  424. $scoreToDisplay = '-';
  425. if (!empty($score[1])) {
  426. $completeScore = $scoredisplay->display_score($score, SCORE_DIV_PERCENT);
  427. $score = $score[0] / $score[1] * $item->get_weight();
  428. $score = $scoredisplay->display_score([$score, null], SCORE_SIMPLE);
  429. $scoreToDisplay = Display::tip($score, $completeScore);
  430. } else {
  431. $categoryScore = null;
  432. }
  433. // Students get the results and certificates columns
  434. $value_data = isset($data[4]) ? $data[4] : null;
  435. $best = isset($data['best']) ? $data['best'] : null;
  436. $average = isset($data['average']) ? $data['average'] : null;
  437. $ranking = isset($data['ranking']) ? $data['ranking'] : null;
  438. $totalResult = [
  439. $data['result_score'][0],
  440. $data['result_score'][1],
  441. ];
  442. $totalUserResult[0] += $totalResult[0] / ($totalResult[1] ?: 1) * $data[3];
  443. $totalUserResult[1] += $data[3];
  444. if (empty($model)) {
  445. $totalBest = [
  446. $scoredisplay->format_score($totalBest[0] + $data['best_score'][0]),
  447. $scoredisplay->format_score($totalBest[1] + $data['best_score'][1]),
  448. ];
  449. $totalAverage = [
  450. $data['average_score'][0],
  451. $data['average_score'][1],
  452. ];
  453. }
  454. // Student result
  455. if (empty($model)) {
  456. $row[] = $value_data;
  457. } else {
  458. $row[] = ExerciseLib::show_score(
  459. $data['result_score'][0],
  460. $data['result_score'][1]
  461. );
  462. }
  463. $mode = SCORE_AVERAGE;
  464. if ($userExerciseScoreInCategory) {
  465. $mode = SCORE_SIMPLE;
  466. $result = ExerciseLib::convertScoreToPlatformSetting($totalAverage[0], $totalAverage[1]);
  467. $totalAverage[0] = $result['score'];
  468. $totalAverage[1] = $result['weight'];
  469. $result = ExerciseLib::convertScoreToPlatformSetting($totalResult[0], $totalResult[1]);
  470. $totalResult[0] = $result['score'];
  471. $totalResult[1] = $result['weight'];
  472. $result = ExerciseLib::convertScoreToPlatformSetting(
  473. $data['result_score'][0],
  474. $data['result_score'][1]
  475. );
  476. $data['my_result_no_float'][0] = $result['score'];
  477. }
  478. $totalResultAverageValue = strip_tags($scoredisplay->display_score($totalResult, $mode));
  479. $totalAverageValue = strip_tags($scoredisplay->display_score($totalAverage, $mode));
  480. $this->dataForGraph['my_result'][] = floatval($totalResultAverageValue);
  481. $this->dataForGraph['average'][] = floatval($totalAverageValue);
  482. $this->dataForGraph['my_result_no_float'][] = $data['result_score'][0];
  483. if (empty($model)) {
  484. // Ranking
  485. if (in_array(1, $this->loadStats)) {
  486. $row[] = $ranking;
  487. }
  488. // Best
  489. if (in_array(2, $this->loadStats)) {
  490. $row[] = $best;
  491. }
  492. // Average
  493. if (in_array(3, $this->loadStats)) {
  494. $row[] = $average;
  495. }
  496. }
  497. if (get_class($item) === 'Category') {
  498. if ($this->exportToPdf == false) {
  499. $row[] = $this->build_edit_column($item);
  500. }
  501. }
  502. }
  503. // Category added.
  504. $sortable_data[] = $row;
  505. // Loading children
  506. if (get_class($item) === 'Category') {
  507. $parent_id = $item->get_id();
  508. $cats = Category::load(
  509. $parent_id,
  510. null,
  511. null,
  512. null,
  513. null,
  514. null
  515. );
  516. if (isset($cats[0])) {
  517. /** @var Category $subCategory */
  518. $subCategory = $cats[0];
  519. $allcat = $subCategory->get_subcategories($this->userId, $course_code, $session_id);
  520. $alleval = $subCategory->get_evaluations($this->userId);
  521. $alllink = $subCategory->get_links($this->userId);
  522. $sub_cat_info = new GradebookDataGenerator($allcat, $alleval, $alllink);
  523. $sub_cat_info->preLoadDataKey = $this->getPreloadDataKey();
  524. $sub_cat_info->userId = $user_id;
  525. $data_array2 = $sub_cat_info->get_data(
  526. $sorting,
  527. $from,
  528. $this->per_page,
  529. false,
  530. $this->studentList
  531. );
  532. $total_weight = 0;
  533. // Links.
  534. foreach ($data_array2 as $data) {
  535. $row = [];
  536. $item = $data[0];
  537. //if the item is invisible, wrap it in a span with class invisible
  538. $invisibility_span_open = $isAllowedToEdit && $item->is_visible() == '0' ? '<span class="text-muted">' : '';
  539. $invisibility_span_close = $isAllowedToEdit && $item->is_visible() == '0' ? '</span>' : '';
  540. if (isset($item)) {
  541. $main_categories[$parent_id]['children'][$item->get_id()]['name'] = $item->get_name();
  542. $main_categories[$parent_id]['children'][$item->get_id()]['weight'] = $item->get_weight();
  543. }
  544. if ($this->teacherView) {
  545. if ($this->exportToPdf == false) {
  546. $row[] = $this->build_id_column($item);
  547. }
  548. }
  549. // Type
  550. $row[] = $this->build_type_column($item, ['style' => 'padding-left:5px']);
  551. // Name.
  552. $row[] = $invisibility_span_open.'&nbsp;&nbsp;&nbsp; '.
  553. $this->build_name_link($item, $type).$invisibility_span_close;
  554. // Description.
  555. if ($this->exportToPdf == false) {
  556. $row[] = $invisibility_span_open.$data[2].$invisibility_span_close;
  557. }
  558. $weight = $data[3];
  559. $total_weight += $weight;
  560. // Weight
  561. $row[] = $invisibility_span_open.$weight.$invisibility_span_close;
  562. // Admins get an edit column.
  563. if (api_is_allowed_to_edit(null, true) &&
  564. isset($_GET['user_id']) == false &&
  565. (isset($_GET['action']) && $_GET['action'] != 'export_all' || !isset($_GET['action']))
  566. ) {
  567. $cat = new Category();
  568. $show_message = $cat->show_message_resource_delete($item->get_course_code());
  569. if ($show_message === false) {
  570. if ($this->exportToPdf == false) {
  571. $row[] = $this->build_edit_column($item);
  572. }
  573. }
  574. } else {
  575. // Students get the results and certificates columns
  576. $eval_n_links = array_merge($alleval, $alllink);
  577. if (count($eval_n_links) > 0) {
  578. $value_data = isset($data[4]) ? $data[4] : null;
  579. if (!is_null($value_data)) {
  580. // Result
  581. $row[] = $value_data;
  582. $best = isset($data['best']) ? $data['best'] : null;
  583. $average = isset($data['average']) ? $data['average'] : null;
  584. $ranking = isset($data['ranking']) ? $data['ranking'] : null;
  585. if (empty($model)) {
  586. if (in_array(1, $this->loadStats)) {
  587. // Ranking
  588. $row[] = $ranking;
  589. }
  590. if (in_array(2, $this->loadStats)) {
  591. // Best
  592. $row[] = $best;
  593. }
  594. // Average
  595. if (in_array(3, $this->loadStats)) {
  596. $row[] = $average;
  597. }
  598. }
  599. }
  600. }
  601. if (!empty($cats)) {
  602. if ($this->exportToPdf == false) {
  603. $row[] = null;
  604. }
  605. }
  606. }
  607. if ($this->exportToPdf == false) {
  608. $row['child_of'] = $parent_id;
  609. }
  610. $sortable_data[] = $row;
  611. }
  612. // "Warning row"
  613. if (!empty($data_array)) {
  614. if ($this->teacherView) {
  615. // Compare the category weight to the sum of all weights inside the category
  616. if (intval($total_weight) == $category_weight) {
  617. $label = null;
  618. $total = GradebookUtils::score_badges(
  619. [
  620. $total_weight.' / '.$category_weight,
  621. '100',
  622. ]
  623. );
  624. } else {
  625. $label = Display::return_icon(
  626. 'warning.png',
  627. sprintf(get_lang('TotalWeightMustBeX'), $category_weight)
  628. );
  629. $total = Display::badge($total_weight.' / '.$category_weight, 'warning');
  630. }
  631. $row = [
  632. null,
  633. null,
  634. "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<h5>".get_lang('SubTotal').'</h5>',
  635. null,
  636. $total.' '.$label,
  637. 'child_of' => $parent_id,
  638. ];
  639. $sortable_data[] = $row;
  640. }
  641. }
  642. }
  643. }
  644. }
  645. } //end looping categories
  646. $main_weight = 0;
  647. if (count($main_cat) > 1) {
  648. /** @var Category $myCat */
  649. foreach ($main_cat as $myCat) {
  650. $myParentId = $myCat->get_parent_id();
  651. if ($myParentId == 0) {
  652. $main_weight = (int) $myCat->get_weight();
  653. }
  654. }
  655. }
  656. if ($this->teacherView) {
  657. // Total for teacher.
  658. if (count($main_cat) > 1) {
  659. if (intval($total_categories_weight) == $main_weight) {
  660. $total = GradebookUtils::score_badges(
  661. [
  662. $total_categories_weight.' / '.$main_weight,
  663. '100',
  664. ]
  665. );
  666. } else {
  667. $total = Display::badge($total_categories_weight.' / '.$main_weight, 'warning');
  668. }
  669. $row = [
  670. null,
  671. null,
  672. '<strong>'.get_lang('Total').'</strong>',
  673. null,
  674. $total,
  675. ];
  676. $sortable_data[] = $row;
  677. }
  678. } else {
  679. // Total for student.
  680. if (count($main_cat) > 1) {
  681. $main_weight = (int) $main_cat[0]->get_weight();
  682. $global = null;
  683. $average = null;
  684. $myTotal = 0;
  685. foreach ($this->dataForGraph['my_result_no_float'] as $result) {
  686. $myTotal += $result;
  687. }
  688. $totalResult[0] = $myTotal;
  689. // Overwrite main weight
  690. $totalResult[1] = $main_weight;
  691. if (!empty($model)) {
  692. $totalResult = ExerciseLib::show_score($totalResult[0], $totalResult[1], false);
  693. } else {
  694. $totalResult = $scoredisplay->display_score(
  695. $totalResult,
  696. SCORE_DIV
  697. );
  698. }
  699. $row = [
  700. null,
  701. '<strong>'.get_lang('Total').'</strong>',
  702. ];
  703. if (!$this->exportToPdf) {
  704. $row[] = null;
  705. }
  706. $row[] = $main_weight;
  707. $row[] = $totalResult;
  708. $categoryId = $main_cat[0]->get_id();
  709. if (empty($model)) {
  710. if (in_array(1, $this->loadStats)) {
  711. if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['ranking'])) {
  712. $totalRanking = $defaultData[$categoryId]['ranking'];
  713. $invalidateRanking = $defaultData[$categoryId]['ranking_invalidate'];
  714. } else {
  715. $totalRanking = [];
  716. $invalidateRanking = true;
  717. $average = 0;
  718. $main_cat[0]->setStudentList($this->studentList);
  719. foreach ($this->studentList as $student) {
  720. $score = $main_cat[0]->calc_score(
  721. $student['user_id'],
  722. null,
  723. api_get_course_id(),
  724. api_get_session_id()
  725. );
  726. if (!empty($score[0])) {
  727. $invalidateRanking = false;
  728. }
  729. $totalRanking[$student['user_id']] = $score[0];
  730. $average += $score[0];
  731. }
  732. $defaultData[$categoryId]['ranking'] = $totalRanking;
  733. $defaultData[$categoryId]['ranking_invalidate'] = $invalidateRanking;
  734. Session::write($this->getPreloadDataKey(), $defaultData);
  735. }
  736. $totalRanking = AbstractLink::getCurrentUserRanking($user_id, $totalRanking);
  737. $totalRanking = $scoredisplay->display_score(
  738. $totalRanking,
  739. SCORE_DIV,
  740. SCORE_BOTH,
  741. true,
  742. true
  743. );
  744. if ($invalidateRanking) {
  745. $totalRanking = null;
  746. }
  747. $row[] = $totalRanking;
  748. }
  749. if (in_array(2, $this->loadStats)) {
  750. if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['best'])) {
  751. $totalBest = $defaultData[$categoryId]['best'];
  752. } else {
  753. // Overwrite main weight
  754. $totalBest[1] = $main_weight;
  755. $defaultData[$categoryId]['best'] = $totalBest;
  756. }
  757. $totalBest = $scoredisplay->display_score(
  758. $totalBest,
  759. SCORE_DIV,
  760. SCORE_BOTH,
  761. true
  762. );
  763. $row[] = $totalBest;
  764. }
  765. if (in_array(3, $this->loadStats)) {
  766. if (isset($defaultData[$categoryId]) && isset($defaultData[$categoryId]['average'])) {
  767. $totalAverage = $defaultData[$categoryId]['average'];
  768. } else {
  769. // Overwrite main weight
  770. $totalAverage[0] = $average / count($this->studentList);
  771. $totalAverage[1] = $main_weight;
  772. $defaultData[$categoryId]['average'] = $totalBest;
  773. }
  774. $totalAverage = $scoredisplay->display_score(
  775. $totalAverage,
  776. SCORE_DIV,
  777. SCORE_BOTH,
  778. true
  779. );
  780. $row[] = $totalAverage;
  781. }
  782. }
  783. if (!empty($row)) {
  784. $sortable_data[] = $row;
  785. }
  786. }
  787. }
  788. Session::write('default_data', $defaultData);
  789. // Warning messages
  790. $view = isset($_GET['view']) ? $_GET['view'] : null;
  791. if ($this->teacherView) {
  792. if (isset($_GET['selectcat']) &&
  793. $_GET['selectcat'] > 0 &&
  794. $view !== 'presence'
  795. ) {
  796. $id_cat = (int) $_GET['selectcat'];
  797. $category = Category::load($id_cat);
  798. $weight_category = (int) $this->build_weight($category[0]);
  799. $course_code = $this->build_course_code($category[0]);
  800. $weight_total_links = round($weight_total_links);
  801. if ($weight_total_links > $weight_category ||
  802. $weight_total_links < $weight_category ||
  803. $weight_total_links > $weight_category
  804. ) {
  805. $warning_message = sprintf(get_lang('TotalWeightMustBeX'), $weight_category);
  806. $modify_icons =
  807. '<a href="gradebook_edit_cat.php?editcat='.$id_cat.'&cidReq='.$course_code.'&id_session='.api_get_session_id().'">'.
  808. Display::return_icon('edit.png', $warning_message, [], ICON_SIZE_SMALL).'</a>';
  809. $warning_message .= $modify_icons;
  810. echo Display::return_message($warning_message, 'warning', false);
  811. }
  812. $content_html = DocumentManager::replace_user_info_into_html(
  813. api_get_user_id(),
  814. $course_code,
  815. api_get_session_id()
  816. );
  817. if (!empty($content_html)) {
  818. $new_content = explode('</head>', $content_html['content']);
  819. }
  820. if (empty($new_content[0])) {
  821. // Set default certificate
  822. $courseData = api_get_course_info($course_code);
  823. DocumentManager::generateDefaultCertificate($courseData);
  824. }
  825. }
  826. if (empty($_GET['selectcat'])) {
  827. $categories = Category::load();
  828. $weight_categories = $certificate_min_scores = $course_codes = [];
  829. foreach ($categories as $category) {
  830. $course_code_category = $this->build_course_code($category);
  831. if (!empty($course_code)) {
  832. if ($course_code_category == $course_code) {
  833. $weight_categories[] = intval($this->build_weight($category));
  834. $certificate_min_scores[] = intval($this->build_certificate_min_score($category));
  835. $course_codes[] = $course_code;
  836. break;
  837. }
  838. } else {
  839. $weight_categories[] = intval($this->build_weight($category));
  840. $certificate_min_scores[] = intval($this->build_certificate_min_score($category));
  841. $course_codes[] = $course_code_category;
  842. }
  843. }
  844. if (is_array($weight_categories) &&
  845. is_array($certificate_min_scores) &&
  846. is_array($course_codes)
  847. ) {
  848. $warning_message = '';
  849. for ($x = 0; $x < count($weight_categories); $x++) {
  850. $weight_category = intval($weight_categories[$x]);
  851. $certificate_min_score = intval($certificate_min_scores[$x]);
  852. $course_code = $course_codes[$x];
  853. if (empty($certificate_min_score) ||
  854. ($certificate_min_score > $weight_category)
  855. ) {
  856. $warning_message .= $course_code.
  857. '&nbsp;-&nbsp;'.get_lang('CertificateMinimunScoreIsRequiredAndMustNotBeMoreThan').
  858. '&nbsp;'.$weight_category.'<br />';
  859. }
  860. }
  861. if (!empty($warning_message)) {
  862. echo Display::return_message($warning_message, 'warning', false);
  863. }
  864. }
  865. }
  866. }
  867. return $sortable_data;
  868. }
  869. /**
  870. * @return string
  871. */
  872. public function getGraph()
  873. {
  874. $data = $this->getDataForGraph();
  875. if (!empty($data) &&
  876. isset($data['categories']) &&
  877. isset($data['my_result']) &&
  878. isset($data['average'])
  879. ) {
  880. $dataSet = new pData();
  881. $dataSet->addPoints($data['my_result'], get_lang('Me'));
  882. // In order to generate random values
  883. // $data['average'] = array(rand(0,50), rand(0,50));
  884. $dataSet->addPoints($data['average'], get_lang('Average'));
  885. $dataSet->addPoints($data['categories'], 'categories');
  886. $dataSet->setAbscissa('categories');
  887. $xSize = 600;
  888. $ySize = 400;
  889. $pChart = new pImage($xSize, $ySize, $dataSet);
  890. /* Turn of Antialiasing */
  891. $pChart->Antialias = false;
  892. /* Add a border to the picture */
  893. $pChart->drawRectangle(
  894. 0,
  895. 0,
  896. $xSize - 1,
  897. $ySize - 1,
  898. ["R" => 0, "G" => 0, "B" => 0]
  899. );
  900. $pChart->drawText(
  901. 80,
  902. 16,
  903. get_lang('Results'),
  904. ["FontSize" => 11, "Align" => TEXT_ALIGN_BOTTOMMIDDLE]
  905. );
  906. $pChart->setGraphArea(50, 30, $xSize - 50, $ySize - 70);
  907. $pChart->setFontProperties(
  908. [
  909. 'FontName' => api_get_path(SYS_FONTS_PATH).'opensans/OpenSans-Regular.ttf',
  910. 'FontSize' => 10,
  911. ]
  912. );
  913. /* Draw the scale */
  914. $scaleSettings = [
  915. "XMargin" => AUTO,
  916. "YMargin" => 10,
  917. "Floating" => true,
  918. "GridR" => 200,
  919. "GridG" => 200,
  920. "GridB" => 200,
  921. "DrawSubTicks" => true,
  922. "CycleBackground" => true,
  923. 'LabelRotation' => 10,
  924. ];
  925. $pChart->drawScale($scaleSettings);
  926. /* Draw the line chart */
  927. $pChart->drawLineChart();
  928. $pChart->drawPlotChart(
  929. [
  930. "DisplayValues" => true,
  931. "PlotBorder" => true,
  932. "BorderSize" => 2,
  933. "Surrounding" => -60,
  934. "BorderAlpha" => 80,
  935. ]
  936. );
  937. /* Write the chart legend */
  938. $pChart->drawLegend(
  939. $xSize - 180,
  940. 9,
  941. [
  942. "Style" => LEGEND_NOBORDER,
  943. "Mode" => LEGEND_HORIZONTAL,
  944. "FontR" => 0,
  945. "FontG" => 0,
  946. "FontB" => 0,
  947. ]
  948. );
  949. $cachePath = api_get_path(SYS_ARCHIVE_PATH);
  950. $myCache = new pCache(['CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)]);
  951. $chartHash = $myCache->getHash($dataSet);
  952. $myCache->writeToCache($chartHash, $pChart);
  953. $imgSysPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
  954. $myCache->saveFromCache($chartHash, $imgSysPath);
  955. $imgWebPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
  956. if (file_exists($imgSysPath)) {
  957. $result = '<br /><div id="contentArea" style="text-align: center;" >';
  958. $result .= '<img src="'.$imgWebPath.'" >';
  959. $result .= '</div>';
  960. return $result;
  961. }
  962. }
  963. return '';
  964. }
  965. /**
  966. * @return array
  967. */
  968. private function getDataForGraph()
  969. {
  970. return $this->dataForGraph;
  971. }
  972. /**
  973. * @param $item
  974. *
  975. * @return mixed
  976. */
  977. private function build_certificate_min_score($item)
  978. {
  979. return $item->getCertificateMinScore();
  980. }
  981. /**
  982. * @param $item
  983. *
  984. * @return mixed
  985. */
  986. private function build_weight($item)
  987. {
  988. return $item->get_weight();
  989. }
  990. /**
  991. * @param $item
  992. *
  993. * @return mixed
  994. */
  995. private function build_course_code($item)
  996. {
  997. return $item->get_course_code();
  998. }
  999. /**
  1000. * @param $item
  1001. *
  1002. * @return string
  1003. */
  1004. private function build_id_column($item)
  1005. {
  1006. switch ($item->get_item_type()) {
  1007. // category
  1008. case 'C':
  1009. return 'CATE'.$item->get_id();
  1010. // evaluation
  1011. case 'E':
  1012. return 'EVAL'.$item->get_id();
  1013. // link
  1014. case 'L':
  1015. return 'LINK'.$item->get_id();
  1016. }
  1017. }
  1018. /**
  1019. * @param $item
  1020. * @param array $attributes
  1021. *
  1022. * @return string
  1023. */
  1024. private function build_type_column($item, $attributes = [])
  1025. {
  1026. return GradebookUtils::build_type_icon_tag($item->get_icon_name(), $attributes);
  1027. }
  1028. /**
  1029. * Generate name column.
  1030. *
  1031. * @param GradebookItem $item
  1032. * @param string $type simple|detail
  1033. *
  1034. * @return string
  1035. */
  1036. private function build_name_link($item, $type = 'detail')
  1037. {
  1038. $view = isset($_GET['view']) ? Security::remove_XSS($_GET['view']) : null;
  1039. $categoryId = $item->getCategory()->get_id();
  1040. switch ($item->get_item_type()) {
  1041. // category
  1042. case 'C':
  1043. $prms_uri = '?selectcat='.$item->get_id().'&view='.$view;
  1044. $isStudentView = api_is_student_view_active();
  1045. if (isset($is_student) || $isStudentView) {
  1046. $prms_uri = $prms_uri.'&amp;isStudentView=studentview';
  1047. }
  1048. $cat = new Category();
  1049. $show_message = $cat->show_message_resource_delete($item->get_course_code());
  1050. return '&nbsp;<a href="'.Category::getUrl().$prms_uri.'">'
  1051. .$item->get_name()
  1052. .'</a>'
  1053. .($item->is_course() ? ' &nbsp;['.$item->get_course_code().']'.$show_message : '');
  1054. // evaluation
  1055. case 'E':
  1056. $cat = new Category();
  1057. $course_id = CourseManager::get_course_by_category($categoryId);
  1058. $show_message = $cat->show_message_resource_delete($course_id);
  1059. // course/platform admin can go to the view_results page
  1060. if (api_is_allowed_to_edit() && $show_message === false) {
  1061. if ($item->get_type() == 'presence') {
  1062. return '&nbsp;'
  1063. .'<a href="gradebook_view_result.php?cidReq='.$course_id.'&amp;selecteval='.$item->get_id().'">'
  1064. .$item->get_name()
  1065. .'</a>';
  1066. } else {
  1067. $extra = Display::label(get_lang('Evaluation'));
  1068. if ($type == 'simple') {
  1069. $extra = '';
  1070. }
  1071. return '&nbsp;'
  1072. .'<a href="gradebook_view_result.php?'.api_get_cidreq().'&selecteval='.$item->get_id().'">'
  1073. .$item->get_name()
  1074. .'</a>&nbsp;'.$extra;
  1075. }
  1076. } elseif (ScoreDisplay::instance()->is_custom() && $show_message === false) {
  1077. // students can go to the statistics page (if custom display enabled)
  1078. return '&nbsp;'
  1079. .'<a href="gradebook_statistics.php?'.api_get_cidreq().'&selecteval='.$item->get_id().'">'
  1080. .$item->get_name()
  1081. .'</a>';
  1082. } elseif ($show_message === false && !api_is_allowed_to_edit() && !ScoreDisplay::instance()->is_custom()) {
  1083. return '&nbsp;'
  1084. .'<a href="gradebook_statistics.php?'.api_get_cidreq().'&selecteval='.$item->get_id().'">'
  1085. .$item->get_name()
  1086. .'</a>';
  1087. } else {
  1088. return '['.get_lang('Evaluation').']&nbsp;&nbsp;'.$item->get_name().$show_message;
  1089. }
  1090. // no break because of return
  1091. case 'L':
  1092. // link
  1093. $cat = new Category();
  1094. $course_id = CourseManager::get_course_by_category($categoryId);
  1095. $show_message = $cat->show_message_resource_delete($course_id);
  1096. $url = $item->get_link();
  1097. $text = $item->get_name();
  1098. if (isset($url) && $show_message === false) {
  1099. $text = '&nbsp;<a href="'.$item->get_link().'">'
  1100. .$item->get_name()
  1101. .'</a>';
  1102. }
  1103. $extra = Display::label($item->get_type_name(), 'info');
  1104. if ($type == 'simple') {
  1105. $extra = '';
  1106. }
  1107. $extra .= $item->getSkillsFromItem();
  1108. $text .= "&nbsp;".$extra.$show_message;
  1109. $cc = $this->currentcat->get_course_code();
  1110. if (empty($cc)) {
  1111. $text .= '&nbsp;[<a href="'.api_get_path(REL_COURSE_PATH).$item->get_course_code().'/">'.$item->get_course_code().'</a>]';
  1112. }
  1113. return $text;
  1114. }
  1115. }
  1116. /**
  1117. * @param AbstractLink $item
  1118. *
  1119. * @return string|null
  1120. */
  1121. private function build_edit_column($item)
  1122. {
  1123. switch ($item->get_item_type()) {
  1124. // category
  1125. case 'C':
  1126. return GradebookUtils::build_edit_icons_cat($item, $this->currentcat);
  1127. // evaluation
  1128. case 'E':
  1129. return GradebookUtils::build_edit_icons_eval($item, $this->currentcat->get_id());
  1130. // link
  1131. case 'L':
  1132. return GradebookUtils::build_edit_icons_link($item, $this->currentcat->get_id());
  1133. }
  1134. }
  1135. }