MDL-42919 Assign: Remove no-label form hacks
[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', $this->get_name(), null, $fileoptions);
148         return true;
149     }
151     /**
152      * Count the number of files.
153      *
154      * @param int $gradeid
155      * @param string $area
156      * @return int
157      */
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',
163                                      $area,
164                                      $gradeid,
165                                      'id',
166                                      false);
168         return count($files);
169     }
171     /**
172      * Update the number of files in the file area.
173      *
174      * @param stdClass $grade The grade record
175      * @return bool - true if the value was saved
176      */
177     public function update_file_count($grade) {
178         global $DB;
180         $filefeedback = $this->get_file_feedback($grade->id);
181         if ($filefeedback) {
182             $filefeedback->numfiles = $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA);
183             return $DB->update_record('assignfeedback_file', $filefeedback);
184         } else {
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;
190         }
191     }
193     /**
194      * Save the feedback files.
195      *
196      * @param stdClass $grade
197      * @param stdClass $data
198      * @return bool
199      */
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'));
207             }
208         }
210         $data = file_postupdate_standard_filemanager($data,
211                                                      $elementname,
212                                                      $fileoptions,
213                                                      $this->assignment->get_context(),
214                                                      'assignfeedback_file',
215                                                      ASSIGNFEEDBACK_FILE_FILEAREA,
216                                                      $grade->id);
218         return $this->update_file_count($grade);
219     }
221     /**
222      * Display the list of files in the feedback status table.
223      *
224      * @param stdClass $grade
225      * @param bool $showviewlink - Set to true to show a link to see the full list of files
226      * @return string
227      */
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,
238                                                         $grade->id);
239         } else {
240             return get_string('countfiles', 'assignfeedback_file', $count);
241         }
242     }
244     /**
245      * Display the list of files in the feedback status table.
246      *
247      * @param stdClass $grade
248      * @return string
249      */
250     public function view(stdClass $grade) {
251         return $this->assignment->render_area_files('assignfeedback_file',
252                                                     ASSIGNFEEDBACK_FILE_FILEAREA,
253                                                     $grade->id);
254     }
256     /**
257      * The assignment has been deleted - cleanup.
258      *
259      * @return bool
260      */
261     public function delete_instance() {
262         global $DB;
263         // Will throw exception on failure.
264         $DB->delete_records('assignfeedback_file',
265                             array('assignment'=>$this->assignment->get_instance()->id));
267         return true;
268     }
270     /**
271      * Return true if there are no feedback files.
272      *
273      * @param stdClass $grade
274      */
275     public function is_empty(stdClass $grade) {
276         return $this->count_files($grade->id, ASSIGNFEEDBACK_FILE_FILEAREA) == 0;
277     }
279     /**
280      * Get file areas returns a list of areas this plugin stores files.
281      *
282      * @return array - An array of fileareas (keys) and descriptions (values)
283      */
284     public function get_file_areas() {
285         return array(ASSIGNFEEDBACK_FILE_FILEAREA=>$this->get_name());
286     }
288     /**
289      * Return true if this plugin can upgrade an old Moodle 2.2 assignment of this type
290      * and version.
291      *
292      * @param string $type old assignment subtype
293      * @param int $version old assignment version
294      * @return bool True if upgrade is possible
295      */
296     public function can_upgrade($type, $version) {
297         if (($type == 'upload' || $type == 'uploadsingle') && $version >= 2011112900) {
298             return true;
299         }
300         return false;
301     }
303     /**
304      * Upgrade the settings from the old assignment to the new plugin based one.
305      *
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)
310      */
311     public function upgrade_settings(context $oldcontext, stdClass $oldassignment, & $log) {
312         // First upgrade settings (nothing to do).
313         return true;
314     }
316     /**
317      * Upgrade the feedback from the old assignment to the new one.
318      *
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
325      */
326     public function upgrade(context $oldcontext,
327                             stdClass $oldassignment,
328                             stdClass $oldsubmission,
329                             stdClass $grade,
330                             & $log) {
331         global $DB;
333         // Now copy the area files.
334         $this->assignment->copy_area_files_for_upgrade($oldcontext->id,
335                                                         'mod_assignment',
336                                                         'response',
337                                                         $oldsubmission->id,
338                                                         $this->assignment->get_context()->id,
339                                                         'assignfeedback_file',
340                                                         ASSIGNFEEDBACK_FILE_FILEAREA,
341                                                         $grade->id);
343         // Now count them!
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);
350             return false;
351         }
352         return true;
353     }
355     /**
356      * Return a list of the batch grading operations performed by this plugin.
357      * This plugin supports batch upload files and upload zip.
358      *
359      * @return array The list of batch grading operations
360      */
361     public function get_grading_batch_operations() {
362         return array('uploadfiles'=>get_string('uploadfiles', 'assignfeedback_file'));
363     }
365     /**
366      * Upload files and send them to multiple users.
367      *
368      * @param array $users - An array of user ids
369      * @return string - The response html
370      */
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,
379                             'users'=>$users,
380                             'context'=>$this->assignment->get_context());
382         $usershtml = '';
384         $usercount = 0;
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);
389                 break;
390             }
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);
401             $usercount += 1;
402         }
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')));
412             return;
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,
416                                                          'files',
417                                                          $this->get_file_options(),
418                                                          $this->assignment->get_context(),
419                                                          'assignfeedback_file',
420                                                          ASSIGNFEEDBACK_FILE_BATCH_FILEAREA,
421                                                          $USER->id);
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,
433                                        $USER->id,
434                                        $this->assignment->get_context()->id,
435                                        'assignfeedback_file',
436                                        ASSIGNFEEDBACK_FILE_FILEAREA,
437                                        $grade->id);
439                 $filefeedback = $this->get_file_feedback($grade->id);
440                 if ($filefeedback) {
441                     $filefeedback->numfiles = $this->count_files($grade->id,
442                                                                  ASSIGNFEEDBACK_FILE_FILEAREA);
443                     $DB->update_record('assignfeedback_file', $filefeedback);
444                 } else {
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);
451                 }
452             }
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,
458                                    $USER->id);
460             redirect(new moodle_url('view.php',
461                                     array('id'=>$this->assignment->get_course_module()->id,
462                                           'action'=>'grading')));
463             return;
464         } else {
466             $header = new assign_header($this->assignment->get_instance(),
467                                         $this->assignment->get_context(),
468                                         false,
469                                         $this->assignment->get_course_module()->id,
470                                         get_string('batchuploadfiles', 'assignfeedback_file'));
471             $o = '';
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();
475         }
477         return $o;
478     }
480     /**
481      * User has chosen a custom grading batch operation and selected some users.
482      *
483      * @param string $action - The chosen action
484      * @param array $users - An array of user ids
485      * @return string - The response html
486      */
487     public function grading_batch_operation($action, $users) {
489         if ($action == 'uploadfiles') {
490             return $this->view_batch_upload_files($users);
491         }
492         return '';
493     }
495     /**
496      * View the upload zip form.
497      *
498      * @return string - The html response
499      */
500     public function view_upload_zip() {
501         global $CFG, $USER;
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);
512         $o = '';
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);
526             redirect($url);
527             return;
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);
537                 redirect($url);
538                 return;
539             }
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',
545                                                         $contextid,
546                                                         'assignfeedback_file',
547                                                         ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
548                                                         $USER->id,
549                                                         '/',
550                                                         'import.zip',
551                                                         true))) {
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(),
561                                         false,
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();
568         } else {
570             $header = new assign_header($this->assignment->get_instance(),
571                                         $this->assignment->get_context(),
572                                         false,
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();
578         }
580         return $o;
581     }
583     /**
584      * Called by the assignment module when someone chooses something from the
585      * grading navigation or batch operations list.
586      *
587      * @param string $action - The page to view
588      * @return string - The html response
589      */
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));
594         }
595         if ($action == 'uploadzip') {
596             return $this->view_upload_zip();
597         }
599         return '';
600     }
602     /**
603      * Return a list of the grading actions performed by this plugin.
604      * This plugin supports upload zip.
605      *
606      * @return array The list of grading actions
607      */
608     public function get_grading_actions() {
609         return array('uploadzip'=>get_string('uploadzip', 'assignfeedback_file'));
610     }
612     /**
613      * Return a description of external params suitable for uploading a feedback file from a webservice.
614      *
615      * @return external_description|null
616      */
617     public function get_external_parameters() {
618         return array(
619             'files_filemanager' => new external_value(
620                 PARAM_INT,
621                 'The id of a draft area containing files for this feedback.'
622             )
623         );
624     }