2012-11-21 14:14:07 +01:00
< ? php
2013-01-14 23:47:50 +01:00
/**
* Interface defining functions the h5p library needs the framework to implement
*/
2013-02-17 15:39:26 +01:00
interface H5PFrameworkInterface {
2013-01-14 23:47:50 +01:00
/**
* Show the user an error message
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param string $message
* The error message
*/
2012-11-22 14:00:06 +01:00
public function setErrorMessage ( $message );
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
* Show the user an information message
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param string $message
* The error message
*/
2012-11-22 14:00:06 +01:00
public function setInfoMessage ( $message );
2013-01-14 23:47:50 +01:00
/**
* Translation function
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param string $message
2013-07-09 10:14:42 +02:00
* The english string to be translated .
2013-01-14 23:47:50 +01:00
* @ param type $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
*/
2012-12-03 00:19:25 +01:00
public function t ( $message , $replacements = array ());
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
* Get the Path to the last uploaded h5p
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ return string Path to the folder where the last uploaded h5p for this session is located .
*/
2012-11-30 15:51:39 +01:00
public function getUploadedH5pFolderPath ();
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
* @ return string Path to the folder where all h5p files are stored
*/
2012-11-30 15:51:39 +01:00
public function getH5pPath ();
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
* Get the path to the last uploaded h5p file
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ return string Path to the last uploaded h5p
*/
2012-11-22 14:00:06 +01:00
public function getUploadedH5pPath ();
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
* Get id to an excisting library
2013-07-09 10:14:42 +02:00
*
2013-03-09 01:20:55 +01:00
* @ param string $machineName
2013-01-14 23:47:50 +01:00
* The librarys machine name
* @ param int $majorVersion
* The librarys major version
* @ param int $minorVersion
* The librarys minor version
* @ return int
* The id of the specified library or FALSE
*/
2013-03-09 01:20:55 +01:00
public function getLibraryId ( $machineName , $majorVersion , $minorVersion );
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
2013-06-30 16:39:17 +02:00
* Is the library a patched version of an existing library ?
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param object $library
* The library data for a library we are checking
* @ return boolean
* TRUE if the library is a patched version of an excisting library
* FALSE otherwise
*/
2012-12-22 06:35:16 +01:00
public function isPatchedLibrary ( $library );
2013-07-09 10:14:42 +02:00
2013-06-30 16:39:17 +02:00
/**
* Is the current user allowed to update the library data ?
*
* @ param object $library
* The library data for a library we are checking
* @ return boolean
* TRUE if the user us allowed to update with the given library data OR the library already exists with the current version levels .
* FALSE if the user is not allowed to update or create the library .
*/
public function isAllowedLibraryUpdate ( $library );
2013-01-14 23:47:50 +01:00
/**
* Store data about a library
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* Also fills in the libraryId in the libraryData object if the object is new
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param object $libraryData
* Object holding the information that is to be stored
*/
2013-02-17 14:05:02 +01:00
public function saveLibraryData ( & $libraryData );
2013-07-09 10:14:42 +02:00
2013-01-14 23:47:50 +01:00
/**
2013-02-15 11:38:49 +01:00
* Stores contentData
2013-07-09 10:14:42 +02:00
*
2013-01-14 23:47:50 +01:00
* @ param int $contentId
* Framework specific id identifying the content
* @ param string $contentJson
* The content data that is to be stored
* @ param array $mainJsonData
* The data extracted from the h5p . json file
* @ param int $contentMainId
* Any contentMainId defined by the framework , for instance to support revisioning
*/
2013-03-08 18:30:38 +01:00
public function saveContentData ( $contentId , $contentJson , $mainJsonData , $mainLibraryId , $contentMainId = NULL );
2013-06-30 22:14:16 +02:00
/**
* Validates content files
*
* @ param string $contentPath
* The path containg content files to validate .
* @ return boolean
* TRUE if all files are valid
* FALSE if one or more files fail validation . Error message should be set accordingly by validator .
*/
public function validateContentFiles ( $contentPath );
2013-03-29 16:35:54 +01:00
/**
* Save what libraries a library is dependending on
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:35:54 +01:00
* @ param int $libraryId
* Library Id for the library we ' re saving dependencies for
* @ param array $dependencies
* List of dependencies in the format used in library . json
* @ param string $dependency_type
* What type of dependency this is , for instance it might be an editor dependency
*/
public function saveLibraryDependencies ( $libraryId , $dependencies , $dependency_type );
2013-02-15 11:38:49 +01:00
/**
2013-03-07 05:31:30 +01:00
* Copies library usage
2013-02-15 11:38:49 +01:00
*
* @ param int $contentId
* Framework specific id identifying the content
* @ param int $copyFromId
* Framework specific id identifying the content to be copied
* @ param int $contentMainId
* Framework specific main id for the content , typically used in frameworks
* That supports versioning . ( In this case the content id will typically be
* the version id , and the contentMainId will be the frameworks content id
*/
2013-03-07 05:31:30 +01:00
public function copyLibraryUsage ( $contentId , $copyFromId , $contentMainId = NULL );
2013-02-15 11:38:49 +01:00
/**
* Deletes content data
*
* @ param int $contentId
* Framework specific id identifying the content
*/
2012-12-22 07:09:17 +01:00
public function deleteContentData ( $contentId );
2013-02-15 11:38:49 +01:00
2013-03-29 16:35:54 +01:00
/**
* Delete what libraries a content item is using
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:35:54 +01:00
* @ param int $contentId
* Content Id of the content we ' ll be deleting library usage for
*/
public function deleteLibraryUsage ( $contentId );
2013-07-09 10:14:42 +02:00
2013-02-15 11:38:49 +01:00
/**
* Saves what libraries the content uses
*
* @ param int $contentId
* Framework specific id identifying the content
* @ param array $librariesInUse
* List of libraries the content uses . Libraries consist of arrays with :
* - libraryId stored in $librariesInUse [ < place > ][ 'library' ][ 'libraryId' ]
* - libraryId stored in $librariesInUse [ < place > ][ 'preloaded' ]
*/
2012-11-30 15:51:39 +01:00
public function saveLibraryUsage ( $contentId , $librariesInUse );
2013-02-15 11:38:49 +01:00
/**
* Loads a library
*
2013-03-09 01:20:55 +01:00
* @ param string $machineName
2013-02-15 11:38:49 +01:00
* @ param int $majorVersion
* @ param int $minorVersion
* @ return array | FALSE
* Array representing the library with dependency descriptions
* FALSE if the library doesn ' t exist
*/
2013-03-09 01:20:55 +01:00
public function loadLibrary ( $machineName , $majorVersion , $minorVersion );
2013-04-13 14:11:25 +02:00
2013-07-09 15:13:09 +02:00
/**
* Loads and decodes library semantics .
*
* @ param string $machineName
* @ param int $majorVersion
* @ param int $minorVersion
* @ return array | FALSE
* Array representing the library with dependency descriptions
* FALSE if the library doesn ' t exist
*/
public function getLibrarySemantics ( $machineName , $majorVersion , $minorVersion );
2013-04-13 14:11:25 +02:00
/**
* Delete all dependencies belonging to given library
*
* @ param int $libraryId
* Library Id
*/
public function deleteLibraryDependencies ( $libraryId );
2012-11-21 14:14:07 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* This class is used for validating H5P files
*/
2013-02-17 15:04:30 +01:00
class H5PValidator {
2012-11-22 14:00:06 +01:00
public $h5pF ;
2012-11-29 22:52:41 +01:00
public $h5pC ;
2013-01-16 19:39:49 +01:00
2012-11-24 01:09:25 +01:00
// Schemas used to validate the h5p files
private $h5pRequired = array (
2012-11-28 14:06:55 +01:00
'title' => '/^.{1,255}$/' ,
'language' => '/^[a-z]{1,5}$/' ,
2012-11-24 01:09:25 +01:00
'preloadedDependencies' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2012-12-04 22:26:20 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
'minorVersion' => '/^[0-9]{1,5}$/' ,
2012-11-24 01:09:25 +01:00
),
2013-03-09 01:20:55 +01:00
'mainLibrary' => '/^[$a-z_][0-9a-z_\.$]{1,254}$/i' ,
2012-11-24 01:09:25 +01:00
'embedTypes' => array ( 'iframe' , 'div' ),
);
2013-01-16 19:39:49 +01:00
2012-11-24 01:09:25 +01:00
private $h5pOptional = array (
2012-11-28 14:06:55 +01:00
'contentType' => '/^.{1,255}$/' ,
'author' => '/^.{1,255}$/' ,
2013-01-16 19:39:49 +01:00
'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT)$/' ,
2012-11-24 01:09:25 +01:00
'dynamicDependencies' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2012-12-04 22:26:20 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
'minorVersion' => '/^[0-9]{1,5}$/' ,
2012-11-24 01:09:25 +01:00
),
2013-03-09 01:20:55 +01:00
'w' => '/^[0-9]{1,4}$/' ,
'h' => '/^[0-9]{1,4}$/' ,
2012-12-02 21:55:51 +01:00
'metaKeywords' => '/^.{1,}$/' ,
'metaDescription' => '/^.{1,}$/k' ,
);
2013-01-14 23:47:50 +01:00
// Schemas used to validate the library files
2012-12-02 21:55:51 +01:00
private $libraryRequired = array (
'title' => '/^.{1,255}$/' ,
2012-12-04 20:56:16 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
2012-12-04 22:26:20 +01:00
'minorVersion' => '/^[0-9]{1,5}$/' ,
'patchVersion' => '/^[0-9]{1,5}$/' ,
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2013-01-20 16:13:11 +01:00
'runnable' => '/^(0|1)$/' ,
2012-12-02 21:55:51 +01:00
);
2013-01-16 19:39:49 +01:00
2012-12-02 21:55:51 +01:00
private $libraryOptional = array (
'author' => '/^.{1,255}$/' ,
2013-01-16 19:39:49 +01:00
'license' => '/^(cc-by|cc-by-sa|cc-by-nd|cc-by-nc|cc-by-nc-sa|cc-by-nc-nd|pd|cr|MIT)$/' ,
2012-12-02 21:55:51 +01:00
'description' => '/^.{1,}$/' ,
'dynamicDependencies' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2012-12-04 22:26:20 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
'minorVersion' => '/^[0-9]{1,5}$/' ,
2013-01-16 19:39:49 +01:00
),
2012-12-02 21:55:51 +01:00
'preloadedDependencies' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2012-12-04 22:26:20 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
2012-12-22 06:35:16 +01:00
'minorVersion' => '/^[0-9]{1,5}$/' ,
2012-12-02 21:55:51 +01:00
),
2013-02-06 17:39:45 +01:00
'editorDependencies' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2013-02-06 17:39:45 +01:00
'majorVersion' => '/^[0-9]{1,5}$/' ,
'minorVersion' => '/^[0-9]{1,5}$/' ,
),
2012-11-30 15:51:39 +01:00
'preloadedJs' => array (
2013-04-14 15:31:29 +02:00
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.js$/i' ,
2012-11-30 15:51:39 +01:00
),
'preloadedCss' => array (
2013-04-14 15:31:29 +02:00
'path' => '/^((\\\|\/)?[a-z_\-\s0-9\.]+)+\.css$/i' ,
2012-11-30 15:51:39 +01:00
),
2013-02-28 20:06:18 +01:00
'dropLibraryCss' => array (
2013-03-09 01:20:55 +01:00
'machineName' => '/^[\w0-9\-\.]{1,255}$/i' ,
2013-02-28 20:06:18 +01:00
),
2013-03-09 01:20:55 +01:00
'w' => '/^[0-9]{1,4}$/' ,
'h' => '/^[0-9]{1,4}$/' ,
2012-12-02 21:55:51 +01:00
'embedTypes' => array ( 'iframe' , 'div' ),
2013-04-11 14:59:33 +02:00
'fullscreen' => '/^(0|1)$/' ,
2012-11-24 01:09:25 +01:00
);
2012-11-22 14:00:06 +01:00
2012-11-29 15:41:06 +01:00
/**
2013-02-17 15:04:30 +01:00
* Constructor for the H5PValidator
2012-11-29 15:41:06 +01:00
*
2013-02-17 15:04:30 +01:00
* @ param object $H5PFramework
2013-02-17 15:39:26 +01:00
* The frameworks implementation of the H5PFrameworkInterface
2012-11-29 15:41:06 +01:00
*/
2013-02-17 15:04:30 +01:00
public function __construct ( $H5PFramework , $H5PCore ) {
$this -> h5pF = $H5PFramework ;
$this -> h5pC = $H5PCore ;
2012-11-22 14:00:06 +01:00
}
2012-11-29 15:41:06 +01:00
/**
* Validates a . h5p file
*
* @ return boolean
* TRUE if the . h5p file is valid
*/
2012-11-30 15:51:39 +01:00
public function isValidPackage () {
2012-11-22 14:00:06 +01:00
// Create a temporary dir to extract package in.
2013-05-01 23:24:58 +02:00
$tmpDir = $this -> h5pF -> getUploadedH5pFolderPath ();
2012-12-03 00:19:25 +01:00
$tmp_path = $this -> h5pF -> getUploadedH5pPath ();
2012-11-22 14:00:06 +01:00
2012-11-28 15:21:34 +01:00
$valid = TRUE ;
2012-11-22 14:00:06 +01:00
// Extract and then remove the package file.
2012-12-03 00:19:25 +01:00
$zip = new ZipArchive ;
if ( $zip -> open ( $tmp_path ) === true ) {
2013-05-01 23:24:58 +02:00
$zip -> extractTo ( $tmpDir );
2012-12-03 00:19:25 +01:00
$zip -> close ();
}
else {
2013-02-28 20:06:18 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'The file you uploaded is not a valid HTML5 Package.' ));
2013-05-01 23:24:58 +02:00
$this -> h5pC -> delTree ( $tmpDir );
2012-11-22 14:00:06 +01:00
return ;
}
unlink ( $tmp_path );
// Process content and libraries
2012-11-28 15:31:55 +01:00
$libraries = array ();
2013-05-01 23:24:58 +02:00
$files = scandir ( $tmpDir );
2012-11-29 15:41:06 +01:00
$mainH5pData ;
2012-11-30 15:51:39 +01:00
$libraryJsonData ;
2012-11-29 08:57:01 +01:00
$mainH5pExists = $imageExists = $contentExists = FALSE ;
2012-11-23 17:06:03 +01:00
foreach ( $files as $file ) {
2013-01-16 22:00:12 +01:00
if ( in_array ( substr ( $file , 0 , 1 ), array ( '.' , '_' ))) {
2012-11-23 17:06:03 +01:00
continue ;
}
2013-05-01 23:24:58 +02:00
$filePath = $tmpDir . DIRECTORY_SEPARATOR . $file ;
2013-01-16 19:39:49 +01:00
// Check for h5p.json file.
2012-11-23 17:06:03 +01:00
if ( strtolower ( $file ) == 'h5p.json' ) {
2013-05-01 17:22:09 +02:00
$mainH5pData = $this -> getJsonData ( $filePath );
2012-11-29 15:41:06 +01:00
if ( $mainH5pData === FALSE ) {
2012-11-29 08:51:36 +01:00
$valid = FALSE ;
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Could not find or parse the main h5p.json file' ));
2012-11-29 08:51:36 +01:00
}
else {
2012-11-29 15:41:06 +01:00
$validH5p = $this -> isValidH5pData ( $mainH5pData , $file , $this -> h5pRequired , $this -> h5pOptional );
2012-11-29 08:51:36 +01:00
if ( $validH5p ) {
$mainH5pExists = TRUE ;
}
else {
$valid = FALSE ;
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Could not find or parse the main h5p.json file' ));
2012-11-29 08:51:36 +01:00
}
}
2012-11-23 17:06:03 +01:00
}
2013-01-16 19:39:49 +01:00
// Check for h5p.jpg?
2012-11-23 17:06:03 +01:00
elseif ( strtolower ( $file ) == 'h5p.jpg' ) {
2012-11-29 08:57:01 +01:00
$imageExists = TRUE ;
2012-11-23 17:06:03 +01:00
}
2013-01-16 19:39:49 +01:00
// Content directory holds content.
2012-11-23 17:06:03 +01:00
elseif ( $file == 'content' ) {
2013-05-01 17:22:09 +02:00
if ( ! is_dir ( $filePath )) {
2012-12-22 06:35:16 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Invalid content folder' ));
$valid = FALSE ;
continue ;
}
2013-05-01 17:22:09 +02:00
$contentJsonData = $this -> getJsonData ( $filePath . DIRECTORY_SEPARATOR . 'content.json' );
2012-11-30 15:51:39 +01:00
if ( $contentJsonData === FALSE ) {
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Could not find or parse the content.json file' ));
2012-11-28 15:21:34 +01:00
$valid = FALSE ;
continue ;
}
2012-11-29 08:51:36 +01:00
else {
2012-11-29 08:57:01 +01:00
$contentExists = TRUE ;
2013-06-30 22:14:16 +02:00
// In the future we might let the libraries provide validation functions for content.json
}
if ( ! $this -> h5pF -> validateContentFiles ( $filePath )) {
$valid = FALSE ;
continue ;
2012-11-28 15:21:34 +01:00
}
2012-11-23 17:06:03 +01:00
}
2012-11-22 14:00:06 +01:00
2013-01-16 19:39:49 +01:00
// The rest should be library folders
2012-11-23 17:06:03 +01:00
else {
2013-05-01 17:22:09 +02:00
if ( ! is_dir ( $filePath )) {
2013-01-16 19:39:49 +01:00
// Ignore this. Probably a file that shouldn't have been included.
2012-12-22 06:35:16 +01:00
continue ;
}
2013-06-30 15:32:12 +02:00
2013-05-01 23:24:58 +02:00
$libraryH5PData = $this -> getLibraryData ( $file , $filePath , $tmpDir );
2012-11-28 15:31:55 +01:00
2013-05-01 23:24:58 +02:00
if ( $libraryH5PData ) {
$libraries [ $file ] = $libraryH5PData ;
2012-11-28 15:31:55 +01:00
}
2013-05-01 17:22:09 +02:00
else {
$valid = FALSE ;
}
2012-11-22 14:00:06 +01:00
}
}
2012-11-29 15:41:06 +01:00
if ( ! $contentExists ) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'A valid content folder is missing' ));
$valid = FALSE ;
}
if ( ! $mainH5pExists ) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'A valid main h5p.json file is missing' ));
$valid = FALSE ;
}
2012-11-28 15:31:55 +01:00
if ( $valid ) {
2012-11-30 15:51:39 +01:00
$this -> h5pC -> librariesJsonData = $libraries ;
$this -> h5pC -> mainJsonData = $mainH5pData ;
$this -> h5pC -> contentJsonData = $contentJsonData ;
2013-07-09 10:14:42 +02:00
2013-04-13 13:28:48 +02:00
$libraries [ 'mainH5pData' ] = $mainH5pData ; // Check for the dependencies in h5p.json as well as in the libraries
2012-11-28 23:29:22 +01:00
$missingLibraries = $this -> getMissingLibraries ( $libraries );
foreach ( $missingLibraries as $missing ) {
2013-03-09 01:20:55 +01:00
if ( $this -> h5pF -> getLibraryId ( $missing [ 'machineName' ], $missing [ 'majorVersion' ], $missing [ 'minorVersion' ])) {
unset ( $missingLibraries [ $missing [ 'machineName' ]]);
2012-11-28 23:29:22 +01:00
}
}
2013-04-13 13:28:48 +02:00
if ( ! empty ( $missingLibraries )) {
foreach ( $missingLibraries as $library ) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Missing required library @library' , array ( '@library' => $this -> h5pC -> libraryToString ( $library ))));
}
}
2012-11-28 23:29:22 +01:00
$valid = empty ( $missingLibraries ) && $valid ;
}
if ( ! $valid ) {
2013-05-01 23:24:58 +02:00
$this -> h5pC -> delTree ( $tmpDir );
2012-11-28 23:29:22 +01:00
}
return $valid ;
}
2012-11-29 15:41:06 +01:00
2013-05-01 17:22:09 +02:00
/**
* Validates a H5P library
*
* @ param string $file
* Name of the library folder
* @ param string $filePath
* Path to the library folder
2013-05-01 23:24:58 +02:00
* @ param string $tmpDir
* Path to the temporary upload directory
2013-05-01 17:22:09 +02:00
* @ return object | boolean
* H5P data from library . json and semantics if the library is valid
* FALSE if the library isn ' t valid
*/
2013-05-01 23:24:58 +02:00
public function getLibraryData ( $file , $filePath , $tmpDir ) {
2013-05-01 17:22:09 +02:00
if ( preg_match ( '/^[\w0-9\-\.]{1,255}$/i' , $file ) === 0 ) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Invalid library name: %name' , array ( '%name' => $file )));
2013-05-01 23:24:58 +02:00
return FALSE ;
2013-05-01 17:22:09 +02:00
}
$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 )));
2013-05-01 23:24:58 +02:00
return FALSE ;
2013-05-01 17:22:09 +02:00
}
2013-06-30 16:39:17 +02:00
// check if allowed to update this library
if ( ! $this -> h5pF -> isAllowedLibraryUpdate ( $h5pData )) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Not allowed to update library %name' , array ( '%name' => $h5pData [ 'machineName' ])));
return FALSE ;
}
2013-05-01 17:22:09 +02:00
// 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 )));
2013-05-01 23:24:58 +02:00
return FALSE ;
2013-05-01 17:22:09 +02:00
}
else {
$h5pData [ 'semantics' ] = $semantics ;
}
}
2013-05-02 17:09:48 +02:00
// validate language folder if it exists
$languagePath = $filePath . DIRECTORY_SEPARATOR . 'language' ;
if ( is_dir ( $languagePath )) {
$languageFiles = scandir ( $languagePath );
foreach ( $languageFiles as $languageFile ) {
2013-05-03 11:21:08 +02:00
if ( in_array ( $languageFile , array ( '.' , '..' ))) {
continue ;
}
if ( preg_match ( '/^(-?[a-z]+){1,7}\.json$/i' , $languageFile ) === 0 ) {
2013-05-02 17:09:48 +02:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Invalid language file %file in library %library' , array ( '%file' => $languageFile , '%library' => $file )));
return FALSE ;
}
2013-05-03 11:21:08 +02:00
$languageJson = $this -> getJsonData ( $languagePath . DIRECTORY_SEPARATOR . $languageFile , TRUE );
2013-05-02 17:09:48 +02:00
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 )));
return FALSE ;
}
$parts = explode ( '.' , $languageFile ); // $parts[0] is the language code
$h5pData [ 'language' ][ $parts [ 0 ]] = $languageJson ;
}
}
2013-05-01 17:22:09 +02:00
$validLibrary = $this -> isValidH5pData ( $h5pData , $file , $this -> libraryRequired , $this -> libraryOptional );
2013-07-08 18:22:38 +02:00
$validLibrary = $this -> h5pF -> validateContentFiles ( $filePath , TRUE ) && $validLibrary ;
2013-05-01 17:22:09 +02:00
if ( isset ( $h5pData [ 'preloadedJs' ])) {
2013-05-01 23:24:58 +02:00
$validLibrary = $this -> isExistingFiles ( $h5pData [ 'preloadedJs' ], $tmpDir , $file ) && $validLibrary ;
2013-05-01 17:22:09 +02:00
}
if ( isset ( $h5pData [ 'preloadedCss' ])) {
2013-05-01 23:24:58 +02:00
$validLibrary = $this -> isExistingFiles ( $h5pData [ 'preloadedCss' ], $tmpDir , $file ) && $validLibrary ;
2013-05-01 17:22:09 +02:00
}
if ( $validLibrary ) {
return $h5pData ;
}
else {
return FALSE ;
}
}
2012-11-29 15:41:06 +01:00
/**
* Use the dependency declarations to find any missing libraries
*
* @ param array $libraries
2013-03-09 01:20:55 +01:00
* A multidimensional array of libraries keyed with machineName first and majorVersion second
2012-11-29 15:41:06 +01:00
* @ return array
2013-03-09 01:20:55 +01:00
* A list of libraries that are missing keyed with machineName and holds objects with
* machineName , majorVersion and minorVersion properties
2012-11-29 15:41:06 +01:00
*/
2012-11-28 23:29:22 +01:00
private function getMissingLibraries ( $libraries ) {
$missing = array ();
foreach ( $libraries as $library ) {
if ( isset ( $library [ 'preloadedDependencies' ])) {
2013-04-13 13:28:48 +02:00
$missing = array_merge ( $missing , $this -> getMissingDependencies ( $library [ 'preloadedDependencies' ], $libraries ));
2012-11-28 23:29:22 +01:00
}
if ( isset ( $library [ 'dynamicDependencies' ])) {
2013-04-13 13:28:48 +02:00
$missing = array_merge ( $missing , $this -> getMissingDependencies ( $library [ 'dynamicDependencies' ], $libraries ));
2012-11-28 23:29:22 +01:00
}
2013-02-08 04:38:27 +01:00
if ( isset ( $library [ 'editorDependencies' ])) {
2013-04-13 13:28:48 +02:00
$missing = array_merge ( $missing , $this -> getMissingDependencies ( $library [ 'editorDependencies' ], $libraries ));
2013-02-08 04:38:27 +01:00
}
2012-11-28 15:31:55 +01:00
}
2012-11-28 23:29:22 +01:00
return $missing ;
}
2012-11-29 15:41:06 +01:00
/**
* Helper function for getMissingLibraries , searches for dependency required libraries in
* the provided list of libraries
*
* @ param array $dependencies
2013-03-09 01:20:55 +01:00
* A list of objects with machineName , majorVersion and minorVersion properties
2012-11-29 15:41:06 +01:00
* @ param array $libraries
2013-03-09 01:20:55 +01:00
* An array of libraries keyed with machineName
2012-11-29 15:41:06 +01:00
* @ return
2013-03-09 01:20:55 +01:00
* A list of libraries that are missing keyed with machineName and holds objects with
* machineName , majorVersion and minorVersion properties
2012-11-29 15:41:06 +01:00
*/
2012-11-28 23:29:22 +01:00
private function getMissingDependencies ( $dependencies , $libraries ) {
$missing = array ();
2012-12-04 15:52:03 +01:00
foreach ( $dependencies as $dependency ) {
2013-03-09 01:20:55 +01:00
if ( isset ( $libraries [ $dependency [ 'machineName' ]])) {
if ( ! $this -> h5pC -> isSameVersion ( $libraries [ $dependency [ 'machineName' ]], $dependency )) {
$missing [ $dependency [ 'machineName' ]] = $dependency ;
2012-11-28 23:29:22 +01:00
}
}
else {
2013-03-09 01:20:55 +01:00
$missing [ $dependency [ 'machineName' ]] = $dependency ;
2012-11-28 23:29:22 +01:00
}
}
return $missing ;
2012-11-26 22:48:51 +01:00
}
2012-11-29 15:41:06 +01:00
/**
* Figure out if the provided file paths exists
*
* Triggers error messages if files doesn ' t exist
*
* @ param array $files
2013-05-01 23:24:58 +02:00
* List of file paths relative to $tmpDir
* @ param string $tmpDir
2012-11-29 15:41:06 +01:00
* Path to the directory where the $files are stored .
* @ param string $library
* Name of the library we are processing
* @ return boolean
* TRUE if all the files excists
*/
2013-05-01 23:24:58 +02:00
private function isExistingFiles ( $files , $tmpDir , $library ) {
2012-12-04 15:52:03 +01:00
foreach ( $files as $file ) {
$path = str_replace ( array ( '/' , '\\' ), DIRECTORY_SEPARATOR , $file [ 'path' ]);
2013-05-01 23:24:58 +02:00
if ( ! file_exists ( $tmpDir . DIRECTORY_SEPARATOR . $library . DIRECTORY_SEPARATOR . $path )) {
2013-01-16 19:39:49 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'The file "%file" is missing from library: "%name"' , array ( '%file' => $path , '%name' => $library )));
2012-11-26 22:48:51 +01:00
return FALSE ;
2012-11-22 14:00:06 +01:00
}
}
2012-11-26 22:48:51 +01:00
return TRUE ;
}
2012-11-29 15:41:06 +01:00
/**
2012-12-02 21:55:51 +01:00
* Validates h5p . json and library . json data
2012-11-29 15:41:06 +01:00
*
* Error messages are triggered if the data isn ' t valid
*
* @ param array $h5pData
* h5p data
* @ param string $library_name
* Name of the library we are processing
* @ param array $required
* Validation pattern for required properties
* @ param array $optional
* Validation pattern for optional properties
* @ return boolean
* TRUE if the $h5pData is valid
*/
2012-11-29 08:51:36 +01:00
private function isValidH5pData ( $h5pData , $library_name , $required , $optional ) {
$valid = $this -> isValidRequiredH5pData ( $h5pData , $required , $library_name );
$valid = $this -> isValidOptionalH5pData ( $h5pData , $optional , $library_name ) && $valid ;
2012-11-28 15:21:34 +01:00
return $valid ;
2012-11-28 13:56:42 +01:00
}
2012-11-29 15:41:06 +01:00
/**
* Helper function for isValidH5pData
*
* Validates the optional part of the h5pData
*
* Triggers error messages
*
* @ param array $h5pData
* h5p data
* @ param array $requirements
* Validation pattern
* @ param string $library_name
* Name of the library we are processing
* @ return boolean
* TRUE if the optional part of the $h5pData is valid
*/
2012-11-28 15:21:34 +01:00
private function isValidOptionalH5pData ( $h5pData , $requirements , $library_name ) {
$valid = TRUE ;
2012-11-28 13:56:42 +01:00
foreach ( $h5pData as $key => $value ) {
if ( isset ( $requirements [ $key ])) {
2012-12-04 15:52:03 +01:00
$valid = $this -> isValidRequirement ( $value , $requirements [ $key ], $library_name , $key ) && $valid ;
2012-11-28 13:56:42 +01:00
}
// Else: ignore, a package can have parameters that this library doesn't care about, but that library
// specific implementations does care about...
}
2012-11-28 15:21:34 +01:00
return $valid ;
2012-11-28 13:56:42 +01:00
}
2012-11-29 15:41:06 +01:00
/**
2013-03-29 16:10:29 +01:00
* Va ( lidate a requirement given as regexp or an array of requirements
2012-11-29 15:41:06 +01:00
*
2013-03-29 16:10:29 +01:00
* @ param mixed $h5pData
* The data to be validated
* @ param mixed $requirement
* The requirement the data is to be validated against , regexp or array of requirements
* @ param string $library_name
* Name of the library we are validating ( used in error messages )
* @ param string $property_name
* Name of the property we are validating ( used in error messages )
* @ return boolean
* TRUE if valid , FALSE if invalid
2012-11-29 15:41:06 +01:00
*/
2012-11-28 15:21:34 +01:00
private function isValidRequirement ( $h5pData , $requirement , $library_name , $property_name ) {
$valid = TRUE ;
2012-11-28 13:56:42 +01:00
if ( is_string ( $requirement )) {
2013-02-23 23:29:41 +01:00
if ( $requirement == 'boolean' ) {
if ( ! is_bool ( $h5pData )) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( " Invalid data provided for %property in %library. Boolean expected. " , array ( '%property' => $property_name , '%library' => $library_name )));
$valid = FALSE ;
2012-11-28 13:56:42 +01:00
}
}
else {
2013-02-23 23:29:41 +01:00
// The requirement is a regexp, match it against the data
if ( is_string ( $h5pData ) || is_int ( $h5pData )) {
if ( preg_match ( $requirement , $h5pData ) === 0 ) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( " Invalid data provided for %property in %library " , array ( '%property' => $property_name , '%library' => $library_name )));
$valid = FALSE ;
}
}
else {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( " Invalid data provided for %property in %library " , array ( '%property' => $property_name , '%library' => $library_name )));
$valid = FALSE ;
}
2012-11-28 13:56:42 +01:00
}
}
elseif ( is_array ( $requirement )) {
// We have sub requirements
if ( is_array ( $h5pData )) {
2012-12-04 15:52:03 +01:00
if ( is_array ( current ( $h5pData ))) {
foreach ( $h5pData as $sub_h5pData ) {
$valid = $this -> isValidRequiredH5pData ( $sub_h5pData , $requirement , $library_name ) && $valid ;
}
}
else {
$valid = $this -> isValidRequiredH5pData ( $h5pData , $requirement , $library_name ) && $valid ;
}
2012-11-28 13:56:42 +01:00
}
else {
2012-12-04 09:19:42 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( " Invalid data provided for %property in %library " , array ( '%property' => $property_name , '%library' => $library_name )));
2012-11-28 15:21:34 +01:00
$valid = FALSE ;
2012-11-28 13:56:42 +01:00
}
}
else {
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( " Can't read the property %property in %library " , array ( '%property' => $property_name , '%library' => $library_name )));
2012-11-28 15:21:34 +01:00
$valid = FALSE ;
2012-11-28 13:56:42 +01:00
}
2012-11-28 15:21:34 +01:00
return $valid ;
2012-11-28 13:56:42 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Validates the required h5p data in libraray . json and h5p . json
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param mixed $h5pData
* Data to be validated
* @ param array $requirements
* Array with regexp to validate the data against
* @ param string $library_name
* Name of the library we are validating ( used in error messages )
* @ return boolean
* TRUE if all the required data exists and is valid , FALSE otherwise
*/
2012-11-28 15:21:34 +01:00
private function isValidRequiredH5pData ( $h5pData , $requirements , $library_name ) {
$valid = TRUE ;
2012-11-28 13:56:42 +01:00
foreach ( $requirements as $required => $requirement ) {
2012-11-28 14:45:54 +01:00
if ( is_int ( $required )) {
// We have an array of allowed options
2012-12-03 00:19:25 +01:00
return $this -> isValidH5pDataOptions ( $h5pData , $requirements , $library_name );
2012-11-28 14:45:54 +01:00
}
2012-11-28 13:56:42 +01:00
if ( isset ( $h5pData [ $required ])) {
2012-12-03 00:19:25 +01:00
$valid = $this -> isValidRequirement ( $h5pData [ $required ], $requirement , $library_name , $required ) && $valid ;
2012-11-28 13:56:42 +01:00
}
else {
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'The required property %property is missing from %library' , array ( '%property' => $required , '%library' => $library_name )));
2012-11-28 15:21:34 +01:00
$valid = FALSE ;
2012-11-28 13:56:42 +01:00
}
}
2012-11-28 15:21:34 +01:00
return $valid ;
2012-11-22 14:00:06 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Validates h5p data against a set of allowed values ( options )
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param array $selected
* The option ( s ) that has been specified
* @ param array $allowed
* The allowed options
* @ param string $library_name
* Name of the library we are validating ( used in error messages )
* @ return boolean
* TRUE if the specified data is valid , FALSE otherwise
*/
2012-11-28 15:21:34 +01:00
private function isValidH5pDataOptions ( $selected , $allowed , $library_name ) {
$valid = TRUE ;
2012-11-28 14:45:54 +01:00
foreach ( $selected as $value ) {
if ( ! in_array ( $value , $allowed )) {
2012-11-29 22:52:41 +01:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Illegal option %option in %library' , array ( '%option' => $value , '%library' => $library_name )));
2012-11-28 15:21:34 +01:00
$valid = FALSE ;
2012-11-28 14:45:54 +01:00
}
}
2012-11-28 15:21:34 +01:00
return $valid ;
2012-11-28 14:45:54 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Fetch json data from file
2013-07-09 10:14:42 +02:00
*
2013-05-01 17:22:09 +02:00
* @ param string $filePath
2013-03-29 16:10:29 +01:00
* Path to the file holding the json string
* @ param boolean $return_as_string
* If true the json data will be decoded in order to validate it , but will be
* returned as string
* @ return mixed
* FALSE if the file can 't be read or the contents can' t be decoded
* string if the $return as string parameter is set
* array otherwise
*/
2013-05-01 17:22:09 +02:00
private function getJsonData ( $filePath , $return_as_string = FALSE ) {
$json = file_get_contents ( $filePath );
2012-11-29 08:51:36 +01:00
if ( ! $json ) {
return FALSE ;
}
2012-12-03 00:19:25 +01:00
$jsonData = json_decode ( $json , TRUE );
2012-11-29 08:51:36 +01:00
if ( ! $jsonData ) {
return FALSE ;
}
2013-01-27 23:08:48 +01:00
return $return_as_string ? $json : $jsonData ;
2012-11-29 08:51:36 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Helper function that copies an array
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param array $array
* The array to be copied
* @ return array
* Copy of $array . All objects are cloned
*/
2012-11-29 08:51:36 +01:00
private function arrayCopy ( array $array ) {
$result = array ();
foreach ( $array as $key => $val ) {
if ( is_array ( $val )) {
$result [ $key ] = arrayCopy ( $val );
}
elseif ( is_object ( $val )) {
$result [ $key ] = clone $val ;
}
else {
$result [ $key ] = $val ;
}
}
return $result ;
}
2012-11-21 14:14:07 +01:00
}
2012-11-29 22:52:41 +01:00
2013-03-29 16:10:29 +01:00
/**
* This class is used for saving H5P files
*/
2013-02-17 15:04:30 +01:00
class H5PStorage {
2013-07-09 10:14:42 +02:00
2012-11-29 22:52:41 +01:00
public $h5pF ;
public $h5pC ;
/**
2013-02-17 15:04:30 +01:00
* Constructor for the H5PStorage
2012-11-29 22:52:41 +01:00
*
2013-02-17 15:04:30 +01:00
* @ param object $H5PFramework
2013-02-17 15:39:26 +01:00
* The frameworks implementation of the H5PFrameworkInterface
2012-11-29 22:52:41 +01:00
*/
2013-02-17 15:04:30 +01:00
public function __construct ( $H5PFramework , $H5PCore ) {
$this -> h5pF = $H5PFramework ;
$this -> h5pC = $H5PCore ;
2012-11-29 22:52:41 +01:00
}
2013-07-09 10:14:42 +02:00
2013-03-29 16:10:29 +01:00
/**
* Saves a H5P file
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param int $contentId
* The id of the content we are saving
* @ param int $contentMainId
* The main id for the content we are saving . This is used if the framework
* we 're integrating with uses content id' s and version id ' s
2013-07-08 17:12:40 +02:00
* @ return boolean
* TRUE if one or more libraries were updated
* FALSE otherwise
2013-03-29 16:10:29 +01:00
*/
2012-12-22 07:09:17 +01:00
public function savePackage ( $contentId , $contentMainId = NULL ) {
2013-03-29 16:10:29 +01:00
// Save the libraries we processed during validation
2013-07-08 17:12:40 +02:00
$library_saved = FALSE ;
2013-07-09 10:14:42 +02:00
2012-12-22 06:35:16 +01:00
foreach ( $this -> h5pC -> librariesJsonData as $key => & $library ) {
2013-03-08 18:30:53 +01:00
$libraryId = $this -> h5pF -> getLibraryId ( $key , $library [ 'majorVersion' ], $library [ 'minorVersion' ]);
2013-04-13 14:11:25 +02:00
$library [ 'saveDependencies' ] = TRUE ;
2013-03-08 18:30:53 +01:00
if ( ! $libraryId ) {
2012-12-22 06:35:16 +01:00
$new = TRUE ;
2012-11-29 22:52:41 +01:00
}
2012-12-22 06:35:16 +01:00
elseif ( $this -> h5pF -> isPatchedLibrary ( $library )) {
$new = FALSE ;
2013-03-08 18:30:53 +01:00
$library [ 'libraryId' ] = $libraryId ;
2012-12-22 06:35:16 +01:00
}
else {
2013-04-13 14:11:25 +02:00
$library [ 'libraryId' ] = $libraryId ;
2012-12-22 06:35:16 +01:00
// We already have the same or a newer version of this library
2013-04-13 14:11:25 +02:00
$library [ 'saveDependencies' ] = FALSE ;
2012-12-22 06:35:16 +01:00
continue ;
2012-12-15 15:45:29 +01:00
}
2013-06-30 16:39:17 +02:00
2013-02-17 14:05:02 +01:00
$this -> h5pF -> saveLibraryData ( $library , $new );
2013-07-08 17:02:05 +02:00
2012-12-22 06:35:16 +01:00
$current_path = $this -> h5pF -> getUploadedH5pFolderPath () . DIRECTORY_SEPARATOR . $key ;
2013-04-13 14:55:33 +02:00
$destination_path = $this -> h5pF -> getH5pPath () . DIRECTORY_SEPARATOR . 'libraries' . DIRECTORY_SEPARATOR . $this -> h5pC -> libraryToString ( $library , TRUE );
2012-12-22 06:35:16 +01:00
$this -> h5pC -> delTree ( $destination_path );
rename ( $current_path , $destination_path );
2013-07-09 10:14:42 +02:00
2013-07-08 17:12:40 +02:00
$library_saved = TRUE ;
2012-11-30 15:51:39 +01:00
}
2013-04-13 14:11:25 +02:00
2013-02-24 00:12:57 +01:00
foreach ( $this -> h5pC -> librariesJsonData as $key => & $library ) {
2013-04-13 14:11:25 +02:00
if ( $library [ 'saveDependencies' ]) {
$this -> h5pF -> deleteLibraryDependencies ( $library [ 'libraryId' ]);
if ( isset ( $library [ 'preloadedDependencies' ])) {
$this -> h5pF -> saveLibraryDependencies ( $library [ 'libraryId' ], $library [ 'preloadedDependencies' ], 'preloaded' );
}
if ( isset ( $library [ 'dynamicDependencies' ])) {
$this -> h5pF -> saveLibraryDependencies ( $library [ 'libraryId' ], $library [ 'dynamicDependencies' ], 'dynamic' );
}
if ( isset ( $library [ 'editorDependencies' ])) {
$this -> h5pF -> saveLibraryDependencies ( $library [ 'libraryId' ], $library [ 'editorDependencies' ], 'editor' );
}
2013-02-24 00:12:57 +01:00
}
}
2013-03-29 16:10:29 +01:00
// Move the content folder
2012-11-30 15:51:39 +01:00
$current_path = $this -> h5pF -> getUploadedH5pFolderPath () . DIRECTORY_SEPARATOR . 'content' ;
$destination_path = $this -> h5pF -> getH5pPath () . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId ;
rename ( $current_path , $destination_path );
2013-03-29 16:10:29 +01:00
// Save what libraries is beeing used by this package/content
2012-11-30 15:51:39 +01:00
$librariesInUse = array ();
$this -> getLibraryUsage ( $librariesInUse , $this -> h5pC -> mainJsonData );
$this -> h5pF -> saveLibraryUsage ( $contentId , $librariesInUse );
2012-12-22 06:35:16 +01:00
$this -> h5pC -> delTree ( $this -> h5pF -> getUploadedH5pFolderPath ());
2013-07-09 10:14:42 +02:00
2013-03-29 16:10:29 +01:00
// Save the data in content.json
2013-03-07 04:12:59 +01:00
$contentJson = file_get_contents ( $destination_path . DIRECTORY_SEPARATOR . 'content.json' );
2013-03-08 18:30:53 +01:00
$mainLibraryId = $librariesInUse [ $this -> h5pC -> mainJsonData [ 'mainLibrary' ]][ 'library' ][ 'libraryId' ];
$this -> h5pF -> saveContentData ( $contentId , $contentJson , $this -> h5pC -> mainJsonData , $mainLibraryId , $contentMainId );
2013-07-09 10:14:42 +02:00
2013-07-08 17:12:40 +02:00
return $library_saved ;
2012-11-30 15:51:39 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Delete an H5P package
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param int $contentId
* The content id
*/
2012-12-22 07:09:17 +01:00
public function deletePackage ( $contentId ) {
$this -> h5pC -> delTree ( $this -> h5pF -> getH5pPath () . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId );
$this -> h5pF -> deleteContentData ( $contentId );
}
2013-03-29 16:10:29 +01:00
/**
* Update an H5P package
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param int $contentId
* The content id
* @ param int $contentMainId
* The content main id ( used by frameworks supporting revisioning )
2013-07-08 17:12:40 +02:00
* @ return boolean
* TRUE if one or more libraries were updated
* FALSE otherwise
2013-03-29 16:10:29 +01:00
*/
2012-12-22 08:04:09 +01:00
public function updatePackage ( $contentId , $contentMainId = NULL ) {
$this -> deletePackage ( $contentId );
2013-07-08 17:12:40 +02:00
return $this -> savePackage ( $contentId , $contentMainId );
2012-12-22 08:04:09 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Copy / clone an H5P package
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* May for instance be used if the content is beeing revisioned without
* uploading a new H5P package
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param int $contentId
* The new content id
* @ param int $copyFromId
* The content id of the content that should be cloned
* @ param int $contentMainId
* The main id of the new content ( used in frameworks that support revisioning )
*/
2012-12-22 08:04:09 +01:00
public function copyPackage ( $contentId , $copyFromId , $contentMainId = NULL ) {
$source_path = $this -> h5pF -> getH5pPath () . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $copyFromId ;
$destination_path = $this -> h5pF -> getH5pPath () . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $contentId ;
$this -> h5pC -> copyTree ( $source_path , $destination_path );
2013-03-07 05:31:30 +01:00
$this -> h5pF -> copyLibraryUsage ( $contentId , $copyFromId , $contentMainId );
2012-12-22 08:04:09 +01:00
}
2013-03-29 16:10:29 +01:00
/**
* Identify what libraries are beeing used taking all dependencies into account
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param array $librariesInUse
* List of libraries in use , indexed by machineName
* @ param array $jsonData
* library . json og h5p . json data holding dependency information
* @ param boolean $dynamic
* Whether or not the current library is a dynamic dependency
*/
2012-11-30 15:51:39 +01:00
public function getLibraryUsage ( & $librariesInUse , $jsonData , $dynamic = FALSE ) {
if ( isset ( $jsonData [ 'preloadedDependencies' ])) {
foreach ( $jsonData [ 'preloadedDependencies' ] as $preloadedDependency ) {
2013-03-09 01:20:55 +01:00
$library = $this -> h5pF -> loadLibrary ( $preloadedDependency [ 'machineName' ], $preloadedDependency [ 'majorVersion' ], $preloadedDependency [ 'minorVersion' ]);
$librariesInUse [ $preloadedDependency [ 'machineName' ]] = array (
2012-12-01 22:18:48 +01:00
'library' => $library ,
2012-11-30 15:51:39 +01:00
'preloaded' => $dynamic ? 0 : 1 ,
);
2012-12-01 22:18:48 +01:00
$this -> getLibraryUsage ( $librariesInUse , $library , $dynamic );
2012-11-30 15:51:39 +01:00
}
}
if ( isset ( $jsonData [ 'dynamicDependencies' ])) {
foreach ( $jsonData [ 'dynamicDependencies' ] as $dynamicDependency ) {
2013-03-09 01:20:55 +01:00
if ( ! isset ( $librariesInUse [ $dynamicDependency [ 'machineName' ]])) {
$library = $this -> h5pF -> loadLibrary ( $dynamicDependency [ 'machineName' ], $dynamicDependency [ 'majorVersion' ], $dynamicDependency [ 'minorVersion' ]);
$librariesInUse [ $dynamicDependency [ 'machineName' ]] = array (
2012-12-01 22:18:48 +01:00
'library' => $library ,
2012-11-30 15:51:39 +01:00
'preloaded' => 0 ,
);
}
2012-12-01 22:18:48 +01:00
$this -> getLibraryUsage ( $librariesInUse , $library , TRUE );
2012-11-29 22:52:41 +01:00
}
}
}
}
2013-03-29 16:10:29 +01:00
/**
* Functions and storage shared by the other H5P classes
*/
2013-02-17 15:04:30 +01:00
class H5PCore {
2013-07-09 10:14:42 +02:00
2013-04-10 17:08:57 +02:00
public static $styles = array (
'styles/h5p.css' ,
);
2013-04-03 15:39:59 +02:00
public static $scripts = array (
'js/jquery.js' ,
'js/h5p.js' ,
'js/flowplayer-3.2.12.min.js' ,
);
2013-07-09 10:14:42 +02:00
2012-11-29 22:52:41 +01:00
public $h5pF ;
2012-11-30 15:51:39 +01:00
public $librariesJsonData ;
public $contentJsonData ;
public $mainJsonData ;
2012-11-29 22:52:41 +01:00
/**
2013-02-17 15:04:30 +01:00
* Constructor for the H5PCore
2012-11-29 22:52:41 +01:00
*
2013-02-17 15:04:30 +01:00
* @ param object $H5PFramework
2013-02-17 15:39:26 +01:00
* The frameworks implementation of the H5PFrameworkInterface
2012-11-29 22:52:41 +01:00
*/
2013-02-17 15:04:30 +01:00
public function __construct ( $H5PFramework ) {
$this -> h5pF = $H5PFramework ;
2012-11-29 22:52:41 +01:00
}
2013-07-09 10:14:42 +02:00
2013-03-29 16:10:29 +01:00
/**
* Check if a library is of the version we ' re looking for
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* Same verision means that the majorVersion and minorVersion is the same
2013-07-09 10:14:42 +02:00
*
2013-03-29 16:10:29 +01:00
* @ param array $library
* Data from library . json
* @ param array $dependency
* Definition of what library we ' re looking for
* @ return boolean
* TRUE if the library is the same version as the dependency
* FALSE otherwise
*/
2012-12-04 22:26:20 +01:00
public function isSameVersion ( $library , $dependency ) {
if ( $library [ 'majorVersion' ] != $dependency [ 'majorVersion' ]) {
return FALSE ;
}
if ( $library [ 'minorVersion' ] != $dependency [ 'minorVersion' ]) {
return FALSE ;
}
return TRUE ;
}
2012-12-22 06:35:16 +01:00
/**
* Recursive function for removing directories .
*
2013-03-29 16:10:29 +01:00
* @ param string $dir
* Path to the directory we ' ll be deleting
* @ return boolean
* Indicates if the directory existed .
2012-12-22 06:35:16 +01:00
*/
public function delTree ( $dir ) {
if ( ! is_dir ( $dir )) {
return ;
}
$files = array_diff ( scandir ( $dir ), array ( '.' , '..' ));
foreach ( $files as $file ) {
( is_dir ( " $dir / $file " )) ? $this -> delTree ( " $dir / $file " ) : unlink ( " $dir / $file " );
}
return rmdir ( $dir );
}
2012-12-22 08:04:09 +01:00
2013-03-29 16:10:29 +01:00
/**
* Recursive function for copying directories .
*
* @ param string $source
* Path to the directory we ' ll be copying
* @ return boolean
* Indicates if the directory existed .
*/
2012-12-22 08:04:09 +01:00
public function copyTree ( $source , $destination ) {
$dir = opendir ( $source );
@ mkdir ( $destination );
while ( false !== ( $file = readdir ( $dir ))) {
if (( $file != '.' ) && ( $file != '..' )) {
2013-06-30 15:32:12 +02:00
if ( is_dir ( $source . DIRECTORY_SEPARATOR . $file )) {
$this -> copyTree ( $source . DIRECTORY_SEPARATOR . $file , $destination . DIRECTORY_SEPARATOR . $file );
2012-12-22 08:04:09 +01:00
}
else {
2013-06-30 15:32:12 +02:00
copy ( $source . DIRECTORY_SEPARATOR . $file , $destination . DIRECTORY_SEPARATOR . $file );
2012-12-22 08:04:09 +01:00
}
}
}
closedir ( $dir );
2013-03-29 16:10:29 +01:00
}
2013-04-13 13:28:48 +02:00
/**
* Writes library data as string on the form { machineName } { majorVersion } . { minorVersion }
*
* @ param array $library
* With keys machineName , majorVersion and minorVersion
2013-07-05 17:35:59 +02:00
* @ param boolean $folderName
* Use hyphen instead of space in returned string .
2013-04-13 13:28:48 +02:00
* @ return string
* On the form { machineName } { majorVersion } . { minorVersion }
*/
2013-04-13 14:55:33 +02:00
public function libraryToString ( $library , $folderName = FALSE ) {
2013-04-13 15:03:04 +02:00
return $library [ 'machineName' ] . ( $folderName ? '-' : ' ' ) . $library [ 'majorVersion' ] . '.' . $library [ 'minorVersion' ];
2013-04-13 13:28:48 +02:00
}
2013-07-05 17:35:59 +02:00
/**
2013-07-09 11:01:29 +02:00
* Parses library data from a string on the form { machineName } { majorVersion } . { minorVersion }
2013-07-05 17:35:59 +02:00
*
* @ param string $libraryString
* On the form { machineName } { majorVersion } . { minorVersion }
* @ return array | FALSE
* With keys machineName , majorVersion and minorVersion .
* Returns FALSE only if string is not parsable in the normal library
* string formats " Lib.Name-x.y " or " Lib.Name x.y "
*/
public function libraryFromString ( $libraryString ) {
$re = '/^([\w0-9\-\.]{1,255})[\-\ ]([0-9]{1,5})\.([0-9]{1,5})$/i' ;
$matches = array ();
$res = preg_match ( $re , $libraryString , $matches );
if ( $res ) {
return array (
'machineName' => $matches [ 1 ],
'majorVersion' => $matches [ 2 ],
'minorVersion' => $matches [ 3 ]
);
}
return FALSE ;
}
2012-11-29 22:52:41 +01:00
}
2013-07-05 17:35:59 +02:00
/**
* Functions for validating basic types from H5P library semantics .
*/
class H5PContentValidator {
public $h5pF ;
public $h5pC ;
private $typeMap ;
private $semanticsCache ;
/**
* Constructor for the H5PContentValidator
*
* @ param object $H5PFramework
* The frameworks implementation of the H5PFrameworkInterface
* @ param object $H5PCore
* The main H5PCore instance
*/
public function __construct ( $H5PFramework , $H5PCore ) {
$this -> h5pF = $H5PFramework ;
$this -> h5pC = $H5PCore ;
$this -> typeMap = array (
'text' => 'validateText' ,
'number' => 'validateNumber' ,
'boolean' => 'validateBoolean' ,
'list' => 'validateList' ,
'group' => 'validateGroup' ,
2013-07-10 09:59:35 +02:00
'file' => 'validateFile' ,
2013-07-05 17:35:59 +02:00
'image' => 'validateImage' ,
'video' => 'validateVideo' ,
'audio' => 'validateAudio' ,
'select' => 'validateSelect' ,
'library' => 'validateLibrary' ,
);
// Cache for semantics used within this validation to avoid unneccessary
// json_decodes if a library is used multiple times.
$this -> semanticsCache = array ();
}
/**
* Validate the given value from content with the matching semantics
* object from semantics
*
* Function will recurse via external functions for container objects like
* 'list' , 'group' and 'library' .
*
* @ param object $value
* Object to be verified . May be a string or an array . ( normal or keyed )
* @ param object $semantics
* Semantics object from semantics . json for main library . Further
* semantics will be loaded from H5PFramework if any libraries are
* found within the value data .
*/
public function validateBySemantics ( & $value , $semantics ) {
$fakebaseobject = ( object ) array (
'type' => 'group' ,
'fields' => $semantics ,
);
2013-07-08 14:56:48 +02:00
$this -> validateGroup ( $value , $fakebaseobject , FALSE );
2013-07-05 17:35:59 +02:00
}
/**
* Validate given text value against text semantics .
*/
public function validateText ( & $text , $semantics ) {
2013-07-11 14:36:31 +02:00
if ( isset ( $semantics -> tags )) {
// Not testing for empty array allows us to use the 4 defaults without
// specifying them in semantics.
$tags = array_merge ( array ( 'div' , 'span' , 'p' , 'br' ), $semantics -> tags );
2013-07-10 09:59:35 +02:00
// Add related tags for table etc.
2013-07-11 13:12:17 +02:00
if ( in_array ( 'table' , $tags )) {
2013-07-10 09:59:35 +02:00
$tags = array_merge ( $tags , array ( 'tr' , 'td' , 'th' , 'colgroup' , 'thead' , 'tbody' , 'tfoot' ));
}
2013-07-11 14:36:31 +02:00
if ( in_array ( 'b' , $tags ) && ! in_array ( 'strong' , $tags )) {
2013-07-10 09:59:35 +02:00
$tags [] = 'strong' ;
}
2013-07-11 14:36:31 +02:00
if ( in_array ( 'i' , $tags ) && ! in_array ( 'em' , $tags )) {
2013-07-10 09:59:35 +02:00
$tags [] = 'em' ;
}
2013-07-11 14:36:31 +02:00
if ( in_array ( 'ul' , $tags ) || in_array ( 'ol' , $tags ) && ! in_array ( 'li' , $tags )) {
2013-07-10 09:59:35 +02:00
$tags [] = 'li' ;
}
2013-07-05 17:35:59 +02:00
// Strip invalid HTML tags.
2013-07-12 14:49:37 +02:00
$text = $this -> filter_xss ( $text , $tags );
2013-07-05 17:35:59 +02:00
}
else {
// Filter text to plain text.
2013-07-11 15:17:26 +02:00
$text = htmlspecialchars ( $text , ENT_QUOTES , 'UTF-8' );
2013-07-05 17:35:59 +02:00
}
2013-07-11 14:36:31 +02:00
2013-07-08 15:28:45 +02:00
// Check if string is within allowed length
if ( isset ( $semantics -> maxLength )) {
$text = mb_substr ( $text , 0 , $semantics -> maxLength );
}
2013-07-11 14:36:31 +02:00
2013-07-08 15:28:45 +02:00
// Check if string is according to optional regexp in semantics
if ( isset ( $semantics -> regexp )) {
2013-07-10 11:02:17 +02:00
$pattern = '|' . $semantics -> regexp -> pattern . '|' ;
2013-07-08 15:28:45 +02:00
$pattern .= isset ( $semantics -> regexp -> modifiers ) ? $semantics -> regexp -> modifiers : '' ;
if ( preg_match ( $pattern , $text ) === 0 ) {
// Note: explicitly ignore return value FALSE, to avoid removing text
// if regexp is invalid...
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Provided string is not valid according to regexp in semantics.' ));
$text = '' ;
}
}
2013-07-05 17:35:59 +02:00
}
private function bracketTags ( $tag ) {
return '<' . $tag . '>' ;
}
/**
* Validate given value against number semantics
*/
public function validateNumber ( & $number , $semantics ) {
// Validate that $number is indeed a number
if ( ! is_numeric ( $number )) {
$number = 0 ;
}
2013-07-08 15:28:45 +02:00
// Check if number is within valid bounds. Move within bounds if not.
if ( isset ( $semantics -> min ) && $number < $semantics -> min ) {
$number = $semantics -> min ;
}
if ( isset ( $semantics -> max ) && $number > $semantics -> max ) {
$number = $semantics -> max ;
}
// Check if number is within allowed bounds even if step value is set.
if ( isset ( $semantics -> step )) {
$testnumber = $number - ( isset ( $semantics -> min ) ? $semantics -> min : 0 );
$rest = $testnumber % $semantics -> step ;
if ( $rest !== 0 ) {
$number -= $rest ;
}
}
// Check if number has proper number of decimals.
if ( isset ( $semantics -> decimals )) {
$number = round ( $number , $semantics -> decimals );
}
2013-07-05 17:35:59 +02:00
}
/**
* Validate given value against boolean semantics
*/
public function validateBoolean ( & $bool , $semantics ) {
if ( ! is_bool ( $bool )) {
$bool = FALSE ;
}
}
/**
* Validate select values
*/
public function validateSelect ( & $select , $semantics ) {
2013-07-08 14:56:48 +02:00
// Special case for dynamicCheckboxes (valid options are generated live)
2013-07-09 09:41:57 +02:00
if ( isset ( $semantics -> widget ) && $semantics -> widget == 'dynamicCheckboxes' ) {
2013-07-08 14:56:48 +02:00
// No practical way to guess valid parameters. Just make sure we don't
// have special chars here. Also, dynamicCheckboxes will insert an
// array, so iterate it.
foreach ( $select as $key => $value ) {
2013-07-11 15:17:26 +02:00
$select [ $key ] = htmlspecialchars ( $value , ENT_QUOTES , 'UTF-8' );
2013-07-08 14:56:48 +02:00
}
}
else if ( ! in_array ( $select , array_map ( array ( $this , 'map_object_value' ), $semantics -> options ))) {
2013-07-05 17:35:59 +02:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Invalid selected option in select.' ));
$select = $semantics -> options [ 0 ] -> value ;
}
}
private function map_object_value ( $o ) {
return $o -> value ;
}
/**
* Validate given list value agains list semantics .
* Will recurse into validating each item in the list according to the type .
*/
public function validateList ( & $list , $semantics ) {
$field = $semantics -> field ;
$function = $this -> typeMap [ $field -> type ];
2013-07-08 15:28:45 +02:00
// Check that list is not longer than allowed length. We do this before
// iterating to avoid unneccessary work.
if ( isset ( $semantics -> max )) {
array_splice ( $list , $semantics -> max );
}
// Validate each element in list.
2013-07-05 17:35:59 +02:00
foreach ( $list as $key => $value ) {
$this -> $function ( $value , $field );
}
}
2013-07-11 15:17:26 +02:00
// Validate a filelike object, such as video, image, audio and file.
private function _validateFilelike ( & $file , $semantics , $typevalidkeys = array ()) {
// Make sure path and mime does not have any special chars
$file -> path = htmlspecialchars ( $file -> path , ENT_QUOTES , 'UTF-8' );
if ( isset ( $file -> mime )) {
$file -> mime = htmlspecialchars ( $file -> mime , ENT_QUOTES , 'UTF-8' );
}
2013-07-10 09:59:35 +02:00
// Remove attributes that should not exist, they may contain JSON escape
// code.
2013-07-11 15:17:26 +02:00
$validkeys = array_merge ( array ( 'path' , 'mime' ), $typevalidkeys );
2013-07-10 09:59:35 +02:00
if ( isset ( $semantics -> extraAttributes )) {
$validkeys = array_merge ( $validkeys , $semantics -> extraAttributes );
}
2013-07-11 15:17:26 +02:00
foreach ( $file as $key => $value ) {
2013-07-10 09:59:35 +02:00
if ( ! in_array ( $key , $validkeys )) {
2013-07-11 15:17:26 +02:00
unset ( $file -> $key );
2013-07-10 09:59:35 +02:00
}
}
}
2013-07-11 15:17:26 +02:00
/**
* Validate given file data
*/
public function validateFile ( & $file , $semantics ) {
$this -> _validateFilelike ( $file , $semantics );
}
2013-07-05 17:35:59 +02:00
/**
* Validate given image data
*/
public function validateImage ( & $image , $semantics ) {
2013-07-11 15:17:26 +02:00
$this -> _validateFilelike ( $image , $semantics , array ( 'width' , 'height' ));
2013-07-05 17:35:59 +02:00
}
/**
* Validate given video data
*/
public function validateVideo ( & $video , $semantics ) {
2013-07-08 14:56:48 +02:00
foreach ( $video as $variant ) {
2013-07-11 15:17:26 +02:00
$this -> _validateFilelike ( $variant , $semantics , array ( 'width' , 'height' ));
2013-07-05 17:35:59 +02:00
}
}
/**
* Validate given audio data
*/
public function validateAudio ( & $audio , $semantics ) {
2013-07-08 14:56:48 +02:00
foreach ( $audio as $variant ) {
2013-07-11 15:17:26 +02:00
$this -> _validateFilelike ( $variant , $semantics );
2013-07-05 17:35:59 +02:00
}
}
/**
* Validate given group value against group semantics .
* Will recurse into validating each group member .
*/
2013-07-08 14:56:48 +02:00
public function validateGroup ( & $group , $semantics , $flatten = TRUE ) {
// Groups with just one field are compressed in the editor to only output
// the child content. (Exemption for fake groups created by
// "validateBySemantics" above)
if ( count ( $semantics -> fields ) == 1 && $flatten ) {
$field = $semantics -> fields [ 0 ];
$function = $this -> typeMap [ $field -> type ];
$this -> $function ( $group , $field );
}
else {
foreach ( $group as $key => & $value ) {
// Find semantics for name=$key
$found = FALSE ;
foreach ( $semantics -> fields as $field ) {
if ( $field -> name == $key ) {
$function = $this -> typeMap [ $field -> type ];
$found = TRUE ;
break ;
}
}
if ( $found ) {
$this -> $function ( $value , $field );
}
else {
// If validator is not found, something exists in content that does
// not have a corresponding semantics field. Remove it.
2013-07-11 15:13:15 +02:00
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'H5P internal error: no validator exists for @key' , array ( '@key' => $key )));
2013-07-08 14:56:48 +02:00
unset ( $group -> $key );
2013-07-05 17:35:59 +02:00
}
2013-07-09 10:10:32 +02:00
}
}
foreach ( $semantics -> fields as $field ) {
if ( ! ( isset ( $field -> optional ) && $field -> optional )) {
// Check if field is in group.
if ( ! property_exists ( $group , $field -> name )) {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'No value given for mandatory field ' . $field -> name ));
}
2013-07-05 17:35:59 +02:00
}
}
}
/**
* Validate given library value against library semantics .
*
* Will recurse into validating the library ' s semantics too .
*/
public function validateLibrary ( & $value , $semantics ) {
// Check if provided library is within allowed options
if ( in_array ( $value -> library , $semantics -> options )) {
2013-07-09 14:43:22 +02:00
if ( isset ( $this -> semanticsCache [ $value -> library ])) {
$librarySemantics = $this -> semanticsCache [ $value -> library ];
2013-07-05 17:35:59 +02:00
}
else {
$libspec = $this -> h5pC -> libraryFromString ( $value -> library );
2013-07-09 15:13:09 +02:00
$librarySemantics = $this -> h5pF -> getLibrarySemantics ( $libspec [ 'machineName' ], $libspec [ 'majorVersion' ], $libspec [ 'minorVersion' ]);
2013-07-09 14:43:22 +02:00
$this -> semanticsCache [ $value -> library ] = $librarySemantics ;
2013-07-05 17:35:59 +02:00
}
2013-07-08 14:56:48 +02:00
$this -> validateBySemantics ( $value -> params , $librarySemantics );
2013-07-05 17:35:59 +02:00
}
else {
$this -> h5pF -> setErrorMessage ( $this -> h5pF -> t ( 'Library used in content is not a valid library according to semantics' ));
}
}
2013-07-12 14:49:37 +02:00
// XSS filters copied from drupal 7 common.inc. Some modifications done to
// replace Drupal one-liner functions with corresponding flat PHP.
/**
* Filters HTML to prevent cross - site - scripting ( XSS ) vulnerabilities .
*
* Based on kses by Ulf Harnhammar , see http :// sourceforge . net / projects / kses .
* For examples of various XSS attacks , see : http :// ha . ckers . org / xss . html .
*
* This code does four things :
* - Removes characters and constructs that can trick browsers .
* - Makes sure all HTML entities are well - formed .
* - Makes sure all HTML tags and attributes are well - formed .
* - Makes sure no HTML tags contain URLs with a disallowed protocol ( e . g .
* javascript : ) .
*
* @ param $string
* The string with raw HTML in it . It will be stripped of everything that can
* cause an XSS attack .
* @ param $allowed_tags
* An array of allowed tags .
*
* @ return
* An XSS safe version of $string , or an empty string if $string is not
* valid UTF - 8.
*
* @ ingroup sanitization
*/
private function filter_xss ( $string , $allowed_tags = array ( 'a' , 'em' , 'strong' , 'cite' , 'blockquote' , 'code' , 'ul' , 'ol' , 'li' , 'dl' , 'dt' , 'dd' )) {
if ( strlen ( $string ) == 0 ) {
return $string ;
}
// Only operate on valid UTF-8 strings. This is necessary to prevent cross
// site scripting issues on Internet Explorer 6. (Line copied from
// drupal_validate_utf8)
if ( preg_match ( '/^./us' , $string ) != 1 ) {
return '' ;
}
// Store the text format.
$this -> _filter_xss_split ( $allowed_tags , TRUE );
// Remove NULL characters (ignored by some browsers).
$string = str_replace ( chr ( 0 ), '' , $string );
// Remove Netscape 4 JS entities.
$string = preg_replace ( '%&\s*\{[^}]*(\}\s*;?|$)%' , '' , $string );
// Defuse all HTML entities.
$string = str_replace ( '&' , '&' , $string );
// Change back only well-formed entities in our whitelist:
// Decimal numeric entities.
$string = preg_replace ( '/&#([0-9]+;)/' , '&#\1' , $string );
// Hexadecimal numeric entities.
$string = preg_replace ( '/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/' , '&#x\1' , $string );
// Named entities.
$string = preg_replace ( '/&([A-Za-z][A-Za-z0-9]*;)/' , '&\1' , $string );
return preg_replace_callback ( ' %
(
< ( ? = [ ^ a - zA - Z !/ ]) # a lone <
| # or
<!--.* ? --> # a comment
| # or
< [ ^> ] * ( >| $ ) # a string that starts with a <, up until the > or the end of the string
| # or
> # just a >
) % x ', array($this, ' _filter_xss_split ' ), $string );
}
/**
* Processes an HTML tag .
*
* @ param $m
* An array with various meaning depending on the value of $store .
* If $store is TRUE then the array contains the allowed tags .
* If $store is FALSE then the array has one element , the HTML tag to process .
* @ param $store
* Whether to store $m .
*
* @ return
* If the element isn ' t allowed , an empty string . Otherwise , the cleaned up
* version of the HTML element .
*/
private function _filter_xss_split ( $m , $store = FALSE ) {
static $allowed_html ;
if ( $store ) {
$allowed_html = array_flip ( $m );
return ;
}
$string = $m [ 1 ];
if ( substr ( $string , 0 , 1 ) != '<' ) {
// We matched a lone ">" character.
return '>' ;
}
elseif ( strlen ( $string ) == 1 ) {
// We matched a lone "<" character.
return '<' ;
}
if ( ! preg_match ( '%^<\s*(/\s*)?([a-zA-Z0-9]+)([^>]*)>?|(<!--.*?-->)$%' , $string , $matches )) {
// Seriously malformed.
return '' ;
}
$slash = trim ( $matches [ 1 ]);
$elem = & $matches [ 2 ];
$attrlist = & $matches [ 3 ];
$comment = & $matches [ 4 ];
if ( $comment ) {
$elem = '!--' ;
}
if ( ! isset ( $allowed_html [ strtolower ( $elem )])) {
// Disallowed HTML element.
return '' ;
}
if ( $comment ) {
return $comment ;
}
if ( $slash != '' ) {
return " </ $elem > " ;
}
// Is there a closing XHTML slash at the end of the attributes?
$attrlist = preg_replace ( '%(\s?)/\s*$%' , '\1' , $attrlist , - 1 , $count );
$xhtml_slash = $count ? ' /' : '' ;
// Clean up attributes.
$attr2 = implode ( ' ' , $this -> _filter_xss_attributes ( $attrlist ));
$attr2 = preg_replace ( '/[<>]/' , '' , $attr2 );
$attr2 = strlen ( $attr2 ) ? ' ' . $attr2 : '' ;
return " < $elem $attr2 $xhtml_slash > " ;
}
/**
* Processes a string of HTML attributes .
*
* @ return
* Cleaned up version of the HTML attributes .
*/
private function _filter_xss_attributes ( $attr ) {
$attrarr = array ();
$mode = 0 ;
$attrname = '' ;
while ( strlen ( $attr ) != 0 ) {
// Was the last operation successful?
$working = 0 ;
switch ( $mode ) {
case 0 :
// Attribute name, href for instance.
if ( preg_match ( '/^([-a-zA-Z]+)/' , $attr , $match )) {
$attrname = strtolower ( $match [ 1 ]);
$skip = ( $attrname == 'style' || substr ( $attrname , 0 , 2 ) == 'on' );
$working = $mode = 1 ;
$attr = preg_replace ( '/^[-a-zA-Z]+/' , '' , $attr );
}
break ;
case 1 :
// Equals sign or valueless ("selected").
if ( preg_match ( '/^\s*=\s*/' , $attr )) {
$working = 1 ; $mode = 2 ;
$attr = preg_replace ( '/^\s*=\s*/' , '' , $attr );
break ;
}
if ( preg_match ( '/^\s+/' , $attr )) {
$working = 1 ; $mode = 0 ;
if ( ! $skip ) {
$attrarr [] = $attrname ;
}
$attr = preg_replace ( '/^\s+/' , '' , $attr );
}
break ;
case 2 :
// Attribute value, a URL after href= for instance.
if ( preg_match ( '/^"([^"]*)"(\s+|$)/' , $attr , $match )) {
$thisval = $this -> filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname = \" $thisval\ " " ;
}
$working = 1 ;
$mode = 0 ;
$attr = preg_replace ( '/^"[^"]*"(\s+|$)/' , '' , $attr );
break ;
}
if ( preg_match ( " /^'([^']*)'( \ s+| $ )/ " , $attr , $match )) {
$thisval = $this -> filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname =' $thisval ' " ;
}
$working = 1 ; $mode = 0 ;
$attr = preg_replace ( " /^'[^']*'( \ s+| $ )/ " , '' , $attr );
break ;
}
if ( preg_match ( " %^([^ \ s \" ']+)( \ s+| $ )% " , $attr , $match )) {
$thisval = $this -> filter_xss_bad_protocol ( $match [ 1 ]);
if ( ! $skip ) {
$attrarr [] = " $attrname = \" $thisval\ " " ;
}
$working = 1 ; $mode = 0 ;
$attr = preg_replace ( " %^[^ \ s \" ']+( \ s+| $ )% " , '' , $attr );
}
break ;
}
if ( $working == 0 ) {
// Not well formed; remove and try again.
$attr = preg_replace ( ' /
^
(
" [^ " ] * ( " | $ ) # - a string that starts with a double quote, up until the next double quote or the end of the string
| # or
\ ' [ ^ \ ' ] * ( \ ' | $ ) | # - a string that starts with a quote, up until the next quote or the end of the string
| # or
\S # - a non-whitespace character
) * # any number of the above three
\s * # any number of whitespaces
/ x ', ' ' , $attr );
$mode = 0 ;
}
}
// The attribute list ends with a valueless attribute like "selected".
if ( $mode == 1 && ! $skip ) {
$attrarr [] = $attrname ;
}
return $attrarr ;
}
/**
* Processes an HTML attribute value and strips dangerous protocols from URLs .
*
* @ param $string
* The string with the attribute value .
* @ param $decode
* ( deprecated ) Whether to decode entities in the $string . Set to FALSE if the
* $string is in plain text , TRUE otherwise . Defaults to TRUE . This parameter
* is deprecated and will be removed in Drupal 8. To process a plain - text URI ,
* call _strip_dangerous_protocols () or check_url () instead .
*
* @ return
* Cleaned up and HTML - escaped version of $string .
*/
private function filter_xss_bad_protocol ( $string , $decode = TRUE ) {
// Get the plain text representation of the attribute value (i.e. its meaning).
// @todo Remove the $decode parameter in Drupal 8, and always assume an HTML
// string that needs decoding.
if ( $decode ) {
$string = html_entity_decode ( $string , ENT_QUOTES , 'UTF-8' );
}
return check_plain ( $this -> _strip_dangerous_protocols ( $string ));
}
/**
* Strips dangerous protocols ( e . g . 'javascript:' ) from a URI .
*
* This function must be called for all URIs within user - entered input prior
* to being output to an HTML attribute value . It is often called as part of
* check_url () or filter_xss (), but those functions return an HTML - encoded
* string , so this function can be called independently when the output needs to
* be a plain - text string for passing to t (), l (), drupal_attributes (), or
* another function that will call check_plain () separately .
*
* @ param $uri
* A plain - text URI that might contain dangerous protocols .
*
* @ return
* A plain - text URI stripped of dangerous protocols . As with all plain - text
* strings , this return value must not be output to an HTML page without
* check_plain () being called on it . However , it can be passed to functions
* expecting plain - text strings .
*
* @ see check_url ()
*/
private function _strip_dangerous_protocols ( $uri ) {
static $allowed_protocols ;
if ( ! isset ( $allowed_protocols )) {
$allowed_protocols = array_flip ( array ( 'ftp' , 'http' , 'https' , 'mailto' ));
}
// Iteratively remove any invalid protocol found.
do {
$before = $uri ;
$colonpos = strpos ( $uri , ':' );
if ( $colonpos > 0 ) {
// We found a colon, possibly a protocol. Verify.
$protocol = substr ( $uri , 0 , $colonpos );
// If a colon is preceded by a slash, question mark or hash, it cannot
// possibly be part of the URL scheme. This must be a relative URL, which
// inherits the (safe) protocol of the base document.
if ( preg_match ( '![/?#]!' , $protocol )) {
break ;
}
// Check if this is a disallowed protocol. Per RFC2616, section 3.2.3
// (URI Comparison) scheme comparison must be case-insensitive.
if ( ! isset ( $allowed_protocols [ strtolower ( $protocol )])) {
$uri = substr ( $uri , $colonpos + 1 );
}
}
} while ( $before != $uri );
return $uri ;
}
2013-07-05 17:35:59 +02:00
}
2012-11-29 22:52:41 +01:00
?>