diff -r 1e4b759da336 -r acb7e23b6ffa includes/sessions.php --- a/includes/sessions.php Sat Apr 26 17:25:28 2008 -0400 +++ b/includes/sessions.php Sun May 04 21:57:48 2008 -0400 @@ -148,6 +148,13 @@ */ var $valid_username = '([^<>&\?\'"%\n\r\t\a\/]+)'; + + /** + * The current user's user title. Defaults to NULL. + * @var string + */ + + var $user_title = null; /** * What we're allowed to do as far as permissions go. This changes based on the value of the "auth" URI param. @@ -247,6 +254,19 @@ var $group_mod = Array(); + /** + * A constant array of user-level-to-rank default associations. + * @var array + */ + + var $level_rank_table = array( + USER_LEVEL_ADMIN => RANK_ID_ADMIN, + USER_LEVEL_MOD => RANK_ID_MOD, + USER_LEVEL_MEMBER => RANK_ID_MEMBER, + USER_LEVEL_CHPREF => RANK_ID_MEMBER, + USER_LEVEL_GUEST => RANK_ID_GUEST + ); + # Basic functions /** @@ -468,6 +488,7 @@ $this->real_name = $userdata['real_name']; $this->email = $userdata['email']; $this->unread_pms = $userdata['num_pms']; + $this->user_title = $userdata['user_title']; if(!$this->compat) { $this->theme = $userdata['theme']; @@ -1232,7 +1253,7 @@ $salt = $db->escape($keydata[3]); // using a normal call to $db->sql_query to avoid failing on errors here $query = $db->sql_query('SELECT u.user_id AS uid,u.username,u.password,u.email,u.real_name,u.user_level,u.theme,u.style,u.signature,' . "\n" - . ' u.reg_time,u.account_active,u.activation_key,u.user_lang,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,' . "\n" + . ' u.reg_time,u.account_active,u.activation_key,u.user_lang,u.user_title,k.source_ip,k.time,k.auth_level,COUNT(p.message_id) AS num_pms,' . "\n" . ' u.user_timezone, x.* FROM '.table_prefix.'session_keys AS k' . "\n" . ' LEFT JOIN '.table_prefix.'users AS u' . "\n" . ' ON ( u.user_id=k.user_id )' . "\n" @@ -2355,6 +2376,249 @@ } # + # USER RANKS + # + + /** + * SYNOPSIS OF THE RANK SYSTEM + * Enano's rank logic calculates a user's rank based on a precedence scale. The way things are checked is: + * 1. Check to see if the user has a specific rank assigned. Use that if possible. + * 2. Check the user's primary group to see if it specifies a rank. Use that if possible. + * 3. Check the other groups a user is in. If one that has a custom rank is encountered, use that rank. + * 4. See if the user's user level has a specific rank hard-coded to be associated with it. (Always overrideable as can be seen above) + * 5. Use the "member" rank + */ + + /** + * Generates a textual SQL query for fetching rank data to be sent to calculate_user_rank(). + * @param string Text to append, possibly a WHERE clause or so + * @return string + */ + + function generate_rank_sql($append = '') + { + // Generate level-to-rank associations + $assoc = array(); + foreach ( $this->level_rank_table as $level => $rank ) + { + $assoc[] = " ( u.user_level = $level AND rl.rank_id = $rank )"; + } + $assoc = implode(" OR\n", $assoc) . "\n"; + + $gid_col = ( ENANO_DBLAYER == 'PGSQL' ) ? + 'array_to_string(array_accum(m.group_id), \',\') AS group_list' : + 'GROUP_CONCAT(m.group_id) AS group_list'; + + // The actual query + $sql = "SELECT u.user_id, u.username, u.user_level, u.user_group, u.user_rank, u.user_title, g.group_rank,\n" + . " COALESCE(ru.rank_id, rg.rank_id, rl.rank_id, rd.rank_id ) AS rank_id,\n" + . " COALESCE(ru.rank_title, rg.rank_title, rl.rank_title, rd.rank_title) AS rank_title,\n" + . " COALESCE(ru.rank_style, rg.rank_style, rl.rank_style, rd.rank_style) AS rank_style,\n" + . " ( ru.rank_id IS NULL AND rg.rank_id IS NULL ) AS using_default," + . " ( ru.rank_id IS NULL AND rg.rank_id IS NOT NULL ) AS using_group," + . " $gid_col\n" + . " FROM " . table_prefix . "users AS u\n" + . " LEFT JOIN groups AS g\n" + . " ON ( g.group_id = u.user_group )\n" + . " LEFT JOIN " . table_prefix . "group_members AS m\n" + . " ON ( u.user_id = m.user_id )\n" + . " LEFT JOIN ranks AS ru\n" + . " ON ( u.user_rank = ru.rank_id )\n" + . " LEFT JOIN ranks AS rg\n" + . " ON ( g.group_rank = rg.rank_id )\n" + . " LEFT JOIN ranks AS rl\n" + . " ON (\n" + . $assoc + . " )\n" + . " LEFT JOIN ranks AS rd\n" + . " ON ( rd.rank_id = 1 )\n" + . " GROUP BY u.user_id, u.username, u.user_level, u.user_group, u.user_rank, u.user_title, g.group_rank,\n" + . " ru.rank_id, ru.rank_title, ru.rank_style,rg.rank_id, rg.rank_title, rg.rank_style,\n" + . " rl.rank_id, rl.rank_title, rl.rank_style,rd.rank_id, rd.rank_title, rd.rank_style$append;"; + + return $sql; + } + + /** + * Returns an associative array with a user's rank information. + * The array will contain the following values: + * username: string The user's username + * user_id: integer Numerical user ID + * rank_id: integer Numerical rank ID + * rank: string The user's current rank + * title: string The user's custom user title if applicable; should be displayed one line below the rank + * style: string CSS for the username + * @param int|string Username *or* user ID + * @return array or false on failure + */ + + function get_user_rank($id) + { + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + global $user_ranks; + // cache info if possible + static $_cache = array(); + + if ( is_int($id) ) + $col = "user_id = $id"; + else if ( is_string($id) ) + $col = ENANO_SQLFUNC_LOWERCASE . "(username) = " . ENANO_SQLFUNC_LOWERCASE . "('" . $db->escape($id) . "')"; + else + // invalid parameter + return false; + + // check the cache + if ( isset($_cache[$id]) ) + return $_cache[$id]; + + // check the disk cache + if ( is_int($id) ) + { + if ( isset($user_ranks[$id]) ) + { + $_cache[$id] =& $user_ranks[$id]; + return $user_ranks[$id]; + } + } + else if ( is_string($id) ) + { + foreach ( $user_ranks as $key => $valarray ) + { + if ( is_string($key) && strtolower($key) == strtolower($id) ) + { + $_cache[$id] = $valarray; + return $valarray; + } + } + } + + $sql = $this->generate_rank_sql("\n WHERE $col"); + + $q = $this->sql($sql); + // any results? + if ( $db->numrows() < 1 ) + { + // nuttin'. + $db->free_result(); + $_cache[$id] = false; + return false; + } + + // Found something. + $row = $db->fetchrow(); + $db->free_result(); + + $row = $this->calculate_user_rank($row); + + $_cache[$id] = $row; + return $row; + } + + /** + * Performs the actual rank calculation based on the contents of a row. + * @param array + * @return array + */ + + function calculate_user_rank($row) + { + global $db, $session, $paths, $template, $plugins; // Common objects + global $lang; + + static $rank_cache = array(); + static $group_ranks = array(); + + // try to cache that rank info + if ( !isset($rank_cache[ intval($row['rank_id']) ]) && $row['rank_id'] ) + { + $rank_cache[ intval($row['rank_id']) ] = array( + 'rank_id' => intval($row['rank_id']), + 'rank_title' => intval($row['rank_title']), + 'rank_style' => intval($row['rank_style']) + ); + } + // cache group info (if appropriate) + if ( $row['using_group'] && !isset($group_ranks[ intval($row['user_group']) ]) ) + { + $group_ranks[ intval($row['user_group']) ] = intval($row['group_rank_id']); + } + + // sanitize and process the as-of-yet rank data + $row['rank_id'] = intval($row["rank_id"]); + $row['rank_title'] = $row["rank_title"]; + + // if we're falling back to some default, then see if we can use one of the user's other groups + if ( $row['using_default'] && !empty($row['group_list']) ) + { + $group_list = explode(',', $row['group_list']); + if ( array_walk($group_list, 'intval') ) + { + // go through the group list and see if any of them has a rank assigned + foreach ( $group_list as $group_id ) + { + // cached in RAM? Preferably use that. + if ( !isset($group_ranks[$group_id]) ) + { + // Not cached - grab it + $q = $this->sql('SELECT group_rank FROM ' . table_prefix . "groups WHERE group_id = $group_id;"); + if ( $db->numrows() < 1 ) + { + $db->free_result(); + continue; + } + list($result) = $db->fetchrow_num(); + $db->free_result(); + + if ( $result === null || $result < 1 ) + { + $group_ranks[$group_id] = false; + } + else + { + $group_ranks[$group_id] = intval($result); + } + } + // we've got it now + if ( $group_ranks[$group_id] ) + { + // found a group with a rank assigned + // so get the rank info + $rank_id =& $group_ranks[$group_id]; + if ( !isset($rank_cache[$rank_id]) ) + { + $q = $this->sql('SELECT rank_id, rank_title, rank_style FROM ' . table_prefix . "ranks WHERE rank_id = $rank_id;"); + if ( $db->numrows() < 1 ) + { + $db->free_result(); + continue; + } + $rank_cache[$rank_id] = $db->fetchrow(); + $db->free_result(); + } + // set the final rank parameters + // die("found member-of-group exception with uid {$row['user_id']} gid $group_id rid $rank_id rt {$rank_cache[$rank_id]['rank_title']}"); + $row['rank_id'] = $rank_id; + $row['rank_title'] = $rank_cache[$rank_id]['rank_title']; + $row['rank_style'] = $rank_cache[$rank_id]['rank_style']; + break; + } + } + } + } + + if ( $row['user_title'] === NULL ) + $row['user_title'] = false; + + $row['user_id'] = intval($row['user_id']); + $row['user_level'] = intval($row['user_level']); + $row['user_group'] = intval($row['user_group']); + + unset($row['user_rank'], $row['group_rank'], $row['group_list'], $row['using_default'], $row['using_group'], $row['user_level'], $row['user_group'], $row['username']); + return $row; + } + + # # Access Control Lists # @@ -3656,4 +3920,10 @@ // Once a week register_cron_task('cron_clean_old_admin_keys', 168); +/** + * Cron task - regenerate cached user rank information + */ + +register_cron_task('generate_ranks_cache', 0.25); + ?>