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
 * The mod_hvp file storage
19
 *
20
 * @package    mod_hvp
21
 * @copyright  2016 Joubel AS
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
namespace mod_hvp;
26
defined('MOODLE_INTERNAL') || die();
27
require_once($CFG->dirroot . '/mod/hvp/library/h5p-file-storage.interface.php');
28
 
29
/**
30
 * The mod_hvp file storage class.
31
 *
32
 * @package    mod_hvp
33
 * @since      Moodle 2.7
34
 * @copyright  2016 Joubel AS
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 *
37
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
38
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
39
 */
40
class file_storage implements \H5PFileStorage {
41
 
42
    /**
43
     * Store the library folder.
44
     *
45
     * @param array $library
46
     *  Library properties
47
     */
48
    // @codingStandardsIgnoreLine
49
    public function saveLibrary($library) {
50
        // Libraries are stored in a system context.
51
        $context = \context_system::instance();
52
        $options = array(
53
            'contextid' => $context->id,
54
            'component' => 'mod_hvp',
55
            'filearea' => 'libraries',
56
            'itemid' => 0,
57
            'filepath' => '/' . \H5PCore::libraryToFolderName($library) . '/',
58
        );
59
 
60
        // Remove any old existing library files.
61
        self::deleteFileTree($context->id, $options['filearea'], $options['filepath']);
62
 
63
        // Move library folder.
64
        self::readFileTree($library['uploadDirectory'], $options);
65
    }
66
 
67
    /**
68
     * Store the content folder.
69
     *
70
     * @param string $source
71
     *  Path on file system to content directory.
72
     * @param array $content
73
     *  Content properties
74
     */
75
    // @codingStandardsIgnoreLine
76
    public function saveContent($source, $content) {
77
        // Remove any old content.
78
        $this->deleteContent($content);
79
 
80
        // Contents are stored in a course context.
81
        $context = \context_module::instance($content['coursemodule']);
82
        $options = array(
83
            'contextid' => $context->id,
84
            'component' => 'mod_hvp',
85
            'filearea' => 'content',
86
            'itemid' => $content['id'],
87
            'filepath' => '/',
88
        );
89
 
90
        // Move content folder.
91
        self::readFileTree($source, $options);
92
    }
93
 
94
    /**
95
     * Remove content folder.
96
     *
97
     * @param array $content
98
     *  Content properties
99
     */
100
    // @codingStandardsIgnoreLine
101
    public function deleteContent($content) {
102
        $context = \context_module::instance($content['coursemodule']);
103
        self::deleteFileTree($context->id, 'content', '/', $content['id']);
104
    }
105
 
106
    /**
107
     * @inheritdoc
108
     */
109
    // @codingStandardsIgnoreLine
110
    public function cloneContent($id, $newid) {
111
        // Not implemented in Moodle.
112
    }
113
 
114
    /**
115
     * Get path to a new unique tmp folder.
116
     *
117
     * @return string Path
118
     */
119
    // @codingStandardsIgnoreLine
120
    public function getTmpPath() {
121
        global $CFG;
122
 
123
        return $CFG->tempdir . uniqid('/hvp-');
124
    }
125
 
126
    /**
127
     * Fetch content folder and save in target directory.
128
     *
129
     * @param int $id
130
     *  Content identifier
131
     * @param string $target
132
     *  Where the content folder will be saved
133
     */
134
    // @codingStandardsIgnoreLine
135
    public function exportContent($id, $target) {
136
        $cm = \get_coursemodule_from_instance('hvp', $id);
137
        $context = \context_module::instance($cm->id);
138
        self::exportFileTree($target, $context->id, 'content', '/', $id);
139
    }
140
 
141
    /**
142
     * Fetch library folder and save in target directory.
143
     *
144
     * @param array $library
145
     *  Library properties
146
     * @param string $target
147
     *  Where the library folder will be saved
148
     */
149
    // @codingStandardsIgnoreLine
150
    public function exportLibrary($library, $target) {
151
        $folder = \H5PCore::libraryToFolderName($library);
152
        $context = \context_system::instance();
153
        self::exportFileTree("{$target}/{$folder}", $context->id, 'libraries', "/{$folder}/");
154
    }
155
 
156
    /**
157
     * Save export in file system
158
     *
159
     * @param string $source
160
     *  Path on file system to temporary export file.
161
     * @param string $filename
162
     *  Name of export file.
163
     */
164
    // @codingStandardsIgnoreLine
165
    public function saveExport($source, $filename) {
166
        global $COURSE;
167
 
168
        // Remove old export.
169
        $this->deleteExport($filename);
170
 
171
        // Create record.
172
        $context = \context_course::instance($COURSE->id);
173
        $record = array(
174
            'contextid' => $context->id,
175
            'component' => 'mod_hvp',
176
            'filearea' => 'exports',
177
            'itemid' => 0,
178
            'filepath' => '/',
179
            'filename' => $filename
180
        );
181
 
182
        // Store new export.
183
        $fs = get_file_storage();
184
        $fs->create_file_from_pathname($record, $source);
185
    }
186
 
187
    /**
188
     * Get file object for given export file.
189
     *
190
     * @param string $filename
191
     * @return stdClass Moodle file object
192
     */
193
    // @codingStandardsIgnoreLine
194
    private function getExportFile($filename) {
195
        global $COURSE;
196
        $context = \context_course::instance($COURSE->id);
197
 
198
        // Check if file exists.
199
        $fs = get_file_storage();
200
        return $fs->get_file($context->id, 'mod_hvp', 'exports', 0, '/', $filename);
201
    }
202
 
203
    /**
204
     * Removes given export file
205
     *
206
     * @param string $filename
207
     */
208
    // @codingStandardsIgnoreLine
209
    public function deleteExport($filename) {
210
        $file = $this->getExportFile($filename);
211
        if ($file) {
212
            // Remove old export.
213
            $file->delete();
214
        }
215
    }
216
 
217
    /**
218
     * Check if the given export file exists
219
     *
220
     * @param string $filename
221
     * @return boolean
222
     */
223
    // @codingStandardsIgnoreLine
224
    public function hasExport($filename) {
225
        return !!$this->getExportFile($filename);
226
    }
227
 
228
    /**
229
     * Will concatenate all JavaScrips and Stylesheets into two files in order
230
     * to improve page performance.
231
     *
232
     * @param array $files
233
     *  A set of all the assets required for content to display
234
     * @param string $key
235
     *  Hashed key for cached asset
236
     */
237
    // @codingStandardsIgnoreLine
238
    public function cacheAssets(&$files, $key) {
239
        $context = \context_system::instance();
240
        $fs = get_file_storage();
241
 
242
        foreach ($files as $type => $assets) {
243
            if (empty($assets)) {
244
                continue;
245
            }
246
 
247
            $content = '';
248
            foreach ($assets as $asset) {
249
                // Find location of asset.
250
                $location = array();
251
                preg_match('/^\/(libraries|development)(.+\/)([^\/]+)$/', $asset->path, $location);
252
 
253
                // Locate file.
254
                $file = $fs->get_file($context->id, 'mod_hvp', $location[1], 0, $location[2], $location[3]);
255
 
256
                // Get file content and concatenate.
257
                if ($type === 'scripts') {
258
                    $content .= $file->get_content() . ";\n";
259
                } else {
260
                    // Rewrite relative URLs used inside stylesheets.
261
                    $content .= preg_replace_callback(
262
                            '/url\([\'"]?([^"\')]+)[\'"]?\)/i',
263
                            function ($matches) use ($location) {
264
                                if (preg_match("/^(data:|([a-z0-9]+:)?\/)/i", $matches[1]) === 1) {
265
                                    return $matches[0]; // Not relative, skip.
266
                                }
267
                                return 'url("../' . $location[1] . $location[2] . $matches[1] . '")';
268
                            },
269
                            $file->get_content()) . "\n";
270
                }
271
            }
272
 
273
            // Create new file for cached assets.
274
            $ext = ($type === 'scripts' ? 'js' : 'css');
275
            $fileinfo = array(
276
                'contextid' => $context->id,
277
                'component' => 'mod_hvp',
278
                'filearea' => 'cachedassets',
279
                'itemid' => 0,
280
                'filepath' => '/',
281
                'filename' => "{$key}.{$ext}"
282
            );
283
 
284
            // Store concatenated content.
285
            $fs->create_file_from_string($fileinfo, $content);
286
            $files[$type] = array((object) array(
287
                'path' => "/cachedassets/{$key}.{$ext}",
288
                'version' => ''
289
            ));
290
        }
291
    }
292
 
293
    /**
294
     * Will check if there are cache assets available for content.
295
     *
296
     * @param string $key
297
     *  Hashed key for cached asset
298
     * @return array
299
     */
300
    // @codingStandardsIgnoreLine
301
    public function getCachedAssets($key) {
302
        $context = \context_system::instance();
303
        $fs = get_file_storage();
304
 
305
        $files = array();
306
 
307
        $js = $fs->get_file($context->id, 'mod_hvp', 'cachedassets', 0, '/', "{$key}.js");
308
        if ($js) {
309
            $files['scripts'] = array((object) array(
310
                'path' => "/cachedassets/{$key}.js",
311
                'version' => ''
312
            ));
313
        }
314
 
315
        $css = $fs->get_file($context->id, 'mod_hvp', 'cachedassets', 0, '/', "{$key}.css");
316
        if ($css) {
317
            $files['styles'] = array((object) array(
318
                'path' => "/cachedassets/{$key}.css",
319
                'version' => ''
320
            ));
321
        }
322
 
323
        return empty($files) ? null : $files;
324
    }
325
 
326
    /**
327
     * Remove the aggregated cache files.
328
     *
329
     * @param array $keys
330
     *   The hash keys of removed files
331
     */
332
    // @codingStandardsIgnoreLine
333
    public function deleteCachedAssets($keys) {
334
        $context = \context_system::instance();
335
        $fs = get_file_storage();
336
 
337
        foreach ($keys as $hash) {
338
            foreach (array('js', 'css') as $type) {
339
                $cachedasset = $fs->get_file($context->id, 'mod_hvp', 'cachedassets', 0, '/', "{$hash}.{$type}");
340
                if ($cachedasset) {
341
                    $cachedasset->delete();
342
                }
343
            }
344
        }
345
    }
346
 
347
    /**
348
     * @inheritdoc
349
     */
350
    // @codingStandardsIgnoreLine
351
    public function getContent($filepath) {
352
        // Grab context and file storage.
353
        $context = \context_system::instance();
354
        $fs      = get_file_storage();
355
 
356
        // Find location of file.
357
        $location = [];
358
        preg_match('/^\/(libraries|development|cachedassets)(.*\/)([^\/]+)$/', $filepath, $location);
359
 
360
        // Locate file.
361
        $file = $fs->get_file($context->id, 'mod_hvp', $location[1], 0, $location[2], $location[3]);
362
        if (!$file) {
363
            throw new \file_serving_exception(
364
                'Could not retrieve the requested file, check your file permissions.'
365
            );
366
        }
367
 
368
        // Return content.
369
        return $file->get_content();
370
    }
371
 
372
    /**
373
     * Save files uploaded through the editor.
374
     *
375
     * @param \H5peditorFile $file
376
     * @param int $contentid
377
     * @param \stdClass $contextid Course Context ID
378
     *
379
     * @return int
380
     */
381
    // @codingStandardsIgnoreLine
382
    public function saveFile($file, $contentid, $contextid = null) {
383
        global $CFG;
384
 
385
        if ($contentid !== 0) {
386
            // Grab cm context.
387
            $cm = \get_coursemodule_from_instance('hvp', $contentid);
388
            $context = \context_module::instance($cm->id);
389
            $contextid = $context->id;
390
        } else if ($contextid === null) {
391
            // Check for context id in params.
392
            $contextid = optional_param('contextId', null, PARAM_INT);
393
            $context = \context::instance_by_id($contextid);
394
        }
395
 
396
        if (!$context) {
397
            \H5PCore::ajaxError(get_string('invalidcontext', 'error'));
398
            return;
399
        }
400
 
401
        $maxsize = get_max_upload_file_size($CFG->maxbytes);
402
        // Check size of each uploaded file and scan for viruses.
403
        foreach ($_FILES as $uploadedfile) {
404
            $filename = clean_param($uploadedfile['name'], PARAM_FILE);
405
 
406
            if (!has_capability('moodle/course:ignorefilesizelimits', $context)) {
407
                if ($uploadedfile['size'] > $maxsize) {
408
                    \H5PCore::ajaxError(get_string('maxbytesfile', 'error', ['file' => $filename, 'size' => display_size($maxsize)]));
409
                    return;
410
                }
411
            }
412
            \core\antivirus\manager::scan_file($uploadedfile['tmp_name'], $filename, true);
413
        }
414
 
415
        // Files not yet related to any activities are stored in a course context
416
        // These are temporary files and should not be part of backups.
417
 
418
        $record = array(
419
            'contextid' => $contextid,
420
            'component' => 'mod_hvp',
421
            'filearea' => $contentid === 0 ? 'editor' : 'content',
422
            'itemid' => $contentid,
423
            'filepath' => '/' . $file->getType() . 's/',
424
            'filename' => $file->getName()
425
        );
426
        $fs = get_file_storage();
427
        $storedfile = $fs->create_file_from_pathname($record, $_FILES['file']['tmp_name']);
428
 
429
        return $storedfile->get_id();
430
    }
431
 
432
    /**
433
     * Copy a file from another content or editor tmp dir.
434
     * Used when copy pasting content in H5P.
435
     *
436
     * @param string $file path + name
437
     * @param string|int $fromid Content ID or 'editor' string
438
     * @param stdClass $tocontent Target Content
439
     */
440
    // @codingStandardsIgnoreLine
441
    public function cloneContentFile($file, $fromid, $tocontent) {
442
 
443
        // Determine source file area and item id.
444
        if ($fromid === 'editor') {
445
            $sourcefilearea = 'editor';
446
            if (empty($tocontent->instance)) {
447
                $sourceitemid = \context_course::instance($tocontent->course);
448
            } else {
449
                $sourceitemid = \context_module::instance($tocontent->coursemodule);
450
            }
451
        } else {
452
            $sourcefilearea = 'content';
453
            $sourceitemid   = $fromid;
454
        };
455
 
456
        // Check to see if source exist.
457
        $sourcefile = $this->getFile($sourcefilearea, $sourceitemid, $file);
458
        if ($sourcefile === false) {
459
            return; // Nothing to copy from.
460
        }
461
 
462
        // Check to make sure source doesn't exist already.
463
        if ($this->getFile('content', $tocontent, $file) !== false) {
464
            return; // File exists, no need to copy.
465
        }
466
 
467
        // Grab context for CM.
468
        $context = \context_module::instance($tocontent->coursemodule);
469
 
470
        // Create new file record.
471
        $record = [
472
            'contextid' => $context->id,
473
            'component' => 'mod_hvp',
474
            'filearea'  => 'content',
475
            'itemid'    => $tocontent->id,
476
            'filepath'  => $this->getFilepath($file),
477
            'filename'  => $this->getFilename($file),
478
        ];
479
        $fs = get_file_storage();
480
        $fs->create_file_from_storedfile($record, $sourcefile);
481
    }
482
 
483
    /**
484
     * Checks to see if content has the given file.
485
     * Used when saving content.
486
     *
487
     * @param string $file path + name
488
     * @param stdClass $content
489
     * @return string|int File ID or null if not found
490
     */
491
    // @codingStandardsIgnoreLine
492
    public function getContentFile($file, $content) {
493
        $file = $this->getFile('content', $content, $file);
494
        return ($file === false ? null : $file->get_id());
495
    }
496
 
497
    /**
498
     * Remove content files that are no longer used.
499
     * Used when saving content.
500
     *
501
     * @param string $file path + name
502
     * @param stdClass $content
503
     */
504
    // @codingStandardsIgnoreLine
505
    public function removeContentFile($file, $content) {
506
        $file = $this->getFile('content', $content, $file);
507
        if ($file !== false) {
508
            $file->delete();
509
        }
510
    }
511
 
512
    /**
513
     * Copies files from tmp folder to Moodle storage.
514
     *
515
     * @param string $source
516
     *  Path to source directory
517
     * @param array $options
518
     *  For Moodle's file record
519
     * @throws \Exception Unable to copy
520
     */
521
    // @codingStandardsIgnoreLine
522
    private static function readFileTree($source, $options) {
523
        $dir = opendir($source);
524
        if ($dir === false) {
525
            trigger_error('Unable to open directory ' . $source, E_USER_WARNING);
526
            throw new \Exception('unabletocopy');
527
        }
528
 
529
        while (false !== ($file = readdir($dir))) {
530
            if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore') {
531
                if (is_dir($source . DIRECTORY_SEPARATOR . $file)) {
532
                    $suboptions = $options;
533
                    $suboptions['filepath'] .= $file . '/';
534
                    self::readFileTree($source . '/' . $file, $suboptions);
535
                } else {
536
                    $record = $options;
537
                    $record['filename'] = $file;
538
                    $fs = get_file_storage();
539
                    $fs->create_file_from_pathname($record, $source . '/' . $file);
540
                }
541
            }
542
        }
543
        closedir($dir);
544
    }
