MyBB Community Forums

Full Version: User Cache
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I see that in functions_user.php and get_user() in functions.php, mybb makes a database call each time info for a specific user is called. And very often, the same thing is requested over 2 or 3 times, such as during login/registration. What I did was outsource the retrievals to a function get_user_info() in functions.php:

Code:
// $row = row or uid & updates user cache
function get_user_info($col, $value)
{
    global $db, $_core;
    
    $value = $db->escape_string($value);

    // no cache--get it from db
    if(!$db->cache['users'][$col][$value])
    {
        if($col == 'uid' and $value == $_core->user['uid'])
        {
            $row = $_core->user;
        }
        else
        {
            $query = $db->query("SELECT * FROM ".TABLE_PREFIX."users WHERE $col='$value' LIMIT 1");
    
            if(!$row = $db->fetch_array($query))
            {
                // not in DB
                return false;
            }
        }
        
        $db->cache['users']['uid'][$row['uid']] = $row;
        $db->cache['users']['username'][$row['username']] = &$db->cache['users']['uid'][$row['uid']];
        $db->cache['users']['email'][$row['email']] = &$db->cache['users']['uid'][$row['uid']];
    }

    return $db->cache['users'][$col][$value];
}

And call that everywhere else. For example, my get_user() is now
Code:
/**
* Get the username of a user id.
*
* @param int The user id of the user.
* @return string The username of the user.
*/
function get_user($uid)
{
    return get_user_info('uid', $uid);
}
and my functions_user.php is now
Code:
<?php

/**
* Checks if a user with uid $uid exists in the database.
*
* @param int The uid to check for.
* @return boolean True when exists, false when not.
*/
function user_exists($uid)
{
    return valid_response(get_user(intval($uid)));
}

function valid_response($input)
{
    return ($input !== false);
}

/**
* Checks if $username already exists in the database.
*
* @param string The username for check for.
* @return boolean True when exists, false when not.
*/
function username_exists($username)
{
    return valid_response(get_user_info("username", $username));
}

/**
* Checks a password with a supplied email.
*
* @param string The username of the user.
* @param string The md5()'ed password.
* @return boolean|array False when no match, array with user info when match.
*/
function validate_password_from_email($email, $password)
{
    global $db;
    
    $user = get_user_info("email", $db->escape_string($email));

    if(!$user['uid'])
    {
        return false;
    }
    else
    {
        return validate_password_from_uid($user['uid'], $password);
    }
}

/**
* Checks a password with a supplied uid.
*
* @param int The user id.
* @param string The md5()'ed password.
* @param string An optional user data array.
* @return boolean|array False when not valid, user data array when valid.
*/
function validate_password_from_uid($uid, $password)
{
    global $db, $_core;
    $user = get_user($uid);

    if(!$user['salt'])
    {
        // Generate a salt for this user and assume the password stored in db is a plain md5 password
        $user['salt'] = generate_salt();
        $user['password'] = salt_password($user['password'], $user['salt']);
        $sql_array = array(
            "salt" => $user['salt'],
            "password" => $user['password']
        );
        $db->update_query(TABLE_PREFIX."users", $sql_array, "uid = ".$user['uid'], 1);
    }

    if(!$user['loginkey'])
    {
        $user['loginkey'] = generate_loginkey();
        $sql_array = array(
            "loginkey" => $user['loginkey']
        );
        $db->update_query(TABLE_PREFIX."users", $sql_array, "uid = ".$user['uid'], 1);
    }
    if(salt_password(md5($password), $user['salt']) == $user['password'])
    {
        return $user;
    }
    else
    {
        return false;
    }
}

