Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
use mod_assign\output\assign_header;
29
 
30
/**
31
 * library class for importing feedback files from a zip
32
 *
33
 * @package   assignfeedback_file
34
 * @copyright 2012 NetSpot {@link http://www.netspot.com.au}
35
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class assignfeedback_file_zip_importer {
38
 
39
    /**
40
     * Is this filename valid (contains a unique participant ID) for import?
41
     *
42
     * @param assign $assignment - The assignment instance
43
     * @param stored_file $fileinfo - The fileinfo
44
     * @param array $participants - A list of valid participants for this module indexed by unique_id or group id.
45
     * @param array $users - Set to array with the user(s) that matches by participant id
46
     * @param assign_plugin $plugin - Set to the plugin that exported the file
47
     * @param string $filename - Set to truncated filename (prefix stripped)
48
     * @return bool If the participant Id can be extracted and this is a valid user
49
     */
50
    public function is_valid_filename_for_import($assignment, $fileinfo, $participants, & $users, & $plugin, & $filename) {
51
        if ($fileinfo->is_directory()) {
52
            return false;
53
        }
54
 
55
        // Ignore hidden files.
56
        if (strpos($fileinfo->get_filename(), '.') === 0) {
57
            return false;
58
        }
59
        // Ignore hidden files.
60
        if (strpos($fileinfo->get_filename(), '~') === 0) {
61
            return false;
62
        }
63
 
64
        // Break the full path-name into path parts.
65
        $pathparts = explode('/', $fileinfo->get_filepath() . $fileinfo->get_filename());
66
 
67
        while (!empty($pathparts)) {
68
            // Get the next path part and break it up by underscores.
69
            $pathpart = array_shift($pathparts);
70
            $info = explode('_', $pathpart, 5);
71
 
72
            // Expected format for the directory names in $pathpart is fullname_userid_plugintype_pluginname (as created by zip
73
            // export in Moodle >= 4.1) resp. fullname_userid_plugintype_pluginname_ (as created by earlier versions). We ensure
74
            // compatibility with both ways here.
75
            if (count($info) < 4) {
76
                continue;
77
            }
78
 
79
            // Check the participant id.
80
            $participantid = $info[1];
81
 
82
            if (!is_numeric($participantid)) {
83
                continue;
84
            }
85
 
86
            // Convert to int.
87
            $participantid += 0;
88
 
89
            if (empty($participants[$participantid])) {
90
                continue;
91
            }
92
 
93
            // Set user, which is by reference, so is used by the calling script.
94
            $users = $participants[$participantid];
95
 
96
            // Set the plugin. This by reference, and is used by the calling script.
97
            $plugin = $assignment->get_plugin_by_type($info[2], $info[3]);
98
 
99
            if (!$plugin) {
100
                continue;
101
            }
102
 
103
            // To get clean path names, we need to have at least an empty entry for $info[4].
104
            if (count($info) == 4) {
105
                $info[4] = '';
106
            }
107
            // Take any remaining text in this part and put it back in the path parts array.
108
            array_unshift($pathparts, $info[4]);
109
 
110
            // Combine the remaining parts and set it as the filename.
111
            // Note that filename is a 'by reference' variable, so we need to set it before returning.
112
            $filename = implode('/', $pathparts);
113
 
114
            return true;
115
        }
116
 
117
        return false;
118
    }
119
 
120
    /**
121
     * Does this file exist in any of the current files supported by this plugin for this user?
122
     *
123
     * @param assign $assignment - The assignment instance
124
     * @param array|stdClass $users The user(s) matching this uploaded file
125
     * @param assign_plugin $plugin The matching plugin from the filename
126
     * @param string $filename The parsed filename from the zip
127
     * @param stored_file $fileinfo The info about the extracted file from the zip
128
     * @return bool - True if the file has been modified or is new
129
     */
