From ae1afd2f6529e4d07b18317304e5e2c302d783ce Mon Sep 17 00:00:00 2001 From: Christopher Smith <chris@jalakai.co.uk> Date: Fri, 2 Aug 2013 17:21:48 +0200 Subject: [PATCH] add csv import functionality to the user manager --- lib/plugins/usermanager/admin.php | 187 +++++++++++++++++++++ lib/plugins/usermanager/lang/en/import.txt | 9 + lib/plugins/usermanager/lang/en/lang.php | 15 ++ lib/plugins/usermanager/style.css | 3 + 4 files changed, 214 insertions(+) create mode 100644 lib/plugins/usermanager/lang/en/import.txt diff --git a/lib/plugins/usermanager/admin.php b/lib/plugins/usermanager/admin.php index 72ac47e15..ddf10a115 100644 --- a/lib/plugins/usermanager/admin.php +++ b/lib/plugins/usermanager/admin.php @@ -30,6 +30,7 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { var $_edit_user = ''; // set to user selected for editing var $_edit_userdata = array(); var $_disabled = ''; // if disabled set to explanatory string + var $_import_failures = array(); /** * Constructor @@ -49,6 +50,11 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { $this->_auth = & $auth; } + + // attempt to retrieve any import failures from the session + if ($_SESSION['import_failures']){ + $this->_import_failures = $_SESSION['import_failures']; + } } /** @@ -102,6 +108,8 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { $this->_start = 0; break; case "export" : $this->_export(); break; + case "import" : $this->_import(); break; + case "importfails" : $this->_downloadImportFailures(); break; } $this->_user_total = $this->_auth->canDo('getUserCount') ? $this->_auth->getUserCount($this->_filter) : -1; @@ -238,6 +246,10 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { ptln(" </div>"); ptln("</div>"); } + + if ($this->_auth->canDo('addUser')) { + $this->_htmlImportForm(); + } ptln("</div>"); } @@ -352,6 +364,59 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { } } + function _htmlImportForm($indent=0) { + global $ID; + + $failure_download_link = wl($ID,array('do'=>'admin','page'=>'usermanager','fn[importfails]'=>1)); + + ptln('<div class="level2 import_users">',$indent); + print $this->locale_xhtml('import'); + ptln(' <form action="'.wl($ID).'" method="post" enctype="multipart/form-data">',$indent); + formSecurityToken(); + ptln(' <label>User list file (csv): <input type="file" name="import" /></label>',$indent); + ptln(' <input type="submit" name="fn[import]" value="'.$this->lang['import'].'" />',$indent); + ptln(' <input type="hidden" name="do" value="admin" />',$indent); + ptln(' <input type="hidden" name="page" value="usermanager" />',$indent); + + $this->_htmlFilterSettings($indent+4); + ptln(' </form>',$indent); + ptln('</div>'); + + // list failures from the previous import + if ($this->_import_failures) { + $digits = strlen(count($this->_import_failures)); + ptln('<div class="level3 import_failures">',$indent); + ptln(' <h3>Most Recent Import - Failures</h3>'); + ptln(' <table class="import_failures">',$indent); + ptln(' <thead>',$indent); + ptln(' <tr>',$indent); + ptln(' <th class="line">'.$this->lang['line'].'</th>',$indent); + ptln(' <th class="error">'.$this->lang['error'].'</th>',$indent); + ptln(' <th class="userid">'.$this->lang['user_id'].'</th>',$indent); + ptln(' <th class="username">'.$this->lang['user_name'].'</th>',$indent); + ptln(' <th class="usermail">'.$this->lang['user_mail'].'</th>',$indent); + ptln(' <th class="usergroups">'.$this->lang['user_groups'].'</th>',$indent); + ptln(' </tr>',$indent); + ptln(' </thead>',$indent); + ptln(' <tbody>',$indent); + foreach ($this->_import_failures as $line => $failure) { + ptln(' <tr>',$indent); + ptln(' <td class="lineno"> '.sprintf('%0'.$digits.'d',$line).' </td>',$indent); + ptln(' <td class="error">' .$failure['error'].' </td>', $indent); + ptln(' <td class="field userid"> '.hsc($failure['user'][0]).' </td>',$indent); + ptln(' <td class="field username"> '.hsc($failure['user'][2]).' </td>',$indent); + ptln(' <td class="field usermail"> '.hsc($failure['user'][3]).' </td>',$indent); + ptln(' <td class="field usergroups"> '.hsc($failure['user'][4]).' </td>',$indent); + ptln(' </tr>',$indent); + } + ptln(' </tbody>',$indent); + ptln(' </table>',$indent); + ptln(' <p><a href="'.$failure_download_link.'">Download Failures as CSV for correction</a></p>'); + ptln('</div>'); + } + + } + function _addUser(){ global $INPUT; if (!checkSecurityToken()) return false; @@ -666,4 +731,126 @@ class admin_plugin_usermanager extends DokuWiki_Admin_Plugin { fclose($fd); die; } + + /* + * import a file of users in csv format + * + * csv file should have 4 columns, user_id, full name, email, groups (comma separated) + */ + function _import() { + // check we are allowed to add users + if (!checkSecurityToken()) return false; + if (!$this->_auth->canDo('addUser')) return false; + + // check file uploaded ok. + if (empty($_FILES['import']['size']) || !empty($FILES['import']['error']) && is_uploaded_file($FILES['import']['tmp_name'])) { + msg($this->lang['import_error_upload'],-1); + return false; + } + // retrieve users from the file + $this->_import_failures = array(); + $import_success_count = 0; + $import_fail_count = 0; + $line = 0; + $fd = fopen($_FILES['import']['tmp_name'],'r'); + if ($fd) { + while($csv = fgets($fd)){ + $raw = str_getcsv($csv); + $error = ''; // clean out any errors from the previous line + // data checks... + if (1 == ++$line) { + if ($raw[0] == 'user_id' || $raw[0] == $this->lang['user_id']) continue; // skip headers + } + if (count($raw) < 4) { // need at least four fields + $import_fail_count++; + $error = sprintf($this->lang['import_error_fields'], count($raw)); + $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); + continue; + } + array_splice($raw,1,0,auth_pwgen()); // splice in a generated password + $clean = $this->_cleanImportUser($raw, $error); + if ($clean && $this->_addImportUser($clean, $error)) { +# $this->_notifyUser($clean[0],$clean[1]); + $import_success_count++; + } else { + $import_fail_count++; + $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); + } + } + msg(sprintf($this->lang['import_success_count'], ($import_success_count+$import_fail_count), $import_success_count),($import_success_count ? 1 : -1)); + if ($import_fail_count) { + msg(sprintf($this->lang['import_failure_count'], $import_fail_count),-1); + } + } else { + msg($this->lang['import_error_readfail'],-1); + } + + // save import failures into the session + if (!headers_sent()) { + session_start(); + $_SESSION['import_failures'] = $this->_import_failures; + session_write_close(); + } + } + + function _cleanImportUser($candidate, & $error){ + global $INPUT; + + // kludgy .... + $INPUT->set('userid', $candidate[0]); + $INPUT->set('userpass', $candidate[1]); + $INPUT->set('username', $candidate[2]); + $INPUT->set('usermail', $candidate[3]); + $INPUT->set('usergroups', $candidate[4]); + + $cleaned = $this->_retrieveUser(); + list($user,$pass,$name,$mail,$grps) = $cleaned; + if (empty($user)) { + $error = $this->lang['import_error_baduserid']; + return false; + } + + // no need to check password, handled elsewhere + + if (!($this->_auth->canDo('modName') xor empty($name))){ + $error = $this->lang['import_error_badname']; + return false; + } + + if (!($this->_auth->canDo('modMail') xor empty($mail))){ + $error = $this->lang['import_error_badmail']; + return false; + } + + return $cleaned; + } + + function _addImportUser($user, & $error){ + if (!$this->_auth->triggerUserMod('create', $user)) { + $error = $this->lang['import_error_create']; + return false; + } + + return true; + } + + function _downloadImportFailures(){ + + // ============================================================================================== + // GENERATE OUTPUT + // normal headers for downloading... + header('Content-type: text/csv;charset=utf-8'); + header('Content-Disposition: attachment; filename="importfails.csv"'); +# // for debugging assistance, send as text plain to the browser +# header('Content-type: text/plain;charset=utf-8'); + + // output the csv + $fd = fopen('php://output','w'); + foreach ($this->_import_failures as $line => $fail) { + fputs($fd, $fail['orig']); + } + fclose($fd); + die; + } + } diff --git a/lib/plugins/usermanager/lang/en/import.txt b/lib/plugins/usermanager/lang/en/import.txt new file mode 100644 index 000000000..2087083e0 --- /dev/null +++ b/lib/plugins/usermanager/lang/en/import.txt @@ -0,0 +1,9 @@ +===== Bulk User Import ===== + +Requires a CSV file of users with at least four columns. +The columns must contain, in order: user-id, full name, email address and groups. +The CSV fields should be separated by commas (,) and strings delimited by quotation marks (""). Backslash (\) can be used for escaping. +For an example of a suitable file, try the "Export Users" function above. +Duplicate user-ids will be ignored. + +A password will be generated and emailed to each successfully imported user. diff --git a/lib/plugins/usermanager/lang/en/lang.php b/lib/plugins/usermanager/lang/en/lang.php index ae0ee1c16..f22d1f805 100644 --- a/lib/plugins/usermanager/lang/en/lang.php +++ b/lib/plugins/usermanager/lang/en/lang.php @@ -33,6 +33,9 @@ $lang['clear'] = 'Reset Search Filter'; $lang['filter'] = 'Filter'; $lang['export_all'] = 'Export All Users (CSV)'; $lang['export_filtered'] = 'Export Filtered User list (CSV)'; +$lang['import'] = 'Import New Users'; +$lang['line'] = 'Line no.'; +$lang['error'] = 'Error message'; $lang['summary'] = 'Displaying users %1$d-%2$d of %3$d found. %4$d users total.'; $lang['nonefound'] = 'No users found. %d users total.'; @@ -58,3 +61,15 @@ $lang['add_fail'] = 'User addition failed'; $lang['notify_ok'] = 'Notification email sent'; $lang['notify_fail'] = 'Notification email could not be sent'; +// import errors +$lang['import_success_count'] = 'User Import: %d users found, %d imported successfully.'; +$lang['import_failure_count'] = 'User Import: %d failed. Failures are listed below.'; +$lang['import_error_fields'] = "Insufficient fields, found %d, require 4."; +$lang['import_error_baduserid'] = "User-id missing"; +$lang['import_error_badname'] = 'Bad name'; +$lang['import_error_badmail'] = 'Bad mail'; +$lang['import_error_upload'] = 'Import Failed. The csv file could not be uploaded or is empty.'; +$lang['import_error_readfail'] = 'Import Failed. Unable to read uploaded file.'; +$lang['import_error_create'] = 'Unable to create the user'; + + diff --git a/lib/plugins/usermanager/style.css b/lib/plugins/usermanager/style.css index ff8e5d9d1..61b6c2c4e 100644 --- a/lib/plugins/usermanager/style.css +++ b/lib/plugins/usermanager/style.css @@ -17,4 +17,7 @@ color: #ccc!important; border-color: #ccc!important; } +#user__manager .import_failures { + margin-top: 1em; +} /* IE won't understand but doesn't require it */ -- GitLab