1<?php 2 3/** 4 * Simple Machines Forum(SMF) API for SMF 2.0 5 * 6 * Use this to integrate your SMF version 2.0 forum with 3rd party software 7 * If you need help using this script or integrating your forum with other 8 * software, feel free to contact andre@r2bconcepts.com 9 * 10 * @package SMF 2.0 API 11 * @author Simple Machines http://www.simplemachines.org 12 * @author Andre Nickatina <andre@r2bconcepts.com> 13 * @copyright 2011 Simple Machines 14 * @link http://www.simplemachines.org Simple Machines 15 * @link http://www.r2bconcepts.com Red2Black Concepts 16 * @license http://www.simplemachines.org/about/smf/license.php BSD 17 * @version 0.1.2 18 * 19 * NOTICE OF LICENSE 20 *********************************************************************************** 21 * This file, and ONLY this file is released under the terms of the BSD License. * 22 * * 23 * Redistribution and use in source and binary forms, with or without * 24 * modification, are permitted provided that the following conditions are met: * 25 * * 26 * Redistributions of source code must retain the above copyright notice, this * 27 * list of conditions and the following disclaimer. * 28 * Redistributions in binary form must reproduce the above copyright notice, this * 29 * list of conditions and the following disclaimer in the documentation and/or * 30 * other materials provided with the distribution. * 31 * Neither the name of Simple Machines LLC nor the names of its contributors may * 32 * be used to endorse or promote products derived from this software without * 33 * specific prior written permission. * 34 * * 35 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * 36 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * 37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * 38 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * 39 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * 40 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * 41 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * 42 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * 43 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * 44 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * 45 **********************************************************************************/ 46 47/* 48 This file includes functions that may help integration with other scripts 49 and programs, such as portals. It is independent of SMF, and meant to run 50 without disturbing your script. It defines several functions, most of 51 which start with the smfapi_ prefix. These are: 52 53 54 array smfapi_getUserByEmail(string $email) 55 - returns all user info from the db in an array 56 57 array smfapi_getUserById(int $id) 58 - returns all user info from the db in an array 59 60 array smfapi_getUserByUsername(string $username) 61 - returns all user info from the db in an array 62 63 array smfapi_getUserData(mixed $identifier) 64 - returns all user info from the db in an array 65 - will accept email address, username or member id 66 67 bool smfapi_login(mixed $identifier, int $cookieLength) 68 - sets cookie and session for user specified 69 - will accept email address, username or member id 70 - does no authentication; do that before calling this 71 72 bool smfapi_authenticate(mixed $username, string $password, bool $encrypted) 73 - authenticates a username/password combo 74 - will accept email address, username or member id 75 76 bool smfapi_logout(string $username) 77 - logs the specified user out 78 - will accept email address, username or member id 79 80 bool smfapi_deleteMembers(int || int array $users) 81 - deletes member(s) by their int member id 82 - will return true unless $users empty 83 - will accept email address, username or member id or a mixed array 84 85 int smfapi_registerMember(array $regOptions) 86 - register a member 87 - $regOptions will contain the variables from the db 88 - dump out the results of smfapi_getUserData($user) to see them all 89 - required variables are: 'member_name' (unique), 'email' (unique), 'password' 90 91 bool smfapi_logError(string $error_message, string $error_type, string $file, int $line) 92 - logs an error message to the smf error log 93 - $error_type will be one of the following: 'general', 'critical', 'database', 'undefined_vars', 'user', 'template' or 'debug' 94 - just use __FILE__ and __LINE__ as $file and $line unless you have other ambitions 95 96 true smfapi_reloadSettings() 97 - loads the $modSettings array 98 - adds the following functions to the $smcFunc array: 99 'entity_fix', 'htmlspecialchars', 'htmltrim', 'strlen', 'strpos', 'substr', 'strtolower', strtoupper', 'truncate', 'ucfirst' and 'ucwords' 100 101 true smfapi_loadUserSettings(mixed $identifier) 102 - loads the $user_info array for user or guest 103 - will accept email address, username or member id 104 - if member data not found, will try cookie then session 105 106 true smfapi_loadSession() 107 - starts the session 108 109 *Session functions* 110 true smfapi_sessionOpen() 111 true smfapi_sessionClose() 112 bool smfapi_sessionRead() 113 bool smfapi_sessionWrite() 114 bool smfapi_sessionDestroy() 115 mixed smfapi_sessionGC() 116 117 bool smfapi_loadDatabase() 118 - loads the db connection 119 - adds the following fuctions to the $smcFunc array: 120 'db_query', 'db_quote', 'db_fetch_assoc', 'db_fetch_row', 'db_free_result', 'db_insert', 'db_insert_id', 'db_num_rows', 121 'db_data_seek', 'db_num_fields', 'db_escape_string', 'db_unescape_string', 'db_server_info', 'db_affected_rows', 122 'db_transaction', 'db_error', 'db_select_db', 'db_title', 'db_sybase', 'db_case_sensitive' and 'db_escape_wildcard_string' 123 124 void smfapi_cachePutData(string $key, mixed $value, int $ttl) 125 - puts data in the cache 126 127 mixed smfapi_cacheGetData(string $key, int $ttl) 128 - gets data from the cache 129 130 bool smfapi_updateMemberData(mixed $member, array $data) 131 - change member data (email, password, name, etc.) 132 - will accept email address, username or member id 133 - data will be an associative array ('email_address' => 'newemail@address.com') etc. 134 135 true smfapi_smfSeedGenerator() 136 - generates random seed 137 138 bool smfapi_updateSettings(array $changeArray, bool $update) 139 - updates settings in $modSettings array and puts them in db 140 - called from smfapi_updateStats(), smfapi_deleteMessages() and smfapi_smfSeedGenerator() 141 142 true smfapi_setLoginCookie(int $cookie_length, int $id, string $password) 143 - called by smfapi_login() to set the cookie 144 145 array smfapi_urlParts(bool $local, bool $global) 146 - called by smfapi_setLoginCookie() to parse the url 147 148 bool smfapi_updateStats(string $type, int $parameter1, string $parameter2) 149 - update forum member stats 150 - called when registering or deleting a member 151 152 string smfapi_unHtmlspecialchars(string $string) 153 - fixes strings with special characters 154 - called when encrypting the password for checking 155 156 bool smfapi_deleteMessages(array $personal_messages, string $folder, int || array $owner) 157 - called by smfapi_deleteMembers() 158 159 string smfapi_generateValidationCode() 160 - used to generate a 10 char alpha validation code during registration 161 162 bool smfapi_isOnline(mixed $username) 163 - check if a user is online 164 - will accept email address, username or member id 165 166 bool smfapi_logOnline(mixed $username) 167 - log a user online 168 169 array smfapi_getMatchingFile(array $files, string $search) 170 - find a file from an array 171 - used to find Settings.php in case this script is not with it 172 173 array smfapi_getDirectoryContents(string $directory, array $exempt, array $files) 174 - gets the contents of a directory and all subdirectories 175 - called by smfapi_getMatchingFile 176 177 --------------------------------------------------------------------------- 178 It also defines the following important variables: 179 180 $smcFunc => Array 181 ( 182 [db_query] => smf_db_query 183 [db_quote] => smf_db_quote 184 [db_fetch_assoc] => mysql_fetch_assoc 185 [db_fetch_row] => mysql_fetch_row 186 [db_free_result] => mysql_free_result 187 [db_insert] => smf_db_insert 188 [db_insert_id] => smf_db_insert_id 189 [db_num_rows] => mysql_num_rows 190 [db_data_seek] => mysql_data_seek 191 [db_num_fields] => mysql_num_fields 192 [db_escape_string] => addslashes 193 [db_unescape_string] => stripslashes 194 [db_server_info] => mysql_get_server_info 195 [db_affected_rows] => smf_db_affected_rows 196 [db_transaction] => smf_db_transaction 197 [db_error] => mysql_error 198 [db_select_db] => mysql_select_db 199 [db_title] => 200 [db_sybase] => 201 [db_case_sensitive] => 202 [db_escape_wildcard_string] => smf_db_escape_wildcard_string 203 [entity_fix] => 204 [htmlspecialchars] => 205 [htmltrim] => 206 [strlen] => 207 [strpos] => 208 [substr] => 209 [strtolower] => 210 [strtoupper] => 211 [truncate] => 212 [ucfirst] => 213 [ucwords] => 214 ) 215 216 $modSettings => Array 217 ( 218 [smfVersion] => 219 [news] => 220 [compactTopicPagesContiguous] => 221 [compactTopicPagesEnable] => 222 [enableStickyTopics] => 223 [todayMod] => 224 [karmaMode] => 225 [karmaTimeRestrictAdmins] => 226 [enablePreviousNext] => 227 [pollMode] => 228 [enableVBStyleLogin] => 229 [enableCompressedOutput] => 230 [karmaWaitTime] => 231 [karmaMinPosts] => 232 [karmaLabel] => 233 [karmaSmiteLabel] => 234 [karmaApplaudLabel] => 235 [attachmentSizeLimit] => 236 [attachmentPostLimit] => 237 [attachmentNumPerPostLimit] => 238 [attachmentDirSizeLimit] => 239 [attachmentUploadDir] => 240 [attachmentExtensions] => 241 [attachmentCheckExtensions] => 242 [attachmentShowImages] => 243 [attachmentEnable] => 244 [attachmentEncryptFilenames] => 245 [attachmentThumbnails] => 246 [attachmentThumbWidth] => 247 [attachmentThumbHeight] => 248 [censorIgnoreCase] => 249 [mostOnline] => 250 [mostOnlineToday] => 251 [mostDate] => 252 [allow_disableAnnounce] => 253 [trackStats] => 254 [userLanguage] => 255 [titlesEnable] => 256 [topicSummaryPosts] => 257 [enableErrorLogging] => 258 [max_image_width] => 259 [max_image_height] => 260 [onlineEnable] => 261 [cal_enabled] => 262 [cal_maxyear] => 263 [cal_minyear] => 264 [cal_daysaslink] => 265 [cal_defaultboard] => 266 [cal_showholidays] => 267 [cal_showbdays] => 268 [cal_showevents] => 269 [cal_showweeknum] => 270 [cal_maxspan] => 271 [smtp_host] => 272 [smtp_port] => 273 [smtp_username] => 274 [smtp_password] => 275 [mail_type] => 276 [timeLoadPageEnable] => 277 [totalMembers] => 278 [totalTopics] => 279 [totalMessages] => 280 [simpleSearch] => 281 [censor_vulgar] => 282 [censor_proper] => 283 [enablePostHTML] => 284 [theme_allow] => 285 [theme_default] => 286 [theme_guests] => 287 [enableEmbeddedFlash] => 288 [xmlnews_enable] => 289 [xmlnews_maxlen] => 290 [hotTopicPosts] => 291 [hotTopicVeryPosts] => 292 [registration_method] => 293 [send_validation_onChange] => 294 [send_welcomeEmail] => 295 [allow_editDisplayName] => 296 [allow_hideOnline] => 297 [guest_hideContacts] => 298 [spamWaitTime] => 299 [pm_spam_settings] => 300 [reserveWord] => 301 [reserveCase] => 302 [reserveUser] => 303 [reserveName] => 304 [reserveNames] => 305 [autoLinkUrls] => 306 [banLastUpdated] => 307 [smileys_dir] => 308 [smileys_url] => 309 [avatar_directory] => 310 [avatar_url] => 311 [avatar_max_height_external] => 312 [avatar_max_width_external] => 313 [avatar_action_too_large] => 314 [avatar_max_height_upload] => 315 [avatar_max_width_upload] => 316 [avatar_resize_upload] => 317 [avatar_download_png] => 318 [failed_login_threshold] => 319 [oldTopicDays] => 320 [edit_wait_time] => 321 [edit_disable_time] => 322 [autoFixDatabase] => 323 [allow_guestAccess] => 324 [time_format] => 325 [number_format] => 326 [enableBBC] => 327 [max_messageLength] => 328 [signature_settings] => 329 [autoOptMaxOnline] => 330 [defaultMaxMessages] => 331 [defaultMaxTopics] => 332 [defaultMaxMembers] => 333 [enableParticipation] => 334 [recycle_enable] => 335 [recycle_board] => 336 [maxMsgID] => 337 [enableAllMessages] => 338 [fixLongWords] => 339 [knownThemes] => 340 [who_enabled] => 341 [time_offset] => 342 [cookieTime] => 343 [lastActive] => 344 [smiley_sets_known] => 345 [smiley_sets_names] => 346 [smiley_sets_default] => 347 [cal_days_for_index] => 348 [requireAgreement] => 349 [unapprovedMembers] => 350 [default_personal_text] => 351 [package_make_backups] => 352 [databaseSession_enable] => 353 [databaseSession_loose] => 354 [databaseSession_lifetime] => 355 [search_cache_size] => 356 [search_results_per_page] => 357 [search_weight_frequency] => 358 [search_weight_age] => 359 [search_weight_length] => 360 [search_weight_subject] => 361 [search_weight_first_message] => 362 [search_max_results] => 363 [search_floodcontrol_time] => 364 [permission_enable_deny] => 365 [permission_enable_postgroups] => 366 [mail_next_send] => 367 [mail_recent] => 368 [settings_updated] => 369 [next_task_time] => 370 [warning_settings] => 371 [warning_watch] => 372 [warning_moderate] => 373 [warning_mute] => 374 [admin_features] => 375 [last_mod_report_action] => 376 [pruningOptions] => 377 [cache_enable] => 378 [reg_verification] => 379 [visual_verification_type] => 380 [enable_buddylist] => 381 [birthday_email] => 382 [dont_repeat_theme_core] => 383 [dont_repeat_smileys_20] => 384 [dont_repeat_buddylists] => 385 [attachment_image_reencode] => 386 [attachment_image_paranoid] => 387 [attachment_thumb_png] => 388 [avatar_reencode] => 389 [avatar_paranoid] => 390 [global_character_set] => 391 [localCookies] => 392 [default_timezone] => 393 [memberlist_updated] => 394 [latestMember] => 395 [latestRealName] => 396 [rand_seed] => 397 [mostOnlineUpdated] => 398 ) 399 400 $user_info => Array 401 ( 402 [groups] => Array 403 ( 404 [0] => 405 [1] => 406 ) 407 408 [possibly_robot] => 409 [id] => 410 [username] => 411 [name] => 412 [email] => 413 [passwd] => 414 [language] => 415 [is_guest] => 416 [is_admin] => 417 [theme] => 418 [last_login] => 419 [ip] => 420 [ip2] => 421 [posts] => 422 [time_format] => 423 [time_offset] => 424 [avatar] => Array 425 ( 426 [url] => 427 [filename] => 428 [custom_dir] => 429 [id_attach] => 430 ) 431 432 [smiley_set] => 433 [messages] => 434 [unread_messages] => 435 [total_time_logged_in] => 436 [buddies] => Array 437 ( 438 ) 439 440 [ignoreboards] => Array 441 ( 442 ) 443 444 [ignoreusers] => Array 445 ( 446 ) 447 448 [warning] => 449 [permissions] => Array 450 ( 451 ) 452 453 ) 454 455 For even *more* member data use the function smfapi_getUserData() 456 It will return an array with the following: 457 458 $userdata => Array 459 ( 460 [id_member] => 461 [member_name] => 462 [date_registered] => 463 [posts] => 464 [id_group] => 465 [lngfile] => 466 [last_login] => 467 [real_name] => 468 [instant_messages] => 469 [unread_messages] => 470 [new_pm] => 471 [buddy_list] => 472 [pm_ignore_list] => 473 [pm_prefs] => 474 [mod_prefs] => 475 [message_labels] => 476 [passwd] => 477 [openid_uri] => 478 [email_address] => 479 [personal_text] => 480 [gender] => 481 [birthdate] => 482 [website_title] => 483 [website_url] => 484 [location] => 485 [icq] => 486 [aim] => 487 [yim] => 488 [msn] => 489 [hide_email] => 490 [show_online] => 491 [time_format] => 492 [signature] => 493 [time_offset] => 494 [avatar] => 495 [pm_email_notify] => 496 [karma_bad] => 497 [karma_good] => 498 [usertitle] => 499 [notify_announcements] => 500 [notify_regularity] => 501 [notify_send_body] => 502 [notify_types] => 503 [member_ip] => 504 [member_ip2] => 505 [secret_question] => 506 [secret_answer] => 507 [id_theme] => 508 [is_activated] => 509 [validation_code] => 510 [id_msg_last_visit] => 511 [additional_groups] => 512 [smiley_set] => 513 [id_post_group] => 514 [total_time_logged_in] => 515 [password_salt] => 516 [ignore_boards] => 517 [warning] => 518 [passwd_flood] => 519 [pm_receive_from] => 520 ) 521 522*/ 523 524// don't do anything if SMF is already loaded 525if (defined('SMF')) 526 return true; 527 528define('SMF', 'API'); 529 530// we're going to want a few globals... these are all set later 531// set from this script 532global $time_start, $scripturl, $context, $settings_path; 533 534// set from Settings.php 535global $maintenance, $mtitle, $mmessage, $mbname, $language, $boardurl; 536global $webmaster_email, $cookiename, $db_type, $db_server, $db_name, $db_user; 537global $db_passwd, $db_prefix, $db_persist, $db_error_send, $boarddir, $sourcedir; 538global $cachedir, $db_last_error, $db_character_set; 539 540// set from smfapi_loadDatabase() 541global $db_connection, $smcFunc; 542 543// set from smfapi_reloadSettings() 544global $modSettings; 545 546// set from smfapi_loadSession() 547global $sc; 548 549// set from smfapi_loadUserSettings() 550global $user_info; 551 552// turn off magic quotes 553if (function_exists('set_magic_quotes_runtime')) { 554 // remember the current configuration so it can be set back 555 $api_magic_quotes_runtime = function_exists('get_magic_quotes_gpc') && get_magic_quotes_runtime(); 556 @set_magic_quotes_runtime(0); 557} 558 559$time_start = microtime(); 560 561// just being safe... 562foreach (array('db_character_set', 'cachedir') as $variable) { 563 if (isset($GLOBALS[$variable])) { 564 unset($GLOBALS[$variable]); 565 } 566} 567 568$saveFile = dirname(__FILE__) . '/smfapi_settings.txt'; 569if (file_exists($saveFile)) { 570 $settings_path = base64_decode(file_get_contents($saveFile)); 571} 572 573// specify the settings path here if it's not in smf root and you want to speed things up 574$settings_path = SMF_SETTINGS; 575 576// get the forum's settings for database and file paths 577if (file_exists(dirname(__FILE__) . '/Settings.php')) { 578 require_once(dirname(__FILE__) . '/Settings.php'); 579} elseif (isset($settings_path)) { 580 require_once($settings_path); 581} else { 582 $directory = $_SERVER['DOCUMENT_ROOT'] . '/'; 583 $exempt = array('.', '..'); 584 $files = smfapi_getDirectoryContents($directory, $exempt); 585 $matches = smfapi_getMatchingFile($files, 'Settings.php'); 586 587 // we're going to search for it... 588 @set_time_limit(600); 589 // try to get some more memory 590 if (@ini_get('memory_limit') < 128) { 591 @ini_set('memory_limit', '128M'); 592 } 593 594 if (1 == count($matches)) { 595 require_once($matches[0]); 596 $settings_path = $matches[0]; 597 file_put_contents($saveFile, base64_encode($settings_path)); 598 } elseif (1 < count($matches)) { 599 $matches = smfapi_getMatchingFile($files, 'Settings_bak.php'); 600 $matches[0] = str_replace('_bak.php', '.php', $matches[0]); 601 require_once($matches[0]); 602 $settings_path = $matches[0]; 603 file_put_contents($saveFile, base64_encode($settings_path)); 604 } else { 605 return false; 606 } 607} 608 609$scripturl = $boardurl . '/index.php'; 610 611// make absolutely sure the cache directory is defined 612if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache')) { 613 $cachedir = $boarddir . '/cache'; 614} 615 616// don't do john didley if the forum's been shut down competely 617if (2 == $maintenance) { 618 return; 619} 620 621// fix for using the current directory as a path 622if (substr($sourcedir, 0, 1) == '.' && substr($sourcedir, 1, 1) != '.') { 623 $sourcedir = dirname(__FILE__) . substr($sourcedir, 1); 624} 625 626// using a pre 5.1 php version? 627if (-1 == @version_compare(PHP_VERSION, '5.1')) { 628 //safe to include, will check if functions exist before declaring 629 require_once($sourcedir . '/Subs-Compat.php'); 630} 631 632// create a variable to store some SMF specific functions in 633$smcFunc = array(); 634 635// we won't put anything in this 636$context = array(); 637 638// initate the database connection and define some database functions to use 639smfapi_loadDatabase(); 640 641// load settings 642smfapi_reloadSettings(); 643 644// create random seed if it's not already created 645if (empty($modSettings['rand_seed']) || mt_rand(1, 250) == 69) { 646 smfapi_smfSeedGenerator(); 647} 648 649// start the session if there isn't one already... 650smfapi_loadSession(); 651 652 653// load the user and their cookie, as well as their settings. 654smfapi_loadUserSettings(); 655 656/** 657 * Gets the user's info from their email address 658 * 659 * Will take the users email address and return an array containing all the 660 * user's information in the db. Will return false on failure 661 * 662 * @param string $email the user's email address 663 * @return array $results containing the user info || bool false 664 * @since 0.1.0 665 */ 666function smfapi_getUserByEmail($email='') 667{ 668 global $smcFunc; 669 670 if ('' == $email || !is_string($email) || 2 > count(explode('@', $email))) { 671 return false; 672 } 673 674 $request = $smcFunc['db_query']('', ' 675 SELECT * 676 FROM {db_prefix}members 677 WHERE email_address = {string:email_address} 678 LIMIT 1', 679 array( 680 'email_address' => $email, 681 ) 682 ); 683 $results = $smcFunc['db_fetch_assoc']($request); 684 $smcFunc['db_free_result']($request); 685 686 if (empty($results)) { 687 return false; 688 } else { 689 // return all the results. 690 return $results; 691 } 692} 693 694/** 695 * Gets the user's info from their member id 696 * 697 * Will take the users member id and return an array containing all the 698 * user's information in the db. Will return false on failure 699 * 700 * @param int $id the user's member id 701 * @return array $results containing the user info || bool false 702 * @since 0.1.0 703 */ 704function smfapi_getUserById($id='') 705{ 706 global $smcFunc; 707 708 if ('' == $id || !is_numeric($id)) { 709 return false; 710 } else{ 711 $id += 0; 712 if (!is_int($id)) { 713 return false; 714 } 715 } 716 717 $request = $smcFunc['db_query']('', ' 718 SELECT * 719 FROM {db_prefix}members 720 WHERE id_member = {int:id_member} 721 LIMIT 1', 722 array( 723 'id_member' => $id, 724 ) 725 ); 726 $results = $smcFunc['db_fetch_assoc']($request); 727 $smcFunc['db_free_result']($request); 728 729 if (empty($results)) { 730 return false; 731 } else { 732 // return all the results. 733 return $results; 734 } 735} 736 737/** 738 * Gets the user's info from their member name (username) 739 * 740 * Will take the users member name and return an array containing all the 741 * user's information in the db. Will return false on failure 742 * 743 * @param string $username the user's member name 744 * @return array $results containing the user info || bool false 745 * @since 0.1.0 746 */ 747function smfapi_getUserByUsername($username='') 748{ 749 global $smcFunc; 750 751 if ('' == $username || !is_string($username)) { 752 return false; 753 } 754 755 $request = $smcFunc['db_query']('', ' 756 SELECT * 757 FROM {db_prefix}members 758 WHERE member_name = {string:member_name} 759 LIMIT 1', 760 array( 761 'member_name' => $username, 762 ) 763 ); 764 $results = $smcFunc['db_fetch_assoc']($request); 765 $smcFunc['db_free_result']($request); 766 767 if (empty($results)) { 768 return false; 769 } else { 770 // return all the results. 771 return $results; 772 } 773} 774 775/** 776 * Gets the user's info 777 * 778 * Will take the users email, username or member id and return their data 779 * 780 * @param int || string $username the user's email address username or member id 781 * @return array $results containing the user info || bool false 782 * @since 0.1.0 783 */ 784function smfapi_getUserData($username='') 785{ 786 if ('' == $username) { 787 return false; 788 } 789 790 $user_data = array(); 791 792 // we'll try id || email, then username 793 if (is_numeric($username)) { 794 // number is most likely a member id 795 $user_data = smfapi_getUserById($username); 796 } else { 797 // the email can't be an int 798 $user_data = smfapi_getUserByEmail($username); 799 } 800 801 if (!$user_data) { 802 $user_data = smfapi_getUserByUsername($username); 803 } 804 805 if (empty($user_data)) { 806 return false; 807 } else { 808 return $user_data; 809 } 810} 811 812/** 813 * Logs the user in by setting the session cookie 814 * 815 * Be sure you've already authenticated the username/password 816 * using smfapi_authenticate() or some other means because 817 * this function WILL set the correct session cookie for the 818 * user you specify and they WILL be logged in 819 * 820 * @param string $username (or int member id or string email. We're not picky) 821 * @param int $cookieLength length to set the cookie for (in minutes) 822 * @return bool whether the login cookie was set or not 823 * @since 0.1.0 824 */ 825function smfapi_login($username='', $cookieLength=525600) 826{ 827 global $scripturl, $user_info, $user_settings, $smcFunc; 828 global $cookiename, $maintenance, $modSettings, $sc, $sourcedir; 829 830 if (1 == $maintenance || '' == $username) { 831 return false; 832 } 833 834 $user_data = smfapi_getUserData($username); 835 836 if (!$user_data) { 837 return false; 838 } 839 840 // cookie set, session too 841 smfapi_setLoginCookie(60 * $cookieLength, $user_data['id_member'], sha1($user_data['passwd'] 842 . $user_data['password_salt'])); 843 844 // you've logged in, haven't you? 845 smfapi_updateMemberData($user_data['id_member'], array('last_login' => time(), 'member_ip' => $user_info['ip'])); 846 847 // get rid of the online entry for that old guest.... 848 $smcFunc['db_query']('', ' 849 DELETE FROM {db_prefix}log_online 850 WHERE session = {string:session}', 851 array( 852 'session' => 'ip' . $user_info['ip'], 853 ) 854 ); 855 856 smfapi_loadUserSettings(); 857 858 return true; 859} 860 861/** 862 * Will authenticate the username/password combo 863 * 864 * Use this before setting the cookie to check if the username password are correct. 865 * 866 * @param mixed $username the user's member name, email or member id 867 * @param string $password the password plaintext or encrypted in any of several 868 * methods including smf's method: sha1(strtolower($username) . $password) 869 * @param bool $encrypted whether the password is encrypted or not. If you get 870 this wrong we'll figure it out anyways, just saves some work if it's right 871 * @return bool whether the user is authenticated or not 872 * @since 0.1.0 873 */ 874function smfapi_authenticate($username='', $password='', $encrypted=true) 875{ 876 877 global $scripturl, $user_info, $user_settings, $smcFunc; 878 global $cookiename, $modSettings, $sc, $sourcedir; 879 880 if ('' == $username || '' == $password) { 881 return false; 882 } 883 884 // just in case they used the email or member id... 885 $data = smfapi_getUserData($username); 886 if (empty($data)) { 887 return false; 888 } else { 889 $username = $data['member_name']; 890 } 891 892 // load the data up! 893 $request = $smcFunc['db_query']('', ' 894 SELECT passwd, id_member, id_group, lngfile, is_activated, email_address, additional_groups, member_name, password_salt, 895 openid_uri, passwd_flood 896 FROM {db_prefix}members 897 WHERE ' . ($smcFunc['db_case_sensitive'] ? 'LOWER(member_name) = LOWER({string:user_name})' : 'member_name = {string:user_name}') . ' 898 LIMIT 1', 899 array( 900 'user_name' => $smcFunc['db_case_sensitive'] ? strtolower($username) : $username, 901 ) 902 ); 903 // no user data found... invalid username 904 if ($smcFunc['db_num_rows']($request) == 0) { 905 return false; 906 } 907 908 $user_settings = $smcFunc['db_fetch_assoc']($request); 909 $smcFunc['db_free_result']($request); 910 911 if (40 != strlen($user_settings['passwd'])) { 912 // invalid hash in the db 913 return false; 914 } 915 916 // if it's not encrypted, do it now 917 if (!$encrypted) { 918 $sha_passwd = sha1(strtolower($user_settings['member_name']) 919 . smfapi_unHtmlspecialchars($password)); 920 } else { 921 $sha_passwd = $password; 922 } 923 924 // if they match the password/hash is correct 925 if ($user_settings['passwd'] == $sha_passwd) { 926 $user_info["id"] = $user_settings['id_member']; 927 return true; 928 } else { 929 // try other hashing schemes 930 $other_passwords = array(); 931 932 // in case they sent the encrypted password into this as unencrypted 933 $other_passwords[] = $password; 934 935 // none of the below cases will be used most of the time 936 // (because the salt is normally set) 937 if ('' == $user_settings['password_salt']) { 938 // YaBB SE, Discus, MD5 (used a lot), SHA-1 (used some), SMF 1.0.x, 939 // IkonBoard, and none at all 940 $other_passwords[] = crypt($password, substr($password, 0, 2)); 941 $other_passwords[] = crypt($password, substr($user_settings['passwd'], 0, 2)); 942 $other_passwords[] = md5($password); 943 $other_passwords[] = sha1($password); 944 $other_passwords[] = md5_hmac($password, strtolower($user_settings['member_name'])); 945 $other_passwords[] = md5($password . strtolower($user_settings['member_name'])); 946 $other_passwords[] = md5(md5($password)); 947 $other_passwords[] = $password; 948 949 // this one is a strange one... MyPHP, crypt() on the MD5 hash 950 $other_passwords[] = crypt(md5($password), md5($password)); 951 952 // Snitz style - SHA-256. Technically, this is a downgrade, but most PHP 953 // configurations don't support sha256 anyway. 954 if (strlen($user_settings['passwd']) == 64 955 && function_exists('mhash') && defined('MHASH_SHA256')) { 956 $other_passwords[] = bin2hex(mhash(MHASH_SHA256, $password)); 957 } 958 959 // phpBB3 users new hashing. We now support it as well ;) 960 $other_passwords[] = phpBB3_password_check($password, $user_settings['passwd']); 961 962 // APBoard 2 login method 963 $other_passwords[] = md5(crypt($password, 'CRYPT_MD5')); 964 } 965 // the hash should be 40 if it's SHA-1, so we're safe with more here too 966 elseif (strlen($user_settings['passwd']) == 32) { 967 // vBulletin 3 style hashing? Let's welcome them with open arms \o/ 968 $other_passwords[] = md5(md5($password) . $user_settings['password_salt']); 969 970 // hmm.. p'raps it's Invision 2 style? 971 $other_passwords[] = md5(md5($user_settings['password_salt']) 972 . md5($password)); 973 974 // some common md5 ones 975 $other_passwords[] = md5($user_settings['password_salt'] . $password); 976 $other_passwords[] = md5($password . $user_settings['password_salt']); 977 } elseif (strlen($user_settings['passwd']) == 40) { 978 // maybe they are using a hash from before the password fix 979 $other_passwords[] = sha1(strtolower($user_settings['member_name']) 980 . smfapi_unHtmlspecialchars($password)); 981 982 // BurningBoard3 style of hashing 983 $other_passwords[] = sha1($user_settings['password_salt'] 984 . sha1($user_settings['password_salt'] 985 . sha1($password))); 986 987 // perhaps we converted to UTF-8 and have a valid password being 988 // hashed differently 989 if (!empty($modSettings['previousCharacterSet']) 990 && $modSettings['previousCharacterSet'] != 'utf8') { 991 992 // try iconv first, for no particular reason 993 if (function_exists('iconv')) { 994 $other_passwords['iconv'] = sha1(strtolower(iconv('UTF-8', $modSettings['previousCharacterSet'], $user_settings['member_name'])) 995 . un_htmlspecialchars(iconv('UTF-8', $modSettings['previousCharacterSet'], $password))); 996 } 997 998 // say it aint so, iconv failed 999 if (empty($other_passwords['iconv']) && function_exists('mb_convert_encoding')) { 1000 $other_passwords[] = sha1(strtolower(mb_convert_encoding($user_settings['member_name'], 'UTF-8', $modSettings['previousCharacterSet'])) 1001 . un_htmlspecialchars(mb_convert_encoding($password, 'UTF-8', $modSettings['previousCharacterSet']))); 1002 } 1003 } 1004 } 1005 1006 // SMF's sha1 function can give a funny result on Linux (not our fault!) 1007 // if we've now got the real one let the old one be valid! 1008 if (strpos(strtolower(PHP_OS), 'win') !== 0) { 1009 require_once($sourcedir . '/Subs-Compat.php'); 1010 $other_passwords[] = sha1_smf(strtolower($user_settings['member_name']) . smfapi_unHtmlspecialchars($password)); 1011 } 1012 1013 // if ANY of these other hashes match we'll accept it 1014 if (in_array($user_settings['passwd'], $other_passwords)) { 1015 // we're not going to update the password or the hash. whatever was 1016 // used worked, so it will work again through this api, or SMF will 1017 // update it if the user authenticates through there. No sense messing 1018 // with it if it's not broken imo. Authentication successful 1019 $user_info["id"] = $user_settings['id_member']; 1020 return true; 1021 } 1022 } 1023 1024 //authentication failed 1025 return false; 1026} 1027 1028/** 1029 * Will log out a user 1030 * 1031 * Takes a username, email or member id and logs that user out. If it can't find 1032 * a match it will look for the currently logged user if any. 1033 * 1034 * @param string $username user's member name (or int member id or string email) 1035 * @return bool whether logout was successful or not 1036 * @since 0.1.0 1037 */ 1038function smfapi_logout($username='') 1039{ 1040 global $sourcedir, $user_info, $user_settings, $context, $modSettings, $smcFunc; 1041 1042 if ('' == $username && $user_info['is_guest']) { 1043 return false; 1044 } 1045 1046 $user_data = smfapi_getUserData($username); 1047 1048 if (!$user_data) { 1049 if (isset($user_info['id_member']) && false !== smfapi_getUserById($user_info['id_member'])) { 1050 $user_data['id_member'] = $user_info['id_member']; 1051 } else { 1052 return false; 1053 } 1054 } 1055 1056 // if you log out, you aren't online anymore :P. 1057 $smcFunc['db_query']('', ' 1058 DELETE FROM {db_prefix}log_online 1059 WHERE id_member = {int:current_member}', 1060 array( 1061 'current_member' => $user_data['id_member'], 1062 ) 1063 ); 1064 1065 if (isset($_SESSION['pack_ftp'])) { 1066 $_SESSION['pack_ftp'] = null; 1067 } 1068 1069 // they cannot be open ID verified any longer. 1070 if (isset($_SESSION['openid'])) { 1071 unset($_SESSION['openid']); 1072 } 1073 1074 // it won't be first login anymore. 1075 unset($_SESSION['first_login']); 1076 1077 smfapi_setLoginCookie(-3600, 0); 1078 1079 return true; 1080} 1081 1082/** 1083 * Delete members 1084 * 1085 * Delete a member or an array of members by member id 1086 * 1087 * @param int || int array $users the member id(s) 1088 * @return bool true when complete or false if user array empty 1089 * @since 0.1.0 1090 */ 1091function smfapi_deleteMembers($users) 1092{ 1093 global $sourcedir, $modSettings, $user_info, $smcFunc; 1094 1095 // try give us a while to sort this out... 1096 @set_time_limit(600); 1097 // try to get some more memory 1098 if (@ini_get('memory_limit') < 128) { 1099 @ini_set('memory_limit', '128M'); 1100 } 1101 1102 // if it's not an array, make it so 1103 if (!is_array($users)) { 1104 $users = array($users); 1105 } else { 1106 $users = array_unique($users); 1107 } 1108 1109 foreach ($users as &$user) { 1110 if (!is_int($user)) { 1111 $data = smfapi_getUserData($user); 1112 $user = $data['id_member'] + 0; 1113 } 1114 } 1115 1116 // make sure there's no void user in here 1117 $users = array_diff($users, array(0)); 1118 1119 if (empty($users)) { 1120 return false; 1121 } 1122 1123 // make these peoples' posts guest posts 1124 $smcFunc['db_query']('', ' 1125 UPDATE {db_prefix}messages 1126 SET id_member = {int:guest_id}, poster_email = {string:blank_email} 1127 WHERE id_member IN ({array_int:users})', 1128 array( 1129 'guest_id' => 0, 1130 'blank_email' => '', 1131 'users' => $users, 1132 ) 1133 ); 1134 1135 $smcFunc['db_query']('', ' 1136 UPDATE {db_prefix}polls 1137 SET id_member = {int:guest_id} 1138 WHERE id_member IN ({array_int:users})', 1139 array( 1140 'guest_id' => 0, 1141 'users' => $users, 1142 ) 1143 ); 1144 1145 // make these peoples' posts guest first posts and last posts 1146 $smcFunc['db_query']('', ' 1147 UPDATE {db_prefix}topics 1148 SET id_member_started = {int:guest_id} 1149 WHERE id_member_started IN ({array_int:users})', 1150 array( 1151 'guest_id' => 0, 1152 'users' => $users, 1153 ) 1154 ); 1155 1156 $smcFunc['db_query']('', ' 1157 UPDATE {db_prefix}topics 1158 SET id_member_updated = {int:guest_id} 1159 WHERE id_member_updated IN ({array_int:users})', 1160 array( 1161 'guest_id' => 0, 1162 'users' => $users, 1163 ) 1164 ); 1165 1166 $smcFunc['db_query']('', ' 1167 UPDATE {db_prefix}log_actions 1168 SET id_member = {int:guest_id} 1169 WHERE id_member IN ({array_int:users})', 1170 array( 1171 'guest_id' => 0, 1172 'users' => $users, 1173 ) 1174 ); 1175 1176 $smcFunc['db_query']('', ' 1177 UPDATE {db_prefix}log_banned 1178 SET id_member = {int:guest_id} 1179 WHERE id_member IN ({array_int:users})', 1180 array( 1181 'guest_id' => 0, 1182 'users' => $users, 1183 ) 1184 ); 1185 1186 $smcFunc['db_query']('', ' 1187 UPDATE {db_prefix}log_errors 1188 SET id_member = {int:guest_id} 1189 WHERE id_member IN ({array_int:users})', 1190 array( 1191 'guest_id' => 0, 1192 'users' => $users, 1193 ) 1194 ); 1195 1196 // delete the member 1197 $smcFunc['db_query']('', ' 1198 DELETE FROM {db_prefix}members 1199 WHERE id_member IN ({array_int:users})', 1200 array( 1201 'users' => $users, 1202 ) 1203 ); 1204 1205 // delete the logs... 1206 $smcFunc['db_query']('', ' 1207 DELETE FROM {db_prefix}log_actions 1208 WHERE id_log = {int:log_type} 1209 AND id_member IN ({array_int:users})', 1210 array( 1211 'log_type' => 2, 1212 'users' => $users, 1213 ) 1214 ); 1215 1216 $smcFunc['db_query']('', ' 1217 DELETE FROM {db_prefix}log_boards 1218 WHERE id_member IN ({array_int:users})', 1219 array( 1220 'users' => $users, 1221 ) 1222 ); 1223 1224 $smcFunc['db_query']('', ' 1225 DELETE FROM {db_prefix}log_comments 1226 WHERE id_recipient IN ({array_int:users}) 1227 AND comment_type = {string:warntpl}', 1228 array( 1229 'users' => $users, 1230 'warntpl' => 'warntpl', 1231 ) 1232 ); 1233 1234 $smcFunc['db_query']('', ' 1235 DELETE FROM {db_prefix}log_group_requests 1236 WHERE id_member IN ({array_int:users})', 1237 array( 1238 'users' => $users, 1239 ) 1240 ); 1241 1242 $smcFunc['db_query']('', ' 1243 DELETE FROM {db_prefix}log_karma 1244 WHERE id_target IN ({array_int:users}) 1245 OR id_executor IN ({array_int:users})', 1246 array( 1247 'users' => $users, 1248 ) 1249 ); 1250 1251 $smcFunc['db_query']('', ' 1252 DELETE FROM {db_prefix}log_mark_read 1253 WHERE id_member IN ({array_int:users})', 1254 array( 1255 'users' => $users, 1256 ) 1257 ); 1258 1259 $smcFunc['db_query']('', ' 1260 DELETE FROM {db_prefix}log_notify 1261 WHERE id_member IN ({array_int:users})', 1262 array( 1263 'users' => $users, 1264 ) 1265 ); 1266 1267 $smcFunc['db_query']('', ' 1268 DELETE FROM {db_prefix}log_online 1269 WHERE id_member IN ({array_int:users})', 1270 array( 1271 'users' => $users, 1272 ) 1273 ); 1274 1275 $smcFunc['db_query']('', ' 1276 DELETE FROM {db_prefix}log_subscribed 1277 WHERE id_member IN ({array_int:users})', 1278 array( 1279 'users' => $users, 1280 ) 1281 ); 1282 1283 $smcFunc['db_query']('', ' 1284 DELETE FROM {db_prefix}log_topics 1285 WHERE id_member IN ({array_int:users})', 1286 array( 1287 'users' => $users, 1288 ) 1289 ); 1290 1291 $smcFunc['db_query']('', ' 1292 DELETE FROM {db_prefix}collapsed_categories 1293 WHERE id_member IN ({array_int:users})', 1294 array( 1295 'users' => $users, 1296 ) 1297 ); 1298 1299 // make their votes appear as guest votes - at least it keeps the totals right 1300 $smcFunc['db_query']('', ' 1301 UPDATE {db_prefix}log_polls 1302 SET id_member = {int:guest_id} 1303 WHERE id_member IN ({array_int:users})', 1304 array( 1305 'guest_id' => 0, 1306 'users' => $users, 1307 ) 1308 ); 1309 1310 // delete personal messages 1311 smfapi_deleteMessages(null, null, $users); 1312 1313 $smcFunc['db_query']('', ' 1314 UPDATE {db_prefix}personal_messages 1315 SET id_member_from = {int:guest_id} 1316 WHERE id_member_from IN ({array_int:users})', 1317 array( 1318 'guest_id' => 0, 1319 'users' => $users, 1320 ) 1321 ); 1322 1323 // they no longer exist, so we don't know who it was sent to 1324 $smcFunc['db_query']('', ' 1325 DELETE FROM {db_prefix}pm_recipients 1326 WHERE id_member IN ({array_int:users})', 1327 array( 1328 'users' => $users, 1329 ) 1330 ); 1331 1332 // it's over, no more moderation for you 1333 $smcFunc['db_query']('', ' 1334 DELETE FROM {db_prefix}moderators 1335 WHERE id_member IN ({array_int:users})', 1336 array( 1337 'users' => $users, 1338 ) 1339 ); 1340 1341 $smcFunc['db_query']('', ' 1342 DELETE FROM {db_prefix}group_moderators 1343 WHERE id_member IN ({array_int:users})', 1344 array( 1345 'users' => $users, 1346 ) 1347 ); 1348 1349 // if you don't exist we can't ban you 1350 $smcFunc['db_query']('', ' 1351 DELETE FROM {db_prefix}ban_items 1352 WHERE id_member IN ({array_int:users})', 1353 array( 1354 'users' => $users, 1355 ) 1356 ); 1357 1358 // remove individual theme settings 1359 $smcFunc['db_query']('', ' 1360 DELETE FROM {db_prefix}themes 1361 WHERE id_member IN ({array_int:users})', 1362 array( 1363 'users' => $users, 1364 ) 1365 ); 1366 1367 // I'm not your buddy, chief 1368 $request = $smcFunc['db_query']('', ' 1369 SELECT id_member, pm_ignore_list, buddy_list 1370 FROM {db_prefix}members 1371 WHERE FIND_IN_SET({raw:pm_ignore_list}, pm_ignore_list) != 0 OR FIND_IN_SET({raw:buddy_list}, buddy_list) != 0', 1372 array( 1373 'pm_ignore_list' => implode(', pm_ignore_list) != 0 OR FIND_IN_SET(', $users), 1374 'buddy_list' => implode(', buddy_list) != 0 OR FIND_IN_SET(', $users), 1375 ) 1376 ); 1377 1378 while ($row = $smcFunc['db_fetch_assoc']($request)) { 1379 $smcFunc['db_query']('', ' 1380 UPDATE {db_prefix}members 1381 SET 1382 pm_ignore_list = {string:pm_ignore_list}, 1383 buddy_list = {string:buddy_list} 1384 WHERE id_member = {int:id_member}', 1385 array( 1386 'id_member' => $row['id_member'], 1387 'pm_ignore_list' => implode(',', array_diff(explode(',', $row['pm_ignore_list']), $users)), 1388 'buddy_list' => implode(',', array_diff(explode(',', $row['buddy_list']), $users)), 1389 ) 1390 ); 1391 } 1392 1393 $smcFunc['db_free_result']($request); 1394 1395 // make sure no member's birthday is still sticking in the calendar... 1396 smfapi_updateSettings(array( 1397 'calendar_updated' => time(), 1398 )); 1399 1400 smfapi_updateStats('member'); 1401 1402 return true; 1403} 1404 1405/** 1406 * Register a member 1407 * 1408 * Register a new member with SMF 1409 * 1410 * @param array $regOptions the registration options 1411 * @return int $memberId the user's member id || bool false 1412 * @since 0.1.0 1413 */ 1414function smfapi_registerMember($regOptions) 1415{ 1416 global $scripturl, $modSettings, $sourcedir; 1417 global $user_info, $options, $settings, $smcFunc; 1418 1419 $reg_errors = array(); 1420 1421 // check username 1422 if (empty($regOptions['member_name'])) { 1423 $reg_errors[] = 'username empty'; 1424 } 1425 1426 if (false !== smfapi_getUserbyUsername($regOptions['member_name'])) { 1427 $reg_errors[] = 'username taken'; 1428 } 1429 1430 // check email 1431 if (empty($regOptions['email']) 1432 || preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $regOptions['email']) === 0 1433 || strlen($regOptions['email']) > 255) { 1434 $reg_errors[] = 'email invalid'; 1435 } 1436 1437 if (false !== smfapi_getUserbyEmail($regOptions['email'])) { 1438 $reg_errors[] = 'email already in use'; 1439 } 1440 1441 // generate a validation code if it's supposed to be emailed 1442 // unless there was one passed in for us to use 1443 $validation_code = ''; 1444 if (!isset($regOptions['require']) || empty($regOptions['require'])) { 1445 //we need to set it to something... 1446 $regOptions['require'] = 'nothing'; 1447 } 1448 if ($regOptions['require'] == 'activation') { 1449 if (isset($regOptions['validation_code'])) { 1450 $validation_code = $regOptions['validation_code']; 1451 } else { 1452 $validation_code = smfapi_generateValidationCode(); 1453 } 1454 } 1455 1456 if (!isset($regOptions['password_check']) || empty($regOptions['password_check'])) { 1457 //make them match if the check wasn't set or it will fail the comparison below 1458 $regOptions['password_check'] = $regOptions['password']; 1459 } 1460 if ($regOptions['password'] != $regOptions['password_check']) { 1461 $reg_errors[] = 'password check failed'; 1462 } 1463 1464 // password empty is an error 1465 if ('' == $regOptions['password']) { 1466 $reg_errors[] = 'password empty'; 1467 } 1468 1469 // if there's any errors left return them at once 1470 if (!empty($reg_errors)) { 1471 return $reg_errors; 1472 } 1473 1474 // some of these might be overwritten (the lower ones that are in the arrays below) 1475 $regOptions['register_vars'] = array( 1476 'member_name' => $regOptions['member_name'], 1477 'email_address' => $regOptions['email'], 1478 'passwd' => sha1(strtolower($regOptions['member_name']) . $regOptions['password']), 1479 'password_salt' => substr(md5(mt_rand()), 0, 4) , 1480 'posts' => 0, 1481 'date_registered' => time(), 1482 'member_ip' => $user_info['ip'], 1483 'member_ip2' => isset($_SERVER['BAN_CHECK_IP'])?$_SERVER['BAN_CHECK_IP']:'', 1484 'validation_code' => $validation_code, 1485 'real_name' => isset($regOptions['real_name'])?$regOptions['real_name']:$regOptions['member_name'], 1486 'personal_text' => $modSettings['default_personal_text'], 1487 'pm_email_notify' => 1, 1488 'id_theme' => 0, 1489 'id_post_group' => 4, 1490 'lngfile' => isset($regOptions['lngfile'])?$regOptions['lngfile']:'', 1491 'buddy_list' => '', 1492 'pm_ignore_list' => '', 1493 'message_labels' => '', 1494 'website_title' => isset($regOptions['website_title'])?$regOptions['website_title']:'', 1495 'website_url' => isset($regOptions['website_url'])?$regOptions['website_url']:'', 1496 'location' => isset($regOptions['location'])?$regOptions['location']:'', 1497 'icq' => isset($regOptions['icq'])?$regOptions['icq']:'', 1498 'aim' => isset($regOptions['aim'])?$regOptions['aim']:'', 1499 'yim' => isset($regOptions['yim'])?$regOptions['yim']:'', 1500 'msn' => isset($regOptions['msn'])?$regOptions['msn']:'', 1501 'time_format' => isset($regOptions['time_format'])?$regOptions['time_format']:'', 1502 'signature' => isset($regOptions['signature'])?$regOptions['signature']:'', 1503 'avatar' => isset($regOptions['avatar'])?$regOptions['avatar']:'', 1504 'usertitle' => '', 1505 'secret_question' => isset($regOptions['secret_question'])?$regOptions['secret_question']:'', 1506 'secret_answer' => isset($regOptions['secret_answer'])?$regOptions['secret_answer']:'', 1507 'additional_groups' => '', 1508 'ignore_boards' => '', 1509 'smiley_set' => '', 1510 'openid_uri' => isset($regOptions['openid_uri'])?$regOptions['openid_uri']:'', 1511 ); 1512 1513 // maybe it can be activated right away? 1514 if ($regOptions['require'] == 'nothing') 1515 $regOptions['register_vars']['is_activated'] = 1; 1516 // maybe it must be activated by email? 1517 elseif ($regOptions['require'] == 'activation') 1518 $regOptions['register_vars']['is_activated'] = 0; 1519 // otherwise it must be awaiting approval! 1520 else 1521 $regOptions['register_vars']['is_activated'] = 3; 1522 1523 if (isset($regOptions['memberGroup'])) 1524 { 1525 // make sure the id_group will be valid, if this is an administator 1526 $regOptions['register_vars']['id_group'] = $regOptions['memberGroup']; 1527 1528 // check if this group is assignable 1529 $unassignableGroups = array(-1, 3); 1530 $request = $smcFunc['db_query']('', ' 1531 SELECT id_group 1532 FROM {db_prefix}membergroups 1533 WHERE min_posts != {int:min_posts}' . ' 1534 OR group_type = {int:is_protected}', 1535 array( 1536 'min_posts' => -1, 1537 'is_protected' => 1, 1538 ) 1539 ); 1540 while ($row = $smcFunc['db_fetch_assoc']($request)) { 1541 $unassignableGroups[] = $row['id_group']; 1542 } 1543 1544 $smcFunc['db_free_result']($request); 1545 1546 if (in_array($regOptions['register_vars']['id_group'], $unassignableGroups)) { 1547 $regOptions['register_vars']['id_group'] = 0; 1548 } 1549 } 1550 1551 // integrate optional user theme options to be set 1552 $theme_vars = array(); 1553 1554 if (!empty($regOptions['theme_vars'])) { 1555 foreach ($regOptions['theme_vars'] as $var => $value) { 1556 $theme_vars[$var] = $value; 1557 } 1558 } 1559 1560 // right, now let's prepare for insertion 1561 $knownInts = array( 1562 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', 1563 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'karma_good', 'karma_bad', 1564 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', 1565 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', 1566 ); 1567 $knownFloats = array( 1568 'time_offset', 1569 ); 1570 1571 $column_names = array(); 1572 $values = array(); 1573 1574 foreach ($regOptions['register_vars'] as $var => $val) { 1575 $type = 'string'; 1576 if (in_array($var, $knownInts)) { 1577 $type = 'int'; 1578 } elseif (in_array($var, $knownFloats)) { 1579 $type = 'float'; 1580 } elseif ($var == 'birthdate') { 1581 $type = 'date'; 1582 } 1583 1584 $column_names[$var] = $type; 1585 $values[$var] = $val; 1586 } 1587 1588 // register them into the database 1589 $smcFunc['db_insert']('', 1590 '{db_prefix}members', 1591 $column_names, 1592 $values, 1593 array('id_member') 1594 ); 1595 1596 $memberID = $smcFunc['db_insert_id']('{db_prefix}members', 'id_member'); 1597 1598 // update the number of members and latest member's info - and pass the name, but remove the 's 1599 if ($regOptions['register_vars']['is_activated'] == 1) { 1600 smfapi_updateStats('member', $memberID, $regOptions['register_vars']['real_name']); 1601 } else { 1602 smfapi_updateStats('member'); 1603 } 1604 1605 // theme variables too? 1606 if (!empty($theme_vars)) { 1607 $inserts = array(); 1608 foreach ($theme_vars as $var => $val) { 1609 $inserts[] = array($memberID, $var, $val); 1610 } 1611 $smcFunc['db_insert']('insert', 1612 '{db_prefix}themes', 1613 array('id_member' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'), 1614 $inserts, 1615 array('id_member', 'variable') 1616 ); 1617 } 1618 1619 // okay, they're for sure registered... make sure the session is aware of this for security 1620 $_SESSION['just_registered'] = 1; 1621 1622 return $memberID; 1623} 1624 1625/** 1626 * Logs an error to the smf error log 1627 * 1628 * Logs errors of different types. Will refer to this file unless $file has a value. 1629 * 1630 * @param string $error_message the error message to log 1631 * @param string $error_type the type of error, see $known_error_types array below 1632 * for valid values to use 1633 * @param string $file the file to reference in the log, if any. Use __FILE__ 1634 * @param int $line the line number to reference in the log, if any. Use __LINE__ 1635 * @return bool true if successful, false if error logging is disabled 1636 * @since 0.1.0 1637 */ 1638function smfapi_logError($error_message, $error_type = 'general', $file = null, $line = null) 1639{ 1640 global $modSettings, $sc, $user_info, $smcFunc, $scripturl, $last_error; 1641 1642 // check if error logging is actually on. 1643 if (empty($modSettings['enableErrorLogging'])) { 1644 return false; 1645 } 1646 1647 // basically, htmlspecialchars it minus &. (for entities!) 1648 $error_message = strtr($error_message, array('<' => '<', '>' => '>', '"' => '"')); 1649 $error_message = strtr($error_message, array('<br />' => '<br />', '<b>' => '<strong>', '</b>' => '</strong>', "\n" => '<br />')); 1650 1651 // add a file and line to the error message? 1652 // don't use the actual txt entries for file and line but instead use %1$s for file and %2$s for line 1653 if ($file == null) { 1654 $file = $scripturl; 1655 } else { 1656 // window style slashes don't play well, lets convert them to the unix style 1657 $file = str_replace('\\', '/', $file); 1658 } 1659 1660 if ($line == null) { 1661 $line = 0; 1662 } else { 1663 $line = (int) $line; 1664 } 1665 1666 // just in case there's no id_member or IP set yet 1667 if (empty($user_info['id'])) { 1668 $user_info['id'] = 0; 1669 } 1670 if (empty($user_info['ip'])) { 1671 $user_info['ip'] = ''; 1672 } 1673 1674 // find the best query string we can... 1675 $query_string = empty($_SERVER['QUERY_STRING']) ? (empty($_SERVER['REQUEST_URL']) ? '' : str_replace($scripturl, '', $_SERVER['REQUEST_URL'])) : $_SERVER['QUERY_STRING']; 1676 1677 // don't log the session hash in the url twice, it's a waste. 1678 $query_string = htmlspecialchars((SMF == 'API' ? '' : '?') . preg_replace(array('~;sesc=[^&;]+~', '~' 1679 . session_name() . '=' . session_id() . '[&;]~'), array(';sesc', ''), $query_string)); 1680 1681 1682 // what types of categories do we have? 1683 $known_error_types = array( 1684 'general', 1685 'critical', 1686 'database', 1687 'undefined_vars', 1688 'user', 1689 'template', 1690 'debug', 1691 ); 1692 1693 // make sure the category that was specified is a valid one 1694 $error_type = in_array($error_type, $known_error_types) && $error_type !== true ? $error_type : 'general'; 1695 1696 // don't log the same error countless times, as we can get in a cycle of depression... 1697 $error_info = array($user_info['id'], time(), $user_info['ip'], $query_string, $error_message, (string) $sc, $error_type, $file, $line); 1698 if (empty($last_error) || $last_error != $error_info) { 1699 // insert the error into the database. 1700 $smcFunc['db_insert']('', 1701 '{db_prefix}log_errors', 1702 array('id_member' => 'int', 'log_time' => 'int', 'ip' => 'string-16', 'url' => 'string-65534', 'message' => 'string-65534', 'session' => 'string', 'error_type' => 'string', 'file' => 'string-255', 'line' => 'int'), 1703 $error_info, 1704 array('id_error') 1705 ); 1706 $last_error = $error_info; 1707 } 1708 1709 return true; 1710} 1711 1712/** 1713 * Load the $modSettings array and adds to the $smcFunc array 1714 * 1715 * Loads the $modSettings array which all has the forum configuration data 1716 * that wasn't in Settings.php and adds the non-db related functions to the 1717 * $smcFunc array. Also sets the timezone so php time funcs won't throw errors 1718 * 1719 * @return bool true when complete, failure is not an option 1720 * @since 0.1.0 1721 */ 1722function smfapi_reloadSettings() 1723{ 1724 global $modSettings, $boarddir, $smcFunc, $txt, $db_character_set, $context, $sourcedir; 1725 1726 // most database systems have not set UTF-8 as their default input charset. 1727 if (!empty($db_character_set)) { 1728 $smcFunc['db_query']('set_character_set', ' 1729 SET NAMES ' . $db_character_set, 1730 array( 1731 ) 1732 ); 1733 } 1734 1735 // try to load it from the cache first; it'll never get cached if the setting is off. 1736 if (($modSettings = smfapi_cacheGetData('modSettings', 90)) == null) { 1737 $request = $smcFunc['db_query']('', ' 1738 SELECT variable, value 1739 FROM {db_prefix}settings', 1740 array( 1741 ) 1742 ); 1743 1744 $modSettings = array(); 1745 1746 if (!$request) { 1747 return false; 1748 } 1749 1750 while ($row = $smcFunc['db_fetch_row']($request)) { 1751 $modSettings[$row[0]] = $row[1]; 1752 } 1753 1754 $smcFunc['db_free_result']($request); 1755 1756 if (!empty($modSettings['cache_enable'])) { 1757 smfapi_cachePutData('modSettings', $modSettings, 90); 1758 } 1759 } 1760 1761 // UTF-8 in regular expressions is unsupported on PHP(win) versions < 4.2.3. 1762 $utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8' && (strpos(strtolower(PHP_OS), 'win') === false || @version_compare(PHP_VERSION, '4.2.3') != -1); 1763 1764 // set a list of common functions. 1765 $ent_list = empty($modSettings['disableEntityCheck']) ? '&(#\d{1,7}|quot|amp|lt|gt|nbsp);' : '&(#021|quot|amp|lt|gt|nbsp);'; 1766 $ent_check = empty($modSettings['disableEntityCheck']) ? array('preg_replace(\'~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~e\', \'$smcFunc[\\\'entity_fix\\\'](\\\'\\2\\\')\', ', ')') : array('', ''); 1767 1768 // preg_replace can handle complex characters only for higher PHP versions. 1769 $space_chars = $utf8 ? (@version_compare(PHP_VERSION, '4.3.3') != -1 ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : "\xC2\xA0\xC2\xAD\xE2\x80\x80-\xE2\x80\x8F\xE2\x80\x9F\xE2\x80\xAF\xE2\x80\x9F\xE3\x80\x80\xEF\xBB\xBF") : '\x00-\x08\x0B\x0C\x0E-\x19\xA0'; 1770 1771 $smcFunc += array( 1772 'entity_fix' => create_function('$string', ' 1773 $num = substr($string, 0, 1) === \'x\' ? hexdec(substr($string, 1)) : (int) $string; 1774 return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num == 0x202E ? \'\' : \'&#\' . $num . \';\';'), 1775 'htmlspecialchars' => create_function('$string, $quote_style = ENT_COMPAT, $charset = \'ISO-8859-1\'', ' 1776 global $smcFunc; 1777 return ' . strtr($ent_check[0], array('&' => '&')) . 'htmlspecialchars($string, $quote_style, ' . ($utf8 ? '\'UTF-8\'' : '$charset') . ')' . $ent_check[1] . ';'), 1778 'htmltrim' => create_function('$string', ' 1779 global $smcFunc; 1780 return preg_replace(\'~^(?:[ \t\n\r\x0B\x00' . $space_chars . ']| )+|(?:[ \t\n\r\x0B\x00' . $space_chars . ']| )+$~' . ($utf8 ? 'u' : '') . '\', \'\', ' . implode('$string', $ent_check) . ');'), 1781 'strlen' => create_function('$string', ' 1782 global $smcFunc; 1783 return strlen(preg_replace(\'~' . $ent_list . ($utf8 ? '|.~u' : '~') . '\', \'_\', ' . implode('$string', $ent_check) . '));'), 1784 'strpos' => create_function('$haystack, $needle, $offset = 0', ' 1785 global $smcFunc; 1786 $haystack_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$haystack', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 1787 $haystack_size = count($haystack_arr); 1788 if (strlen($needle) === 1) 1789 { 1790 $result = array_search($needle, array_slice($haystack_arr, $offset)); 1791 return is_int($result) ? $result + $offset : false; 1792 } 1793 else 1794 { 1795 $needle_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$needle', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 1796 $needle_size = count($needle_arr); 1797 1798 $result = array_search($needle_arr[0], array_slice($haystack_arr, $offset)); 1799 while (is_int($result)) 1800 { 1801 $offset += $result; 1802 if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr) 1803 return $offset; 1804 $result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset)); 1805 } 1806 return false; 1807 }'), 1808 'substr' => create_function('$string, $start, $length = null', ' 1809 global $smcFunc; 1810 $ent_arr = preg_split(\'~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|"|&|<|>| |.)~' . ($utf8 ? 'u' : '') . '\', ' . implode('$string', $ent_check) . ', -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); 1811 return $length === null ? implode(\'\', array_slice($ent_arr, $start)) : implode(\'\', array_slice($ent_arr, $start, $length));'), 1812 'strtolower' => $utf8 ? (function_exists('mb_strtolower') ? create_function('$string', ' 1813 return mb_strtolower($string, \'UTF-8\');') : create_function('$string', ' 1814 global $sourcedir; 1815 require_once($sourcedir . \'/Subs-Charset.php\'); 1816 return utf8_strtolower($string);')) : 'strtolower', 1817 'strtoupper' => $utf8 ? (function_exists('mb_strtoupper') ? create_function('$string', ' 1818 return mb_strtoupper($string, \'UTF-8\');') : create_function('$string', ' 1819 global $sourcedir; 1820 require_once($sourcedir . \'/Subs-Charset.php\'); 1821 return utf8_strtoupper($string);')) : 'strtoupper', 1822 'truncate' => create_function('$string, $length', (empty($modSettings['disableEntityCheck']) ? ' 1823 global $smcFunc; 1824 $string = ' . implode('$string', $ent_check) . ';' : '') . ' 1825 preg_match(\'~^(' . $ent_list . '|.){\' . $smcFunc[\'strlen\'](substr($string, 0, $length)) . \'}~'. ($utf8 ? 'u' : '') . '\', $string, $matches); 1826 $string = $matches[0]; 1827 while (strlen($string) > $length) 1828 $string = preg_replace(\'~(?:' . $ent_list . '|.)$~'. ($utf8 ? 'u' : '') . '\', \'\', $string); 1829 return $string;'), 1830 'ucfirst' => $utf8 ? create_function('$string', ' 1831 global $smcFunc; 1832 return $smcFunc[\'strtoupper\']($smcFunc[\'substr\']($string, 0, 1)) . $smcFunc[\'substr\']($string, 1);') : 'ucfirst', 1833 'ucwords' => $utf8 ? create_function('$string', ' 1834 global $smcFunc; 1835 $words = preg_split(\'~([\s\r\n\t]+)~\', $string, -1, PREG_SPLIT_DELIM_CAPTURE); 1836 for ($i = 0, $n = count($words); $i < $n; $i += 2) 1837 $words[$i] = $smcFunc[\'ucfirst\']($words[$i]); 1838 return implode(\'\', $words);') : 'ucwords', 1839 ); 1840 1841 // setting the timezone is a requirement for some functions in PHP >= 5.1. 1842 if (isset($modSettings['default_timezone']) 1843 && function_exists('date_default_timezone_set')) { 1844 date_default_timezone_set($modSettings['default_timezone']); 1845 } 1846 1847 return true; 1848} 1849 1850/** 1851 * Loads the $user_info array 1852 * 1853 * Will take the user's member id and load the handy $user_info array. If no 1854 * user id is supplied, it will try the cookie, then the session to get a user id. 1855 * If that fails, the $user_info array will contain the fallback data for guests. 1856 * 1857 * @param int $id_member the member id 1858 * @return bool true when complete, failure is not an option 1859 * @since 0.1.0 1860 */ 1861function smfapi_loadUserSettings($id_member=0) 1862{ 1863 global $modSettings, $user_settings, $sourcedir, $smcFunc; 1864 global $cookiename, $user_info, $language; 1865 1866 if (0 == $id_member && isset($_COOKIE[$cookiename])) { 1867 // fix a security hole in PHP 4.3.9 and below... 1868 if (preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~i', $_COOKIE[$cookiename]) == 1) { 1869 list ($id_member, $password) = @unserialize($_COOKIE[$cookiename]); 1870 $id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0; 1871 } else { 1872 $id_member = 0; 1873 } 1874 } elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA']))) { 1875 // !!! perhaps we can do some more checking on this, such as on the first octet of the IP? 1876 list ($id_member, $password, $login_span) = @unserialize($_SESSION['login_' . $cookiename]); 1877 $id_member = !empty($id_member) && strlen($password) == 40 && $login_span > time() ? (int) $id_member : 0; 1878 } 1879 1880 // only load this stuff if the user isn't a guest. 1881 if ($id_member != 0) { 1882 // is the member data cached? 1883 if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 1884 || ($user_settings = smfapi_cacheGetData('user_settings-' . $id_member, 60)) == null) { 1885 $request = $smcFunc['db_query']('', ' 1886 SELECT mem.*, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type 1887 FROM {db_prefix}members AS mem 1888 LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member}) 1889 WHERE mem.id_member = {int:id_member} 1890 LIMIT 1', 1891 array( 1892 'id_member' => $id_member, 1893 ) 1894 ); 1895 1896 $user_settings = $smcFunc['db_fetch_assoc']($request); 1897 $smcFunc['db_free_result']($request); 1898 1899 if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2) { 1900 smfapi_cachePutData('user_settings-' . $id_member, $user_settings, 60); 1901 } 1902 } 1903 } 1904 1905 // found 'im, let's set up the variables. 1906 if (0 != $id_member) { 1907 $username = $user_settings['member_name']; 1908 1909 if (empty($user_settings['additional_groups'])) { 1910 $user_info = array( 1911 'groups' => array($user_settings['id_group'], $user_settings['id_post_group']) 1912 ); 1913 } else { 1914 $user_info = array( 1915 'groups' => array_merge( 1916 array($user_settings['id_group'], $user_settings['id_post_group']), 1917 explode(',', $user_settings['additional_groups']) 1918 ) 1919 ); 1920 } 1921 1922 // because history has proven that it is possible for groups to go bad - clean up in case. 1923 foreach ($user_info['groups'] as $k => $v) { 1924 $user_info['groups'][$k] = (int) $v; 1925 } 1926 1927 // this is a logged in user, so definitely not a spider. 1928 $user_info['possibly_robot'] = false; 1929 } else { 1930 // this is what a guest's variables should be. 1931 $username = ''; 1932 $user_info = array('groups' => array(-1)); 1933 $user_settings = array(); 1934 $user_info['possibly_robot'] = false; 1935 } 1936 1937 // set up the $user_info array. 1938 $user_info += array( 1939 'id' => $id_member, 1940 'username' => $username, 1941 'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '', 1942 'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '', 1943 'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '', 1944 'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'], 1945 'is_guest' => $id_member == 0, 1946 'is_admin' => in_array(1, $user_info['groups']), 1947 'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'], 1948 'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'], 1949 'ip' => $_SERVER['REMOTE_ADDR'], 1950 'ip2' => isset($_SERVER['BAN_CHECK_IP']) ? $_SERVER['BAN_CHECK_IP']: '', 1951 'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'], 1952 'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'], 1953 'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'], 1954 'avatar' => array( 1955 'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '', 1956 'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'], 1957 'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1, 1958 'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0 1959 ), 1960 'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '', 1961 'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'], 1962 'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'], 1963 'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'], 1964 'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(), 1965 'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(), 1966 'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(), 1967 'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0, 1968 'permissions' => array(), 1969 ); 1970 $user_info['groups'] = array_unique($user_info['groups']); 1971 1972 return true; 1973} 1974 1975/** 1976 * Attempt to start the session, unless it already has been 1977 * 1978 * Modifies some ini settings, starts the session if there isn't one already and 1979 * sets the session variables. 1980 * 1981 * @return bool true when complete, failure is not an option 1982 * @since 0.1.0 1983 */ 1984function smfapi_loadSession() 1985{ 1986 global $HTTP_SESSION_VARS, $modSettings, $boardurl, $sc; 1987 1988 // attempt to change a few PHP settings. 1989 @ini_set('session.use_cookies', true); 1990 @ini_set('session.use_only_cookies', false); 1991 @ini_set('url_rewriter.tags', ''); 1992 @ini_set('session.use_trans_sid', false); 1993 @ini_set('arg_separator.output', '&'); 1994 1995 if (!empty($modSettings['globalCookies'])) { 1996 $parsed_url = parse_url($boardurl); 1997 1998 if (preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 1999 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) { 2000 @ini_set('session.cookie_domain', '.' . $parts[1]); 2001 } 2002 } 2003 2004 // if it's already been started... probably best to skip this. 2005 if ('' == session_id()) { 2006 2007 // this is here to stop people from using bad junky PHPSESSIDs. 2008 if (isset($_REQUEST[session_name()]) 2009 && preg_match('~^[A-Za-z0-9]{16,32}$~', $_REQUEST[session_name()]) == 0 2010 && !isset($_COOKIE[session_name()])) { 2011 2012 $session_id = md5(md5('smf_sess_' . time()) . mt_rand()); 2013 $_REQUEST[session_name()] = $session_id; 2014 $_GET[session_name()] = $session_id; 2015 $_POST[session_name()] = $session_id; 2016 } 2017 2018 // use database sessions? (they don't work in 4.1.x!) 2019 if (!empty($modSettings['databaseSession_enable']) 2020 && @version_compare(PHP_VERSION, '4.2.0') != -1) { 2021 2022 session_set_save_handler('smfapi_sessionOpen', 'smfapi_sessionClose', 2023 'smfapi_sessionRead', 'smfapi_sessionWrite', 2024 'smfapi_sessionDestroy', 'smfapi_sessionGC'); 2025 @ini_set('session.gc_probability', '1'); 2026 } elseif (@ini_get('session.gc_maxlifetime') <= 1440 2027 && !empty($modSettings['databaseSession_lifetime'])) { 2028 2029 @ini_set('session.gc_maxlifetime', max($modSettings['databaseSession_lifetime'], 60)); 2030 } 2031 2032 // use cache setting sessions? 2033 if (empty($modSettings['databaseSession_enable']) 2034 && !empty($modSettings['cache_enable']) && php_sapi_name() != 'cli') { 2035 2036 if (function_exists('mmcache_set_session_handlers')) { 2037 mmcache_set_session_handlers(); 2038 } elseif (function_exists('eaccelerator_set_session_handlers')) { 2039 eaccelerator_set_session_handlers(); 2040 } 2041 } 2042 2043 session_start(); 2044 2045 // change it so the cache settings are a little looser than default. 2046 if (!empty($modSettings['databaseSession_loose'])) { 2047 header('Cache-Control: private'); 2048 } 2049 } 2050 2051 // while PHP 4.1.x should use $_SESSION, it seems to need this to do it right. 2052 if (@version_compare(PHP_VERSION, '4.2.0') == -1) { 2053 $HTTP_SESSION_VARS['php_412_bugfix'] = true; 2054 } 2055 2056 // set the randomly generated code. 2057 if (!isset($_SESSION['session_value'])) { 2058 $_SESSION['session_value'] = md5(session_id() . mt_rand()); 2059 } 2060 2061 if (!isset($_SESSION['session_var'])) { 2062 $_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', sha1(mt_rand() 2063 . session_id() . mt_rand())), 0, rand(7, 12)); 2064 } 2065 2066 $sc = $_SESSION['session_value']; 2067 2068 return true; 2069} 2070 2071/** 2072 * Session open 2073 * 2074 * It doesn't do much :p 2075 * 2076 * @param string $save_path 2077 * @param string $session_name 2078 * @return true 2079 * @since 0.1.0 2080 */ 2081function smfapi_sessionOpen($save_path, $session_name) 2082{ 2083 return true; 2084} 2085 2086/** 2087 * Session close 2088 * 2089 * It doesn't do much :p 2090 * 2091 * @return true 2092 * @since 0.1.0 2093 */ 2094function smfapi_sessionClose() 2095{ 2096 return true; 2097} 2098 2099/** 2100 * Session read 2101 * 2102 * Loads the session data from the session code 2103 * 2104 * @param string $session_id the session code 2105 * @return array $sess_data the session data || bool false if the session code 2106 * isn't valid or there is no session data to return 2107 * @since 0.1.0 2108 */ 2109function smfapi_sessionRead($session_id) 2110{ 2111 global $smcFunc; 2112 2113 if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0) { 2114 return false; 2115 } 2116 2117 // look for it in the database. 2118 $result = $smcFunc['db_query']('', ' 2119 SELECT data 2120 FROM {db_prefix}sessions 2121 WHERE session_id = {string:session_id} 2122 LIMIT 1', 2123 array( 2124 'session_id' => $session_id, 2125 ) 2126 ); 2127 list ($sess_data) = $smcFunc['db_fetch_row']($result); 2128 $smcFunc['db_free_result']($result); 2129 2130 if (!empty($sess_data)) { 2131 return $sess_data; 2132 } 2133 2134 return false; 2135} 2136 2137/** 2138 * Session write 2139 * 2140 * Writes data into the session 2141 * 2142 * @param string $session_id the session code 2143 * @param string $data the data to write into the session 2144 * @return bool true when complete, false if the session code isn't valid 2145 * @since 0.1.0 2146 */ 2147function smfapi_sessionWrite($session_id, $data) 2148{ 2149 global $smcFunc; 2150 2151 if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0) { 2152 return false; 2153 } 2154 2155 // first try to update an existing row... 2156 $result = $smcFunc['db_query']('', ' 2157 UPDATE {db_prefix}sessions 2158 SET data = {string:data}, last_update = {int:last_update} 2159 WHERE session_id = {string:session_id}', 2160 array( 2161 'last_update' => time(), 2162 'data' => $data, 2163 'session_id' => $session_id, 2164 ) 2165 ); 2166 2167 // if that didn't work, try inserting a new one. 2168 if ($smcFunc['db_affected_rows']() == 0) { 2169 $result = $smcFunc['db_insert']('ignore', 2170 '{db_prefix}sessions', 2171 array('session_id' => 'string', 'data' => 'string', 'last_update' => 'int'), 2172 array($session_id, $data, time()), 2173 array('session_id') 2174 ); 2175 } 2176 2177 return true; 2178} 2179 2180/** 2181 * Session destroy 2182 * 2183 * Destroy a session 2184 * 2185 * @param string $session_id the session code 2186 * @return bool true || string db error || false if the session code isn't valid 2187 * @since 0.1.0 2188 */ 2189function smfapi_sessionDestroy($session_id) 2190{ 2191 global $smcFunc; 2192 2193 if (preg_match('~^[A-Za-z0-9]{16,32}$~', $session_id) == 0) { 2194 return false; 2195 } 2196 2197 // just delete the row... 2198 return $smcFunc['db_query']('', ' 2199 DELETE FROM {db_prefix}sessions 2200 WHERE session_id = {string:session_id}', 2201 array( 2202 'session_id' => $session_id, 2203 ) 2204 ); 2205} 2206 2207/** 2208 * Session garbage collection 2209 * 2210 * How long should the unupdated session remain in the db before we delete it? 2211 * 2212 * @param int $max_lifetime 2213 * @return bool true || string db error 2214 * @since 0.1.0 2215 */ 2216function smfapi_sessionGC($max_lifetime) 2217{ 2218 global $modSettings, $smcFunc; 2219 2220 // just set to the default or lower? ignore it for a higher value. (hopefully) 2221 if (!empty($modSettings['databaseSession_lifetime']) 2222 && ($max_lifetime <= 1440 2223 || $modSettings['databaseSession_lifetime'] > $max_lifetime)) { 2224 2225 $max_lifetime = max($modSettings['databaseSession_lifetime'], 60); 2226 } 2227 2228 // clean up ;). 2229 return $smcFunc['db_query']('', ' 2230 DELETE FROM {db_prefix}sessions 2231 WHERE last_update < {int:last_update}', 2232 array( 2233 'last_update' => time() - $max_lifetime, 2234 ) 2235 ); 2236} 2237 2238/** 2239 * Load the db connection 2240 * 2241 * Will add the db functions to $smcFunc array and set up and test the db connection 2242 * 2243 * @return bool if the db connection exists or not 2244 * @since 0.1.0 2245 */ 2246function smfapi_loadDatabase() 2247{ 2248 global $db_persist, $db_connection, $db_server, $db_user, $db_passwd; 2249 global $db_type, $db_name, $sourcedir, $db_prefix; 2250 2251 // figure out what type of database we are using. 2252 if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')) { 2253 $db_type = 'mysql'; 2254 } 2255 2256 // load the file for the database (safe to load) 2257 require_once($sourcedir . '/Subs-Db-' . $db_type . '.php'); 2258 2259 // make connection 2260 if (empty($db_connection)) { 2261 $db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, array('persist' => $db_persist, 'dont_select_db' => SMF == 'API')); 2262 } 2263 2264 // safe guard here, if there isn't a valid connection lets put a stop to it. 2265 if (!$db_connection) { 2266 return false; 2267 } 2268 2269 // defined in Subs-Db-*.php 2270 db_fix_prefix($db_prefix, $db_name); 2271 2272 return true; 2273} 2274 2275/** 2276 * Put data in the cache 2277 * 2278 * Adds data to whatever cache method we're using 2279 * 2280 * @param string $key the cache data identifier 2281 * @param mixed $value the value to be stored 2282 * @param int $ttl how long are we going to cache this data (in seconds) 2283 * @return void 2284 * @since 0.1.0 2285 */ 2286function smfapi_cachePutData($key, $value, $ttl = 120) 2287{ 2288 global $boardurl, $sourcedir, $modSettings, $memcached; 2289 global $cache_hits, $cache_count, $db_show_debug, $cachedir; 2290 2291 if (empty($modSettings['cache_enable']) && !empty($modSettings)) { 2292 return; 2293 } 2294 2295 $cache_count = isset($cache_count) ? $cache_count + 1 : 1; 2296 2297 if (isset($db_show_debug) && $db_show_debug === true) { 2298 $cache_hits[$cache_count] = array('k' => $key, 2299 'd' => 'put', 2300 's' => $value === null ? 0 : strlen(serialize($value))); 2301 $st = microtime(); 2302 } 2303 2304 $key = md5($boardurl . filemtime($sourcedir . '/Load.php')) 2305 . '-SMF-' . strtr($key, ':', '-'); 2306 $value = $value === null ? null : serialize($value); 2307 2308 // eAccelerator... 2309 if (function_exists('eaccelerator_put')) { 2310 if (mt_rand(0, 10) == 1) { 2311 eaccelerator_gc(); 2312 } 2313 2314 if ($value === null) { 2315 @eaccelerator_rm($key); 2316 } else { 2317 eaccelerator_put($key, $value, $ttl); 2318 } 2319 } 2320 // turck MMCache? 2321 elseif (function_exists('mmcache_put')) { 2322 if (mt_rand(0, 10) == 1) { 2323 mmcache_gc(); 2324 } 2325 2326 if ($value === null) { 2327 @mmcache_rm($key); 2328 } else { 2329 mmcache_put($key, $value, $ttl); 2330 } 2331 } 2332 // alternative PHP Cache, ahoy! 2333 elseif (function_exists('apc_store')) { 2334 // An extended key is needed to counteract a bug in APC. 2335 if ($value === null) { 2336 apc_delete($key . 'smf'); 2337 } else { 2338 apc_store($key . 'smf', $value, $ttl); 2339 } 2340 } 2341 // zend Platform/ZPS/etc. 2342 elseif (function_exists('output_cache_put')) { 2343 output_cache_put($key, $value); 2344 } elseif (function_exists('xcache_set') && ini_get('xcache.var_size') > 0) { 2345 if ($value === null) { 2346 xcache_unset($key); 2347 } else { 2348 xcache_set($key, $value, $ttl); 2349 } 2350 } 2351 // otherwise custom cache? 2352 else { 2353 if ($value === null) { 2354 @unlink($cachedir . '/data_' . $key . '.php'); 2355 } else { 2356 $cache_data = '<' . '?' . 'php if (!defined(\'SMF\')) die; if (' 2357 . (time() + $ttl) 2358 . ' < time()) $expired = true; else{$expired = false; $value = \'' 2359 . addcslashes($value, '\\\'') . '\';}' . '?' . '>'; 2360 $fh = @fopen($cachedir . '/data_' . $key . '.php', 'w'); 2361 2362 if ($fh) { 2363 // write the file. 2364 set_file_buffer($fh, 0); 2365 flock($fh, LOCK_EX); 2366 $cache_bytes = fwrite($fh, $cache_data); 2367 flock($fh, LOCK_UN); 2368 fclose($fh); 2369 2370 // check that the cache write was successful; all the data should be written 2371 // if it fails due to low diskspace, remove the cache file 2372 if ($cache_bytes != strlen($cache_data)) { 2373 @unlink($cachedir . '/data_' . $key . '.php'); 2374 } 2375 } 2376 } 2377 } 2378 2379 if (isset($db_show_debug) && $db_show_debug === true) { 2380 $cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); 2381 } 2382 2383 return; 2384} 2385 2386/** 2387 * Get data from the cache 2388 * 2389 * Get data from the cache that matches this key, if it exists 2390 * 2391 * @param string $key the cache data identifier 2392 * @param int $ttl how long will the data be considered fresh (in seconds) 2393 * @return mixed $value the cache data or null 2394 * @since 0.1.0 2395 */ 2396function smfapi_cacheGetData($key, $ttl = 120) 2397{ 2398 global $boardurl, $sourcedir, $modSettings, $memcached; 2399 global $cache_hits, $cache_count, $db_show_debug, $cachedir; 2400 2401 if (empty($modSettings['cache_enable']) && !empty($modSettings)) { 2402 return; 2403 } 2404 2405 $cache_count = isset($cache_count) ? $cache_count + 1 : 1; 2406 2407 if (isset($db_show_debug) && $db_show_debug === true) { 2408 $cache_hits[$cache_count] = array('k' => $key, 'd' => 'get'); 2409 $st = microtime(); 2410 } 2411 2412 $key = md5($boardurl . filemtime($sourcedir . '/Load.php')) 2413 . '-SMF-' . strtr($key, ':', '-'); 2414 2415 // again, eAccelerator. 2416 if (function_exists('eaccelerator_get')) { 2417 $value = eaccelerator_get($key); 2418 } 2419 // the older, but ever-stable, Turck MMCache... 2420 elseif (function_exists('mmcache_get')) { 2421 $value = mmcache_get($key); 2422 } 2423 // this is the free APC from PECL. 2424 elseif (function_exists('apc_fetch')) { 2425 $value = apc_fetch($key . 'smf'); 2426 } 2427 // zend's pricey stuff. 2428 elseif (function_exists('output_cache_get')) { 2429 $value = output_cache_get($key, $ttl); 2430 } elseif (function_exists('xcache_get') && ini_get('xcache.var_size') > 0) { 2431 $value = xcache_get($key); 2432 } 2433 // otherwise it's SMF data! 2434 elseif (file_exists($cachedir . '/data_' . $key . '.php') 2435 && filesize($cachedir . '/data_' . $key . '.php') > 10) { 2436 2437 require($cachedir . '/data_' . $key . '.php'); 2438 2439 if (!empty($expired) && isset($value)) { 2440 @unlink($cachedir . '/data_' . $key . '.php'); 2441 unset($value); 2442 } 2443 } 2444 2445 if (isset($db_show_debug) && $db_show_debug === true) { 2446 $cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st)); 2447 $cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0; 2448 } 2449 2450 if (empty($value)) { 2451 return null; 2452 } 2453 // if it's broke, it's broke... so give up on it. 2454 else { 2455 return @unserialize($value); 2456 } 2457} 2458 2459/** 2460 * Update member data 2461 * 2462 * Update member data such as 'passwd' (hash), 'email_address', 'is_activated' 2463 * 'password_salt', 'member_name' or any other user info in the db 2464 * 2465 * @param int $member member id (will also work with string username or email) 2466 * @param associative array $data the data to be updated (htmlspecialchar'd) 2467 * @return bool whether update was successful or not 2468 * @since 0.1.0 2469 */ 2470function smfapi_updateMemberData($member='', $data='') 2471{ 2472 global $modSettings, $user_info, $smcFunc; 2473 2474 if ('' == $member || '' == $data) { 2475 return false; 2476 } 2477 2478 $user_data = smfapi_getUserData($member); 2479 2480 if (!$user_data) { 2481 $member = $user_info['id']; 2482 } elseif (isset($user_data['id_member'])) { 2483 $member = $user_data['id_member']; 2484 } else { 2485 return false; 2486 } 2487 2488 $parameters = array(); 2489 $condition = 'id_member = {int:member}'; 2490 $parameters['member'] = $member; 2491 2492 // everything is assumed to be a string unless it's in the below. 2493 $knownInts = array( 2494 'date_registered', 'posts', 'id_group', 'last_login', 'instant_messages', 'unread_messages', 2495 'new_pm', 'pm_prefs', 'gender', 'hide_email', 'show_online', 'pm_email_notify', 'pm_receive_from', 'karma_good', 'karma_bad', 2496 'notify_announcements', 'notify_send_body', 'notify_regularity', 'notify_types', 2497 'id_theme', 'is_activated', 'id_msg_last_visit', 'id_post_group', 'total_time_logged_in', 'warning', 2498 ); 2499 $knownFloats = array( 2500 'time_offset', 2501 ); 2502 2503 $setString = ''; 2504 foreach ($data as $var => $val) { 2505 $type = 'string'; 2506 if (in_array($var, $knownInts)) { 2507 $type = 'int'; 2508 } elseif (in_array($var, $knownFloats)) { 2509 $type = 'float'; 2510 } 2511 2512 $setString .= ' ' . $var . ' = {' . $type . ':p_' . $var . '},'; 2513 $parameters['p_' . $var] = $val; 2514 } 2515 2516 $smcFunc['db_query']('', ' 2517 UPDATE {db_prefix}members 2518 SET' . substr($setString, 0, -1) . ' 2519 WHERE ' . $condition, 2520 $parameters 2521 ); 2522 2523 // clear any caching? 2524 if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 2525 && !empty($members)) { 2526 2527 if ($modSettings['cache_enable'] >= 3) { 2528 smfapi_cachePutData('member_data-profile-' . $member, null, 120); 2529 smfapi_cachePutData('member_data-normal-' . $member, null, 120); 2530 smfapi_cachePutData('member_data-minimal-' . $member, null, 120); 2531 } 2532 smfapi_cachePutData('user_settings-' . $member, null, 60); 2533 } 2534 2535 return true; 2536} 2537 2538/** 2539 * Create random seed 2540 * 2541 * Generate a random seed and ensure it's stored in settings 2542 * 2543 * @return bool true when complete 2544 * @since 0.1.0 2545 */ 2546function smfapi_smfSeedGenerator() 2547{ 2548 global $modSettings; 2549 2550 // never existed? 2551 if (empty($modSettings['rand_seed'])) { 2552 $modSettings['rand_seed'] = microtime() * 1000000; 2553 smfapi_updateSettings(array('rand_seed' => $modSettings['rand_seed'])); 2554 } 2555 2556 if (@version_compare(PHP_VERSION, '4.2.0') == -1) { 2557 $seed = ($modSettings['rand_seed'] + ((double) microtime() * 1000003)) & 0x7fffffff; 2558 mt_srand($seed); 2559 } 2560 2561 // update the settings with the new seed 2562 smfapi_updateSettings(array('rand_seed' => mt_rand())); 2563 2564 return true; 2565} 2566 2567/** 2568 * Update SMF settings 2569 * 2570 * Updates settings in the $modSettings array and stores them in the db. Also 2571 * clears the modSettings cache 2572 * 2573 * @param associative array $changeArray the settings to update 2574 * @param bool $update whether to update or replace in db 2575 * @param bool $debug deprecated 2576 * @return bool whether settings were changed or not 2577 * @since 0.1.0 2578 */ 2579function smfapi_updateSettings($changeArray, $update = false, $debug = false) 2580{ 2581 global $modSettings, $smcFunc; 2582 2583 if (empty($changeArray) || !is_array($changeArray)) { 2584 return false; 2585 } 2586 2587 // in some cases, this may be better and faster, but for large sets we don't want so many UPDATEs. 2588 if ($update) { 2589 foreach ($changeArray as $variable => $value) { 2590 $smcFunc['db_query']('', ' 2591 UPDATE {db_prefix}settings 2592 SET value = {' . ($value === false || $value === true ? 'raw' : 'string') . ':value} 2593 WHERE variable = {string:variable}', 2594 array( 2595 'value' => $value === true ? 'value + 1' : ($value === false ? 'value - 1' : $value), 2596 'variable' => $variable, 2597 ) 2598 ); 2599 $modSettings[$variable] = $value === true ? $modSettings[$variable] + 1 : ($value === false ? $modSettings[$variable] - 1 : $value); 2600 } 2601 2602 // clean out the cache and make sure the cobwebs are gone too 2603 smfapi_cachePutData('modSettings', null, 90); 2604 2605 return true; 2606 } 2607 2608 $replaceArray = array(); 2609 foreach ($changeArray as $variable => $value) { 2610 // don't bother if it's already like that ;). 2611 if (isset($modSettings[$variable]) && $modSettings[$variable] == $value) { 2612 continue; 2613 } 2614 // if the variable isn't set, but would only be set to nothing'ness, then don't bother setting it. 2615 elseif (!isset($modSettings[$variable]) && empty($value)) { 2616 continue; 2617 } 2618 2619 $replaceArray[] = array($variable, $value); 2620 2621 $modSettings[$variable] = $value; 2622 } 2623 2624 if (empty($replaceArray)) { 2625 return false; 2626 } 2627 2628 $smcFunc['db_insert']('replace', 2629 '{db_prefix}settings', 2630 array('variable' => 'string-255', 'value' => 'string-65534'), 2631 $replaceArray, 2632 array('variable') 2633 ); 2634 2635 // clear the cache of modsettings data 2636 smfapi_cachePutData('modSettings', null, 90); 2637 2638 return true; 2639} 2640 2641/** 2642 * Sets the login cookie 2643 * 2644 * Refreshes old cookie and session or creates new ones 2645 * 2646 * @param int $cookie_length cookie length in seconds 2647 * @param int $id the user's member id 2648 * @param string $password the password already hashed with SMF's encryption 2649 * @return true on completion 2650 * @since 0.1.0 2651 */ 2652function smfapi_setLoginCookie($cookie_length, $id, $password = '') 2653{ 2654 global $cookiename, $boardurl, $modSettings; 2655 2656 // if changing state force them to re-address some permission caching 2657 $_SESSION['mc']['time'] = 0; 2658 2659 // the cookie may already exist, and have been set with different options 2660 $cookie_state = (empty($modSettings['localCookies']) ? 0 : 1) | (empty($modSettings['globalCookies']) ? 0 : 2); 2661 2662 if (isset($_COOKIE[$cookiename]) 2663 && preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~', $_COOKIE[$cookiename]) === 1) { 2664 $array = @unserialize($_COOKIE[$cookiename]); 2665 2666 // out with the old, in with the new 2667 if (isset($array[3]) && $array[3] != $cookie_state) { 2668 $cookie_url = smfapi_urlParts($array[3] & 1 > 0, $array[3] & 2 > 0); 2669 setcookie($cookiename, serialize(array(0, '', 0)), time() - 3600, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); 2670 } 2671 } 2672 2673 // get the data and path to set it on 2674 $data = serialize(empty($id) ? array(0, '', 0) : array($id, $password, time() + $cookie_length, $cookie_state)); 2675 $cookie_url = smfapi_urlParts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); 2676 2677 // set the cookie, $_COOKIE, and session variable 2678 setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); 2679 2680 // if subdomain-independent cookies are on, unset the subdomain-dependent cookie too 2681 if (empty($id) && !empty($modSettings['globalCookies'])) { 2682 setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], '', !empty($modSettings['secureCookies'])); 2683 } 2684 2685 // any alias URLs? this is mainly for use with frames, etc 2686 if (!empty($modSettings['forum_alias_urls'])) { 2687 $aliases = explode(',', $modSettings['forum_alias_urls']); 2688 2689 $temp = $boardurl; 2690 foreach ($aliases as $alias) { 2691 // fake the $boardurl so we can set a different cookie 2692 $alias = strtr(trim($alias), array('http://' => '', 'https://' => '')); 2693 $boardurl = 'http://' . $alias; 2694 2695 $cookie_url = smfapi_urlParts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])); 2696 2697 if ($cookie_url[0] == '') { 2698 $cookie_url[0] = strtok($alias, '/'); 2699 } 2700 2701 setcookie($cookiename, $data, time() + $cookie_length, $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); 2702 } 2703 2704 $boardurl = $temp; 2705 } 2706 2707 $_COOKIE[$cookiename] = $data; 2708 2709 // make sure the user logs in with a new session ID 2710 if (!isset($_SESSION['login_' . $cookiename]) 2711 || $_SESSION['login_' . $cookiename] !== $data) { 2712 2713 // version 4.3.2 didn't store the cookie of the new session 2714 if (version_compare(PHP_VERSION, '4.3.2') === 0) { 2715 $sessionCookieLifetime = @ini_get('session.cookie_lifetime'); 2716 setcookie(session_name(), session_id(), time() + (empty($sessionCookieLifetime) ? $cookie_length : $sessionCookieLifetime), $cookie_url[1], $cookie_url[0], !empty($modSettings['secureCookies'])); 2717 } 2718 2719 $_SESSION['login_' . $cookiename] = $data; 2720 } 2721 2722 return true; 2723} 2724 2725/** 2726 * Session regenerate id 2727 * 2728 * Regenerate the session id. In case PHP version < 4.3.2 2729 * 2730 * @return bool whether session id was regenerated or not 2731 * @since 0.1.0 2732 */ 2733if (!function_exists('session_regenerate_id')) { 2734 2735 function session_regenerate_id() 2736 { 2737 // too late to change the session now 2738 if (headers_sent()) { 2739 return false; 2740 } else { 2741 session_id(strtolower(md5(uniqid(mt_rand(), true)))); 2742 } 2743 2744 return true; 2745 } 2746} 2747 2748/** 2749 * Break the url into pieces 2750 * 2751 * Gets the domain and path for the cookie 2752 * 2753 * @param bool $local whether local cookies are on 2754 * @param bool $global whether global cookies are on 2755 * @return array with domain and path for the cookie to set using 2756 * @since 0.1.0 2757 */ 2758function smfapi_urlParts($local, $global) 2759{ 2760 global $boardurl; 2761 2762 // parse the URL with PHP to make life easier 2763 $parsed_url = parse_url($boardurl); 2764 2765 // are local cookies off? 2766 if (empty($parsed_url['path']) || !$local) { 2767 $parsed_url['path'] = ''; 2768 } 2769 2770 // globalize cookies across domains (filter out IP-addresses)? 2771 if ($global && preg_match('~^\d{1,3}(\.\d{1,3}){3}$~', $parsed_url['host']) == 0 2772 && preg_match('~(?:[^\.]+\.)?([^\.]{2,}\..+)\z~i', $parsed_url['host'], $parts) == 1) { 2773 2774 $parsed_url['host'] = '.' . $parts[1]; 2775 } 2776 // we shouldn't use a host at all if both options are off 2777 elseif (!$local && !$global) { 2778 $parsed_url['host'] = ''; 2779 } 2780 // the host also shouldn't be set if there aren't any dots in it 2781 elseif (!isset($parsed_url['host']) || strpos($parsed_url['host'], '.') === false) { 2782 $parsed_url['host'] = ''; 2783 } 2784 2785 return array($parsed_url['host'], $parsed_url['path'] . '/'); 2786} 2787 2788/** 2789 * Update forum statistics for new member registration 2790 * 2791 * Updates latest member || updates member counts 2792 * 2793 * @param string $type the type of update (we're only doing member) 2794 * @param int $parameter1 the user's member id || null 2795 * @param string $parameter2 the user's real name || null 2796 * @return bool whether stats were updated or not 2797 * @since 0.1.0 2798 */ 2799function smfapi_updateStats($type='member', $parameter1 = null, $parameter2 = null) 2800{ 2801 global $sourcedir, $modSettings, $smcFunc; 2802 2803 switch ($type) { 2804 2805 case 'member': 2806 $changes = array( 2807 'memberlist_updated' => time(), 2808 ); 2809 2810 // #1 latest member ID, #2 the real name for a new registration 2811 if (is_numeric($parameter1)) { 2812 $changes['latestMember'] = $parameter1; 2813 $changes['latestRealName'] = $parameter2; 2814 2815 smfapi_updateSettings(array('totalMembers' => true), true); 2816 } 2817 // we need to calculate the totals. 2818 else { 2819 // Update the latest activated member (highest id_member) and count. 2820 $result = $smcFunc['db_query']('', ' 2821 SELECT COUNT(*), MAX(id_member) 2822 FROM {db_prefix}members 2823 WHERE is_activated = {int:is_activated}', 2824 array( 2825 'is_activated' => 1, 2826 ) 2827 ); 2828 list ($changes['totalMembers'], $changes['latestMember']) = $smcFunc['db_fetch_row']($result); 2829 $smcFunc['db_free_result']($result); 2830 2831 // Get the latest activated member's display name. 2832 $result = $smcFunc['db_query']('', ' 2833 SELECT real_name 2834 FROM {db_prefix}members 2835 WHERE id_member = {int:id_member} 2836 LIMIT 1', 2837 array( 2838 'id_member' => (int) $changes['latestMember'], 2839 ) 2840 ); 2841 list ($changes['latestRealName']) = $smcFunc['db_fetch_row']($result); 2842 $smcFunc['db_free_result']($result); 2843 2844 // Are we using registration approval? 2845 if ((!empty($modSettings['registration_method']) 2846 && $modSettings['registration_method'] == 2) 2847 || !empty($modSettings['approveAccountDeletion'])) { 2848 2849 // Update the amount of members awaiting approval - ignoring COPPA accounts, as you can't approve them until you get permission. 2850 $result = $smcFunc['db_query']('', ' 2851 SELECT COUNT(*) 2852 FROM {db_prefix}members 2853 WHERE is_activated IN ({array_int:activation_status})', 2854 array( 2855 'activation_status' => array(3, 4), 2856 ) 2857 ); 2858 list ($changes['unapprovedMembers']) = $smcFunc['db_fetch_row']($result); 2859 $smcFunc['db_free_result']($result); 2860 } 2861 } 2862 2863 smfapi_updateSettings($changes); 2864 break; 2865 2866 default: 2867 return false; 2868 } 2869 2870 return true; 2871} 2872 2873/** 2874 * Removes special entities from strings 2875 * 2876 * Fixes strings with special characters for compatibility with db 2877 * 2878 * @param string $string the input string to be cleaned 2879 * @return string the string with special entities removed 2880 * @since 0.1.0 2881 */ 2882function smfapi_unHtmlspecialchars($string) 2883{ 2884 static $translation; 2885 2886 if (!isset($translation)) { 2887 $translation = array_flip(get_html_translation_table(HTML_SPECIALCHARS, ENT_QUOTES)) + array(''' => '\'', ' ' => ' '); 2888 } 2889 2890 return strtr($string, $translation); 2891} 2892 2893/** 2894 * Delete personal messages 2895 * 2896 * Deletes the personal messages for a member or an array of members. 2897 * Called by the delete member function 2898 * 2899 * @param array $personal_messages the messages to delete 2900 * @param string $folder the folder to delete from 2901 * @param int || array $owner the member id(s) that need message deletion 2902 * @return bool whether deletion occurred or not 2903 * @since 0.1.0 2904 */ 2905function smfapi_deleteMessages($personal_messages, $folder = null, $owner = null) 2906{ 2907 global $user_info, $smcFunc; 2908 2909 if ($owner === null) { 2910 $owner = array($user_info['id']); 2911 } elseif (empty($owner)) { 2912 return false; 2913 } elseif (!is_array($owner)) { 2914 $owner = array($owner); 2915 } 2916 2917 if (null !== $personal_messages) { 2918 if (empty($personal_messages) || !is_array($personal_messages)) { 2919 return false; 2920 } 2921 2922 foreach ($personal_messages as $index => $delete_id) { 2923 $personal_messages[$index] = (int) $delete_id; 2924 } 2925 2926 $where = 'AND id_pm IN ({array_int:pm_list})'; 2927 } else { 2928 $where = ''; 2929 } 2930 2931 if ('sent' == $folder || null === $folder) { 2932 $smcFunc['db_query']('', ' 2933 UPDATE {db_prefix}personal_messages 2934 SET deleted_by_sender = {int:is_deleted} 2935 WHERE id_member_from IN ({array_int:member_list}) 2936 AND deleted_by_sender = {int:not_deleted}' . $where, 2937 array( 2938 'member_list' => $owner, 2939 'is_deleted' => 1, 2940 'not_deleted' => 0, 2941 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), 2942 ) 2943 ); 2944 } 2945 2946 if ('sent' != $folder || null === $folder) { 2947 // calculate the number of messages each member's gonna lose... 2948 $request = $smcFunc['db_query']('', ' 2949 SELECT id_member, COUNT(*) AS num_deleted_messages, CASE WHEN is_read & 1 >= 1 THEN 1 ELSE 0 END AS is_read 2950 FROM {db_prefix}pm_recipients 2951 WHERE id_member IN ({array_int:member_list}) 2952 AND deleted = {int:not_deleted}' . $where . ' 2953 GROUP BY id_member, is_read', 2954 array( 2955 'member_list' => $owner, 2956 'not_deleted' => 0, 2957 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), 2958 ) 2959 ); 2960 // ...and update the statistics accordingly - now including unread messages 2961 while ($row = $smcFunc['db_fetch_assoc']($request)) { 2962 if ($row['is_read']) { 2963 smfapi_updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' 2964 . $row['num_deleted_messages'])); 2965 } else { 2966 smfapi_updateMemberData($row['id_member'], array('instant_messages' => $where == '' ? 0 : 'instant_messages - ' 2967 . $row['num_deleted_messages'], 'unread_messages' => $where == '' ? 0 : 'unread_messages - ' 2968 . $row['num_deleted_messages'])); 2969 } 2970 2971 // if this is the current member we need to make their message count correct 2972 if ($user_info['id'] == $row['id_member']) { 2973 $user_info['messages'] -= $row['num_deleted_messages']; 2974 if (!($row['is_read'])) 2975 $user_info['unread_messages'] -= $row['num_deleted_messages']; 2976 } 2977 } 2978 2979 $smcFunc['db_free_result']($request); 2980 2981 // do the actual deletion 2982 $smcFunc['db_query']('', ' 2983 UPDATE {db_prefix}pm_recipients 2984 SET deleted = {int:is_deleted} 2985 WHERE id_member IN ({array_int:member_list}) 2986 AND deleted = {int:not_deleted}' . $where, 2987 array( 2988 'member_list' => $owner, 2989 'is_deleted' => 1, 2990 'not_deleted' => 0, 2991 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), 2992 ) 2993 ); 2994 } 2995 2996 // if sender and recipients all have deleted their message, it can be removed 2997 $request = $smcFunc['db_query']('', ' 2998 SELECT pm.id_pm AS sender, pmr.id_pm 2999 FROM {db_prefix}personal_messages AS pm 3000 LEFT JOIN {db_prefix}pm_recipients AS pmr ON (pmr.id_pm = pm.id_pm AND pmr.deleted = {int:not_deleted}) 3001 WHERE pm.deleted_by_sender = {int:is_deleted} 3002 ' . str_replace('id_pm', 'pm.id_pm', $where) . ' 3003 GROUP BY sender, pmr.id_pm 3004 HAVING pmr.id_pm IS null', 3005 array( 3006 'not_deleted' => 0, 3007 'is_deleted' => 1, 3008 'pm_list' => $personal_messages !== null ? array_unique($personal_messages) : array(), 3009 ) 3010 ); 3011 3012 $remove_pms = array(); 3013 3014 while ($row = $smcFunc['db_fetch_assoc']($request)) { 3015 $remove_pms[] = $row['sender']; 3016 } 3017 3018 $smcFunc['db_free_result']($request); 3019 3020 if (!empty($remove_pms)) { 3021 $smcFunc['db_query']('', ' 3022 DELETE FROM {db_prefix}personal_messages 3023 WHERE id_pm IN ({array_int:pm_list})', 3024 array( 3025 'pm_list' => $remove_pms, 3026 ) 3027 ); 3028 3029 $smcFunc['db_query']('', ' 3030 DELETE FROM {db_prefix}pm_recipients 3031 WHERE id_pm IN ({array_int:pm_list})', 3032 array( 3033 'pm_list' => $remove_pms, 3034 ) 3035 ); 3036 } 3037 3038 // any cached numbers may be wrong now 3039 smfapi_cachePutData('labelCounts:' . $user_info['id'], null, 720); 3040 3041 return true; 3042} 3043 3044/** 3045 * Generate validation code 3046 * 3047 * Generate a random validation code for registration purposes 3048 * 3049 * @return string random validation code (10 char) 3050 * @since 0.1.0 3051 */ 3052function smfapi_generateValidationCode() 3053{ 3054 global $smcFunc, $modSettings; 3055 3056 $request = $smcFunc['db_query']('get_random_number', ' 3057 SELECT RAND()', 3058 array( 3059 ) 3060 ); 3061 3062 list ($dbRand) = $smcFunc['db_fetch_row']($request); 3063 $smcFunc['db_free_result']($request); 3064 3065 return substr(preg_replace('/\W/', '', sha1(microtime() 3066 . mt_rand() . $dbRand . $modSettings['rand_seed'])), 0, 10); 3067} 3068 3069/** 3070 * Check if user is online 3071 * 3072 * Checks whether the specified user is online or not 3073 * 3074 * @param string || int $username the username, member id or email of the user 3075 * @return bool whether the user is online or not 3076 * @since 0.1.0 3077 */ 3078function smfapi_isOnline($username='') 3079{ 3080 global $smcFunc; 3081 3082 $user_data = smfapi_getUserData($username); 3083 3084 if (!$user_data) { 3085 return false; 3086 } 3087 3088 $request = $smcFunc['db_query']('', ' 3089 SELECT lo.id_member 3090 FROM {db_prefix}log_online AS lo 3091 WHERE lo.id_member = {int:id_member}', 3092 array( 3093 'id_member' => $user_data['id_member'], 3094 ) 3095 ); 3096 3097 if ($smcFunc['db_num_rows']($request) == 0) { 3098 return false; 3099 } else { 3100 $smcFunc['db_free_result']($request); 3101 return true; 3102 } 3103} 3104 3105/** 3106 * Log user online 3107 * 3108 * Logs a user online, if their settings allow it 3109 * 3110 * @param string || int $username the username, member id or email of the user 3111 * @return bool whether they were logged online or not 3112 * @since 0.1.0 3113 */ 3114function smfapi_logOnline($username='') 3115{ 3116 global $smcFunc, $modSettings; 3117 3118 $user_data = smfapi_getUserData($username); 3119 3120 if (!$user_data) { 3121 return false; 3122 } 3123 3124 if (!$user_data['show_online']) { 3125 return false; 3126 } 3127 3128 if (smfapi_isOnline($username)) { 3129 $do_delete = true; 3130 } else { 3131 $do_delete = false; 3132 } 3133 3134 $smcFunc['db_query']('', ' 3135 DELETE FROM {db_prefix}log_online 3136 WHERE ' . ($do_delete ? 'log_time < {int:log_time}' : '') 3137 . ($do_delete && !empty($user_data['id_member']) ? ' OR ' : '') 3138 . (empty($user_data['id_member']) ? '' : 'id_member = {int:current_member}'), 3139 array( 3140 'current_member' => $user_data['id_member'], 3141 'log_time' => time() - $modSettings['lastActive'] * 60, 3142 ) 3143 ); 3144 3145 $smcFunc['db_insert']($do_delete ? 'ignore' : 'replace', 3146 '{db_prefix}log_online', 3147 array('session' => 'string', 3148 'id_member' => 'int', 3149 'id_spider' => 'int', 3150 'log_time' => 'int', 3151 'ip' => 'raw', 3152 'url' => 'string'), 3153 array(session_id(), 3154 $user_data['id_member'], 3155 0, 3156 time(), 3157 'IFNULL(INET_ATON(\'' . (isset($user_data['ip']) ? $user_data['ip'] : '') . '\'), 0)', 3158 ''), 3159 array('session') 3160 ); 3161 3162 // Mark the session as being logged. 3163 $_SESSION['log_time'] = time(); 3164 3165 // Well, they are online now. 3166 if (empty($_SESSION['timeOnlineUpdated'])) { 3167 $_SESSION['timeOnlineUpdated'] = time(); 3168 } 3169 3170 return true; 3171} 3172 3173/** 3174 * Find a file 3175 * 3176 * Locates a file given directories to search 3177 * 3178 * @param array $files the files to search 3179 * @param string $search the file or filetype we're searching for 3180 * @return array $matches the matching files found 3181 * @since 0.1.0 3182 */ 3183function smfapi_getMatchingFile($files, $search) 3184{ 3185 $matches = array(); 3186 3187 // split to name and filetype 3188 if (strpos($search, ".")) { 3189 $baseexp = substr($search, 0, strpos($search, ".")); 3190 $typeexp = substr($search, strpos($search, ".") +1, strlen($search)); 3191 } else { 3192 $baseexp = $search; 3193 $typeexp = ""; 3194 } 3195 3196 // escape all regexp characters 3197 $baseexp = preg_quote($baseexp); 3198 $typeexp = preg_quote($typeexp); 3199 3200 // allow ? and * 3201 $baseexp = str_replace(array("\*", "\?"), array(".*", "."), $baseexp); 3202 $typeexp = str_replace(array("\*", "\?"), array(".*", "."), $typeexp); 3203 3204 // search for matches 3205 $i=0; 3206 foreach ($files as $file) { 3207 $filename = basename($file); 3208 3209 if (strpos($filename, '.')) { 3210 $base = substr($filename, 0, strpos($filename, '.')); 3211 $type = substr($filename, strpos($filename, '.') +1, strlen($filename)); 3212 } else { 3213 $base = $filename; 3214 $type = ''; 3215 } 3216 3217 if (preg_match("/^" . $baseexp . "$/i", $base) 3218 && preg_match("/^" . $typeexp . "$/i", $type)) { 3219 3220 $matches[$i] = $file; 3221 $i++; 3222 } 3223 } 3224 3225 return $matches; 3226} 3227 3228/** 3229 * Returns all the files of a directory and subdirectories 3230 * 3231 * @param string $directory the absolute path of directory to begin searching 3232 * @param array $exempt files to exclude 3233 * @param array $files the files found passed by reference 3234 * @return array $files the files found 3235 * @since 0.1.0 3236 */ 3237function smfapi_getDirectoryContents($directory, $exempt = array('.', '..'), &$files = array()) { 3238 $handle = opendir($directory); 3239 while (false !== ($resource = readdir($handle))) { 3240 if (!in_array(strtolower($resource), $exempt)) { 3241 if (is_dir($directory . $resource . '/')) { 3242 array_merge($files, 3243 smfapi_getDirectoryContents($directory . $resource . '/', $exempt, $files)); 3244 } else { 3245 $files[] = $directory . $resource; 3246 } 3247 } 3248 } 3249 3250 closedir($handle); 3251 3252 return $files; 3253} 3254?> 3255