/**
* Updates a user's password.
*
* @param int The user's id.
* @param string The md5()'ed password.
* @param string (Optional) The salt of the user.
* @return array The new password.
*/
function update_password($uid, $password, $salt="")
{
    global $db, $plugins;

    $newpassword = array();

    //
    // If no salt was specified, check in database first, if still doesn't exist, create one
    //
    if(!$salt)
    {
        $user = get_user($uid);

        if($user['salt'])
        {
            $salt = $user['salt'];
        }
        else
        {
            $salt = generate_salt();
        }
        $newpassword['salt'] = $salt;
    }

    //
    // Create new password based on salt
    //
    $saltedpw = salt_password($password, $salt);

    //
    // Generate new login key
    //
    $loginkey = generate_loginkey();

    //
    // Update password and login key in database
    //
    $newpassword['password'] = $saltedpw;
    $newpassword['loginkey'] = $loginkey;
    $db->update_query(TABLE_PREFIX."users", $newpassword, "uid='$uid'", 1);

    $plugins->run_hooks("password_changed");

    return $newpassword;
}

/**
* Salts a password based on a supplied salt.
*
* @param string The md5()'ed password.
* @param string The salt.
* @return string The password hash.
*/
function salt_password($password, $salt)
{
    return md5(md5($salt).$password);
}

/**
* Generates a random salt
*
* @return string The salt.
*/
function generate_salt()
{
    return random_str(8);
}

/**
* Generates a 50 character random login key.
*
* @return string The login key.
*/
function generate_loginkey()
{
    return random_str(50);
}

/**
* Updates a user's salt in the database (does not update a password).
*
* @param int The uid of the user to update.
* @return string The new salt.
*/
function update_salt($uid)
{
    global $db;
    $salt = generate_salt();
    $sql_array = array(
        "salt" => $salt
    );
    $db->cache['users']['uid'][$uid]['salt'] = $salt;
    $db->update_query(TABLE_PREFIX."users", $sql_array, "uid = ".$uid, 1);
    return $salt;
}

/**
* Generates a new login key for a user.
*
* @param int The uid of the user to update.
* @return string The new login key.
*/
function update_loginkey($uid)
{
    global $db;
    $loginkey = generate_loginkey();
    $sql_array = array(
        "loginkey" => $loginkey
    );
    $db->cache['users']['uid'][$uid]['loginkey'] = $loginkey;
    $db->update_query(TABLE_PREFIX."users", $sql_array, "uid = ".$uid, 1);
    return $loginkey;

}

/**
* Adds a thread to a user's favorite thread list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The tid of the thread to add to the list.
* @param int (Optional) The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function add_favorite_thread($tid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $query = $db->query("SELECT * FROM ".TABLE_PREFIX."favorites WHERE tid='".intval($tid)."' AND type='f' AND uid='".intval($uid)."' LIMIT 1");
    $favorite = $db->fetch_array($query);
    if(!$favorite['tid'])
    {
        $db->query("INSERT INTO ".TABLE_PREFIX."favorites (uid,tid,type) VALUES ('".intval($uid)."','".intval($tid)."','f')");
    }
    return true;
}

/**
* Removes a thread from a user's favorite thread list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The tid of the thread to remove from the list.
* @param int (Optional)The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function remove_favorite_thread($tid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $db->query("DELETE FROM ".TABLE_PREFIX."favorites WHERE tid='".intval($tid)."' AND type='f' AND uid='".intval($uid)."'");
    return true;
}

/**
* Adds a thread to a user's thread subscription list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The tid of the thread to add to the list.
* @param int (Optional) The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function add_subscribed_thread($tid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $query = $db->query("SELECT * FROM ".TABLE_PREFIX."favorites WHERE tid='".intval($tid)."' AND type='s' AND uid='".intval($uid)."' LIMIT 1");
    $favorite = $db->fetch_array($query);
    if(!$favorite['tid'])
    {
        $db->query("INSERT INTO ".TABLE_PREFIX."favorites (uid,tid,type) VALUES ('".intval($uid)."','".intval($tid)."','s')");
    }
    return true;
}

/**
* Remove a thread from a user's thread subscription list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The tid of the thread to remove from the list.
* @param int (Optional) The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function remove_subscribed_thread($tid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $db->query("DELETE FROM ".TABLE_PREFIX."favorites WHERE tid='".$tid."' AND type='s' AND uid='".$uid."'");
    return true;
}

/**
* Adds a forum to a user's forum subscription list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The fid of the forum to add to the list.
* @param int (Optional) The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function add_subscribed_forum($fid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $query = $db->query("SELECT * FROM ".TABLE_PREFIX."forumsubscriptions WHERE fid='".$fid."' AND uid='".$uid."' LIMIT 1");
    $fsubscription = $db->fetch_array($query);
    if(!$fsubscription['fid'])
    {
        $db->query("INSERT INTO ".TABLE_PREFIX."forumsubscriptions (fid,uid) VALUES ('".$fid."','".$uid."')");
    }
    return true;
}

/**
* Removes a forum from a user's forum subscription list.
* If no uid is supplied, the currently logged in user's id will be used.
*
* @param int The fid of the forum to remove from the list.
* @param int (Optional) The uid of the user who's list to update.
* @return boolean True when success, false when otherwise.
*/
function remove_subscribed_forum($fid, $uid="")
{
    global $_core, $db;
    if(!$uid)
    {
        $uid = $_core->user['uid'];
    }
    if(!$uid)
    {
        return;
    }
    $db->query("DELETE FROM ".TABLE_PREFIX."forumsubscriptions WHERE fid='".$fid."' AND uid='".$uid."'");
    return true;
}

