* @version 1.0 * @since 20 Aug 2014 17:20:09 */ require_once 'models/Model.inc.php'; class User extends Model { // The database fields used by this model. public static $fields = array( 'user_id' => '', 'account_id' => '', 'username' => '', 'old_username' => '', 'userpass' => '', 'first_name' => '', 'last_name' => '', 'email' => '', 'old_email' => '', 'user_type' => '', 'status' => '', 'login_abuse_exempt' => '', 'blocked' => '', 'blocked_reason' => '', 'abuse_warning_level' => '', 'seconds_online' => '', 'last_login_datetime' => '', 'last_access_datetime' => '', 'last_login_ip' => '0.0.0.0', 'added_datetime' => '', 'added_by_user_id' => '', 'modified_datetime' => '', 'modified_by_user_id' => '', ); /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function insert($frm) { global $auth, $cache; $db =& DB::getInstance(); $app =& App::getInstance(); // Remove any stale cached list data. $cache->delete('user list'); // Create the record under the user's account, unless an account_id was provided. $account_id = isset($frm['account_id']) && is_numeric($frm['account_id']) ? $frm['account_id'] : $auth->get('account_id'); // Insert record data. $app->logMsg(sprintf('%s with data %s', __METHOD__, getDump($frm)), LOG_DEBUG, __FILE__, __LINE__); $db->query(" INSERT INTO user_tbl ( account_id, username, first_name, last_name, email, login_abuse_exempt, added_datetime, added_by_user_id, modified_datetime, modified_by_user_id ) VALUES ( '" . $db->escapeString($account_id) . "', '" . $db->escapeString($frm['username']) . "', '" . $db->escapeString($frm['first_name']) . "', '" . $db->escapeString($frm['last_name']) . "', '" . $db->escapeString($frm['email']) . "', '" . (isset($frm['login_abuse_exempt']) && !empty($frm['login_abuse_exempt'])) . "', NOW(), '" . $db->escapeString($auth->get('user_id')) . "', NOW(), '" . $db->escapeString($auth->get('user_id')) . "' ) "); $last_insert_id = mysql_insert_id($db->getDBH()); // Set user password. $auth->setPassword($last_insert_id, $frm['userpass']); // Create version. $version = Version::getInstance($auth); $version->create('user_tbl', 'user_id', $last_insert_id, $frm['username']); return $last_insert_id; } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function update($frm) { global $auth, $lock, $cache; $db =& DB::getInstance(); $app =& App::getInstance(); $lock->select('user_tbl', 'user_id', $frm['user_id']); if ($lock->isLocked() && !$lock->isMine()) { $lock->dieErrorPage(); } // Remove any stale cached list data. $cache->delete('user list'); $status_clause = ''; if ('' != $frm['old_email'] && $frm['old_email'] != $frm['email']) { // User has changed their email address; we need to reverify. $status_clause = "status = 'email pending',"; // TODO: excessive message? // $app->raiseMsg(sprintf(_("This email address has not yet been confirmed. Please check your email for a confirmation link to keep your account active."), $frm['email']), MSG_NOTICE, __FILE__, __LINE__); self::requestEmailConfirmation($frm['user_id'], $frm['email']); } // Create the record under the user's account, unless an account_id was provided. $account_id = isset($frm['account_id']) && is_numeric($frm['account_id']) ? $frm['account_id'] : $auth->get('account_id'); // Update record data. $app->logMsg(sprintf('%s with data %s', __METHOD__, getDump($frm)), LOG_DEBUG, __FILE__, __LINE__); $db->query(" UPDATE user_tbl SET account_id = '" . $db->escapeString($account_id) . "', username = '" . $db->escapeString($frm['username']) . "', first_name = '" . $db->escapeString($frm['first_name']) . "', last_name = '" . $db->escapeString($frm['last_name']) . "', email = '" . $db->escapeString($frm['email']) . "', $status_clause -- login_abuse_exempt = '" . (isset($frm['login_abuse_exempt']) && !empty($frm['login_abuse_exempt'])) . "', modified_datetime = NOW(), modified_by_user_id = '" . $db->escapeString($auth->get('user_id')) . "' WHERE user_id = '" . $db->escapeString($frm['user_id']) . "' "); // Update the password if it has been given. if ('' != trim($frm['userpass'])) { // Set user password. if (false === $auth->setPassword($frm['user_id'], $frm['userpass'])) { $app->raiseMsg(sprintf(_("Setting password failed. Please try again."), null), MSG_ERR, __FILE__, __LINE__); } } // If the email was changed during edit, require confirmation. if ($frm['email'] != $frm['old_email']) { // TODO } // Create version. $version = Version::getInstance($auth); $version->create('user_tbl', 'user_id', $frm['user_id'], $frm['username']); // Unlock record. $lock->remove(); } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function delete($id) { global $auth, $lock, $cache, $locally_carried_queries; $db =& DB::getInstance(); $app =& App::getInstance(); $lock->select('user_tbl', 'user_id', $id); if ($lock->isLocked() && !$lock->isMine()) { $lock->dieErrorPage(); } // Remove any stale cached list data. $cache->delete('user list'); // Delete the record. // $db->query("DELETE FROM user_tbl WHERE user_id = '" . $db->escapeString($id) . "'"); /// // Unlock record. $lock->remove(); } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function getPaginatedList($where_clause='') { global $page, $so, $cache; $db =& DB::getInstance(); $app =& App::getInstance(); // Build search query if available. if (getFormData('q', false)) { $qry_words = preg_split('/[^\w]/', getFormData('q')); for ($i=0; $iescapeString($qry_words[$i]) . "%' OR user_tbl.username LIKE '%" . $db->escapeString($qry_words[$i]) . "%' OR user_tbl.first_name LIKE '%" . $db->escapeString($qry_words[$i]) . "%' OR user_tbl.last_name LIKE '%" . $db->escapeString($qry_words[$i]) . "%' OR user_tbl.email LIKE '%" . $db->escapeString($qry_words[$i]) . "%' ) "; } } if (getFormData('filter_account_id', false)) { // Limit by filter. $where_clause .= (empty($where_clause) ? 'WHERE' : ' AND') . " user_tbl.account_id = '" . $db->escapeString(getFormData('filter_account_id')) . "'"; } // Count the total number of records so we can do something about the page numbers. $qid = $db->query(" SELECT COUNT(*) FROM user_tbl $where_clause "); list($num_results) = mysql_fetch_row($qid); // Set page numbers now we know (needed for next step). $page->setTotalItems($num_results); $page->calculate(); // Final SQL, with sort and page limiters. $sql = " SELECT user_tbl.*, '' AS userpass, a1.username AS added_by_username, a2.username AS modified_by_username FROM user_tbl LEFT JOIN user_tbl a1 ON (user_tbl.added_by_user_id = a1.user_id) LEFT JOIN user_tbl a2 ON (user_tbl.modified_by_user_id = a2.user_id) $where_clause " . $so->getSortOrderSQL() . " " . $page->getLimitSQL() . " "; // Use a cache hash to determine if the result-set has changed. // A unique key for this query, with the total_items in case db records // were added since the last cache. This identifies a unique set of // cached data, but we must refer to the list that is cached by a more // generic name. so that we can flush the cache (if records updated) // without knowing the hash. $cache_hash = md5($sql . '|' . $page->total_items); $tmp_prefs = new Prefs('session', array('storagetype' => 'session')); if ($tmp_prefs->get('cache_hash') != $cache_hash) { $cache->delete('user list'); $tmp_prefs->set('cache_hash', $cache_hash); } // First try to return from the cache. if ($cache->exists('user list')) { return $cache->get('user list'); } // The list was not cached, so issue the real query. $qid = $db->query($sql); $list = array(); while ($row = mysql_fetch_assoc($qid)) { $list[] = $row; } // Save this list into the cache. if (isset($list) && !empty($list)) { $cache->set('user list', $list); } return $list; } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function requestEmailConfirmation($user_id, $user_email=null, $email_template='email_confirmation.eml') { global $auth; $app =& App::getInstance(); $user = parent::get(array('user_id' => $user_id)); $user_email = isset($user_email) ? $user_email : $user['email']; if ('email pending' != $user['status']) { $app->raiseMsg(sprintf(_("Your email address is already confirmed. No action requried."), null), MSG_SUCCESS, __FILE__, __LINE__); return false; } // Timestamp is number of days since the epoch in hex (i.e., 4 digits instead of 10). $confirm_code = addSignature(sprintf('%s-%s', $user_id, dechex(ceil(time()/86400)))); $email = new Email(array( 'to' => sprintf('%s %s <%s>', $user['first_name'], $user['last_name'], $user_email), 'from' => sprintf('%s <%s>', $app->getParam('site_name'), $app->getParam('site_email')), 'subject' => _("Please confirm your email address"), )); $email->setTemplate($email_template); $email->replace(array( 'site_name' => $app->getParam('site_name'), 'site_url' => $app->getParam('site_url'), 'site_email' => $app->getParam('site_email'), 'username' => $user['username'], 'first_name' => $user['first_name'], 'confirm_url' => sprintf('%s/confirm.php?c=%s', $app->getParam('site_url'), $confirm_code), 'account_url' => sprintf('%s/accounts.php?op=edit&account_id=%s', $app->getParam('site_url'), $user['account_id']), )); $app->logMsg(sprintf('Sending %s to %s for user_id %s account_id %s', $email_template, $user_email, $user_id, $user['account_id']), LOG_INFO, __FILE__, __LINE__); return $email->send(); } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function validateEmailConfirmation($code) { global $auth; $app =& App::getInstance(); // We can't trust this data until the signature is verified. list($user_id, $time) = explode('-', removeSignature($code)); $user_c = User::get(array('user_id' => $user_id)); if (!verifySignature($code) || false === $user_c) { // Confirmation code invalid. $app->raiseMsg(_("Sorry, you submitted an invalid confirmation code.") . sprintf(_(" Resend confirmation email or change your email address."), $cfg['resend_confirm_url'], $cfg['user_edit_url']), MSG_ERR, __FILE__, __LINE__); $app->logMsg(sprintf('Invalid confirmation code: %s', $code), LOG_NOTICE, __FILE__, __LINE__); $app->dieURL('/'); } if ($auth->get('user_id') != $user_id) { // The user_id doesn't match the one in the confirmation code. The user must have logged into the wrong account. $app->raiseMsg(sprintf(_("You are not logged into the account being confirmed. Please log in with username ā€˜%sā€™."), $user_c['username']), MSG_ERR, __FILE__, __LINE__); $app->logMsg(sprintf('User mismatch confirmation code (user_id %s != user_id %s)', $auth->get('user_id'), $user_id), LOG_NOTICE, __FILE__, __LINE__); $auth->clear(); $app->setBoomerangURL(absoluteMe(), 'login'); $app->dieURL(sprintf('/login.php?username=%s', $user_c['username'])); } if ((hexdec($time) * 86400) < time() - 604800) { // Confirmation code expired (7 days). $app->raiseMsg(_("That confirmation code has expired.") . sprintf(_(" Resend confirmation email or change your email address."), $cfg['resend_confirm_url'], $cfg['user_edit_url']), MSG_ERR, __FILE__, __LINE__); $app->logMsg(sprintf('Expired confirmation code %s (from %s)', $code, date('Y-m-d', hexdec($time) * 86400)), LOG_NOTICE, __FILE__, __LINE__); $app->dieURL('/'); } } /* * * * @access public * @param * @return * @author Quinn Comendant * @version 1.0 * @since 16 Nov 2014 17:45:40 */ static public function completeEmailConfirmation($user_id) { $app =& App::getInstance(); $db =& DB::getInstance(); $db->query(" UPDATE user_tbl SET status = 'email confirmed', modified_datetime = NOW(), modified_by_user_id = '" . $db->escapeString($user_id) . "' WHERE user_id = '" . $db->escapeString($user_id) . "' "); $app->raiseMsg(sprintf(_("Thank you! Your email address has been confirmed."), null), MSG_SUCCESS, __FILE__, __LINE__); $app->logMsg(sprintf('Success confirming email for user_id %s', $user_id), LOG_INFO, __FILE__, __LINE__); $app->dieURL('/'); } }