545
 
546
    /**
547
     * Copies files from Moodle storage to temporary folder.
548
     *
549
     * @param string $target
550
     *  Path to temporary folder
551
     * @param int $contextid
552
     *  Moodle context where the files are found
553
     * @param string $filearea
554
     *  Moodle file area
555
     * @param string $filepath
556
     *  Moodle file path
557
     * @param int $itemid
558
     *  Optional Moodle item ID
559
     */
560
    // @codingStandardsIgnoreLine
561
    private static function exportFileTree($target, $contextid, $filearea, $filepath, $itemid = 0) {
562
        // Make sure target folder exists.
563
        if (!file_exists($target)) {
564
            mkdir($target, 0777, true);
565
        }
566
 
567
        // Read source files.
568
        $fs = get_file_storage();
569
        $files = $fs->get_directory_files($contextid, 'mod_hvp', $filearea, $itemid, $filepath, true);
570
 
571
        foreach ($files as $file) {
572
            // Correct target path for file.
573
            $path = $target . str_replace($filepath, '/', $file->get_filepath());
574
 
575
            if ($file->is_directory()) {
576
                // Create directory.
577
                $path = rtrim($path, '/');
578
                if (!file_exists($path)) {
579
                    mkdir($path, 0777, true);
580
                }
581
            } else {
582
                // Copy file.
583
                $file->copy_content_to($path . $file->get_filename());
584
            }
585
        }
586
    }