/**
* Constructs the usercp navigation menu.
*
*/
function usercp_menu()
{
    global $_core, $templates, $theme, $plugins, $lang, $usercpnav, $usercpmenu;

    $lang->load("usercpnav");

    // Add the default items as plugins with separated priorities of 10
    if($_core->settings['enablepms'] != "no")
    {
        $plugins->add_hook("usercp_menu", "usercp_menu_messenger", 10);
    }
    $plugins->add_hook("usercp_menu", "usercp_menu_profile", 20);
    $plugins->add_hook("usercp_menu", "usercp_menu_misc", 30);

    //
    // Run the plugin hooks
    //
    $plugins->run_hooks("usercp_menu");
    global $usercpmenu;

    eval("\$usercpnav = \"".$templates->get("usercp_nav")."\";");

    $plugins->run_hooks("usercp_menu_built");
}

/**
* Constructs the usercp messenger menu.
*
*/
function usercp_menu_messenger()
{
    global $db, $_core, $templates, $theme, $usercpmenu, $lang;

    $foldersexploded = explode("$%%$", $_core->user['pmfolders']);
    foreach($foldersexploded as $key => $folders)
    {
        $folderinfo = explode("**", $folders, 2);
        $folderinfo[1] = get_pm_folder_name($folderinfo[0], $folderinfo[1]);
        $folderlinks .= "<li class=\"pmfolders\"><a href=\"private.php?fid=$folderinfo[0]\">$folderinfo[1]</a></li>\n";
    }
    eval("\$usercpmenu .= \"".$templates->get("usercp_nav_messenger")."\";");
}

/**
* Constructs the usercp profile menu.
*
*/
function usercp_menu_profile()
{
    global $db, $_core, $templates, $theme, $usercpmenu, $lang;

    if($_core->usergroup['canchangename'] != "no")
    {
        eval("\$changenameop = \"".$templates->get("usercp_nav_changename")."\";");
    }
    eval("\$usercpmenu .= \"".$templates->get("usercp_nav_profile")."\";");
}

/**
* Constructs the usercp misc menu.
*
*/
function usercp_menu_misc()
{
    global $db, $_core, $templates, $theme, $usercpmenu, $lang;

    $query = $db->query("SELECT COUNT(*) AS draftcount FROM ".TABLE_PREFIX."posts WHERE visible='-2' AND uid='".$_core->user['uid']."'");
    $count = $db->fetch_array($query);
    $draftcount = "(".my_number_format($count['draftcount']).")";
    if($count['draftcount'] > 0)
    {
        $draftstart = "<strong>";
        $draftend = "</strong>";
    }
    eval("\$usercpmenu .= \"".$templates->get("usercp_nav_misc")."\";");
}

