6ed295eca93c0bf57d82c304cc4490b470ad4971
[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();
27 /**
28  * File areas for file feedback assignment
29  */
30 define('ASSIGNFEEDBACK_FILE_FILEAREA', 'feedback_files');
31 define('ASSIGNFEEDBACK_FILE_BATCH_FILEAREA', 'feedback_files_batch');
32 define('ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA', 'feedback_files_import');
33 define('ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES', 5);
34 define('ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS', 5);
35 define('ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME', 120);
37 /**
38  * library class for file feedback plugin extending feedback plugin base class
39  *
40  * @package   asignfeedback_file
41  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
42  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43  */
44 class assign_feedback_file extends assign_feedback_plugin {
46     /**
47      * Get the name of the file feedback plugin
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      * @return array
68      */
69     private function get_file_options() {
70         global $COURSE;
72         $fileoptions = array('subdirs'=>1,
73                                 'maxbytes'=>$COURSE->maxbytes,
74                                 'accepted_types'=>'*',
75                                 'return_types'=>FILE_INTERNAL);
76         return $fileoptions;
77     }
79     /**
80      * Copy all the files from one file area to another
81      *
82      * @param file_storage $fs - The source context id
83      * @param int $fromcontextid - The source context id
84      * @param string $fromcomponent - The source component
85      * @param string $fromfilearea - The source filearea
86      * @param int $fromitemid - The source item id
87      * @param int $tocontextid - The destination context id
88      * @param string $tocomponent - The destination component
89      * @param string $tofilearea - The destination filearea
90      * @param int $toitemid - The destination item id
91      * @return boolean
92      */
93     private function copy_area_files(file_storage $fs,
94                                      $fromcontextid,
95                                      $fromcomponent,
96                                      $fromfilearea,
97                                      $fromitemid,
98                                      $tocontextid,
99                                      $tocomponent,
100                                      $tofilearea,
101                                      $toitemid) {
103         $newfilerecord = new stdClass();
104         $newfilerecord->contextid = $tocontextid;
105         $newfilerecord->component = $tocomponent;
106         $newfilerecord->filearea = $tofilearea;
107         $newfilerecord->itemid = $toitemid;
109         if ($files = $fs->get_area_files($fromcontextid, $fromcomponent, $fromfilearea, $fromitemid)) {
110             foreach ($files as $file) {
111                 if ($file->is_directory() and $file->get_filepath() === '/') {
112                     // We need a way to mark the age of each draft area.
113                     // By not copying the root dir we force it to be created automatically with current timestamp.
114                     continue;
115                 }
116                 $newfile = $fs->create_file_from_storedfile($newfilerecord, $file);
117             }
118         }
119         return true;
120     }
122     /**
123      * Get form elements for grading form
124      *
125      * @param stdClass $grade
126      * @param MoodleQuickForm $mform
127      * @param stdClass $data
128      * @param int $userid The userid we are currently grading
129      * @return bool true if elements were added to the form
130      */
131     public function get_form_elements_for_user($grade, MoodleQuickForm $mform, stdClass $data, $userid) {
133         $fileoptions = $this->get_file_options();
134         $gradeid = $grade ? $grade->id : 0;
135         $elementname = 'files_' . $userid;
137         $data = file_prepare_standard_filemanager($data,
138                                                   $elementname,
139                                                   $fileoptions,
140                                                   $this->assignment->get_context(),
141                                                   'assignfeedback_file',
142                                                   ASSIGNFEEDBACK_FILE_FILEAREA,
143                                                   $gradeid);
145         $mform->addElement('filemanager', $elementname . '_filemanager', '', null, $fileoptions);
147         return true;
148     }
150     /**
151      * Count the number of files
152      *
153      * @param int $gradeid
154      * @param string $area
155      * @return int
156      */
157     private function count_files($gradeid, $area) {
159         $fs = get_file_storage();
160         $files = $fs->get_area_files($this->assignment->get_context()->id, 'assignfeedback_file', $area, $gradeid, "id", false);
162         return count($files);
163     }
165     /**
166      * Update the number of files in the file area
167      *
168      * @param stdClass $grade The grade record
169      * @return bool - true if the value was saved
170      */
171     public function update_file_count($grade) {
172         global $DB;
174         $filefeedback = $this->get_file_feedback($grade->id);
175         if ($filefeedback) {
176             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
177             return $DB->update_record('assignfeedback_file', $filefeedback);
178         } else {
179             $filefeedback = new stdClass();
180             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
181             $filefeedback->grade = $grade->id;
182             $filefeedback->assignment = $this->assignment->get_instance()->id;
183             return $DB->insert_record('assignfeedback_file', $filefeedback) > 0;
184         }
185     }
187     /**
188      * Save the feedback files
189      *
190      * @param stdClass $grade
191      * @param stdClass $data
192      * @return bool
193      */
194     public function save(stdClass $grade, stdClass $data) {
195         $fileoptions = $this->get_file_options();
197         // The element name may have been for a different user.
198         foreach ($data as $key => $value) {
199             if (strpos($key, 'files_') === 0 && strpos($key, '_filemanager')) {
200                 $elementname = substr($key, 0, strpos($key, '_filemanager'));
201             }
202         }
204         $data = file_postupdate_standard_filemanager($data,
205                                                      $elementname,
206                                                      $fileoptions,
207                                                      $this->assignment->get_context(),
208                                                      'assignfeedback_file',
209                                                      ASSIGNFEEDBACK_FILE_FILEAREA,
210                                                      $grade->id);
212         return $this->update_file_count($grade);
213     }
215     /**
216      * Display the list of files  in the feedback status table
217      *
218      * @param stdClass $grade
219      * @param bool $showviewlink - Set to true to show a link to see the full list of files
220      * @return string
221      */
222     public function view_summary(stdClass $grade, & $showviewlink) {
223         $count = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
224         // show a view all link if the number of files is over this limit
225         $showviewlink = $count > ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES;
227         if ($count <= ASSIGNFEEDBACK_FILE_MAXSUMMARYFILES) {
228             return $this->assignment->render_area_files('assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);
229         } else {
230             return get_string('countfiles', 'assignfeedback_file', $count);
231         }
232     }
234     /**
235      * Display the list of files  in the feedback status table
236      * @param stdClass $grade
237      * @return string
238      */
239     public function view(stdClass $grade) {
240         return $this->assignment->render_area_files('assignfeedback_file', ASSIGNFEEDBACK_FILE_FILEAREA, $grade->id);
241     }
243     /**
244      * The assignment has been deleted - cleanup
245      *
246      * @return bool
247      */
248     public function delete_instance() {
249         global $DB;
250         // will throw exception on failure
251         $DB->delete_records('assignfeedback_file', array('assignment'=>$this->assignment->get_instance()->id));
253         return true;
254     }
256     /**
257      * Return true if there are no feedback files
258      * @param stdClass $grade
259      */
260     public function is_empty(stdClass $grade) {
261         return $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA) == 0;
262     }
264     /**
265      * Get file areas returns a list of areas this plugin stores files
266      * @return array - An array of fileareas (keys) and descriptions (values)
267      */
268     public function get_file_areas() {
269         return array(ASSIGNFEEDBACK_FILE_FILEAREA=>$this->get_name());
270     }
272     /**
273      * Return true if this plugin can upgrade an old Moodle 2.2 assignment of this type
274      * and version.
275      *
276      * @param string $type old assignment subtype
277      * @param int $version old assignment version
278      * @return bool True if upgrade is possible
279      */
280     public function can_upgrade($type, $version) {
282         if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
283             return true;
284         }
285         return false;
286     }
288     /**
289      * Upgrade the settings from the old assignment to the new plugin based one
290      *
291      * @param context $oldcontext - the context for the old assignment
292      * @param stdClass $oldassignment - the data for the old assignment
293      * @param string $log - can be appended to by the upgrade
294      * @return bool was it a success? (false will trigger a rollback)
295      */
296     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
297         // first upgrade settings (nothing to do)
298         return true;
299     }
301     /**
302      * Upgrade the feedback from the old assignment to the new one
303      *
304      * @param context $oldcontext - the database for the old assignment context
305      * @param stdClass $oldassignment The data record for the old assignment
306      * @param stdClass $oldsubmission The data record for the old submission
307      * @param stdClass $grade The data record for the new grade
308      * @param string $log Record upgrade messages in the log
309      * @return bool true or false - false will trigger a rollback
310      */
311     public function upgrade(context $oldcontext, stdClass $oldassignment, stdClass $oldsubmission, stdClass $grade, & $log) {
312         global $DB;
314         // now copy the area files
315         $this->assignment->copy_area_files_for_upgrade($oldcontext->id,
316                                                         'mod_assignment',
317                                                         'response',
318                                                         $oldsubmission->id,
319                                                         // New file area
320                                                         $this->assignment->get_context()->id,
321                                                         'assignfeedback_file',
322                                                         ASSIGNFEEDBACK_FILE_FILEAREA,
323                                                         $grade->id);
325         // now count them!
326         $filefeedback = new stdClass();
327         $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
328         $filefeedback->grade = $grade->id;
329         $filefeedback->assignment = $this->assignment->get_instance()->id;
330         if (!$DB->insert_record('assignfeedback_file', $filefeedback) > 0) {
331             $log .= get_string('couldnotconvertgrade', 'mod_assign', $grade->userid);
332             return false;
333         }
334         return true;
335     }
337     /**
338      * Return a list of the batch grading operations performed by this plugin
339      * This plugin supports batch upload files and upload zip
340      *
341      * @return array The list of batch grading operations
342      */
343     public function get_grading_batch_operations() {
344         return array('uploadfiles'=>get_string('uploadfiles', 'assignfeedback_file'));
345     }
347     /**
348      * Upload files and send them to multiple users
349      *
350      * @param array $users - An array of user ids
351      * @return string - The response html
352      */
353     public function view_batch_upload_files($users) {
354         global $CFG, $DB, $USER;
356         require_capability('mod/assign:grade', $this->assignment->get_context());
357         require_once($CFG->dirroot . '/mod/assign/feedback/file/batchuploadfilesform.php');
358         require_once($CFG->dirroot . '/mod/assign/renderable.php');
360         $formparams = array('cm'=>$this->assignment->get_course_module()->id,
361                             'users'=>$users,
362                             'context'=>$this->assignment->get_context());
364         $usershtml = '';
366         $usercount = 0;
367         foreach ($users as $userid) {
368             if ($usercount >= ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS) {
369                 $usershtml .= get_string('moreusers', 'assignfeedback_file', count($users) - ASSIGNFEEDBACK_FILE_MAXSUMMARYUSERS);
370                 break;
371             }
372             $user = $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
374             $usershtml .= $this->assignment->get_renderer()->render(new assign_user_summary($user,
375                                                                 $this->assignment->get_course()->id,
376                                                                 has_capability('moodle/site:viewfullnames',
377                                                                 $this->assignment->get_course_context()),
378                                                                 $this->assignment->is_blind_marking(),
379                                                                 $this->assignment->get_uniqueid_for_user($user->id)));
380             $usercount += 1;
381         }
383         $formparams['usershtml'] = $usershtml;
385         $mform = new assignfeedback_file_batch_upload_files_form(null, $formparams);
387         if ($mform->is_cancelled()) {
388             redirect(new moodle_url('view.php',
389                                     array('id'=>$this->assignment->get_course_module()->id,
390                                           'action'=>'grading')));
391             return;
392         } else if ($data = $mform->get_data()) {
393             // Copy the files from the draft area to a temporary import area.
394             $data = file_postupdate_standard_filemanager($data,
395                                                          'files',
396                                                          $this->get_file_options(),
397                                                          $this->assignment->get_context(),
398                                                          'assignfeedback_file',
399                                                          ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
400                                                          $USER->id);
401             $fs = get_file_storage();
403             // Now copy each of these files to the users feedback file area.
404             foreach ($users as $userid) {
405                 $grade = $this->assignment->get_user_grade($userid, true);
407                 $this->copy_area_files($fs,
408                                        $this->assignment->get_context()->id,
409                                        'assignfeedback_file',
410                                        ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
411                                        $USER->id,
412                                        $this->assignment->get_context()->id,
413                                        'assignfeedback_file',
414                                        ASSIGNFEEDBACK_FILE_FILEAREA,
415                                        $grade->id);
417                 $filefeedback = $this->get_file_feedback($grade->id);
418                 if ($filefeedback) {
419                     $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
420                     $DB->update_record('assignfeedback_file', $filefeedback);
421                 } else {
422                     $filefeedback = new stdClass();
423                     $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
424                     $filefeedback->grade = $grade->id;
425                     $filefeedback->assignment = $this->assignment->get_instance()->id;
426                     $DB->insert_record('assignfeedback_file', $filefeedback);
427                 }
428             }
430             // Now delete the temporary import area.
431             $fs->delete_area_files($this->assignment->get_context()->id,
432                                    'assignfeedback_file',
433                                    ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
434                                    $USER->id);
436             redirect(new moodle_url('view.php',
437                                     array('id'=>$this->assignment->get_course_module()->id,
438                                           'action'=>'grading')));
439             return;
440         } else {
442             $o = '';
443             $o .= $this->assignment->get_renderer()->render(new assign_header($this->assignment->get_instance(),
444                                                           $this->assignment->get_context(),
445                                                           false,
446                                                           $this->assignment->get_course_module()->id,
447                                                           get_string('batchuploadfiles', 'assignfeedback_file')));
448             $o .= $this->assignment->get_renderer()->render(new assign_form('batchuploadfiles', $mform));
449             $o .= $this->assignment->get_renderer()->render_footer();
450         }
452         return $o;
453     }
455     /**
456      * User has chosen a custom grading batch operation and selected some users
457      *
458      * @param string $action - The chosen action
459      * @param array $users - An array of user ids
460      * @return string - The response html
461      */
462     public function grading_batch_operation($action, $users) {
464         if ($action == 'uploadfiles') {
465             return $this->view_batch_upload_files($users);
466         }
467         return '';
468     }
470     /**
471      * View the upload zip form
472      *
473      * @return string - The html response
474      */
475     public function view_upload_zip() {
476         global $CFG, $USER;
478         require_capability('mod/assign:grade', $this->assignment->get_context());
479         require_once($CFG->dirroot . '/mod/assign/feedback/file/uploadzipform.php');
480         require_once($CFG->dirroot . '/mod/assign/feedback/file/importziplib.php');
481         require_once($CFG->dirroot . '/mod/assign/feedback/file/importzipform.php');
483         $mform = new assignfeedback_file_upload_zip_form(null,
484                                                           array('context'=>$this->assignment->get_context(),
485                                                                 'cm'=>$this->assignment->get_course_module()->id));
487         $o = '';
489         $confirm = optional_param('confirm', 0, PARAM_BOOL);
490         $renderer = $this->assignment->get_renderer();
491         // Delete any existing files.
492         $importer = new assignfeedback_file_zip_importer();
493         $contextid = $this->assignment->get_context()->id;
495         if ($mform->is_cancelled()) {
496             $importer->delete_import_files($contextid);
497             redirect(new moodle_url('view.php',
498                                     array('id'=>$this->assignment->get_course_module()->id,
499                                           'action'=>'grading')));
500             return;
501         } else if ($confirm) {
502             $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
504             $mform = new assignfeedback_file_import_zip_form(null, $params);
505             if ($mform->is_cancelled()) {
506                 $importer->delete_import_files($contextid);
507                 redirect(new moodle_url('view.php',
508                                         array('id'=>$this->assignment->get_course_module()->id,
509                                           'action'=>'grading')));
510                 return;
511             }
513             $o .= $importer->import_zip_files($this->assignment, $this);
514             $importer->delete_import_files($contextid);
515         } else if (($data = $mform->get_data()) &&
516                    ($zipfile = $mform->save_stored_file('feedbackzip',
517                                                         $contextid,
518                                                         'assignfeedback_file',
519                                                         ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
520                                                         $USER->id,
521                                                         '/',
522                                                         'import.zip',
523                                                         true))) {
525             $importer->extract_files_from_zip($zipfile, $contextid);
527             $params = array('assignment'=>$this->assignment, 'importer'=>$importer);
529             $mform = new assignfeedback_file_import_zip_form(null, $params);
531             $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
532                                                             $this->assignment->get_context(),
533                                                             false,
534                                                             $this->assignment->get_course_module()->id,
535                                                             get_string('confirmuploadzip', 'assignfeedback_file')));
536             $o .= $renderer->render(new assign_form('confirmimportzip', $mform));
537             $o .= $renderer->render_footer();
539         } else {
541             $o .= $renderer->render(new assign_header($this->assignment->get_instance(),
542                                                             $this->assignment->get_context(),
543                                                             false,
544                                                             $this->assignment->get_course_module()->id,
545                                                             get_string('uploadzip', 'assignfeedback_file')));
546             $o .= $renderer->render(new assign_form('uploadfeedbackzip', $mform));
547             $o .= $renderer->render_footer();
548         }
550         return $o;
551     }
553     /**
554      * Called by the assignment module when someone chooses something from the grading navigation or batch operations list
555      *
556      * @param string $action - The page to view
557      * @return string - The html response
558      */
559     public function view_page($action) {
560         if ($action == 'uploadfiles') {
561             $users = required_param('selectedusers', PARAM_TEXT);
562             return $this->view_batch_upload_files(explode(',', $users));
563         }
564         if ($action == 'uploadzip') {
565             return $this->view_upload_zip();
566         }
568         return '';
569     }
571     /**
572      * Return a list of the grading actions performed by this plugin
573      * This plugin supports upload zip
574      *
575      * @return array The list of grading actions
576      */
577     public function get_grading_actions() {
578         return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
579     }