scoredisplay.class.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <?php
  2. /* For licensing terms, see /license.txt */
  3. /**
  4. * Class ScoreDisplay
  5. * Display scores according to the settings made by the platform admin.
  6. * This class works as a singleton: call instance() to retrieve an object.
  7. * @author Bert Steppé
  8. * @package chamilo.gradebook
  9. */
  10. class ScoreDisplay
  11. {
  12. private $coloring_enabled;
  13. private $color_split_value;
  14. private $custom_enabled;
  15. private $upperlimit_included;
  16. private $custom_display;
  17. private $custom_display_conv;
  18. /**
  19. * Get the instance of this class
  20. * @param int $category_id
  21. */
  22. public static function instance($category_id = 0)
  23. {
  24. static $instance;
  25. if (!isset ($instance)) {
  26. $instance = new ScoreDisplay($category_id);
  27. }
  28. return $instance;
  29. }
  30. /**
  31. * Compare the custom display of 2 scores, can be useful in sorting
  32. */
  33. public static function compare_scores_by_custom_display ($score1, $score2)
  34. {
  35. if (!isset($score1)) {
  36. return (isset($score2) ? 1 : 0);
  37. } elseif (!isset($score2)) {
  38. return -1;
  39. } else {
  40. $scoredisplay = ScoreDisplay :: instance();
  41. $custom1 = $scoredisplay->display_custom($score1);
  42. $custom2 = $scoredisplay->display_custom($score2);
  43. if ($custom1 == $custom2) {
  44. return 0;
  45. } else {
  46. return (($score1[0]/$score1[1]) < ($score2[0]/$score2[1]) ? -1 : 1);
  47. }
  48. }
  49. }
  50. /**
  51. * Protected constructor - call instance() to instantiate
  52. */
  53. public function __construct($category_id = 0)
  54. {
  55. if (!empty($category_id)) {
  56. $this->category_id = $category_id;
  57. }
  58. // Loading portal settings + using standard functions.
  59. $value = api_get_setting('gradebook.my_display_coloring');
  60. // Setting coloring.
  61. $this->coloring_enabled = $value == 'true' ? true : false;
  62. if ($this->coloring_enabled) {
  63. $value = api_get_setting('gradebook_score_display_colorsplit');
  64. if (isset($value)) {
  65. $this->color_split_value = $value;
  66. }
  67. }
  68. // Setting custom enabled
  69. $value = api_get_setting('gradebook.gradebook_score_display_custom');
  70. $this->custom_enabled = $value == 'true' ? true : false;
  71. if ($this->custom_enabled) {
  72. $params = array('category = ?' => array('Gradebook'));
  73. $displays = api_get_settings_params($params);
  74. $portal_displays = array();
  75. if (!empty($displays)) {
  76. foreach ($displays as $display) {
  77. $data = explode('::', $display['selected_value']);
  78. if (empty($data[1])) {
  79. $data[1] = "";
  80. }
  81. $portal_displays[$data[0]] = array('score' => $data[0], 'display' => $data[1]);
  82. }
  83. sort($portal_displays);
  84. }
  85. $this->custom_display = $portal_displays;
  86. if (count($this->custom_display)>0) {
  87. $value = api_get_setting('gradebook_score_display_upperlimit');
  88. $value = $value['my_display_upperlimit'];
  89. $this->upperlimit_included = $value == 'true' ? true : false;
  90. $this->custom_display_conv = $this->convert_displays($this->custom_display);
  91. }
  92. }
  93. //If teachers can override the portal parameters
  94. if (api_get_setting(
  95. 'gradebook.teachers_can_change_score_settings'
  96. ) == 'true'
  97. ) {
  98. //Load course settings
  99. if ($this->custom_enabled) {
  100. $this->custom_display = $this->get_custom_displays();
  101. if (count($this->custom_display)> 0) {
  102. $this->custom_display_conv = $this->convert_displays($this->custom_display);
  103. }
  104. }
  105. if ($this->coloring_enabled) {
  106. $this->color_split_value = $this->get_score_color_percent();
  107. }
  108. }
  109. }
  110. /**
  111. * Is coloring enabled ?
  112. */
  113. public function is_coloring_enabled()
  114. {
  115. return $this->coloring_enabled;
  116. }
  117. /**
  118. * Is custom score display enabled ?
  119. */
  120. public function is_custom()
  121. {
  122. return $this->custom_enabled;
  123. }
  124. /**
  125. * Is upperlimit included ?
  126. */
  127. public function is_upperlimit_included()
  128. {
  129. return $this->upperlimit_included;
  130. }
  131. /**
  132. * If custom score display is enabled, this will return the current settings.
  133. * See also update_custom_score_display_settings
  134. * @return array current settings (or null if feature not enabled)
  135. */
  136. public function get_custom_score_display_settings()
  137. {
  138. return $this->custom_display;
  139. }
  140. /**
  141. * If coloring is enabled, scores below this value will be displayed in red.
  142. * @return int color split value, in percent (or null if feature not enabled)
  143. */
  144. public function get_color_split_value()
  145. {
  146. return $this->color_split_value;
  147. }
  148. /**
  149. * Get current gradebook category id
  150. * @return int Category id
  151. */
  152. private function get_current_gradebook_category_id()
  153. {
  154. $curr_course_id = api_get_course_int_id();
  155. $curr_session_id = api_get_session_id();
  156. $em = Database::getManager();
  157. $qb = $em->createQueryBuilder();
  158. $qb
  159. ->select('gc')
  160. ->from('ChamiloCoreBundle:GradebookCategory')
  161. ->where(
  162. Criteria::expr()->eq('course', $curr_course_id)
  163. );
  164. if (empty($curr_session_id)) {
  165. $qb->andWhere(
  166. $qb->expr()->isNull('sessionId')
  167. );
  168. } else {
  169. $qb->andWhere(
  170. $qb->expr()->eq('sessionId', $curr_session_id)
  171. );
  172. }
  173. $rs = $qb->getQuery()->getResult();
  174. $category_id = 0;
  175. if (count($rs) > 0) {
  176. $row = current($rs);
  177. $category_id = $row->getId();
  178. }
  179. return $category_id;
  180. }
  181. /**
  182. * Update custom score display settings
  183. * @param array $displays 2-dimensional array - every sub array must have keys (score, display)
  184. * @param int score color percent (optional)
  185. * @param int gradebook category id (optional)
  186. */
  187. public function update_custom_score_display_settings($displays, $scorecolpercent = 0, $category_id = null)
  188. {
  189. $this->custom_display = $displays;
  190. $this->custom_display_conv = $this->convert_displays($this->custom_display);
  191. if (isset($category_id)) {
  192. $category_id = intval($category_id);
  193. } else {
  194. $category_id = $this->get_current_gradebook_category_id();
  195. }
  196. // remove previous settings
  197. $table = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
  198. $sql = 'DELETE FROM '.$table.' WHERE category_id = '.$category_id;
  199. Database::query($sql);
  200. // add new settings
  201. $count = 0;
  202. foreach ($displays as $display) {
  203. $params = [
  204. 'score' => $display['score'],
  205. 'display' => $display['display'],
  206. 'category_id' => $category_id,
  207. 'score_color_percent' => $scorecolpercent,
  208. ];
  209. Database::insert($table, $params);
  210. $count++;
  211. }
  212. }
  213. /**
  214. * @param int $category_id
  215. * @return bool
  216. */
  217. public function insert_defaults($category_id)
  218. {
  219. if (empty($category_id)) {
  220. return false;
  221. }
  222. //Get this from DB settings
  223. $display = array(
  224. 50 => get_lang('GradebookFailed'),
  225. 60 => get_lang('GradebookPoor'),
  226. 70 => get_lang('GradebookFair'),
  227. 80 => get_lang('GradebookGood'),
  228. 90 => get_lang('GradebookOutstanding'),
  229. 100 => get_lang('GradebookExcellent')
  230. );
  231. $tbl_display = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
  232. foreach($display as $value => $text) {
  233. $params = array(
  234. 'score' => $value,
  235. 'display' => $text,
  236. 'category_id' => $category_id,
  237. 'score_color_percent' => 0,
  238. );
  239. Database::insert($tbl_display, $params);
  240. }
  241. }
  242. /**
  243. * @return int|null|string
  244. */
  245. public function get_number_decimals()
  246. {
  247. $number_decimals = api_get_setting(
  248. 'gradebook.gradebook_number_decimals'
  249. );
  250. if (!isset($number_decimals)) {
  251. $number_decimals = 0;
  252. }
  253. return $number_decimals;
  254. }
  255. /**
  256. * Formats a number depending of the number of decimals
  257. *
  258. * @param float $score
  259. * @return float the score formatted
  260. */
  261. public function format_score($score)
  262. {
  263. return floatval(number_format($score, $this->get_number_decimals()));
  264. }
  265. /**
  266. * Display a score according to the current settings
  267. * @param array $score data structure, as returned by the calc_score functions
  268. * @param int $type one of the following constants:
  269. * SCORE_DIV, SCORE_PERCENT, SCORE_DIV_PERCENT, SCORE_AVERAGE
  270. * (ignored for student's view if custom score display is enabled)
  271. * @param int $what one of the following constants:
  272. * SCORE_BOTH, SCORE_ONLY_DEFAULT, SCORE_ONLY_CUSTOM (default: SCORE_BOTH)
  273. * (only taken into account if custom score display is enabled and for course/platform admin)
  274. *
  275. * @return string
  276. */
  277. public function display_score(
  278. $score,
  279. $type = SCORE_DIV_PERCENT,
  280. $what = SCORE_BOTH,
  281. $no_color = false
  282. ) {
  283. $my_score = $score == 0 ? 1 : $score;
  284. if ($type == SCORE_BAR) {
  285. $percentage = $my_score[0]/$my_score[1]*100;
  286. return Display::bar_progress($percentage);
  287. }
  288. if ($type == SCORE_SIMPLE) {
  289. $simple_score = $this->format_score($my_score[0]);
  290. return $simple_score;
  291. }
  292. if ($this->custom_enabled && isset($this->custom_display_conv)) {
  293. $display = $this->display_default($my_score, $type);
  294. } else {
  295. // if no custom display set, use default display
  296. $display = $this->display_default($my_score, $type);
  297. }
  298. if ($this->coloring_enabled && $no_color == false) {
  299. $my_score_denom = isset($score[1]) && !empty($score[1]) ? $score[1] : 1;
  300. $scoreCleaned = isset($score[0]) ? $score[0] : 0;
  301. if (($scoreCleaned / $my_score_denom) < ($this->color_split_value / 100)) {
  302. $display = Display::tag('font', $display, array('color'=>'red'));
  303. }
  304. }
  305. return $display;
  306. }
  307. /**
  308. * @param $score
  309. * @param $type
  310. * @return float|string
  311. */
  312. private function display_default($score, $type)
  313. {
  314. switch ($type) {
  315. case SCORE_DIV : // X / Y
  316. return $this->display_as_div($score);
  317. case SCORE_PERCENT : // XX %
  318. return $this->display_as_percent($score);
  319. case SCORE_DIV_PERCENT : // X / Y (XX %)
  320. return $this->display_as_div($score).' (' . $this->display_as_percent($score) . ')';
  321. case SCORE_AVERAGE : // XX %
  322. return $this->display_as_percent($score);
  323. case SCORE_DECIMAL : // 0.50 (X/Y)
  324. return $this->display_as_decimal($score);
  325. case SCORE_DIV_PERCENT_WITH_CUSTOM : // X / Y (XX %) - Good!
  326. $custom = $this->display_custom($score);
  327. if (!empty($custom)) {
  328. $custom = ' - '.$custom;
  329. }
  330. return $this->display_as_div($score).' (' . $this->display_as_percent($score) . ')'.$custom;
  331. case SCORE_DIV_SIMPLE_WITH_CUSTOM : // X - Good!
  332. $custom = $this->display_custom($score);
  333. if (!empty($custom)) {
  334. $custom = ' - '.$custom;
  335. }
  336. return $this->display_simple_score($score).$custom;
  337. break;
  338. case SCORE_DIV_SIMPLE_WITH_CUSTOM_LETTERS:
  339. $custom = $this->display_custom($score);
  340. if (!empty($custom)) {
  341. $custom = ' - '.$custom;
  342. }
  343. $score = $this->display_simple_score($score);
  344. //needs sudo apt-get install php5-intl
  345. if (class_exists('NumberFormatter')) {
  346. $iso = api_get_language_isocode();
  347. $f = new NumberFormatter($iso, NumberFormatter::SPELLOUT);
  348. $letters = $f->format($score);
  349. $letters = api_strtoupper($letters);
  350. $letters = " ($letters) ";
  351. }
  352. return $score.$letters.$custom;
  353. break;
  354. case SCORE_CUSTOM: // Good!
  355. return $this->display_custom($score);
  356. }
  357. }
  358. /**
  359. * @param array $score
  360. * @return float|string
  361. */
  362. private function display_simple_score($score)
  363. {
  364. if (isset($score[0])) {
  365. return $this->format_score($score[0]);
  366. }
  367. return '';
  368. }
  369. /**
  370. * Returns "1" for array("100", "100");
  371. * @param array $score
  372. * @return float
  373. */
  374. private function display_as_decimal($score)
  375. {
  376. $score_denom = ($score[1]==0) ? 1 : $score[1];
  377. return $this->format_score($score[0]/$score_denom);
  378. }
  379. /**
  380. * Returns "100 %" for array("100", "100");
  381. */
  382. private function display_as_percent($score)
  383. {
  384. $score_denom = ($score[1]==0) ? 1 : $score[1];
  385. return $this->format_score($score[0]/$score_denom*100) . ' %';
  386. }
  387. /**
  388. * Returns 10.00 / 10.00 for array("100", "100");
  389. * @param array $score
  390. *
  391. * @return string
  392. */
  393. private function display_as_div($score)
  394. {
  395. if ($score == 1) {
  396. return '0 / 0';
  397. } else {
  398. $score[0] = isset($score[0]) ? $this->format_score($score[0]) : 0;
  399. $score[1] = isset($score[1]) ? $this->format_score($score[1]) : 0;
  400. return $score[0] . ' / ' . $score[1];
  401. }
  402. }
  403. /**
  404. * Depends on the teacher's configuration of thresholds. i.e. [0 50] "Bad", [50:100] "Good"
  405. * @param array $score
  406. */
  407. private function display_custom($score)
  408. {
  409. $my_score_denom= ($score[1]==0) ? 1 : $score[1];
  410. $scaledscore = $score[0] / $my_score_denom;
  411. if ($this->upperlimit_included) {
  412. foreach ($this->custom_display_conv as $displayitem) {
  413. if ($scaledscore <= $displayitem['score']) {
  414. return $displayitem['display'];
  415. }
  416. }
  417. } else {
  418. if (!empty($this->custom_display_conv)) {
  419. foreach ($this->custom_display_conv as $displayitem) {
  420. if ($scaledscore < $displayitem['score'] || $displayitem['score'] == 1) {
  421. return $displayitem['display'];
  422. }
  423. }
  424. }
  425. }
  426. }
  427. /**
  428. * Get score color percent by category
  429. * @param int Gradebook category id
  430. * @return int Score
  431. */
  432. private function get_score_color_percent($category_id = null)
  433. {
  434. $tbl_display = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
  435. if (isset($category_id)) {
  436. $category_id = intval($category_id);
  437. } else {
  438. $category_id = $this->get_current_gradebook_category_id();
  439. }
  440. $sql = 'SELECT score_color_percent FROM '.$tbl_display.'
  441. WHERE category_id = '.$category_id.'
  442. LIMIT 1';
  443. $result = Database::query($sql);
  444. $score = 0;
  445. if (Database::num_rows($result) > 0) {
  446. $row = Database::fetch_row($result);
  447. $score = $row[0];
  448. }
  449. return $score;
  450. }
  451. /**
  452. * Get current custom score display settings
  453. * @param int Gradebook category id
  454. * @return array 2-dimensional array every element contains 3 subelements (id, score, display)
  455. */
  456. private function get_custom_displays($category_id = null)
  457. {
  458. $tbl_display = Database :: get_main_table(TABLE_MAIN_GRADEBOOK_SCORE_DISPLAY);
  459. if (isset($category_id)) {
  460. $category_id = intval($category_id);
  461. } else {
  462. $category_id = $this->get_current_gradebook_category_id();
  463. }
  464. $sql = 'SELECT * FROM '.$tbl_display.'
  465. WHERE category_id = '.$category_id.'
  466. ORDER BY score';
  467. $result = Database::query($sql);
  468. return Database::store_result($result,'ASSOC');
  469. }
  470. /**
  471. * Convert display settings to internally used values
  472. */
  473. private function convert_displays($custom_display)
  474. {
  475. if (isset($custom_display)) {
  476. // get highest score entry, and copy each element to a new array
  477. $converted = array();
  478. $highest = 0;
  479. foreach ($custom_display as $element) {
  480. if ($element['score'] > $highest) {
  481. $highest = $element['score'];
  482. }
  483. $converted[] = $element;
  484. }
  485. // sort the new array (ascending)
  486. usort($converted, array('ScoreDisplay', 'sort_display'));
  487. // adjust each score in such a way that
  488. // each score is scaled between 0 and 1
  489. // the highest score in this array will be equal to 1
  490. $converted2 = array();
  491. foreach ($converted as $element) {
  492. $newelement = array();
  493. if (isset($highest) && !empty($highest) && $highest > 0) {
  494. $newelement['score'] = $element['score'] / $highest;
  495. } else {
  496. $newelement['score'] = 0;
  497. }
  498. $newelement['display'] = $element['display'];
  499. $converted2[] = $newelement;
  500. }
  501. return $converted2;
  502. } else {
  503. return null;
  504. }
  505. }
  506. /**
  507. * @param array $item1
  508. * @param array $item2
  509. * @return int
  510. */
  511. private function sort_display($item1, $item2)
  512. {
  513. if ($item1['score'] === $item2['score']) {
  514. return 0;
  515. } else {
  516. return ($item1['score'] < $item2['score'] ? -1 : 1);
  517. }
  518. }
  519. }