1106950-40 by ttkaminski and edb: Various enhancements and fixes for Drupal 7 port.
[project/drupalvb.git] / drupalvb.inc.php
1 <?php
2 /**
3 * @file
4 * Drupal vB CRUD functions.
5 */
6
7 /**
8 * Set the necessary cookies for the user to be logged into the forum.
9 *
10 * Frontend cookie names:
11 * - lastvisit, lastactivity, sessionhash
12 * Backend cookie names:
13 * - cpsession, userid, password
14 *
15 * However, in all cases the cookiedomain is NOT prefixed with a dot unless
16 * cookie domain has not been manually altered to either a suggested value or
17 * custom value in vB's settings.
18 */
19 function drupalvb_set_login_cookies($userid) {
20 // Load required vB user data.
21 $vbuser = drupalvb_db_query("SELECT userid, password, salt FROM {user} WHERE userid = :userid", array(":userid" => $userid))->fetchAssoc();
22 if (!$vbuser) {
23 return FALSE;
24 }
25
26 $vb_options = drupalvb_get_options();
27 $cookie_prefix = drupalvb_get_cookieprefix();
28 $cookie_path = $vb_options['cookiepath'];
29 $now = time();
30 $expire = $now + (@ini_get('session.cookie_lifetime') ? ini_get('session.cookie_lifetime') : 60 * 60 * 24 * 365);
31
32 $vb_cookie_domain = (!empty($vb_options['cookiedomain']) ? $vb_options['cookiedomain'] : $GLOBALS['cookie_domain']);
33 // Per RFC 2109, cookie domains must contain at least one dot other than the
34 // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
35 // @see conf_init()
36 if (!(count(explode('.', $vb_cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $vb_cookie_domain)))) {
37 $vb_cookie_domain = '';
38 }
39
40 // Clear out old session (if available).
41 if (!empty($_COOKIE[$cookie_prefix .'sessionhash'])) {
42 drupalvb_db_query("DELETE FROM {session} WHERE sessionhash = :hash", array(":hash" => $_COOKIE[$cookie_prefix .'sessionhash']));
43 }
44
45 // Setup user session.
46 $ip = implode('.', array_slice(explode('.', drupalvb_get_ip()), 0, 4 - $vb_options['ipcheck']));
47 $idhash = md5($_SERVER['HTTP_USER_AGENT'] . $ip);
48 $sessionhash = md5($now . request_uri() . $idhash . $_SERVER['REMOTE_ADDR'] . user_password(6));
49 $browserstring = substr(trim($_SERVER['HTTP_USER_AGENT']), 0, 100);
50
51 drupalvb_db_query("REPLACE INTO {session} (sessionhash, userid, host, idhash, lastactivity, location, useragent, loggedin) VALUES (:hash, :userid, :host, :idhash, :lastactivity, :location, :useragent, :loggedin)", array(":hash" => $sessionhash, ":userid" => $vbuser['userid'], ":host" => substr($_SERVER['REMOTE_ADDR'], 0, 15), ":idhash" => $idhash, ":lastactivity" => $now, ":location" => '/forum/', ":useragent" => $browserstring, ":loggedin" => 2));
52
53 // Setup cookies.
54 setcookie($cookie_prefix .'sessionhash', $sessionhash, $expire, $cookie_path, $vb_cookie_domain);
55 setcookie($cookie_prefix .'lastvisit', $now, $expire, $cookie_path, $vb_cookie_domain);
56 setcookie($cookie_prefix .'lastactivity', $now, $expire, $cookie_path, $vb_cookie_domain);
57 setcookie($cookie_prefix .'userid', $vbuser['userid'], $expire, $cookie_path, $vb_cookie_domain);
58 setcookie($cookie_prefix .'password', md5($vbuser['password'] . variable_get('drupalvb_license', '')), $expire, $cookie_path, $vb_cookie_domain);
59 return TRUE;
60 }
61
62 /**
63 * Clear all vB cookies for the current user.
64 *
65 * @see drupalvb_logout(), drupalvb_user_logout()
66 */
67 function drupalvb_clear_cookies($userid = NULL) {
68 $vb_options = drupalvb_get_options();
69
70 $cookie_prefix = drupalvb_get_cookieprefix();
71 $cookie_path = $vb_options['cookiepath'];
72 $expire = time() - 86400;
73
74 $vb_cookie_domain = (!empty($vb_options['cookiedomain']) ? $vb_options['cookiedomain'] : $GLOBALS['cookie_domain']);
75 // Per RFC 2109, cookie domains must contain at least one dot other than the
76 // first. For hosts such as 'localhost' or IP Addresses we don't set a cookie domain.
77 // @see conf_init()
78 if (!(count(explode('.', $vb_cookie_domain)) > 2 && !is_numeric(str_replace('.', '', $vb_cookie_domain)))) {
79 $vb_cookie_domain = '';
80 }
81
82 // @todo Without a vB user id, we cannot delete the session, so vBulletin
83 // will automatically authenticate again. We badly need a solution here,
84 // since this is the cause for broken session handling. Proposal: Use
85 // drupalvb_get_ip() to count the # of sessions; if there exactly one,
86 // kill it.
87 // @ttkaminski's suggestion - validate the sessionhash and userid from the
88 // cookies against the vBulletin session table. If it matches, then kill
89 // the session.
90
91 if (!empty($userid)) {
92 drupalvb_db_query("DELETE FROM {session} WHERE userid = :userid", array(':userid' => $userid));
93 drupalvb_db_query("UPDATE {user} SET lastvisit = :time WHERE userid = :userid", array(":time" => time(), ":userid" => $userid));
94 }
95
96 setcookie($cookie_prefix .'sessionhash', '', $expire, $cookie_path, $vb_cookie_domain);
97 setcookie($cookie_prefix .'lastvisit', '', $expire, $cookie_path, $vb_cookie_domain);
98 setcookie($cookie_prefix .'lastactivity', '', $expire, $cookie_path, $vb_cookie_domain);
99 setcookie($cookie_prefix .'userid', '', $expire, $cookie_path, $vb_cookie_domain);
100 setcookie($cookie_prefix .'password', '', $expire, $cookie_path, $vb_cookie_domain);
101 }
102
103 /**
104 * Determines the IP address of current user.
105 *
106 * @todo Duplicate of ip_address() in D6+ ?
107 */
108 function drupalvb_get_ip() {
109 $ip = $_SERVER['REMOTE_ADDR'];
110
111 if (isset($_SERVER['HTTP_CLIENT_IP'])) {
112 $ip = $_SERVER['HTTP_CLIENT_IP'];
113 }
114 elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']) && preg_match_all('#\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}#s', $_SERVER['HTTP_X_FORWARDED_FOR'], $matches)) {
115 // Make sure we don't pick up an internal IP defined by RFC1918.
116 foreach ($matches[0] as $match) {
117 if (!preg_match("#^(10|172\.16|192\.168)\.#", $match)) {
118 $ip = $match;
119 break;
120 }
121 }
122 }
123 elseif (isset($_SERVER['HTTP_FROM'])) {
124 $ip = $_SERVER['HTTP_FROM'];
125 }
126 return $ip;
127 }
128
129 /**
130 * Create a user in vBulletin.
131 *
132 * @param object $account
133 * A Drupal user account.
134 * @param array $edit
135 * Form values provided by hook_user().
136 */
137 function drupalvb_create_user($account, $edit) {
138 // Ensure we are not duplicating a user.
139 $userid = drupalvb_db_select("user","u")
140 ->fields("u", array("userid") )
141 ->condition("username", drupalvb_htmlspecialchars($edit['name']) )
142 ->execute()
143 ->fetchColumn(0);
144
145 if(!$userid) {
146 $salt = '';
147 for ($i = 0; $i < 3; $i++) {
148 $salt .= chr(rand(32, 126));
149 }
150 // Note: Password is already hashed during user export.
151 if (isset($edit['md5pass'])) {
152 $passhash = md5($edit['md5pass'] . $salt);
153 }
154 else {
155 $passhash = md5(md5($edit['pass']) . $salt);
156 }
157
158 $time = $account->created;
159 $passdate = date('Y-m-d', $time);
160 $joindate = $time;
161
162 // Attempt to grab the user title from the database.
163 $result = drupalvb_db_query("SELECT title FROM {usertitle} WHERE minposts = 0");
164 if ($resarray = $result->fetchAssoc()) {
165 $usertitle = $resarray['title'];
166 }
167 else {
168 $usertitle = 'Junior Member';
169 }
170
171 // Divide timezone by 3600, since vBulletin stores hours.
172 $timezone = variable_get('date_default_timezone', 0);
173 $timezone = ($timezone != 0 ? $timezone / 3600 : 0);
174
175 // Default new user options: I got these by setting up a new user how I
176 // wanted and looking in the database to see what options were set for him.
177 $options = variable_get('drupalvb_default_options', '3415');
178
179 // Default usergroup id.
180 $usergroupid = variable_get('drupalvb_default_usergroup', '2');
181 $lid = drupalvb_get_languageid();
182 if (!isset($lid)) {
183 $lid = 1;
184 }
185 // Insert user to vBulletin
186 $userid = drupalvb_db_insert("user")
187 ->fields( array(
188 "username" => drupalvb_htmlspecialchars($edit['name']),
189 "usergroupid" => $usergroupid,
190 "password" => $passhash,
191 "passworddate" => $passdate,
192 "usertitle" => $usertitle,
193 "email" => $account->mail,
194 "salt" => $salt,
195 "languageid" => 1,
196 "timezoneoffset" => $timezone,
197 "joindate" => $joindate,
198 "lastvisit" => time(),
199 "lastactivity" => time(),
200 "options" => $options
201 ))->execute();
202
203 $rr = drupalvb_db_query("SELECT * FROM {userfield} WHERE userid=1");
204 $fields = $rr->fetchAssoc();
205 foreach($fields as $f => $v) $fields[$f] = '';
206 $fields['userid'] = $userid;
207 drupalvb_db_insert("userfield")->fields($fields)->execute();
208
209 $rr = drupalvb_db_query("SELECT * FROM {usertextfield} WHERE userid=1");
210 $fields = $rr->fetchAssoc();
211 foreach($fields as $f => $v) $fields[$f] = '';
212 $fields['userid'] = $userid;
213 drupalvb_db_insert("usertextfield")->fields($fields)->execute();
214 }
215
216 // Insert new user into mapping table.
217 drupalvb_set_mapping($account->uid, $userid);
218
219 // Return userid of newly created account.
220 return $userid;
221 }
222
223 /**
224 * Update a user in vBulletin.
225 */
226 function drupalvb_update_user($account, $edit) {
227 $fields = $values = array();
228
229 foreach ($edit as $field => $value) {
230 if (empty($value)) {
231 continue;
232 }
233 switch ($field) {
234 case 'name':
235 $fields[] = "username = :name";
236 $values[':name'] = drupalvb_htmlspecialchars($value);
237 break;
238
239 case 'current_pass':
240 $fields[] = "password = :password";
241 $values[':password'] = md5(md5($value) . $edit['salt']);
242 $fields[] = "salt = :salt";
243 $values[':salt'] = $edit['salt'];
244 $fields[] = "passworddate = :date";
245 $values[':date'] = date('Y-m-d', time());
246 break;
247
248 case 'mail':
249 $fields[] = "email = :email";
250 $values[':email'] = $value;
251 break;
252
253 case 'language':
254 $fields[] = "languageid = :lid";
255 $values[':lid'] = 1;
256 break;
257 }
258 }
259 $fields[] = 'lastactivity = :activity';
260 $values[':activity'] = time();
261
262 // Ensure this user exists in the mapping table.
263 // When integrating an existing installation, the mapping may not yet exist.
264 if (isset($edit['userid'])) {
265 $userid = $edit['userid'];
266 }
267 else {
268 $userid = drupalvb_db_query("SELECT userid FROM {user} WHERE username = :name", array(':name' => drupalvb_htmlspecialchars($account->name)))->fetchField();
269 }
270 drupalvb_set_mapping($account->uid, $userid);
271
272 $values[':userid'] = $userid;
273 drupalvb_db_query("UPDATE {user} SET " . implode(', ', $fields) . " WHERE userid=:userid", $values);
274 }
275
276 /**
277 * Ensure that a mapping between two existing user accounts exists.
278 *
279 * @param $uid
280 * A Drupal user id.
281 * @param $userid
282 * A vBulletin user id.
283 */
284 function drupalvb_set_mapping($uid, $userid) {
285 db_query("INSERT IGNORE INTO {drupalvb_users} (uid, userid) VALUES (:uid, :userid)", array(':uid' => $uid, ':userid' => $userid));
286 }
287
288 /**
289 * Export all drupal users to vBulletin.
290 */
291 function drupalvb_export_drupal_users() {
292 module_load_include('inc', 'drupalvb');
293
294 $result = db_query("SELECT * FROM {users} WHERE uid>0 ORDER BY uid");
295 foreach ($result as $user) {
296 // Let create/update functions know that passwords are hashed already.
297 $user->md5pass = $user->pass;
298 if (!drupalvb_create_user($user, (array)$user)) {
299 // Username already exists, update email and password only.
300 // Case insensitive username is required to detect collisions.
301 $vbuser = drupalvb_db_query("SELECT salt,userid FROM {user} WHERE LOWER(username) = LOWER(:name)", array(':name' => drupalvb_htmlspecialchars($user->name)))->fetchAssoc();
302 drupalvb_update_user($user, array_merge((array)$user, $vbuser));
303 }
304 }
305 }
306
307 /**
308 * Get vBulletin configuration options.
309 */
310 function drupalvb_get_options() {
311 static $options = array();
312
313 if (empty($options)) {
314 $result = drupalvb_db_query("SELECT varname, value FROM {setting}");
315 foreach ($result as $var) {
316 $options[$var->varname] = $var->value;
317 }
318 }
319 return $options;
320 }
321
322 /**
323 * Get vBulletin configuration.
324 *
325 * @return array
326 * An associative array containing the vBulletin configuration, plus
327 * additional key:
328 * - version: The vBulletin version number string, as contained in the first
329 * PHP comment lines of config.php.
330 */
331 function drupalvb_get_config() {
332 static $config;
333
334 if (!isset($config)) {
335 $config = array();
336 $config['version'] = NULL;
337
338 // @todo Find & include vB's config automatically?
339 // $files = file_scan_directory('.', '^config.php$', $nomask = array('.', '..', 'CVS', '.svn'));
340 $config_file = './' . conf_path() . '/config.php';
341 if (!file_exists($config_file)) {
342 $config_file = drupal_get_path('module', 'drupalvb') . '/config.php';
343 }
344 if (file_exists($config_file)) {
345 require_once $config_file;
346
347 // Additionally parse the vBulletin version out of the php file header, as
348 // some integration functionality needs to account for API changes in
349 // later vBulletin versions.
350 $file = fopen($config_file, 'r');
351 $max_lines = 10;
352 while ($max_lines && $line = fgets($file, 30)) {
353 if (preg_match('@vBulletin\s+([0-9a-zA-Z\.-]+)@', $line, $version)) {
354 $config['version'] = $version[1];
355 }
356 $max_lines--;
357 }
358 fclose($file);
359 }
360 }
361 return $config;
362 }
363
364 /**
365 * Get vB user roles.
366 */
367 function drupalvb_get_roles() {
368 $result = drupalvb_db_query("SELECT usergroupid, title FROM {usergroup}");
369
370 $roles = array();
371 foreach ($result as $data) {
372 $roles[$data->usergroupid] = $data->title;
373 }
374 if (!$roles) {
375 $roles[] = t('No user roles could be found.');
376 }
377 return $roles;
378 }
379
380 /**
381 * Get vB language id by given ISO language code.
382 */
383 function drupalvb_get_languageid($language = NULL) {
384 //static $vblanguages
385
386 if (!isset($vblanguages)) {
387 $vblanguages = array();
388 $result = drupalvb_db_query("SELECT languageid, title, languagecode FROM {language}");
389 foreach ($result as $lang) {
390 $vblanguages[$lang->languagecode] = $lang->languageid;
391 }
392 }
393 $options = drupalvb_get_options();
394 return 1;
395 //return (!empty($language) && isset($vblanguages[$language]) ? $vblanguages[$language] : $vblanguages[$options['languageid']]);
396 }
397
398 /**
399 * Get counts of guests and members currently online.
400 */
401 function drupalvb_get_users_online() {
402 $vb_options = drupalvb_get_options();
403
404 $datecut = time() - $vb_options['cookietimeout'];
405 $numbervisible = 0;
406 $numberregistered = 0;
407 $numberguest = 0;
408
409 $result = drupalvb_db_query("SELECT user.username, user.usergroupid, session.userid, session.lastactivity FROM {session} AS session LEFT JOIN {user} AS user ON (user.userid = session.userid) WHERE session.lastactivity > :datecut", array(':datecut' => $datecut));
410
411 $userinfos = array();
412
413 while ($loggedin = $result->fetchAssoc()) {
414 $userid = $loggedin['userid'];
415 if (!$userid) {
416 $numberguest++;
417 }
418 elseif (empty($userinfos[$userid]) || ($userinfos[$userid]['lastactivity'] < $loggedin['lastactivity'])) {
419 $userinfos[$userid] = $loggedin;
420 }
421 }
422 foreach ($userinfos as $userid => $loggedin) {
423 $numberregistered++;
424 }
425 return array('guests' => $numberguest, 'members' => $numberregistered);
426 }
427
428 /**
429 * Get counts of new or recent posts for the current user.
430 */
431 function drupalvb_get_recent_posts($scope = 'last') {
432 global $user;
433
434 // Queries the vB user database to find a matching set of user data.
435 $result = drupalvb_db_query("SELECT userid, username, lastvisit FROM {user} WHERE username = :name", array(':name' => drupalvb_htmlspecialchars($user->name)));
436
437 // Make sure a user is logged in to get their last visit and appropriate post
438 // count.
439 if ($vb_user = $result->fetchAssoc()) {
440 if ($scope == 'last') {
441 $datecut = $vb_user['lastvisit'];
442 }
443 elseif ($scope == 'daily') {
444 $datecut = time() - 86400;
445 }
446 $posts = drupalvb_db_query("SELECT COUNT(postid) FROM {post} WHERE dateline > :datecut", array(':datecut' => $datecut))->fetchField();
447 }
448 else {
449 $posts = 0;
450 }
451 return $posts;
452 }
453
454 function drupalvb_htmlspecialchars($text) {
455 $text = preg_replace('/&(?!#[0-9]+|shy;)/si', '&amp;', $text);
456 return str_replace(array('<', '>', '"'), array('&lt;', '&gt;', '&quot;'), $text);
457 }
458
459 /**
460 * Get vB cookie prefix.
461 */
462 function drupalvb_get_cookieprefix() {
463 $vb_config = drupalvb_get_config();
464 $cookie_prefix = (isset($vb_config['Misc']['cookieprefix']) ? $vb_config['Misc']['cookieprefix'] : 'bb');
465
466 // Version 4 began using an underscore following the prefix.
467 if (version_compare($vb_config['version'], 4, '>=')) {
468 $cookie_prefix .= '_';
469 }
470
471 return $cookie_prefix;
472 }
473