PageRenderTime 52ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/qa-include/app/users-edit.php

http://github.com/q2a/question2answer
PHP | 542 lines | 295 code | 117 blank | 130 comment | 45 complexity | 59e28ecb8078a54b0af5003b9c285eb0 MD5 | raw file
Possible License(s): LGPL-2.1
  1. <?php
  2. /*
  3. Question2Answer by Gideon Greenspan and contributors
  4. http://www.question2answer.org/
  5. Description: User management (application level) for creating/modifying users
  6. This program is free software; you can redistribute it and/or
  7. modify it under the terms of the GNU General Public License
  8. as published by the Free Software Foundation; either version 2
  9. of the License, or (at your option) any later version.
  10. This program is distributed in the hope that it will be useful,
  11. but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. GNU General Public License for more details.
  14. More about this license: http://www.question2answer.org/license.php
  15. */
  16. use Q2A\Exceptions\FatalErrorException;
  17. if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
  18. header('Location: ../../');
  19. exit;
  20. }
  21. if (!defined('QA_MIN_PASSWORD_LEN')) {
  22. define('QA_MIN_PASSWORD_LEN', 8);
  23. }
  24. if (!defined('QA_NEW_PASSWORD_LEN')){
  25. /**
  26. * @deprecated This was the length of the reset password generated by Q2A. No longer used.
  27. */
  28. define('QA_NEW_PASSWORD_LEN', 8);
  29. }
  30. /**
  31. * Return $errors fields for any invalid aspect of user-entered $handle (username) and $email. Works by calling through
  32. * to all filter modules and also rejects existing values in database unless they belongs to $olduser (if set).
  33. * @param string $handle
  34. * @param string $email
  35. * @param array $olduser
  36. * @return array
  37. */
  38. function qa_handle_email_filter(&$handle, &$email, $olduser = null)
  39. {
  40. require_once QA_INCLUDE_DIR . 'db/users.php';
  41. require_once QA_INCLUDE_DIR . 'util/string.php';
  42. $errors = array();
  43. // sanitize 4-byte Unicode and invisible characters
  44. $handle = qa_remove_utf8mb4($handle);
  45. $handle = preg_replace('/\p{C}+/u', '', $handle);
  46. $filtermodules = qa_load_modules_with('filter', 'filter_handle');
  47. foreach ($filtermodules as $filtermodule) {
  48. $error = $filtermodule->filter_handle($handle, $olduser);
  49. if (isset($error)) {
  50. $errors['handle'] = $error;
  51. break;
  52. }
  53. }
  54. if (!isset($errors['handle'])) { // first test through filters, then check for duplicates here
  55. $handleusers = qa_db_user_find_by_handle($handle);
  56. if (count($handleusers) && ((!isset($olduser['userid'])) || (array_search($olduser['userid'], $handleusers) === false)))
  57. $errors['handle'] = qa_lang('users/handle_exists');
  58. }
  59. $filtermodules = qa_load_modules_with('filter', 'filter_email');
  60. $error = null;
  61. foreach ($filtermodules as $filtermodule) {
  62. $error = $filtermodule->filter_email($email, $olduser);
  63. if (isset($error)) {
  64. $errors['email'] = $error;
  65. break;
  66. }
  67. }
  68. if (!isset($errors['email'])) {
  69. $emailusers = qa_db_user_find_by_email($email);
  70. if (count($emailusers) && ((!isset($olduser['userid'])) || (array_search($olduser['userid'], $emailusers) === false)))
  71. $errors['email'] = qa_lang('users/email_exists');
  72. }
  73. return $errors;
  74. }
  75. /**
  76. * Make $handle valid and unique in the database - if $allowuserid is set, allow it to match that user only
  77. * @param string $handle
  78. * @return string
  79. */
  80. function qa_handle_make_valid($handle)
  81. {
  82. require_once QA_INCLUDE_DIR . 'util/string.php';
  83. require_once QA_INCLUDE_DIR . 'db/maxima.php';
  84. require_once QA_INCLUDE_DIR . 'db/users.php';
  85. if (!strlen($handle))
  86. $handle = qa_lang('users/registered_user');
  87. $handle = preg_replace('/[\\@\\+\\/]/', ' ', $handle);
  88. for ($attempt = 0; $attempt <= 99; $attempt++) {
  89. $suffix = $attempt ? (' ' . $attempt) : '';
  90. $tryhandle = qa_substr($handle, 0, QA_DB_MAX_HANDLE_LENGTH - strlen($suffix)) . $suffix;
  91. $filtermodules = qa_load_modules_with('filter', 'filter_handle');
  92. foreach ($filtermodules as $filtermodule) {
  93. // filter first without worrying about errors, since our goal is to get a valid one
  94. $filtermodule->filter_handle($tryhandle, null);
  95. }
  96. $haderror = false;
  97. foreach ($filtermodules as $filtermodule) {
  98. $error = $filtermodule->filter_handle($tryhandle, null); // now check for errors after we've filtered
  99. if (isset($error))
  100. $haderror = true;
  101. }
  102. if (!$haderror) {
  103. $handleusers = qa_db_user_find_by_handle($tryhandle);
  104. if (!count($handleusers))
  105. return $tryhandle;
  106. }
  107. }
  108. qa_fatal_error('Could not create a valid and unique handle from: ' . $handle);
  109. }
  110. /**
  111. * Return an array with a single element (key 'password') if user-entered $password is valid, otherwise an empty array.
  112. * Works by calling through to all filter modules.
  113. * @param string $password
  114. * @param array $olduser
  115. * @return array
  116. */
  117. function qa_password_validate($password, $olduser = null)
  118. {
  119. $error = null;
  120. $filtermodules = qa_load_modules_with('filter', 'validate_password');
  121. foreach ($filtermodules as $filtermodule) {
  122. $error = $filtermodule->validate_password($password, $olduser);
  123. if (isset($error))
  124. break;
  125. }
  126. if (!isset($error)) {
  127. $minpasslen = max(QA_MIN_PASSWORD_LEN, 1);
  128. if (qa_strlen($password) < $minpasslen)
  129. $error = qa_lang_sub('users/password_min', $minpasslen);
  130. }
  131. if (isset($error))
  132. return array('password' => $error);
  133. return array();
  134. }
  135. /**
  136. * Create a new user (application level) with $email, $password, $handle and $level.
  137. * Set $confirmed to true if the email address has been confirmed elsewhere.
  138. * Handles user points, notification and optional email confirmation.
  139. * @param string $email
  140. * @param string|null $password
  141. * @param string $handle
  142. * @param int $level
  143. * @param bool $confirmed
  144. * @return mixed
  145. */
  146. function qa_create_new_user($email, $password, $handle, $level = QA_USER_LEVEL_BASIC, $confirmed = false)
  147. {
  148. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  149. require_once QA_INCLUDE_DIR . 'db/users.php';
  150. require_once QA_INCLUDE_DIR . 'db/points.php';
  151. require_once QA_INCLUDE_DIR . 'app/options.php';
  152. require_once QA_INCLUDE_DIR . 'app/emails.php';
  153. require_once QA_INCLUDE_DIR . 'app/cookies.php';
  154. $userid = qa_db_user_create($email, $password, $handle, $level, qa_remote_ip_address());
  155. qa_db_points_update_ifuser($userid, null);
  156. qa_db_uapprovecount_update();
  157. if ($confirmed)
  158. qa_db_user_set_flag($userid, QA_USER_FLAGS_EMAIL_CONFIRMED, true);
  159. if (qa_opt('show_notice_welcome'))
  160. qa_db_user_set_flag($userid, QA_USER_FLAGS_WELCOME_NOTICE, true);
  161. $custom = qa_opt('show_custom_welcome') ? trim(qa_opt('custom_welcome')) : '';
  162. if (qa_opt('confirm_user_emails') && $level < QA_USER_LEVEL_EXPERT && !$confirmed) {
  163. $confirm = strtr(qa_lang('emails/welcome_confirm'), array(
  164. '^url' => qa_get_new_confirm_url($userid, $handle),
  165. ));
  166. if (qa_opt('confirm_user_required'))
  167. qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_CONFIRM, true);
  168. } else
  169. $confirm = '';
  170. // we no longer use the 'approve_user_required' option to set QA_USER_FLAGS_MUST_APPROVE; this can be handled by the Permissions settings
  171. qa_send_notification($userid, $email, $handle, qa_lang('emails/welcome_subject'), qa_lang('emails/welcome_body'), array(
  172. '^password' => isset($password) ? qa_lang('main/hidden') : qa_lang('users/password_to_set'), // v 1.6.3: no longer email out passwords
  173. '^url' => qa_opt('site_url'),
  174. '^custom' => strlen($custom) ? ($custom . "\n\n") : '',
  175. '^confirm' => $confirm,
  176. ));
  177. qa_report_event('u_register', $userid, $handle, qa_cookie_get(), array(
  178. 'email' => $email,
  179. 'level' => $level,
  180. ));
  181. return $userid;
  182. }
  183. /**
  184. * Delete $userid and all their votes and flags. Their posts will become anonymous.
  185. * Handles recalculations of votes and flags for posts this user has affected.
  186. * @param mixed $userid
  187. * @return mixed
  188. */
  189. function qa_delete_user($userid)
  190. {
  191. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  192. require_once QA_INCLUDE_DIR . 'db/votes.php';
  193. require_once QA_INCLUDE_DIR . 'db/users.php';
  194. require_once QA_INCLUDE_DIR . 'db/post-update.php';
  195. require_once QA_INCLUDE_DIR . 'db/points.php';
  196. $postids = qa_db_uservoteflag_user_get($userid); // posts this user has flagged or voted on, whose counts need updating
  197. qa_db_user_delete($userid);
  198. qa_db_uapprovecount_update();
  199. qa_db_userpointscount_update();
  200. foreach ($postids as $postid) { // hoping there aren't many of these - saves a lot of new SQL code...
  201. qa_db_post_recount_votes($postid);
  202. qa_db_post_recount_flags($postid);
  203. }
  204. $postuserids = qa_db_posts_get_userids($postids);
  205. foreach ($postuserids as $postuserid) {
  206. qa_db_points_update_ifuser($postuserid, array('avoteds', 'qvoteds', 'upvoteds', 'downvoteds'));
  207. }
  208. }
  209. /**
  210. * Set a new email confirmation code for the user and send it out
  211. * @param mixed $userid
  212. * @return mixed
  213. */
  214. function qa_send_new_confirm($userid)
  215. {
  216. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  217. require_once QA_INCLUDE_DIR . 'db/users.php';
  218. require_once QA_INCLUDE_DIR . 'db/selects.php';
  219. require_once QA_INCLUDE_DIR . 'app/emails.php';
  220. $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
  221. $emailcode = qa_db_user_rand_emailcode();
  222. if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/confirm_subject'), qa_lang('emails/confirm_body'), array(
  223. '^url' => qa_get_new_confirm_url($userid, $userinfo['handle'], $emailcode),
  224. '^code' => $emailcode,
  225. ))) {
  226. qa_fatal_error('Could not send email confirmation');
  227. }
  228. }
  229. /**
  230. * Set a new email confirmation code for the user and return the corresponding link. If the email code is also sent then that value
  231. * is used. Otherwise, a new email code is generated
  232. * @param mixed $userid
  233. * @param string $handle
  234. * @param string $emailcode
  235. * @return string
  236. */
  237. function qa_get_new_confirm_url($userid, $handle, $emailcode = null)
  238. {
  239. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  240. require_once QA_INCLUDE_DIR . 'db/users.php';
  241. if (!isset($emailcode)) {
  242. $emailcode = qa_db_user_rand_emailcode();
  243. }
  244. qa_db_user_set($userid, 'emailcode', $emailcode);
  245. return qa_path_absolute('confirm', array('c' => $emailcode, 'u' => $handle));
  246. }
  247. /**
  248. * Complete the email confirmation process for the user
  249. * @param mixed $userid
  250. * @param string $email
  251. * @param string $handle
  252. * @return mixed
  253. */
  254. function qa_complete_confirm($userid, $email, $handle)
  255. {
  256. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  257. require_once QA_INCLUDE_DIR . 'db/users.php';
  258. require_once QA_INCLUDE_DIR . 'app/cookies.php';
  259. qa_db_user_set_flag($userid, QA_USER_FLAGS_EMAIL_CONFIRMED, true);
  260. qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_CONFIRM, false);
  261. qa_db_user_set($userid, 'emailcode', ''); // to prevent re-use of the code
  262. qa_report_event('u_confirmed', $userid, $handle, qa_cookie_get(), array(
  263. 'email' => $email,
  264. ));
  265. }
  266. /**
  267. * Set the user level of user $userid with $handle to $level (one of the QA_USER_LEVEL_* constraints in /qa-include/app/users.php)
  268. * Pass the previous user level in $oldlevel. Reports the appropriate event, assumes change performed by the logged in user.
  269. * @param mixed $userid
  270. * @param string $handle
  271. * @param int $level
  272. * @param int $oldlevel
  273. */
  274. function qa_set_user_level($userid, $handle, $level, $oldlevel)
  275. {
  276. require_once QA_INCLUDE_DIR . 'db/users.php';
  277. qa_db_user_set($userid, 'level', $level);
  278. qa_db_uapprovecount_update();
  279. if ($level >= QA_USER_LEVEL_APPROVED) {
  280. // no longer necessary as QA_USER_FLAGS_MUST_APPROVE is deprecated, but kept for posterity
  281. qa_db_user_set_flag($userid, QA_USER_FLAGS_MUST_APPROVE, false);
  282. }
  283. qa_report_event('u_level', qa_get_logged_in_userid(), qa_get_logged_in_handle(), qa_cookie_get(), array(
  284. 'userid' => $userid,
  285. 'handle' => $handle,
  286. 'level' => $level,
  287. 'oldlevel' => $oldlevel,
  288. ));
  289. }
  290. /**
  291. * Set the status of user $userid with $handle to blocked if $blocked is true, otherwise to unblocked. Reports the appropriate
  292. * event, assumes change performed by the logged in user.
  293. * @param mixed $userid
  294. * @param string $handle
  295. * @param string $blocked
  296. */
  297. function qa_set_user_blocked($userid, $handle, $blocked)
  298. {
  299. require_once QA_INCLUDE_DIR . 'db/users.php';
  300. qa_db_user_set_flag($userid, QA_USER_FLAGS_USER_BLOCKED, $blocked);
  301. qa_db_uapprovecount_update();
  302. qa_report_event($blocked ? 'u_block' : 'u_unblock', qa_get_logged_in_userid(), qa_get_logged_in_handle(), qa_cookie_get(), array(
  303. 'userid' => $userid,
  304. 'handle' => $handle,
  305. ));
  306. }
  307. /**
  308. * Start the 'I forgot my password' process for $userid, sending reset code
  309. * @param mixed $userid
  310. * @return mixed
  311. */
  312. function qa_start_reset_user($userid)
  313. {
  314. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  315. require_once QA_INCLUDE_DIR . 'db/users.php';
  316. require_once QA_INCLUDE_DIR . 'app/options.php';
  317. require_once QA_INCLUDE_DIR . 'app/emails.php';
  318. require_once QA_INCLUDE_DIR . 'db/selects.php';
  319. qa_db_user_set($userid, 'emailcode', qa_db_user_rand_emailcode());
  320. $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
  321. if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/reset_subject'), qa_lang('emails/reset_body'), array(
  322. '^code' => $userinfo['emailcode'],
  323. '^url' => qa_path_absolute('reset', array('c' => $userinfo['emailcode'], 'e' => $userinfo['email'])),
  324. ))) {
  325. qa_fatal_error('Could not send reset password email');
  326. }
  327. }
  328. /**
  329. * Successfully finish the 'I forgot my password' process for $userid, sending new password
  330. * @deprecated This function has been replaced by qa_finish_reset_user since Q2A 1.8
  331. * @param mixed $userid
  332. * @return mixed
  333. */
  334. function qa_complete_reset_user($userid)
  335. {
  336. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  337. require_once QA_INCLUDE_DIR . 'util/string.php';
  338. require_once QA_INCLUDE_DIR . 'app/options.php';
  339. require_once QA_INCLUDE_DIR . 'app/emails.php';
  340. require_once QA_INCLUDE_DIR . 'app/cookies.php';
  341. require_once QA_INCLUDE_DIR . 'db/selects.php';
  342. $password = qa_random_alphanum(max(QA_MIN_PASSWORD_LEN, QA_NEW_PASSWORD_LEN));
  343. $userinfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userid, true));
  344. if (!qa_send_notification($userid, $userinfo['email'], $userinfo['handle'], qa_lang('emails/new_password_subject'), qa_lang('emails/new_password_body'), array(
  345. '^password' => $password,
  346. '^url' => qa_opt('site_url'),
  347. ))) {
  348. qa_fatal_error('Could not send new password - password not reset');
  349. }
  350. qa_db_user_set_password($userid, $password); // do this last, to be safe
  351. qa_db_user_set($userid, 'emailcode', ''); // so can't be reused
  352. qa_report_event('u_reset', $userid, $userinfo['handle'], qa_cookie_get(), array(
  353. 'email' => $userinfo['email'],
  354. ));
  355. }
  356. /**
  357. * Successfully finish the 'I forgot my password' process for $userid, cleaning the emailcode field and logging in the user
  358. * @param mixed $userId The userid identifiying the user who will have the password reset
  359. * @param string $newPassword The new password for the user
  360. * @return mixed
  361. */
  362. function qa_finish_reset_user($userId, $newPassword)
  363. {
  364. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  365. // For qa_db_user_set_password(), qa_db_user_set()
  366. require_once QA_INCLUDE_DIR . 'db/users.php';
  367. // For qa_set_logged_in_user()
  368. require_once QA_INCLUDE_DIR . 'app/options.php';
  369. // For qa_cookie_get()
  370. require_once QA_INCLUDE_DIR . 'app/cookies.php';
  371. // For qa_db_select_with_pending(), qa_db_user_account_selectspec()
  372. require_once QA_INCLUDE_DIR . 'db/selects.php';
  373. // For qa_set_logged_in_user()
  374. require_once QA_INCLUDE_DIR . 'app/users.php';
  375. qa_db_user_set_password($userId, $newPassword);
  376. qa_db_user_set($userId, 'emailcode', ''); // to prevent re-use of the code
  377. $userInfo = qa_db_select_with_pending(qa_db_user_account_selectspec($userId, true));
  378. qa_set_logged_in_user($userId, $userInfo['handle'], false, $userInfo['sessionsource']); // reinstate this specific session
  379. qa_report_event('u_reset', $userId, $userInfo['handle'], qa_cookie_get(), array(
  380. 'email' => $userInfo['email'],
  381. ));
  382. }
  383. /**
  384. * Flush any information about the currently logged in user, so it is retrieved from database again
  385. */
  386. function qa_logged_in_user_flush()
  387. {
  388. global $qa_cached_logged_in_user;
  389. $qa_cached_logged_in_user = null;
  390. }
  391. /**
  392. * Set the avatar of $userid to the image in $imagedata, and remove $oldblobid from the database if not null
  393. * @param mixed $userid
  394. * @param string $imagedata
  395. * @param string $oldblobid
  396. * @return bool
  397. */
  398. function qa_set_user_avatar($userid, $imagedata, $oldblobid = null)
  399. {
  400. if (qa_to_override(__FUNCTION__)) { $args=func_get_args(); return qa_call_override(__FUNCTION__, $args); }
  401. require_once QA_INCLUDE_DIR . 'util/image.php';
  402. $imagedata = qa_image_constrain_data($imagedata, $width, $height, qa_opt('avatar_store_size'));
  403. if (isset($imagedata)) {
  404. require_once QA_INCLUDE_DIR . 'app/blobs.php';
  405. $newblobid = qa_create_blob($imagedata, 'jpeg', null, $userid, null, qa_remote_ip_address());
  406. if (isset($newblobid)) {
  407. qa_db_user_set($userid, array(
  408. 'avatarblobid' => $newblobid,
  409. 'avatarwidth' => $width,
  410. 'avatarheight' => $height,
  411. ));
  412. qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_AVATAR, true);
  413. qa_db_user_set_flag($userid, QA_USER_FLAGS_SHOW_GRAVATAR, false);
  414. if (isset($oldblobid))
  415. qa_delete_blob($oldblobid);
  416. return true;
  417. }
  418. }
  419. return false;
  420. }