587
 
588
    /**
589
     * Recursive removal of given filepath.
590
     *
591
     * @param int $contextid
592
     * @param string $filearea
593
     * @param string $filepath
594
     * @param int $itemid
595
     */
596
    // @codingStandardsIgnoreLine
597
    private static function deleteFileTree($contextid, $filearea, $filepath, $itemid = 0) {
598
        $fs = get_file_storage();
599
        if ($filepath === '/') {
600
            // Remove complete file area.
601
            $fs->delete_area_files($contextid, 'mod_hvp', $filearea, $itemid);
602
            return;
603
        }
604
 
605
        // Look up files and remove.
606
        $files = $fs->get_directory_files($contextid, 'mod_hvp', $filearea, $itemid, $filepath, true);
607
        foreach ($files as $file) {
608
            $file->delete();
609
        }
610
 
611
        // Remove root dir.
612
        $file = $fs->get_file($contextid, 'mod_hvp', $filearea, $itemid, $filepath, '.');
613
        if ($file) {
614
            $file->delete();
615
        }
616
    }
617
 
618
    /**
619
     * Help make it easy to load content files.
620
     *
621
     * @param string $filearea
622
     * @param int|object $itemid
623
     * @param string $file path + name
624
     *
625
     * @return \stored_file|bool
626
     */
