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
 * Classes for converting files between different file formats.
19
 *
20
 * @package    core_files
21
 * @copyright  2017 Andrew Nicols <andrew@nicols.co.uk>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
namespace core_files;
25
 
26
defined('MOODLE_INTERNAL') || die();
27
 
28
use stored_file;
29
 
30
/**
31
 * Class representing a conversion currently in progress.
32
 *
33
 * @package    core_files
34
 * @copyright  2017 Andrew Nicols <andrew@nicols.co.uk>
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class conversion extends \core\persistent {
38
 
39
    /**
40
     * Status value representing a conversion waiting to start.
41
     */
42
    const STATUS_PENDING = 0;
43
 
44
    /**
45
     * Status value representing a conversion in progress.
46
     */
47
    const STATUS_IN_PROGRESS = 1;
48
 
49
    /**
50
     * Status value representing a successful conversion.
51
     */
52
    const STATUS_COMPLETE = 2;
53
 
54
    /**
55
     * Status value representing a failed conversion.
56
     */
57
    const STATUS_FAILED = -1;
58
 
59
    /**
60
     * Table name for this persistent.
61
     */
62
    const TABLE = 'file_conversion';
63
 
64
    /**
65
     * Define properties.
66
     *
67
     * @return array
68
     */
69
    protected static function define_properties() {
70
        return array(
71
            'sourcefileid' => [
72
                'type' => PARAM_INT,
73
            ],
74
            'targetformat' => [
75
                'type' => PARAM_ALPHANUMEXT,
76
            ],
77
            'status' => [
78
                'type' => PARAM_INT,
79
                'choices' => [
80
                    self::STATUS_PENDING,
81
                    self::STATUS_IN_PROGRESS,
82
                    self::STATUS_COMPLETE,
83
                    self::STATUS_FAILED,
84
                ],
85
                'default' => self::STATUS_PENDING,
86
            ],
87
            'statusmessage' => [
88
                'type' => PARAM_RAW,
89
                'null' => NULL_ALLOWED,
90
                'default' => null,
91
            ],
92
            'converter' => [
93
                'type' => PARAM_RAW,
94
                'null' => NULL_ALLOWED,
95
                'default' => null,
96
            ],
97
            'destfileid' => [
98
                'type' => PARAM_INT,
99
                'null' => NULL_ALLOWED,
100
                'default' => null,
101
            ],
102
            'data' => [
103
                'type' => PARAM_RAW,
104
                'null' => NULL_ALLOWED,
105
                'default' => null,
106
            ],
107
        );
108
    }
109
 
110
    /**
111
     * Fetch all conversions relating to the specified file.
112
     *
113
     * Only conversions which have a valid file are returned.
114
     *
115
     * @param   stored_file $file The source file being converted
116
     * @param   string $format The targetforamt to filter to
117
     * @return  conversion[]
118
     */
