Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
43 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
 * Internal API of local listcoursefiles.
19
 *
20
 * @package    local_listcoursefiles
21
 * @copyright  2017 Martin Gauk (@innoCampus, TU Berlin)
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace local_listcoursefiles;
26
 
27
/**
28
 * Class course_files
29
 * @package local_listcoursefiles
30
 */
31
class course_files {
32
    /** @var int maximum number of files per page. */
33
    const MAX_FILES = 500;
34
 
35
    /**
36
     * @var \context
37
     */
38
    protected $context;
39
 
40
    /**
41
     * @var int
42
     */
43
    protected $filescount = -1;
44
 
45
    /**
46
     * @var array
47
     */
48
    protected $components = null;
49
 
50
    /**
51
     * @var array
52
     */
53
    protected $filelist = null;
54
 
55
    /**
56
     * @var string
57
     */
58
    protected $filtercomponent;
59
 
60
    /**
61
     * @var string
62
     */
63
    protected $filterfiletype;
64
 
65
    /**
66
     * @var \course_modinfo
67
     */
68
    protected $coursemodinfo;
69
 
70
    /**
71
     * @var int
72
     */
73
    protected $courseid;
74
 
75
    /**
76
     * course_files constructor.
77
     * @param integer $courseid
78
     * @param \context $context
79
     * @param string $component
80
     * @param string $filetype
81
     * @throws \moodle_exception
82
     */
83
    public function __construct($courseid, \context $context, $component, $filetype) {
84
        $this->courseid = $courseid;
85
        $this->context = $context;
86
        $this->filtercomponent = $component;
87
        $this->filterfiletype = $filetype;
88
        $this->coursemodinfo = get_fast_modinfo($courseid);
89
    }
90
 
91
    /**
92
     * Get course id.
93
     *
94
     * @return int
95
     */
96
    public function get_course_id(): int {
97
        return $this->courseid;
98
    }
99
 
100
    /**
101
     * Get filter component name.
102
     *
103
     * @return string
104
     */
105
    public function get_filter_component(): string {
106
        return $this->filtercomponent;
107
    }
108
 
109
    /**
110
     * Get filter file type name.
111
     *
112
     * @return string
113
     */
114
    public function get_filter_file_type(): string {
115
        return $this->filterfiletype;
116
    }
117
 
118
    /**
119
     * Retrieve the files within a course/context.
120
     *
121
     * @param int $offset
122
     * @param int $limit
123
     * @return array
124
     */
125
    public function get_file_list($offset, $limit) {
126
        global $DB;
127
 
128
        if ($this->filelist !== null) {
129
            return $this->filelist;
130
        }
131
 
132
        $availcomponents = $this->get_components();
133
        $sqlwhere = '';
134
        $sqlwherecomponent = '';
135
        if ($this->filtercomponent === 'all_wo_submissions') {
136
            $sqlwhere .= 'AND f.component NOT LIKE :component';
137
            $sqlwherecomponent = 'assign%';
138
        } else if ($this->filtercomponent !== 'all' && isset($availcomponents[$this->filtercomponent])) {
139
            $sqlwhere .= 'AND f.component LIKE :component';
140
            $sqlwherecomponent = $this->filtercomponent;
141
        }
142
 
143
        if ($this->filterfiletype === 'other') {
144
            $sqlwhere .= ' AND ' . $this->get_sql_mimetype(array_keys(mimetypes::get_mime_types()), false);
145
        } else if (isset(mimetypes::get_mime_types()[$this->filterfiletype])) {
146
            $sqlwhere .= ' AND ' . $this->get_sql_mimetype($this->filterfiletype, true);
147
        }
148
 
149
        $usernameselect = implode(', ', array_map(function($field) {
150
            return 'u.' . $field;
151
        }, \core_user\fields::get_name_fields()));
152
 
153
        $sql = "FROM {files} f
154
           LEFT JOIN {context} c ON (c.id = f.contextid)
155
           LEFT JOIN {user} u ON (u.id = f.userid)
156
               WHERE f.filename NOT LIKE '.'
157
                     AND (c.path LIKE :path OR c.id = :cid) $sqlwhere";
158
 
159
        $sqlselectfiles = "SELECT f.*, c.contextlevel, c.instanceid, $usernameselect $sql
160
                         ORDER BY f.component, f.filename";
161
 
162
        $params = [
163
            'path' => $this->context->path . '/%',
164
            'cid' => $this->context->id,
165
            'component' => $sqlwherecomponent,
166
        ];
167
 
168
        $this->filelist = $DB->get_records_sql($sqlselectfiles, $params, $offset, $limit);
169
 
170
        // Determine number of all files.
171
        if (count($this->filelist) < $limit) {
172
            $this->filescount = count($this->filelist) + $offset;
173
        } else {
174
            $sqlcount = "SELECT COUNT(*) $sql";
175
            $this->filescount = $DB->count_records_sql($sqlcount, $params);
176
        }
177
 
178
        return $this->filelist;
179
    }
180
 
181
    /**
182
     * Creates an SQL snippet
183
     *
184
     * @param mixed $types
185
     * @param boolean $in
186
     * @return string
187
     */
188
    protected function get_sql_mimetype($types, $in) {
189
        if (is_array($types)) {
190
            $list = [];
191
            foreach ($types as $type) {
192
                $list = array_merge($list, mimetypes::get_mime_types()[$type]);
193
            }
194
        } else {
195
            $list = &mimetypes::get_mime_types()[$types];
196
        }
197
 
198
        if ($in) {
199
            $first = "(f.mimetype LIKE '";
200
            $glue = "' OR f.mimetype LIKE '";
201
        } else {
202
            $first = "(f.mimetype NOT LIKE '";
203
            $glue = "' AND f.mimetype NOT LIKE '";
204
        }
205
 
206
        return $first . implode($glue, $list) . "')";
207
    }
208
 
209
    /**
210
     * Returns the number of files in a component and with a specific file type.
211
     * May only be called after get_file_list.
212
     */
213
    public function get_file_list_total_size() {
214
        return $this->filescount;
215
    }
216
 
217
    /**
218
     * Get all available components with files.
219
     * @return array
220
     */
221
    public function get_components() {
222
        global $DB;
223
 
224
        if ($this->components !== null) {
225
            return $this->components;
226
        }
227
 
228
        $sql = "SELECT f.component
229
                  FROM {files} f
230
             LEFT JOIN {context} c ON (c.id = f.contextid)
231
                 WHERE f.filename NOT LIKE '.'
232
                       AND (c.path LIKE :path OR c.id = :cid)
233
              GROUP BY f.component";
234
 
235
        $params = ['path' => $this->context->path . '/%', 'cid' => $this->context->id];
236
        $ret = $DB->get_fieldset_sql($sql, $params);
237
 
238
        $this->components = [];
239
        foreach ($ret as $r) {
240
            $this->components[$r] = self::get_component_translation($r);
241
        }
242
 
243
        asort($this->components, SORT_STRING | SORT_FLAG_CASE);
244
        $componentsall = [
245
            'all' => \get_string('all_files', 'local_listcoursefiles'),
246
            'all_wo_submissions' => \get_string('all_wo_submissions', 'local_listcoursefiles'),
247
        ];
248
        $this->components = $componentsall + $this->components;
249
 
250
        return $this->components;
251
    }
252
 
253
    /**
254
     * Change the license of multiple files.
255
     *
256
     * @param array $fileids keys are the file IDs
257
     * @param string $license shortname of the license
258
     * @throws \moodle_exception
259
     */
260
    public function set_files_license($fileids, $license) {
261
        global $DB;
262
 
263
        $licenses = licences::get_available_licenses();
264
        if (!isset($licenses[$license])) {
265
            throw new \moodle_exception('invalid_license', 'local_listcoursefiles');
266
        }
267
 
268
        if (count($fileids) > self::MAX_FILES) {
269
            throw new \moodle_exception('too_many_files', 'local_listcoursefiles');
270
        }
271
 
272
        if (count($fileids) == 0) {
273
            return;
274
        }
275
 
276
        // Check if the given files really belong to the context.
277
        list($sqlin, $paramfids) = $DB->get_in_or_equal(array_keys($fileids), SQL_PARAMS_QM);
278
        $sql = "SELECT f.id, f.contextid, c.path
279
                  FROM {files} f
280
                  JOIN {context} c ON (c.id = f.contextid)
281
                 WHERE f.id $sqlin";
282
        $res = $DB->get_records_sql($sql, $paramfids);
283
 
284
        $checkedfileids = array_column($this->check_files_context($res), 'id');
285
        if (count($checkedfileids) == 0) {
286
            return;
287
        }
288
 
289
        list($sqlin, $paramfids) = $DB->get_in_or_equal($checkedfileids, SQL_PARAMS_QM);
290
        $transaction = $DB->start_delegated_transaction();
291
        $sql = "UPDATE {files} SET license = ? WHERE id $sqlin";
292
        $DB->execute($sql, array_merge([$license], $paramfids));
293
 
294
        foreach ($checkedfileids as $fid) {
295
            $event = event\license_changed::create([
296
                'context' => $this->context,
297
                'objectid' => $fid,
298
                'other' => ['license' => $license],
299
            ]);
300
            $event->trigger();
301
        }
302
        $transaction->allow_commit();
303
    }
304
 
305
    /**
306
     * Check given files whether they belong to the context.
307
     *
308
     * The file objects need to have the contextid and the context path.
309
     *
310
     * @param array $files array of stdClass as retrieved from the files and context table
311
     * @return array file ids that belong to the context
312
     */
313
    protected function check_files_context(&$files) {
314
        $thiscontextpath = $this->context->path . '/';
315
        $thiscontextpathlen = strlen($thiscontextpath);
316
        $thiscontextid = $this->context->id;
317
        $checkedfiles = [];
318
        foreach ($files as &$f) {
319
            if ($f->contextid == $thiscontextid || substr($f->path, 0, $thiscontextpathlen) === $thiscontextpath) {
320
                $checkedfiles[] = $f;
321
            }
322
        }
323
 
324
        return $checkedfiles;
325
    }
326
 
327
    /**
328
     * Download a zip file of the files with the given ids.
329
     *
330
     * This function does not return if the zip archive could be created.
331
     *
332
     * @param array $fileids file ids
333
     * @throws \moodle_exception
334
     */
335
    public function download_files(&$fileids) {
336
        global $DB, $CFG;
337
 
338
        if (count($fileids) > self::MAX_FILES) {
339
            throw new \moodle_exception('too_many_files', 'local_listcoursefiles');
340
        }
341
 
342
        if (count($fileids) == 0) {
343
            throw new \moodle_exception('no_file_selected', 'local_listcoursefiles');
344
        }
345
 
346
        list($sqlin, $paramfids) = $DB->get_in_or_equal(array_keys($fileids), SQL_PARAMS_QM);
347
        $sql = "SELECT f.*, c.path, r.repositoryid, r.reference, r.lastsync AS referencelastsync
348
                  FROM {files} f
349
             LEFT JOIN {context} c ON (c.id = f.contextid)
350
             LEFT JOIN {files_reference} r ON (f.referencefileid = r.id)
351
                 WHERE f.id $sqlin";
352
        $res = $DB->get_records_sql($sql, $paramfids);
353
 
354
        $checkedfiles = $this->check_files_context($res);
355
        $fs = get_file_storage();
356
        $filesforzipping = [];
357
        foreach ($checkedfiles as $file) {
358
            $fname = $this->download_get_unique_file_name($file->filename, $filesforzipping);
359
            $filesforzipping[$fname] = $fs->get_file_instance($file);
360
        }
361
 
362
        $filename = clean_filename($this->coursemodinfo->get_course()->fullname . '.zip');
363
        $tmpfile = tempnam($CFG->tempdir . '/', 'local_listcoursefiles');
364
        $zip = new \zip_packer();
365
        if ($zip->archive_to_pathname($filesforzipping, $tmpfile)) {
366
            send_temp_file($tmpfile, $filename);
367
        }
368
    }
369
 
370
    /**
371
     * Generate a unique file name for storage.
372
     *
373
     * If a file does already exist with $filename in $existingfiles as key,
374
     * a number in parentheses is appended to the file name.
375
     *
376
     * @param string $filename
377
     * @param array $existingfiles
378
     * @return string unique file name
379
     */
380
    protected function download_get_unique_file_name($filename, &$existingfiles) {
381
        $name = clean_filename($filename);
382
 
383
        $lastdot = strrpos($name, '.');
384
        if ($lastdot === false) {
385
            $filename = $name;
386
            $extension = '';
387
        } else {
388
            $filename = substr($name, 0, $lastdot);
389
            $extension = substr($name, $lastdot);
390
        }
391
 
392
        $i = 1;
393
        while (isset($existingfiles[$name])) {
394
            $name = $filename . '(' . $i++ . ')' . $extension;
395
        }
396
 
397
        return $name;
398
    }
399
 
400
    /**
401
     * Collate an array of available file types
402
     *
403
     * @return array
404
     * @throws \coding_exception
405
     */
406
    public static function get_file_types() {
407
        $types = ['all' => \get_string('filetype_all', 'local_listcoursefiles')];
408
        foreach (array_keys(mimetypes::get_mime_types()) as $type) {
409
            $types[$type] = \get_string('filetype_' . $type, 'local_listcoursefiles');
410
        }
411
        $types['other'] = \get_string('filetype_other', 'local_listcoursefiles');
412
        return $types;
413
    }
414
 
415
    /**
416
     * Try to get the name of the file component in the user's lang.
417
     *
418
     * @param string $name
419
     * @return \lang_string|string
420
     * @throws \coding_exception
421
     */
422
    public static function get_component_translation($name) {
423
        if (get_string_manager()->string_exists('pluginname', $name)) {
424
            return get_string('pluginname', $name);
425
        } else if (get_string_manager()->string_exists($name, '')) {
426
            return get_string($name, '');
427
        }
428
        return $name;
429
    }
430
}