627
    // @codingStandardsIgnoreLine
628
    private function getFile($filearea, $itemid, $file) {
629
        if ($filearea === 'editor') {
630
            // Itemid is actually cm or course context.
631
            $context = $itemid;
632
            $itemid = 0;
633
        } else if (is_object($itemid)) {
634
            // Grab CM context from item.
635
            $context = \context_module::instance($itemid->coursemodule);
636
            $itemid = $itemid->id;
637
        } else {
638
            // Use item ID to find CM context.
639
            $cm = \get_coursemodule_from_instance('hvp', $itemid);
640
            $context = \context_module::instance($cm->id);
641
        }
642
 
643
        // Load file.
644
        $fs = get_file_storage();
645
        return $fs->get_file($context->id, 'mod_hvp', $filearea, $itemid, $this->getFilepath($file), $this->getFilename($file));
646
    }
647
 
648
    /**
649
     * Extract Moodle compatible filepath
650
     *
651
     * @param string $file
652
     * @return string With slashes
653
     */
654
    // @codingStandardsIgnoreLine
655
    private function getFilepath($file) {
656
        return '/' . dirname($file) . '/';
657
    }
658
 
659
    /**
660
     * Extract filename from filepath string
661
     *
662
     * @param string $file
663
     * @return string Without slashes
664
     */
