diff --git a/lam/HISTORY b/lam/HISTORY index 8e72b205..bcae7f22 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -3,6 +3,7 @@ September 2020 - Configuration export and import - Show password prompt when a user with expired password logs into LAM admin interface (requires PHP 7.2) - Better error messages on login when account is expired/deactivated/... + - Personal: photo can be uploaded via webcam - Windows users: group display format can be configured (cn/dn) - LAM Pro: -> Windows: new cron job to send users a summary of their managed groups diff --git a/lam/graphics/webcam.png b/lam/graphics/webcam.png new file mode 100644 index 00000000..f55a6f3d Binary files /dev/null and b/lam/graphics/webcam.png differ diff --git a/lam/lib/html.inc b/lam/lib/html.inc index e41ed223..58a4cd78 100644 --- a/lam/lib/html.inc +++ b/lam/lib/html.inc @@ -3350,6 +3350,8 @@ class htmlLink extends htmlElement { private $onClick = null; /** show as button */ private $showAsButton = false; + /** link id */ + private $id; /** * Constructor. @@ -3399,8 +3401,8 @@ class htmlLink extends htmlElement { $onClick = ' onclick="' . $this->onClick . '"'; } $idAttr = ''; - if ($this->showAsButton) { - $id = 'a_' . preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->target); + if ($this->showAsButton || !empty($this->id)) { + $id = !empty($this->id) ? $this->id : 'a_' . preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->target); $idAttr = ' id="' . $id . '"'; } $classAttr = ''; @@ -3463,6 +3465,15 @@ class htmlLink extends htmlElement { $this->onClick = htmlspecialchars($event); } + /** + * Sets the element id. + * + * @param string $id unique id + */ + public function setId($id) { + $this->id = $id; + } + } /** diff --git a/lam/lib/modules/inetOrgPerson.inc b/lam/lib/modules/inetOrgPerson.inc index d9852cb7..f7b74bd7 100644 --- a/lam/lib/modules/inetOrgPerson.inc +++ b/lam/lib/modules/inetOrgPerson.inc @@ -1715,12 +1715,12 @@ class inetOrgPerson extends baseModule implements passwordService { $container->addField(new htmlAccountPageButton(get_class($this), 'photo', 'upload', _('Upload'))); $container->addVerticalSpacer('1rem'); $webcamContent = new htmlResponsiveRow(); - $webcamContent->add(new htmlSubTitle(_('Get from webcam')), 12); + $webcamContent->add(new htmlSubTitle(_('Use webcam')), 12); $errorMessage = new htmlStatusMessage('ERROR', ''); $errorMessage->setCSSClasses(array('hidden', 'lam-webcam-message')); $webcamContent->add($errorMessage, 12); $captureButton = new htmlButton('lam-webcam-capture', _('Start capture')); - $captureButton->setOnClick('window.lam.tools.startWebcamCapture(event);'); + $captureButton->setOnClick('window.lam.tools.webcam.capture(event);'); $webcamContent->add($captureButton, 12, 12, 12, 'text-center'); $video = new htmlVideo('lam-webcam-video'); $video->setCSSClasses(array('hidden')); @@ -1728,7 +1728,7 @@ class inetOrgPerson extends baseModule implements passwordService { $webcamContent->addVerticalSpacer('0.5rem'); $webcamUploadButton = new htmlButton('uploadWebcam', _('Upload')); $webcamUploadButton->setCSSClasses(array('btn-lam-webcam-upload', 'hidden')); - $webcamUploadButton->setOnClick('window.lam.tools.startWebcamUpload();'); + $webcamUploadButton->setOnClick('window.lam.tools.webcam.upload();'); $webcamContent->add($webcamUploadButton, 12, 12, 12, 'text-center'); $canvas = new htmlCanvas('lam-webcam-canvas'); $canvas->setCSSClasses(array('hidden')); @@ -3092,6 +3092,33 @@ class inetOrgPerson extends baseModule implements passwordService { $uploadStatus = new htmlDiv('inetOrgPersonPhotoUploadStatus', new htmlOutputText('')); $uploadStatus->setCSSClasses(array('qq-upload-list')); $row->add($uploadStatus, 12); + // webcam button + $webcamContent = new htmlResponsiveRow(); + $webcamContent->addVerticalSpacer('0.5rem'); + $errorMessage = new htmlStatusMessage('ERROR', ''); + $errorMessage->setCSSClasses(array('hidden', 'lam-webcam-message')); + $webcamContent->add($errorMessage, 12); + $webcamContent->addVerticalSpacer('0.5rem'); + $captureButton = new htmlLink(_('Use webcam'), '#', '../../graphics/webcam.png', true); + $captureButton->setId('btn_lam-webcam-capture'); + $captureButton->setOnClick('window.lam.tools.webcam.capture(event);'); + $webcamContent->add($captureButton, 12, 12, 12); + $video = new htmlVideo('lam-webcam-video'); + $video->setCSSClasses(array('hidden')); + $webcamContent->add($video, 12, 12, 12, 'text-center'); + $webcamContent->addVerticalSpacer('1rem'); + $webcamUploadButton = new htmlLink(_('Upload'), '#', '../../graphics/up.gif', true); + $webcamUploadButton->setId('btn-lam-webcam-upload'); + $webcamUploadButton->setCSSClasses(array('btn-lam-webcam-upload', 'hidden')); + $webcamUploadButton->setOnClick('window.lam.tools.webcam.uploadSelfService(event, "' . getSecurityTokenName() + . '", "' . getSecurityTokenValue() . '", "inetOrgPerson", "user", "' . _('File upload failed!') . '", "inetOrgPersonPhotoUploadContent");'); + $webcamContent->add($webcamUploadButton, 12, 12, 12); + $canvas = new htmlCanvas('lam-webcam-canvas'); + $canvas->setCSSClasses(array('hidden')); + $webcamContent->add($canvas, 12); + $webcamDiv = new htmlDiv('lam_webcam_div', $webcamContent, array('hidden')); + $webcamContent->addVerticalSpacer('1rem'); + $row->add($webcamDiv, 12); return $row; } @@ -3125,6 +3152,7 @@ class inetOrgPerson extends baseModule implements passwordService { if (data.success) { if (data.html) { jQuery(\'#inetOrgPersonPhotoUploadContent\').html(data.html); + window.lam.tools.webcam.init(); } } else { @@ -3149,6 +3177,7 @@ class inetOrgPerson extends baseModule implements passwordService { function inetOrgPersonDeletePhotoHandleReply(data) { if (data.errorsOccured == "false") { jQuery(\'#inetOrgPersonPhotoUploadContent\').html(data.html); + window.lam.tools.webcam.init(); } else { alert(data.errormessage); @@ -3820,13 +3849,20 @@ class inetOrgPerson extends baseModule implements passwordService { */ private function ajaxUploadPhoto() { $result = array('success' => true); - if (!isset($_FILES['qqfile']) || ($_FILES['qqfile']['size'] < 100)) { + if ((!isset($_FILES['qqfile']) || ($_FILES['qqfile']['size'] < 100)) && empty($_POST['webcamData'])) { $result = array('error' => _('No file received.')); } else { - $handle = fopen($_FILES['qqfile']['tmp_name'], "r"); - $data = fread($handle, 100000000); - fclose($handle); + if (empty($_POST['webcamData'])) { + $handle = fopen($_FILES['qqfile']['tmp_name'], "r"); + $data = fread($handle, 100000000); + fclose($handle); + } + else { + $data = $_POST['webcamData']; + $data = str_replace('data:image/png;base64,', '', $data); + $data = base64_decode($data); + } try { include_once dirname(__FILE__) . '/../imageutils.inc'; $imageManipulator = ImageManipulationFactory::getImageManipulator($data); diff --git a/lam/style/500_layout.css b/lam/style/500_layout.css index 157d6f23..3312b922 100644 --- a/lam/style/500_layout.css +++ b/lam/style/500_layout.css @@ -314,11 +314,6 @@ table.collapse { font-weight: bold; } -#lam-webcam-video { - max-width: 200px; - max-height: 200px; -} - /** buttons */ .saveButton { background-image: url(../graphics/save.png) !important; diff --git a/lam/style/responsive/120_lam.css b/lam/style/responsive/120_lam.css index 190b9c42..174983a5 100644 --- a/lam/style/responsive/120_lam.css +++ b/lam/style/responsive/120_lam.css @@ -193,6 +193,11 @@ table.responsive-table td { padding: 5px 5px 5px 5px; } + #lam-webcam-video { + max-width: 200px; + max-height: 200px; + } + } /* tablet */ @@ -222,6 +227,11 @@ table.responsive-table td { padding: 5px 20px 5px 20px; } + #lam-webcam-video { + max-width: 300px; + max-height: 300px; + } + } /* desktop */ @@ -255,4 +265,9 @@ table.responsive-table td { padding: 5px 20px 5px 20px; } + #lam-webcam-video { + max-width: 400px; + max-height: 400px; + } + } diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index edb3d32e..5a451667 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -934,10 +934,12 @@ window.lam.tools.setInitialFocus = function() { jQuery('.lam-initial-focus').focus(); }; +window.lam.tools.webcam = window.lam.tools.webcam || {}; + /** * Initializes the webcam capture. */ -window.lam.tools.initWebcamCapture = function() { +window.lam.tools.webcam.init = function() { var contentDiv = jQuery('#lam_webcam_div'); if (contentDiv.length === 0) { return; @@ -957,7 +959,7 @@ window.lam.tools.initWebcamCapture = function() { /** * Starts the webcam capture. */ -window.lam.tools.startWebcamCapture = function(event) { +window.lam.tools.webcam.capture = function(event) { event.preventDefault(); var video = document.getElementById('lam-webcam-video'); var msg = jQuery('.lam-webcam-message'); @@ -988,28 +990,86 @@ window.lam.tools.startWebcamCapture = function(event) { /** * Starts the webcam upload. */ -window.lam.tools.startWebcamUpload = function() { +window.lam.tools.webcam.upload = function() { + var form = jQuery('#lam-webcam-canvas').closest('form'); + canvasData = window.lam.tools.webcam.prepareData(); + var canvasDataInput = jQuery(""); + canvasDataInput.attr('name', 'webcamData'); + canvasDataInput.attr('id', 'webcamData'); + canvasDataInput.attr('type', 'hidden'); + canvasDataInput.attr('value', canvasData); + form.append(canvasDataInput); + form.submit(); + return true; +} + +/** + * Starts the webcam upload. + * + * @param event click event + * @param tokenName security token name + * @param tokenValue security token value + * @param moduleName module name + * @param scope account type + * @param uploadErrorMessage error message if upload fails + * @param contentId id of content to replace + */ +window.lam.tools.webcam.uploadSelfService = function(event, tokenName, tokenValue, moduleName, scope, uploadErrorMessage, contentId) { + event.preventDefault(); + var msg = jQuery('.lam-webcam-message'); + canvasData = window.lam.tools.webcam.prepareData(); + var data = { + webcamData: canvasData + }; + data[tokenName] = tokenValue; + jQuery.ajax({ + url: '../misc/ajax.php?selfservice=1&action=ajaxPhotoUpload' + + '&module=' + moduleName + '&scope=' + scope, + method: 'POST', + data: data + }) + .done(function(jsonData) { + if (jsonData.success) { + if (jsonData.html) { + jQuery('#' + contentId).html(jsonData.html); + window.lam.tools.webcam.init(); + } + return false; + } + else if (jsonData.error) { + msg.find('.statusTitle').text(jsonData.error); + msg.show(); + } + }) + .fail(function() { + msg.find('.statusTitle').text(errorMessage); + msg.show(); + }); + jQuery('#btn_lam-webcam-capture').show(); + jQuery('.btn-lam-webcam-upload').hide(); + return false; +} + +/** + * Starts the webcam upload. + * + * @return webcam data as string + */ +window.lam.tools.webcam.prepareData = function() { var canvas = document.getElementById('lam-webcam-canvas'); var video = document.getElementById('lam-webcam-video'); - var form = jQuery('#lam-webcam-canvas').closest('form'); canvas.setAttribute('width', video.videoWidth); canvas.setAttribute('height', video.videoHeight); var context = canvas.getContext('2d'); context.drawImage(video, 0, 0, video.videoWidth, video.videoHeight); var canvasData = canvas.toDataURL("image/png"); - var canvasDataInput = jQuery(""); - canvasDataInput.attr('name', 'webcamData'); - canvasDataInput.attr('type', 'hidden'); - canvasDataInput.attr('value', canvasData); video.pause(); window.lam.tools.webcamStream.getTracks().forEach(function(track) { track.stop(); }); - form.append(canvasDataInput); - jQuery(canvas).remove(); - jQuery(video).remove(); - form.submit(); - return true; + jQuery(canvas).hide(); + jQuery(video).hide(); + return canvasData; } window.lam.tools.schema = window.lam.tools.schema || {}; @@ -1866,7 +1926,7 @@ jQuery(document).ready(function() { window.lam.tools.addSavedSelectListener(); window.lam.tools.activateTab(); window.lam.tools.setInitialFocus(); - window.lam.tools.initWebcamCapture(); + window.lam.tools.webcam.init(); window.lam.tools.schema.select(); window.lam.html.activateLightboxes(); window.lam.html.preventEnter();