See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
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/>. 16 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 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 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 { 36 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 } 52 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 } 61 62 // Break the full path-name into path parts. 63 $pathparts = explode('/', $fileinfo->get_filepath() . $fileinfo->get_filename()); 64 65 while (!empty($pathparts)) { 66 // Get the next path part and break it up by underscores. 67 $pathpart = array_shift($pathparts); 68 $info = explode('_', $pathpart, 5); 69 70 if (count($info) < 5) { 71 continue; 72 } 73 74 // Check the participant id. 75 $participantid = $info[1]; 76 77 if (!is_numeric($participantid)) { 78 continue; 79 } 80 81 // Convert to int. 82 $participantid += 0; 83 84 if (empty($participants[$participantid])) { 85 continue; 86 } 87 88 // Set user, which is by reference, so is used by the calling script. 89 $user = $participants[$participantid]; 90 91 // Set the plugin. This by reference, and is used by the calling script. 92 $plugin = $assignment->get_plugin_by_type($info[2], $info[3]); 93 94 if (!$plugin) { 95 continue; 96 } 97 98 // Take any remaining text in this part and put it back in the path parts array. 99 array_unshift($pathparts, $info[4]); 100 101 // Combine the remaining parts and set it as the filename. 102 // Note that filename is a 'by reference' variable, so we need to set it before returning. 103 $filename = implode('/', $pathparts); 104 105 return true; 106 } 107 108 return false; 109 } 110 111 /** 112 * Does this file exist in any of the current files supported by this plugin for this user? 113 * 114 * @param assign $assignment - The assignment instance 115 * @param stdClass $user The user matching this uploaded file 116 * @param assign_plugin $plugin The matching plugin from the filename 117 * @param string $filename The parsed filename from the zip 118 * @param stored_file $fileinfo The info about the extracted file from the zip 119 * @return bool - True if the file has been modified or is new 120 */ 121 public function is_file_modified($assignment, $user, $plugin, $filename, $fileinfo) { 122 $sg = null; 123 124 if ($plugin->get_subtype() == 'assignsubmission') { 125 $sg = $assignment->get_user_submission($user->id, false); 126 } else if ($plugin->get_subtype() == 'assignfeedback') { 127 $sg = $assignment->get_user_grade($user->id, false); 128 } else { 129 return false; 130 } 131 132 if (!$sg) { 133 return true; 134 } 135 foreach ($plugin->get_files($sg, $user) as $pluginfilename => $file) { 136 if ($pluginfilename == $filename) { 137 // Extract the file and compare hashes. 138 $contenthash = ''; 139 if (is_array($file)) { 140 $content = reset($file); 141 $contenthash = file_storage::hash_from_string($content); 142 } else { 143 $contenthash = $file->get_contenthash(); 144 } 145 if ($contenthash != $fileinfo->get_contenthash()) { 146 return true; 147 } else { 148 return false; 149 } 150 } 151 } 152 return true; 153 } 154 155 /** 156 * Delete all temp files used when importing a zip 157 * 158 * @param int $contextid - The context id of this assignment instance 159 * @return bool true if all files were deleted 160 */ 161 public function delete_import_files($contextid) { 162 global $USER; 163 164 $fs = get_file_storage(); 165 166 return $fs->delete_area_files($contextid, 167 'assignfeedback_file', 168 ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA, 169 $USER->id); 170 } 171 172 /** 173 * Extract the uploaded zip to a temporary import area for this user 174 * 175 * @param stored_file $zipfile The uploaded file 176 * @param int $contextid The context for this assignment 177 * @return bool - True if the files were unpacked 178 */ 179 public function extract_files_from_zip($zipfile, $contextid) { 180 global $USER; 181 182 $feedbackfilesupdated = 0; 183 $feedbackfilesadded = 0; 184 $userswithnewfeedback = array(); 185 186 // Unzipping a large zip file is memory intensive. 187 raise_memory_limit(MEMORY_EXTRA); 188 189 $packer = get_file_packer('application/zip'); 190 core_php_time_limit::raise(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME); 191 192 return $packer->extract_to_storage($zipfile, 193 $contextid, 194 'assignfeedback_file', 195 ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA, 196 $USER->id, 197 'import'); 198 199 } 200 201 /** 202 * Get the list of files extracted from the uploaded zip 203 * 204 * @param int $contextid 205 * @return array of stored_files 206 */ 207 public function get_import_files($contextid) { 208 global $USER; 209 210 $fs = get_file_storage(); 211 $files = $fs->get_directory_files($contextid, 212 'assignfeedback_file', 213 ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA, 214 $USER->id, 215 '/import/', true); // Get files recursive (all levels). 216 217 $keys = array_keys($files); 218 219 return $files; 220 } 221 222 /** 223 * Process an uploaded zip file 224 * 225 * @param assign $assignment - The assignment instance 226 * @param assign_feedback_file $fileplugin - The file feedback plugin 227 * @return string - The html response 228 */ 229 public function import_zip_files($assignment, $fileplugin) { 230 global $CFG, $PAGE, $DB; 231 232 core_php_time_limit::raise(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME); 233 $packer = get_file_packer('application/zip'); 234 235 $feedbackfilesupdated = 0; 236 $feedbackfilesadded = 0; 237 $userswithnewfeedback = array(); 238 $contextid = $assignment->get_context()->id; 239 240 $fs = get_file_storage(); 241 $files = $this->get_import_files($contextid); 242 243 $currentgroup = groups_get_activity_group($assignment->get_course_module(), true); 244 $allusers = $assignment->list_participants($currentgroup, false); 245 $participants = array(); 246 foreach ($allusers as $user) { 247 $participants[$assignment->get_uniqueid_for_user($user->id)] = $user; 248 } 249 250 foreach ($files as $unzippedfile) { 251 // Set the timeout for unzipping each file. 252 $user = null; 253 $plugin = null; 254 $filename = ''; 255 256 if ($this->is_valid_filename_for_import($assignment, $unzippedfile, $participants, $user, $plugin, $filename)) { 257 if ($this->is_file_modified($assignment, $user, $plugin, $filename, $unzippedfile)) { 258 $grade = $assignment->get_user_grade($user->id, true); 259 260 // In 3.1 the default download structure of the submission files changed so that each student had their own 261 // separate folder, the files were not renamed and the folder structure was kept. It is possible that 262 // a user downloaded the submission files in 3.0 (or earlier) and edited the zip to add feedback or 263 // changed the behavior back to the previous format, the following code means that we will still support the 264 // old file structure. For more information please see - MDL-52489 / MDL-56022. 265 $path = pathinfo($filename); 266 if ($path['dirname'] == '.') { // Student submissions are not in separate folders. 267 $basename = $filename; 268 $dirname = "/"; 269 $dirnamewslash = "/"; 270 } else { 271 $basename = $path['basename']; 272 $dirname = $path['dirname']; 273 $dirnamewslash = $dirname . "/"; 274 } 275 276 if ($oldfile = $fs->get_file($contextid, 277 'assignfeedback_file', 278 ASSIGNFEEDBACK_FILE_FILEAREA, 279 $grade->id, 280 $dirname, 281 $basename)) { 282 // Update existing feedback file. 283 $oldfile->replace_file_with($unzippedfile); 284 $feedbackfilesupdated++; 285 } else { 286 // Create a new feedback file. 287 $newfilerecord = new stdClass(); 288 $newfilerecord->contextid = $contextid; 289 $newfilerecord->component = 'assignfeedback_file'; 290 $newfilerecord->filearea = ASSIGNFEEDBACK_FILE_FILEAREA; 291 $newfilerecord->filename = $basename; 292 $newfilerecord->filepath = $dirnamewslash; 293 $newfilerecord->itemid = $grade->id; 294 $fs->create_file_from_storedfile($newfilerecord, $unzippedfile); 295 $feedbackfilesadded++; 296 } 297 $userswithnewfeedback[$user->id] = 1; 298 299 // Update the number of feedback files for this user. 300 $fileplugin->update_file_count($grade); 301 302 // Update the last modified time on the grade which will trigger student notifications. 303 $assignment->notify_grade_modified($grade); 304 } 305 } 306 } 307 308 require_once($CFG->dirroot . '/mod/assign/feedback/file/renderable.php'); 309 $importsummary = new assignfeedback_file_import_summary($assignment->get_course_module()->id, 310 count($userswithnewfeedback), 311 $feedbackfilesadded, 312 $feedbackfilesupdated); 313 314 $assignrenderer = $assignment->get_renderer(); 315 $renderer = $PAGE->get_renderer('assignfeedback_file'); 316 317 $o = ''; 318 319 $o .= $assignrenderer->render(new assign_header($assignment->get_instance(), 320 $assignment->get_context(), 321 false, 322 $assignment->get_course_module()->id, 323 get_string('uploadzipsummary', 'assignfeedback_file'))); 324 325 $o .= $renderer->render($importsummary); 326 327 $o .= $assignrenderer->render_footer(); 328 return $o; 329 } 330 331 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body