119
    public static function get_conversions_for_file(stored_file $file, $format) {
120
        global $DB;
121
        $instances = [];
122
 
123
        // Conversion records are intended for tracking a conversion in progress or recently completed.
124
        // The record is removed periodically, but the destination file is not.
125
        // We need to fetch all conversion records which match the source file and target, and also all source and
126
        // destination files which do not have a conversion record.
127
        $sqlfields = self::get_sql_fields('c', 'conversion');
128
 
129
        // Fetch actual conversions which relate to the specified source file, and have a matching conversion record,
130
        // and either have a valid destination file which still exists, or do not have a destination file at all.
131
        $sql = "SELECT {$sqlfields}
132
                  FROM {" . self::TABLE . "} c
133
                  JOIN {files} conversionsourcefile ON conversionsourcefile.id = c.sourcefileid
134
             LEFT JOIN {files} conversiondestfile ON conversiondestfile.id = c.destfileid
135
                 WHERE conversionsourcefile.contenthash = :ccontenthash
136
                       AND c.targetformat = :cformat
137
                       AND (c.destfileid IS NULL OR conversiondestfile.id IS NOT NULL)";
138
 
139
        // Fetch a empty conversion record for each source/destination combination that we find to match where the
140
        // destination file is in the correct filearea/filepath/filename combination to meet the requirements.
141
        // This ensures that existing conversions are used where possible, even if there is no 'conversion' record for
142
        // them.
143
        $sql .= "
144
            UNION ALL
145
                SELECT
146
                    NULL AS conversionid,
147
                    orphanedsourcefile.id AS conversionsourcefileid,
148
                    :oformat AS conversiontargetformat,
149
                    2 AS conversionstatus,
150
                    NULL AS conversionstatusmessage,
151
                    NULL AS conversionconverter,
152
                    orphaneddestfile.id AS conversiondestfileid,
153
                    NULL AS conversiondata,
154
 
155
 
156
 
157
                FROM {files} orphanedsourcefile
158
                INNER JOIN {files} orphaneddestfile ON (
159
                        orphaneddestfile.filename = orphanedsourcefile.contenthash
160
                    AND orphaneddestfile.component = 'core'
161
                    AND orphaneddestfile.filearea = 'documentconversion'
162
                    AND orphaneddestfile.filepath = :ofilepath
163
                )
164
                LEFT JOIN {" . self::TABLE . "} orphanedconversion ON orphanedconversion.destfileid = orphaneddestfile.id
165
                WHERE
166
                    orphanedconversion.id IS NULL
167
                AND
168
                    orphanedsourcefile.id = :osourcefileid
169
                ";
170
        $records = $DB->get_records_sql($sql, [
171
            'ccontenthash' => $file->get_contenthash(),
172
            'osourcefileid' => $file->get_id(),
173
            'ofilepath' => "/{$format}/",
174
            'cformat' => $format,
175
            'oformat' => $format,
176
        ]);
177
 
178
        foreach ($records as $record) {
179
            $data = self::extract_record($record, 'conversion');
180
            $newrecord = new static(0, $data);
181
            $instances[] = $newrecord;
182
        }
183
 
184
        return $instances;
185
    }
186
 
187
    /**
188
     * Remove all old conversion records.
189
     */
190
    public static function remove_old_conversion_records() {
191
        global $DB;
192
 
193
        $DB->delete_records_select(self::TABLE, 'timemodified <= :weekagosecs', [
194
            'weekagosecs' => time() - WEEKSECS,
195
        ]);
196
    }
197
 
198
    /**
199
     * Remove orphan records.
200
     *
201
     * Records are considered orphans when their source file not longer exists.
202
     * In this scenario we do not want to keep the converted file any longer,
203
     * in particular to be compliant with privacy laws.
204
     */
205
    public static function remove_orphan_records() {
206
        global $DB;
207
 
208
        $sql = "
209
            SELECT c.id
210
              FROM {" . self::TABLE . "} c
211
         LEFT JOIN {files} f
212
                ON f.id = c.sourcefileid
213
             WHERE f.id IS NULL";
214
        $ids = $DB->get_fieldset_sql($sql, []);
215
 
216
        if (empty($ids)) {
217
            return;
218
        }
219
 
220
        list($insql, $inparams) = $DB->get_in_or_equal($ids, SQL_PARAMS_NAMED);
221
        $DB->delete_records_select(self::TABLE, "id $insql", $inparams);
222
    }
223
 
224
    /**
225
     * Set the source file id for the conversion.
226
     *
227
     * @param   stored_file $file The file to convert
228
     * @return  $this
229
     */
230
    public function set_sourcefile(stored_file $file) {
231
        $this->raw_set('sourcefileid', $file->get_id());
232
 
233
        return $this;
234
    }
235
 
236
    /**
237
     * Fetch the source file.
238
     *
239
     * @return  stored_file|false The source file
240
     */
