2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * This file contains the definition for the library class for file feedback plugin
21 * @package assignfeedback_file
22 * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
28 // File areas for file feedback assignment.
29 define('ASSIGNFEEDBACK_FILE_FILEAREA', 'feedback_files');
30 define('ASSIGNFEEDBACK_FILE_BATCH_FILEAREA', 'feedback_files_batch');
31 define('ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA', 'feedback_files_import');
32 define('ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES', 5);
33 define('ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS', 5);
34 define('ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME', 120);
37 * Library class for file feedback plugin extending feedback plugin base class.
39 * @package assignfeedback_file
40 * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 class assign_feedback_file extends assign_feedback_plugin {
46 * Get the name of the file feedback plugin.
50 public function get_name() {
51 return get_string('file', 'assignfeedback_file');
55 * Get file feedback information from the database.
60 public function get_file_feedback($gradeid) {
62 return $DB->get_record('assignfeedback_file', array('grade'=>$gradeid));
66 * File format options.
70 private function get_file_options() {
73 $fileoptions = array('subdirs'=>1,
74 'maxbytes'=>$COURSE->maxbytes,
75 'accepted_types'=>'*',
76 'return_types'=>FILE_INTERNAL);
81 * Copy all the files from one file area to another.
83 * @param file_storage $fs - The source context id
84 * @param int $fromcontextid - The source context id
85 * @param string $fromcomponent - The source component
86 * @param string $fromfilearea - The source filearea
87 * @param int $fromitemid - The source item id
88 * @param int $tocontextid - The destination context id
89 * @param string $tocomponent - The destination component
90 * @param string $tofilearea - The destination filearea
91 * @param int $toitemid - The destination item id
94 private function copy_area_files(file_storage $fs,
104 $newfilerecord = new stdClass();
105 $newfilerecord->contextid = $tocontextid;
106 $newfilerecord->component = $tocomponent;
107 $newfilerecord->filearea = $tofilearea;
108 $newfilerecord->itemid = $toitemid;
110 if ($files = $fs->get_area_files($fromcontextid, $fromcomponent, $fromfilearea, $fromitemid)) {
111 foreach ($files as $file) {
112 if ($file->is_directory() and $file->get_filepath() === '/') {
113 // We need a way to mark the age of each draft area.
114 // By not copying the root dir we force it to be created
115 // automatically with current timestamp.
118 $newfile = $fs->create_file_from_storedfile($newfilerecord, $file);
125 * Get form elements for grading form.
127 * @param stdClass $grade
128 * @param MoodleQuickForm $mform
129 * @param stdClass $data
130 * @param int $userid The userid we are currently grading
131 * @return bool true if elements were added to the form
133 public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
135 $fileoptions = $this->get_file_options();
136 $gradeid = $grade ? $grade->id : 0;
137 $elementname = 'files_' . $userid;
139 $data = file_prepare_standard_filemanager($data,
142 $this->assignment->get_context(),
143 'assignfeedback_file',
144 ASSIGNFEEDBACK_FILE_FILEAREA,
146 $mform->addElement('filemanager', $elementname . '_filemanager', $this->get_name(), null, $fileoptions);
152 * Count the number of files.
154 * @param int $gradeid
155 * @param string $area
158 private function count_files($gradeid, $area) {
160 $fs = get_file_storage();
161 $files = $fs->get_area_files($this->assignment->get_context()->id,
162 'assignfeedback_file',
168 return count($files);
172 * Update the number of files in the file area.
174 * @param stdClass $grade The grade record
175 * @return bool - true if the value was saved
177 public function update_file_count($grade) {
180 $filefeedback = $this->get_file_feedback($grade->id);
182 $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
183 return $DB->update_record('assignfeedback_file', $filefeedback);
185 $filefeedback = new stdClass();
186 $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
187 $filefeedback->grade = $grade->id;
188 $filefeedback->assignment = $this->assignment->get_instance()->id;
189 return $DB->insert_record('assignfeedback_file', $filefeedback) > 0;
194 * Save the feedback files.
196 * @param stdClass $grade
197 * @param stdClass $data
200 public function save(stdClass $grade, stdClass $data) {
201 $fileoptions = $this->get_file_options();
203 // The element name may have been for a different user.
204 foreach ($data as $key => $value) {
205 if (strpos($key, 'files_') === 0 && strpos($key, '_filemanager')) {
206 $elementname = substr($key, 0, strpos($key, '_filemanager'));
210 $data = file_postupdate_standard_filemanager($data,
213 $this->assignment->get_context(),
214 'assignfeedback_file',
215 ASSIGNFEEDBACK_FILE_FILEAREA,
218 return $this->update_file_count($grade);
222 * Display the list of files in the feedback status table.
224 * @param stdClass $grade
225 * @param bool $showviewlink - Set to true to show a link to see the full list of files
228 public function view_summary(stdClass $grade, & $showviewlink) {
230 $count = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
232 // Show a view all link if the number of files is over this limit.
233 $showviewlink = $count > ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES;
235 if ($count <= ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES) {
236 return $this->assignment->render_area_files('assignfeedback_file',
237 ASSIGNFEEDBACK_FILE_FILEAREA,
240 return get_string('countfiles', 'assignfeedback_file', $count);
245 * Display the list of files in the feedback status table.
247 * @param stdClass $grade
250 public function view(stdClass $grade) {
251 return $this->assignment->render_area_files('assignfeedback_file',
252 ASSIGNFEEDBACK_FILE_FILEAREA,
257 * The assignment has been deleted - cleanup.
261 public function delete_instance() {
263 // Will throw exception on failure.
264 $DB->delete_records('assignfeedback_file',
265 array('assignment'=>$this->assignment->get_instance()->id));
271 * Return true if there are no feedback files.
273 * @param stdClass $grade
275 public function is_empty(stdClass $grade) {
276 return $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA) == 0;
280 * Get file areas returns a list of areas this plugin stores files.
282 * @return array - An array of fileareas (keys) and descriptions (values)
284 public function get_file_areas() {
285 return array(ASSIGNFEEDBACK_FILE_FILEAREA=>$this->get_name());
289 * Return true if this plugin can upgrade an old Moodle 2.2 assignment of this type
292 * @param string $type old assignment subtype
293 * @param int $version old assignment version
294 * @return bool True if upgrade is possible
296 public function can_upgrade($type, $version) {
297 if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
304 * Upgrade the settings from the old assignment to the new plugin based one.
306 * @param context $oldcontext - the context for the old assignment
307 * @param stdClass $oldassignment - the data for the old assignment
308 * @param string $log - can be appended to by the upgrade
309 * @return bool was it a success? (false will trigger a rollback)
311 public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
312 // First upgrade settings (nothing to do).
317 * Upgrade the feedback from the old assignment to the new one.
319 * @param context $oldcontext - the database for the old assignment context
320 * @param stdClass $oldassignment The data record for the old assignment
321 * @param stdClass $oldsubmission The data record for the old submission
322 * @param stdClass $grade The data record for the new grade
323 * @param string $log Record upgrade messages in the log
324 * @return bool true or false - false will trigger a rollback
326 public function upgrade(context $oldcontext,
327 stdClass $oldassignment,
328 stdClass $oldsubmission,
333 // Now copy the area files.
334 $this->assignment->copy_area_files_for_upgrade($oldcontext->id,
338 $this->assignment->get_context()->id,
339 'assignfeedback_file',
340 ASSIGNFEEDBACK_FILE_FILEAREA,
344 $filefeedback = new stdClass();
345 $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
346 $filefeedback->grade = $grade->id;
347 $filefeedback->assignment = $this->assignment->get_instance()->id;
348 if (!$DB->insert_record('assignfeedback_file', $filefeedback) > 0) {
349 $log .= get_string('couldnotconvertgrade', 'mod_assign', $grade->userid);
356 * Return a list of the batch grading operations performed by this plugin.
357 * This plugin supports batch upload files and upload zip.
359 * @return array The list of batch grading operations
361 public function get_grading_batch_operations() {
362 return array('uploadfiles'=>get_string('uploadfiles', 'assignfeedback_file'));
366 * Upload files and send them to multiple users.
368 * @param array $users - An array of user ids
369 * @return string - The response html
371 public function view_batch_upload_files($users) {
372 global $CFG, $DB, $USER;
374 require_capability('mod/assign:grade', $this->assignment->get_context());
375 require_once($CFG->dirroot . '/mod/assign/feedback/file/batchuploadfilesform.php');
376 require_once($CFG->dirroot . '/mod/assign/renderable.php');
378 $formparams = array('cm'=>$this->assignment->get_course_module()->id,
380 'context'=>$this->assignment->get_context());
385 foreach ($users as $userid) {
386 if ($usercount >= ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS) {
387 $moreuserscount = count($users) - ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS;
388 $usershtml .= get_string('moreusers', 'assignfeedback_file', $moreuserscount);
391 $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
393 $usersummary = new assign_user_summary($user,
394 $this->assignment->get_course()->id,
395 has_capability('moodle/site:viewfullnames',
396 $this->assignment->get_course_context()),
397 $this->assignment->is_blind_marking(),
398 $this->assignment->get_uniqueid_for_user($user->id),
399 get_extra_user_fields($this->assignment->get_context()));
400 $usershtml .= $this->assignment->get_renderer()->render($usersummary);
404 $formparams['usershtml'] = $usershtml;
406 $mform = new assignfeedback_file_batch_upload_files_form(null, $formparams);
408 if ($mform->is_cancelled()) {
409 redirect(new moodle_url('view.php',
410 array('id'=>$this->assignment->get_course_module()->id,
411 'action'=>'grading')));
413 } else if ($data = $mform->get_data()) {
414 // Copy the files from the draft area to a temporary import area.
415 $data = file_postupdate_standard_filemanager($data,
417 $this->get_file_options(),
418 $this->assignment->get_context(),
419 'assignfeedback_file',
420 ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
422 $fs = get_file_storage();
424 // Now copy each of these files to the users feedback file area.
425 foreach ($users as $userid) {
426 $grade = $this->assignment->get_user_grade($userid, true);
427 $this->assignment->notify_grade_modified($grade);
429 $this->copy_area_files($fs,
430 $this->assignment->get_context()->id,
431 'assignfeedback_file',
432 ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
434 $this->assignment->get_context()->id,
435 'assignfeedback_file',
436 ASSIGNFEEDBACK_FILE_FILEAREA,
439 $filefeedback = $this->get_file_feedback($grade->id);
441 $filefeedback->numfiles = $this->count_files($grade->id,
442 ASSIGNFEEDBACK_FILE_FILEAREA);
443 $DB->update_record('assignfeedback_file', $filefeedback);
445 $filefeedback = new stdClass();
446 $filefeedback->numfiles = $this->count_files($grade->id,
447 ASSIGNFEEDBACK_FILE_FILEAREA);
448 $filefeedback->grade = $grade->id;
449 $filefeedback->assignment = $this->assignment->get_instance()->id;
450 $DB->insert_record('assignfeedback_file', $filefeedback);
454 // Now delete the temporary import area.
455 $fs->delete_area_files($this->assignment->get_context()->id,
456 'assignfeedback_file',
457 ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
460 redirect(new moodle_url('view.php',
461 array('id'=>$this->assignment->get_course_module()->id,
462 'action'=>'grading')));
466 $header = new assign_header($this->assignment->get_instance(),
467 $this->assignment->get_context(),
469 $this->assignment->get_course_module()->id,
470 get_string('batchuploadfiles', 'assignfeedback_file'));
472 $o .= $this->assignment->get_renderer()->render($header);
473 $o .= $this->assignment->get_renderer()->render(new assign_form('batchuploadfiles', $mform));
474 $o .= $this->assignment->get_renderer()->render_footer();
481 * User has chosen a custom grading batch operation and selected some users.
483 * @param string $action - The chosen action
484 * @param array $users - An array of user ids
485 * @return string - The response html
487 public function grading_batch_operation($action, $users) {
489 if ($action == 'uploadfiles') {
490 return $this->view_batch_upload_files($users);
496 * View the upload zip form.
498 * @return string - The html response
500 public function view_upload_zip() {
503 require_capability('mod/assign:grade', $this->assignment->get_context());
504 require_once($CFG->dirroot . '/mod/assign/feedback/file/uploadzipform.php');
505 require_once($CFG->dirroot . '/mod/assign/feedback/file/importziplib.php');
506 require_once($CFG->dirroot . '/mod/assign/feedback/file/importzipform.php');
508 $formparams = array('context'=>$this->assignment->get_context(),
509 'cm'=>$this->assignment->get_course_module()->id);
510 $mform = new assignfeedback_file_upload_zip_form(null, $formparams);
514 $confirm = optional_param('confirm', 0, PARAM_BOOL);
515 $renderer = $this->assignment->get_renderer();
517 // Delete any existing files.
518 $importer = new assignfeedback_file_zip_importer();
519 $contextid = $this->assignment->get_context()->id;
521 if ($mform->is_cancelled()) {
522 $importer->delete_import_files($contextid);
523 $urlparams = array('id'=>$this->assignment->get_course_module()->id,
524 'action'=>'grading');
525 $url = new moodle_url('view.php', $urlparams);
528 } else if ($confirm) {
529 $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
531 $mform = new assignfeedback_file_import_zip_form(null, $params);
532 if ($mform->is_cancelled()) {
533 $importer->delete_import_files($contextid);
534 $urlparams = array('id'=>$this->assignment->get_course_module()->id,
535 'action'=>'grading');
536 $url = new moodle_url('view.php', $urlparams);
541 $o .= $importer->import_zip_files($this->assignment, $this);
542 $importer->delete_import_files($contextid);
543 } else if (($data = $mform->get_data()) &&
544 ($zipfile = $mform->save_stored_file('feedbackzip',
546 'assignfeedback_file',
547 ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
553 $importer->extract_files_from_zip($zipfile, $contextid);
555 $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
557 $mform = new assignfeedback_file_import_zip_form(null, $params);
559 $header = new assign_header($this->assignment->get_instance(),
560 $this->assignment->get_context(),
562 $this->assignment->get_course_module()->id,
563 get_string('confirmuploadzip', 'assignfeedback_file'));
564 $o .= $renderer->render($header);
565 $o .= $renderer->render(new assign_form('confirmimportzip', $mform));
566 $o .= $renderer->render_footer();
570 $header = new assign_header($this->assignment->get_instance(),
571 $this->assignment->get_context(),
573 $this->assignment->get_course_module()->id,
574 get_string('uploadzip', 'assignfeedback_file'));
575 $o .= $renderer->render($header);
576 $o .= $renderer->render(new assign_form('uploadfeedbackzip', $mform));
577 $o .= $renderer->render_footer();
584 * Called by the assignment module when someone chooses something from the
585 * grading navigation or batch operations list.
587 * @param string $action - The page to view
588 * @return string - The html response
590 public function view_page($action) {
591 if ($action == 'uploadfiles') {
592 $users = required_param('selectedusers', PARAM_SEQUENCE);
593 return $this->view_batch_upload_files(explode(',', $users));
595 if ($action == 'uploadzip') {
596 return $this->view_upload_zip();
603 * Return a list of the grading actions performed by this plugin.
604 * This plugin supports upload zip.
606 * @return array The list of grading actions
608 public function get_grading_actions() {
609 return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
613 * Return a description of external params suitable for uploading a feedback file from a webservice.
615 * @return external_description|null
617 public function get_external_parameters() {
619 'files_filemanager' => new external_value(
621 'The id of a draft area containing files for this feedback.'