<?php /* Question2Answer by Gideon Greenspan and contributors http://www.question2answer.org/ File: qa-include/qa-app-users-edit.php Description: User management (application level) for creating/modifying users 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 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. More about this license: http://www.question2answer.org/license.php */ if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser header('Location: ../'); exit; } @define('QA_MIN_PASSWORD_LEN', 4); @define('QA_NEW_PASSWORD_LEN', 8); // when resetting password /** * Return $errors fields for any invalid aspect of user-entered $handle (username) and $email. Works by calling through * to all filter modules and also rejects existing values in database unless they belongs to $olduser (if set). * @param $handle * @param $email * @param $olduser * @return array */ function qa_handle_email_filter(&$handle, &$email, $olduser = null) { require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'util/string.php'; $errors = array(); // sanitise 4-byte Unicode $handle = qa_remove_utf8mb4($handle); $filtermodules = qa_load_modules_with('filter', 'filter_handle'); foreach ($filtermodules as $filtermodule) { $error = $filtermodule->filter_handle($handle, $olduser); if (isset($error)) { $errors['handle'] = $error; break; } } if (!isset($errors['handle'])) { // first test through filters, then check for duplicates here $handleusers = qa_db_user_find_by_handle($handle); if (count($handleusers) && ((!isset($olduser['userid'])) || (array_search($olduser['userid'], $handleusers) === false))) $errors['handle'] = qa_lang('users/handle_exists'); } $filtermodules = qa_load_modules_with('filter', 'filter_email'); $error = null; foreach ($filtermodules as $filtermodule) { $error = $filtermodule->filter_email($email, $olduser); if (isset($error)) { $errors['email'] = $error; break; } } if (!isset($errors['email'])) { $emailusers = qa_db_user_find_by_email($email); if (count($emailusers) && ((!isset($olduser['userid'])) || (array_search($olduser['userid'], $emailusers) === false))) $errors['email'] = qa_lang('users/email_exists'); } return $errors; } /** * Make $handle valid and unique in the database - if $allowuserid is set, allow it to match that user only * @param $handle * @return string */ function qa_handle_make_valid($handle) { require_once QA_INCLUDE_DIR . 'util/string.php'; require_once QA_INCLUDE_DIR . 'db/maxima.php'; require_once QA_INCLUDE_DIR . 'db/users.php'; if (!strlen($handle)) $handle = qa_lang('users/registered_user'); $handle = preg_replace('/[\\@\\+\\/]/', ' ', $handle); for ($attempt = 0; $attempt <= 99; $attempt++) { $suffix = $attempt ? (' ' . $attempt) : ''; $tryhandle = qa_substr($handle, 0, QA_DB_MAX_HANDLE_LENGTH - strlen($suffix)) . $suffix; $filtermodules = qa_load_modules_with('filter', 'filter_handle'); foreach ($filtermodules as $filtermodule) { // filter first without worrying about errors, since our goal is to get a valid one $filtermodule->filter_handle($tryhandle, null); } $haderror = false; foreach ($filtermodules as $filtermodule) { $error = $filtermodule->filter_handle($tryhandle, null); // now check for errors after we've filtered if (isset($error)) $haderror = true; } if (!$haderror) { $handleusers = qa_db_user_find_by_handle($tryhandle); if (!count($handleusers)) return $tryhandle; } } qa_fatal_error('Could not create a valid and unique handle from: ' . $handle); } /** * Return an array with a single element (key 'password') if user-entered $password is valid, otherwise an empty array. * Works by calling through to all filter modules. * @param $password * @param $olduser * @return array */ function qa_password_validate($password, $olduser = null) { $error = null; $filtermodules = qa_load_modules_with('filter', 'validate_password'); foreach ($filtermodules as $filtermodule) { $error = $filtermodule->validate_password($password, $olduser); if (isset($error)) break; } if (!isset($error)) { $minpasslen = max(QA_MIN_PASSWORD_LEN, 1); if (qa_strlen($password) < $minpasslen) $error = qa_lang_sub('users/password_min', $minpasslen); } if (isset($error)) return array('password' => $error); return array(); } /** * Create a new user (application level) with $email, $password, $handle and $level. * Set $confirmed to true if the email address has been confirmed elsewhere. * Handles user points, notification and optional email confirmation. * @param $email * @param $password * @param $handle * @param int $level * @param bool $confirmed * @return mixed */ function qa_create_new_user($email, $password, $handle, $level = QA_USER_LEVEL_BASIC, $confirmed = false) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'db/points.php'; require_once QA_INCLUDE_DIR . 'app/options.php'; require_once QA_INCLUDE_DIR . 'app/emails.php'; require_once QA_INCLUDE_DIR . 'app/cookies.php'; $userid = qa_db_user_create($email, $password, $handle, $level, qa_remote_ip_address()); qa_db_points_update_ifuser($userid, null); qa_db_uapprovecount_update(); if ($confirmed) qa_db_user_set_flag($userid, QA_USER_FLAGS_EMAIL_CONFIRMED, true); if (qa_opt('show_notice_welcome')) qa_db_user_set_flag($userid, QA_USER_FLAGS_WELCOME_NOTICE, true); $custom = qa_opt('show_custom_welcome') ? trim(qa_opt('custom_welcome')) : ''; if (qa_opt('confirm_user_emails') && $level < QA_USER_LEVEL_EXPERT && !$confirmed) { $confirm = strtr(qa_lang('emails/welcome_confirm'), array( '^url' => qa_get_new_confirm_url($userid, $handle), )); if (qa_opt('confirm_user_required')) qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_CONFIRM, true); } else $confirm = ''; if (qa_opt('moderate_users') && qa_opt('approve_user_required') && $level < QA_USER_LEVEL_EXPERT) qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_APPROVE, true); qa_send_notification($userid, $email, $handle, qa_lang('emails/welcome_subject'), qa_lang('emails/welcome_body'), array( '^password' => isset($password) ? qa_lang('main/hidden') : qa_lang('users/password_to_set'), // v 1.6.3: no longer email out passwords '^url' => qa_opt('site_url'), '^custom' => strlen($custom) ? ($custom . "\n\n") : '', '^confirm' => $confirm, )); qa_report_event('u_register', $userid, $handle, qa_cookie_get(), array( 'email' => $email, 'level' => $level, )); return $userid; } /** * Delete $userid and all their votes and flags. Their posts will become anonymous. * Handles recalculations of votes and flags for posts this user has affected. * @param $userid * @return mixed */ function qa_delete_user($userid) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/votes.php'; require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'db/post-update.php'; require_once QA_INCLUDE_DIR . 'db/points.php'; $postids = qa_db_uservoteflag_user_get($userid); // posts this user has flagged or voted on, whose counts need updating qa_db_user_delete($userid); qa_db_uapprovecount_update(); qa_db_userpointscount_update(); foreach ($postids as $postid) { // hoping there aren't many of these - saves a lot of new SQL code... qa_db_post_recount_votes($postid); qa_db_post_recount_flags($postid); } $postuserids = qa_db_posts_get_userids($postids); foreach ($postuserids as $postuserid) { qa_db_points_update_ifuser($postuserid, array('avoteds', 'qvoteds', 'upvoteds', 'downvoteds')); } } /** * Set a new email confirmation code for the user and send it out * @param $userid * @return mixed */ function qa_send_new_confirm($userid) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'db/selects.php'; require_once QA_INCLUDE_DIR . 'app/emails.php'; $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true)); $emailcode = qa_db_user_rand_emailcode(); if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/confirm_subject'), qa_lang('emails/confirm_body'), array( '^url' => qa_get_new_confirm_url($userid, $userinfo['handle'], $emailcode), '^code' => $emailcode, ))) { qa_fatal_error('Could not send email confirmation'); } } /** * Set a new email confirmation code for the user and return the corresponding link. If the email code is also sent then that value * is used. Otherwise, a new email code is generated * @param $userid * @param $handle * @param $emailcode * @return mixed|string */ function qa_get_new_confirm_url($userid, $handle, $emailcode = null) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/users.php'; if (!isset($emailcode)) { $emailcode = qa_db_user_rand_emailcode(); } qa_db_user_set($userid, 'emailcode', $emailcode); return qa_path_absolute('confirm', array('c' => $emailcode, 'u' => $handle)); } /** * Complete the email confirmation process for the user * @param $userid * @param $email * @param $handle * @return mixed */ function qa_complete_confirm($userid, $email, $handle) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'app/cookies.php'; qa_db_user_set_flag($userid, QA_USER_FLAGS_EMAIL_CONFIRMED, true); qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_CONFIRM, false); qa_db_user_set($userid, 'emailcode', ''); // to prevent re-use of the code qa_report_event('u_confirmed', $userid, $handle, qa_cookie_get(), array( 'email' => $email, )); } /** * Set the user level of user $userid with $handle to $level (one of the QA_USER_LEVEL_* constraints in qa-app-users.php) * Pass the previous user level in $oldlevel. Reports the appropriate event, assumes change performed by the logged in user. * @param $userid * @param $handle * @param $level * @param $oldlevel */ function qa_set_user_level($userid, $handle, $level, $oldlevel) { require_once QA_INCLUDE_DIR . 'db/users.php'; qa_db_user_set($userid, 'level', $level); qa_db_uapprovecount_update(); if ($level >= QA_USER_LEVEL_APPROVED) qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_APPROVE, false); qa_report_event('u_level', qa_get_logged_in_userid(), qa_get_logged_in_handle(), qa_cookie_get(), array( 'userid' => $userid, 'handle' => $handle, 'level' => $level, 'oldlevel' => $oldlevel, )); } /** * Set the status of user $userid with $handle to blocked if $blocked is true, otherwise to unblocked. Reports the appropriate * event, assumes change performed by the logged in user. * @param $userid * @param $handle * @param $blocked */ function qa_set_user_blocked($userid, $handle, $blocked) { require_once QA_INCLUDE_DIR . 'db/users.php'; qa_db_user_set_flag($userid, QA_USER_FLAGS_USER_BLOCKED, $blocked); qa_db_uapprovecount_update(); qa_report_event($blocked ? 'u_block' : 'u_unblock', qa_get_logged_in_userid(), qa_get_logged_in_handle(), qa_cookie_get(), array( 'userid' => $userid, 'handle' => $handle, )); } /** * Start the 'I forgot my password' process for $userid, sending reset code * @param $userid * @return mixed */ function qa_start_reset_user($userid) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'db/users.php'; require_once QA_INCLUDE_DIR . 'app/options.php'; require_once QA_INCLUDE_DIR . 'app/emails.php'; require_once QA_INCLUDE_DIR . 'db/selects.php'; qa_db_user_set($userid, 'emailcode', qa_db_user_rand_emailcode()); $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true)); if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/reset_subject'), qa_lang('emails/reset_body'), array( '^code' => $userinfo['emailcode'], '^url' => qa_path_absolute('reset', array('c' => $userinfo['emailcode'], 'e' => $userinfo['email'])), ))) { qa_fatal_error('Could not send reset password email'); } } /** * Successfully finish the 'I forgot my password' process for $userid, sending new password * * @deprecated This function has been replaced by qa_finish_reset_user since Q2A 1.8 * @param $userid * @return mixed */ function qa_complete_reset_user($userid) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'util/string.php'; require_once QA_INCLUDE_DIR . 'app/options.php'; require_once QA_INCLUDE_DIR . 'app/emails.php'; require_once QA_INCLUDE_DIR . 'app/cookies.php'; require_once QA_INCLUDE_DIR . 'db/selects.php'; $password = qa_random_alphanum(max(QA_MIN_PASSWORD_LEN, QA_NEW_PASSWORD_LEN)); $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true)); if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/new_password_subject'), qa_lang('emails/new_password_body'), array( '^password' => $password, '^url' => qa_opt('site_url'), ))) { qa_fatal_error('Could not send new password - password not reset'); } qa_db_user_set_password($userid, $password); // do this last, to be safe qa_db_user_set($userid, 'emailcode', ''); // so can't be reused qa_report_event('u_reset', $userid, $userinfo['handle'], qa_cookie_get(), array( 'email' => $userinfo['email'], )); } /** * Successfully finish the 'I forgot my password' process for $userid, cleaning the emailcode field and logging in the user * @param mixed $userId The userid identifiying the user who will have the password reset * @param string $newPassword The new password for the user * @return void */ function qa_finish_reset_user($userId, $newPassword) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } // For qa_db_user_set_password(), qa_db_user_set() require_once QA_INCLUDE_DIR . 'db/users.php'; // For qa_set_logged_in_user() require_once QA_INCLUDE_DIR . 'app/options.php'; // For qa_cookie_get() require_once QA_INCLUDE_DIR . 'app/cookies.php'; // For qa_db_select_with_pending(), qa_db_user_account_selectspec() require_once QA_INCLUDE_DIR . 'db/selects.php'; // For qa_set_logged_in_user() require_once QA_INCLUDE_DIR . 'app/users.php'; qa_db_user_set_password($userId, $newPassword); qa_db_user_set($userId, 'emailcode', ''); // to prevent re-use of the code $userInfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userId, true)); qa_set_logged_in_user($userId, $userInfo['handle'], false, $userInfo['sessionsource']); // reinstate this specific session qa_report_event('u_reset', $userId, $userInfo['handle'], qa_cookie_get(), array( 'email' => $userInfo['email'], )); } /** * Flush any information about the currently logged in user, so it is retrieved from database again */ function qa_logged_in_user_flush() { global $qa_cached_logged_in_user; $qa_cached_logged_in_user = null; } /** * Set the avatar of $userid to the image in $imagedata, and remove $oldblobid from the database if not null * @param $userid * @param $imagedata * @param $oldblobid * @return bool */ function qa_set_user_avatar($userid, $imagedata, $oldblobid = null) { if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); } require_once QA_INCLUDE_DIR . 'util/image.php'; $imagedata = qa_image_constrain_data($imagedata, $width, $height, qa_opt('avatar_store_size')); if (isset($imagedata)) { require_once QA_INCLUDE_DIR . 'app/blobs.php'; $newblobid = qa_create_blob($imagedata, 'jpeg', null, $userid, null, qa_remote_ip_address()); if (isset($newblobid)) { qa_db_user_set($userid, 'avatarblobid', $newblobid); qa_db_user_set($userid, 'avatarwidth', $width); qa_db_user_set($userid, 'avatarheight', $height); qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_AVATAR, true); qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_GRAVATAR, false); if (isset($oldblobid)) qa_delete_blob($oldblobid); return true; } } return false; }