getLang('error_badauth')); return; } $this->_auth = $auth; // attempt to retrieve any import failures from the session if (!empty($_SESSION['import_failures'])){ $this->_import_failures = $_SESSION['import_failures']; } } /** * handle user request */ public function handle() { global $INPUT; $cmd = $INPUT->param('cmd'); if (!empty($cmd)) { switch(key($cmd)) { case "import": if (!checkSecurityToken()) return false; if (!$this->_auth->canDo('addUser')) return false; if ($this->validateDefaults() === true) { $this->_import(); } break; case "importfails": $this->_downloadImportFailures(); break; } } return true; } /** * Output html of the admin page */ public function html() { print $this->locale_xhtml('intro'); $this->printFormHTML(); $this->printFailuresHTML(); } /** * Prints the import form */ protected function printFormHTML() { $form = new Form(['enctype' => 'multipart/form-data', 'id' => 'plugin__userimportextended_csv']); $form->setHiddenField('do', 'admin'); $form->setHiddenField('page', $this->getPluginName()); $form->addFieldsetOpen($this->getLang('legend_defaults')); $form->addTextInput('defaults[name]', $this->getLang('form_name') . '*'); $form->addHTML('
'); $form->addTextInput('defaults[email]', $this->getLang('form_email') . '*'); $form->addHTML('
'); $form->addTextInput('defaults[password]', $this->getLang('form_password') . '*'); $form->addHTML('
'); $form->addTextInput('defaults[groups]', $this->getLang('form_groups')); $form->addFieldsetClose(); $form->addFieldsetOpen($this->getLang('legend_csv')); $form->addElement(new \dokuwiki\Form\InputElement('file', 'import'))->attr('accept', '.csv'); $form->addHTML('
'); $form->addButton('cmd[import]', $this->getLang('btn_import')); $form->addFieldsetClose(); echo $form->toHTML(); } /** * Prints a table of failed imports */ protected function printFailuresHTML() { var_dump($this->lang); global $ID; $failure_download_link = wl($ID,array('do'=>'admin','page'=>'userimportextended','cmd[importfails]'=>1)); if ($this->_import_failures) { $digits = strlen(count($this->_import_failures)); ptln('
'); ptln('

'.$this->getLang('import_header').'

'); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); foreach ($this->_import_failures as $line => $failure) { ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); ptln(' '); } ptln(' '); ptln('
'.$this->getLang('line').''.$this->getLang('error').''.$this->getLang('user_id').''.$this->getLang('user_pass').''.$this->getLang('user_name').''.$this->getLang('user_mail').''.$this->getLang('user_groups').'
'.sprintf('%0'.$digits.'d',$line).' ' .$failure['error'].' '.hsc($failure['user'][0]).' '.hsc($failure['user'][1]).' '.hsc($failure['user'][2]).' '.hsc($failure['user'][3]).' '.hsc($failure['user'][4]).'
'); ptln('

'.$this->getLang('import_downloadfailures').'

