diff --git a/include/class.user.php b/include/class.user.php index 3a5fd1c29e205db10434a235315117b808de74c2..1bef5404b7eb68cec647d2e4864e296f85680b00 100644 --- a/include/class.user.php +++ b/include/class.user.php @@ -462,14 +462,22 @@ class User extends UserModel { $users[] = $data; } + db_autocommit(false); + $error = false; foreach ($users as $u) { $vars = array_combine($keys, $u); - if (!static::fromVars($vars)) - return sprintf(__('Unable to import user: %s'), + if (!static::fromVars($vars)) { + $error = sprintf(__('Unable to import user: %s'), print_r($vars, true)); + break; + } } + if ($error) + db_rollback(); + + db_autocommit(true); - return count($users); + return $error ?: count($users); } function importFromPost($stuff, $extra=array()) { @@ -937,6 +945,10 @@ class UserAccount extends UserAccountModel { return static::sendUnlockEmail('registration-client') === true; } + function setPassword($new) { + $this->set('passwd', Passwd::hash($new)); + } + protected function sendUnlockEmail($template) { global $ost, $cfg; @@ -1021,7 +1033,7 @@ class UserAccount extends UserAccountModel { $this->set('username', $vars['username']); if ($vars['passwd1']) { - $this->set('passwd', Passwd::hash($vars['passwd1'])); + $this->setPassword($vars['passwd1']); $this->setStatus(UserAccountStatus::CONFIRMED); } diff --git a/include/mysqli.php b/include/mysqli.php index ed70cd82ef3c5fc8ae0f209759ed9c969f2ceb61..1793f889e262edb157241497f415af96c71da652 100644 --- a/include/mysqli.php +++ b/include/mysqli.php @@ -90,6 +90,12 @@ function db_autocommit($enable=true) { return $__db->autocommit($enable); } +function db_rollback() { + global $__db; + + return $__db->rollback(); +} + function db_close() { global $__db; return @$__db->close(); diff --git a/setup/cli/modules/class.module.php b/setup/cli/modules/class.module.php index a1647ce3cac8d2be9a57e29ab0fe60fe1eb82ce5..e01e507abc8a677ca2534895b18214dc2de0856a 100644 --- a/setup/cli/modules/class.module.php +++ b/setup/cli/modules/class.module.php @@ -39,8 +39,14 @@ class Option { $value = null; elseif ($value) $nargs = 1; - if ($this->type == 'int') + switch ($this->type) { + case 'int': $value = (int)$value; + break; + case 'bool': + $value = strcasecmp($value, 'true') === 0 || ((int) $value); + break; + } switch ($this->action) { case 'store_true': $value = true; @@ -156,7 +162,7 @@ class Module { if ($this->arguments) { echo "\nArguments:\n"; - foreach ($this->arguments as $name=>$help) + foreach ($this->arguments as $name=>$help) { $extra = ''; if (is_array($help)) { if (isset($help['options']) && is_array($help['options'])) { @@ -169,6 +175,7 @@ class Module { echo $name . "\n " . wordwrap( preg_replace('/\s+/', ' ', $help), 76, "\n ") .$extra."\n"; + } } if ($this->epilog) { diff --git a/setup/cli/modules/cron.php b/setup/cli/modules/cron.php new file mode 100644 index 0000000000000000000000000000000000000000..724c06caca04c3fd2e6870c6896052727a3e8431 --- /dev/null +++ b/setup/cli/modules/cron.php @@ -0,0 +1,34 @@ +<?php +require_once dirname(__file__) . "/class.module.php"; +require_once dirname(__file__) . "/../cli.inc.php"; + +class CronManager extends Module { + var $prologue = 'CLI cron manager for osTicket'; + + var $arguments = array( + 'action' => array( + 'help' => 'Action to be performed', + 'options' => array( + 'fetch' => 'Fetch email', + 'search' => 'Build search index' + ), + ), + ); + + function run($args, $options) { + Bootstrap::connect(); + $ost = osTicket::start(); + + switch (strtolower($args[0])) { + case 'fetch': + Cron::MailFetcher(); + break; + case 'search': + $ost->searcher->backend->IndexOldStuff(); + break; + } + } +} + +Module::register('cron', 'CronManager'); +?> diff --git a/setup/cli/modules/user.php b/setup/cli/modules/user.php index 2d6d146a9950a95fda1bd4ad07c4b7a836148368..6fd05f7782eaa1dcf163cb3a3734df08e185ffe7 100644 --- a/setup/cli/modules/user.php +++ b/setup/cli/modules/user.php @@ -9,8 +9,12 @@ class UserManager extends Module { 'action' => array( 'help' => 'Action to be performed', 'options' => array( - 'import' => 'Import users to the system', - 'export' => 'Export users from the system', + 'import' => 'Import users from CSV file', + 'export' => 'Export users from the system to CSV', + 'activate' => 'Create or activate an account', + 'lock' => "Lock a user's account", + 'set-password' => "Set a user's account password", + 'list' => 'List users based on search criteria', ), ), ); @@ -21,6 +25,26 @@ class UserManager extends Module { 'help' => 'File or stream to process'), 'org' => array('-O', '--org', 'metavar'=>'ORGID', 'help' => 'Set the organization ID on import'), + + 'send-mail' => array('-m', '--send-mail', + 'help' => 'Send the user an email. Used with `activate` and `set-password`', + 'default' => false, 'action' => 'store_true'), + + 'verbose' => array('-v', '--verbose', 'default'=>false, + 'action'=>'store_true', 'help' => 'Be more verbose' + ), + + // -- Search criteria + 'account' => array('-A', '--account', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on activation status'), + 'isconfirmed' => array('-C', '--isconfirmed', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on confirmation status'), + 'islocked' => array('-L', '--islocked', 'type'=>'bool', 'metavar'=>'bool', + 'help' => 'Search for users based on locked status'), + 'email' => array('-E', '--email', + 'help' => 'Search by email address'), + 'id' => array('-U', '--id', + 'help' => 'Search by user id'), ); var $stream; @@ -30,42 +54,164 @@ class UserManager extends Module { Bootstrap::connect(); switch ($args['action']) { - case 'import': - // Properly detect Macintosh style line endings - ini_set('auto_detect_line_endings', true); - - if (!$options['file']) - $this->fail('CSV file to import users from is required!'); - elseif (!($this->stream = fopen($options['file'], 'rb'))) - $this->fail("Unable to open input file [{$options['file']}]"); - - $extras = array(); - if ($options['org']) { - if (!($org = Organization::lookup($options['org']))) - $this->fail($options['org'].': Unknown organization ID'); - $extras['org_id'] = $options['org']; + case 'import': + // Properly detect Macintosh style line endings + ini_set('auto_detect_line_endings', true); + + if (!$options['file']) + $this->fail('CSV file to import users from is required!'); + elseif (!($this->stream = fopen($options['file'], 'rb'))) + $this->fail("Unable to open input file [{$options['file']}]"); + + $extras = array(); + if ($options['org']) { + if (!($org = Organization::lookup($options['org']))) + $this->fail($options['org'].': Unknown organization ID'); + $extras['org_id'] = $options['org']; + } + $status = User::importCsv($this->stream, $extras); + if (is_numeric($status)) + $this->stderr->write("Successfully imported $status clients\n"); + else + $this->fail($status); + break; + + case 'export': + $stream = $options['file'] ?: 'php://stdout'; + if (!($this->stream = fopen($stream, 'c'))) + $this->fail("Unable to open output file [{$options['file']}]"); + + fputcsv($this->stream, array('Name', 'Email')); + foreach (User::objects() as $user) + fputcsv($this->stream, + array((string) $user->getName(), $user->getEmail())); + break; + + case 'activate': + $users = $this->getQuerySet($options); + foreach ($users as $U) { + if ($options['verbose']) { + $this->stderr->write(sprintf( + "Activating %s <%s>\n", + $U->getName(), $U->getDefaultEmail() + )); } - $status = User::importCsv($this->stream, $extras); - if (is_numeric($status)) - $this->stderr->write("Successfully imported $status clients\n"); - else - $this->fail($status); + if (!($account = $U->getAccount())) { + $account = UserAccount::create(array('user' => $U)); + $U->account = $account; + $U->save(); + } + + if ($options['send-mail']) { + global $ost, $cfg; + $ost = osTicket::start(); + $cfg = $ost->getConfig(); + + if (($error = $account->sendConfirmEmail()) && $error !== true) { + $this->warn(sprintf('%s: Unable to send email: %s', + $U->getDefaultEmail(), $error->getMessage() + )); + } + } + } + + break; + + case 'lock': + $users = $this->getQuerySet($options); + $users->select_related('account'); + foreach ($users as $U) { + if (!($account = $U->getAccount())) { + $this->warn(sprintf( + '%s: User does not have a client account', + $U->getName() + )); + } + $account->setFlag(UserAccountStatus::LOCKED); + $account->save(); + } + + break; + + case 'list': + $users = $this->getQuerySet($options); + + foreach ($users as $U) { + $this->stdout->write(sprintf( + "%d %s <%s>%s\n", + $U->id, $U->getName(), $U->getDefaultEmail(), + ($O = $U->getOrganization()) ? " ({$O->getName()})" : '' + )); + } + + break; + + case 'set-password': + $this->stderr->write('Enter new password: '); + $ps1 = fgets(STDIN); + if (!function_exists('posix_isatty') || !posix_isatty(STDIN)) { + $this->stderr->write('Re-enter new password: '); + $ps2 = fgets(STDIN); + + if ($ps1 != $ps2) + $this->fail('Passwords do not match'); + } + + // Account is required + $options['account'] = true; + $users = $this->getQuerySet($options); + + $updated = 0; + foreach ($users as $U) { + $U->account->setPassword($ps1); + if ($U->account->save()) + $updated++; + } + $this->stdout->write(sprintf('Updated %d users', $updated)); + break; + + default: + $this->stderr->write('Unknown action!'); + } + @fclose($this->stream); + } + + function getQuerySet($options, $requireOne=false) { + $users = User::objects(); + foreach ($options as $O=>$V) { + if (!isset($V)) + continue; + switch ($O) { + case 'account': + $users->filter(array('account__isnull' => !$V)); break; - case 'export': - $stream = $options['file'] ?: 'php://stdout'; - if (!($this->stream = fopen($stream, 'c'))) - $this->fail("Unable to open output file [{$options['file']}]"); + case 'isconfirmed': + case 'islocked': + $flags = array( + 'isconfirmed' => UserAccountStatus::CONFIRMED, + 'islocked' => UserAccountStatus::LOCKED, + ); + $Q = new Q(array('account__status__hasbit'=>$flags[$O])); + if (!$V) + $Q->negate(); + $users->filter($Q); + break; - fputcsv($this->stream, array('Name', 'Email')); - foreach (User::objects() as $user) - fputcsv($this->stream, - array((string) $user->getName(), $user->getEmail())); + case 'org': + if (is_numeric($V)) + $users->filter(array('org__id'=>$V)); + else + $users->filter(array('org__name__contains'=>$V)); break; - default: - $this->stderr->write('Unknown action!'); + + case 'id': + $users->filter(array('id'=>$V)); + break; + } + } - @fclose($this->stream); + return $users; } } Module::register('user', 'UserManager');