Completed
Push — master ( 8c1400...a48a4d )
by Abdelouahab
04:48
created

LaravelH5pStorage::hasPresave()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 1
eloc 0
c 1
b 0
f 1
nc 1
nop 2
dl 0
loc 1
rs 10
1
<?php
2
3
/*
4
 *
5
 * @Project        Expression project.displayName is undefined on line 5, column 35 in Templates/Licenses/license-default.txt.
6
 * @Copyright      Djoudi
7
 * @Created        2017-02-11
8
 * @Filename       FilesStorage.php
9
 * @Description
10
 *
11
 */
12
13
namespace Djoudi\LaravelH5p\Storages;
14
15
use H5PFileStorage;
16
17
//use Illuminate\Filesystem\Filesystem;
18
//use Symfony\Component\Finder\Finder;
19
20
class LaravelH5pStorage implements H5PFileStorage
21
{
22
    private $path;
23
    private $alteditorpath;
24
25
    /**
26
     * The great Constructor!
27
     *
28
     * @param string $path
29
     *                              The base location of H5P files
30
     * @param string $alteditorpath
31
     *                              Optional. Use a different editor path
32
     */
33
    public function __construct($path, $alteditorpath = null)
34
    {
35
        // Set H5P storage path
36
        $this->path = $path;
37
        $this->alteditorpath = $alteditorpath;
38
    }
39
40
41
 public function hasPresave($libraryName, $developmentPath = null){
42
43
 }
44
45
   public function getUpgradeScript($machineName, $majorVersion, $minorVersion){}
46
47
    /**
48
     * Store the library folder.
49
     *
50
     * @param array $library
51
     *                       Library properties
52
     */
53
    public function saveLibrary($library)
54
    {
55
        $dest = $this->path.'/libraries/'.\H5PCore::libraryToString($library, true);
56
57
        // Make sure destination dir doesn't exist
58
        \H5PCore::deleteFileTree($dest);
59
60
        // Move library folder
61
        self::copyFileTree($library['uploadDirectory'], $dest);
62
    }
63
64
    /**
65
     * Store the content folder.
66
     *
67
     * @param string $source
68
     *                        Path on file system to content directory.
69
     * @param array  $content
70
     *                        Content properties
71
     */
72
    public function saveContent($source, $content)
73
    {
74
        $dest = "{$this->path}/content/{$content['id']}";
75
76
        // Remove any old content
77
        \H5PCore::deleteFileTree($dest);
78
79
        self::copyFileTree($source, $dest);
80
    }
81
82
    /**
83
     * Remove content folder.
84
     *
85
     * @param array $content
86
     *                       Content properties
87
     */
88
    public function deleteContent($content)
89
    {
90
        \H5PCore::deleteFileTree("{$this->path}/content/{$content['id']}");
91
    }
92
93
    /**
94
     * Creates a stored copy of the content folder.
95
     *
96
     * @param string $id
97
     *                      Identifier of content to clone.
98
     * @param int    $newId
99
     *                      The cloned content's identifier
100
     */
101
    public function cloneContent($id, $newId)
102
    {
103
        $path = $this->path.'/content/';
104
        if (file_exists($path.$id)) {
105
            self::copyFileTree($path.$id, $path.$newId);
106
        }
107
    }
108
109
    /**
110
     * Get path to a new unique tmp folder.
111
     *
112
     * @return string
113
     *                Path
114
     */
115
    public function getTmpPath()
116
    {
117
        $temp = "{$this->path}/temp";
118
        self::dirReady($temp);
119
120
        return "{$temp}/".uniqid('h5p-');
121
    }
122
123
    /**
124
     * Fetch content folder and save in target directory.
125
     *
126
     * @param int    $id
127
     *                       Content identifier
128
     * @param string $target
129
     *                       Where the content folder will be saved
130
     */
131
    public function exportContent($id, $target)
132
    {
133
        $source = "{$this->path}/content/{$id}";
134
        if (file_exists($source)) {
135
            // Copy content folder if it exists
136
            self::copyFileTree($source, $target);
137
        } else {
138
            // No contnet folder, create emty dir for content.json
139
            self::dirReady($target);
140
        }
141
    }
142
143
    /**
144
     * Fetch library folder and save in target directory.
145
     *
146
     * @param array  $library
147
     *                                Library properties
148
     * @param string $target
149
     *                                Where the library folder will be saved
150
     * @param string $developmentPath
151
     *                                Folder that library resides in
152
     */
153
    public function exportLibrary($library, $target, $developmentPath = null)
154
    {
155
        $folder = \H5PCore::libraryToString($library, true);
156
        $srcPath = ($developmentPath === null ? "/libraries/{$folder}" : $developmentPath);
157
        self::copyFileTree("{$this->path}{$srcPath}", "{$target}/{$folder}");
158
    }
159
160
    /**
161
     * Save export in file system.
162
     *
163
     * @param string $source
164
     *                         Path on file system to temporary export file.
165
     * @param string $filename
166
     *                         Name of export file.
167
     *
168
     * @throws Exception Unable to save the file
169
     */
170
    public function saveExport($source, $filename)
171
    {
172
        $this->deleteExport($filename);
173
174
        if (!self::dirReady("{$this->path}/exports")) {
175
            throw new Exception('Unable to create directory for H5P export file.');
0 ignored issues
show
Bug introduced by
The type Djoudi\LaravelH5p\Storages\Exception was not found. Did you mean Exception? If so, make sure to prefix the type with \.
Loading history...
176
        }
177
178
        if (!copy($source, "{$this->path}/exports/{$filename}")) {
179
            throw new Exception('Unable to save H5P export file.');
180
        }
181
    }
182
183
    /**
184
     * Removes given export file.
185
     *
186
     * @param string $filename
187
     */
188
    public function deleteExport($filename)
189
    {
190
        $target = "{$this->path}/exports/{$filename}";
191
        if (file_exists($target)) {
192
            unlink($target);
193
        }
194
    }
195
196
    /**
197
     * Check if the given export file exists.
198
     *
199
     * @param string $filename
200
     *
201
     * @return bool
202
     */
203
    public function hasExport($filename)
204
    {
205
        $target = "{$this->path}/exports/{$filename}";
206
207
        return file_exists($target);
208
    }
209
210
    /**
211
     * Will concatenate all JavaScrips and Stylesheets into two files in order
212
     * to improve page performance.
213
     *
214
     * @param array  $files
215
     *                      A set of all the assets required for content to display
216
     * @param string $key
217
     *                      Hashed key for cached asset
218
     */
219
    public function cacheAssets(&$files, $key)
220
    {
221
        foreach ($files as $type => $assets) {
222
            if (empty($assets)) {
223
                continue; // Skip no assets
224
            }
225
226
            $content = '';
227
            foreach ($assets as $asset) {
228
                // Get content from asset file
229
                $assetContent = file_get_contents($this->path.$asset->path);
230
                $cssRelPath = preg_replace('/[^\/]+$/', '', $asset->path);
231
232
                // Get file content and concatenate
233
                if ($type === 'scripts') {
234
                    $content .= $assetContent.";\n";
235
                } else {
236
                    // Rewrite relative URLs used inside stylesheets
237
                    $content .= preg_replace_callback(
238
                        '/url\([\'"]?([^"\')]+)[\'"]?\)/i', function ($matches) use ($cssRelPath) {
239
                            if (preg_match("/^(data:|([a-z0-9]+:)?\/)/i", $matches[1]) === 1) {
240
                                return $matches[0]; // Not relative, skip
241
                            }
242
243
                            return 'url("../'.$cssRelPath.$matches[1].'")';
244
                        }, $assetContent)."\n";
245
                }
246
            }
247
248
            self::dirReady("{$this->path}/cachedassets");
249
            $ext = ($type === 'scripts' ? 'js' : 'css');
250
            $outputfile = "/cachedassets/{$key}.{$ext}";
251
            file_put_contents($this->path.$outputfile, $content);
252
            $files[$type] = [(object) [
253
                'path'    => $outputfile,
254
                'version' => '',
255
            ]];
256
        }
257
    }
258
259
    /**
260
     * Will check if there are cache assets available for content.
261
     *
262
     * @param string $key
263
     *                    Hashed key for cached asset
264
     *
265
     * @return array
266
     */
267
    public function getCachedAssets($key)
268
    {
269
        $files = [];
270
271
        $js = "/cachedassets/{$key}.js";
272
        if (file_exists($this->path.$js)) {
273
            $files['scripts'] = [(object) [
274
                'path'    => $js,
275
                'version' => '',
276
            ]];
277
        }
278
279
        $css = "/cachedassets/{$key}.css";
280
        if (file_exists($this->path.$css)) {
281
            $files['styles'] = [(object) [
282
                'path'    => $css,
283
                'version' => '',
284
            ]];
285
        }
286
287
        return empty($files) ? null : $files;
288
    }
289
290
    /**
291
     * Remove the aggregated cache files.
292
     *
293
     * @param array $keys
294
     *                    The hash keys of removed files
295
     */
296
    public function deleteCachedAssets($keys)
297
    {
298
        foreach ($keys as $hash) {
299
            foreach (['js', 'css'] as $ext) {
300
                $path = "{$this->path}/cachedassets/{$hash}.{$ext}";
301
                if (file_exists($path)) {
302
                    unlink($path);
303
                }
304
            }
305
        }
306
    }
307
308
    /**
309
     * Read file content of given file and then return it.
310
     *
311
     * @param string $file_path
312
     *
313
     * @return string
314
     */
315
    public function getContent($file_path)
316
    {
317
        return file_get_contents($file_path);
318
    }
319
320
    /**
321
     * Save files uploaded through the editor.
322
     * The files must be marked as temporary until the content form is saved.
323
     *
324
     * @param \H5peditorFile $file
325
     * @param int            $contentid
326
     */
327
    public function saveFile($file, $contentId)
328
    {
329
        // Prepare directory
330
        if (empty($contentId)) {
331
            // Should be in editor tmp folder
332
            $path = $this->getEditorPath();
333
        } else {
334
            // Should be in content folder
335
            $path = $this->path.'/content/'.$contentId;
336
        }
337
        $path .= '/'.$file->getType().'s';
338
339
        self::dirReady($path);
340
341
        // Add filename to path
342
        $path .= '/'.$file->getName();
343
344
        $fileData = $file->getData();
0 ignored issues
show
Bug introduced by
The method getData() does not exist on H5peditorFile. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

344
        /** @scrutinizer ignore-call */ 
345
        $fileData = $file->getData();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
345
        if ($fileData) {
346
            file_put_contents($path, $fileData);
347
        } else {
348
            copy($_FILES['file']['tmp_name'], $path);
349
        }
350
351
        return $file;
352
    }
353
354
    /**
355
     * Copy a file from another content or editor tmp dir.
356
     * Used when copy pasting content in H5P Editor.
357
     *
358
     * @param string     $file   path + name
359
     * @param string|int $fromid Content ID or 'editor' string
360
     * @param int        $toid   Target Content ID
361
     */
362
    public function cloneContentFile($file, $fromId, $toId)
363
    {
364
        // Determine source path
365
        if ($fromId === 'editor') {
366
            $sourcepath = $this->getEditorPath();
367
        } else {
368
            $sourcepath = "{$this->path}/content/{$fromId}";
369
        }
370
        $sourcepath .= '/'.$file;
371
372
        // Determine target path
373
        $filename = basename($file);
374
        $filedir = str_replace($filename, '', $file);
375
        $targetpath = "{$this->path}/content/{$toId}/{$filedir}";
376
377
        // Make sure it's ready
378
        self::dirReady($targetpath);
379
380
        $targetpath .= $filename;
381
382
        // Check to see if source exist and if target doesn't
383
        if (!file_exists($sourcepath) || file_exists($targetpath)) {
384
            return; // Nothing to copy from or target already exists
385
        }
386
387
        copy($sourcepath, $targetpath);
388
    }
389
390
    /**
391
     * Copy a content from one directory to another. Defaults to cloning
392
     * content from the current temporary upload folder to the editor path.
393
     *
394
     * @param string $source    path to source directory
395
     * @param string $contentId Id of content
396
     *
397
     * @return object Object containing h5p json and content json data
398
     */
399
    public function moveContentDirectory($source, $contentId = null)
400
    {
401
        if ($source === null) {
0 ignored issues
show
introduced by
The condition $source === null is always false.
Loading history...
402
            return;
403
        }
404
405
        if ($contentId === null || $contentId == 0) {
406
            $target = $this->getEditorPath();
407
        } else {
408
            // Use content folder
409
            $target = "{$this->path}/content/{$contentId}";
410
        }
411
412
        $contentSource = $source.DIRECTORY_SEPARATOR.'content';
413
        $contentFiles = array_diff(scandir($contentSource), ['.', '..', 'content.json']);
0 ignored issues
show
Bug introduced by
It seems like scandir($contentSource) can also be of type false; however, parameter $array1 of array_diff() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

413
        $contentFiles = array_diff(/** @scrutinizer ignore-type */ scandir($contentSource), ['.', '..', 'content.json']);
Loading history...
414
        foreach ($contentFiles as $file) {
415
            if (is_dir("{$contentSource}/{$file}")) {
416
                self::copyFileTree("{$contentSource}/{$file}", "{$target}/{$file}");
417
            } else {
418
                copy("{$contentSource}/{$file}", "{$target}/{$file}");
419
            }
420
        }
421
422
        // Successfully loaded content json of file into editor
423
        $h5pJson = $this->getContent($source.DIRECTORY_SEPARATOR.'h5p.json');
424
        $contentJson = $this->getContent($contentSource.DIRECTORY_SEPARATOR.'content.json');
425
426
        return (object) [
427
            'h5pJson'     => $h5pJson,
428
            'contentJson' => $contentJson,
429
        ];
430
    }
431
432
    /**
433
     * Checks to see if content has the given file.
434
     * Used when saving content.
435
     *
436
     * @param string $file      path + name
437
     * @param int    $contentId
438
     *
439
     * @return string File ID or NULL if not found
440
     */
441
    public function getContentFile($file, $contentId)
442
    {
443
        $path = "{$this->path}/content/{$contentId}/{$file}";
444
445
        return file_exists($path) ? $path : null;
446
    }
447
448
    /**
449
     * Checks to see if content has the given file.
450
     * Used when saving content.
451
     *
452
     * @param string $file      path + name
453
     * @param int    $contentid
454
     *
455
     * @return string|int File ID or NULL if not found
456
     */
457
    public function removeContentFile($file, $contentId)
458
    {
459
        $path = "{$this->path}/content/{$contentId}/{$file}";
460
        if (file_exists($path)) {
461
            unlink($path);
462
        }
463
    }
464
465
    /**
466
     * Check if server setup has write permission to
467
     * the required folders.
468
     *
469
     * @return bool True if site can write to the H5P files folder
470
     */
471
    public function hasWriteAccess()
472
    {
473
        return self::dirReady($this->path);
474
    }
475
476
    /**
477
     * Recursive function for copying directories.
478
     *
479
     * @param string $source
480
     *                            From path
481
     * @param string $destination
482
     *                            To path
483
     *
484
     * @throws Exception Unable to copy the file
485
     *
486
     * @return bool
487
     *              Indicates if the directory existed.
488
     */
489
    private static function copyFileTree($source, $destination)
490
    {
491
        if (!self::dirReady($destination)) {
492
            throw new \Exception('unabletocopy');
493
        }
494
495
        $ignoredFiles = self::getIgnoredFiles("{$source}/.h5pignore");
496
497
        $dir = opendir($source);
498
        if ($dir === false) {
499
            trigger_error('Unable to open directory '.$source, E_USER_WARNING);
500
501
            throw new \Exception('unabletocopy');
502
        }
503
504
        while (false !== ($file = readdir($dir))) {
505
            if (($file != '.') && ($file != '..') && $file != '.git' && $file != '.gitignore' && !in_array($file, $ignoredFiles)) {
506
                if (is_dir("{$source}/{$file}")) {
507
                    self::copyFileTree("{$source}/{$file}", "{$destination}/{$file}");
508
                } else {
509
                    copy("{$source}/{$file}", "{$destination}/{$file}");
510
                }
511
            }
512
        }
513
        closedir($dir);
514
    }
515
516
    /**
517
     * Retrieve array of file names from file.
518
     *
519
     * @param string $file
520
     *
521
     * @return array Array with files that should be ignored
522
     */
523
    private static function getIgnoredFiles($file)
524
    {
525
        if (file_exists($file) === false) {
526
            return [];
527
        }
528
529
        $contents = file_get_contents($file);
530
        if ($contents === false) {
531
            return [];
532
        }
533
534
        return preg_split('/\s+/', $contents);
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_split('/\s+/', $contents) could also return false which is incompatible with the documented return type array. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
535
    }
536
537
    /**
538
     * Recursive function that makes sure the specified directory exists and
539
     * is writable.
540
     *
541
     * @param string $path
542
     *
543
     * @return bool
544
     */
545
    private static function dirReady($path)
546
    {
547
        if (!file_exists($path)) {
548
            $parent = preg_replace("/\/[^\/]+\/?$/", '', $path);
549
            if (!self::dirReady($parent)) {
550
                return false;
551
            }
552
553
            mkdir($path, 0777, true);
554
        }
555
556
        if (!is_dir($path)) {
557
            trigger_error('Path is not a directory '.$path, E_USER_WARNING);
558
559
            return false;
560
        }
561
562
        if (!is_writable($path)) {
563
            trigger_error('Unable to write to '.$path.' – check directory permissions –', E_USER_WARNING);
564
565
            return false;
566
        }
567
568
        return true;
569
    }
570
571
    /**
572
     * Easy helper function for retrieving the editor path.
573
     *
574
     * @return string Path to editor files
575
     */
576
    private function getEditorPath()
577
    {
578
        return $this->path.'/editor';
579
//        return ($this->alteditorpath !== NULL ? $this->alteditorpath : "{$this->path}/editor");
580
    }
581
}
582