7b64583ac8e2ab19de0eee036b50abf5ecaa2249
[moodle.git] / mod / assign / feedback / file / locallib.php
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/>.
17 /**
18  * This file contains the definition for the library class for file feedback plugin
19  *
20  *
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
24  */
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);
36 /**
37  * Library class for file feedback plugin extending feedback plugin base class.
38  *
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
42  */
43 class assign_feedback_file extends assign_feedback_plugin {
45     /**
46      * Get the name of the file feedback plugin.
47      *
48      * @return string
49      */
50     public function get_name() {
51         return get_string('file', 'assignfeedback_file');
52     }
54     /**
55      * Get file feedback information from the database.
56      *
57      * @param int $gradeid
58      * @return mixed
59      */
60     public function get_file_feedback($gradeid) {
61         global $DB;
62         return $DB->get_record('assignfeedback_file', array('grade'=>$gradeid));
63     }
65     /**
66      * File format options.
67      *
68      * @return array
69      */
70     private function get_file_options() {
71         global $COURSE;
73         $fileoptions = array('subdirs'=>1,
74                              'maxbytes'=>$COURSE->maxbytes,
75                              'accepted_types'=>'*',
76                              'return_types'=>FILE_INTERNAL);
77         return $fileoptions;
78     }
80     /**
81      * Copy all the files from one file area to another.
82      *
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
92      * @return boolean
93      */
94     private function copy_area_files(file_storage $fs,
95                                      $fromcontextid,
96                                      $fromcomponent,
97                                      $fromfilearea,
98                                      $fromitemid,
99                                      $tocontextid,
100                                      $tocomponent,
101                                      $tofilearea,
102                                      $toitemid) {
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.
116                     continue;
117                 }
118                 $newfile = $fs->create_file_from_storedfile($newfilerecord, $file);
119             }
120         }
121         return true;
122     }
124     /**
125      * Get form elements for grading form.
126      *
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
132      */
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,
140                                                   $elementname,
141                                                   $fileoptions,
142                                                   $this->assignment->get_context(),
143                                                   'assignfeedback_file',
144                                                   ASSIGNFEEDBACK_FILE_FILEAREA,
145                                                   $gradeid);
146         $mform->addElement('filemanager', $elementname . '_filemanager', html_writer::tag('span', $this->get_name(),
147             array('class' => 'accesshide')), null, $fileoptions);
149         return true;
150     }
152     /**
153      * Count the number of files.
154      *
155      * @param int $gradeid
156      * @param string $area
157      * @return int
158      */
159     private function count_files($gradeid, $area) {
161         $fs = get_file_storage();
162         $files = $fs->get_area_files($this->assignment->get_context()->id,
163                                      'assignfeedback_file',
164                                      $area,
165                                      $gradeid,
166                                      'id',
167                                      false);
169         return count($files);
170     }
172     /**
173      * Update the number of files in the file area.
174      *
175      * @param stdClass $grade The grade record
176      * @return bool - true if the value was saved
177      */
178     public function update_file_count($grade) {
179         global $DB;
181         $filefeedback = $this->get_file_feedback($grade->id);
182         if ($filefeedback) {
183             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
184             return $DB->update_record('assignfeedback_file', $filefeedback);
185         } else {
186             $filefeedback = new stdClass();
187             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
188             $filefeedback->grade = $grade->id;
189             $filefeedback->assignment = $this->assignment->get_instance()->id;
190             return $DB->insert_record('assignfeedback_file', $filefeedback) > 0;
191         }
192     }
194     /**
195      * Save the feedback files.
196      *
197      * @param stdClass $grade
198      * @param stdClass $data
199      * @return bool
200      */
201     public function save(stdClass $grade, stdClass $data) {
202         $fileoptions = $this->get_file_options();
204         // The element name may have been for a different user.
205         foreach ($data as $key => $value) {
206             if (strpos($key, 'files_') === 0 && strpos($key, '_filemanager')) {
207                 $elementname = substr($key, 0, strpos($key, '_filemanager'));
208             }
209         }
211         $data = file_postupdate_standard_filemanager($data,
212                                                      $elementname,
213                                                      $fileoptions,
214                                                      $this->assignment->get_context(),
215                                                      'assignfeedback_file',
216                                                      ASSIGNFEEDBACK_FILE_FILEAREA,
217                                                      $grade->id);
219         return $this->update_file_count($grade);
220     }
222     /**
223      * Display the list of files in the feedback status table.
224      *
225      * @param stdClass $grade
226      * @param bool $showviewlink - Set to true to show a link to see the full list of files
227      * @return string
228      */
229     public function view_summary(stdClass $grade, & $showviewlink) {
231         $count = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
233         // Show a view all link if the number of files is over this limit.
234         $showviewlink = $count > ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES;
236         if ($count <= ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES) {
237             return $this->assignment->render_area_files('assignfeedback_file',
238                                                         ASSIGNFEEDBACK_FILE_FILEAREA,
239                                                         $grade->id);
240         } else {
241             return get_string('countfiles', 'assignfeedback_file', $count);
242         }
243     }
245     /**
246      * Display the list of files in the feedback status table.
247      *
248      * @param stdClass $grade
249      * @return string
250      */
251     public function view(stdClass $grade) {
252         return $this->assignment->render_area_files('assignfeedback_file',
253                                                     ASSIGNFEEDBACK_FILE_FILEAREA,
254                                                     $grade->id);
255     }
257     /**
258      * The assignment has been deleted - cleanup.
259      *
260      * @return bool
261      */
262     public function delete_instance() {
263         global $DB;
264         // Will throw exception on failure.
265         $DB->delete_records('assignfeedback_file',
266                             array('assignment'=>$this->assignment->get_instance()->id));
268         return true;
269     }
271     /**
272      * Return true if there are no feedback files.
273      *
274      * @param stdClass $grade
275      */
276     public function is_empty(stdClass $grade) {
277         return $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA) == 0;
278     }
280     /**
281      * Get file areas returns a list of areas this plugin stores files.
282      *
283      * @return array - An array of fileareas (keys) and descriptions (values)
284      */
285     public function get_file_areas() {
286         return array(ASSIGNFEEDBACK_FILE_FILEAREA=>$this->get_name());
287     }
289     /**
290      * Return true if this plugin can upgrade an old Moodle 2.2 assignment of this type
291      * and version.
292      *
293      * @param string $type old assignment subtype
294      * @param int $version old assignment version
295      * @return bool True if upgrade is possible
296      */
297     public function can_upgrade($type, $version) {
298         if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
299             return true;
300         }
301         return false;
302     }
304     /**
305      * Upgrade the settings from the old assignment to the new plugin based one.
306      *
307      * @param context $oldcontext - the context for the old assignment
308      * @param stdClass $oldassignment - the data for the old assignment
309      * @param string $log - can be appended to by the upgrade
310      * @return bool was it a success? (false will trigger a rollback)
311      */
312     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
313         // First upgrade settings (nothing to do).
314         return true;
315     }
317     /**
318      * Upgrade the feedback from the old assignment to the new one.
319      *
320      * @param context $oldcontext - the database for the old assignment context
321      * @param stdClass $oldassignment The data record for the old assignment
322      * @param stdClass $oldsubmission The data record for the old submission
323      * @param stdClass $grade The data record for the new grade
324      * @param string $log Record upgrade messages in the log
325      * @return bool true or false - false will trigger a rollback
326      */
327     public function upgrade(context $oldcontext,
328                             stdClass $oldassignment,
329                             stdClass $oldsubmission,
330                             stdClass $grade,
331                             & $log) {
332         global $DB;
334         // Now copy the area files.
335         $this->assignment->copy_area_files_for_upgrade($oldcontext->id,
336                                                         'mod_assignment',
337                                                         'response',
338                                                         $oldsubmission->id,
339                                                         $this->assignment->get_context()->id,
340                                                         'assignfeedback_file',
341                                                         ASSIGNFEEDBACK_FILE_FILEAREA,
342                                                         $grade->id);
344         // Now count them!
345         $filefeedback = new stdClass();
346         $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
347         $filefeedback->grade = $grade->id;
348         $filefeedback->assignment = $this->assignment->get_instance()->id;
349         if (!$DB->insert_record('assignfeedback_file', $filefeedback) > 0) {
350             $log .= get_string('couldnotconvertgrade', 'mod_assign', $grade->userid);
351             return false;
352         }
353         return true;
354     }
356     /**
357      * Return a list of the batch grading operations performed by this plugin.
358      * This plugin supports batch upload files and upload zip.
359      *
360      * @return array The list of batch grading operations
361      */
362     public function get_grading_batch_operations() {
363         return array('uploadfiles'=>get_string('uploadfiles', 'assignfeedback_file'));
364     }
366     /**
367      * Upload files and send them to multiple users.
368      *
369      * @param array $users - An array of user ids
370      * @return string - The response html
371      */
372     public function view_batch_upload_files($users) {
373         global $CFG, $DB, $USER;
375         require_capability('mod/assign:grade', $this->assignment->get_context());
376         require_once($CFG->dirroot . '/mod/assign/feedback/file/batchuploadfilesform.php');
377         require_once($CFG->dirroot . '/mod/assign/renderable.php');
379         $formparams = array('cm'=>$this->assignment->get_course_module()->id,
380                             'users'=>$users,
381                             'context'=>$this->assignment->get_context());
383         $usershtml = '';
385         $usercount = 0;
386         foreach ($users as $userid) {
387             if ($usercount >= ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS) {
388                 $moreuserscount = count($users) - ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS;
389                 $usershtml .= get_string('moreusers', 'assignfeedback_file', $moreuserscount);
390                 break;
391             }
392             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
394             $usersummary = new assign_user_summary($user,
395                                                    $this->assignment->get_course()->id,
396                                                    has_capability('moodle/site:viewfullnames',
397                                                    $this->assignment->get_course_context()),
398                                                    $this->assignment->is_blind_marking(),
399                                                    $this->assignment->get_uniqueid_for_user($user->id),
400                                                    get_extra_user_fields($this->assignment->get_context()));
401             $usershtml .= $this->assignment->get_renderer()->render($usersummary);
402             $usercount += 1;
403         }
405         $formparams['usershtml'] = $usershtml;
407         $mform = new assignfeedback_file_batch_upload_files_form(null, $formparams);
409         if ($mform->is_cancelled()) {
410             redirect(new moodle_url('view.php',
411                                     array('id'=>$this->assignment->get_course_module()->id,
412                                           'action'=>'grading')));
413             return;
414         } else if ($data = $mform->get_data()) {
415             // Copy the files from the draft area to a temporary import area.
416             $data = file_postupdate_standard_filemanager($data,
417                                                          'files',
418                                                          $this->get_file_options(),
419                                                          $this->assignment->get_context(),
420                                                          'assignfeedback_file',
421                                                          ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
422                                                          $USER->id);
423             $fs = get_file_storage();
425             // Now copy each of these files to the users feedback file area.
426             foreach ($users as $userid) {
427                 $grade = $this->assignment->get_user_grade($userid, true);
428                 $this->assignment->notify_grade_modified($grade);
430                 $this->copy_area_files($fs,
431                                        $this->assignment->get_context()->id,
432                                        'assignfeedback_file',
433                                        ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
434                                        $USER->id,
435                                        $this->assignment->get_context()->id,
436                                        'assignfeedback_file',
437                                        ASSIGNFEEDBACK_FILE_FILEAREA,
438                                        $grade->id);
440                 $filefeedback = $this->get_file_feedback($grade->id);
441                 if ($filefeedback) {
442                     $filefeedback->numfiles = $this->count_files($grade->id,
443                                                                  ASSIGNFEEDBACK_FILE_FILEAREA);
444                     $DB->update_record('assignfeedback_file', $filefeedback);
445                 } else {
446                     $filefeedback = new stdClass();
447                     $filefeedback->numfiles = $this->count_files($grade->id,
448                                                                  ASSIGNFEEDBACK_FILE_FILEAREA);
449                     $filefeedback->grade = $grade->id;
450                     $filefeedback->assignment = $this->assignment->get_instance()->id;
451                     $DB->insert_record('assignfeedback_file', $filefeedback);
452                 }
453             }
455             // Now delete the temporary import area.
456             $fs->delete_area_files($this->assignment->get_context()->id,
457                                    'assignfeedback_file',
458                                    ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
459                                    $USER->id);
461             redirect(new moodle_url('view.php',
462                                     array('id'=>$this->assignment->get_course_module()->id,
463                                           'action'=>'grading')));
464             return;
465         } else {
467             $header = new assign_header($this->assignment->get_instance(),
468                                         $this->assignment->get_context(),
469                                         false,
470                                         $this->assignment->get_course_module()->id,
471                                         get_string('batchuploadfiles', 'assignfeedback_file'));
472             $o = '';
473             $o .= $this->assignment->get_renderer()->render($header);
474             $o .= $this->assignment->get_renderer()->render(new assign_form('batchuploadfiles', $mform));
475             $o .= $this->assignment->get_renderer()->render_footer();
476         }
478         return $o;
479     }
481     /**
482      * User has chosen a custom grading batch operation and selected some users.
483      *
484      * @param string $action - The chosen action
485      * @param array $users - An array of user ids
486      * @return string - The response html
487      */
488     public function grading_batch_operation($action, $users) {
490         if ($action == 'uploadfiles') {
491             return $this->view_batch_upload_files($users);
492         }
493         return '';
494     }
496     /**
497      * View the upload zip form.
498      *
499      * @return string - The html response
500      */
501     public function view_upload_zip() {
502         global $CFG, $USER;
504         require_capability('mod/assign:grade', $this->assignment->get_context());
505         require_once($CFG->dirroot . '/mod/assign/feedback/file/uploadzipform.php');
506         require_once($CFG->dirroot . '/mod/assign/feedback/file/importziplib.php');
507         require_once($CFG->dirroot . '/mod/assign/feedback/file/importzipform.php');
509         $formparams = array('context'=>$this->assignment->get_context(),
510                             'cm'=>$this->assignment->get_course_module()->id);
511         $mform = new assignfeedback_file_upload_zip_form(null, $formparams);
513         $o = '';
515         $confirm = optional_param('confirm', 0, PARAM_BOOL);
516         $renderer = $this->assignment->get_renderer();
518         // Delete any existing files.
519         $importer = new assignfeedback_file_zip_importer();
520         $contextid = $this->assignment->get_context()->id;
522         if ($mform->is_cancelled()) {
523             $importer->delete_import_files($contextid);
524             $urlparams = array('id'=>$this->assignment->get_course_module()->id,
525                                'action'=>'grading');
526             $url = new moodle_url('view.php', $urlparams);
527             redirect($url);
528             return;
529         } else if ($confirm) {
530             $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
532             $mform = new assignfeedback_file_import_zip_form(null, $params);
533             if ($mform->is_cancelled()) {
534                 $importer->delete_import_files($contextid);
535                 $urlparams = array('id'=>$this->assignment->get_course_module()->id,
536                                    'action'=>'grading');
537                 $url = new moodle_url('view.php', $urlparams);
538                 redirect($url);
539                 return;
540             }
542             $o .= $importer->import_zip_files($this->assignment, $this);
543             $importer->delete_import_files($contextid);
544         } else if (($data = $mform->get_data()) &&
545                    ($zipfile = $mform->save_stored_file('feedbackzip',
546                                                         $contextid,
547                                                         'assignfeedback_file',
548                                                         ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
549                                                         $USER->id,
550                                                         '/',
551                                                         'import.zip',
552                                                         true))) {
554             $importer->extract_files_from_zip($zipfile, $contextid);
556             $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
558             $mform = new assignfeedback_file_import_zip_form(null, $params);
560             $header = new assign_header($this->assignment->get_instance(),
561                                         $this->assignment->get_context(),
562                                         false,
563                                         $this->assignment->get_course_module()->id,
564                                         get_string('confirmuploadzip', 'assignfeedback_file'));
565             $o .= $renderer->render($header);
566             $o .= $renderer->render(new assign_form('confirmimportzip', $mform));
567             $o .= $renderer->render_footer();
569         } else {
571             $header = new assign_header($this->assignment->get_instance(),
572                                         $this->assignment->get_context(),
573                                         false,
574                                         $this->assignment->get_course_module()->id,
575                                         get_string('uploadzip', 'assignfeedback_file'));
576             $o .= $renderer->render($header);
577             $o .= $renderer->render(new assign_form('uploadfeedbackzip', $mform));
578             $o .= $renderer->render_footer();
579         }
581         return $o;
582     }
584     /**
585      * Called by the assignment module when someone chooses something from the
586      * grading navigation or batch operations list.
587      *
588      * @param string $action - The page to view
589      * @return string - The html response
590      */
591     public function view_page($action) {
592         if ($action == 'uploadfiles') {
593             $users = required_param('selectedusers', PARAM_SEQUENCE);
594             return $this->view_batch_upload_files(explode(',', $users));
595         }
596         if ($action == 'uploadzip') {
597             return $this->view_upload_zip();
598         }
600         return '';
601     }
603     /**
604      * Return a list of the grading actions performed by this plugin.
605      * This plugin supports upload zip.
606      *
607      * @return array The list of grading actions
608      */
609     public function get_grading_actions() {
610         return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
611     }
613     /**
614      * Return a description of external params suitable for uploading a feedback file from a webservice.
615      *
616      * @return external_description|null
617      */
618     public function get_external_parameters() {
619         return array(
620             'files_filemanager' => new external_value(
621                 PARAM_INT,
622                 'The id of a draft area containing files for this feedback.'
623             )
624         );
625     }