/**
* Gets the usertitle for a specific uid.
*
* @param int The uid of the user to get the usertitle of.
* @return string The usertitle of the user.
*/
function get_usertitle($uid="")
{
    global $db, $_core;
    $user = get_user($uid);

    if($user['usertitle'])
    {
        return $user['usertitle'];
    }
    else
    {
        $query = $db->query("SELECT title FROM ".TABLE_PREFIX."usertitles WHERE posts<='".$user['postnum']."' ORDER BY posts DESC");
        $usertitle = $db->fetch_array($query);
        return $usertitle['title'];
    }
}

/**
* Updates a users private message count in the users table with the number of pms they have.
*
* @param int The user id to update the count for. If none, assumes currently logged in user.
* @param int Bitwise value for what to update. 1 = total, 2 = new, 4 = unread. Combinations accepted.
* @param int The unix timestamp the user with uid last visited. If not specified, will be queried.
*/
function update_pm_count($uid=0, $count_to_update=7, $lastvisit=0)
{
    global $db, $_core;
    static $pm_lastvisit_cache;
    
    $uid = intval($uid);
    
    // If no user id, assume that we mean the current logged in user.
    if($uid == 0)
    {
        $uid = $_core->user['uid'];
    }

    // If using logged in user, use the last visit
    if($uid == $_core->user['uid'])
    {
        $lastvisit = $_core->user['lastvisit'];
    }
    // Else, if no last visit is specified, query for it.
    elseif(intval($lastvisit) < 1)
    {
        if(!$pm_lastvisit_cache[$uid])
        {
            $query = $db->query("SELECT lastvisit FROM ".TABLE_PREFIX."users WHERE uid='".$uid."'");
            $user = $db->fetch_array($query);
            $pm_lastvisit_cache[$uid] = $user['lastvisit'];
        }
        $lastvisit = $pm_lastvisit_cache[$uid];
    }
    // Update total number of messages.
    if($count_to_update & 1)
    {
        $query = $db->query("SELECT COUNT(pmid) AS pms_total FROM ".TABLE_PREFIX."privatemessages WHERE uid='".$uid."'");
        $total = $db->fetch_array($query);
        $pmcount['totalpms'] = $total['pms_total'];
    }
    // Update number of new messages.
    if($count_to_update & 2)
    {
        $query = $db->query("SELECT COUNT(pmid) AS pms_new FROM ".TABLE_PREFIX."privatemessages WHERE uid='".$uid."' AND dateline>'".$lastvisit."' AND folder=1");
        $new = $db->fetch_array($query);
        $pmcount['newpms'] = $new['pms_new'];
    }
    // Update number of unread messages.
    if($count_to_update & 4)
    {
        $query = $db->query("SELECT COUNT(pmid) AS pms_unread FROM ".TABLE_PREFIX."privatemessages WHERE uid='".$uid."' AND status=0 AND folder='1'");
        $unread = $db->fetch_array($query);
        $pmcount['unreadpms'] = $unread['pms_unread'];
    }
    if(is_array($pmcount))
    {
        $db->update_query(TABLE_PREFIX."users", $pmcount, "uid='".$uid."'");
    }
    return $pmcount;
}

/**
* Return the language specific name for a PM folder.
*
* @param int The ID of the folder.
* @param string The folder name - can be blank, will use language default.
* @return string The name of the folder.
*/
function get_pm_folder_name($fid, $name="")
{
    global $lang;

    if($name != '')
    {
        return $name;
    }

    switch($fid)
    {
        case 1;
            return $lang->folder_inbox;
            break;
        case 2:
            return $lang->folder_sent_items;
            break;
        case 3:
            return $lang->folder_drafts;
            break;
        case 4:
            return $lang->folder_trash;
            break;
        default:
            return $lang->folder_untitled;
    }
}
?>

Wouldn't it be more efficient to do a DB call just once, and store the data into a DB cache array?
function get_user($uid) already uses an internal cache
I know that, but that's the only instance that uses an internal cache. If you make that global on the sql driver level, then you can save a lot of queries.
Reference URL's