forked from h5p/h5p-php-library
-
Notifications
You must be signed in to change notification settings - Fork 0
/
h5p.classes.php
4243 lines (3814 loc) · 142 KB
/
h5p.classes.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?php
/**
* Interface defining functions the h5p library needs the framework to implement
*/
interface H5PFrameworkInterface {
/**
* Returns info for the current platform
*
* @return array
* An associative array containing:
* - name: The name of the platform, for instance "Wordpress"
* - version: The version of the platform, for instance "4.0"
* - h5pVersion: The version of the H5P plugin/module
*/
public function getPlatformInfo();
/**
* Fetches a file from a remote server using HTTP GET
*
* @param string $url Where you want to get or send data.
* @param array $data Data to post to the URL.
* @param bool $blocking Set to 'FALSE' to instantly time out (fire and forget).
* @param string $stream Path to where the file should be saved.
* @return string The content (response body). NULL if something went wrong
*/
public function fetchExternalData($url, $data = NULL, $blocking = TRUE, $stream = NULL);
/**
* Set the tutorial URL for a library. All versions of the library is set
*
* @param string $machineName
* @param string $tutorialUrl
*/
public function setLibraryTutorialUrl($machineName, $tutorialUrl);
/**
* Show the user an error message
*
* @param string $message The error message
* @param string $code An optional code
*/
public function setErrorMessage($message, $code = NULL);
/**
* Show the user an information message
*
* @param string $message
* The error message
*/
public function setInfoMessage($message);
/**
* Return messages
*
* @param string $type 'info' or 'error'
* @return string[]
*/
public function getMessages($type);
/**
* Translation function
*
* @param string $message
* The english string to be translated.
* @param array $replacements
* An associative array of replacements to make after translation. Incidences
* of any key in this array are replaced with the corresponding value. Based
* on the first character of the key, the value is escaped and/or themed:
* - !variable: inserted as is
* - @variable: escape plain text to HTML
* - %variable: escape text and theme as a placeholder for user-submitted
* content
* @return string Translated string
* Translated string
*/
public function t($message, $replacements = array());
/**
* Get URL to file in the specific library
* @param string $libraryFolderName
* @param string $fileName
* @return string URL to file
*/
public function getLibraryFileUrl($libraryFolderName, $fileName);
/**
* Get the Path to the last uploaded h5p
*
* @return string
* Path to the folder where the last uploaded h5p for this session is located.
*/
public function getUploadedH5pFolderPath();
/**
* Get the path to the last uploaded h5p file
*
* @return string
* Path to the last uploaded h5p
*/
public function getUploadedH5pPath();
/**
* Get a list of the current installed libraries
*
* @return array
* Associative array containing one entry per machine name.
* For each machineName there is a list of libraries(with different versions)
*/
public function loadLibraries();
/**
* Returns the URL to the library admin page
*
* @return string
* URL to admin page
*/
public function getAdminUrl();
/**
* Get id to an existing library.
* If version number is not specified, the newest version will be returned.
*
* @param string $machineName
* The librarys machine name
* @param int $majorVersion
* Optional major version number for library
* @param int $minorVersion
* Optional minor version number for library
* @return int
* The id of the specified library or FALSE
*/
public function getLibraryId($machineName, $majorVersion = NULL, $minorVersion = NULL);
/**
* Get file extension whitelist
*
* The default extension list is part of h5p, but admins should be allowed to modify it
*
* @param boolean $isLibrary
* TRUE if this is the whitelist for a library. FALSE if it is the whitelist
* for the content folder we are getting
* @param string $defaultContentWhitelist
* A string of file extensions separated by whitespace
* @param string $defaultLibraryWhitelist
* A string of file extensions separated by whitespace
*/
public function getWhitelist($isLibrary, $defaultContentWhitelist, $defaultLibraryWhitelist);
/**
* Is the library a patched version of an existing library?
*
* @param object $library
* An associative array containing:
* - machineName: The library machineName
* - majorVersion: The librarys majorVersion
* - minorVersion: The librarys minorVersion
* - patchVersion: The librarys patchVersion
* @return boolean
* TRUE if the library is a patched version of an existing library
* FALSE otherwise
*/
public function isPatchedLibrary($library);
/**
* Is H5P in development mode?
*
* @return boolean
* TRUE if H5P development mode is active
* FALSE otherwise
*/
public function isInDevMode();
/**
* Is the current user allowed to update libraries?
*
* @return boolean
* TRUE if the user is allowed to update libraries
* FALSE if the user is not allowed to update libraries
*/
public function mayUpdateLibraries();
/**
* Store data about a library
*
* Also fills in the libraryId in the libraryData object if the object is new
*
* @param object $libraryData
* Associative array containing:
* - libraryId: The id of the library if it is an existing library.
* - title: The library's name
* - machineName: The library machineName
* - majorVersion: The library's majorVersion
* - minorVersion: The library's minorVersion
* - patchVersion: The library's patchVersion
* - runnable: 1 if the library is a content type, 0 otherwise
* - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise
* - embedTypes(optional): list of supported embed types
* - preloadedJs(optional): list of associative arrays containing:
* - path: path to a js file relative to the library root folder
* - preloadedCss(optional): list of associative arrays containing:
* - path: path to css file relative to the library root folder
* - dropLibraryCss(optional): list of associative arrays containing:
* - machineName: machine name for the librarys that are to drop their css
* - semantics(optional): Json describing the content structure for the library
* - language(optional): associative array containing:
* - languageCode: Translation in json format
* @param bool $new
* @return
*/
public function saveLibraryData(&$libraryData, $new = TRUE);
/**
* Insert new content.
*
* @param array $content
* An associative array containing:
* - id: The content id
* - params: The content in json format
* - library: An associative array containing:
* - libraryId: The id of the main library for this content
* @param int $contentMainId
* Main id for the content if this is a system that supports versions
*/
public function insertContent($content, $contentMainId = NULL);
/**
* Update old content.
*
* @param array $content
* An associative array containing:
* - id: The content id
* - params: The content in json format
* - library: An associative array containing:
* - libraryId: The id of the main library for this content
* @param int $contentMainId
* Main id for the content if this is a system that supports versions
*/
public function updateContent($content, $contentMainId = NULL);
/**
* Resets marked user data for the given content.
*
* @param int $contentId
*/
public function resetContentUserData($contentId);
/**
* Save what libraries a library is depending on
*
* @param int $libraryId
* Library Id for the library we're saving dependencies for
* @param array $dependencies
* List of dependencies as associative arrays containing:
* - machineName: The library machineName
* - majorVersion: The library's majorVersion
* - minorVersion: The library's minorVersion
* @param string $dependency_type
* What type of dependency this is, the following values are allowed:
* - editor
* - preloaded
* - dynamic
*/
public function saveLibraryDependencies($libraryId, $dependencies, $dependency_type);
/**
* Give an H5P the same library dependencies as a given H5P
*
* @param int $contentId
* Id identifying the content
* @param int $copyFromId
* Id identifying the content to be copied
* @param int $contentMainId
* Main id for the content, typically used in frameworks
* That supports versions. (In this case the content id will typically be
* the version id, and the contentMainId will be the frameworks content id
*/
public function copyLibraryUsage($contentId, $copyFromId, $contentMainId = NULL);
/**
* Deletes content data
*
* @param int $contentId
* Id identifying the content
*/
public function deleteContentData($contentId);
/**
* Delete what libraries a content item is using
*
* @param int $contentId
* Content Id of the content we'll be deleting library usage for
*/
public function deleteLibraryUsage($contentId);
/**
* Saves what libraries the content uses
*
* @param int $contentId
* Id identifying the content
* @param array $librariesInUse
* List of libraries the content uses. Libraries consist of associative arrays with:
* - library: Associative array containing:
* - dropLibraryCss(optional): comma separated list of machineNames
* - machineName: Machine name for the library
* - libraryId: Id of the library
* - type: The dependency type. Allowed values:
* - editor
* - dynamic
* - preloaded
*/
public function saveLibraryUsage($contentId, $librariesInUse);
/**
* Get number of content/nodes using a library, and the number of
* dependencies to other libraries
*
* @param int $libraryId
* Library identifier
* @param boolean $skipContent
* Flag to indicate if content usage should be skipped
* @return array
* Associative array containing:
* - content: Number of content using the library
* - libraries: Number of libraries depending on the library
*/
public function getLibraryUsage($libraryId, $skipContent = FALSE);
/**
* Loads a library
*
* @param string $machineName
* The library's machine name
* @param int $majorVersion
* The library's major version
* @param int $minorVersion
* The library's minor version
* @return array|FALSE
* FALSE if the library does not exist.
* Otherwise an associative array containing:
* - libraryId: The id of the library if it is an existing library.
* - title: The library's name
* - machineName: The library machineName
* - majorVersion: The library's majorVersion
* - minorVersion: The library's minorVersion
* - patchVersion: The library's patchVersion
* - runnable: 1 if the library is a content type, 0 otherwise
* - fullscreen(optional): 1 if the library supports fullscreen, 0 otherwise
* - embedTypes(optional): list of supported embed types
* - preloadedJs(optional): comma separated string with js file paths
* - preloadedCss(optional): comma separated sting with css file paths
* - dropLibraryCss(optional): list of associative arrays containing:
* - machineName: machine name for the librarys that are to drop their css
* - semantics(optional): Json describing the content structure for the library
* - preloadedDependencies(optional): list of associative arrays containing:
* - machineName: Machine name for a library this library is depending on
* - majorVersion: Major version for a library this library is depending on
* - minorVersion: Minor for a library this library is depending on
* - dynamicDependencies(optional): list of associative arrays containing:
* - machineName: Machine name for a library this library is depending on
* - majorVersion: Major version for a library this library is depending on
* - minorVersion: Minor for a library this library is depending on
* - editorDependencies(optional): list of associative arrays containing:
* - machineName: Machine name for a library this library is depending on
* - majorVersion: Major version for a library this library is depending on
* - minorVersion: Minor for a library this library is depending on
*/
public function loadLibrary($machineName, $majorVersion, $minorVersion);
/**
* Loads library semantics.
*
* @param string $machineName
* Machine name for the library
* @param int $majorVersion
* The library's major version
* @param int $minorVersion
* The library's minor version
* @return string
* The library's semantics as json
*/
public function loadLibrarySemantics($machineName, $majorVersion, $minorVersion);
/**
* Makes it possible to alter the semantics, adding custom fields, etc.
*
* @param array $semantics
* Associative array representing the semantics
* @param string $machineName
* The library's machine name
* @param int $majorVersion
* The library's major version
* @param int $minorVersion
* The library's minor version
*/
public function alterLibrarySemantics(&$semantics, $machineName, $majorVersion, $minorVersion);
/**
* Delete all dependencies belonging to given library
*
* @param int $libraryId
* Library identifier
*/
public function deleteLibraryDependencies($libraryId);
/**
* Start an atomic operation against the dependency storage
*/
public function lockDependencyStorage();
/**
* Stops an atomic operation against the dependency storage
*/
public function unlockDependencyStorage();
/**
* Delete a library from database and file system
*
* @param stdClass $library
* Library object with id, name, major version and minor version.
*/
public function deleteLibrary($library);
/**
* Load content.
*
* @param int $id
* Content identifier
* @return array
* Associative array containing:
* - contentId: Identifier for the content
* - params: json content as string
* - embedType: csv of embed types
* - title: The contents title
* - language: Language code for the content
* - libraryId: Id for the main library
* - libraryName: The library machine name
* - libraryMajorVersion: The library's majorVersion
* - libraryMinorVersion: The library's minorVersion
* - libraryEmbedTypes: CSV of the main library's embed types
* - libraryFullscreen: 1 if fullscreen is supported. 0 otherwise.
*/
public function loadContent($id);
/**
* Load dependencies for the given content of the given type.
*
* @param int $id
* Content identifier
* @param int $type
* Dependency types. Allowed values:
* - editor
* - preloaded
* - dynamic
* @return array
* List of associative arrays containing:
* - libraryId: The id of the library if it is an existing library.
* - machineName: The library machineName
* - majorVersion: The library's majorVersion
* - minorVersion: The library's minorVersion
* - patchVersion: The library's patchVersion
* - preloadedJs(optional): comma separated string with js file paths
* - preloadedCss(optional): comma separated sting with css file paths
* - dropCss(optional): csv of machine names
*/
public function loadContentDependencies($id, $type = NULL);
/**
* Get stored setting.
*
* @param string $name
* Identifier for the setting
* @param string $default
* Optional default value if settings is not set
* @return mixed
* Whatever has been stored as the setting
*/
public function getOption($name, $default = NULL);
/**
* Stores the given setting.
* For example when did we last check h5p.org for updates to our libraries.
*
* @param string $name
* Identifier for the setting
* @param mixed $value Data
* Whatever we want to store as the setting
*/
public function setOption($name, $value);
/**
* This will update selected fields on the given content.
*
* @param int $id Content identifier
* @param array $fields Content fields, e.g. filtered or slug.
*/
public function updateContentFields($id, $fields);
/**
* Will clear filtered params for all the content that uses the specified
* library. This means that the content dependencies will have to be rebuilt,
* and the parameters re-filtered.
*
* @param int $library_id
*/
public function clearFilteredParameters($library_id);
/**
* Get number of contents that has to get their content dependencies rebuilt
* and parameters re-filtered.
*
* @return int
*/
public function getNumNotFiltered();
/**
* Get number of contents using library as main library.
*
* @param int $libraryId
* @return int
*/
public function getNumContent($libraryId);
/**
* Determines if content slug is used.
*
* @param string $slug
* @return boolean
*/
public function isContentSlugAvailable($slug);
/**
* Generates statistics from the event log per library
*
* @param string $type Type of event to generate stats for
* @return array Number values indexed by library name and version
*/
public function getLibraryStats($type);
/**
* Aggregate the current number of H5P authors
* @return int
*/
public function getNumAuthors();
/**
* Stores hash keys for cached assets, aggregated JavaScripts and
* stylesheets, and connects it to libraries so that we know which cache file
* to delete when a library is updated.
*
* @param string $key
* Hash key for the given libraries
* @param array $libraries
* List of dependencies(libraries) used to create the key
*/
public function saveCachedAssets($key, $libraries);
/**
* Locate hash keys for given library and delete them.
* Used when cache file are deleted.
*
* @param int $library_id
* Library identifier
* @return array
* List of hash keys removed
*/
public function deleteCachedAssets($library_id);
/**
* Get the amount of content items associated to a library
* return int
*/
public function getLibraryContentCount();
/**
* Will trigger after the export file is created.
*/
public function afterExportCreated($content, $filename);
/**
* Check if user has permissions to an action
*
* @method hasPermission
* @param [H5PPermission] $permission Permission type, ref H5PPermission
* @param [int] $id Id need by platform to determine permission
* @return boolean
*/
public function hasPermission($permission, $id = NULL);
/**
* Replaces existing content type cache with the one passed in
*
* @param object $contentTypeCache Json with an array called 'libraries'
* containing the new content type cache that should replace the old one.
*/
public function replaceContentTypeCache($contentTypeCache);
}
/**
* This class is used for validating H5P files
*/
class H5PValidator {
public $h5pF;
public $h5pC;
// Schemas used to validate the h5p files
private $h5pRequired = array(
'title' => '/^.{1,255}$/',
'language' => '/^[-a-zA-Z]{1,10}$/',
'preloadedDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'mainLibrary' => '/^[$a-z_][0-9a-z_\.$]{1,254}$/i',
'embedTypes' => array('iframe', 'div'),
);
private $h5pOptional = array(
'contentType' => '/^.{1,255}$/',
'author' => '/^.{1,255}$/',
'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT|GPL1|GPL2|GPL3|MPL|MPL2)$/',
'dynamicDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'w' => '/^[0-9]{1,4}$/',
'h' => '/^[0-9]{1,4}$/',
'metaKeywords' => '/^.{1,}$/',
'metaDescription' => '/^.{1,}$/',
);
// Schemas used to validate the library files
private $libraryRequired = array(
'title' => '/^.{1,255}$/',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
'patchVersion' => '/^[0-9]{1,5}$/',
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'runnable' => '/^(0|1)$/',
);
private $libraryOptional = array(
'author' => '/^.{1,255}$/',
'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT|GPL1|GPL2|GPL3|MPL|MPL2)$/',
'description' => '/^.{1,}$/',
'dynamicDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'preloadedDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'editorDependencies' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
'preloadedJs' => array(
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.js$/i',
),
'preloadedCss' => array(
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.css$/i',
),
'dropLibraryCss' => array(
'machineName' => '/^[\w0-9\-\.]{1,255}$/i',
),
'w' => '/^[0-9]{1,4}$/',
'h' => '/^[0-9]{1,4}$/',
'embedTypes' => array('iframe', 'div'),
'fullscreen' => '/^(0|1)$/',
'coreApi' => array(
'majorVersion' => '/^[0-9]{1,5}$/',
'minorVersion' => '/^[0-9]{1,5}$/',
),
);
/**
* Constructor for the H5PValidator
*
* @param H5PFrameworkInterface $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @param H5PCore $H5PCore
*/
public function __construct($H5PFramework, $H5PCore) {
$this->h5pF = $H5PFramework;
$this->h5pC = $H5PCore;
$this->h5pCV = new H5PContentValidator($this->h5pF, $this->h5pC);
}
/**
* Validates a .h5p file
*
* @param bool $skipContent
* @param bool $upgradeOnly
* @return bool TRUE if the .h5p file is valid
* TRUE if the .h5p file is valid
*/
public function isValidPackage($skipContent = FALSE, $upgradeOnly = FALSE) {
// Check dependencies, make sure Zip is present
if (!class_exists('ZipArchive')) {
$this->h5pF->setErrorMessage($this->h5pF->t('Your PHP version does not support ZipArchive.'), 'zip-archive-unsupported');
return FALSE;
}
// Create a temporary dir to extract package in.
$tmpDir = $this->h5pF->getUploadedH5pFolderPath();
$tmpPath = $this->h5pF->getUploadedH5pPath();
// Extract and then remove the package file.
$zip = new ZipArchive;
// Only allow files with the .h5p extension:
if (strtolower(substr($tmpPath, -3)) !== 'h5p') {
$this->h5pF->setErrorMessage($this->h5pF->t('The file you uploaded is not a valid HTML5 Package (It does not have the .h5p file extension)'), 'missing-h5p-extension');
H5PCore::deleteFileTree($tmpDir);
return FALSE;
}
if ($zip->open($tmpPath) === true) {
$zip->extractTo($tmpDir);
$zip->close();
}
else {
$this->h5pF->setErrorMessage($this->h5pF->t('The file you uploaded is not a valid HTML5 Package (We are unable to unzip it)'), 'unable-to-unzip');
H5PCore::deleteFileTree($tmpDir);
return FALSE;
}
unlink($tmpPath);
// Process content and libraries
$valid = TRUE;
$libraries = array();
$files = scandir($tmpDir);
$mainH5pData = null;
$libraryJsonData = null;
$contentJsonData = null;
$mainH5pExists = $contentExists = FALSE;
foreach ($files as $file) {
if (in_array(substr($file, 0, 1), array('.', '_'))) {
continue;
}
$filePath = $tmpDir . DIRECTORY_SEPARATOR . $file;
// Check for h5p.json file.
if (strtolower($file) == 'h5p.json') {
if ($skipContent === TRUE) {
continue;
}
$mainH5pData = $this->getJsonData($filePath);
if ($mainH5pData === FALSE) {
$valid = FALSE;
$this->h5pF->setErrorMessage($this->h5pF->t('Could not parse the main h5p.json file'), 'invalid-h5p-json-file');
}
else {
$validH5p = $this->isValidH5pData($mainH5pData, $file, $this->h5pRequired, $this->h5pOptional);
if ($validH5p) {
$mainH5pExists = TRUE;
}
else {
$valid = FALSE;
$this->h5pF->setErrorMessage($this->h5pF->t('The main h5p.json file is not valid'), 'invalid-h5p-json-file');
}
}
}
// Content directory holds content.
elseif ($file == 'content') {
// We do a separate skipContent check to avoid having the content folder being treated as a library
if ($skipContent) {
continue;
}
if (!is_dir($filePath)) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid content folder'), 'invalid-content-folder');
$valid = FALSE;
continue;
}
$contentJsonData = $this->getJsonData($filePath . DIRECTORY_SEPARATOR . 'content.json');
if ($contentJsonData === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Could not find or parse the content.json file'), 'invalid-content-json-file');
$valid = FALSE;
continue;
}
else {
$contentExists = TRUE;
// In the future we might let the libraries provide validation functions for content.json
}
if (!$this->h5pCV->validateContentFiles($filePath)) {
// validateContentFiles adds potential errors to the queue
$valid = FALSE;
continue;
}
}
// The rest should be library folders
elseif ($this->h5pC->mayUpdateLibraries()) {
if (!is_dir($filePath)) {
// Ignore this. Probably a file that shouldn't have been included.
continue;
}
$libraryH5PData = $this->getLibraryData($file, $filePath, $tmpDir);
if ($libraryH5PData !== FALSE) {
// Library's directory name must be:
// - <machineName>
// - or -
// - <machineName>-<majorVersion>.<minorVersion>
// where machineName, majorVersion and minorVersion is read from library.json
if ($libraryH5PData['machineName'] !== $file && H5PCore::libraryToString($libraryH5PData, TRUE) !== $file) {
$this->h5pF->setErrorMessage($this->h5pF->t('Library directory name must match machineName or machineName-majorVersion.minorVersion (from library.json). (Directory: %directoryName , machineName: %machineName, majorVersion: %majorVersion, minorVersion: %minorVersion)', array(
'%directoryName' => $file,
'%machineName' => $libraryH5PData['machineName'],
'%majorVersion' => $libraryH5PData['majorVersion'],
'%minorVersion' => $libraryH5PData['minorVersion'])), 'library-directory-name-mismatch');
$valid = FALSE;
continue;
}
$libraryH5PData['uploadDirectory'] = $filePath;
$libraries[H5PCore::libraryToString($libraryH5PData)] = $libraryH5PData;
}
else {
$valid = FALSE;
}
}
}
if ($skipContent === FALSE) {
if (!$contentExists) {
$this->h5pF->setErrorMessage($this->h5pF->t('A valid content folder is missing'), 'invalid-content-folder');
$valid = FALSE;
}
if (!$mainH5pExists) {
$this->h5pF->setErrorMessage($this->h5pF->t('A valid main h5p.json file is missing'), 'invalid-h5p-json-file');
$valid = FALSE;
}
}
if ($valid) {
if ($upgradeOnly) {
// When upgrading, we only add the already installed libraries, and
// the new dependent libraries
$upgrades = array();
foreach ($libraries as $libString => &$library) {
// Is this library already installed?
if ($this->h5pF->getLibraryId($library['machineName']) !== FALSE) {
$upgrades[$libString] = $library;
}
}
while ($missingLibraries = $this->getMissingLibraries($upgrades)) {
foreach ($missingLibraries as $libString => $missing) {
$library = $libraries[$libString];
if ($library) {
$upgrades[$libString] = $library;
}
}
}
$libraries = $upgrades;
}
$this->h5pC->librariesJsonData = $libraries;
if ($skipContent === FALSE) {
$this->h5pC->mainJsonData = $mainH5pData;
$this->h5pC->contentJsonData = $contentJsonData;
$libraries['mainH5pData'] = $mainH5pData; // Check for the dependencies in h5p.json as well as in the libraries
}
$missingLibraries = $this->getMissingLibraries($libraries);
foreach ($missingLibraries as $libString => $missing) {
if ($this->h5pC->getLibraryId($missing, $libString)) {
unset($missingLibraries[$libString]);
}
}
if (!empty($missingLibraries)) {
foreach ($missingLibraries as $libString => $library) {
$this->h5pF->setErrorMessage($this->h5pF->t('Missing required library @library', array('@library' => $libString)), 'missing-required-library');
}
if (!$this->h5pC->mayUpdateLibraries()) {
$this->h5pF->setInfoMessage($this->h5pF->t("Note that the libraries may exist in the file you uploaded, but you're not allowed to upload new libraries. Contact the site administrator about this."));
}
}
$valid = empty($missingLibraries) && $valid;
}
if (!$valid) {
H5PCore::deleteFileTree($tmpDir);
}
return $valid;
}
/**
* Validates a H5P library
*
* @param string $file
* Name of the library folder
* @param string $filePath
* Path to the library folder
* @param string $tmpDir
* Path to the temporary upload directory
* @return boolean|array
* H5P data from library.json and semantics if the library is valid
* FALSE if the library isn't valid
*/
public function getLibraryData($file, $filePath, $tmpDir) {
if (preg_match('/^[\w0-9\-\.]{1,255}$/i', $file) === 0) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid library name: %name', array('%name' => $file)), 'invalid-library-name');
return FALSE;
}
$h5pData = $this->getJsonData($filePath . DIRECTORY_SEPARATOR . 'library.json');
if ($h5pData === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Could not find library.json file with valid json format for library %name', array('%name' => $file)), 'invalid-library-json-file');
return FALSE;
}
// validate json if a semantics file is provided
$semanticsPath = $filePath . DIRECTORY_SEPARATOR . 'semantics.json';
if (file_exists($semanticsPath)) {
$semantics = $this->getJsonData($semanticsPath, TRUE);
if ($semantics === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid semantics.json file has been included in the library %name', array('%name' => $file)), 'invalid-semantics-json-file');
return FALSE;
}
else {
$h5pData['semantics'] = $semantics;
}
}
// validate language folder if it exists
$languagePath = $filePath . DIRECTORY_SEPARATOR . 'language';
if (is_dir($languagePath)) {
$languageFiles = scandir($languagePath);
foreach ($languageFiles as $languageFile) {
if (in_array($languageFile, array('.', '..'))) {
continue;
}
if (preg_match('/^(-?[a-z]+){1,7}\.json$/i', $languageFile) === 0) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid language file %file in library %library', array('%file' => $languageFile, '%library' => $file)), 'invalid-language-file');
return FALSE;
}
$languageJson = $this->getJsonData($languagePath . DIRECTORY_SEPARATOR . $languageFile, TRUE);
if ($languageJson === FALSE) {
$this->h5pF->setErrorMessage($this->h5pF->t('Invalid language file %languageFile has been included in the library %name', array('%languageFile' => $languageFile, '%name' => $file)), 'invalid-language-file');
return FALSE;
}
$parts = explode('.', $languageFile); // $parts[0] is the language code
$h5pData['language'][$parts[0]] = $languageJson;
}
}
// Check for icon:
$h5pData['hasIcon'] = file_exists($filePath . DIRECTORY_SEPARATOR . 'icon.svg');
$validLibrary = $this->isValidH5pData($h5pData, $file, $this->libraryRequired, $this->libraryOptional);
$validLibrary = $this->h5pCV->validateContentFiles($filePath, TRUE) && $validLibrary;
if (isset($h5pData['preloadedJs'])) {
$validLibrary = $this->isExistingFiles($h5pData['preloadedJs'], $tmpDir, $file) && $validLibrary;
}
if (isset($h5pData['preloadedCss'])) {
$validLibrary = $this->isExistingFiles($h5pData['preloadedCss'], $tmpDir, $file) && $validLibrary;
}
if ($validLibrary) {
return $h5pData;
}
else {
return FALSE;
}
}
/**
* Use the dependency declarations to find any missing libraries
*
* @param array $libraries
* A multidimensional array of libraries keyed with machineName first and majorVersion second
* @return array
* A list of libraries that are missing keyed with machineName and holds objects with
* machineName, majorVersion and minorVersion properties
*/
private function getMissingLibraries($libraries) {
$missing = array();
foreach ($libraries as $library) {
if (isset($library['preloadedDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['preloadedDependencies'], $libraries));
}
if (isset($library['dynamicDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['dynamicDependencies'], $libraries));
}
if (isset($library['editorDependencies'])) {
$missing = array_merge($missing, $this->getMissingDependencies($library['editorDependencies'], $libraries));
}
}
return $missing;
}