PageRenderTime 66ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/mod/assign/locallib.php

https://bitbucket.org/andrewdavidson/sl-clone
PHP | 3377 lines | 2037 code | 481 blank | 859 comment | 430 complexity | 52364399fde9e76d730c2878f3c9b36e MD5 | raw file
Possible License(s): AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, Apache-2.0, GPL-3.0, BSD-3-Clause, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  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. * This file contains the definition for the class assignment
  18. *
  19. * This class provides all the functionality for the new assign module.
  20. *
  21. * @package mod_assign
  22. * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  23. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24. */
  25. defined('MOODLE_INTERNAL') || die();
  26. /**
  27. * Assignment submission statuses
  28. */
  29. define('ASSIGN_SUBMISSION_STATUS_DRAFT', 'draft'); // student thinks it is a draft
  30. define('ASSIGN_SUBMISSION_STATUS_SUBMITTED', 'submitted'); // student thinks it is finished
  31. /**
  32. * Search filters for grading page
  33. */
  34. define('ASSIGN_FILTER_SUBMITTED', 'submitted');
  35. define('ASSIGN_FILTER_SINGLE_USER', 'singleuser');
  36. define('ASSIGN_FILTER_REQUIRE_GRADING', 'require_grading');
  37. /** Include accesslib.php */
  38. require_once($CFG->libdir.'/accesslib.php');
  39. /** Include formslib.php */
  40. require_once($CFG->libdir.'/formslib.php');
  41. /** Include repository/lib.php */
  42. require_once($CFG->dirroot . '/repository/lib.php');
  43. /** Include local mod_form.php */
  44. require_once($CFG->dirroot.'/mod/assign/mod_form.php');
  45. /** gradelib.php */
  46. require_once($CFG->libdir.'/gradelib.php');
  47. /** grading lib.php */
  48. require_once($CFG->dirroot.'/grade/grading/lib.php');
  49. /** Include feedbackplugin.php */
  50. require_once($CFG->dirroot.'/mod/assign/feedbackplugin.php');
  51. /** Include submissionplugin.php */
  52. require_once($CFG->dirroot.'/mod/assign/submissionplugin.php');
  53. /** Include renderable.php */
  54. require_once($CFG->dirroot.'/mod/assign/renderable.php');
  55. /** Include gradingtable.php */
  56. require_once($CFG->dirroot.'/mod/assign/gradingtable.php');
  57. /** Include eventslib.php */
  58. require_once($CFG->libdir.'/eventslib.php');
  59. /**
  60. * Standard base class for mod_assign (assignment types).
  61. *
  62. * @package mod_assign
  63. * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
  64. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  65. */
  66. class assign {
  67. /** @var stdClass the assignment record that contains the global settings for this assign instance */
  68. private $instance;
  69. /** @var context the context of the course module for this assign instance (or just the course if we are
  70. creating a new one) */
  71. private $context;
  72. /** @var stdClass the course this assign instance belongs to */
  73. private $course;
  74. /** @var stdClass the admin config for all assign instances */
  75. private $adminconfig;
  76. /** @var assign_renderer the custom renderer for this module */
  77. private $output;
  78. /** @var stdClass the course module for this assign instance */
  79. private $coursemodule;
  80. /** @var array cache for things like the coursemodule name or the scale menu - only lives for a single
  81. request */
  82. private $cache;
  83. /** @var array list of the installed submission plugins */
  84. private $submissionplugins;
  85. /** @var array list of the installed feedback plugins */
  86. private $feedbackplugins;
  87. /** @var string action to be used to return to this page (without repeating any form submissions etc.) */
  88. private $returnaction = 'view';
  89. /** @var array params to be used to return to this page */
  90. private $returnparams = array();
  91. /** @var string modulename prevents excessive calls to get_string */
  92. private static $modulename = null;
  93. /** @var string modulenameplural prevents excessive calls to get_string */
  94. private static $modulenameplural = null;
  95. /**
  96. * Constructor for the base assign class
  97. *
  98. * @param mixed $coursemodulecontext context|null the course module context (or the course context if the coursemodule has not been created yet)
  99. * @param mixed $coursemodule the current course module if it was already loaded - otherwise this class will load one from the context as required
  100. * @param mixed $course the current course if it was already loaded - otherwise this class will load one from the context as required
  101. */
  102. public function __construct($coursemodulecontext, $coursemodule, $course) {
  103. global $PAGE;
  104. $this->context = $coursemodulecontext;
  105. $this->coursemodule = $coursemodule;
  106. $this->course = $course;
  107. $this->cache = array(); // temporary cache only lives for a single request - used to reduce db lookups
  108. $this->submissionplugins = $this->load_plugins('assignsubmission');
  109. $this->feedbackplugins = $this->load_plugins('assignfeedback');
  110. $this->output = $PAGE->get_renderer('mod_assign');
  111. }
  112. /**
  113. * Set the action and parameters that can be used to return to the current page
  114. *
  115. * @param string $action The action for the current page
  116. * @param array $params An array of name value pairs which form the parameters to return to the current page
  117. * @return void
  118. */
  119. public function register_return_link($action, $params) {
  120. $this->returnaction = $action;
  121. $this->returnparams = $params;
  122. }
  123. /**
  124. * Return an action that can be used to get back to the current page
  125. * @return string action
  126. */
  127. public function get_return_action() {
  128. return $this->returnaction;
  129. }
  130. /**
  131. * Based on the current assignment settings should we display the intro
  132. * @return bool showintro
  133. */
  134. private function show_intro() {
  135. if ($this->get_instance()->alwaysshowdescription ||
  136. time() > $this->get_instance()->allowsubmissionsfromdate) {
  137. return true;
  138. }
  139. return false;
  140. }
  141. /**
  142. * Return a list of parameters that can be used to get back to the current page
  143. * @return array params
  144. */
  145. public function get_return_params() {
  146. return $this->returnparams;
  147. }
  148. /**
  149. * Set the submitted form data
  150. * @param stdClass $data The form data (instance)
  151. */
  152. public function set_instance(stdClass $data) {
  153. $this->instance = $data;
  154. }
  155. /**
  156. * Set the context
  157. * @param context $context The new context
  158. */
  159. public function set_context(context $context) {
  160. $this->context = $context;
  161. }
  162. /**
  163. * Set the course data
  164. * @param stdClass $course The course data
  165. */
  166. public function set_course(stdClass $course) {
  167. $this->course = $course;
  168. }
  169. /**
  170. * get list of feedback plugins installed
  171. * @return array
  172. */
  173. public function get_feedback_plugins() {
  174. return $this->feedbackplugins;
  175. }
  176. /**
  177. * get list of submission plugins installed
  178. * @return array
  179. */
  180. public function get_submission_plugins() {
  181. return $this->submissionplugins;
  182. }
  183. /**
  184. * get a specific submission plugin by its type
  185. * @param string $subtype assignsubmission | assignfeedback
  186. * @param string $type
  187. * @return mixed assign_plugin|null
  188. */
  189. private function get_plugin_by_type($subtype, $type) {
  190. $shortsubtype = substr($subtype, strlen('assign'));
  191. $name = $shortsubtype . 'plugins';
  192. $pluginlist = $this->$name;
  193. foreach ($pluginlist as $plugin) {
  194. if ($plugin->get_type() == $type) {
  195. return $plugin;
  196. }
  197. }
  198. return null;
  199. }
  200. /**
  201. * Get a feedback plugin by type
  202. * @param string $type - The type of plugin e.g comments
  203. * @return mixed assign_feedback_plugin|null
  204. */
  205. public function get_feedback_plugin_by_type($type) {
  206. return $this->get_plugin_by_type('assignfeedback', $type);
  207. }
  208. /**
  209. * Get a submission plugin by type
  210. * @param string $type - The type of plugin e.g comments
  211. * @return mixed assign_submission_plugin|null
  212. */
  213. public function get_submission_plugin_by_type($type) {
  214. return $this->get_plugin_by_type('assignsubmission', $type);
  215. }
  216. /**
  217. * Load the plugins from the sub folders under subtype
  218. * @param string $subtype - either submission or feedback
  219. * @return array - The sorted list of plugins
  220. */
  221. private function load_plugins($subtype) {
  222. global $CFG;
  223. $result = array();
  224. $names = get_plugin_list($subtype);
  225. foreach ($names as $name => $path) {
  226. if (file_exists($path . '/locallib.php')) {
  227. require_once($path . '/locallib.php');
  228. $shortsubtype = substr($subtype, strlen('assign'));
  229. $pluginclass = 'assign_' . $shortsubtype . '_' . $name;
  230. $plugin = new $pluginclass($this, $name);
  231. if ($plugin instanceof assign_plugin) {
  232. $idx = $plugin->get_sort_order();
  233. while (array_key_exists($idx, $result)) $idx +=1;
  234. $result[$idx] = $plugin;
  235. }
  236. }
  237. }
  238. ksort($result);
  239. return $result;
  240. }
  241. /**
  242. * Display the assignment, used by view.php
  243. *
  244. * The assignment is displayed differently depending on your role,
  245. * the settings for the assignment and the status of the assignment.
  246. * @param string $action The current action if any.
  247. * @return void
  248. */
  249. public function view($action='') {
  250. $o = '';
  251. $mform = null;
  252. // handle form submissions first
  253. if ($action == 'savesubmission') {
  254. $action = 'editsubmission';
  255. if ($this->process_save_submission($mform)) {
  256. $action = 'view';
  257. }
  258. } else if ($action == 'lock') {
  259. $this->process_lock();
  260. $action = 'grading';
  261. } else if ($action == 'reverttodraft') {
  262. $this->process_revert_to_draft();
  263. $action = 'grading';
  264. } else if ($action == 'unlock') {
  265. $this->process_unlock();
  266. $action = 'grading';
  267. } else if ($action == 'confirmsubmit') {
  268. $this->process_submit_for_grading();
  269. // save and show next button
  270. } else if ($action == 'batchgradingoperation') {
  271. $this->process_batch_grading_operation();
  272. $action = 'grading';
  273. } else if ($action == 'submitgrade') {
  274. if (optional_param('saveandshownext', null, PARAM_ALPHA)) {
  275. //save and show next
  276. $action = 'grade';
  277. if ($this->process_save_grade($mform)) {
  278. $action = 'nextgrade';
  279. }
  280. } else if (optional_param('nosaveandprevious', null, PARAM_ALPHA)) {
  281. $action = 'previousgrade';
  282. } else if (optional_param('nosaveandnext', null, PARAM_ALPHA)) {
  283. //show next button
  284. $action = 'nextgrade';
  285. } else if (optional_param('savegrade', null, PARAM_ALPHA)) {
  286. //save changes button
  287. $action = 'grade';
  288. if ($this->process_save_grade($mform)) {
  289. $action = 'grading';
  290. }
  291. } else {
  292. //cancel button
  293. $action = 'grading';
  294. }
  295. }else if ($action == 'quickgrade') {
  296. $message = $this->process_save_quick_grades();
  297. $action = 'quickgradingresult';
  298. }else if ($action == 'saveoptions') {
  299. $this->process_save_grading_options();
  300. $action = 'grading';
  301. }
  302. $returnparams = array('rownum'=>optional_param('rownum', 0, PARAM_INT));
  303. $this->register_return_link($action, $returnparams);
  304. // now show the right view page
  305. if ($action == 'previousgrade') {
  306. $mform = null;
  307. $o .= $this->view_single_grade_page($mform, -1);
  308. } else if ($action == 'quickgradingresult') {
  309. $mform = null;
  310. $o .= $this->view_quickgrading_result($message);
  311. } else if ($action == 'nextgrade') {
  312. $mform = null;
  313. $o .= $this->view_single_grade_page($mform, 1);
  314. } else if ($action == 'grade') {
  315. $o .= $this->view_single_grade_page($mform);
  316. } else if ($action == 'viewpluginassignfeedback') {
  317. $o .= $this->view_plugin_content('assignfeedback');
  318. } else if ($action == 'viewpluginassignsubmission') {
  319. $o .= $this->view_plugin_content('assignsubmission');
  320. } else if ($action == 'editsubmission') {
  321. $o .= $this->view_edit_submission_page($mform);
  322. } else if ($action == 'grading') {
  323. $o .= $this->view_grading_page();
  324. } else if ($action == 'downloadall') {
  325. $o .= $this->download_submissions();
  326. } else if ($action == 'submit') {
  327. $o .= $this->check_submit_for_grading();
  328. } else {
  329. $o .= $this->view_submission_page();
  330. }
  331. return $o;
  332. }
  333. /**
  334. * Add this instance to the database
  335. *
  336. * @param stdClass $formdata The data submitted from the form
  337. * @param bool $callplugins This is used to skip the plugin code
  338. * when upgrading an old assignment to a new one (the plugins get called manually)
  339. * @return mixed false if an error occurs or the int id of the new instance
  340. */
  341. public function add_instance(stdClass $formdata, $callplugins) {
  342. global $DB;
  343. $err = '';
  344. // add the database record
  345. $update = new stdClass();
  346. $update->name = $formdata->name;
  347. $update->timemodified = time();
  348. $update->timecreated = time();
  349. $update->course = $formdata->course;
  350. $update->courseid = $formdata->course;
  351. $update->intro = $formdata->intro;
  352. $update->introformat = $formdata->introformat;
  353. $update->alwaysshowdescription = $formdata->alwaysshowdescription;
  354. $update->preventlatesubmissions = $formdata->preventlatesubmissions;
  355. $update->submissiondrafts = $formdata->submissiondrafts;
  356. $update->sendnotifications = $formdata->sendnotifications;
  357. $update->sendlatenotifications = $formdata->sendlatenotifications;
  358. $update->duedate = $formdata->duedate;
  359. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  360. $update->grade = $formdata->grade;
  361. $returnid = $DB->insert_record('assign', $update);
  362. $this->instance = $DB->get_record('assign', array('id'=>$returnid), '*', MUST_EXIST);
  363. // cache the course record
  364. $this->course = $DB->get_record('course', array('id'=>$formdata->course), '*', MUST_EXIST);
  365. if ($callplugins) {
  366. // call save_settings hook for submission plugins
  367. foreach ($this->submissionplugins as $plugin) {
  368. if (!$this->update_plugin_instance($plugin, $formdata)) {
  369. print_error($plugin->get_error());
  370. return false;
  371. }
  372. }
  373. foreach ($this->feedbackplugins as $plugin) {
  374. if (!$this->update_plugin_instance($plugin, $formdata)) {
  375. print_error($plugin->get_error());
  376. return false;
  377. }
  378. }
  379. // in the case of upgrades the coursemodule has not been set so we need to wait before calling these two
  380. // TODO: add event to the calendar
  381. $this->update_calendar($formdata->coursemodule);
  382. // TODO: add the item in the gradebook
  383. $this->update_gradebook(false, $formdata->coursemodule);
  384. }
  385. $update = new stdClass();
  386. $update->id = $this->get_instance()->id;
  387. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  388. $DB->update_record('assign', $update);
  389. return $returnid;
  390. }
  391. /**
  392. * Delete all grades from the gradebook for this assignment
  393. *
  394. * @return bool
  395. */
  396. private function delete_grades() {
  397. global $CFG;
  398. return grade_update('mod/assign', $this->get_course()->id, 'mod', 'assign', $this->get_instance()->id, 0, NULL, array('deleted'=>1)) == GRADE_UPDATE_OK;
  399. }
  400. /**
  401. * Delete this instance from the database
  402. *
  403. * @return bool false if an error occurs
  404. */
  405. public function delete_instance() {
  406. global $DB;
  407. $result = true;
  408. foreach ($this->submissionplugins as $plugin) {
  409. if (!$plugin->delete_instance()) {
  410. print_error($plugin->get_error());
  411. $result = false;
  412. }
  413. }
  414. foreach ($this->feedbackplugins as $plugin) {
  415. if (!$plugin->delete_instance()) {
  416. print_error($plugin->get_error());
  417. $result = false;
  418. }
  419. }
  420. // delete files associated with this assignment
  421. $fs = get_file_storage();
  422. if (! $fs->delete_area_files($this->context->id) ) {
  423. $result = false;
  424. }
  425. // delete_records will throw an exception if it fails - so no need for error checking here
  426. $DB->delete_records('assign_submission', array('assignment'=>$this->get_instance()->id));
  427. $DB->delete_records('assign_grades', array('assignment'=>$this->get_instance()->id));
  428. $DB->delete_records('assign_plugin_config', array('assignment'=>$this->get_instance()->id));
  429. // delete items from the gradebook
  430. if (! $this->delete_grades()) {
  431. $result = false;
  432. }
  433. // delete the instance
  434. $DB->delete_records('assign', array('id'=>$this->get_instance()->id));
  435. return $result;
  436. }
  437. /**
  438. * Update the settings for a single plugin
  439. *
  440. * @param assign_plugin $plugin The plugin to update
  441. * @param stdClass $formdata The form data
  442. * @return bool false if an error occurs
  443. */
  444. private function update_plugin_instance(assign_plugin $plugin, stdClass $formdata) {
  445. if ($plugin->is_visible()) {
  446. $enabledname = $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled';
  447. if ($formdata->$enabledname) {
  448. $plugin->enable();
  449. if (!$plugin->save_settings($formdata)) {
  450. print_error($plugin->get_error());
  451. return false;
  452. }
  453. } else {
  454. $plugin->disable();
  455. }
  456. }
  457. return true;
  458. }
  459. /**
  460. * Update the gradebook information for this assignment
  461. *
  462. * @param bool $reset If true, will reset all grades in the gradbook for this assignment
  463. * @param int $coursemoduleid This is required because it might not exist in the database yet
  464. * @return bool
  465. */
  466. public function update_gradebook($reset, $coursemoduleid) {
  467. global $CFG;
  468. /** Include lib.php */
  469. require_once($CFG->dirroot.'/mod/assign/lib.php');
  470. $assign = clone $this->get_instance();
  471. $assign->cmidnumber = $coursemoduleid;
  472. $param = null;
  473. if ($reset) {
  474. $param = 'reset';
  475. }
  476. return assign_grade_item_update($assign, $param);
  477. }
  478. /** Load and cache the admin config for this module
  479. *
  480. * @return stdClass the plugin config
  481. */
  482. public function get_admin_config() {
  483. if ($this->adminconfig) {
  484. return $this->adminconfig;
  485. }
  486. $this->adminconfig = get_config('assign');
  487. return $this->adminconfig;
  488. }
  489. /**
  490. * Update the calendar entries for this assignment
  491. *
  492. * @param int $coursemoduleid - Required to pass this in because it might not exist in the database yet
  493. * @return bool
  494. */
  495. public function update_calendar($coursemoduleid) {
  496. global $DB, $CFG;
  497. require_once($CFG->dirroot.'/calendar/lib.php');
  498. // special case for add_instance as the coursemodule has not been set yet.
  499. if ($this->get_instance()->duedate) {
  500. $event = new stdClass();
  501. if ($event->id = $DB->get_field('event', 'id', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id))) {
  502. $event->name = $this->get_instance()->name;
  503. $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
  504. $event->timestart = $this->get_instance()->duedate;
  505. $calendarevent = calendar_event::load($event->id);
  506. $calendarevent->update($event);
  507. } else {
  508. $event = new stdClass();
  509. $event->name = $this->get_instance()->name;
  510. $event->description = format_module_intro('assign', $this->get_instance(), $coursemoduleid);
  511. $event->courseid = $this->get_instance()->course;
  512. $event->groupid = 0;
  513. $event->userid = 0;
  514. $event->modulename = 'assign';
  515. $event->instance = $this->get_instance()->id;
  516. $event->eventtype = 'due';
  517. $event->timestart = $this->get_instance()->duedate;
  518. $event->timeduration = 0;
  519. calendar_event::create($event);
  520. }
  521. } else {
  522. $DB->delete_records('event', array('modulename'=>'assign', 'instance'=>$this->get_instance()->id));
  523. }
  524. }
  525. /**
  526. * Update this instance in the database
  527. *
  528. * @param stdClass $formdata - the data submitted from the form
  529. * @return bool false if an error occurs
  530. */
  531. public function update_instance($formdata) {
  532. global $DB;
  533. $update = new stdClass();
  534. $update->id = $formdata->instance;
  535. $update->name = $formdata->name;
  536. $update->timemodified = time();
  537. $update->course = $formdata->course;
  538. $update->intro = $formdata->intro;
  539. $update->introformat = $formdata->introformat;
  540. $update->alwaysshowdescription = $formdata->alwaysshowdescription;
  541. $update->preventlatesubmissions = $formdata->preventlatesubmissions;
  542. $update->submissiondrafts = $formdata->submissiondrafts;
  543. $update->sendnotifications = $formdata->sendnotifications;
  544. $update->sendlatenotifications = $formdata->sendlatenotifications;
  545. $update->duedate = $formdata->duedate;
  546. $update->allowsubmissionsfromdate = $formdata->allowsubmissionsfromdate;
  547. $update->grade = $formdata->grade;
  548. $result = $DB->update_record('assign', $update);
  549. $this->instance = $DB->get_record('assign', array('id'=>$update->id), '*', MUST_EXIST);
  550. // load the assignment so the plugins have access to it
  551. // call save_settings hook for submission plugins
  552. foreach ($this->submissionplugins as $plugin) {
  553. if (!$this->update_plugin_instance($plugin, $formdata)) {
  554. print_error($plugin->get_error());
  555. return false;
  556. }
  557. }
  558. foreach ($this->feedbackplugins as $plugin) {
  559. if (!$this->update_plugin_instance($plugin, $formdata)) {
  560. print_error($plugin->get_error());
  561. return false;
  562. }
  563. }
  564. // update the database record
  565. // update all the calendar events
  566. $this->update_calendar($this->get_course_module()->id);
  567. $this->update_gradebook(false, $this->get_course_module()->id);
  568. $update = new stdClass();
  569. $update->id = $this->get_instance()->id;
  570. $update->nosubmissions = (!$this->is_any_submission_plugin_enabled()) ? 1: 0;
  571. $DB->update_record('assign', $update);
  572. return $result;
  573. }
  574. /**
  575. * add elements in grading plugin form
  576. *
  577. * @param mixed $grade stdClass|null
  578. * @param MoodleQuickForm $mform
  579. * @param stdClass $data
  580. * @return void
  581. */
  582. private function add_plugin_grade_elements($grade, MoodleQuickForm $mform, stdClass $data) {
  583. foreach ($this->feedbackplugins as $plugin) {
  584. if ($plugin->is_enabled() && $plugin->is_visible()) {
  585. $mform->addElement('header', 'header_' . $plugin->get_type(), $plugin->get_name());
  586. if (!$plugin->get_form_elements($grade, $mform, $data)) {
  587. $mform->removeElement('header_' . $plugin->get_type());
  588. }
  589. }
  590. }
  591. }
  592. /**
  593. * Add one plugins settings to edit plugin form
  594. *
  595. * @param assign_plugin $plugin The plugin to add the settings from
  596. * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
  597. * @return void
  598. */
  599. private function add_plugin_settings(assign_plugin $plugin, MoodleQuickForm $mform) {
  600. global $CFG;
  601. if ($plugin->is_visible()) {
  602. // enabled
  603. //tied disableIf rule to this select element
  604. $mform->addElement('selectyesno', $plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $plugin->get_name());
  605. $mform->addHelpButton($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', 'enabled', $plugin->get_subtype() . '_' . $plugin->get_type());
  606. $default = get_config($plugin->get_subtype() . '_' . $plugin->get_type(), 'default');
  607. if ($plugin->get_config('enabled') !== false) {
  608. $default = $plugin->is_enabled();
  609. }
  610. $mform->setDefault($plugin->get_subtype() . '_' . $plugin->get_type() . '_enabled', $default);
  611. $plugin->get_settings($mform);
  612. }
  613. }
  614. /**
  615. * Add settings to edit plugin form
  616. *
  617. * @param MoodleQuickForm $mform The form to add the configuration settings to. This form is modified directly (not returned)
  618. * @return void
  619. */
  620. public function add_all_plugin_settings(MoodleQuickForm $mform) {
  621. $mform->addElement('header', 'general', get_string('submissionsettings', 'assign'));
  622. foreach ($this->submissionplugins as $plugin) {
  623. $this->add_plugin_settings($plugin, $mform);
  624. }
  625. $mform->addElement('header', 'general', get_string('feedbacksettings', 'assign'));
  626. foreach ($this->feedbackplugins as $plugin) {
  627. $this->add_plugin_settings($plugin, $mform);
  628. }
  629. }
  630. /**
  631. * Allow each plugin an opportunity to update the defaultvalues
  632. * passed in to the settings form (needed to set up draft areas for
  633. * editor and filemanager elements)
  634. * @param array $defaultvalues
  635. */
  636. public function plugin_data_preprocessing(&$defaultvalues) {
  637. foreach ($this->submissionplugins as $plugin) {
  638. if ($plugin->is_visible()) {
  639. $plugin->data_preprocessing($defaultvalues);
  640. }
  641. }
  642. foreach ($this->feedbackplugins as $plugin) {
  643. if ($plugin->is_visible()) {
  644. $plugin->data_preprocessing($defaultvalues);
  645. }
  646. }
  647. }
  648. /**
  649. * Get the name of the current module.
  650. *
  651. * @return string the module name (Assignment)
  652. */
  653. protected function get_module_name() {
  654. if (isset(self::$modulename)) {
  655. return self::$modulename;
  656. }
  657. self::$modulename = get_string('modulename', 'assign');
  658. return self::$modulename;
  659. }
  660. /**
  661. * Get the plural name of the current module.
  662. *
  663. * @return string the module name plural (Assignments)
  664. */
  665. protected function get_module_name_plural() {
  666. if (isset(self::$modulenameplural)) {
  667. return self::$modulenameplural;
  668. }
  669. self::$modulenameplural = get_string('modulenameplural', 'assign');
  670. return self::$modulenameplural;
  671. }
  672. /**
  673. * Has this assignment been constructed from an instance?
  674. *
  675. * @return bool
  676. */
  677. public function has_instance() {
  678. return $this->instance || $this->get_course_module();
  679. }
  680. /**
  681. * Get the settings for the current instance of this assignment
  682. *
  683. * @return stdClass The settings
  684. */
  685. public function get_instance() {
  686. global $DB;
  687. if ($this->instance) {
  688. return $this->instance;
  689. }
  690. if ($this->get_course_module()) {
  691. $this->instance = $DB->get_record('assign', array('id' => $this->get_course_module()->instance), '*', MUST_EXIST);
  692. }
  693. if (!$this->instance) {
  694. throw new coding_exception('Improper use of the assignment class. Cannot load the assignment record.');
  695. }
  696. return $this->instance;
  697. }
  698. /**
  699. * Get the context of the current course
  700. * @return mixed context|null The course context
  701. */
  702. public function get_course_context() {
  703. if (!$this->context && !$this->course) {
  704. throw new coding_exception('Improper use of the assignment class. Cannot load the course context.');
  705. }
  706. if ($this->context) {
  707. return $this->context->get_course_context();
  708. } else {
  709. return context_course::instance($this->course->id);
  710. }
  711. }
  712. /**
  713. * Get the current course module
  714. *
  715. * @return mixed stdClass|null The course module
  716. */
  717. public function get_course_module() {
  718. if ($this->coursemodule) {
  719. return $this->coursemodule;
  720. }
  721. if (!$this->context) {
  722. return null;
  723. }
  724. if ($this->context->contextlevel == CONTEXT_MODULE) {
  725. $this->coursemodule = get_coursemodule_from_id('assign', $this->context->instanceid, 0, false, MUST_EXIST);
  726. return $this->coursemodule;
  727. }
  728. return null;
  729. }
  730. /**
  731. * Get context module
  732. *
  733. * @return context
  734. */
  735. public function get_context() {
  736. return $this->context;
  737. }
  738. /**
  739. * Get the current course
  740. * @return mixed stdClass|null The course
  741. */
  742. public function get_course() {
  743. global $DB;
  744. if ($this->course) {
  745. return $this->course;
  746. }
  747. if (!$this->context) {
  748. return null;
  749. }
  750. $this->course = $DB->get_record('course', array('id' => $this->get_course_context()->instanceid), '*', MUST_EXIST);
  751. return $this->course;
  752. }
  753. /**
  754. * Return a grade in user-friendly form, whether it's a scale or not
  755. *
  756. * @param mixed $grade int|null
  757. * @param boolean $editing Are we allowing changes to this grade?
  758. * @param int $userid The user id the grade belongs to
  759. * @param int $modified Timestamp from when the grade was last modified
  760. * @return string User-friendly representation of grade
  761. */
  762. public function display_grade($grade, $editing, $userid=0, $modified=0) {
  763. global $DB;
  764. static $scalegrades = array();
  765. if ($this->get_instance()->grade >= 0) {
  766. // Normal number
  767. if ($editing && $this->get_instance()->grade > 0) {
  768. if ($grade < 0) {
  769. $displaygrade = '';
  770. } else {
  771. $displaygrade = format_float($grade);
  772. }
  773. $o = '<input type="text" name="quickgrade_' . $userid . '" value="' . $displaygrade . '" size="6" maxlength="10" class="quickgrade"/>';
  774. $o .= '&nbsp;/&nbsp;' . format_float($this->get_instance()->grade,2);
  775. $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
  776. return $o;
  777. } else {
  778. if ($grade == -1 || $grade === null) {
  779. return '-';
  780. } else {
  781. return format_float(($grade),2) .'&nbsp;/&nbsp;'. format_float($this->get_instance()->grade,2);
  782. }
  783. }
  784. } else {
  785. // Scale
  786. if (empty($this->cache['scale'])) {
  787. if ($scale = $DB->get_record('scale', array('id'=>-($this->get_instance()->grade)))) {
  788. $this->cache['scale'] = make_menu_from_list($scale->scale);
  789. } else {
  790. return '-';
  791. }
  792. }
  793. if ($editing) {
  794. $o = '<select name="quickgrade_' . $userid . '" class="quickgrade">';
  795. $o .= '<option value="-1">' . get_string('nograde') . '</option>';
  796. foreach ($this->cache['scale'] as $optionid => $option) {
  797. $selected = '';
  798. if ($grade == $optionid) {
  799. $selected = 'selected="selected"';
  800. }
  801. $o .= '<option value="' . $optionid . '" ' . $selected . '>' . $option . '</option>';
  802. }
  803. $o .= '</select>';
  804. $o .= '<input type="hidden" name="grademodified_' . $userid . '" value="' . $modified . '"/>';
  805. return $o;
  806. } else {
  807. $scaleid = (int)$grade;
  808. if (isset($this->cache['scale'][$scaleid])) {
  809. return $this->cache['scale'][$scaleid];
  810. }
  811. return '-';
  812. }
  813. }
  814. }
  815. /**
  816. * Load a list of users enrolled in the current course with the specified permission and group (0 for no group)
  817. *
  818. * @param int $currentgroup
  819. * @param bool $idsonly
  820. * @return array List of user records
  821. */
  822. public function list_participants($currentgroup, $idsonly) {
  823. if ($idsonly) {
  824. return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup, 'u.id');
  825. } else {
  826. return get_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
  827. }
  828. }
  829. /**
  830. * Load a count of users enrolled in the current course with the specified permission and group (0 for no group)
  831. *
  832. * @param int $currentgroup
  833. * @return int number of matching users
  834. */
  835. public function count_participants($currentgroup) {
  836. return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
  837. }
  838. /**
  839. * Load a count of users enrolled in the current course with the specified permission and group (optional)
  840. *
  841. * @param string $status The submission status - should match one of the constants
  842. * @return int number of matching submissions
  843. */
  844. public function count_submissions_with_status($status) {
  845. global $DB;
  846. return $DB->count_records_sql("SELECT COUNT('x')
  847. FROM {assign_submission}
  848. WHERE assignment = ? AND
  849. status = ?", array($this->get_course_module()->instance, $status));
  850. }
  851. /**
  852. * Utility function to get the userid for every row in the grading table
  853. * so the order can be frozen while we iterate it
  854. *
  855. * @return array An array of userids
  856. */
  857. private function get_grading_userid_list(){
  858. $filter = get_user_preferences('assign_filter', '');
  859. $table = new assign_grading_table($this, 0, $filter, 0, false);
  860. $useridlist = $table->get_column_data('userid');
  861. return $useridlist;
  862. }
  863. /**
  864. * Utility function get the userid based on the row number of the grading table.
  865. * This takes into account any active filters on the table.
  866. *
  867. * @param int $num The row number of the user
  868. * @param bool $last This is set to true if this is the last user in the table
  869. * @return mixed The user id of the matching user or false if there was an error
  870. */
  871. private function get_userid_for_row($num, $last){
  872. if (!array_key_exists('userid_for_row', $this->cache)) {
  873. $this->cache['userid_for_row'] = array();
  874. }
  875. if (array_key_exists($num, $this->cache['userid_for_row'])) {
  876. list($userid, $last) = $this->cache['userid_for_row'][$num];
  877. return $userid;
  878. }
  879. $filter = get_user_preferences('assign_filter', '');
  880. $table = new assign_grading_table($this, 0, $filter, 0, false);
  881. $userid = $table->get_cell_data($num, 'userid', $last);
  882. $this->cache['userid_for_row'][$num] = array($userid, $last);
  883. return $userid;
  884. }
  885. /**
  886. * Return all assignment submissions by ENROLLED students (even empty)
  887. *
  888. * @param string $sort optional field names for the ORDER BY in the sql query
  889. * @param string $dir optional specifying the sort direction, defaults to DESC
  890. * @return array The submission objects indexed by id
  891. */
  892. private function get_all_submissions( $sort="", $dir="DESC") {
  893. global $CFG, $DB;
  894. if ($sort == "lastname" or $sort == "firstname") {
  895. $sort = "u.$sort $dir";
  896. } else if (empty($sort)) {
  897. $sort = "a.timemodified DESC";
  898. } else {
  899. $sort = "a.$sort $dir";
  900. }
  901. return $DB->get_records_sql("SELECT a.*
  902. FROM {assign_submission} a, {user} u
  903. WHERE u.id = a.userid
  904. AND a.assignment = ?
  905. ORDER BY $sort", array($this->get_instance()->id));
  906. }
  907. /**
  908. * Generate zip file from array of given files
  909. *
  910. * @param array $filesforzipping - array of files to pass into archive_to_pathname - this array is indexed by the final file name and each element in the array is an instance of a stored_file object
  911. * @return path of temp file - note this returned file does not have a .zip extension - it is a temp file.
  912. */
  913. private function pack_files($filesforzipping) {
  914. global $CFG;
  915. //create path for new zip file.
  916. $tempzip = tempnam($CFG->tempdir.'/', 'assignment_');
  917. //zip files
  918. $zipper = new zip_packer();
  919. if ($zipper->archive_to_pathname($filesforzipping, $tempzip)) {
  920. return $tempzip;
  921. }
  922. return false;
  923. }
  924. /**
  925. * Finds all assignment notifications that have yet to be mailed out, and mails them.
  926. *
  927. * Cron function to be run periodically according to the moodle cron
  928. *
  929. * @return bool
  930. */
  931. static function cron() {
  932. global $DB;
  933. // only ever send a max of one days worth of updates
  934. $yesterday = time() - (24 * 3600);
  935. $timenow = time();
  936. // Collect all submissions from the past 24 hours that require mailing.
  937. $sql = "SELECT s.*, a.course, a.name, g.*, g.id as gradeid, g.timemodified as lastmodified
  938. FROM {assign} a
  939. JOIN {assign_grades} g ON g.assignment = a.id
  940. LEFT JOIN {assign_submission} s ON s.assignment = a.id AND s.userid = g.userid
  941. WHERE g.timemodified >= :yesterday AND
  942. g.timemodified <= :today AND
  943. g.mailed = 0";
  944. $params = array('yesterday' => $yesterday, 'today' => $timenow);
  945. $submissions = $DB->get_records_sql($sql, $params);
  946. if (empty($submissions)) {
  947. mtrace('done.');
  948. return true;
  949. }
  950. mtrace('Processing ' . count($submissions) . ' assignment submissions ...');
  951. // Preload courses we are going to need those.
  952. $courseids = array();
  953. foreach ($submissions as $submission) {
  954. $courseids[] = $submission->course;
  955. }
  956. // Filter out duplicates
  957. $courseids = array_unique($courseids);
  958. $ctxselect = context_helper::get_preload_record_columns_sql('ctx');
  959. list($courseidsql, $params) = $DB->get_in_or_equal($courseids, SQL_PARAMS_NAMED);
  960. $sql = "SELECT c.*, {$ctxselect}
  961. FROM {course} c
  962. LEFT JOIN {context} ctx ON ctx.instanceid = c.id AND ctx.contextlevel = :contextlevel
  963. WHERE c.id {$courseidsql}";
  964. $params['contextlevel'] = CONTEXT_COURSE;
  965. $courses = $DB->get_records_sql($sql, $params);
  966. // Clean up... this could go on for a while.
  967. unset($courseids);
  968. unset($ctxselect);
  969. unset($courseidsql);
  970. unset($params);
  971. // Simple array we'll use for caching modules.
  972. $modcache = array();
  973. // Message students about new feedback
  974. foreach ($submissions as $submission) {
  975. mtrace("Processing assignment submission $submission->id ...");
  976. // do not cache user lookups - could be too many
  977. if (!$user = $DB->get_record("user", array("id"=>$submission->userid))) {
  978. mtrace("Could not find user $submission->userid");
  979. continue;
  980. }
  981. // use a cache to prevent the same DB queries happening over and over
  982. if (!array_key_exists($submission->course, $courses)) {
  983. mtrace("Could not find course $submission->course");
  984. continue;
  985. }
  986. $course = $courses[$submission->course];
  987. if (isset($course->ctxid)) {
  988. // Context has not yet been preloaded. Do so now.
  989. context_helper::preload_from_record($course);
  990. }
  991. // Override the language and timezone of the "current" user, so that
  992. // mail is customised for the receiver.
  993. cron_setup_user($user, $course);
  994. // context lookups are already cached
  995. $coursecontext = context_course::instance($course->id);
  996. if (!is_enrolled($coursecontext, $user->id)) {
  997. $courseshortname = format_string($course->shortname, true, array('context' => $coursecontext));
  998. mtrace(fullname($user)." not an active participant in " . $courseshortname);
  999. continue;
  1000. }
  1001. if (!$grader = $DB->get_record("user", array("id"=>$submission->grader))) {
  1002. mtrace("Could not find grader $submission->grader");
  1003. continue;
  1004. }
  1005. if (!array_key_exists($submission->assignment, $modcache)) {
  1006. if (! $mod = get_coursemodule_from_instance("assign", $submission->assignment, $course->id)) {
  1007. mtrace("Could not find course module for assignment id $submission->assignment");
  1008. continue;
  1009. }
  1010. $modcache[$submission->assignment] = $mod;
  1011. } else {
  1012. $mod = $modcache[$submission->assignment];
  1013. }
  1014. // context lookups are already cached
  1015. $contextmodule = context_module::instance($mod->id);
  1016. if (!$mod->visible) {
  1017. // Hold mail notification for hidden assignments until later
  1018. continue;
  1019. }
  1020. // need to send this to the student
  1021. $messagetype = 'feedbackavailable';
  1022. $eventtype = 'assign_notification';
  1023. $updatetime = $submission->lastmodified;
  1024. $modulename = get_string('modulename', 'assign');
  1025. self::send_assignment_notification($grader, $user, $messagetype, $eventtype, $updatetime, $mod, $contextmodule, $course, $modulename, $submission->name);
  1026. $grade = new stdClass();
  1027. $grade->id = $submission->gradeid;
  1028. $grade->mailed = 1;
  1029. $DB->update_record('assign_grades', $grade);
  1030. mtrace('Done');
  1031. }
  1032. mtrace('Done processing ' . count($submissions) . ' assignment submissions');
  1033. cron_setup_user();
  1034. // Free up memory just to be sure
  1035. unset($courses);
  1036. unset($modcache);
  1037. return true;
  1038. }
  1039. /**
  1040. * Update a grade in the grade table for the assignment and in the gradebook
  1041. *
  1042. * @param stdClass $grade a grade record keyed on id
  1043. * @return bool true for success
  1044. */
  1045. private function update_grade($grade) {
  1046. global $DB;
  1047. $grade->timemodified = time();
  1048. if ($grade->grade && $grade->grade != -1) {
  1049. if ($this->get_instance()->grade > 0) {
  1050. if (!is_numeric($grade->grade)) {
  1051. return false;
  1052. } else if ($grade->grade > $this->get_instance()->grade) {
  1053. return false;
  1054. } else if ($grade->grade < 0) {
  1055. return false;
  1056. }
  1057. } else {
  1058. // this is a scale
  1059. if ($scale = $DB->get_record('scale', array('id' => -($this->get_instance()->grade)))) {
  1060. $scaleoptions = make_menu_from_list($scale->scale);
  1061. if (!array_key_exists((int) $grade->grade, $scaleoptions)) {
  1062. return false;
  1063. }
  1064. }
  1065. }
  1066. }
  1067. $result = $DB->update_record('assign_grades', $grade);
  1068. if ($result) {
  1069. $this->gradebook_item_update(null, $grade);
  1070. }
  1071. return $result;
  1072. }
  1073. /**
  1074. * display the submission that is used by a plugin
  1075. * Uses url parameters 'sid', 'gid' and 'plugin'
  1076. * @param string $pluginsubtype
  1077. * @return string
  1078. */
  1079. private function view_plugin_content($pluginsubtype) {
  1080. global $USER;
  1081. $o = '';
  1082. $submissionid = optional_param('sid', 0, PARAM_INT);
  1083. $gradeid = optional_param('gid', 0, PARAM_INT);
  1084. $plugintype = required_param('plugin', PARAM_TEXT);
  1085. $item = null;
  1086. if ($pluginsubtype == 'assignsubmission') {
  1087. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1088. if ($submissionid <= 0) {
  1089. throw new coding_exception('Submission id should not be 0');
  1090. }
  1091. $item = $this->get_submission($submissionid);
  1092. // permissions
  1093. if ($item->userid != $USER->id) {
  1094. require_capability('mod/assign:grade', $this->context);
  1095. }
  1096. $o .= $this->output->render(new assign_header($this->get_instance(),
  1097. $this->get_context(),
  1098. $this->show_intro(),
  1099. $this->get_course_module()->id,
  1100. $plugin->get_name()));
  1101. $o .= $this->output->render(new assign_submission_plugin_submission($plugin,
  1102. $item,
  1103. assign_submission_plugin_submission::FULL,
  1104. $this->get_course_module()->id,
  1105. $this->get_return_action(),
  1106. $this->get_return_params()));
  1107. $this->add_to_log('view submission', get_string('viewsubmissionforuser', 'assign', $item->userid));
  1108. } else {
  1109. $plugin = $this->get_feedback_plugin_by_type($plugintype);
  1110. if ($gradeid <= 0) {
  1111. throw new coding_exception('Grade id should not be 0');
  1112. }
  1113. $item = $this->get_grade($gradeid);
  1114. // permissions
  1115. if ($item->userid != $USER->id) {
  1116. require_capability('mod/assign:grade', $this->context);
  1117. }
  1118. $o .= $this->output->render(new assign_header($this->get_instance(),
  1119. $this->get_context(),
  1120. $this->show_intro(),
  1121. $this->get_course_module()->id,
  1122. $plugin->get_name()));
  1123. $o .= $this->output->render(new assign_feedback_plugin_feedback($plugin,
  1124. $item,
  1125. assign_feedback_plugin_feedback::FULL,
  1126. $this->get_course_module()->id,
  1127. $this->get_return_action(),
  1128. $this->get_return_params()));
  1129. $this->add_to_log('view feedback', get_string('viewfeedbackforuser', 'assign', $item->userid));
  1130. }
  1131. $o .= $this->view_return_links();
  1132. $o .= $this->view_footer();
  1133. return $o;
  1134. }
  1135. /**
  1136. * render the content in editor that is often used by plugin
  1137. *
  1138. * @param string $filearea
  1139. * @param int $submissionid
  1140. * @param string $plugintype
  1141. * @param string $editor
  1142. * @param string $component
  1143. * @return string
  1144. */
  1145. public function render_editor_content($filearea, $submissionid, $plugintype, $editor, $component) {
  1146. global $CFG;
  1147. $result = '';
  1148. $plugin = $this->get_submission_plugin_by_type($plugintype);
  1149. $text = $plugin->get_editor_text($editor, $submissionid);
  1150. $format = $plugin->get_editor_format($editor, $submissionid);
  1151. $finaltext = file_rewrite_pluginfile_urls($text, 'pluginfile.php', $this->get_context()->id, $component, $filearea, $submissionid);
  1152. $result .= format_text($finaltext, $format, array('overflowdiv' => true, 'context' => $this->get_context()));
  1153. if ($CFG->enableportfolios) {
  1154. require_once($CFG->libdir . '/portfoliolib.php');
  1155. $button = new portfolio_add_button();
  1156. $button->set_callback_options('assiā€¦

Large files files are truncated, but you can click here to view the full file