'); ptln('
'); } } /** * Tries to set all defaults. Returns false if any of the required defaults are empty. * * @return bool */ protected function validateDefaults() { foreach ($this->defaults as $field) { if (!in_array($field, self::DEFAULT_EMPTY) && empty($_REQUEST['defaults'][$field])) { msg($this->getLang('error_required_defaults'), -1); return false; } $this->defaults[$field] = $_REQUEST['defaults'][$field]; // make sure groups include "user" if ($field === 'groups' && strpos($_REQUEST['defaults'][$field], 'user') === false) { $this->defaults[$field] .= ',user'; } } return true; } /** * Import a file of users in csv format * * csv file should have 5 columns, user_id, password, full name, email, groups (comma separated) * * @return bool whether successful */ protected function _import() { // check we are allowed to add users if (!checkSecurityToken()) return false; if (!$this->_auth->canDo('addUser')) return false; // check file uploaded ok. $upl = $this->_isUploadedFile($_FILES['import']['tmp_name']); if (empty($_FILES['import']['size']) || !empty($_FILES['import']['error']) && $upl) { msg($this->getLang('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)){ if (!utf8_check($csv)) { $csv = utf8_encode($csv); } $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->getLang('user_id')) continue; // skip headers } // in contrast to User Manager, 5 columns are required if (count($raw) < 5) { // need at least five fields $import_fail_count++; $error = sprintf($this->getLang('import_error_fields'), count($raw)); $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); continue; } $clean = $this->_cleanImportUser($raw, $error); if ($clean && $this->_addImportUser($clean, $error)) { $sent = $this->_notifyUser($clean[0],$clean[1],false); if (!$sent){ msg(sprintf($this->getLang('import_notify_fail'),$clean[0],$clean[3]),-1); } $import_success_count++; } else { $import_fail_count++; $this->_import_failures[$line] = array('error' => $error, 'user' => $raw, 'orig' => $csv); } } msg(sprintf($this->getLang('import_success_count'), ($import_success_count+$import_fail_count), $import_success_count),($import_success_count ? 1 : -1)); if ($import_fail_count) { msg(sprintf($this->getLang('import_failure_count'), $import_fail_count),-1); } } else { msg($this->getLang('import_error_readfail'),-1); } // save import failures into the session if (!headers_sent()) { session_start(); $_SESSION['import_failures'] = $this->_import_failures; session_write_close(); } return true; } /** * Replaces empty values with defaults * * @param array $candidate */ protected function insertDefaults(&$candidate) { if (empty($candidate[1])) { $candidate[1] = $this->defaults['password']; } if (empty($candidate[2])) { $candidate[2] = $this->defaults['name']; } if (empty($candidate[3])) { $candidate[3] = $this->defaults['email']; } if (empty($candidate[4])) { $candidate[4] = $this->defaults['groups']; } } /** * Returns cleaned user data * * @param array $candidate raw values of line from input file * @param string $error * @return array|false cleaned data or false */ protected function _cleanImportUser($candidate, &$error) { global $INPUT; // fill in defaults if needed $this->insertDefaults($candidate); // 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->getLang('import_error_baduserid'); return false; } // no need to check password, handled elsewhere if (!($this->_auth->canDo('modName') xor empty($name))){ $error = $this->getLang('import_error_badname'); return false; } if ($this->_auth->canDo('modMail')) { if (empty($mail) || !mail_isvalid($mail)) { $error = $this->getLang('import_error_badmail'); return false; } } else { if (!empty($mail)) { $error = $this->getLang('import_error_badmail'); return false; } } return $cleaned; } /** * Adds imported user to auth backend * * Required a check of canDo('addUser') before * * @param array $user data of user * @param string &$error reference catched error message * @return bool whether successful */ protected function _addImportUser($user, & $error){ if (!$this->_auth->triggerUserMod('create', $user)) { $error = $this->getLang('import_error_create'); return false; } return true; } /** * Retrieve & clean user data from the form * * @param bool $clean whether the cleanUser method of the authentication backend is applied * @return array (user, password, full name, email, array(groups)) */ protected function _retrieveUser($clean=true) { /** @var DokuWiki_Auth_Plugin $auth */ global $auth; global $INPUT; $user = []; $user[0] = ($clean) ? $auth->cleanUser($INPUT->str('userid')) : $INPUT->str('userid'); $user[1] = $INPUT->str('userpass'); $user[2] = $INPUT->str('username'); $user[3] = $INPUT->str('usermail'); $user[4] = explode(',',$INPUT->str('usergroups')); $user[5] = $INPUT->str('userpass2'); // repeated password for confirmation $user[4] = array_map('trim',$user[4]); if($clean) $user[4] = array_map(array($auth,'cleanGroup'),$user[4]); $user[4] = array_filter($user[4]); $user[4] = array_unique($user[4]); if(!count($user[4])) $user[4] = null; return $user; } /** * Send password change notification email * * @param string $user id of user * @param string $password plain text * @param bool $status_alert whether status alert should be shown * @return bool whether succesful */ protected function _notifyUser($user, $password, $status_alert=true) { $sent = auth_sendPassword($user,$password); if ($sent) { if ($status_alert) { msg($this->getLang('notify_ok'), 1); } } else { if ($status_alert) { msg($this->getLang('notify_fail'), -1); } } return $sent; } /** * Downloads failures as csv file */ protected 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 $fail) { fputs($fd, $fail['orig']); } fclose($fd); die; } /** * wrapper for is_uploaded_file to facilitate overriding by test suite * * @param string $file filename * @return bool */ protected function _isUploadedFile($file) { return is_uploaded_file($file); } }