Merge branch 'master' of github.com:h5p/h5p-php-library

pull/23/head
Frode Petterson 2016-06-21 09:34:37 +02:00
commit 18e2b79cc6
7 changed files with 262 additions and 110 deletions

View File

@ -1055,24 +1055,27 @@ class H5PValidator {
$valid = $this->isValidRequiredH5pData($h5pData, $required, $library_name); $valid = $this->isValidRequiredH5pData($h5pData, $required, $library_name);
$valid = $this->isValidOptionalH5pData($h5pData, $optional, $library_name) && $valid; $valid = $this->isValidOptionalH5pData($h5pData, $optional, $library_name) && $valid;
// Test library core version requirement. If no requirement is set, // Check the library's required API version of Core.
// this implicitly means 1.0, which shall work on newer versions // If no requirement is set this implicitly means 1.0.
// too.
if (isset($h5pData['coreApi']) && !empty($h5pData['coreApi'])) { if (isset($h5pData['coreApi']) && !empty($h5pData['coreApi'])) {
if (($h5pData['coreApi']['majorVersion'] > H5PCore::$coreApi['majorVersion']) || if (($h5pData['coreApi']['majorVersion'] > H5PCore::$coreApi['majorVersion']) ||
(($h5pData['coreApi']['majorVersion'] == H5PCore::$coreApi['majorVersion']) && ( ($h5pData['coreApi']['majorVersion'] == H5PCore::$coreApi['majorVersion']) &&
($h5pData['coreApi']['minorVersion'] > H5PCore::$coreApi['minorVersion']))) ($h5pData['coreApi']['minorVersion'] > H5PCore::$coreApi['minorVersion']) )) {
{
$this->h5pF->setErrorMessage( $this->h5pF->setErrorMessage(
$this->h5pF->t('The library "%libraryName" requires H5P %requiredVersion, but only H5P %coreApi is installed.', $this->h5pF->t('The system was unable to install the <em>%component</em> component from the package, it requires a newer version of the H5P plugin. This site is currently running version %current, whereas the required version is %required or higher. You should consider upgrading and then try again.',
array( array(
'%libraryName' => $library_name, '%component' => (isset($h5pData['title']) ? $h5pData['title'] : $library_name),
'%requiredVersion' => $h5pData['coreApi']['majorVersion'] . '.' . $h5pData['coreApi']['minorVersion'], '%current' => H5PCore::$coreApi['majorVersion'] . '.' . H5PCore::$coreApi['minorVersion'],
'%coreApi' => H5PCore::$coreApi['majorVersion'] . '.' . H5PCore::$coreApi['minorVersion'] '%required' => $h5pData['coreApi']['majorVersion'] . '.' . $h5pData['coreApi']['minorVersion']
))); )
)
);
$valid = false; $valid = false;
} }
} }
return $valid; return $valid;
} }
@ -2469,7 +2472,9 @@ class H5PCore {
// Determine remote/visitor origin // Determine remote/visitor origin
if ($type === 'network' || if ($type === 'network' ||
($type === 'local' && !preg_match('/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/i', $_SERVER['REMOTE_ADDR']))) { ($type === 'local' &&
isset($_SERVER['REMOTE_ADDR']) &&
!preg_match('/^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*\:)*?:?0*1$/i', $_SERVER['REMOTE_ADDR']))) {
if (isset($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) { if (isset($_SERVER['REMOTE_ADDR']) && filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
// Internet // Internet
$this->h5pF->setOption('site_type', 'internet'); $this->h5pF->setOption('site_type', 'internet');
@ -2893,7 +2898,12 @@ class H5PContentValidator {
// Check if string is within allowed length // Check if string is within allowed length
if (isset($semantics->maxLength)) { if (isset($semantics->maxLength)) {
$text = mb_substr($text, 0, $semantics->maxLength); if (!extension_loaded('mbstring')) {
$this->h5pF->setErrorMessage($this->h5pF->t('The mbstring PHP extension is not loaded. H5P need this to function properly'), 'error');
}
else {
$text = mb_substr($text, 0, $semantics->maxLength);
}
} }
// Check if string is according to optional regexp in semantics // Check if string is according to optional regexp in semantics
@ -2943,7 +2953,11 @@ class H5PContentValidator {
// file name, 2. testing against a returned error array that could // file name, 2. testing against a returned error array that could
// never be more than 1 element long anyway, 3. recreating the regex // never be more than 1 element long anyway, 3. recreating the regex
// for every file. // for every file.
if (!preg_match($wl_regex, mb_strtolower($file))) { if (!extension_loaded('mbstring')) {
$this->h5pF->setErrorMessage($this->h5pF->t('The mbstring PHP extension is not loaded. H5P need this to function properly'), 'error');
$valid = FALSE;
}
else if (!preg_match($wl_regex, mb_strtolower($file))) {
$this->h5pF->setErrorMessage($this->h5pF->t('File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.', array('%filename' => $file, '%files-allowed' => $whitelist)), 'error'); $this->h5pF->setErrorMessage($this->h5pF->t('File "%filename" not allowed. Only files with the following extensions are allowed: %files-allowed.', array('%filename' => $file, '%files-allowed' => $whitelist)), 'error');
$valid = FALSE; $valid = FALSE;
} }

View File

@ -17,6 +17,10 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
EventDispatcher.call(this); EventDispatcher.call(this);
var self = this; var self = this;
// Make sure confirmation dialogs have unique id
H5P.ConfirmationDialog.uniqueId += 1;
var uniqueId = H5P.ConfirmationDialog.uniqueId;
// Default options // Default options
options = options || {}; options = options || {};
options.headerText = options.headerText || H5P.t('confirmDialogHeader'); options.headerText = options.headerText || H5P.t('confirmDialogHeader');
@ -24,6 +28,36 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
options.cancelText = options.cancelText || H5P.t('cancelLabel'); options.cancelText = options.cancelText || H5P.t('cancelLabel');
options.confirmText = options.confirmText || H5P.t('confirmLabel'); options.confirmText = options.confirmText || H5P.t('confirmLabel');
/**
* Handle confirming event
* @param {Event} e
*/
function dialogConfirmed(e) {
self.hide();
self.trigger('confirmed');
e.preventDefault();
}
/**
* Handle dialog canceled
* @param {Event} e
*/
function dialogCanceled(e) {
self.hide();
self.trigger('canceled');
e.preventDefault();
}
/**
* Flow focus to element
* @param {HTMLElement} element Next element to be focused
* @param {Event} e Original tab event
*/
function flowTo(element, e) {
element.focus();
e.preventDefault();
}
// Offset of exit button // Offset of exit button
var exitButtonOffset = 2 * 16; var exitButtonOffset = 2 * 16;
var shadowOffset = 8; var shadowOffset = 8;
@ -39,7 +73,15 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
// Create outer popup // Create outer popup
var popup = document.createElement('div'); var popup = document.createElement('div');
popup.classList.add('h5p-confirmation-dialog-popup', 'hidden'); popup.classList.add('h5p-confirmation-dialog-popup', 'hidden');
popup.setAttribute('role', 'dialog');
popup.setAttribute('aria-labelledby', 'h5p-confirmation-dialog-dialog-text-' + uniqueId);
popupBackground.appendChild(popup); popupBackground.appendChild(popup);
popup.addEventListener('keydown', function (e) {
if (e.which === 27) {// Esc key
// Exit dialog
dialogCanceled(e);
}
});
// Popup header // Popup header
var header = document.createElement('div'); var header = document.createElement('div');
@ -61,6 +103,7 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
var text = document.createElement('div'); var text = document.createElement('div');
text.classList.add('h5p-confirmation-dialog-text'); text.classList.add('h5p-confirmation-dialog-text');
text.innerHTML = options.dialogText; text.innerHTML = options.dialogText;
text.id = 'h5p-confirmation-dialog-dialog-text-' + uniqueId;
body.appendChild(text); body.appendChild(text);
// Popup buttons // Popup buttons
@ -69,89 +112,160 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
body.appendChild(buttons); body.appendChild(buttons);
// Cancel button // Cancel button
var cancelButton = document.createElement('a'); var cancelButton = document.createElement('button');
cancelButton.classList.add('h5p-core-cancel-button'); cancelButton.classList.add('h5p-core-cancel-button');
cancelButton.href = '#';
cancelButton.textContent = options.cancelText; cancelButton.textContent = options.cancelText;
cancelButton.onclick = function (e) {
self.hide();
self.trigger('canceled');
e.preventDefault();
};
cancelButton.onkeydown = function (e) {
if (e.which === 32) { // Space
// Prevent jumping
e.preventDefault();
}
else if (e.which === 13) { // Enter
self.hide();
self.trigger('canceled');
e.preventDefault();
}
};
buttons.appendChild(cancelButton);
// Confirm button // Confirm button
var confirmButton = document.createElement('a'); var confirmButton = document.createElement('button');
confirmButton.classList.add('h5p-core-button', confirmButton.classList.add('h5p-core-button',
'h5p-confirmation-dialog-confirm-button'); 'h5p-confirmation-dialog-confirm-button');
confirmButton.href = '#';
confirmButton.textContent = options.confirmText; confirmButton.textContent = options.confirmText;
confirmButton.onclick = function (e) {
self.hide();
self.trigger('confirmed');
e.preventDefault();
};
confirmButton.onkeydown = function (e) {
if (e.which === 32) { // Space
// Prevent jumping
e.preventDefault();
}
else if (e.which === 13) { // Enter
self.hide();
self.trigger('confirmed');
e.preventDefault();
}
};
buttons.appendChild(confirmButton);
// Exit button // Exit button
var exitButton = document.createElement('a'); var exitButton = document.createElement('button');
exitButton.href = '#';
exitButton.classList.add('h5p-confirmation-dialog-exit'); exitButton.classList.add('h5p-confirmation-dialog-exit');
exitButton.onclick = function (e) { exitButton.setAttribute('aria-hidden', 'true');
self.hide(); exitButton.tabIndex = -1;
self.trigger('canceled'); exitButton.title = options.cancelText;
e.preventDefault();
}; // Cancel handler
exitButton.onkeydown = function (e) { cancelButton.addEventListener('click', dialogCanceled);
cancelButton.addEventListener('keydown', function (e) {
if (e.which === 32) { // Space if (e.which === 32) { // Space
// Prevent jumping dialogCanceled(e);
e.preventDefault();
} }
else if (e.which === 13) { // Enter else if (e.which === 9 && e.shiftKey) { // Shift-tab
self.hide(); flowTo(confirmButton, e);
self.trigger('canceled');
e.preventDefault();
} }
}; });
buttons.appendChild(cancelButton);
// Confirm handler
confirmButton.addEventListener('click', dialogConfirmed);
confirmButton.addEventListener('keydown', function (e) {
if (e.which === 32) { // Space
dialogConfirmed(e);
}
else if (e.which === 9 && !e.shiftKey) { // Tab
flowTo(cancelButton, e);
}
});
buttons.appendChild(confirmButton);
// Exit handler
exitButton.addEventListener('click', dialogCanceled);
exitButton.addEventListener('keydown', function (e) {
if (e.which === 32) { // Space
dialogCanceled(e);
}
});
popup.appendChild(exitButton); popup.appendChild(exitButton);
// Wrapper element // Wrapper element
var wrapperElement; var wrapperElement;
// Focus capturing
var focusPredator;
// Maintains hidden state of elements
var wrapperSiblingsHidden = [];
var popupSiblingsHidden = [];
// Element with focus before dialog
var previouslyFocused;
/** /**
* Append confirmation dialog * Set parent of confirmation dialog
* @param {HTMLElement} wrapper * @param {HTMLElement} wrapper
* @returns {H5P.ConfirmationDialog} * @returns {H5P.ConfirmationDialog}
*/ */
this.appendTo = function (wrapper) { this.appendTo = function (wrapper) {
wrapper.appendChild(popupBackground);
wrapperElement = wrapper; wrapperElement = wrapper;
return this; return this;
}; };
/**
* Capture the focus element, send it to confirmation button
* @param {Event} e Original focus event
*/
var captureFocus = function (e) {
if (!popupBackground.contains(e.target)) {
e.preventDefault();
confirmButton.focus();
}
};
/**
* Hide siblings of element from assistive technology
*
* @param {HTMLElement} element
* @returns {Array} The previous hidden state of all siblings
*/
var hideSiblings = function (element) {
var hiddenSiblings = [];
var siblings = element.parentNode.children;
var i;
for (i = 0; i < siblings.length; i += 1) {
// Preserve hidden state
hiddenSiblings[i] = siblings[i].getAttribute('aria-hidden') ?
true : false;
if (siblings[i] !== element) {
siblings[i].setAttribute('aria-hidden', true);
}
}
return hiddenSiblings;
};
/**
* Restores assistive technology state of element's siblings
*
* @param {HTMLElement} element
* @param {Array} hiddenSiblings Hidden state of all siblings
*/
var restoreSiblings = function (element, hiddenSiblings) {
var siblings = element.parentNode.children;
var i;
for (i = 0; i < siblings.length; i += 1) {
if (siblings[i] !== element && !hiddenSiblings[i]) {
siblings[i].removeAttribute('aria-hidden');
}
}
};
/**
* Start capturing focus of parent and send it to dialog
*/
var startCapturingFocus = function () {
focusPredator = wrapperElement.parentNode || wrapperElement;
focusPredator.addEventListener('focus', captureFocus, true);
};
/**
* Clean up event listener for capturing focus
*/
var stopCapturingFocus = function () {
focusPredator.removeAttribute('aria-hidden');
focusPredator.removeEventListener('focus', captureFocus, true);
};
/**
* Hide siblings in underlay from assistive technologies
*/
var disableUnderlay = function () {
wrapperSiblingsHidden = hideSiblings(wrapperElement);
popupSiblingsHidden = hideSiblings(popupBackground);
};
/**
* Restore state of underlay for assistive technologies
*/
var restoreUnderlay = function () {
restoreSiblings(wrapperElement, wrapperSiblingsHidden);
restoreSiblings(popupBackground, popupSiblingsHidden);
};
/** /**
* Fit popup to container. Makes sure it doesn't overflow. * Fit popup to container. Makes sure it doesn't overflow.
* @params {number} [offsetTop] Offset of popup * @params {number} [offsetTop] Offset of popup
@ -182,6 +296,11 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
* @returns {H5P.ConfirmationDialog} * @returns {H5P.ConfirmationDialog}
*/ */
this.show = function (offsetTop) { this.show = function (offsetTop) {
// Capture focused item
previouslyFocused = document.activeElement;
wrapperElement.appendChild(popupBackground);
startCapturingFocus();
disableUnderlay();
popupBackground.classList.remove('hidden'); popupBackground.classList.remove('hidden');
fitToContainer(offsetTop); fitToContainer(offsetTop);
setTimeout(function () { setTimeout(function () {
@ -213,8 +332,14 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
this.hide = function () { this.hide = function () {
popupBackground.classList.add('hiding'); popupBackground.classList.add('hiding');
popup.classList.add('hidden'); popup.classList.add('hidden');
// Restore focus
stopCapturingFocus();
previouslyFocused.focus();
restoreUnderlay();
setTimeout(function () { setTimeout(function () {
popupBackground.classList.add('hidden'); popupBackground.classList.add('hidden');
wrapperElement.removeChild(popupBackground);
}, 100); }, 100);
return this; return this;
@ -227,3 +352,5 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
return ConfirmationDialog; return ConfirmationDialog;
}(H5P.EventDispatcher)); }(H5P.EventDispatcher));
H5P.ConfirmationDialog.uniqueId = -1;

View File

@ -108,11 +108,11 @@ var H5PDataView = (function ($) {
else { else {
// Update table data // Update table data
self.updateTable(data.rows); self.updateTable(data.rows);
// Update pagination widget
self.updatePagination(data.num);
} }
// Update pagination widget
self.updatePagination(data.num);
if (self.loaded !== undefined) { if (self.loaded !== undefined) {
self.loaded(); self.loaded();
} }
@ -255,7 +255,6 @@ var H5PDataView = (function ($) {
var remove = function () { var remove = function () {
self.facets[col].$tag.remove(); self.facets[col].$tag.remove();
delete self.facets[col]; delete self.facets[col];
self.offset = 0; // Reset to page 1
self.loadData(); self.loadData();
}; };
@ -276,9 +275,6 @@ var H5PDataView = (function ($) {
} }
}); });
// Reset to page 1
self.offset = 0;
// Load data with new filter // Load data with new filter
self.loadData(); self.loadData();
}; };
@ -292,6 +288,11 @@ var H5PDataView = (function ($) {
var self = this; var self = this;
if (self.pagination === undefined) { if (self.pagination === undefined) {
if (self.table === undefined) {
// No table, no pagination
return;
}
// Create new widget // Create new widget
var $pagerContainer = $('<div/>', {'class': 'h5p-pagination'}); var $pagerContainer = $('<div/>', {'class': 'h5p-pagination'});
self.pagination = new H5PUtils.Pagination(num, self.limit, function (offset) { self.pagination = new H5PUtils.Pagination(num, self.limit, function (offset) {

View File

@ -188,12 +188,18 @@ H5P.XAPIEvent.prototype.setActor = function () {
} }
else { else {
var uuid; var uuid;
if (localStorage.H5PUserUUID) { try {
uuid = localStorage.H5PUserUUID; if (localStorage.H5PUserUUID) {
uuid = localStorage.H5PUserUUID;
}
else {
uuid = H5P.createUUID();
localStorage.H5PUserUUID = uuid;
}
} }
else { catch (err) {
uuid = H5P.createUUID(); // LocalStorage and Cookies are probably disabled. Do not track the user.
localStorage.H5PUserUUID = uuid; uuid = 'not-trackable-' + H5P.createUUID();
} }
this.data.statement.actor = { this.data.statement.actor = {
'account': { 'account': {

View File

@ -122,13 +122,15 @@ H5P.init = function (target) {
delete contentData.contentUserData; delete contentData.contentUserData;
var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '<p>' + H5P.t('contentChanged') + '</p><p>' + H5P.t('startingOver') + '</p><div class="h5p-dialog-ok-button" tabIndex="0" role="button">OK</div>', $container); var dialog = new H5P.Dialog('content-user-data-reset', 'Data Reset', '<p>' + H5P.t('contentChanged') + '</p><p>' + H5P.t('startingOver') + '</p><div class="h5p-dialog-ok-button" tabIndex="0" role="button">OK</div>', $container);
H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) { H5P.jQuery(dialog).on('dialog-opened', function (event, $dialog) {
$dialog.find('.h5p-dialog-ok-button').click(function () {
dialog.close(); var closeDialog = function (event) {
}).keypress(function (event) { if (event.type === 'click' || event.which === 32) {
if (event.which === 32) {
dialog.close(); dialog.close();
H5P.deleteUserData(contentId, 'state', 0);
} }
}); };
$dialog.find('.h5p-dialog-ok-button').click(closeDialog).keypress(closeDialog);
}); });
dialog.open(); dialog.open();
} }

View File

@ -81,9 +81,9 @@
float: right; float: right;
} }
a.h5p-confirmation-dialog-exit:visited, button.h5p-confirmation-dialog-exit:visited,
a.h5p-confirmation-dialog-exit:link, button.h5p-confirmation-dialog-exit:link,
a.h5p-confirmation-dialog-exit { button.h5p-confirmation-dialog-exit {
position: absolute; position: absolute;
background: none; background: none;
border: none; border: none;
@ -95,8 +95,8 @@ a.h5p-confirmation-dialog-exit {
text-decoration: none; text-decoration: none;
} }
a.h5p-confirmation-dialog-exit:focus, button.h5p-confirmation-dialog-exit:focus,
a.h5p-confirmation-dialog-exit:hover { button.h5p-confirmation-dialog-exit:hover {
color: #555; color: #555;
} }

View File

@ -1,6 +1,6 @@
a.h5p-core-button:visited, button.h5p-core-button:visited,
a.h5p-core-button:link, button.h5p-core-button:link,
a.h5p-core-button { button.h5p-core-button {
font-size: 1em; font-size: 1em;
line-height: 1.2; line-height: 1.2;
padding: 0.5em 1.25em; padding: 0.5em 1.25em;
@ -20,15 +20,15 @@ a.h5p-core-button {
vertical-align: baseline; vertical-align: baseline;
text-decoration: none; text-decoration: none;
} }
a.h5p-core-button:hover, button.h5p-core-button:hover,
a.h5p-core-button:focus { button.h5p-core-button:focus {
background: #1356a3; background: #1356a3;
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
-webkit-transition: initial; -webkit-transition: initial;
transition: initial; transition: initial;
} }
a.h5p-core-button:active { button.h5p-core-button:active {
position: relative; position: relative;
background: #104888; background: #104888;
@ -37,7 +37,7 @@ a.h5p-core-button:active {
box-shadow: inset 0 4px 0 #0e407a; box-shadow: inset 0 4px 0 #0e407a;
} }
a.h5p-core-button:before { button.h5p-core-button:before {
font-family: 'H5P'; font-family: 'H5P';
padding-right: 0.15em; padding-right: 0.15em;
font-size: 1.5em; font-size: 1.5em;
@ -45,18 +45,20 @@ a.h5p-core-button:before {
line-height: 0.7; line-height: 0.7;
} }
a.h5p-core-cancel-button:visited, button.h5p-core-cancel-button:visited,
a.h5p-core-cancel-button:link, button.h5p-core-cancel-button:link,
a.h5p-core-cancel-button { button.h5p-core-cancel-button {
border: none;
background: none; background: none;
color: #a00; color: #a00;
margin-right: 1em; margin-right: 1em;
font-size: 1em; font-size: 1em;
text-decoration: none; text-decoration: none;
cursor: pointer;
} }
a.h5p-core-cancel-button:hover, button.h5p-core-cancel-button:hover,
a.h5p-core-cancel-button:focus { button.h5p-core-cancel-button:focus {
background: none; background: none;
border: none; border: none;
color: #e40000; color: #e40000;