Merge branch 'OPPG-413'
commit
b76f1395f3
314
h5p.classes.php
314
h5p.classes.php
|
@ -1071,11 +1071,325 @@ class H5PCore {
|
||||||
*
|
*
|
||||||
* @param array $library
|
* @param array $library
|
||||||
* With keys machineName, majorVersion and minorVersion
|
* With keys machineName, majorVersion and minorVersion
|
||||||
|
* @param boolean $folderName
|
||||||
|
* Use hyphen instead of space in returned string.
|
||||||
* @return string
|
* @return string
|
||||||
* On the form {machineName} {majorVersion}.{minorVersion}
|
* On the form {machineName} {majorVersion}.{minorVersion}
|
||||||
*/
|
*/
|
||||||
public function libraryToString($library, $folderName = FALSE) {
|
public function libraryToString($library, $folderName = FALSE) {
|
||||||
return $library['machineName'] . ($folderName ? '-' : ' ') . $library['majorVersion'] . '.' . $library['minorVersion'];
|
return $library['machineName'] . ($folderName ? '-' : ' ') . $library['majorVersion'] . '.' . $library['minorVersion'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes library data as string on the form {machineName} {majorVersion}.{minorVersion}
|
||||||
|
*
|
||||||
|
* @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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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',
|
||||||
|
'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,
|
||||||
|
);
|
||||||
|
$this->validateGroup($value, $fakebaseobject, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate given text value against text semantics.
|
||||||
|
*/
|
||||||
|
public function validateText(&$text, $semantics) {
|
||||||
|
if ($semantics->widget && $semantics->widget == 'html') {
|
||||||
|
// Build allowed tag list, based in $semantics->tags and known defaults.
|
||||||
|
// These four are always allowed.
|
||||||
|
$tags = array('div', 'span', 'p', 'br');
|
||||||
|
if (isset($semantics->tags)) {
|
||||||
|
$tags = array_merge($tags, $semantics->tags);
|
||||||
|
// Add related tags for table etc.
|
||||||
|
if (in_array('table', $semantics->tags)) {
|
||||||
|
$tags = array_merge($tags, array('tr', 'td', 'th', 'colgroup', 'thead', 'tbody', 'tfoot'));
|
||||||
|
}
|
||||||
|
if (in_array('b', $semantics->tags)) {
|
||||||
|
$tags[] = 'strong';
|
||||||
|
}
|
||||||
|
if (in_array('i', $semantics->tags)) {
|
||||||
|
$tags[] = 'em';
|
||||||
|
}
|
||||||
|
if (in_array('ul', $semantics->tags) || in_array('ol', $semantics->tags)) {
|
||||||
|
$tags[] = 'li';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$allowedtags = implode('', array_map(array($this, 'bracketTags'), $tags));
|
||||||
|
|
||||||
|
// Strip invalid HTML tags.
|
||||||
|
$text = strip_tags($text, $allowedtags);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Filter text to plain text.
|
||||||
|
$text = htmlspecialchars($text);
|
||||||
|
}
|
||||||
|
// Check if string is within allowed length
|
||||||
|
if (isset($semantics->maxLength)) {
|
||||||
|
$text = mb_substr($text, 0, $semantics->maxLength);
|
||||||
|
}
|
||||||
|
// Check if string is according to optional regexp in semantics
|
||||||
|
if (isset($semantics->regexp)) {
|
||||||
|
$pattern = $semantics->regexp->pattern;
|
||||||
|
$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 = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
// Special case for dynamicCheckboxes (valid options are generated live)
|
||||||
|
if ($semantics->widget == 'dynamicCheckboxes') {
|
||||||
|
// 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) {
|
||||||
|
$select[$key] = htmlspecialchars($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!in_array($select, array_map(array($this, 'map_object_value'), $semantics->options))) {
|
||||||
|
$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];
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
foreach ($list as $key => $value) {
|
||||||
|
$this->$function($value, $field);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate given image data
|
||||||
|
*/
|
||||||
|
public function validateImage(&$image, $semantics) {
|
||||||
|
$image->path = htmlspecialchars($image->path);
|
||||||
|
if ($image->mime && substr($image->mime, 0, 5) !== 'image') {
|
||||||
|
unset($image->mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate given video data
|
||||||
|
*/
|
||||||
|
public function validateVideo(&$video, $semantics) {
|
||||||
|
foreach ($video as $variant) {
|
||||||
|
$variant->path = htmlspecialchars($variant->path);
|
||||||
|
if ($variant->mime && substr($variant->mime, 0, 5) !== 'video') {
|
||||||
|
unset($variant->mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate given audio data
|
||||||
|
*/
|
||||||
|
public function validateAudio(&$audio, $semantics) {
|
||||||
|
foreach ($audio as $variant) {
|
||||||
|
$variant->path = htmlspecialchars($variant->path);
|
||||||
|
if ($variant->mime && substr($variant->mime, 0, 5) !== 'audio') {
|
||||||
|
unset($variant->mime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate given group value against group semantics.
|
||||||
|
* Will recurse into validating each group member.
|
||||||
|
*/
|
||||||
|
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.
|
||||||
|
$this->h5pF->setErrorMessage($this->h5pF->t('H5P internal error: no validator exists for ' . $key));
|
||||||
|
unset($group->$key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)) {
|
||||||
|
if (isset($semanticsCache[$value->library])) {
|
||||||
|
$librarySemantics = $semanticsCache[$value->library];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$libspec = $this->h5pC->libraryFromString($value->library);
|
||||||
|
$library = $this->h5pF->loadLibrary($libspec['machineName'], $libspec['majorVersion'], $libspec['minorVersion']);
|
||||||
|
$librarySemantics = json_decode($library['semantics']);
|
||||||
|
$semanticsCache[$value->library] = $librarySemantics;
|
||||||
|
}
|
||||||
|
$this->validateBySemantics($value->params, $librarySemantics);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->h5pF->setErrorMessage($this->h5pF->t('Library used in content is not a valid library according to semantics'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
?>
|
?>
|
Loading…
Reference in New Issue