241
    public function get_sourcefile() {
242
        $fs = get_file_storage();
243
 
244
        return $fs->get_file_by_id($this->get('sourcefileid'));
245
    }
246
 
247
    /**
248
     * Set the destination file for this conversion.
249
     *
250
     * @param   string $filepath The path to the converted file
251
     * @return  $this
252
     */
253
    public function store_destfile_from_path($filepath) {
254
        if ($record = $this->get_file_record()) {
255
            $fs = get_file_storage();
256
            $existing = $fs->get_file(
257
                $record['contextid'],
258
                $record['component'],
259
                $record['filearea'],
260
                $record['itemid'],
261
                $record['filepath'],
262
                $record['filename']
263
            );
264
            if ($existing) {
265
                $existing->delete();
266
            }
267
            $file = $fs->create_file_from_pathname($record, $filepath);
268
 
269
            $this->raw_set('destfileid', $file->get_id());
270
        }
271
 
272
        return $this;
273
    }
274
 
275
    /**
276
     * Set the destination file for this conversion.
277
     *
278
     * @param   string $content The content of the converted file
279
     * @return  $this
280
     */
281
    public function store_destfile_from_string($content) {
282
        if ($record = $this->get_file_record()) {
283
            $fs = get_file_storage();
284
            $existing = $fs->get_file(
285
                $record['contextid'],
286
                $record['component'],
287
                $record['filearea'],
288
                $record['itemid'],
289
                $record['filepath'],
290
                $record['filename']
291
            );
292
            if ($existing) {
293
                $existing->delete();
294
            }
295
            $file = $fs->create_file_from_string($record, $content);
296
 
297
            $this->raw_set('destfileid', $file->get_id());
298
        }
299
 
300
        return $this;
301
    }
302
 
303
    /**
304
     * Get the destination file.
305
     *
306
     * @return  stored_file|bool Destination file
307
     */
308
    public function get_destfile() {
309
        $fs = get_file_storage();
310
 
311
        return $fs->get_file_by_id($this->get('destfileid'));
312
    }
313
 
314
    /**
315
     * Helper to ensure that the returned status is always an int.
316
     *
317
     * @return  int status
318
     */
319
    protected function get_status() {
320
        return (int) $this->raw_get('status');
321
    }
322
 
323
    /**
324
     * Get an instance of the current converter.
325
     *
326
     * @return  converter_interface|false current converter instance
327
     */
328
    public function get_converter_instance() {
329
        $currentconverter = $this->get('converter');
330
 
331
        if ($currentconverter && class_exists($currentconverter)) {
332
            return new $currentconverter();
333
        } else {
334
            return false;
335
        }
336
    }
337
 
338
    /**
339
     * Transform data into a storable format.
340
     *
341
     * @param   \stdClass $data The data to be stored
342
     * @return  $this
343
     */
344
    protected function set_data($data) {
345
        $this->raw_set('data', json_encode($data));
346
 
347
        return $this;
348
    }
349
 
350
    /**
351
     * Transform data into a storable format.
352
     *
353
     * @return  \stdClass The stored data
354
     */
355
    protected function get_data() {
356
        $data = $this->raw_get('data');
357
 
358
        if (!empty($data)) {
359
            return json_decode($data);
360
        }
361
 
362
        return (object) [];
363
    }
364
 
365
    /**
366
     * Return the file record base for use in the files table.
367
     *
368
     * @return  array|bool
369
     */
370
    protected function get_file_record() {
371
        $file = $this->get_sourcefile();
372
 
373
        if (!$file) {
374
            // If the source file was removed before we completed, we must return early.
375
            return false;
376
        }
377
 
378
        return [
379
            'contextid' => \context_system::instance()->id,
380
            'component' => 'core',
381
            'filearea'  => 'documentconversion',
382
            'itemid'    => 0,
383
            'filepath'  => "/" . $this->get('targetformat') . "/",
384
            'filename'  => $file->get_contenthash(),
385
        ];
386
    }
387
}