c3a0441ea7a905d66a03b0d7b2c4e874e32d8468
[moodle.git] / mod / assign / feedback / file / importziplib.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 /**
29  * library class for importing feedback files from a zip
30  *
31  * @package   assignfeedback_file
32  * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
33  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class assignfeedback_file_zip_importer {
37     /**
38      * Is this filename valid (contains a unique participant ID) for import?
39      *
40      * @param assign $assignment - The assignment instance
41      * @param stored_file $fileinfo - The fileinfo
42      * @param array $participants - A list of valid participants for this module indexed by unique_id
43      * @param stdClass $user - Set to the user that matches by participant id
44      * @param assign_plugin $plugin - Set to the plugin that exported the file
45      * @param string $filename - Set to truncated filename (prefix stripped)
46      * @return true If the participant Id can be extracted and this is a valid user
47      */
48     public function is_valid_filename_for_import($assignment, $fileinfo, $participants, & $user, & $plugin, & $filename) {
49         if ($fileinfo->is_directory()) {
50             return false;
51         }
53         // Ignore hidden files.
54         if (strpos($fileinfo->get_filename(), '.') === 0) {
55             return false;
56         }
57         // Ignore hidden files.
58         if (strpos($fileinfo->get_filename(), '~') === 0) {
59             return false;
60         }
62         $info = explode('_', $fileinfo->get_filename(), 5);
64         if (count($info) < 5) {
65             return false;
66         }
68         $participantid = $info[1];
69         $filename = $info[4];
70         $plugin = $assignment->get_plugin_by_type($info[2], $info[3]);
72         if (!is_numeric($participantid)) {
73             return false;
74         }
76         if (!$plugin) {
77             return false;
78         }
80         // Convert to int.
81         $participantid += 0;
83         if (empty($participants[$participantid])) {
84             return false;
85         }
87         $user = $participants[$participantid];
88         return true;
89     }
91     /**
92      * Does this file exist in any of the current files supported by this plugin for this user?
93      *
94      * @param assign $assignment - The assignment instance
95      * @param stdClass $user The user matching this uploaded file
96      * @param assign_plugin $plugin The matching plugin from the filename
97      * @param string $filename The parsed filename from the zip
98      * @param stored_file $fileinfo The info about the extracted file from the zip
99      * @return bool - True if the file has been modified or is new
100      */
101     public function is_file_modified($assignment, $user, $plugin, $filename, $fileinfo) {
102         $sg = null;
104         if ($plugin->get_subtype() == 'assignsubmission') {
105             $sg = $assignment->get_user_submission($user->id, false);
106         } else if ($plugin->get_subtype() == 'assignfeedback') {
107             $sg = $assignment->get_user_grade($user->id, false);
108         } else {
109             return false;
110         }
112         if (!$sg) {
113             return true;
114         }
115         foreach ($plugin->get_files($sg) as $pluginfilename => $file) {
116             if ($pluginfilename == $filename) {
117                 // Extract the file and compare hashes.
118                 $contenthash = '';
119                 if (is_array($file)) {
120                     $content = reset($file);
121                     $contenthash = sha1($content);
122                 } else {
123                     $contenthash = $file->get_contenthash();
124                 }
125                 if ($contenthash != $fileinfo->get_contenthash()) {
126                     return true;
127                 } else {
128                     return false;
129                 }
130             }
131         }
132         return true;
133     }
135     /**
136      * Delete all temp files used when importing a zip
137      *
138      * @param int $contextid - The context id of this assignment instance
139      * @return bool true if all files were deleted
140      */
141     public function delete_import_files($contextid) {
142         global $USER;
144         $fs = get_file_storage();
146         return $fs->delete_area_files($contextid,
147                                       'assignfeedback_files',
148                                       ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
149                                       $USER->id);
150     }
152     /**
153      * Extract the uploaded zip to a temporary import area for this user
154      *
155      * @param stored_file $zipfile The uploaded file
156      * @param int $contextid The context for this assignment
157      * @return bool - True if the files were unpacked
158      */
159     public function extract_files_from_zip($zipfile, $contextid) {
160         global $USER;
162         $feedbackfilesupdated = 0;
163         $feedbackfilesadded = 0;
164         $userswithnewfeedback = array();
166         // Unzipping a large zip file is memory intensive.
167         raise_memory_limit(MEMORY_EXTRA);
169         $packer = get_file_packer('application/zip');
170         @set_time_limit(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
172         return $packer->extract_to_storage($zipfile,
173                                     $contextid,
174                                     'assignfeedback_file',
175                                     ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
176                                     $USER->id,
177                                     'import');
179     }
181     /**
182      * Get the list of files extracted from the uploaded zip
183      *
184      * @param int $contextid
185      * @return array of stored_files
186      */
187     public function get_import_files($contextid) {
188         global $USER;
190         $fs = get_file_storage();
191         $files = $fs->get_directory_files($contextid,
192                                           'assignfeedback_file',
193                                           ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
194                                           $USER->id,
195                                           '/import/');
197         return $files;
198     }
200     /**
201      * Process an uploaded zip file
202      *
203      * @param assign $assignment - The assignment instance
204      * @param assign_feedback_file $fileplugin - The file feedback plugin
205      * @return string - The html response
206      */
207     public function import_zip_files($assignment, $fileplugin) {
208         global $USER, $CFG, $PAGE, $DB;
210         @set_time_limit(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
211         $packer = get_file_packer('application/zip');
213         $feedbackfilesupdated = 0;
214         $feedbackfilesadded = 0;
215         $userswithnewfeedback = array();
216         $contextid = $assignment->get_context()->id;
218         $fs = get_file_storage();
219         $files = $fs->get_directory_files($contextid,
220                                           'assignfeedback_file',
221                                           ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
222                                           $USER->id,
223                                           '/import/');
225         $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
226         $allusers = $assignment->list_participants($currentgroup, false);
227         $participants = array();
228         foreach ($allusers as $user) {
229             $participants[$assignment->get_uniqueid_for_user($user->id)] = $user;
230         }
232         foreach ($files as $unzippedfile) {
233             // Set the timeout for unzipping each file.
234             $user = null;
235             $plugin = null;
236             $filename = '';
238             if ($this->is_valid_filename_for_import($assignment, $unzippedfile, $participants, $user, $plugin, $filename)) {
239                 if ($this->is_file_modified($assignment, $user, $plugin, $filename, $unzippedfile)) {
240                     $grade = $assignment->get_user_grade($user->id, true);
242                     if ($oldfile = $fs->get_file($contextid,
243                                                  'assignfeedback_file',
244                                                  ASSIGNFEEDBACK_FILE_FILEAREA,
245                                                  $grade->id,
246                                                  '/',
247                                                  $filename)) {
248                         // Update existing feedback file.
249                         $oldfile->replace_content_with($unzippedfile);
250                         $feedbackfilesupdated++;
251                     } else {
252                         // Create a new feedback file.
253                         $newfilerecord = new stdClass();
254                         $newfilerecord->contextid = $contextid;
255                         $newfilerecord->component = 'assignfeedback_file';
256                         $newfilerecord->filearea = ASSIGNFEEDBACK_FILE_FILEAREA;
257                         $newfilerecord->filename = $filename;
258                         $newfilerecord->filepath = '/';
259                         $newfilerecord->itemid = $grade->id;
260                         $fs->create_file_from_storedfile($newfilerecord, $unzippedfile);
261                         $feedbackfilesadded++;
262                     }
263                     $userswithnewfeedback[$user->id] = 1;
265                     // Update the number of feedback files for this user.
266                     $fileplugin->update_file_count($grade);
268                     // Update the last modified time on the grade which will trigger student notifications.
269                     $assignment->notify_grade_modified($grade);
270                 }
271             }
272         }
274         require_once($CFG->dirroot . '/mod/assign/feedback/file/renderable.php');
275         $importsummary = new assignfeedback_file_import_summary($assignment->get_course_module()->id,
276                                                             count($userswithnewfeedback),
277                                                             $feedbackfilesadded,
278                                                             $feedbackfilesupdated);
280         $assignrenderer = $assignment->get_renderer();
281         $renderer = $PAGE->get_renderer('assignfeedback_file');
283         $o = '';
285         $o .= $assignrenderer->render(new assign_header($assignment->get_instance(),
286                                                         $assignment->get_context(),
287                                                         false,
288                                                         $assignment->get_course_module()->id,
289                                                         get_string('uploadzipsummary', 'assignfeedback_file')));
291         $o .= $renderer->render($importsummary);
293         $o .= $assignrenderer->render_footer();
294         return $o;
295     }