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('<' => '&lt;', '>' => '&gt;', '"' => '&quot;'));
1649	$error_message = strtr($error_message, array('&lt;br /&gt;' => '<br />', '&lt;b&gt;' => '<strong>', '&lt;/b&gt;' => '</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('&' => '&amp;')) . '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 . ']|&nbsp;)+|(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+$~' . ($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') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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', '&amp;');
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('&#039;' => '\'', '&nbsp;' => ' ');
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