130
    public function is_file_modified($assignment, $users, $plugin, $filename, $fileinfo) {
131
        $sg = null;
132
 
133
        if (is_array($users)) {
134
            $user = $users[0];
135
        } else {
136
            $user = $users;
137
        }
138
 
139
        if ($plugin->get_subtype() == 'assignsubmission') {
140
            if ($assignment->get_instance()->teamsubmission) {
141
                $sg = $assignment->get_group_submission($user->id, 0, false);
142
            } else {
143
                $sg = $assignment->get_user_submission($user->id, false);
144
            }
145
        } else if ($plugin->get_subtype() == 'assignfeedback') {
146
            $sg = $assignment->get_user_grade($user->id, false);
147
        } else {
148
            return false;
149
        }
150
 
151
        if (!$sg) {
152
            return true;
153
        }
154
        foreach ($plugin->get_files($sg, $user) as $pluginfilename => $file) {
155
            if ($pluginfilename == $filename) {
156
                // Extract the file and compare hashes.
157
                $contenthash = '';
158
                if (is_array($file)) {
159
                    $content = reset($file);
160
                    $contenthash = file_storage::hash_from_string($content);
161
                } else {
162
                    $contenthash = $file->get_contenthash();
163
                }
164
                if ($contenthash != $fileinfo->get_contenthash()) {
165
                    return true;
166
                } else {
167
                    return false;
168
                }
169
            }
170
        }
171
        return true;
172
    }
173
 
174
    /**
175
     * Delete all temp files used when importing a zip
176
     *
177
     * @param int $contextid - The context id of this assignment instance
178
     * @return bool true if all files were deleted
179
     */
180
    public function delete_import_files($contextid) {
181
        global $USER;
182
 
183
        $fs = get_file_storage();
184
 
185
        return $fs->delete_area_files($contextid,
186
                                      'assignfeedback_file',
187
                                      ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
188
                                      $USER->id);
189
    }
190
 
191
    /**
192
     * Extract the uploaded zip to a temporary import area for this user
193
     *
194
     * @param stored_file $zipfile The uploaded file
195
     * @param int $contextid The context for this assignment
196
     * @return bool - True if the files were unpacked
197
     */
