PageRenderTime 30ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 1ms

/grade/lib.php

https://bitbucket.org/synergylearning/campusconnect
PHP | 2840 lines | 1843 code | 325 blank | 672 comment | 389 complexity | 2fea5cda5172928f8fd6dee352a6d6aa MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, LGPL-2.1, Apache-2.0, BSD-3-Clause, AGPL-3.0
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Functions used by gradebook plugins and reports.
  18. *
  19. * @package core_grades
  20. * @copyright 2009 Petr Skoda and Nicolas Connault
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. require_once($CFG->libdir . '/gradelib.php');
  24. require_once($CFG->dirroot . '/grade/export/lib.php');
  25. /**
  26. * This class iterates over all users that are graded in a course.
  27. * Returns detailed info about users and their grades.
  28. *
  29. * @author Petr Skoda <skodak@moodle.org>
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. */
  32. class graded_users_iterator {
  33. /**
  34. * The couse whose users we are interested in
  35. */
  36. protected $course;
  37. /**
  38. * An array of grade items or null if only user data was requested
  39. */
  40. protected $grade_items;
  41. /**
  42. * The group ID we are interested in. 0 means all groups.
  43. */
  44. protected $groupid;
  45. /**
  46. * A recordset of graded users
  47. */
  48. protected $users_rs;
  49. /**
  50. * A recordset of user grades (grade_grade instances)
  51. */
  52. protected $grades_rs;
  53. /**
  54. * Array used when moving to next user while iterating through the grades recordset
  55. */
  56. protected $gradestack;
  57. /**
  58. * The first field of the users table by which the array of users will be sorted
  59. */
  60. protected $sortfield1;
  61. /**
  62. * Should sortfield1 be ASC or DESC
  63. */
  64. protected $sortorder1;
  65. /**
  66. * The second field of the users table by which the array of users will be sorted
  67. */
  68. protected $sortfield2;
  69. /**
  70. * Should sortfield2 be ASC or DESC
  71. */
  72. protected $sortorder2;
  73. /**
  74. * Should users whose enrolment has been suspended be ignored?
  75. */
  76. protected $onlyactive = false;
  77. /**
  78. * Enable user custom fields
  79. */
  80. protected $allowusercustomfields = false;
  81. /**
  82. * List of suspended users in course. This includes users whose enrolment status is suspended
  83. * or enrolment has expired or not started.
  84. */
  85. protected $suspendedusers = array();
  86. /**
  87. * Constructor
  88. *
  89. * @param object $course A course object
  90. * @param array $grade_items array of grade items, if not specified only user info returned
  91. * @param int $groupid iterate only group users if present
  92. * @param string $sortfield1 The first field of the users table by which the array of users will be sorted
  93. * @param string $sortorder1 The order in which the first sorting field will be sorted (ASC or DESC)
  94. * @param string $sortfield2 The second field of the users table by which the array of users will be sorted
  95. * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC)
  96. */
  97. public function __construct($course, $grade_items=null, $groupid=0,
  98. $sortfield1='lastname', $sortorder1='ASC',
  99. $sortfield2='firstname', $sortorder2='ASC') {
  100. $this->course = $course;
  101. $this->grade_items = $grade_items;
  102. $this->groupid = $groupid;
  103. $this->sortfield1 = $sortfield1;
  104. $this->sortorder1 = $sortorder1;
  105. $this->sortfield2 = $sortfield2;
  106. $this->sortorder2 = $sortorder2;
  107. $this->gradestack = array();
  108. }
  109. /**
  110. * Initialise the iterator
  111. *
  112. * @return boolean success
  113. */
  114. public function init() {
  115. global $CFG, $DB;
  116. $this->close();
  117. export_verify_grades($this->course->id);
  118. $course_item = grade_item::fetch_course_item($this->course->id);
  119. if ($course_item->needsupdate) {
  120. // Can not calculate all final grades - sorry.
  121. return false;
  122. }
  123. $coursecontext = context_course::instance($this->course->id);
  124. list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($coursecontext->get_parent_context_ids(true), SQL_PARAMS_NAMED, 'relatedctx');
  125. list($gradebookroles_sql, $params) = $DB->get_in_or_equal(explode(',', $CFG->gradebookroles), SQL_PARAMS_NAMED, 'grbr');
  126. list($enrolledsql, $enrolledparams) = get_enrolled_sql($coursecontext, '', 0, $this->onlyactive);
  127. $params = array_merge($params, $enrolledparams, $relatedctxparams);
  128. if ($this->groupid) {
  129. $groupsql = "INNER JOIN {groups_members} gm ON gm.userid = u.id";
  130. $groupwheresql = "AND gm.groupid = :groupid";
  131. // $params contents: gradebookroles
  132. $params['groupid'] = $this->groupid;
  133. } else {
  134. $groupsql = "";
  135. $groupwheresql = "";
  136. }
  137. if (empty($this->sortfield1)) {
  138. // We must do some sorting even if not specified.
  139. $ofields = ", u.id AS usrt";
  140. $order = "usrt ASC";
  141. } else {
  142. $ofields = ", u.$this->sortfield1 AS usrt1";
  143. $order = "usrt1 $this->sortorder1";
  144. if (!empty($this->sortfield2)) {
  145. $ofields .= ", u.$this->sortfield2 AS usrt2";
  146. $order .= ", usrt2 $this->sortorder2";
  147. }
  148. if ($this->sortfield1 != 'id' and $this->sortfield2 != 'id') {
  149. // User order MUST be the same in both queries,
  150. // must include the only unique user->id if not already present.
  151. $ofields .= ", u.id AS usrt";
  152. $order .= ", usrt ASC";
  153. }
  154. }
  155. $userfields = 'u.*';
  156. $customfieldssql = '';
  157. if ($this->allowusercustomfields && !empty($CFG->grade_export_customprofilefields)) {
  158. $customfieldscount = 0;
  159. $customfieldsarray = grade_helper::get_user_profile_fields($this->course->id, $this->allowusercustomfields);
  160. foreach ($customfieldsarray as $field) {
  161. if (!empty($field->customid)) {
  162. $customfieldssql .= "
  163. LEFT JOIN (SELECT * FROM {user_info_data}
  164. WHERE fieldid = :cf$customfieldscount) cf$customfieldscount
  165. ON u.id = cf$customfieldscount.userid";
  166. $userfields .= ", cf$customfieldscount.data AS customfield_{$field->shortname}";
  167. $params['cf'.$customfieldscount] = $field->customid;
  168. $customfieldscount++;
  169. }
  170. }
  171. }
  172. $users_sql = "SELECT $userfields $ofields
  173. FROM {user} u
  174. JOIN ($enrolledsql) je ON je.id = u.id
  175. $groupsql $customfieldssql
  176. JOIN (
  177. SELECT DISTINCT ra.userid
  178. FROM {role_assignments} ra
  179. WHERE ra.roleid $gradebookroles_sql
  180. AND ra.contextid $relatedctxsql
  181. ) rainner ON rainner.userid = u.id
  182. WHERE u.deleted = 0
  183. $groupwheresql
  184. ORDER BY $order";
  185. $this->users_rs = $DB->get_recordset_sql($users_sql, $params);
  186. if (!$this->onlyactive) {
  187. $context = context_course::instance($this->course->id);
  188. $this->suspendedusers = get_suspended_userids($context);
  189. } else {
  190. $this->suspendedusers = array();
  191. }
  192. if (!empty($this->grade_items)) {
  193. $itemids = array_keys($this->grade_items);
  194. list($itemidsql, $grades_params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED, 'items');
  195. $params = array_merge($params, $grades_params);
  196. $grades_sql = "SELECT g.* $ofields
  197. FROM {grade_grades} g
  198. JOIN {user} u ON g.userid = u.id
  199. JOIN ($enrolledsql) je ON je.id = u.id
  200. $groupsql
  201. JOIN (
  202. SELECT DISTINCT ra.userid
  203. FROM {role_assignments} ra
  204. WHERE ra.roleid $gradebookroles_sql
  205. AND ra.contextid $relatedctxsql
  206. ) rainner ON rainner.userid = u.id
  207. WHERE u.deleted = 0
  208. AND g.itemid $itemidsql
  209. $groupwheresql
  210. ORDER BY $order, g.itemid ASC";
  211. $this->grades_rs = $DB->get_recordset_sql($grades_sql, $params);
  212. } else {
  213. $this->grades_rs = false;
  214. }
  215. return true;
  216. }
  217. /**
  218. * Returns information about the next user
  219. * @return mixed array of user info, all grades and feedback or null when no more users found
  220. */
  221. public function next_user() {
  222. if (!$this->users_rs) {
  223. return false; // no users present
  224. }
  225. if (!$this->users_rs->valid()) {
  226. if ($current = $this->_pop()) {
  227. // this is not good - user or grades updated between the two reads above :-(
  228. }
  229. return false; // no more users
  230. } else {
  231. $user = $this->users_rs->current();
  232. $this->users_rs->next();
  233. }
  234. // find grades of this user
  235. $grade_records = array();
  236. while (true) {
  237. if (!$current = $this->_pop()) {
  238. break; // no more grades
  239. }
  240. if (empty($current->userid)) {
  241. break;
  242. }
  243. if ($current->userid != $user->id) {
  244. // grade of the next user, we have all for this user
  245. $this->_push($current);
  246. break;
  247. }
  248. $grade_records[$current->itemid] = $current;
  249. }
  250. $grades = array();
  251. $feedbacks = array();
  252. if (!empty($this->grade_items)) {
  253. foreach ($this->grade_items as $grade_item) {
  254. if (!isset($feedbacks[$grade_item->id])) {
  255. $feedbacks[$grade_item->id] = new stdClass();
  256. }
  257. if (array_key_exists($grade_item->id, $grade_records)) {
  258. $feedbacks[$grade_item->id]->feedback = $grade_records[$grade_item->id]->feedback;
  259. $feedbacks[$grade_item->id]->feedbackformat = $grade_records[$grade_item->id]->feedbackformat;
  260. unset($grade_records[$grade_item->id]->feedback);
  261. unset($grade_records[$grade_item->id]->feedbackformat);
  262. $grades[$grade_item->id] = new grade_grade($grade_records[$grade_item->id], false);
  263. } else {
  264. $feedbacks[$grade_item->id]->feedback = '';
  265. $feedbacks[$grade_item->id]->feedbackformat = FORMAT_MOODLE;
  266. $grades[$grade_item->id] =
  267. new grade_grade(array('userid'=>$user->id, 'itemid'=>$grade_item->id), false);
  268. }
  269. }
  270. }
  271. // Set user suspended status.
  272. $user->suspendedenrolment = isset($this->suspendedusers[$user->id]);
  273. $result = new stdClass();
  274. $result->user = $user;
  275. $result->grades = $grades;
  276. $result->feedbacks = $feedbacks;
  277. return $result;
  278. }
  279. /**
  280. * Close the iterator, do not forget to call this function
  281. */
  282. public function close() {
  283. if ($this->users_rs) {
  284. $this->users_rs->close();
  285. $this->users_rs = null;
  286. }
  287. if ($this->grades_rs) {
  288. $this->grades_rs->close();
  289. $this->grades_rs = null;
  290. }
  291. $this->gradestack = array();
  292. }
  293. /**
  294. * Should all enrolled users be exported or just those with an active enrolment?
  295. *
  296. * @param bool $onlyactive True to limit the export to users with an active enrolment
  297. */
  298. public function require_active_enrolment($onlyactive = true) {
  299. if (!empty($this->users_rs)) {
  300. debugging('Calling require_active_enrolment() has no effect unless you call init() again', DEBUG_DEVELOPER);
  301. }
  302. $this->onlyactive = $onlyactive;
  303. }
  304. /**
  305. * Allow custom fields to be included
  306. *
  307. * @param bool $allow Whether to allow custom fields or not
  308. * @return void
  309. */
  310. public function allow_user_custom_fields($allow = true) {
  311. if ($allow) {
  312. $this->allowusercustomfields = true;
  313. } else {
  314. $this->allowusercustomfields = false;
  315. }
  316. }
  317. /**
  318. * Add a grade_grade instance to the grade stack
  319. *
  320. * @param grade_grade $grade Grade object
  321. *
  322. * @return void
  323. */
  324. private function _push($grade) {
  325. array_push($this->gradestack, $grade);
  326. }
  327. /**
  328. * Remove a grade_grade instance from the grade stack
  329. *
  330. * @return grade_grade current grade object
  331. */
  332. private function _pop() {
  333. global $DB;
  334. if (empty($this->gradestack)) {
  335. if (empty($this->grades_rs) || !$this->grades_rs->valid()) {
  336. return null; // no grades present
  337. }
  338. $current = $this->grades_rs->current();
  339. $this->grades_rs->next();
  340. return $current;
  341. } else {
  342. return array_pop($this->gradestack);
  343. }
  344. }
  345. }
  346. /**
  347. * Print a selection popup form of the graded users in a course.
  348. *
  349. * @deprecated since 2.0
  350. *
  351. * @param int $course id of the course
  352. * @param string $actionpage The page receiving the data from the popoup form
  353. * @param int $userid id of the currently selected user (or 'all' if they are all selected)
  354. * @param int $groupid id of requested group, 0 means all
  355. * @param int $includeall bool include all option
  356. * @param bool $return If true, will return the HTML, otherwise, will print directly
  357. * @return null
  358. */
  359. function print_graded_users_selector($course, $actionpage, $userid=0, $groupid=0, $includeall=true, $return=false) {
  360. global $CFG, $USER, $OUTPUT;
  361. return $OUTPUT->render(grade_get_graded_users_select(substr($actionpage, 0, strpos($actionpage, '/')), $course, $userid, $groupid, $includeall));
  362. }
  363. function grade_get_graded_users_select($report, $course, $userid, $groupid, $includeall) {
  364. global $USER, $CFG;
  365. if (is_null($userid)) {
  366. $userid = $USER->id;
  367. }
  368. $coursecontext = context_course::instance($course->id);
  369. $defaultgradeshowactiveenrol = !empty($CFG->grade_report_showonlyactiveenrol);
  370. $showonlyactiveenrol = get_user_preferences('grade_report_showonlyactiveenrol', $defaultgradeshowactiveenrol);
  371. $showonlyactiveenrol = $showonlyactiveenrol || !has_capability('moodle/course:viewsuspendedusers', $coursecontext);
  372. $menu = array(); // Will be a list of userid => user name
  373. $menususpendedusers = array(); // Suspended users go to a separate optgroup.
  374. $gui = new graded_users_iterator($course, null, $groupid);
  375. $gui->require_active_enrolment($showonlyactiveenrol);
  376. $gui->init();
  377. $label = get_string('selectauser', 'grades');
  378. if ($includeall) {
  379. $menu[0] = get_string('allusers', 'grades');
  380. $label = get_string('selectalloroneuser', 'grades');
  381. }
  382. while ($userdata = $gui->next_user()) {
  383. $user = $userdata->user;
  384. $userfullname = fullname($user);
  385. if ($user->suspendedenrolment) {
  386. $menususpendedusers[$user->id] = $userfullname;
  387. } else {
  388. $menu[$user->id] = $userfullname;
  389. }
  390. }
  391. $gui->close();
  392. if ($includeall) {
  393. $menu[0] .= " (" . (count($menu) + count($menususpendedusers) - 1) . ")";
  394. }
  395. if (!empty($menususpendedusers)) {
  396. $menu[] = array(get_string('suspendedusers') => $menususpendedusers);
  397. }
  398. $select = new single_select(new moodle_url('/grade/report/'.$report.'/index.php', array('id'=>$course->id)), 'userid', $menu, $userid);
  399. $select->label = $label;
  400. $select->formid = 'choosegradeuser';
  401. return $select;
  402. }
  403. /**
  404. * Print grading plugin selection popup form.
  405. *
  406. * @param array $plugin_info An array of plugins containing information for the selector
  407. * @param boolean $return return as string
  408. *
  409. * @return nothing or string if $return true
  410. */
  411. function print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return=false) {
  412. global $CFG, $OUTPUT, $PAGE;
  413. $menu = array();
  414. $count = 0;
  415. $active = '';
  416. foreach ($plugin_info as $plugin_type => $plugins) {
  417. if ($plugin_type == 'strings') {
  418. continue;
  419. }
  420. $first_plugin = reset($plugins);
  421. $sectionname = $plugin_info['strings'][$plugin_type];
  422. $section = array();
  423. foreach ($plugins as $plugin) {
  424. $link = $plugin->link->out(false);
  425. $section[$link] = $plugin->string;
  426. $count++;
  427. if ($plugin_type === $active_type and $plugin->id === $active_plugin) {
  428. $active = $link;
  429. }
  430. }
  431. if ($section) {
  432. $menu[] = array($sectionname=>$section);
  433. }
  434. }
  435. // finally print/return the popup form
  436. if ($count > 1) {
  437. $select = new url_select($menu, $active, null, 'choosepluginreport');
  438. $select->set_label(get_string('gradereport', 'grades'), array('class' => 'accesshide'));
  439. if ($return) {
  440. return $OUTPUT->render($select);
  441. } else {
  442. echo $OUTPUT->render($select);
  443. }
  444. } else {
  445. // only one option - no plugin selector needed
  446. return '';
  447. }
  448. }
  449. /**
  450. * Print grading plugin selection tab-based navigation.
  451. *
  452. * @param string $active_type type of plugin on current page - import, export, report or edit
  453. * @param string $active_plugin active plugin type - grader, user, cvs, ...
  454. * @param array $plugin_info Array of plugins
  455. * @param boolean $return return as string
  456. *
  457. * @return nothing or string if $return true
  458. */
  459. function grade_print_tabs($active_type, $active_plugin, $plugin_info, $return=false) {
  460. global $CFG, $COURSE;
  461. if (!isset($currenttab)) { //TODO: this is weird
  462. $currenttab = '';
  463. }
  464. $tabs = array();
  465. $top_row = array();
  466. $bottom_row = array();
  467. $inactive = array($active_plugin);
  468. $activated = array();
  469. $count = 0;
  470. $active = '';
  471. foreach ($plugin_info as $plugin_type => $plugins) {
  472. if ($plugin_type == 'strings') {
  473. continue;
  474. }
  475. // If $plugins is actually the definition of a child-less parent link:
  476. if (!empty($plugins->id)) {
  477. $string = $plugins->string;
  478. if (!empty($plugin_info[$active_type]->parent)) {
  479. $string = $plugin_info[$active_type]->parent->string;
  480. }
  481. $top_row[] = new tabobject($plugin_type, $plugins->link, $string);
  482. continue;
  483. }
  484. $first_plugin = reset($plugins);
  485. $url = $first_plugin->link;
  486. if ($plugin_type == 'report') {
  487. $url = $CFG->wwwroot.'/grade/report/index.php?id='.$COURSE->id;
  488. }
  489. $top_row[] = new tabobject($plugin_type, $url, $plugin_info['strings'][$plugin_type]);
  490. if ($active_type == $plugin_type) {
  491. foreach ($plugins as $plugin) {
  492. $bottom_row[] = new tabobject($plugin->id, $plugin->link, $plugin->string);
  493. if ($plugin->id == $active_plugin) {
  494. $inactive = array($plugin->id);
  495. }
  496. }
  497. }
  498. }
  499. $tabs[] = $top_row;
  500. $tabs[] = $bottom_row;
  501. if ($return) {
  502. return print_tabs($tabs, $active_type, $inactive, $activated, true);
  503. } else {
  504. print_tabs($tabs, $active_type, $inactive, $activated);
  505. }
  506. }
  507. /**
  508. * grade_get_plugin_info
  509. *
  510. * @param int $courseid The course id
  511. * @param string $active_type type of plugin on current page - import, export, report or edit
  512. * @param string $active_plugin active plugin type - grader, user, cvs, ...
  513. *
  514. * @return array
  515. */
  516. function grade_get_plugin_info($courseid, $active_type, $active_plugin) {
  517. global $CFG, $SITE;
  518. $context = context_course::instance($courseid);
  519. $plugin_info = array();
  520. $count = 0;
  521. $active = '';
  522. $url_prefix = $CFG->wwwroot . '/grade/';
  523. // Language strings
  524. $plugin_info['strings'] = grade_helper::get_plugin_strings();
  525. if ($reports = grade_helper::get_plugins_reports($courseid)) {
  526. $plugin_info['report'] = $reports;
  527. }
  528. //showing grade categories and items make no sense if we're not within a course
  529. if ($courseid!=$SITE->id) {
  530. if ($edittree = grade_helper::get_info_edit_structure($courseid)) {
  531. $plugin_info['edittree'] = $edittree;
  532. }
  533. }
  534. if ($scale = grade_helper::get_info_scales($courseid)) {
  535. $plugin_info['scale'] = array('view'=>$scale);
  536. }
  537. if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
  538. $plugin_info['outcome'] = $outcomes;
  539. }
  540. if ($letters = grade_helper::get_info_letters($courseid)) {
  541. $plugin_info['letter'] = $letters;
  542. }
  543. if ($imports = grade_helper::get_plugins_import($courseid)) {
  544. $plugin_info['import'] = $imports;
  545. }
  546. if ($exports = grade_helper::get_plugins_export($courseid)) {
  547. $plugin_info['export'] = $exports;
  548. }
  549. foreach ($plugin_info as $plugin_type => $plugins) {
  550. if (!empty($plugins->id) && $active_plugin == $plugins->id) {
  551. $plugin_info['strings']['active_plugin_str'] = $plugins->string;
  552. break;
  553. }
  554. foreach ($plugins as $plugin) {
  555. if (is_a($plugin, 'grade_plugin_info')) {
  556. if ($active_plugin == $plugin->id) {
  557. $plugin_info['strings']['active_plugin_str'] = $plugin->string;
  558. }
  559. }
  560. }
  561. }
  562. //hide course settings if we're not in a course
  563. if ($courseid!=$SITE->id) {
  564. if ($setting = grade_helper::get_info_manage_settings($courseid)) {
  565. $plugin_info['settings'] = array('course'=>$setting);
  566. }
  567. }
  568. // Put preferences last
  569. if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
  570. $plugin_info['preferences'] = $preferences;
  571. }
  572. foreach ($plugin_info as $plugin_type => $plugins) {
  573. if (!empty($plugins->id) && $active_plugin == $plugins->id) {
  574. $plugin_info['strings']['active_plugin_str'] = $plugins->string;
  575. break;
  576. }
  577. foreach ($plugins as $plugin) {
  578. if (is_a($plugin, 'grade_plugin_info')) {
  579. if ($active_plugin == $plugin->id) {
  580. $plugin_info['strings']['active_plugin_str'] = $plugin->string;
  581. }
  582. }
  583. }
  584. }
  585. return $plugin_info;
  586. }
  587. /**
  588. * A simple class containing info about grade plugins.
  589. * Can be subclassed for special rules
  590. *
  591. * @package core_grades
  592. * @copyright 2009 Nicolas Connault
  593. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  594. */
  595. class grade_plugin_info {
  596. /**
  597. * A unique id for this plugin
  598. *
  599. * @var mixed
  600. */
  601. public $id;
  602. /**
  603. * A URL to access this plugin
  604. *
  605. * @var mixed
  606. */
  607. public $link;
  608. /**
  609. * The name of this plugin
  610. *
  611. * @var mixed
  612. */
  613. public $string;
  614. /**
  615. * Another grade_plugin_info object, parent of the current one
  616. *
  617. * @var mixed
  618. */
  619. public $parent;
  620. /**
  621. * Constructor
  622. *
  623. * @param int $id A unique id for this plugin
  624. * @param string $link A URL to access this plugin
  625. * @param string $string The name of this plugin
  626. * @param object $parent Another grade_plugin_info object, parent of the current one
  627. *
  628. * @return void
  629. */
  630. public function __construct($id, $link, $string, $parent=null) {
  631. $this->id = $id;
  632. $this->link = $link;
  633. $this->string = $string;
  634. $this->parent = $parent;
  635. }
  636. }
  637. /**
  638. * Prints the page headers, breadcrumb trail, page heading, (optional) dropdown navigation menu and
  639. * (optional) navigation tabs for any gradebook page. All gradebook pages MUST use these functions
  640. * in favour of the usual print_header(), print_header_simple(), print_heading() etc.
  641. * !IMPORTANT! Use of tabs.php file in gradebook pages is forbidden unless tabs are switched off at
  642. * the site level for the gradebook ($CFG->grade_navmethod = GRADE_NAVMETHOD_DROPDOWN).
  643. *
  644. * @param int $courseid Course id
  645. * @param string $active_type The type of the current page (report, settings,
  646. * import, export, scales, outcomes, letters)
  647. * @param string $active_plugin The plugin of the current page (grader, fullview etc...)
  648. * @param string $heading The heading of the page. Tries to guess if none is given
  649. * @param boolean $return Whether to return (true) or echo (false) the HTML generated by this function
  650. * @param string $bodytags Additional attributes that will be added to the <body> tag
  651. * @param string $buttons Additional buttons to display on the page
  652. * @param boolean $shownavigation should the gradebook navigation drop down (or tabs) be shown?
  653. *
  654. * @return string HTML code or nothing if $return == false
  655. */
  656. function print_grade_page_head($courseid, $active_type, $active_plugin=null,
  657. $heading = false, $return=false,
  658. $buttons=false, $shownavigation=true) {
  659. global $CFG, $OUTPUT, $PAGE;
  660. $plugin_info = grade_get_plugin_info($courseid, $active_type, $active_plugin);
  661. // Determine the string of the active plugin
  662. $stractive_plugin = ($active_plugin) ? $plugin_info['strings']['active_plugin_str'] : $heading;
  663. $stractive_type = $plugin_info['strings'][$active_type];
  664. if (empty($plugin_info[$active_type]->id) || !empty($plugin_info[$active_type]->parent)) {
  665. $title = $PAGE->course->fullname.': ' . $stractive_type . ': ' . $stractive_plugin;
  666. } else {
  667. $title = $PAGE->course->fullname.': ' . $stractive_plugin;
  668. }
  669. if ($active_type == 'report') {
  670. $PAGE->set_pagelayout('report');
  671. } else {
  672. $PAGE->set_pagelayout('admin');
  673. }
  674. $PAGE->set_title(get_string('grades') . ': ' . $stractive_type);
  675. $PAGE->set_heading($title);
  676. if ($buttons instanceof single_button) {
  677. $buttons = $OUTPUT->render($buttons);
  678. }
  679. $PAGE->set_button($buttons);
  680. grade_extend_settings($plugin_info, $courseid);
  681. $returnval = $OUTPUT->header();
  682. if (!$return) {
  683. echo $returnval;
  684. }
  685. // Guess heading if not given explicitly
  686. if (!$heading) {
  687. $heading = $stractive_plugin;
  688. }
  689. if ($shownavigation) {
  690. if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_DROPDOWN) {
  691. $returnval .= print_grade_plugin_selector($plugin_info, $active_type, $active_plugin, $return);
  692. }
  693. if ($return) {
  694. $returnval .= $OUTPUT->heading($heading);
  695. } else {
  696. echo $OUTPUT->heading($heading);
  697. }
  698. if ($CFG->grade_navmethod == GRADE_NAVMETHOD_COMBO || $CFG->grade_navmethod == GRADE_NAVMETHOD_TABS) {
  699. $returnval .= grade_print_tabs($active_type, $active_plugin, $plugin_info, $return);
  700. }
  701. }
  702. if ($return) {
  703. return $returnval;
  704. }
  705. }
  706. /**
  707. * Utility class used for return tracking when using edit and other forms in grade plugins
  708. *
  709. * @package core_grades
  710. * @copyright 2009 Nicolas Connault
  711. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  712. */
  713. class grade_plugin_return {
  714. public $type;
  715. public $plugin;
  716. public $courseid;
  717. public $userid;
  718. public $page;
  719. /**
  720. * Constructor
  721. *
  722. * @param array $params - associative array with return parameters, if null parameter are taken from _GET or _POST
  723. */
  724. public function grade_plugin_return($params = null) {
  725. if (empty($params)) {
  726. $this->type = optional_param('gpr_type', null, PARAM_SAFEDIR);
  727. $this->plugin = optional_param('gpr_plugin', null, PARAM_PLUGIN);
  728. $this->courseid = optional_param('gpr_courseid', null, PARAM_INT);
  729. $this->userid = optional_param('gpr_userid', null, PARAM_INT);
  730. $this->page = optional_param('gpr_page', null, PARAM_INT);
  731. } else {
  732. foreach ($params as $key=>$value) {
  733. if (property_exists($this, $key)) {
  734. $this->$key = $value;
  735. }
  736. }
  737. }
  738. }
  739. /**
  740. * Returns return parameters as options array suitable for buttons.
  741. * @return array options
  742. */
  743. public function get_options() {
  744. if (empty($this->type)) {
  745. return array();
  746. }
  747. $params = array();
  748. if (!empty($this->plugin)) {
  749. $params['plugin'] = $this->plugin;
  750. }
  751. if (!empty($this->courseid)) {
  752. $params['id'] = $this->courseid;
  753. }
  754. if (!empty($this->userid)) {
  755. $params['userid'] = $this->userid;
  756. }
  757. if (!empty($this->page)) {
  758. $params['page'] = $this->page;
  759. }
  760. return $params;
  761. }
  762. /**
  763. * Returns return url
  764. *
  765. * @param string $default default url when params not set
  766. * @param array $extras Extra URL parameters
  767. *
  768. * @return string url
  769. */
  770. public function get_return_url($default, $extras=null) {
  771. global $CFG;
  772. if (empty($this->type) or empty($this->plugin)) {
  773. return $default;
  774. }
  775. $url = $CFG->wwwroot.'/grade/'.$this->type.'/'.$this->plugin.'/index.php';
  776. $glue = '?';
  777. if (!empty($this->courseid)) {
  778. $url .= $glue.'id='.$this->courseid;
  779. $glue = '&amp;';
  780. }
  781. if (!empty($this->userid)) {
  782. $url .= $glue.'userid='.$this->userid;
  783. $glue = '&amp;';
  784. }
  785. if (!empty($this->page)) {
  786. $url .= $glue.'page='.$this->page;
  787. $glue = '&amp;';
  788. }
  789. if (!empty($extras)) {
  790. foreach ($extras as $key=>$value) {
  791. $url .= $glue.$key.'='.$value;
  792. $glue = '&amp;';
  793. }
  794. }
  795. return $url;
  796. }
  797. /**
  798. * Returns string with hidden return tracking form elements.
  799. * @return string
  800. */
  801. public function get_form_fields() {
  802. if (empty($this->type)) {
  803. return '';
  804. }
  805. $result = '<input type="hidden" name="gpr_type" value="'.$this->type.'" />';
  806. if (!empty($this->plugin)) {
  807. $result .= '<input type="hidden" name="gpr_plugin" value="'.$this->plugin.'" />';
  808. }
  809. if (!empty($this->courseid)) {
  810. $result .= '<input type="hidden" name="gpr_courseid" value="'.$this->courseid.'" />';
  811. }
  812. if (!empty($this->userid)) {
  813. $result .= '<input type="hidden" name="gpr_userid" value="'.$this->userid.'" />';
  814. }
  815. if (!empty($this->page)) {
  816. $result .= '<input type="hidden" name="gpr_page" value="'.$this->page.'" />';
  817. }
  818. }
  819. /**
  820. * Add hidden elements into mform
  821. *
  822. * @param object &$mform moodle form object
  823. *
  824. * @return void
  825. */
  826. public function add_mform_elements(&$mform) {
  827. if (empty($this->type)) {
  828. return;
  829. }
  830. $mform->addElement('hidden', 'gpr_type', $this->type);
  831. $mform->setType('gpr_type', PARAM_SAFEDIR);
  832. if (!empty($this->plugin)) {
  833. $mform->addElement('hidden', 'gpr_plugin', $this->plugin);
  834. $mform->setType('gpr_plugin', PARAM_PLUGIN);
  835. }
  836. if (!empty($this->courseid)) {
  837. $mform->addElement('hidden', 'gpr_courseid', $this->courseid);
  838. $mform->setType('gpr_courseid', PARAM_INT);
  839. }
  840. if (!empty($this->userid)) {
  841. $mform->addElement('hidden', 'gpr_userid', $this->userid);
  842. $mform->setType('gpr_userid', PARAM_INT);
  843. }
  844. if (!empty($this->page)) {
  845. $mform->addElement('hidden', 'gpr_page', $this->page);
  846. $mform->setType('gpr_page', PARAM_INT);
  847. }
  848. }
  849. /**
  850. * Add return tracking params into url
  851. *
  852. * @param moodle_url $url A URL
  853. *
  854. * @return string $url with return tracking params
  855. */
  856. public function add_url_params(moodle_url $url) {
  857. if (empty($this->type)) {
  858. return $url;
  859. }
  860. $url->param('gpr_type', $this->type);
  861. if (!empty($this->plugin)) {
  862. $url->param('gpr_plugin', $this->plugin);
  863. }
  864. if (!empty($this->courseid)) {
  865. $url->param('gpr_courseid' ,$this->courseid);
  866. }
  867. if (!empty($this->userid)) {
  868. $url->param('gpr_userid', $this->userid);
  869. }
  870. if (!empty($this->page)) {
  871. $url->param('gpr_page', $this->page);
  872. }
  873. return $url;
  874. }
  875. }
  876. /**
  877. * Function central to gradebook for building and printing the navigation (breadcrumb trail).
  878. *
  879. * @param string $path The path of the calling script (using __FILE__?)
  880. * @param string $pagename The language string to use as the last part of the navigation (non-link)
  881. * @param mixed $id Either a plain integer (assuming the key is 'id') or
  882. * an array of keys and values (e.g courseid => $courseid, itemid...)
  883. *
  884. * @return string
  885. */
  886. function grade_build_nav($path, $pagename=null, $id=null) {
  887. global $CFG, $COURSE, $PAGE;
  888. $strgrades = get_string('grades', 'grades');
  889. // Parse the path and build navlinks from its elements
  890. $dirroot_length = strlen($CFG->dirroot) + 1; // Add 1 for the first slash
  891. $path = substr($path, $dirroot_length);
  892. $path = str_replace('\\', '/', $path);
  893. $path_elements = explode('/', $path);
  894. $path_elements_count = count($path_elements);
  895. // First link is always 'grade'
  896. $PAGE->navbar->add($strgrades, new moodle_url('/grade/index.php', array('id'=>$COURSE->id)));
  897. $link = null;
  898. $numberofelements = 3;
  899. // Prepare URL params string
  900. $linkparams = array();
  901. if (!is_null($id)) {
  902. if (is_array($id)) {
  903. foreach ($id as $idkey => $idvalue) {
  904. $linkparams[$idkey] = $idvalue;
  905. }
  906. } else {
  907. $linkparams['id'] = $id;
  908. }
  909. }
  910. $navlink4 = null;
  911. // Remove file extensions from filenames
  912. foreach ($path_elements as $key => $filename) {
  913. $path_elements[$key] = str_replace('.php', '', $filename);
  914. }
  915. // Second level links
  916. switch ($path_elements[1]) {
  917. case 'edit': // No link
  918. if ($path_elements[3] != 'index.php') {
  919. $numberofelements = 4;
  920. }
  921. break;
  922. case 'import': // No link
  923. break;
  924. case 'export': // No link
  925. break;
  926. case 'report':
  927. // $id is required for this link. Do not print it if $id isn't given
  928. if (!is_null($id)) {
  929. $link = new moodle_url('/grade/report/index.php', $linkparams);
  930. }
  931. if ($path_elements[2] == 'grader') {
  932. $numberofelements = 4;
  933. }
  934. break;
  935. default:
  936. // If this element isn't among the ones already listed above, it isn't supported, throw an error.
  937. debugging("grade_build_nav() doesn't support ". $path_elements[1] .
  938. " as the second path element after 'grade'.");
  939. return false;
  940. }
  941. $PAGE->navbar->add(get_string($path_elements[1], 'grades'), $link);
  942. // Third level links
  943. if (empty($pagename)) {
  944. $pagename = get_string($path_elements[2], 'grades');
  945. }
  946. switch ($numberofelements) {
  947. case 3:
  948. $PAGE->navbar->add($pagename, $link);
  949. break;
  950. case 4:
  951. if ($path_elements[2] == 'grader' AND $path_elements[3] != 'index.php') {
  952. $PAGE->navbar->add(get_string('pluginname', 'gradereport_grader'), new moodle_url('/grade/report/grader/index.php', $linkparams));
  953. }
  954. $PAGE->navbar->add($pagename);
  955. break;
  956. }
  957. return '';
  958. }
  959. /**
  960. * General structure representing grade items in course
  961. *
  962. * @package core_grades
  963. * @copyright 2009 Nicolas Connault
  964. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  965. */
  966. class grade_structure {
  967. public $context;
  968. public $courseid;
  969. /**
  970. * Reference to modinfo for current course (for performance, to save
  971. * retrieving it from courseid every time). Not actually set except for
  972. * the grade_tree type.
  973. * @var course_modinfo
  974. */
  975. public $modinfo;
  976. /**
  977. * 1D array of grade items only
  978. */
  979. public $items;
  980. /**
  981. * Returns icon of element
  982. *
  983. * @param array &$element An array representing an element in the grade_tree
  984. * @param bool $spacerifnone return spacer if no icon found
  985. *
  986. * @return string icon or spacer
  987. */
  988. public function get_element_icon(&$element, $spacerifnone=false) {
  989. global $CFG, $OUTPUT;
  990. require_once $CFG->libdir.'/filelib.php';
  991. switch ($element['type']) {
  992. case 'item':
  993. case 'courseitem':
  994. case 'categoryitem':
  995. $is_course = $element['object']->is_course_item();
  996. $is_category = $element['object']->is_category_item();
  997. $is_scale = $element['object']->gradetype == GRADE_TYPE_SCALE;
  998. $is_value = $element['object']->gradetype == GRADE_TYPE_VALUE;
  999. $is_outcome = !empty($element['object']->outcomeid);
  1000. if ($element['object']->is_calculated()) {
  1001. $strcalc = get_string('calculatedgrade', 'grades');
  1002. return '<img src="'.$OUTPUT->pix_url('i/calc') . '" class="icon itemicon" title="'.
  1003. s($strcalc).'" alt="'.s($strcalc).'"/>';
  1004. } else if (($is_course or $is_category) and ($is_scale or $is_value)) {
  1005. if ($category = $element['object']->get_item_category()) {
  1006. switch ($category->aggregation) {
  1007. case GRADE_AGGREGATE_MEAN:
  1008. case GRADE_AGGREGATE_MEDIAN:
  1009. case GRADE_AGGREGATE_WEIGHTED_MEAN:
  1010. case GRADE_AGGREGATE_WEIGHTED_MEAN2:
  1011. case GRADE_AGGREGATE_EXTRACREDIT_MEAN:
  1012. $stragg = get_string('aggregation', 'grades');
  1013. return '<img src="'.$OUTPUT->pix_url('i/agg_mean') . '" ' .
  1014. 'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
  1015. case GRADE_AGGREGATE_SUM:
  1016. $stragg = get_string('aggregation', 'grades');
  1017. return '<img src="'.$OUTPUT->pix_url('i/agg_sum') . '" ' .
  1018. 'class="icon itemicon" title="'.s($stragg).'" alt="'.s($stragg).'"/>';
  1019. }
  1020. }
  1021. } else if ($element['object']->itemtype == 'mod') {
  1022. //prevent outcomes being displaying the same icon as the activity they are attached to
  1023. if ($is_outcome) {
  1024. $stroutcome = s(get_string('outcome', 'grades'));
  1025. return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
  1026. 'class="icon itemicon" title="'.$stroutcome.
  1027. '" alt="'.$stroutcome.'"/>';
  1028. } else {
  1029. $strmodname = get_string('modulename', $element['object']->itemmodule);
  1030. return '<img src="'.$OUTPUT->pix_url('icon',
  1031. $element['object']->itemmodule) . '" ' .
  1032. 'class="icon itemicon" title="' .s($strmodname).
  1033. '" alt="' .s($strmodname).'"/>';
  1034. }
  1035. } else if ($element['object']->itemtype == 'manual') {
  1036. if ($element['object']->is_outcome_item()) {
  1037. $stroutcome = get_string('outcome', 'grades');
  1038. return '<img src="'.$OUTPUT->pix_url('i/outcomes') . '" ' .
  1039. 'class="icon itemicon" title="'.s($stroutcome).
  1040. '" alt="'.s($stroutcome).'"/>';
  1041. } else {
  1042. $strmanual = get_string('manualitem', 'grades');
  1043. return '<img src="'.$OUTPUT->pix_url('i/manual_item') . '" '.
  1044. 'class="icon itemicon" title="'.s($strmanual).
  1045. '" alt="'.s($strmanual).'"/>';
  1046. }
  1047. }
  1048. break;
  1049. case 'category':
  1050. $strcat = get_string('category', 'grades');
  1051. return '<img src="'.$OUTPUT->pix_url('i/folder') . '" class="icon itemicon" ' .
  1052. 'title="'.s($strcat).'" alt="'.s($strcat).'" />';
  1053. }
  1054. if ($spacerifnone) {
  1055. return $OUTPUT->spacer().' ';
  1056. } else {
  1057. return '';
  1058. }
  1059. }
  1060. /**
  1061. * Returns name of element optionally with icon and link
  1062. *
  1063. * @param array &$element An array representing an element in the grade_tree
  1064. * @param bool $withlink Whether or not this header has a link
  1065. * @param bool $icon Whether or not to display an icon with this header
  1066. * @param bool $spacerifnone return spacer if no icon found
  1067. *
  1068. * @return string header
  1069. */
  1070. public function get_element_header(&$element, $withlink=false, $icon=true, $spacerifnone=false) {
  1071. $header = '';
  1072. if ($icon) {
  1073. $header .= $this->get_element_icon($element, $spacerifnone);
  1074. }
  1075. $header .= $element['object']->get_name();
  1076. if ($element['type'] != 'item' and $element['type'] != 'categoryitem' and
  1077. $element['type'] != 'courseitem') {
  1078. return $header;
  1079. }
  1080. if ($withlink) {
  1081. $url = $this->get_activity_link($element);
  1082. if ($url) {
  1083. $a = new stdClass();
  1084. $a->name = get_string('modulename', $element['object']->itemmodule);
  1085. $title = get_string('linktoactivity', 'grades', $a);
  1086. $header = html_writer::link($url, $header, array('title' => $title));
  1087. }
  1088. }
  1089. return $header;
  1090. }
  1091. private function get_activity_link($element) {
  1092. global $CFG;
  1093. /** @var array static cache of the grade.php file existence flags */
  1094. static $hasgradephp = array();
  1095. $itemtype = $element['object']->itemtype;
  1096. $itemmodule = $element['object']->itemmodule;
  1097. $iteminstance = $element['object']->iteminstance;
  1098. $itemnumber = $element['object']->itemnumber;
  1099. // Links only for module items that have valid instance, module and are
  1100. // called from grade_tree with valid modinfo
  1101. if ($itemtype != 'mod' || !$iteminstance || !$itemmodule || !$this->modinfo) {
  1102. return null;
  1103. }
  1104. // Get $cm efficiently and with visibility information using modinfo
  1105. $instances = $this->modinfo->get_instances();
  1106. if (empty($instances[$itemmodule][$iteminstance])) {
  1107. return null;
  1108. }
  1109. $cm = $instances[$itemmodule][$iteminstance];
  1110. // Do not add link if activity is not visible to the current user
  1111. if (!$cm->uservisible) {
  1112. return null;
  1113. }
  1114. if (!array_key_exists($itemmodule, $hasgradephp)) {
  1115. if (file_exists($CFG->dirroot . '/mod/' . $itemmodule . '/grade.php')) {
  1116. $hasgradephp[$itemmodule] = true;
  1117. } else {
  1118. $hasgradephp[$itemmodule] = false;
  1119. }
  1120. }
  1121. // If module has grade.php, link to that, otherwise view.php
  1122. if ($hasgradephp[$itemmodule]) {
  1123. $args = array('id' => $cm->id, 'itemnumber' => $itemnumber);
  1124. if (isset($element['userid'])) {
  1125. $args['userid'] = $element['userid'];
  1126. }
  1127. return new moodle_url('/mod/' . $itemmodule . '/grade.php', $args);
  1128. } else {
  1129. return new moodle_url('/mod/' . $itemmodule . '/view.php', array('id' => $cm->id));
  1130. }
  1131. }
  1132. /**
  1133. * Returns URL of a page that is supposed to contain detailed grade analysis
  1134. *
  1135. * At the moment, only activity modules are supported. The method generates link
  1136. * to the module's file grade.php with the parameters id (cmid), itemid, itemnumber,
  1137. * gradeid and userid. If the grade.php does not exist, null is returned.
  1138. *
  1139. * @return moodle_url|null URL or null if unable to construct it
  1140. */
  1141. public function get_grade_analysis_url(grade_grade $grade) {
  1142. global $CFG;
  1143. /** @var array static cache of the grade.php file existence flags */
  1144. static $hasgradephp = array();
  1145. if (empty($grade->grade_item) or !($grade->grade_item instanceof grade_item)) {
  1146. throw new coding_exception('Passed grade without the associated grade item');
  1147. }
  1148. $item = $grade->grade_item;
  1149. if (!$item->is_external_item()) {
  1150. // at the moment, only activity modules are supported
  1151. return null;
  1152. }
  1153. if ($item->itemtype !== 'mod') {
  1154. throw new coding_exception('Unknown external itemtype: '.$item->itemtype);
  1155. }
  1156. if (empty($item->iteminstance) or empty($item->itemmodule) or empty($this->modinfo)) {
  1157. return null;
  1158. }
  1159. if (!array_key_exists($item->itemmodule, $hasgradephp)) {
  1160. if (file_exists($CFG->dirroot . '/mod/' . $item->itemmodule . '/grade.php')) {
  1161. $hasgradephp[$item->itemmodule] = true;
  1162. } else {
  1163. $hasgradephp[$item->itemmodule] = false;
  1164. }
  1165. }
  1166. if (!$hasgradephp[$item->itemmodule]) {
  1167. return null;
  1168. }
  1169. $instances = $this->modinfo->get_instances();
  1170. if (empty($instances[$item->itemmodule][$item->iteminstance])) {
  1171. return null;
  1172. }
  1173. $cm = $instances[$item->itemmodule][$item->iteminstance];
  1174. if (!$cm->uservisible) {
  1175. return null;
  1176. }
  1177. $url = new moodle_url('/mod/'.$item->itemmodule.'/grade.php', array(
  1178. 'id' => $cm->id,
  1179. 'itemid' => $item->id,
  1180. 'itemnumber' => $item->itemnumber,
  1181. 'gradeid' => $grade->id,
  1182. 'userid' => $grade->userid,
  1183. ));
  1184. return $url;
  1185. }
  1186. /**
  1187. * Returns an action icon leading to the grade analysis page
  1188. *
  1189. * @param grade_grade $grade
  1190. * @return string
  1191. */
  1192. public function get_grade_analysis_icon(grade_grade $grade) {
  1193. global $OUTPUT;
  1194. $url = $this->get_grade_analysis_url($grade);
  1195. if (is_null($url)) {
  1196. return '';
  1197. }
  1198. return $OUTPUT->action_icon($url, new pix_icon('t/preview',
  1199. get_string('gradeanalysis', 'core_grades')));
  1200. }
  1201. /**
  1202. * Returns the grade eid - the grade may not exist yet.
  1203. *
  1204. * @param grade_grade $grade_grade A grade_grade object
  1205. *
  1206. * @return string eid
  1207. */
  1208. public function get_grade_eid($grade_grade) {
  1209. if (empty($grade_grade->id)) {
  1210. return 'n'.$grade_grade->itemid.'u'.$grade_grade->userid;
  1211. } else {
  1212. return 'g'.$grade_grade->id;
  1213. }
  1214. }
  1215. /**
  1216. * Returns the grade_item eid
  1217. * @param grade_item $grade_item A grade_item object
  1218. * @return string eid
  1219. */
  1220. public function get_item_eid($grade_item) {
  1221. return 'i'.$grade_item->id;
  1222. }
  1223. /**
  1224. * Given a grade_tree element, returns an array of parameters
  1225. * used to build an icon for that element.
  1226. *
  1227. * @param array $element An array representing an element in the grade_tree
  1228. *
  1229. * @return array
  1230. */
  1231. public function get_params_for_iconstr($element) {
  1232. $strparams = new stdClass();
  1233. $strparams->category = '';
  1234. $strparams->itemname = '';
  1235. $strparams->itemmodule = '';
  1236. if (!method_exists($element['object'], 'get_name')) {
  1237. return $strparams;
  1238. }
  1239. $strparams->itemname = html_to_text($element['object']->get_name());
  1240. // If element name is categorytotal, get the name of the parent category
  1241. if ($strparams->itemname == get_string('categorytotal', 'grades')) {
  1242. $parent = $element['object']->get_parent_category();
  1243. $strparams->category = $parent->get_name() . ' ';
  1244. } else {
  1245. $strparams->category = '';
  1246. }
  1247. $strparams->itemmodule = null;
  1248. if (isset($element['object']->itemmodule)) {
  1249. $strparams->itemmodule = $element['object']->itemmodule;
  1250. }
  1251. return $strparams;
  1252. }
  1253. /**
  1254. * Return edit icon for give element
  1255. *
  1256. * @param array $element An array representing an element in the grade_tree
  1257. * @param object $gpr A grade_plugin_return object
  1258. *
  1259. * @return string
  1260. */
  1261. public function get_edit_icon($element, $gpr) {
  1262. global $CFG, $OUTPUT;
  1263. if (!has_capability('moodle/grade:manage', $this->context)) {
  1264. if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
  1265. // oki - let them override grade
  1266. } else {
  1267. return '';
  1268. }
  1269. }
  1270. static $strfeedback = null;
  1271. static $streditgrade = null;
  1272. if (is_null($streditgrade)) {
  1273. $streditgrade = get_string('editgrade', 'grades');
  1274. $strfeedback = get_string('feedback');
  1275. }
  1276. $strparams = $this->get_params_for_iconstr($element);
  1277. $object = $element['object'];
  1278. switch ($element['type']) {
  1279. case 'item':
  1280. case 'categoryitem':
  1281. case 'courseitem':
  1282. $stredit = get_string('editverbose', 'grades', $strparams);
  1283. if (empty($object->outcomeid) || empty($CFG->enableoutcomes)) {
  1284. $url = new moodle_url('/grade/edit/tree/item.php',
  1285. array('courseid' => $this->courseid, 'id' => $object->id));
  1286. } else {
  1287. $url = new moodle_url('/grade/edit/tree/outcomeitem.php',
  1288. array('courseid' => $this->courseid, 'id' => $object->id));
  1289. }
  1290. break;
  1291. case 'category':
  1292. $stredit = get_string('editverbose', 'grades', $strparams);
  1293. $url = new moodle_url('/grade/edit/tree/category.php',
  1294. array('courseid' => $this->courseid, 'id' => $object->id));
  1295. break;
  1296. case 'grade':
  1297. $stredit = $streditgrade;
  1298. if (empty($object->id)) {
  1299. $url = new moodle_url('/grade/edit/tree/grade.php',
  1300. array('courseid' => $this->courseid, 'itemid' => $object->itemid, 'userid' => $object->userid));
  1301. } else {
  1302. $url = new moodle_url('/grade/edit/tree/grade.php',
  1303. array('courseid' => $this->courseid, 'id' => $object->id));
  1304. }
  1305. if (!empty($object->feedback)) {
  1306. $feedback = addslashes_js(trim(format_string($object->feedback, $object->feedbackformat)));
  1307. }
  1308. break;
  1309. default:
  1310. $url = null;
  1311. }
  1312. if ($url) {
  1313. return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
  1314. } else {
  1315. return '';
  1316. }
  1317. }
  1318. /**
  1319. * Return hiding icon for give element
  1320. *
  1321. * @param array $element An array representing an element in the grade_tree
  1322. * @param object $gpr A grade_plugin_return object
  1323. *
  1324. * @return string
  1325. */
  1326. public function get_hiding_icon($element, $gpr) {
  1327. global $CFG, $OUTPUT;
  1328. if (!$element['object']->can_control_visibility()) {
  1329. return '';
  1330. }
  1331. if (!has_capability('moodle/grade:manage', $this->context) and
  1332. !has_capability('moodle/grade:hide', $this->context)) {
  1333. return '';
  1334. }
  1335. $strparams = $this->get_params_for_iconstr($element);
  1336. $strshow = get_string('showverbose', 'grades', $strparams);
  1337. $strhide = get_string('hideverbose', 'grades', $strparams);
  1338. $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
  1339. $url = $gpr->add_url_params($url);
  1340. if ($element['object']->is_hidden()) {
  1341. $type = 'show';
  1342. $tooltip = $strshow;
  1343. // Change the icon and add a tooltip showing the date
  1344. if ($element['type'] != 'category' and $element['object']->get_hidden() > 1) {
  1345. $type = 'hiddenuntil';
  1346. $tooltip = get_string('hiddenuntildate', 'grades',
  1347. userdate($element['object']->get_hidden()));
  1348. }
  1349. $url->param('action', 'show');
  1350. $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon')));
  1351. } else {
  1352. $url->param('action', 'hide');
  1353. $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
  1354. }
  1355. return $hideicon;
  1356. }
  1357. /**
  1358. * Return locking icon for given element
  1359. *
  1360. * @param array $element An array representing an element in the grade_tree
  1361. * @param object $gpr A grade_plugin_return object
  1362. *
  1363. * @return string
  1364. */
  1365. public function get_locking_icon($element, $gpr) {
  1366. global $CFG, $OUTPUT;
  1367. $strparams = $this->get_params_for_iconstr($element);
  1368. $strunlock = get_string('unlockverbose', 'grades', $strparams);
  1369. $strlock = get_string('lockverbose', 'grades', $strparams);
  1370. $url = new moodle_url('/grade/edit/tree/action.php', array('id' => $this->courseid, 'sesskey' => sesskey(), 'eid' => $element['eid']));
  1371. $url = $gpr->add_url_params($url);
  1372. // Don't allow an unlocking action for a grade whose grade item is locked: just print a state icon
  1373. if ($element['type'] == 'grade' && $element['object']->grade_item->is_locked()) {
  1374. $strparamobj = new stdClass();
  1375. $strparamobj->itemname = $element['object']->grade_item->itemname;
  1376. $strnonunlockable = get_string('nonunlockableverbose', 'grades', $strparamobj);
  1377. $action = html_writer::tag('span', $OUTPUT->pix_icon('t/locked', $strnonunlockable),
  1378. array('class' => 'action-icon'));
  1379. } else if ($element['object']->is_locked()) {
  1380. $type = 'unlock';
  1381. $tooltip = $strunlock;
  1382. // Change the icon and add a tooltip showing the date
  1383. if ($element['type'] != 'category' and $element['object']->get_locktime() > 1) {
  1384. $type = 'locktime';
  1385. $tooltip = get_string('locktimedate', 'grades',
  1386. userdate($element['object']->get_locktime()));
  1387. }
  1388. if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:unlock', $this->context)) {
  1389. $action = '';
  1390. } else {
  1391. $url->param('action', 'unlock');
  1392. $action = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strunlock, 'class'=>'smallicon')));
  1393. }
  1394. } else {
  1395. if (!has_capability('moodle/grade:manage', $this->context) and !has_capability('moodle/grade:lock', $this->context)) {
  1396. $action = '';
  1397. } else {
  1398. $url->param('action', 'lock');
  1399. $action = $OUTPUT->action_icon($url, new pix_icon('t/lock', $strlock));
  1400. }
  1401. }
  1402. return $action;
  1403. }
  1404. /**
  1405. * Return calculation icon for given element
  1406. *
  1407. * @param array $element An array representing an element in the grade_tree
  1408. * @param object $gpr A grade_plugin_return object
  1409. *
  1410. * @return string
  1411. */
  1412. public function get_calculation_icon($element, $gpr) {
  1413. global $CFG, $OUTPUT;
  1414. if (!has_capability('moodle/grade:manage', $this->context)) {
  1415. return '';
  1416. }
  1417. $type = $element['type'];
  1418. $object = $element['object'];
  1419. if ($type == 'item' or $type == 'courseitem' or $type == 'categoryitem') {
  1420. $strparams = $this->get_params_for_iconstr($element);
  1421. $streditcalculation = get_string('editcalculationverbose', 'grades', $strparams);
  1422. $is_scale = $object->gradetype == GRADE_TYPE_SCALE;
  1423. $is_value = $object->gradetype == GRADE_TYPE_VALUE;
  1424. // show calculation icon only when calculation possible
  1425. if (!$object->is_external_item() and ($is_scale or $is_value)) {
  1426. if ($object->is_calculated()) {
  1427. $icon = 't/calc';
  1428. } else {
  1429. $icon = 't/calc_off';
  1430. }
  1431. $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
  1432. $url = $gpr->add_url_params($url);
  1433. return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation));
  1434. }
  1435. }
  1436. return '';
  1437. }
  1438. }
  1439. /**
  1440. * Flat structure similar to grade tree.
  1441. *
  1442. * @uses grade_structure
  1443. * @package core_grades
  1444. * @copyright 2009 Nicolas Connault
  1445. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  1446. */
  1447. class grade_seq extends grade_structure {
  1448. /**
  1449. * 1D array of elements
  1450. */
  1451. public $elements;
  1452. /**
  1453. * Constructor, retrieves and stores array of all grade_category and grade_item
  1454. * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
  1455. *
  1456. * @param int $courseid The course id
  1457. * @param bool $category_grade_last category grade item is the last child
  1458. * @param bool $nooutcomes Whether or not outcomes should be included
  1459. */
  1460. public function grade_seq($courseid, $category_grade_last=false, $nooutcomes=false) {
  1461. global $USER, $CFG;
  1462. $this->courseid = $courseid;
  1463. $this->context = context_course::instance($courseid);
  1464. // get course grade tree
  1465. $top_element = grade_category::fetch_course_tree($courseid, true);
  1466. $this->elements = grade_seq::flatten($top_element, $category_grade_last, $nooutcomes);
  1467. foreach ($this->elements as $key=>$unused) {
  1468. $this->items[$this->elements[$key]['object']->id] =& $this->elements[$key]['object'];
  1469. }
  1470. }
  1471. /**
  1472. * Static recursive helper - makes the grade_item for category the last children
  1473. *
  1474. * @param array &$element The seed of the recursion
  1475. * @param bool $category_grade_last category grade item is the last child
  1476. * @param bool $nooutcomes Whether or not outcomes should be included
  1477. *
  1478. * @return array
  1479. */
  1480. public function flatten(&$element, $category_grade_last, $nooutcomes) {
  1481. if (empty($element['children'])) {
  1482. return array();
  1483. }
  1484. $children = array();
  1485. foreach ($element['children'] as $sortorder=>$unused) {
  1486. if ($nooutcomes and $element['type'] != 'category' and
  1487. $element['children'][$sortorder]['object']->is_outcome_item()) {
  1488. continue;
  1489. }
  1490. $children[] = $element['children'][$sortorder];
  1491. }
  1492. unset($element['children']);
  1493. if ($category_grade_last and count($children) > 1) {
  1494. $cat_item = array_shift($children);
  1495. array_push($children, $cat_item);
  1496. }
  1497. $result = array();
  1498. foreach ($children as $child) {
  1499. if ($child['type'] == 'category') {
  1500. $result = $result + grade_seq::flatten($child, $category_grade_last, $nooutcomes);
  1501. } else {
  1502. $child['eid'] = 'i'.$child['object']->id;
  1503. $result[$child['object']->id] = $child;
  1504. }
  1505. }
  1506. return $result;
  1507. }
  1508. /**
  1509. * Parses the array in search of a given eid and returns a element object with
  1510. * information about the element it has found.
  1511. *
  1512. * @param int $eid Gradetree Element ID
  1513. *
  1514. * @return object element
  1515. */
  1516. public function locate_element($eid) {
  1517. // it is a grade - construct a new object
  1518. if (strpos($eid, 'n') === 0) {
  1519. if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
  1520. return null;
  1521. }
  1522. $itemid = $matches[1];
  1523. $userid = $matches[2];
  1524. //extra security check - the grade item must be in this tree
  1525. if (!$item_el = $this->locate_element('i'.$itemid)) {
  1526. return null;
  1527. }
  1528. // $gradea->id may be null - means does not exist yet
  1529. $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
  1530. $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
  1531. return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
  1532. } else if (strpos($eid, 'g') === 0) {
  1533. $id = (int) substr($eid, 1);
  1534. if (!$grade = grade_grade::fetch(array('id'=>$id))) {
  1535. return null;
  1536. }
  1537. //extra security check - the grade item must be in this tree
  1538. if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
  1539. return null;
  1540. }
  1541. $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
  1542. return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
  1543. }
  1544. // it is a category or item
  1545. foreach ($this->elements as $element) {
  1546. if ($element['eid'] == $eid) {
  1547. return $element;
  1548. }
  1549. }
  1550. return null;
  1551. }
  1552. }
  1553. /**
  1554. * This class represents a complete tree of categories, grade_items and final grades,
  1555. * organises as an array primarily, but which can also be converted to other formats.
  1556. * It has simple method calls with complex implementations, allowing for easy insertion,
  1557. * deletion and moving of items and categories within the tree.
  1558. *
  1559. * @uses grade_structure
  1560. * @package core_grades
  1561. * @copyright 2009 Nicolas Connault
  1562. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  1563. */
  1564. class grade_tree extends grade_structure {
  1565. /**
  1566. * The basic representation of the tree as a hierarchical, 3-tiered array.
  1567. * @var object $top_element
  1568. */
  1569. public $top_element;
  1570. /**
  1571. * 2D array of grade items and categories
  1572. * @var array $levels
  1573. */
  1574. public $levels;
  1575. /**
  1576. * Grade items
  1577. * @var array $items
  1578. */
  1579. public $items;
  1580. /**
  1581. * Constructor, retrieves and stores a hierarchical array of all grade_category and grade_item
  1582. * objects for the given courseid. Full objects are instantiated. Ordering sequence is fixed if needed.
  1583. *
  1584. * @param int $courseid The Course ID
  1585. * @param bool $fillers include fillers and colspans, make the levels var "rectangular"
  1586. * @param bool $category_grade_last category grade item is the last child
  1587. * @param array $collapsed array of collapsed categories
  1588. * @param bool $nooutcomes Whether or not outcomes should be included
  1589. */
  1590. public function grade_tree($courseid, $fillers=true, $category_grade_last=false,
  1591. $collapsed=null, $nooutcomes=false) {
  1592. global $USER, $CFG, $COURSE, $DB;
  1593. $this->courseid = $courseid;
  1594. $this->levels = array();
  1595. $this->context = context_course::instance($courseid);
  1596. if (!empty($COURSE->id) && $COURSE->id == $this->courseid) {
  1597. $course = $COURSE;
  1598. } else {
  1599. $course = $DB->get_record('course', array('id' => $this->courseid));
  1600. }
  1601. $this->modinfo = get_fast_modinfo($course);
  1602. // get course grade tree
  1603. $this->top_element = grade_category::fetch_course_tree($courseid, true);
  1604. // collapse the categories if requested
  1605. if (!empty($collapsed)) {
  1606. grade_tree::category_collapse($this->top_element, $collapsed);
  1607. }
  1608. // no otucomes if requested
  1609. if (!empty($nooutcomes)) {
  1610. grade_tree::no_outcomes($this->top_element);
  1611. }
  1612. // move category item to last position in category
  1613. if ($category_grade_last) {
  1614. grade_tree::category_grade_last($this->top_element);
  1615. }
  1616. if ($fillers) {
  1617. // inject fake categories == fillers
  1618. grade_tree::inject_fillers($this->top_element, 0);
  1619. // add colspans to categories and fillers
  1620. grade_tree::inject_colspans($this->top_element);
  1621. }
  1622. grade_tree::fill_levels($this->levels, $this->top_element, 0);
  1623. }
  1624. /**
  1625. * Static recursive helper - removes items from collapsed categories
  1626. *
  1627. * @param array &$element The seed of the recursion
  1628. * @param array $collapsed array of collapsed categories
  1629. *
  1630. * @return void
  1631. */
  1632. public function category_collapse(&$element, $collapsed) {
  1633. if ($element['type'] != 'category') {
  1634. return;
  1635. }
  1636. if (empty($element['children']) or count($element['children']) < 2) {
  1637. return;
  1638. }
  1639. if (in_array($element['object']->id, $collapsed['aggregatesonly'])) {
  1640. $category_item = reset($element['children']); //keep only category item
  1641. $element['children'] = array(key($element['children'])=>$category_item);
  1642. } else {
  1643. if (in_array($element['object']->id, $collapsed['gradesonly'])) { // Remove category item
  1644. reset($element['children']);
  1645. $first_key = key($element['children']);
  1646. unset($element['children'][$first_key]);
  1647. }
  1648. foreach ($element['children'] as $sortorder=>$child) { // Recurse through the element's children
  1649. grade_tree::category_collapse($element['children'][$sortorder], $collapsed);
  1650. }
  1651. }
  1652. }
  1653. /**
  1654. * Static recursive helper - removes all outcomes
  1655. *
  1656. * @param array &$element The seed of the recursion
  1657. *
  1658. * @return void
  1659. */
  1660. public function no_outcomes(&$element) {
  1661. if ($element['type'] != 'category') {
  1662. return;
  1663. }
  1664. foreach ($element['children'] as $sortorder=>$child) {
  1665. if ($element['children'][$sortorder]['type'] == 'item'
  1666. and $element['children'][$sortorder]['object']->is_outcome_item()) {
  1667. unset($element['children'][$sortorder]);
  1668. } else if ($element['children'][$sortorder]['type'] == 'category') {
  1669. grade_tree::no_outcomes($element['children'][$sortorder]);
  1670. }
  1671. }
  1672. }
  1673. /**
  1674. * Static recursive helper - makes the grade_item for category the last children
  1675. *
  1676. * @param array &$element The seed of the recursion
  1677. *
  1678. * @return void
  1679. */
  1680. public function category_grade_last(&$element) {
  1681. if (empty($element['children'])) {
  1682. return;
  1683. }
  1684. if (count($element['children']) < 2) {
  1685. return;
  1686. }
  1687. $first_item = reset($element['children']);
  1688. if ($first_item['type'] == 'categoryitem' or $first_item['type'] == 'courseitem') {
  1689. // the category item might have been already removed
  1690. $order = key($element['children']);
  1691. unset($element['children'][$order]);
  1692. $element['children'][$order] =& $first_item;
  1693. }
  1694. foreach ($element['children'] as $sortorder => $child) {
  1695. grade_tree::category_grade_last($element['children'][$sortorder]);
  1696. }
  1697. }
  1698. /**
  1699. * Static recursive helper - fills the levels array, useful when accessing tree elements of one level
  1700. *
  1701. * @param array &$levels The levels of the grade tree through which to recurse
  1702. * @param array &$element The seed of the recursion
  1703. * @param int $depth How deep are we?
  1704. * @return void
  1705. */
  1706. public function fill_levels(&$levels, &$element, $depth) {
  1707. if (!array_key_exists($depth, $levels)) {
  1708. $levels[$depth] = array();
  1709. }
  1710. // prepare unique identifier
  1711. if ($element['type'] == 'category') {
  1712. $element['eid'] = 'c'.$element['object']->id;
  1713. } else if (in_array($element['type'], array('item', 'courseitem', 'categoryitem'))) {
  1714. $element['eid'] = 'i'.$element['object']->id;
  1715. $this->items[$element['object']->id] =& $element['object'];
  1716. }
  1717. $levels[$depth][] =& $element;
  1718. $depth++;
  1719. if (empty($element['children'])) {
  1720. return;
  1721. }
  1722. $prev = 0;
  1723. foreach ($element['children'] as $sortorder=>$child) {
  1724. grade_tree::fill_levels($levels, $element['children'][$sortorder], $depth);
  1725. $element['children'][$sortorder]['prev'] = $prev;
  1726. $element['children'][$sortorder]['next'] = 0;
  1727. if ($prev) {
  1728. $element['children'][$prev]['next'] = $sortorder;
  1729. }
  1730. $prev = $sortorder;
  1731. }
  1732. }
  1733. /**
  1734. * Static recursive helper - makes full tree (all leafes are at the same level)
  1735. *
  1736. * @param array &$element The seed of the recursion
  1737. * @param int $depth How deep are we?
  1738. *
  1739. * @return int
  1740. */
  1741. public function inject_fillers(&$element, $depth) {
  1742. $depth++;
  1743. if (empty($element['children'])) {
  1744. return $depth;
  1745. }
  1746. $chdepths = array();
  1747. $chids = array_keys($element['children']);
  1748. $last_child = end($chids);
  1749. $first_child = reset($chids);
  1750. foreach ($chids as $chid) {
  1751. $chdepths[$chid] = grade_tree::inject_fillers($element['children'][$chid], $depth);
  1752. }
  1753. arsort($chdepths);
  1754. $maxdepth = reset($chdepths);
  1755. foreach ($chdepths as $chid=>$chd) {
  1756. if ($chd == $maxdepth) {
  1757. continue;
  1758. }
  1759. for ($i=0; $i < $maxdepth-$chd; $i++) {
  1760. if ($chid == $first_child) {
  1761. $type = 'fillerfirst';
  1762. } else if ($chid == $last_child) {
  1763. $type = 'fillerlast';
  1764. } else {
  1765. $type = 'filler';
  1766. }
  1767. $oldchild =& $element['children'][$chid];
  1768. $element['children'][$chid] = array('object'=>'filler', 'type'=>$type,
  1769. 'eid'=>'', 'depth'=>$element['object']->depth,
  1770. 'children'=>array($oldchild));
  1771. }
  1772. }
  1773. return $maxdepth;
  1774. }
  1775. /**
  1776. * Static recursive helper - add colspan information into categories
  1777. *
  1778. * @param array &$element The seed of the recursion
  1779. *
  1780. * @return int
  1781. */
  1782. public function inject_colspans(&$element) {
  1783. if (empty($element['children'])) {
  1784. return 1;
  1785. }
  1786. $count = 0;
  1787. foreach ($element['children'] as $key=>$child) {
  1788. $count += grade_tree::inject_colspans($element['children'][$key]);
  1789. }
  1790. $element['colspan'] = $count;
  1791. return $count;
  1792. }
  1793. /**
  1794. * Parses the array in search of a given eid and returns a element object with
  1795. * information about the element it has found.
  1796. * @param int $eid Gradetree Element ID
  1797. * @return object element
  1798. */
  1799. public function locate_element($eid) {
  1800. // it is a grade - construct a new object
  1801. if (strpos($eid, 'n') === 0) {
  1802. if (!preg_match('/n(\d+)u(\d+)/', $eid, $matches)) {
  1803. return null;
  1804. }
  1805. $itemid = $matches[1];
  1806. $userid = $matches[2];
  1807. //extra security check - the grade item must be in this tree
  1808. if (!$item_el = $this->locate_element('i'.$itemid)) {
  1809. return null;
  1810. }
  1811. // $gradea->id may be null - means does not exist yet
  1812. $grade = new grade_grade(array('itemid'=>$itemid, 'userid'=>$userid));
  1813. $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
  1814. return array('eid'=>'n'.$itemid.'u'.$userid,'object'=>$grade, 'type'=>'grade');
  1815. } else if (strpos($eid, 'g') === 0) {
  1816. $id = (int) substr($eid, 1);
  1817. if (!$grade = grade_grade::fetch(array('id'=>$id))) {
  1818. return null;
  1819. }
  1820. //extra security check - the grade item must be in this tree
  1821. if (!$item_el = $this->locate_element('i'.$grade->itemid)) {
  1822. return null;
  1823. }
  1824. $grade->grade_item =& $item_el['object']; // this may speedup grade_grade methods!
  1825. return array('eid'=>'g'.$id,'object'=>$grade, 'type'=>'grade');
  1826. }
  1827. // it is a category or item
  1828. foreach ($this->levels as $row) {
  1829. foreach ($row as $element) {
  1830. if ($element['type'] == 'filler') {
  1831. continue;
  1832. }
  1833. if ($element['eid'] == $eid) {
  1834. return $element;
  1835. }
  1836. }
  1837. }
  1838. return null;
  1839. }
  1840. /**
  1841. * Returns a well-formed XML representation of the grade-tree using recursion.
  1842. *
  1843. * @param array $root The current element in the recursion. If null, starts at the top of the tree.
  1844. * @param string $tabs The control character to use for tabs
  1845. *
  1846. * @return string $xml
  1847. */
  1848. public function exporttoxml($root=null, $tabs="\t") {
  1849. $xml = null;
  1850. $first = false;
  1851. if (is_null($root)) {
  1852. $root = $this->top_element;
  1853. $xml = '<?xml version="1.0" encoding="UTF-8" ?>' . "\n";
  1854. $xml .= "<gradetree>\n";
  1855. $first = true;
  1856. }
  1857. $type = 'undefined';
  1858. if (strpos($root['object']->table, 'grade_categories') !== false) {
  1859. $type = 'category';
  1860. } else if (strpos($root['object']->table, 'grade_items') !== false) {
  1861. $type = 'item';
  1862. } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
  1863. $type = 'outcome';
  1864. }
  1865. $xml .= "$tabs<element type=\"$type\">\n";
  1866. foreach ($root['object'] as $var => $value) {
  1867. if (!is_object($value) && !is_array($value) && !empty($value)) {
  1868. $xml .= "$tabs\t<$var>$value</$var>\n";
  1869. }
  1870. }
  1871. if (!empty($root['children'])) {
  1872. $xml .= "$tabs\t<children>\n";
  1873. foreach ($root['children'] as $sortorder => $child) {
  1874. $xml .= $this->exportToXML($child, $tabs."\t\t");
  1875. }
  1876. $xml .= "$tabs\t</children>\n";
  1877. }
  1878. $xml .= "$tabs</element>\n";
  1879. if ($first) {
  1880. $xml .= "</gradetree>";
  1881. }
  1882. return $xml;
  1883. }
  1884. /**
  1885. * Returns a JSON representation of the grade-tree using recursion.
  1886. *
  1887. * @param array $root The current element in the recursion. If null, starts at the top of the tree.
  1888. * @param string $tabs Tab characters used to indent the string nicely for humans to enjoy
  1889. *
  1890. * @return string
  1891. */
  1892. public function exporttojson($root=null, $tabs="\t") {
  1893. $json = null;
  1894. $first = false;
  1895. if (is_null($root)) {
  1896. $root = $this->top_element;
  1897. $first = true;
  1898. }
  1899. $name = '';
  1900. if (strpos($root['object']->table, 'grade_categories') !== false) {
  1901. $name = $root['object']->fullname;
  1902. if ($name == '?') {
  1903. $name = $root['object']->get_name();
  1904. }
  1905. } else if (strpos($root['object']->table, 'grade_items') !== false) {
  1906. $name = $root['object']->itemname;
  1907. } else if (strpos($root['object']->table, 'grade_outcomes') !== false) {
  1908. $name = $root['object']->itemname;
  1909. }
  1910. $json .= "$tabs {\n";
  1911. $json .= "$tabs\t \"type\": \"{$root['type']}\",\n";
  1912. $json .= "$tabs\t \"name\": \"$name\",\n";
  1913. foreach ($root['object'] as $var => $value) {
  1914. if (!is_object($value) && !is_array($value) && !empty($value)) {
  1915. $json .= "$tabs\t \"$var\": \"$value\",\n";
  1916. }
  1917. }
  1918. $json = substr($json, 0, strrpos($json, ','));
  1919. if (!empty($root['children'])) {
  1920. $json .= ",\n$tabs\t\"children\": [\n";
  1921. foreach ($root['children'] as $sortorder => $child) {
  1922. $json .= $this->exportToJSON($child, $tabs."\t\t");
  1923. }
  1924. $json = substr($json, 0, strrpos($json, ','));
  1925. $json .= "\n$tabs\t]\n";
  1926. }
  1927. if ($first) {
  1928. $json .= "\n}";
  1929. } else {
  1930. $json .= "\n$tabs},\n";
  1931. }
  1932. return $json;
  1933. }
  1934. /**
  1935. * Returns the array of levels
  1936. *
  1937. * @return array
  1938. */
  1939. public function get_levels() {
  1940. return $this->levels;
  1941. }
  1942. /**
  1943. * Returns the array of grade items
  1944. *
  1945. * @return array
  1946. */
  1947. public function get_items() {
  1948. return $this->items;
  1949. }
  1950. /**
  1951. * Returns a specific Grade Item
  1952. *
  1953. * @param int $itemid The ID of the grade_item object
  1954. *
  1955. * @return grade_item
  1956. */
  1957. public function get_item($itemid) {
  1958. if (array_key_exists($itemid, $this->items)) {
  1959. return $this->items[$itemid];
  1960. } else {
  1961. return false;
  1962. }
  1963. }
  1964. }
  1965. /**
  1966. * Local shortcut function for creating an edit/delete button for a grade_* object.
  1967. * @param string $type 'edit' or 'delete'
  1968. * @param int $courseid The Course ID
  1969. * @param grade_* $object The grade_* object
  1970. * @return string html
  1971. */
  1972. function grade_button($type, $courseid, $object) {
  1973. global $CFG, $OUTPUT;
  1974. if (preg_match('/grade_(.*)/', get_class($object), $matches)) {
  1975. $objectidstring = $matches[1] . 'id';
  1976. } else {
  1977. throw new coding_exception('grade_button() only accepts grade_* objects as third parameter!');
  1978. }
  1979. $strdelete = get_string('delete');
  1980. $stredit = get_string('edit');
  1981. if ($type == 'delete') {
  1982. $url = new moodle_url('index.php', array('id' => $courseid, $objectidstring => $object->id, 'action' => 'delete', 'sesskey' => sesskey()));
  1983. } else if ($type == 'edit') {
  1984. $url = new moodle_url('edit.php', array('courseid' => $courseid, 'id' => $object->id));
  1985. }
  1986. return $OUTPUT->action_icon($url, new pix_icon('t/'.$type, ${'str'.$type}, '', array('class' => 'iconsmall')));
  1987. }
  1988. /**
  1989. * This method adds settings to the settings block for the grade system and its
  1990. * plugins
  1991. *
  1992. * @global moodle_page $PAGE
  1993. */
  1994. function grade_extend_settings($plugininfo, $courseid) {
  1995. global $PAGE;
  1996. $gradenode = $PAGE->settingsnav->prepend(get_string('gradeadministration', 'grades'), null, navigation_node::TYPE_CONTAINER);
  1997. $strings = array_shift($plugininfo);
  1998. if ($reports = grade_helper::get_plugins_reports($courseid)) {
  1999. foreach ($reports as $report) {
  2000. $gradenode->add($report->string, $report->link, navigation_node::TYPE_SETTING, null, $report->id, new pix_icon('i/report', ''));
  2001. }
  2002. }
  2003. if ($imports = grade_helper::get_plugins_import($courseid)) {
  2004. $importnode = $gradenode->add($strings['import'], null, navigation_node::TYPE_CONTAINER);
  2005. foreach ($imports as $import) {
  2006. $importnode->add($import->string, $import->link, navigation_node::TYPE_SETTING, null, $import->id, new pix_icon('i/import', ''));
  2007. }
  2008. }
  2009. if ($exports = grade_helper::get_plugins_export($courseid)) {
  2010. $exportnode = $gradenode->add($strings['export'], null, navigation_node::TYPE_CONTAINER);
  2011. foreach ($exports as $export) {
  2012. $exportnode->add($export->string, $export->link, navigation_node::TYPE_SETTING, null, $export->id, new pix_icon('i/export', ''));
  2013. }
  2014. }
  2015. if ($setting = grade_helper::get_info_manage_settings($courseid)) {
  2016. $gradenode->add(get_string('coursegradesettings', 'grades'), $setting->link, navigation_node::TYPE_SETTING, null, $setting->id, new pix_icon('i/settings', ''));
  2017. }
  2018. if ($preferences = grade_helper::get_plugins_report_preferences($courseid)) {
  2019. $preferencesnode = $gradenode->add(get_string('myreportpreferences', 'grades'), null, navigation_node::TYPE_CONTAINER);
  2020. foreach ($preferences as $preference) {
  2021. $preferencesnode->add($preference->string, $preference->link, navigation_node::TYPE_SETTING, null, $preference->id, new pix_icon('i/settings', ''));
  2022. }
  2023. }
  2024. if ($letters = grade_helper::get_info_letters($courseid)) {
  2025. $letters = array_shift($letters);
  2026. $gradenode->add($strings['letter'], $letters->link, navigation_node::TYPE_SETTING, null, $letters->id, new pix_icon('i/settings', ''));
  2027. }
  2028. if ($outcomes = grade_helper::get_info_outcomes($courseid)) {
  2029. $outcomes = array_shift($outcomes);
  2030. $gradenode->add($strings['outcome'], $outcomes->link, navigation_node::TYPE_SETTING, null, $outcomes->id, new pix_icon('i/outcomes', ''));
  2031. }
  2032. if ($scales = grade_helper::get_info_scales($courseid)) {
  2033. $gradenode->add($strings['scale'], $scales->link, navigation_node::TYPE_SETTING, null, $scales->id, new pix_icon('i/scales', ''));
  2034. }
  2035. if ($categories = grade_helper::get_info_edit_structure($courseid)) {
  2036. $categoriesnode = $gradenode->add(get_string('categoriesanditems','grades'), null, navigation_node::TYPE_CONTAINER);
  2037. foreach ($categories as $category) {
  2038. $categoriesnode->add($category->string, $category->link, navigation_node::TYPE_SETTING, null, $category->id, new pix_icon('i/report', ''));
  2039. }
  2040. }
  2041. if ($gradenode->contains_active_node()) {
  2042. // If the gradenode is active include the settings base node (gradeadministration) in
  2043. // the navbar, typcially this is ignored.
  2044. $PAGE->navbar->includesettingsbase = true;
  2045. // If we can get the course admin node make sure it is closed by default
  2046. // as in this case the gradenode will be opened
  2047. if ($coursenode = $PAGE->settingsnav->get('courseadmin', navigation_node::TYPE_COURSE)){
  2048. $coursenode->make_inactive();
  2049. $coursenode->forceopen = false;
  2050. }
  2051. }
  2052. }
  2053. /**
  2054. * Grade helper class
  2055. *
  2056. * This class provides several helpful functions that work irrespective of any
  2057. * current state.
  2058. *
  2059. * @copyright 2010 Sam Hemelryk
  2060. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  2061. */
  2062. abstract class grade_helper {
  2063. /**
  2064. * Cached manage settings info {@see get_info_settings}
  2065. * @var grade_plugin_info|false
  2066. */
  2067. protected static $managesetting = null;
  2068. /**
  2069. * Cached grade report plugins {@see get_plugins_reports}
  2070. * @var array|false
  2071. */
  2072. protected static $gradereports = null;
  2073. /**
  2074. * Cached grade report plugins preferences {@see get_info_scales}
  2075. * @var array|false
  2076. */
  2077. protected static $gradereportpreferences = null;
  2078. /**
  2079. * Cached scale info {@see get_info_scales}
  2080. * @var grade_plugin_info|false
  2081. */
  2082. protected static $scaleinfo = null;
  2083. /**
  2084. * Cached outcome info {@see get_info_outcomes}
  2085. * @var grade_plugin_info|false
  2086. */
  2087. protected static $outcomeinfo = null;
  2088. /**
  2089. * Cached info on edit structure {@see get_info_edit_structure}
  2090. * @var array|false
  2091. */
  2092. protected static $edittree = null;
  2093. /**
  2094. * Cached leftter info {@see get_info_letters}
  2095. * @var grade_plugin_info|false
  2096. */
  2097. protected static $letterinfo = null;
  2098. /**
  2099. * Cached grade import plugins {@see get_plugins_import}
  2100. * @var array|false
  2101. */
  2102. protected static $importplugins = null;
  2103. /**
  2104. * Cached grade export plugins {@see get_plugins_export}
  2105. * @var array|false
  2106. */
  2107. protected static $exportplugins = null;
  2108. /**
  2109. * Cached grade plugin strings
  2110. * @var array
  2111. */
  2112. protected static $pluginstrings = null;
  2113. /**
  2114. * Gets strings commonly used by the describe plugins
  2115. *
  2116. * report => get_string('view'),
  2117. * edittree => get_string('edittree', 'grades'),
  2118. * scale => get_string('scales'),
  2119. * outcome => get_string('outcomes', 'grades'),
  2120. * letter => get_string('letters', 'grades'),
  2121. * export => get_string('export', 'grades'),
  2122. * import => get_string('import'),
  2123. * preferences => get_string('mypreferences', 'grades'),
  2124. * settings => get_string('settings')
  2125. *
  2126. * @return array
  2127. */
  2128. public static function get_plugin_strings() {
  2129. if (self::$pluginstrings === null) {
  2130. self::$pluginstrings = array(
  2131. 'report' => get_string('view'),
  2132. 'edittree' => get_string('edittree', 'grades'),
  2133. 'scale' => get_string('scales'),
  2134. 'outcome' => get_string('outcomes', 'grades'),
  2135. 'letter' => get_string('letters', 'grades'),
  2136. 'export' => get_string('export', 'grades'),
  2137. 'import' => get_string('import'),
  2138. 'preferences' => get_string('mypreferences', 'grades'),
  2139. 'settings' => get_string('settings')
  2140. );
  2141. }
  2142. return self::$pluginstrings;
  2143. }
  2144. /**
  2145. * Get grade_plugin_info object for managing settings if the user can
  2146. *
  2147. * @param int $courseid
  2148. * @return grade_plugin_info
  2149. */
  2150. public static function get_info_manage_settings($courseid) {
  2151. if (self::$managesetting !== null) {
  2152. return self::$managesetting;
  2153. }
  2154. $context = context_course::instance($courseid);
  2155. if (has_capability('moodle/grade:manage', $context)) {
  2156. self::$managesetting = new grade_plugin_info('coursesettings', new moodle_url('/grade/edit/settings/index.php', array('id'=>$courseid)), get_string('course'));
  2157. } else {
  2158. self::$managesetting = false;
  2159. }
  2160. return self::$managesetting;
  2161. }
  2162. /**
  2163. * Returns an array of plugin reports as grade_plugin_info objects
  2164. *
  2165. * @param int $courseid
  2166. * @return array
  2167. */
  2168. public static function get_plugins_reports($courseid) {
  2169. global $SITE;
  2170. if (self::$gradereports !== null) {
  2171. return self::$gradereports;
  2172. }
  2173. $context = context_course::instance($courseid);
  2174. $gradereports = array();
  2175. $gradepreferences = array();
  2176. foreach (core_component::get_plugin_list('gradereport') as $plugin => $plugindir) {
  2177. //some reports make no sense if we're not within a course
  2178. if ($courseid==$SITE->id && ($plugin=='grader' || $plugin=='user')) {
  2179. continue;
  2180. }
  2181. // Remove ones we can't see
  2182. if (!has_capability('gradereport/'.$plugin.':view', $context)) {
  2183. continue;
  2184. }
  2185. $pluginstr = get_string('pluginname', 'gradereport_'.$plugin);
  2186. $url = new moodle_url('/grade/report/'.$plugin.'/index.php', array('id'=>$courseid));
  2187. $gradereports[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
  2188. // Add link to preferences tab if such a page exists
  2189. if (file_exists($plugindir.'/preferences.php')) {
  2190. $url = new moodle_url('/grade/report/'.$plugin.'/preferences.php', array('id'=>$courseid));
  2191. $gradepreferences[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
  2192. }
  2193. }
  2194. if (count($gradereports) == 0) {
  2195. $gradereports = false;
  2196. $gradepreferences = false;
  2197. } else if (count($gradepreferences) == 0) {
  2198. $gradepreferences = false;
  2199. asort($gradereports);
  2200. } else {
  2201. asort($gradereports);
  2202. asort($gradepreferences);
  2203. }
  2204. self::$gradereports = $gradereports;
  2205. self::$gradereportpreferences = $gradepreferences;
  2206. return self::$gradereports;
  2207. }
  2208. /**
  2209. * Returns an array of grade plugin report preferences for plugin reports that
  2210. * support preferences
  2211. * @param int $courseid
  2212. * @return array
  2213. */
  2214. public static function get_plugins_report_preferences($courseid) {
  2215. if (self::$gradereportpreferences !== null) {
  2216. return self::$gradereportpreferences;
  2217. }
  2218. self::get_plugins_reports($courseid);
  2219. return self::$gradereportpreferences;
  2220. }
  2221. /**
  2222. * Get information on scales
  2223. * @param int $courseid
  2224. * @return grade_plugin_info
  2225. */
  2226. public static function get_info_scales($courseid) {
  2227. if (self::$scaleinfo !== null) {
  2228. return self::$scaleinfo;
  2229. }
  2230. if (has_capability('moodle/course:managescales', context_course::instance($courseid))) {
  2231. $url = new moodle_url('/grade/edit/scale/index.php', array('id'=>$courseid));
  2232. self::$scaleinfo = new grade_plugin_info('scale', $url, get_string('view'));
  2233. } else {
  2234. self::$scaleinfo = false;
  2235. }
  2236. return self::$scaleinfo;
  2237. }
  2238. /**
  2239. * Get information on outcomes
  2240. * @param int $courseid
  2241. * @return grade_plugin_info
  2242. */
  2243. public static function get_info_outcomes($courseid) {
  2244. global $CFG, $SITE;
  2245. if (self::$outcomeinfo !== null) {
  2246. return self::$outcomeinfo;
  2247. }
  2248. $context = context_course::instance($courseid);
  2249. $canmanage = has_capability('moodle/grade:manage', $context);
  2250. $canupdate = has_capability('moodle/course:update', $context);
  2251. if (!empty($CFG->enableoutcomes) && ($canmanage || $canupdate)) {
  2252. $outcomes = array();
  2253. if ($canupdate) {
  2254. if ($courseid!=$SITE->id) {
  2255. $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
  2256. $outcomes['course'] = new grade_plugin_info('course', $url, get_string('outcomescourse', 'grades'));
  2257. }
  2258. $url = new moodle_url('/grade/edit/outcome/index.php', array('id'=>$courseid));
  2259. $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('editoutcomes', 'grades'));
  2260. $url = new moodle_url('/grade/edit/outcome/import.php', array('courseid'=>$courseid));
  2261. $outcomes['import'] = new grade_plugin_info('import', $url, get_string('importoutcomes', 'grades'));
  2262. } else {
  2263. if ($courseid!=$SITE->id) {
  2264. $url = new moodle_url('/grade/edit/outcome/course.php', array('id'=>$courseid));
  2265. $outcomes['edit'] = new grade_plugin_info('edit', $url, get_string('outcomescourse', 'grades'));
  2266. }
  2267. }
  2268. self::$outcomeinfo = $outcomes;
  2269. } else {
  2270. self::$outcomeinfo = false;
  2271. }
  2272. return self::$outcomeinfo;
  2273. }
  2274. /**
  2275. * Get information on editing structures
  2276. * @param int $courseid
  2277. * @return array
  2278. */
  2279. public static function get_info_edit_structure($courseid) {
  2280. if (self::$edittree !== null) {
  2281. return self::$edittree;
  2282. }
  2283. if (has_capability('moodle/grade:manage', context_course::instance($courseid))) {
  2284. $url = new moodle_url('/grade/edit/tree/index.php', array('sesskey'=>sesskey(), 'showadvanced'=>'0', 'id'=>$courseid));
  2285. self::$edittree = array(
  2286. 'simpleview' => new grade_plugin_info('simpleview', $url, get_string('simpleview', 'grades')),
  2287. 'fullview' => new grade_plugin_info('fullview', new moodle_url($url, array('showadvanced'=>'1')), get_string('fullview', 'grades'))
  2288. );
  2289. } else {
  2290. self::$edittree = false;
  2291. }
  2292. return self::$edittree;
  2293. }
  2294. /**
  2295. * Get information on letters
  2296. * @param int $courseid
  2297. * @return array
  2298. */
  2299. public static function get_info_letters($courseid) {
  2300. global $SITE;
  2301. if (self::$letterinfo !== null) {
  2302. return self::$letterinfo;
  2303. }
  2304. $context = context_course::instance($courseid);
  2305. $canmanage = has_capability('moodle/grade:manage', $context);
  2306. $canmanageletters = has_capability('moodle/grade:manageletters', $context);
  2307. if ($canmanage || $canmanageletters) {
  2308. // Redirect to system context when report is accessed from admin settings MDL-31633
  2309. if ($context->instanceid == $SITE->id) {
  2310. $param = array('edit' => 1);
  2311. } else {
  2312. $param = array('edit' => 1,'id' => $context->id);
  2313. }
  2314. self::$letterinfo = array(
  2315. 'view' => new grade_plugin_info('view', new moodle_url('/grade/edit/letter/index.php', array('id'=>$context->id)), get_string('view')),
  2316. 'edit' => new grade_plugin_info('edit', new moodle_url('/grade/edit/letter/index.php', $param), get_string('edit'))
  2317. );
  2318. } else {
  2319. self::$letterinfo = false;
  2320. }
  2321. return self::$letterinfo;
  2322. }
  2323. /**
  2324. * Get information import plugins
  2325. * @param int $courseid
  2326. * @return array
  2327. */
  2328. public static function get_plugins_import($courseid) {
  2329. global $CFG;
  2330. if (self::$importplugins !== null) {
  2331. return self::$importplugins;
  2332. }
  2333. $importplugins = array();
  2334. $context = context_course::instance($courseid);
  2335. if (has_capability('moodle/grade:import', $context)) {
  2336. foreach (core_component::get_plugin_list('gradeimport') as $plugin => $plugindir) {
  2337. if (!has_capability('gradeimport/'.$plugin.':view', $context)) {
  2338. continue;
  2339. }
  2340. $pluginstr = get_string('pluginname', 'gradeimport_'.$plugin);
  2341. $url = new moodle_url('/grade/import/'.$plugin.'/index.php', array('id'=>$courseid));
  2342. $importplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
  2343. }
  2344. if ($CFG->gradepublishing) {
  2345. $url = new moodle_url('/grade/import/keymanager.php', array('id'=>$courseid));
  2346. $importplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
  2347. }
  2348. }
  2349. if (count($importplugins) > 0) {
  2350. asort($importplugins);
  2351. self::$importplugins = $importplugins;
  2352. } else {
  2353. self::$importplugins = false;
  2354. }
  2355. return self::$importplugins;
  2356. }
  2357. /**
  2358. * Get information export plugins
  2359. * @param int $courseid
  2360. * @return array
  2361. */
  2362. public static function get_plugins_export($courseid) {
  2363. global $CFG;
  2364. if (self::$exportplugins !== null) {
  2365. return self::$exportplugins;
  2366. }
  2367. $context = context_course::instance($courseid);
  2368. $exportplugins = array();
  2369. if (has_capability('moodle/grade:export', $context)) {
  2370. foreach (core_component::get_plugin_list('gradeexport') as $plugin => $plugindir) {
  2371. if (!has_capability('gradeexport/'.$plugin.':view', $context)) {
  2372. continue;
  2373. }
  2374. $pluginstr = get_string('pluginname', 'gradeexport_'.$plugin);
  2375. $url = new moodle_url('/grade/export/'.$plugin.'/index.php', array('id'=>$courseid));
  2376. $exportplugins[$plugin] = new grade_plugin_info($plugin, $url, $pluginstr);
  2377. }
  2378. if ($CFG->gradepublishing) {
  2379. $url = new moodle_url('/grade/export/keymanager.php', array('id'=>$courseid));
  2380. $exportplugins['keymanager'] = new grade_plugin_info('keymanager', $url, get_string('keymanager', 'grades'));
  2381. }
  2382. }
  2383. if (count($exportplugins) > 0) {
  2384. asort($exportplugins);
  2385. self::$exportplugins = $exportplugins;
  2386. } else {
  2387. self::$exportplugins = false;
  2388. }
  2389. return self::$exportplugins;
  2390. }
  2391. /**
  2392. * Returns the value of a field from a user record
  2393. *
  2394. * @param stdClass $user object
  2395. * @param stdClass $field object
  2396. * @return string value of the field
  2397. */
  2398. public static function get_user_field_value($user, $field) {
  2399. if (!empty($field->customid)) {
  2400. $fieldname = 'customfield_' . $field->shortname;
  2401. if (!empty($user->{$fieldname}) || is_numeric($user->{$fieldname})) {
  2402. $fieldvalue = $user->{$fieldname};
  2403. } else {
  2404. $fieldvalue = $field->default;
  2405. }
  2406. } else {
  2407. $fieldvalue = $user->{$field->shortname};
  2408. }
  2409. return $fieldvalue;
  2410. }
  2411. /**
  2412. * Returns an array of user profile fields to be included in export
  2413. *
  2414. * @param int $courseid
  2415. * @param bool $includecustomfields
  2416. * @return array An array of stdClass instances with customid, shortname, datatype, default and fullname fields
  2417. */
  2418. public static function get_user_profile_fields($courseid, $includecustomfields = false) {
  2419. global $CFG, $DB;
  2420. // Gets the fields that have to be hidden
  2421. $hiddenfields = array_map('trim', explode(',', $CFG->hiddenuserfields));
  2422. $context = context_course::instance($courseid);
  2423. $canseehiddenfields = has_capability('moodle/course:viewhiddenuserfields', $context);
  2424. if ($canseehiddenfields) {
  2425. $hiddenfields = array();
  2426. }
  2427. $fields = array();
  2428. require_once($CFG->dirroot.'/user/lib.php'); // Loads user_get_default_fields()
  2429. require_once($CFG->dirroot.'/user/profile/lib.php'); // Loads constants, such as PROFILE_VISIBLE_ALL
  2430. $userdefaultfields = user_get_default_fields();
  2431. // Sets the list of profile fields
  2432. $userprofilefields = array_map('trim', explode(',', $CFG->grade_export_userprofilefields));
  2433. if (!empty($userprofilefields)) {
  2434. foreach ($userprofilefields as $field) {
  2435. $field = trim($field);
  2436. if (in_array($field, $hiddenfields) || !in_array($field, $userdefaultfields)) {
  2437. continue;
  2438. }
  2439. $obj = new stdClass();
  2440. $obj->customid = 0;
  2441. $obj->shortname = $field;
  2442. $obj->fullname = get_string($field);
  2443. $fields[] = $obj;
  2444. }
  2445. }
  2446. // Sets the list of custom profile fields
  2447. $customprofilefields = array_map('trim', explode(',', $CFG->grade_export_customprofilefields));
  2448. if ($includecustomfields && !empty($customprofilefields)) {
  2449. list($wherefields, $whereparams) = $DB->get_in_or_equal($customprofilefields);
  2450. $customfields = $DB->get_records_sql("SELECT f.*
  2451. FROM {user_info_field} f
  2452. JOIN {user_info_category} c ON f.categoryid=c.id
  2453. WHERE f.shortname $wherefields
  2454. ORDER BY c.sortorder ASC, f.sortorder ASC", $whereparams);
  2455. if (!is_array($customfields)) {
  2456. continue;
  2457. }
  2458. foreach ($customfields as $field) {
  2459. // Make sure we can display this custom field
  2460. if (!in_array($field->shortname, $customprofilefields)) {
  2461. continue;
  2462. } else if (in_array($field->shortname, $hiddenfields)) {
  2463. continue;
  2464. } else if ($field->visible != PROFILE_VISIBLE_ALL && !$canseehiddenfields) {
  2465. continue;
  2466. }
  2467. $obj = new stdClass();
  2468. $obj->customid = $field->id;
  2469. $obj->shortname = $field->shortname;
  2470. $obj->fullname = format_string($field->name);
  2471. $obj->datatype = $field->datatype;
  2472. $obj->default = $field->defaultdata;
  2473. $fields[] = $obj;
  2474. }
  2475. }
  2476. return $fields;
  2477. }
  2478. }