Merge branch 'release' into improved-file-handling

pull/22/head
Frode Petterson 2016-06-13 09:58:18 +02:00
commit 5e87aee63c
2 changed files with 139 additions and 21 deletions

View File

@ -2898,8 +2898,13 @@ class H5PContentValidator {
// Check if string is within allowed length // Check if string is within allowed length
if (isset($semantics->maxLength)) { if (isset($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); $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
if (!($text === '' && isset($semantics->optional) && $semantics->optional) && isset($semantics->regexp)) { if (!($text === '' && isset($semantics->optional) && $semantics->optional) && isset($semantics->regexp)) {
@ -2948,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');
@ -70,13 +74,14 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
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('role', 'dialog');
popup.setAttribute('aria-labelledby', 'h5p-confirmation-dialog-dialog-text-' + uniqueId);
popupBackground.appendChild(popup); popupBackground.appendChild(popup);
popup.onkeydown = function (e) { popup.addEventListener('keydown', function (e) {
if (e.which === 27) {// Esc key if (e.which === 27) {// Esc key
// Exit dialog // Exit dialog
dialogCanceled(e); dialogCanceled(e);
} }
}; });
// Popup header // Popup header
var header = document.createElement('div'); var header = document.createElement('div');
@ -97,8 +102,8 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
// Popup text // Popup text
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.setAttribute('role', 'alert');
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
@ -120,44 +125,56 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
// Exit button // Exit button
var exitButton = document.createElement('button'); var exitButton = document.createElement('button');
exitButton.classList.add('h5p-confirmation-dialog-exit'); exitButton.classList.add('h5p-confirmation-dialog-exit');
exitButton.setAttribute('aria-hidden', 'true');
exitButton.tabIndex = -1;
exitButton.title = options.cancelText; exitButton.title = options.cancelText;
// Cancel handler // Cancel handler
cancelButton.onclick = dialogCanceled; cancelButton.addEventListener('click', dialogCanceled);
cancelButton.onkeydown = function (e) { cancelButton.addEventListener('keydown', function (e) {
if (e.which === 32) { // Space if (e.which === 32) { // Space
dialogCanceled(e); dialogCanceled(e);
} }
else if (e.which === 9 && e.shiftKey) { // Shift-tab else if (e.which === 9 && e.shiftKey) { // Shift-tab
flowTo(exitButton, e); flowTo(confirmButton, e);
} }
}; });
buttons.appendChild(cancelButton); buttons.appendChild(cancelButton);
// Confirm handler // Confirm handler
confirmButton.onclick = dialogConfirmed; confirmButton.addEventListener('click', dialogConfirmed);
confirmButton.onkeydown = function (e) { confirmButton.addEventListener('keydown', function (e) {
if (e.which === 32) { // Space if (e.which === 32) { // Space
dialogConfirmed(e); dialogConfirmed(e);
} }
};
buttons.appendChild(confirmButton);
// Exit handler
exitButton.onclick = dialogCanceled;
exitButton.onkeydown = function (e) {
if (e.which === 32) { // Space
dialogCanceled(e);
}
else if (e.which === 9 && !e.shiftKey) { // Tab else if (e.which === 9 && !e.shiftKey) { // Tab
flowTo(cancelButton, e); 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;
/** /**
* Set parent of confirmation dialog * Set parent of confirmation dialog
* @param {HTMLElement} wrapper * @param {HTMLElement} wrapper
@ -168,6 +185,87 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
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
@ -198,7 +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); wrapperElement.appendChild(popupBackground);
startCapturingFocus();
disableUnderlay();
popupBackground.classList.remove('hidden'); popupBackground.classList.remove('hidden');
fitToContainer(offsetTop); fitToContainer(offsetTop);
setTimeout(function () { setTimeout(function () {
@ -230,6 +332,11 @@ 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); wrapperElement.removeChild(popupBackground);
@ -245,3 +352,5 @@ H5P.ConfirmationDialog = (function (EventDispatcher) {
return ConfirmationDialog; return ConfirmationDialog;
}(H5P.EventDispatcher)); }(H5P.EventDispatcher));
H5P.ConfirmationDialog.uniqueId = -1;