665
    // @codingStandardsIgnoreLine
666
    private function getFilename($file) {
667
        return basename($file);
668
    }
669
 
670
    /**
671
     * Checks if a file exists
672
     *
673
     * @method fileExists
674
     * @param  string     $filearea [description]
675
     * @param  string     $filepath [description]
676
     * @param  string     $filename [description]
677
     * @return boolean
678
     */
679
    // @codingStandardsIgnoreLine
680
    public static function fileExists($contextid, $filearea, $filepath, $filename) {
681
        // Check if file exists.
682
        $fs = get_file_storage();
683
        return ($fs->get_file($contextid, 'mod_hvp', $filearea, 0, $filepath, $filename) !== false);
684
    }
685
 
686
    /**
687
     * Check if server setup has write permission to
688
     * the required folders
689
     *
690
     * @return bool true if server has the proper write access
691
     */
692
    // @codingStandardsIgnoreLine
693
    public function hasWriteAccess() {
694
        global $CFG;
695
 
696
        if (!is_dir($CFG->dataroot)) {
697
            trigger_error('Path is not a directory ' . $CFG->dataroot, E_USER_WARNING);
698
            return false;
699
        }
700
 
701
        if (!is_writable($CFG->dataroot)) {
702
            trigger_error('Unable to write to ' . $CFG->dataroot . ' – check directory permissions –', E_USER_WARNING);
703
            return false;
704
        }
705
 
706
        return true;
707
    }
