diff --git a/lam/lib/lists.inc b/lam/lib/lists.inc index ea23a8b9..9eb15957 100644 --- a/lam/lib/lists.inc +++ b/lam/lib/lists.inc @@ -584,7 +584,7 @@ class lamList { } // file upload elseif (isset($_POST['fileUpload']) && checkIfNewEntriesAreAllowed($this->type)){ - metaRefresh("../masscreate.php?type=" . $this->type); + metaRefresh("../upload/masscreate.php?type=" . $this->type); exit; } } @@ -1091,7 +1091,7 @@ class lamList { elseif (isset($_GET['uploadAllOk'])) { StatusMessage('INFO', _("Upload has finished")); if (isset($_SESSION['mass_pdf']['file'])) { - StatusMessage('INFO', sprintf(_('You can download your PDF files {link=%s}{color=#d2131a}here{endcolor}{endlink}.'), '../' . $_SESSION['mass_pdf']['file'])); + StatusMessage('INFO', sprintf(_('You can download your PDF files {link=%s}{color=#d2131a}here{endcolor}{endlink}.'), $_SESSION['mass_pdf']['file'])); } } elseif (isset($_GET['accountEditInvalidID'])) { diff --git a/lam/lib/tools/fileUpload.inc b/lam/lib/tools/fileUpload.inc index e90f27c8..a2943cee 100644 --- a/lam/lib/tools/fileUpload.inc +++ b/lam/lib/tools/fileUpload.inc @@ -9,12 +9,12 @@ $Id$ it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA @@ -30,56 +30,56 @@ $Id$ /** * File upload - * + * * @package tools - */ + */ class toolFileUpload implements LAMTool { - + /** * Returns the name of the tool. - * + * * @return string name */ function getName() { return _("File upload"); } - + /** * returns a description text for the tool. - * + * * @return string description */ function getDescription() { return _("Creates accounts by uploading a CSV formated file."); } - + /** * Returns a link to the tool page (relative to templates/). - * + * * @return string link */ function getLink() { - return "masscreate.php"; + return "upload/masscreate.php"; } - - /** + + /** * Returns if the tool requires write access to LDAP. - * + * * @return boolean true if write access is needed */ function getRequiresWriteAccess() { return true; } - + /** * Returns if the tool requires password change rights. - * + * * @return boolean true if password change rights are needed */ function getRequiresPasswordChangeRights() { return true; } - + /** * Returns the link to the tool image (relative to graphics/) * @@ -88,7 +88,7 @@ class toolFileUpload implements LAMTool { function getImageLink() { return 'up.gif'; } - + /** * Returns the prefered position of this tool on the tools page. * The position may be between 0 and 1000. 0 is the top position. @@ -98,16 +98,16 @@ class toolFileUpload implements LAMTool { function getPosition() { return 300; } - + /** * Returns a list of sub tools or an empty array. - * + * * @return array list of subtools (LAMTool) */ function getSubTools() { return array(); } - + /** * Returns if the tool is visible in the menu. * @@ -116,16 +116,16 @@ class toolFileUpload implements LAMTool { function isVisible() { return (sizeof($_SESSION['config']->get_ActiveTypes()) > 0); } - + /** * Returns if a tool may be hidden by configuration in the LAM server profile. - * + * * @return boolean hideable */ function isHideable() { return true; } - + } ?> \ No newline at end of file diff --git a/lam/lib/upload.inc b/lam/lib/upload.inc new file mode 100644 index 00000000..3836fa15 --- /dev/null +++ b/lam/lib/upload.inc @@ -0,0 +1,307 @@ + &$value) { + if (strpos($key, 'mass_') === 0) { + unset($_SESSION[$key]); + } + } + } + + /** + * Constructor + */ + public function __construct($scope) { + $this->accounts = unserialize(lamDecrypt($_SESSION['mass_accounts'])); + $this->data = unserialize(lamDecrypt($_SESSION['mass_data'])); + $this->scope = $scope; + $startTime = time(); + $maxTime = get_cfg_var('max_execution_time') - 5; + if ($maxTime > Uploader::TIME_LIMIT) $maxTime = Uploader::TIME_LIMIT; + if ($maxTime <= 0) $maxTime = Uploader::TIME_LIMIT; + $this->endTime = $startTime + $maxTime; + } + + /** + * Start or continues the file upload. + * + * @return String JSON of upload status + */ + public function doUpload() { + $this->securityCheck(); + if (!isset($_SESSION['mass_counter'])) { + return $this->startUpload(); + } + elseif ($_SESSION['mass_counter'] < sizeof($this->accounts)) { + return $this->continueUpload(); + } + elseif (!isset($_SESSION['mass_postActions']['finished'])) { + return $this->runPostActions(); + } + elseif (($_SESSION['mass_pdf']['structure'] != null) && !isset($_SESSION['mass_pdf']['finished'])) { + return $this->createPDF(); + } + return $this->buildUploadStatus(100, true, '', 100, true, 100); + } + + /** + * Continues to upload accounts. + * + * @return String JSON of upload status + */ + private function continueUpload() { + while (($_SESSION['mass_counter'] < sizeof($this->accounts)) && ($this->endTime > time())) { + $this->uploadEntry($_SESSION['mass_counter']); + $_SESSION['mass_counter']++; + } + $accountsProgress = round(($_SESSION['mass_counter'] * 100) / sizeof($this->accounts), 2); + $accountsFinished = ($_SESSION['mass_counter'] == sizeof($this->accounts)); + return $this->buildUploadStatus($accountsProgress, $accountsFinished); + } + + /** + * Performs the upload of a single LDAP entry. + * + * @param int $position position in $this->accounts. + */ + private function uploadEntry($position) { + $attrs = $this->accounts[$position]; + $dn = $attrs['dn']; + unset($attrs['dn']); + // remove informational attributes + foreach ($attrs as $key => $value) { + if (strpos($key, 'INFO.') === 0) { + unset($attrs[$key]); + } + } + // run preactions + $preAttributes = array(); + foreach ($attrs as $key => $value) { + $preAttributes[$key] = &$attrs[$key]; + } + $preAttributes['dn'] = &$dn; + $preMessages = doUploadPreActions($this->scope, $_SESSION['mass_selectedModules'], $preAttributes); + $preActionOk = true; + for ($i = 0; $i < sizeof($preMessages); $i++) { + if (($preMessages[$i][0] == 'ERROR') || ($preMessages[$i][0] == 'WARN')) { + $preActionOk = false; + $_SESSION['mass_errors'][] = $preMessages[$i]; + } + } + if ($preActionOk) { + // add LDAP entry + $success = @ldap_add($_SESSION['ldap']->server(), $dn, $attrs); + if (!$success) { + $errorMessage = array( + "ERROR", + _("LAM was unable to create account %s! An LDAP error occured."), + getDefaultLDAPErrorString($_SESSION['ldap']->server()), + array($position)); + $_SESSION['mass_errors'][] = $errorMessage; + $_SESSION['mass_failed'][] = $position; + } + } + } + + /** + * Starts the file upload. + * + * @return String JSON of upload status + */ + private function startUpload() { + $_SESSION['mass_counter'] = 0; + return $this->buildUploadStatus(); + } + + /** + * Returns the upload status as JSON. + * + * @param number $accountsProgress progress for LDAP entries + * @param string $accountsFinished all entries in LDAP + * @param string $postActionsTitle title for current post action + * @param number $postActionsProgress progress for post actions + * @param string $postActionsFinished post actions finished + * @param number $pdfProgress PDF creation progress + * @return String JSON status + */ + private function buildUploadStatus($accountsProgress = 0, $accountsFinished = false, + $postActionsTitle = '', $postActionsProgress = 0, $postActionsFinished = false, + $pdfProgress = 0) { + $pdfFinished = ($_SESSION['mass_pdf']['structure'] == null) || isset($_SESSION['mass_pdf']['finished']); + $allDone = $accountsFinished && $postActionsFinished && $pdfFinished; + $errorHtml = ''; + if ($allDone && !empty($_SESSION['mass_errors'])) { + foreach ($_SESSION['mass_errors'] as $error) { + $text = isset($error[2]) ? $error[2] : ''; + $vars = isset($error[3]) ? $error[3] : array(); + $errorHtml .= StatusMessage($error[0], $error[1], $text, $vars, true); + } + } + $status = array( + 'title' => _("LDAP upload in progress. Please wait."), + 'titleFinished' => _("Upload has finished"), + 'titleErrors' => _("There were errors while uploading:"), + 'titlePDF' => _('Create PDF files'), + 'accountsProgress' => $accountsProgress, + 'accountsFinished' => $accountsFinished, + 'postActionsTitle' => $postActionsTitle, + 'postActionsProgress' => $postActionsProgress, + 'postActionsFinished' => $postActionsFinished, + 'pdfProgress' => $pdfProgress, + 'pdfFinished' => $pdfFinished, + 'allDone' => $allDone, + 'errorHtml' => $errorHtml, + 'scope' => $this->scope + ); + return json_encode($status); + } + + /** + * Checks for security violations and stops processing if needed. + */ + private function securityCheck() { + if (!isLoggedIn() || empty($this->scope) + || isAccountTypeHidden($this->scope) + || !checkIfNewEntriesAreAllowed($this->scope) + || !checkIfWriteAccessIsAllowed($this->scope)) { + die; + } + } + + /** + * Performs any post create actions by modules. + * + * @return String JSON of upload status + */ + private function runPostActions() { + $return = $this->runModulePostActions(); + while (!isset($_SESSION['mass_postActions']['finished']) && ($this->endTime > time())) { + $return = $this->runModulePostActions(); + } + $title = _("Additional tasks for module:") . ' ' . getModuleAlias($return['module'], $this->scope); + $progress = round($return['progress'], 2); + $finished = isset($_SESSION['mass_postActions']['finished']); + return $this->buildUploadStatus(100, true, $title, $progress, $finished); + } + + /** + * Runs a single post create action by modules. + * + * @return array status array + */ + private function runModulePostActions() { + $return = doUploadPostActions($this->scope, $this->data, $_SESSION['mass_ids'], $_SESSION['mass_failed'], $_SESSION['mass_selectedModules'], $this->accounts); + if ($return['status'] == 'finished') { + $_SESSION['mass_postActions']['finished'] = true; + } + if (isset($return['errors'])) { + for ($i = 0; $i < sizeof($return['errors']); $i++) { + $_SESSION['mass_errors'][] = $return['errors'][$i]; + } + } + return $return; + } + + /** + * Creates the PDF files. + * + * @return String JSON of upload status + */ + private function createPDF() { + $file = $_SESSION['mass_pdf']['file']; + $pdfStructure = $_SESSION['mass_pdf']['structure']; + $pdfZip = new ZipArchive(); + if ($_SESSION['mass_pdf']['counter'] == 0) { + $pdfZipResult = @$pdfZip->open($_SESSION['mass_pdf']['file'], ZipArchive::CREATE); + if (!$pdfZipResult === true) { + $_SESSION['mass_errors'][] = array('ERROR', _('Unable to create ZIP file for PDF export.'), $file); + $_SESSION['mass_pdf']['finished'] = true; + } + } + else { + @$pdfZip->open($_SESSION['mass_pdf']['file']); + } + while (!isset($_SESSION['mass_pdf']['finished']) && ($this->endTime > time())) { + $attrs = $this->accounts[$_SESSION['mass_pdf']['counter']]; + $dn = $attrs['dn']; + // get informational attributes + $infoAttributes = array(); + foreach ($attrs as $key => $value) { + if (strpos($key, 'INFO.') === 0) { + $infoAttributes[$key] = $value; + } + } + // load account + $_SESSION['mass_pdfAccount'] = new accountContainer($this->scope, 'mass_pdfAccount'); + $pdfErrors = $_SESSION['mass_pdfAccount']->load_account($dn, $infoAttributes); + if (sizeof($pdfErrors) > 0) { + $_SESSION['mass_errors'] = array_merge($_SESSION['mass_errors'], $pdfErrors); + $_SESSION['mass_pdf']['finished'] = true; + break; + } + // create and save PDF + $pdfContent = createModulePDF(array($_SESSION['mass_pdfAccount']), $pdfStructure, true); + $fileName = $dn . '.pdf'; + $pdfZip->addFromString($fileName, $pdfContent); + $_SESSION['mass_pdf']['counter'] ++; + if ($_SESSION['mass_pdf']['counter'] >= sizeof($this->accounts)) { + $_SESSION['mass_pdf']['finished'] = true; + } + } + @$pdfZip->close(); + $progress = ($_SESSION['mass_pdf']['counter'] * 100) / sizeof($this->accounts); + return $this->buildUploadStatus(100, true, '', 100, true, $progress); + } + +} diff --git a/lam/templates/lib/500_lam.js b/lam/templates/lib/500_lam.js index 5e25db95..f0c80956 100644 --- a/lam/templates/lib/500_lam.js +++ b/lam/templates/lib/500_lam.js @@ -3,7 +3,7 @@ $Id$ This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2014 Roland Gruber + Copyright (C) 2003 - 2016 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,7 +23,7 @@ $Id$ /** * Called when user clicks on a table row. This toggles the checkbox in the row. - * + * * @param box checkbox name */ function list_click(box) { @@ -38,7 +38,7 @@ function list_click(box) { /** * The user changed the value in the OU selection box. This will reload the list view with the new suffix. - * + * * @param type account type * @param element dropdown box */ @@ -48,7 +48,7 @@ function listOUchanged(type, element) { /** * The user pressed a key in the page number box. On enter this will reload the list view with the new page. - * + * * @param url target URL * @param e event */ @@ -83,7 +83,7 @@ function listResizeITabContentDiv() { /** * Shows the dialog to change the list settings. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -104,7 +104,7 @@ function listShowSettingsDialog(title, okText, cancelText) { /** * Submits the form by clicking on the given button if enter was pressed. * Example: SubmitForm('apply_filter', event); - * + * * @param id button ID * @param e event * @returns Boolean result @@ -147,7 +147,7 @@ function addResizeHandler(item, min, max) { } jQuery(item).toggleClass('imgExpanded'); } - ); + ); } /** @@ -168,7 +168,7 @@ function list_switchAccountSelection() { /** * The user changed the value in the profile selection box. This will reload the login page with the new profile. - * + * * @param element dropdown box */ function loginProfileChanged(element) { @@ -177,7 +177,7 @@ function loginProfileChanged(element) { /** * Shows the dialog to delete a profile. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -206,7 +206,7 @@ function profileShowDeleteDialog(title, okText, cancelText, scope, selectFieldNa /** * Shows a simple dialog. - * + * * @param title dialog title * @param okText text for Ok button (optional, submits form) * @param cancelText text for Cancel button @@ -230,7 +230,7 @@ function showSimpleDialog(title, okText, cancelText, formID, dialogDivID) { /** * Shows the dialog to change the password. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -259,7 +259,7 @@ function passwordShowChangeDialog(title, okText, cancelText, randomText, ajaxURL /** * Manages the password change when a button is pressed. - * + * * @param random "true" if random password should be generated * @param ajaxURL URL used for AJAX request */ @@ -292,7 +292,7 @@ function passwordHandleInput(random, ajaxURL) { /** * Manages the server reply to a password change request. - * + * * @param data JSON reply */ function passwordHandleReply(data) { @@ -306,12 +306,12 @@ function passwordHandleReply(data) { } else { jQuery('#passwordDialogMessageArea').html(data.messages); - } + } } /** * Shows a general confirmation dialog and submits a form if the user accepted. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -345,7 +345,7 @@ function showConfirmationDialog(title, okText, cancelText, dialogDiv, formName, /** * Appends the input fields of a dialog back to the form and submits it. - * + * * @param dialogDiv ID of dialog div * @param formName name of form */ @@ -361,7 +361,7 @@ function appendDialogInputsToFormAndSubmit(dialogDiv, formName) { /** * Shows a simple confirmation dialog. * If the user presses Cancel then the current action is stopped (event.preventDefault()). - * + * * @param text dialog text * @param e event */ @@ -380,7 +380,7 @@ function confirmOrStopProcessing(text, e) { /** * Alines the elements with the given IDs to the same width. - * + * * @param elementIDs IDs */ function equalWidth(elementIDs) { @@ -401,7 +401,7 @@ function equalWidth(elementIDs) { /** * Alines the elements with the given IDs to the same height. - * + * * @param elementIDs IDs */ function equalHeight(elementIDs) { @@ -418,7 +418,7 @@ function equalHeight(elementIDs) { /** * Shows the dialog to change the list settings. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -430,7 +430,7 @@ function showDistributionDialog(title, okText, cancelText, scope, type, selectFi // show dialog var buttonList = {}; var dialogId = ''; - + if (type == 'export') { // show structure name to export jQuery('#exportName').text(jQuery('[name=' + selectFieldName + ']').val()); @@ -451,7 +451,7 @@ function showDistributionDialog(title, okText, cancelText, scope, type, selectFi buttonList[okText] = function() { document.forms["importDialogForm_" + scope].submit(); }; } buttonList[cancelText] = function() { jQuery(this).dialog("close"); }; - + jQuery('#' + dialogId).dialog({ modal: true, title: title, @@ -468,7 +468,7 @@ function showDistributionDialog(title, okText, cancelText, scope, type, selectFi /** * Stores the current scroll position in the form. - * + * * @param formName ID of form */ function saveScrollPosition(formName) { @@ -484,11 +484,11 @@ function saveScrollPosition(formName) { name: 'scrollPositionLeft', value: left }).appendTo(jQuery('#' + formName)); -} +} /** * Shows the dialog to create a DNS zone. - * + * * @param title dialog title * @param okText text for Ok button * @param cancelText text for Cancel button @@ -528,14 +528,14 @@ jQuery(document).ready( /** * Checks if the given field has the same value as the reference field. * Field is marked red if different and green if equal. - * + * * @param fieldID ID of field to check * @param fieldIDReference ID of reference field */ function checkFieldsHaveSameValues(fieldID, fieldIDReference) { var field = jQuery('#' + fieldID); var fieldRef = jQuery('#' + fieldIDReference); - var check = + var check = function() { var value = field.val(); var valueRef = fieldRef.val(); @@ -546,7 +546,7 @@ function checkFieldsHaveSameValues(fieldID, fieldIDReference) { else { if (value == valueRef) { field.removeClass('markFail'); - field.addClass('markOk'); + field.addClass('markOk'); } else { field.addClass('markFail'); @@ -561,12 +561,12 @@ function checkFieldsHaveSameValues(fieldID, fieldIDReference) { /** * Checks if the value of the given password field matches LAM's password policy. * Field is marked red if fail and green if ok. - * + * * @param fieldID ID of field to check */ function checkPasswordStrength(fieldID, ajaxURL) { var field = jQuery('#' + fieldID); - var check = + var check = function() { var value = field.val(); var pwdJSON = { @@ -580,7 +580,7 @@ function checkPasswordStrength(fieldID, ajaxURL) { /** * Manages the server reply to a password strength check request. - * + * * @param data JSON reply * @param fieldID input field ID */ @@ -593,19 +593,19 @@ function checkPasswordStrengthHandleReply(data, fieldID) { } else if (field.val() == '') { field.removeClass('markFail'); - field.removeClass('markOk'); + field.removeClass('markOk'); } else { field.addClass('markFail'); field.removeClass('markOk'); field.prop('title', data.result); - } + } } /** * Updates the positions of a htmlSortable list in a hidden input field. * The positions must be separated by comma (e.g. "0,1,2,3"). - * + * * @param id HTML ID of hidden input field * @param oldPos old position * @param newPos new position @@ -631,7 +631,7 @@ function updateModulePositions(id, oldPos, newPos) { /** * Filters a select box by the value of the filter input field. - * + * * @param filterInput ID of input field for filter * @param select ID of select box to filter * @param event key event @@ -658,3 +658,117 @@ function filterSelect(filterInput, select, event) { } }); } + +window.lam = window.lam || {}; +window.lam.upload = window.lam.upload || {}; + +/** + * Continues a CSV file upload. + * + * @param url URL where to get status JSON + */ +window.lam.upload.continueUpload = function(url) { + jQuery.ajax({ + url: url, + method: 'POST', + data: 'jsonInput=' + }) + .done(function(jsonData){ + if (!jsonData.accountsFinished) { + window.lam.upload.printBasicStatus(jsonData); + } + else if (!jsonData.postActionsFinished) { + window.lam.upload.printPostActionStatus(jsonData); + } + else if (!jsonData.pdfFinished) { + window.lam.upload.printPDFStatus(jsonData); + } + // next call if not finished + if (!jsonData.allDone) { + window.lam.upload.continueUpload(url); + } + else { + window.lam.upload.uploadDone(jsonData); + } + }); +}; + +/** + * Prints the upload status when accounts are still being created. + * + * @param jsonData status JSON + */ +window.lam.upload.printBasicStatus = function(jsonData) { + var htmlOut = '