198
    public function extract_files_from_zip($zipfile, $contextid) {
199
        global $USER;
200
 
201
        $feedbackfilesupdated = 0;
202
        $feedbackfilesadded = 0;
203
        $userswithnewfeedback = array();
204
 
205
        // Unzipping a large zip file is memory intensive.
206
        raise_memory_limit(MEMORY_EXTRA);
207
 
208
        $packer = get_file_packer('application/zip');
209
        core_php_time_limit::raise(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
210
 
211
        return $packer->extract_to_storage($zipfile,
212
                                    $contextid,
213
                                    'assignfeedback_file',
214
                                    ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
215
                                    $USER->id,
216
                                    'import');
217
 
218
    }
219
 
220
    /**
221
     * Get the list of files extracted from the uploaded zip
222
     *
223
     * @param int $contextid
224
     * @return array of stored_files
225
     */
226
    public function get_import_files($contextid) {
227
        global $USER;
228
 
229
        $fs = get_file_storage();
230
        $files = $fs->get_directory_files($contextid,
231
                                          'assignfeedback_file',
232
                                          ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
233
                                          $USER->id,
234
                                          '/import/', true); // Get files recursive (all levels).
235
 
236
        $keys = array_keys($files);
237
 
238
        return $files;
239
    }
240
 
241
    /**
242
     * Returns a mapping from unique user / group ids in folder names to array of moodle users.
243
     *
244
     * @param assign $assignment  - The assignment instance
245
     * @return array the mapping.
246
     */
247
    public function get_participant_mapping(assign $assignment): array {
248
        $currentgroup = groups_get_activity_group($assignment->get_course_module(), true);
249
        $allusers = $assignment->list_participants($currentgroup, false);
250
        $participants = array();
251
        foreach ($allusers as $user) {
252
            if ($assignment->get_instance()->teamsubmission) {
253
                $group = $assignment->get_submission_group($user->id);
254
                if (!$group) {
255
                    continue;
256
                }
257
                if (!isset($participants[$group->id])) {
258
                    $participants[$group->id] = [];
259
                }
260
                $participants[$group->id][] = $user;
261
            } else {
262
                $participants[$assignment->get_uniqueid_for_user($user->id)] = [$user];
263
            }
264
        }
265
        return $participants;
266
    }
267
 
268
    /**
269
     * Process an uploaded zip file
270
     *
271
     * @param assign $assignment - The assignment instance
272
     * @param assign_feedback_file $fileplugin - The file feedback plugin
273
     * @return string - The html response
274
     */
275
    public function import_zip_files($assignment, $fileplugin) {
276
        global $CFG, $PAGE, $DB;
277
 
278
        core_php_time_limit::raise(ASSIGNFEEDBACK_FILE_MAXFILEUNZIPTIME);
279
        $packer = get_file_packer('application/zip');
280
 
281
        $feedbackfilesupdated = 0;
282
        $feedbackfilesadded = 0;
283
        $userswithnewfeedback = array();
284
        $contextid = $assignment->get_context()->id;
285
 
286
        $fs = get_file_storage();
287
        $files = $this->get_import_files($contextid);
288
 
289
        $participants = $this->get_participant_mapping($assignment);
290
 
291
        foreach ($files as $unzippedfile) {
292
            $users = null;
293
            $plugin = null;
294
            $filename = '';
295
 
296
            if ($this->is_valid_filename_for_import($assignment, $unzippedfile, $participants, $users, $plugin, $filename)) {
297
                if ($this->is_file_modified($assignment, $users, $plugin, $filename, $unzippedfile)) {
298
                    foreach ($users as $user) {
299
                        $grade = $assignment->get_user_grade($user->id, true);
300
 
301
                        // In 3.1 the default download structure of the submission files changed so that each student had their own
302
                        // separate folder, the files were not renamed and the folder structure was kept. It is possible that
303
                        // a user downloaded the submission files in 3.0 (or earlier) and edited the zip to add feedback or
304
                        // changed the behavior back to the previous format, the following code means that we will still support the
305
                        // old file structure. For more information please see - MDL-52489 / MDL-56022.
306
                        $path = pathinfo($filename);
307
                        if ($path['dirname'] == '.') { // Student submissions are not in separate folders.
308
                            $basename = $filename;
309
                            $dirname = "/";
310
                            $dirnamewslash = "/";
311
                        } else {
312
                            $basename = $path['basename'];
313
                            $dirname = $path['dirname'];
314
                            $dirnamewslash = $dirname . "/";
315
                        }
316
 
317
                        if ($oldfile = $fs->get_file($contextid,
318
                                                     'assignfeedback_file',
319
                                                     ASSIGNFEEDBACK_FILE_FILEAREA,
320
                                                     $grade->id,
321
                                                     $dirname,
322
                                                     $basename)) {
323
                            // Update existing feedback file.
324
                            $oldfile->replace_file_with($unzippedfile);
325
                            $feedbackfilesupdated++;
326
                        } else {
327
                            // Create a new feedback file.
328
                            $newfilerecord = new stdClass();
329
                            $newfilerecord->contextid = $contextid;
330
                            $newfilerecord->component = 'assignfeedback_file';
331
                            $newfilerecord->filearea = ASSIGNFEEDBACK_FILE_FILEAREA;
332
                            $newfilerecord->filename = $basename;
333
                            $newfilerecord->filepath = $dirnamewslash;
334
                            $newfilerecord->itemid = $grade->id;
335
                            $fs->create_file_from_storedfile($newfilerecord, $unzippedfile);
336
                            $feedbackfilesadded++;
337
                        }
338
                        $userswithnewfeedback[$user->id] = 1;
339
 
340
                        // Update the number of feedback files for this user.
341
                        $fileplugin->update_file_count($grade);
342
 
343
                        // Update the last modified time on the grade which will trigger student notifications.
344
                        $assignment->notify_grade_modified($grade);
345
                    }
346
                }
347
            }
348
        }
349
 
350
        require_once($CFG->dirroot . '/mod/assign/feedback/file/renderable.php');
351
        $importsummary = new assignfeedback_file_import_summary($assignment->get_course_module()->id,
352
                                                            count($userswithnewfeedback),
353
                                                            $feedbackfilesadded,
354
                                                            $feedbackfilesupdated);
355
 
356
        $assignrenderer = $assignment->get_renderer();
357
        $renderer = $PAGE->get_renderer('assignfeedback_file');
358
 
359
        $o = '';
360
 
361
        $o .= $assignrenderer->render(new assign_header($assignment->get_instance(),
362
                                                        $assignment->get_context(),
363
                                                        false,
364
                                                        $assignment->get_course_module()->id,
365
                                                        get_string('uploadzipsummary', 'assignfeedback_file')));
366
 
367
        $o .= $renderer->render($importsummary);
368
 
369
        $o .= $assignrenderer->render_footer();
370
        return $o;
371
    }
372
 
373
}