708
 
709
    /**
710
     * Copy a content from one directory to another. Defaults to cloning
711
     * content from the current temporary upload folder to the editor path.
712
     *
713
     * @param string $source path to source directory
714
     * @param string $contentid path of target directory. Defaults to editor path
715
     *
716
     * @return object|null Object containing h5p json and content json data
717
     */
718
    // @codingStandardsIgnoreLine
719
    public function moveContentDirectory($source, $contentid = null) {
720
        if ($source === null) {
721
            return null;
722
        }
723
 
724
        // Default to 0 (editor).
725
        if (!isset($contentid)) {
726
            $contentid = 0;
727
        }
728
 
729
        // Find content context.
730
        if ($contentid > 0) {
731
            // Grab cm context.
732
            $cm = \get_coursemodule_from_instance('hvp', $contentid);
733
            $context = \context_module::instance($cm->id);
734
            $contextid = $context->id;
735
        }
736
 
737
        // Get context from parameters.
738
        if (!isset($contextid)) {
739
            $contextid = required_param('contextId', PARAM_INT);
740
        }
741
 
742
        // Get h5p and content json.
743
        $contentsource = $source . DIRECTORY_SEPARATOR . 'content';
744
 
745
        // Move all temporary content files to editor.
746
        $contentfiles = array_diff(scandir($contentsource), array('.', '..', 'content.json'));
747
        foreach ($contentfiles as $file) {
748
            if (is_dir("{$contentsource}/{$file}")) {
749
                self::moveFileTree("{$contentsource}/{$file}", $contextid, $contentid);
750
            } else {
751
                self::moveFile("{$contentsource}/{$file}", $contextid, $contentid);
752
            }
753
        }
754
 
755
        // TODO: Return list of all files so they can be marked as temporary. JI-366.
756
    }
757
 
758
    /**
759
     * Move a single file to editor
760
     *
761
     * @param string $sourcefile Path to source fil
762
     * @param int $contextid Id of context
763
     * @param int $contentid Id of content, 0 if editor
764
     */
765
    // @codingStandardsIgnoreLine
766
    private static function moveFile($sourcefile, $contextid, $contentid) {
767
        $fs = get_file_storage();
768
 
769
        $pathparts = pathinfo($sourcefile);
770
        $filename  = $pathparts['basename'];
771
        $filepath  = $pathparts['dirname'];
772
        $foldername = basename($filepath);
773
 
774
        if ($contentid > 0) {
775
            // Create file record for content.
776
            $record = array(
777
                'contextid' => $contextid,
778
                'component' => 'mod_hvp',
779
                'filearea' => $contentid > 0 ? 'content' : 'editor',
780
                'itemid' => $contentid,
781
                'filepath' => '/' . $foldername . '/',
782
                'filename' => $filename
783
            );
784
        } else {
785
            // Create file record for editor.
786
            $record = array(
787
                'contextid' => $contextid,
788
                'component' => 'mod_hvp',
789
                'filearea' => 'editor',
790
                'itemid' => 0,
791
                'filepath' => '/' . $foldername . '/',
792
                'filename' => $filename
793
            );
794
        }
795
 
796
        $sourcedata = file_get_contents($sourcefile);
797
 
798
        // Check if file already exists.
799
        $fileexists = $fs->file_exists($record['contextid'], 'mod_hvp',
800
            $record['filearea'], $record['itemid'], $record['filepath'],
801
            $record['filename']
802
        );
803
 
804
        if ($fileexists) {
805
            // Delete it to make sure that it is replaced with correct content.
806
            $file = $fs->get_file($record['contextid'], 'mod_hvp',
807
                $record['filearea'], $record['itemid'], $record['filepath'],
808
                $record['filename']
809
            );
810
            if ($file) {
811
                $file->delete();
812
            }
813
        }
814
 
815
        $fs->create_file_from_string($record, $sourcedata);
816
    }
817
 
818
    /**
819
     * Move a complete file tree to the editor
820
     *
821
     * @param string $sourcefiletree Path of file tree that should be moved
822
     * @param int $contextid Id of context
823
     * @param int $contentid Id of content, 0 for editor
824
     *
825
     * @throws \Exception
826
     */
827
    // @codingStandardsIgnoreLine
828
    private static function moveFileTree($sourcefiletree, $contextid, $contentid) {
829
        $dir = opendir($sourcefiletree);
830
        if ($dir === false) {
831
            trigger_error('Unable to open directory ' . $sourcefiletree, E_USER_WARNING);
832
            throw new \Exception('unabletocopy');
833
        }
834
 
835
        while (false !== ($file = readdir($dir))) {
836
            if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore') {
837
                if (is_dir("{$sourcefiletree}/{$file}")) {
838
                    self::moveFileTree("{$sourcefiletree}/{$file}", $contextid, $contentid);
839
                } else {
840
                    self::moveFile("{$sourcefiletree}/{$file}", $contextid, $contentid);
841
                }
842
            }
843
        }
844
        closedir($dir);
845
    }
846
 
847
    /**
848
     * Check if the library has a presave.js in the root folder
849
     *
850
     * @param string $libraryname
851
     * @param string $developmentpath
852
     *
853
     * @return bool
854
     */
855
    // @codingStandardsIgnoreLine
856
    public function hasPresave($libraryname, $developmentpath = null) {
857
        // TODO: Implement.
858
        return false;
859
    }
860
 
861
    /**
862
     * Check if upgrades script exist for library.
863
     *
864
     * @param string $machineName
865
     * @param int $majorVersion
866
     * @param int $minorVersion
867
     * @return string Relative path
868
     */
869
    // @codingStandardsIgnoreLine
870
    public function getUpgradeScript($machinename, $majorversion, $minorversion) {
871
        $context = \context_system::instance();
872
        $fs = get_file_storage();
873
        $area = 'libraries';
874
        $path = "/{$machinename}-{$majorversion}.{$minorversion}/";
875
        $file = 'upgrades.js';
876
        if ($fs->get_file($context->id, 'mod_hvp', $area, 0, $path, $file)) {
877
            return "/{$area}{$path}{$file}";
878
        } else {
879
            return null;
880
        }
881
    }
882
 
883
    /**
884
     * Store the given stream into the given file.
885
     *
886
     * @param string $path
887
     * @param string $file
888
     * @param resource $stream
889
     *
890
     * @return bool
891
     */
892
    // @codingStandardsIgnoreLine
893
    public function saveFileFromZip($path, $file, $stream) {
894
        $filepath = $path . '/' . $file;
895
 
896
        // Make sure the directory exists first.
897
        $matches = array();
898
        preg_match('/(.+)\/[^\/]*$/', $filepath, $matches);
899
        // Recursively make directories.
900
        if (!file_exists($matches[1])) {
901
            mkdir($matches[1], 0777, true);
902
        }
903
 
904
        // Store in local storage folder.
905
        return file_put_contents($filepath, $stream);
906
    }
907
 
908
    // @codingStandardIgnoreLine
909
    public function deleteLibrary($library) {
910
        // TODO: Implement deleteLibrary() method.
911
    }
912
}