comparison forum/Sources/Profile-Modify.php @ 76:e3e11437ecea website

Add forum code
author Chris Cannam
date Sun, 07 Jul 2013 11:25:48 +0200
parents
children
comparison
equal deleted inserted replaced
75:72f59aa7e503 76:e3e11437ecea
1 <?php
2
3 /**
4 * Simple Machines Forum (SMF)
5 *
6 * @package SMF
7 * @author Simple Machines http://www.simplemachines.org
8 * @copyright 2011 Simple Machines
9 * @license http://www.simplemachines.org/about/smf/license.php BSD
10 *
11 * @version 2.0.2
12 */
13
14 if (!defined('SMF'))
15 die('Hacking attempt...');
16
17 /* This file has the primary job of showing and editing people's profiles.
18 It also allows the user to change some of their or another's preferences,
19 and such things. It uses the following functions:
20
21 void loadProfileFields(bool force_reload = false)
22 // !!!
23
24 void setupProfileContext(array fields)
25 // !!!
26
27 void saveProfileFields()
28 // !!!
29
30 void saveProfileChanges(array &profile_variables, array &errors, int id_member)
31 // !!!
32
33 void makeThemeChanges(int id_member, int id_theme)
34 // !!!
35
36 void makeNotificationChanges(int id_member)
37 // !!!
38
39 void makeCustomFieldChanges(int id_member, string area, bool sanitize = true)
40 // !!!
41
42 void editBuddies(int id_member)
43 // !!!
44
45 void editIgnoreList(int id_member)
46 // !!!
47
48 void account(int id_member)
49 // !!!
50
51 void forumProfile(int id_member)
52 // !!!
53
54 void pmprefs(int id_member)
55 // !!!
56
57 array getAvatars(string directory, int level)
58 // !!!
59
60 void theme(int id_member)
61 // !!!
62
63 void authentication(int id_member, bool saving = false)
64 // !!!
65
66 void notification(int id_member)
67 // !!!
68
69 int list_getTopicNotificationCount(int memID)
70 // !!!
71
72 array list_getTopicNotifications(int start, int items_per_page, string sort, int memID)
73 // !!!
74
75 array list_getBoardNotifications(int start, int items_per_page, string sort, int memID)
76 // !!!
77
78 void loadThemeOptions(int id_member)
79 // !!!
80
81 void ignoreboards(int id_member)
82 // !!!
83
84 bool profileLoadLanguages()
85 // !!!
86
87 bool profileLoadGroups()
88 // !!!
89
90 bool profileLoadSignatureData()
91 // !!!
92
93 bool profileLoadAvatarData()
94 // !!!
95
96 bool profileSaveGroups(mixed &value)
97 // !!!
98
99 mixed profileSaveAvatarData(array &value)
100 // !!!
101
102 mixed profileValidateSignature(mixed &value)
103 // !!!
104
105 bool profileValidateEmail(string email, int id_member = none)
106 // !!!
107
108 void profileReloadUser()
109 // !!!
110
111 void profileSendActivation()
112 // !!!
113
114 void groupMembership(int id_member)
115 // !!!
116
117 mixed groupMembership2(array profile_vars, array post_erros, int id_member)
118 // !!!
119
120 Adding new fields to the profile:
121 ---------------------------------------------------------------------------
122 // !!!
123 */
124
125 // This defines every profile field known to man.
126 function loadProfileFields($force_reload = false)
127 {
128 global $context, $profile_fields, $txt, $scripturl, $modSettings, $user_info, $old_profile, $smcFunc, $cur_profile, $language;
129
130 // Don't load this twice!
131 if (!empty($profile_fields) && !$force_reload)
132 return;
133
134 /* This horrific array defines all the profile fields in the whole world!
135 In general each "field" has one array - the key of which is the database column name associated with said field. Each item
136 can have the following attributes:
137
138 string $type: The type of field this is - valid types are:
139 - callback: This is a field which has its own callback mechanism for templating.
140 - check: A simple checkbox.
141 - hidden: This doesn't have any visual aspects but may have some validity.
142 - password: A password box.
143 - select: A select box.
144 - text: A string of some description.
145
146 string $label: The label for this item - default will be $txt[$key] if this isn't set.
147 string $subtext: The subtext (Small label) for this item.
148 int $size: Optional size for a text area.
149 array $input_attr: An array of text strings to be added to the input box for this item.
150 string $value: The value of the item. If not set $cur_profile[$key] is assumed.
151 string $permission: Permission required for this item (Excluded _any/_own subfix which is applied automatically).
152 function $input_validate: A runtime function which validates the element before going to the database. It is passed
153 the relevant $_POST element if it exists and should be treated like a reference.
154
155 Return types:
156 - true: Element can be stored.
157 - false: Skip this element.
158 - a text string: An error occured - this is the error message.
159
160 function $preload: A function that is used to load data required for this element to be displayed. Must return
161 true to be displayed at all.
162
163 string $cast_type: If set casts the element to a certain type. Valid types (bool, int, float).
164 string $save_key: If the index of this element isn't the database column name it can be overriden
165 with this string.
166 bool $is_dummy: If set then nothing is acted upon for this element.
167 bool $enabled: A test to determine whether this is even available - if not is unset.
168 string $link_with: Key which links this field to an overall set.
169
170 Note that all elements that have a custom input_validate must ensure they set the value of $cur_profile correct to enable
171 the changes to be displayed correctly on submit of the form.
172
173 */
174
175 $profile_fields = array(
176 'aim' => array(
177 'type' => 'text',
178 'label' => $txt['aim'],
179 'subtext' => $txt['your_aim'],
180 'size' => 24,
181 'value' => strtr(empty($cur_profile['aim']) ? '' : $cur_profile['aim'], '+', ' '),
182 'permission' => 'profile_extra',
183 'input_validate' => create_function('&$value', '
184 $value = strtr($value, \' \', \'+\');
185 return true;
186 '),
187 ),
188 'avatar_choice' => array(
189 'type' => 'callback',
190 'callback_func' => 'avatar_select',
191 // This handles the permissions too.
192 'preload' => 'profileLoadAvatarData',
193 'input_validate' => 'profileSaveAvatarData',
194 'save_key' => 'avatar',
195 ),
196 'bday1' => array(
197 'type' => 'callback',
198 'callback_func' => 'birthdate',
199 'permission' => 'profile_extra',
200 'preload' => create_function('', '
201 global $cur_profile, $context;
202
203 // Split up the birthdate....
204 list ($uyear, $umonth, $uday) = explode(\'-\', empty($cur_profile[\'birthdate\']) || $cur_profile[\'birthdate\'] == \'0001-01-01\' ? \'0000-00-00\' : $cur_profile[\'birthdate\']);
205 $context[\'member\'][\'birth_date\'] = array(
206 \'year\' => $uyear == \'0004\' ? \'0000\' : $uyear,
207 \'month\' => $umonth,
208 \'day\' => $uday,
209 );
210
211 return true;
212 '),
213 'input_validate' => create_function('&$value', '
214 global $profile_vars, $cur_profile;
215
216 if (isset($_POST[\'bday2\'], $_POST[\'bday3\']) && $value > 0 && $_POST[\'bday2\'] > 0)
217 {
218 // Set to blank?
219 if ((int) $_POST[\'bday3\'] == 1 && (int) $_POST[\'bday2\'] == 1 && (int) $value == 1)
220 $value = \'0001-01-01\';
221 else
222 $value = checkdate($value, $_POST[\'bday2\'], $_POST[\'bday3\'] < 4 ? 4 : $_POST[\'bday3\']) ? sprintf(\'%04d-%02d-%02d\', $_POST[\'bday3\'] < 4 ? 4 : $_POST[\'bday3\'], $_POST[\'bday1\'], $_POST[\'bday2\']) : \'0001-01-01\';
223 }
224 else
225 $value = \'0001-01-01\';
226
227 $profile_vars[\'birthdate\'] = $value;
228 $cur_profile[\'birthdate\'] = $value;
229 return false;
230 '),
231 ),
232 // Setting the birthdate the old style way?
233 'birthdate' => array(
234 'type' => 'hidden',
235 'permission' => 'profile_extra',
236 'input_validate' => create_function('&$value', '
237 global $cur_profile;
238 // !!! Should we check for this year and tell them they made a mistake :P? (based on coppa at least?)
239 if (preg_match(\'/(\d{4})[\-\., ](\d{2})[\-\., ](\d{2})/\', $value, $dates) === 1)
240 {
241 $value = checkdate($dates[2], $dates[3], $dates[1] < 4 ? 4 : $dates[1]) ? sprintf(\'%04d-%02d-%02d\', $dates[1] < 4 ? 4 : $dates[1], $dates[2], $dates[3]) : \'0001-01-01\';
242 return true;
243 }
244 else
245 {
246 $value = empty($cur_profile[\'birthdate\']) ? \'0001-01-01\' : $cur_profile[\'birthdate\'];
247 return false;
248 }
249 '),
250 ),
251 'date_registered' => array(
252 'type' => 'text',
253 'value' => empty($cur_profile['date_registered']) ? $txt['not_applicable'] : strftime('%Y-%m-%d', $cur_profile['date_registered'] + ($user_info['time_offset'] + $modSettings['time_offset']) * 3600),
254 'label' => $txt['date_registered'],
255 'log_change' => true,
256 'permission' => 'moderate_forum',
257 'input_validate' => create_function('&$value', '
258 global $txt, $user_info, $modSettings, $cur_profile, $context;
259
260 // Bad date! Go try again - please?
261 if (($value = strtotime($value)) === -1)
262 {
263 $value = $cur_profile[\'date_registered\'];
264 return $txt[\'invalid_registration\'] . \' \' . strftime(\'%d %b %Y \' . (strpos($user_info[\'time_format\'], \'%H\') !== false ? \'%I:%M:%S %p\' : \'%H:%M:%S\'), forum_time(false));
265 }
266 // As long as it doesn\'t equal "N/A"...
267 elseif ($value != $txt[\'not_applicable\'] && $value != strtotime(strftime(\'%Y-%m-%d\', $cur_profile[\'date_registered\'] + ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600)))
268 $value = $value - ($user_info[\'time_offset\'] + $modSettings[\'time_offset\']) * 3600;
269 else
270 $value = $cur_profile[\'date_registered\'];
271
272 return true;
273 '),
274 ),
275 'email_address' => array(
276 'type' => 'text',
277 'label' => $txt['email'],
278 'subtext' => $txt['valid_email'],
279 'log_change' => true,
280 'permission' => 'profile_identity',
281 'input_validate' => create_function('&$value', '
282 global $context, $old_profile, $context, $profile_vars, $sourcedir, $modSettings;
283
284 if (strtolower($value) == strtolower($old_profile[\'email_address\']))
285 return false;
286
287 $isValid = profileValidateEmail($value, $context[\'id_member\']);
288
289 // Do they need to revalidate? If so schedule the function!
290 if ($isValid === true && !empty($modSettings[\'send_validation_onChange\']) && !allowedTo(\'moderate_forum\'))
291 {
292 require_once($sourcedir . \'/Subs-Members.php\');
293 $profile_vars[\'validation_code\'] = generateValidationCode();
294 $profile_vars[\'is_activated\'] = 2;
295 $context[\'profile_execute_on_save\'][] = \'profileSendActivation\';
296 unset($context[\'profile_execute_on_save\'][\'reload_user\']);
297 }
298
299 return $isValid;
300 '),
301 ),
302 'gender' => array(
303 'type' => 'select',
304 'cast_type' => 'int',
305 'options' => 'return array(0 => \'\', 1 => $txt[\'male\'], 2 => $txt[\'female\']);',
306 'label' => $txt['gender'],
307 'permission' => 'profile_extra',
308 ),
309 'hide_email' => array(
310 'type' => 'check',
311 'value' => empty($cur_profile['hide_email']) ? true : false,
312 'label' => $txt['allow_user_email'],
313 'permission' => 'profile_identity',
314 'input_validate' => create_function('&$value', '
315 $value = $value == 0 ? 1 : 0;
316
317 return true;
318 '),
319 ),
320 'icq' => array(
321 'type' => 'text',
322 'label' => $txt['icq'],
323 'subtext' => $txt['your_icq'],
324 'size' => 24,
325 'permission' => 'profile_extra',
326 // Need to make sure ICQ doesn't equal 0.
327 'input_validate' => create_function('&$value', '
328 if (empty($value))
329 $value = \'\';
330 else
331 $value = (int) $value;
332 return true;
333 '),
334 ),
335 // Selecting group membership is a complicated one so we treat it separate!
336 'id_group' => array(
337 'type' => 'callback',
338 'callback_func' => 'group_manage',
339 'permission' => 'manage_membergroups',
340 'preload' => 'profileLoadGroups',
341 'log_change' => true,
342 'input_validate' => 'profileSaveGroups',
343 ),
344 'id_theme' => array(
345 'type' => 'callback',
346 'callback_func' => 'theme_pick',
347 'permission' => 'profile_extra',
348 'enabled' => $modSettings['theme_allow'] || allowedTo('admin_forum'),
349 'preload' => create_function('', '
350 global $smcFunc, $context, $cur_profile, $txt;
351
352 $request = $smcFunc[\'db_query\'](\'\', \'
353 SELECT value
354 FROM {db_prefix}themes
355 WHERE id_theme = {int:id_theme}
356 AND variable = {string:variable}
357 LIMIT 1\', array(
358 \'id_theme\' => $cur_profile[\'id_theme\'],
359 \'variable\' => \'name\',
360 )
361 );
362 list ($name) = $smcFunc[\'db_fetch_row\']($request);
363 $smcFunc[\'db_free_result\']($request);
364
365 $context[\'member\'][\'theme\'] = array(
366 \'id\' => $cur_profile[\'id_theme\'],
367 \'name\' => empty($cur_profile[\'id_theme\']) ? $txt[\'theme_forum_default\'] : $name
368 );
369 return true;
370 '),
371 'input_validate' => create_function('&$value', '
372 $value = (int) $value;
373 return true;
374 '),
375 ),
376 'karma_good' => array(
377 'type' => 'callback',
378 'callback_func' => 'karma_modify',
379 'permission' => 'admin_forum',
380 // Set karma_bad too!
381 'input_validate' => create_function('&$value', '
382 global $profile_vars, $cur_profile;
383
384 $value = (int) $value;
385 if (isset($_POST[\'karma_bad\']))
386 {
387 $profile_vars[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0;
388 $cur_profile[\'karma_bad\'] = $_POST[\'karma_bad\'] != \'\' ? (int) $_POST[\'karma_bad\'] : 0;
389 }
390 return true;
391 '),
392 'preload' => create_function('', '
393 global $context, $cur_profile;
394
395 $context[\'member\'][\'karma\'][\'good\'] = $cur_profile[\'karma_good\'];
396 $context[\'member\'][\'karma\'][\'bad\'] = $cur_profile[\'karma_bad\'];
397
398 return true;
399 '),
400 'enabled' => !empty($modSettings['karmaMode']),
401 ),
402 'lngfile' => array(
403 'type' => 'select',
404 'options' => 'return $context[\'profile_languages\'];',
405 'label' => $txt['preferred_language'],
406 'permission' => 'profile_identity',
407 'preload' => 'profileLoadLanguages',
408 'enabled' => !empty($modSettings['userLanguage']),
409 'value' => empty($cur_profile['lngfile']) ? $language : $cur_profile['lngfile'],
410 'input_validate' => create_function('&$value', '
411 global $context, $cur_profile;
412
413 // Load the languages.
414 profileLoadLanguages();
415
416 if (isset($context[\'profile_languages\'][$value]))
417 {
418 if ($context[\'user\'][\'is_owner\'] && empty($context[\'password_auth_failed\']))
419 $_SESSION[\'language\'] = $value;
420 return true;
421 }
422 else
423 {
424 $value = $cur_profile[\'lngfile\'];
425 return false;
426 }
427 '),
428 ),
429 'location' => array(
430 'type' => 'text',
431 'label' => $txt['location'],
432 'log_change' => true,
433 'size' => 50,
434 'permission' => 'profile_extra',
435 ),
436 // The username is not always editable - so adjust it as such.
437 'member_name' => array(
438 'type' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? 'text' : 'label',
439 'label' => $txt['username'],
440 'subtext' => allowedTo('admin_forum') && !isset($_GET['changeusername']) ? '(<a href="' . $scripturl . '?action=profile;u=' . $context['id_member'] . ';area=account;changeusername" style="font-style: italic;">' . $txt['username_change'] . '</a>)' : '',
441 'log_change' => true,
442 'permission' => 'profile_identity',
443 'prehtml' => allowedTo('admin_forum') && isset($_GET['changeusername']) ? '<div class="alert">' . $txt['username_warning'] . '</div>' : '',
444 'input_validate' => create_function('&$value', '
445 global $sourcedir, $context, $user_info, $cur_profile;
446
447 if (allowedTo(\'admin_forum\'))
448 {
449 // We\'ll need this...
450 require_once($sourcedir . \'/Subs-Auth.php\');
451
452 // Maybe they are trying to change their password as well?
453 $resetPassword = true;
454 if (isset($_POST[\'passwrd1\']) && $_POST[\'passwrd1\'] != \'\' && isset($_POST[\'passwrd2\']) && $_POST[\'passwrd1\'] == $_POST[\'passwrd2\'] && validatePassword($_POST[\'passwrd1\'], $value, array($cur_profile[\'real_name\'], $user_info[\'username\'], $user_info[\'name\'], $user_info[\'email\'])) == null)
455 $resetPassword = false;
456
457 // Do the reset... this will send them an email too.
458 if ($resetPassword)
459 resetPassword($context[\'id_member\'], $value);
460 elseif ($value !== null)
461 {
462 validateUsername($context[\'id_member\'], $value);
463 updateMemberData($context[\'id_member\'], array(\'member_name\' => $value));
464 }
465 }
466 return false;
467 '),
468 ),
469 'msn' => array(
470 'type' => 'text',
471 'label' => $txt['msn'],
472 'subtext' => $txt['msn_email_address'],
473 'size' => 24,
474 'permission' => 'profile_extra',
475 'input_validate' => create_function('&$value', '
476 global $cur_profile;
477 // Make sure the msn one is an email address, not something like \'none\' :P.
478 if ($value != \'\' && preg_match(\'~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\\\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~\', $value) == 0)
479 {
480 $value = $cur_profile[\'msn\'];
481 return false;
482 }
483 return true;
484 '),
485 ),
486 'passwrd1' => array(
487 'type' => 'password',
488 'label' => $txt['choose_pass'],
489 'subtext' => $txt['password_strength'],
490 'size' => 20,
491 'value' => '',
492 'enabled' => empty($cur_profile['openid_uri']),
493 'permission' => 'profile_identity',
494 'save_key' => 'passwd',
495 // Note this will only work if passwrd2 also exists!
496 'input_validate' => create_function('&$value', '
497 global $sourcedir, $user_info, $smcFunc, $cur_profile;
498
499 // If we didn\'t try it then ignore it!
500 if ($value == \'\')
501 return false;
502
503 // Do the two entries for the password even match?
504 if (!isset($_POST[\'passwrd2\']) || $value != $_POST[\'passwrd2\'])
505 return \'bad_new_password\';
506
507 // Let\'s get the validation function into play...
508 require_once($sourcedir . \'/Subs-Auth.php\');
509 $passwordErrors = validatePassword($value, $cur_profile[\'member_name\'], array($cur_profile[\'real_name\'], $user_info[\'username\'], $user_info[\'name\'], $user_info[\'email\']));
510
511 // Were there errors?
512 if ($passwordErrors != null)
513 return \'password_\' . $passwordErrors;
514
515 // Set up the new password variable... ready for storage.
516 $value = sha1(strtolower($cur_profile[\'member_name\']) . un_htmlspecialchars($value));
517 return true;
518 '),
519 ),
520 'passwrd2' => array(
521 'type' => 'password',
522 'label' => $txt['verify_pass'],
523 'enabled' => empty($cur_profile['openid_uri']),
524 'size' => 20,
525 'value' => '',
526 'permission' => 'profile_identity',
527 'is_dummy' => true,
528 ),
529 'personal_text' => array(
530 'type' => 'text',
531 'label' => $txt['personal_text'],
532 'log_change' => true,
533 'input_attr' => array('maxlength="50"'),
534 'size' => 50,
535 'permission' => 'profile_extra',
536 ),
537 // This does ALL the pm settings
538 'pm_prefs' => array(
539 'type' => 'callback',
540 'callback_func' => 'pm_settings',
541 'permission' => 'pm_read',
542 'preload' => create_function('', '
543 global $context, $cur_profile;
544
545 $context[\'display_mode\'] = $cur_profile[\'pm_prefs\'] & 3;
546 $context[\'send_email\'] = $cur_profile[\'pm_email_notify\'];
547 $context[\'receive_from\'] = !empty($cur_profile[\'pm_receive_from\']) ? $cur_profile[\'pm_receive_from\'] : 0;
548
549 return true;
550 '),
551 'input_validate' => create_function('&$value', '
552 global $cur_profile, $profile_vars;
553
554 // Simple validate and apply the two "sub settings"
555 $value = max(min($value, 2), 0);
556
557 $cur_profile[\'pm_email_notify\'] = $profile_vars[\'pm_email_notify\'] = max(min((int) $_POST[\'pm_email_notify\'], 2), 0);
558 $cur_profile[\'pm_receive_from\'] = $profile_vars[\'pm_receive_from\'] = max(min((int) $_POST[\'pm_receive_from\'], 4), 0);
559
560 return true;
561 '),
562 ),
563 'posts' => array(
564 'type' => 'int',
565 'label' => $txt['profile_posts'],
566 'log_change' => true,
567 'size' => 7,
568 'permission' => 'moderate_forum',
569 'input_validate' => create_function('&$value', '
570 $value = $value != \'\' ? strtr($value, array(\',\' => \'\', \'.\' => \'\', \' \' => \'\')) : 0;
571 return true;
572 '),
573 ),
574 'real_name' => array(
575 'type' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum') ? 'text' : 'label',
576 'label' => $txt['name'],
577 'subtext' => $txt['display_name_desc'],
578 'log_change' => true,
579 'input_attr' => array('maxlength="60"'),
580 'permission' => 'profile_identity',
581 'enabled' => !empty($modSettings['allow_editDisplayName']) || allowedTo('moderate_forum'),
582 'input_validate' => create_function('&$value', '
583 global $context, $smcFunc, $sourcedir, $cur_profile;
584
585 $value = trim(preg_replace(\'~[\s]~\' . ($context[\'utf8\'] ? \'u\' : \'\'), \' \', $value));
586
587 if (trim($value) == \'\')
588 return \'no_name\';
589 elseif ($smcFunc[\'strlen\']($value) > 60)
590 return \'name_too_long\';
591 elseif ($cur_profile[\'real_name\'] != $value)
592 {
593 require_once($sourcedir . \'/Subs-Members.php\');
594 if (isReservedName($value, $context[\'id_member\']))
595 return \'name_taken\';
596 }
597 return true;
598 '),
599 ),
600 'secret_question' => array(
601 'type' => 'text',
602 'label' => $txt['secret_question'],
603 'subtext' => $txt['secret_desc'],
604 'size' => 50,
605 'permission' => 'profile_identity',
606 ),
607 'secret_answer' => array(
608 'type' => 'text',
609 'label' => $txt['secret_answer'],
610 'subtext' => $txt['secret_desc2'],
611 'size' => 20,
612 'postinput' => '<span class="smalltext" style="margin-left: 4ex;"><a href="' . $scripturl . '?action=helpadmin;help=secret_why_blank" onclick="return reqWin(this.href);">' . $txt['secret_why_blank'] . '</a></span>',
613 'value' => '',
614 'permission' => 'profile_identity',
615 'input_validate' => create_function('&$value', '
616 $value = $value != \'\' ? md5($value) : \'\';
617 return true;
618 '),
619 ),
620 'signature' => array(
621 'type' => 'callback',
622 'callback_func' => 'signature_modify',
623 'permission' => 'profile_extra',
624 'enabled' => substr($modSettings['signature_settings'], 0, 1) == 1,
625 'preload' => 'profileLoadSignatureData',
626 'input_validate' => 'profileValidateSignature',
627 ),
628 'show_online' => array(
629 'type' => 'check',
630 'label' => $txt['show_online'],
631 'permission' => 'profile_identity',
632 'enabled' => !empty($modSettings['allow_hideOnline']) || allowedTo('moderate_forum'),
633 ),
634 'smiley_set' => array(
635 'type' => 'callback',
636 'callback_func' => 'smiley_pick',
637 'enabled' => !empty($modSettings['smiley_sets_enable']),
638 'permission' => 'profile_extra',
639 'preload' => create_function('', '
640 global $modSettings, $context, $txt, $cur_profile;
641
642 $context[\'member\'][\'smiley_set\'][\'id\'] = empty($cur_profile[\'smiley_set\']) ? \'\' : $cur_profile[\'smiley_set\'];
643 $context[\'smiley_sets\'] = explode(\',\', \'none,,\' . $modSettings[\'smiley_sets_known\']);
644 $set_names = explode("\n", $txt[\'smileys_none\'] . "\n" . $txt[\'smileys_forum_board_default\'] . "\n" . $modSettings[\'smiley_sets_names\']);
645 foreach ($context[\'smiley_sets\'] as $i => $set)
646 {
647 $context[\'smiley_sets\'][$i] = array(
648 \'id\' => htmlspecialchars($set),
649 \'name\' => htmlspecialchars($set_names[$i]),
650 \'selected\' => $set == $context[\'member\'][\'smiley_set\'][\'id\']
651 );
652
653 if ($context[\'smiley_sets\'][$i][\'selected\'])
654 $context[\'member\'][\'smiley_set\'][\'name\'] = $set_names[$i];
655 }
656 return true;
657 '),
658 'input_validate' => create_function('&$value', '
659 global $modSettings;
660
661 $smiley_sets = explode(\',\', $modSettings[\'smiley_sets_known\']);
662 if (!in_array($value, $smiley_sets) && $value != \'none\')
663 $value = \'\';
664 return true;
665 '),
666 ),
667 // Pretty much a dummy entry - it populates all the theme settings.
668 'theme_settings' => array(
669 'type' => 'callback',
670 'callback_func' => 'theme_settings',
671 'permission' => 'profile_extra',
672 'is_dummy' => true,
673 'preload' => create_function('', '
674 loadLanguage(\'Settings\');
675 return true;
676 '),
677 ),
678 'time_format' => array(
679 'type' => 'callback',
680 'callback_func' => 'timeformat_modify',
681 'permission' => 'profile_extra',
682 'preload' => create_function('', '
683 global $context, $user_info, $txt, $cur_profile, $modSettings;
684
685 $context[\'easy_timeformats\'] = array(
686 array(\'format\' => \'\', \'title\' => $txt[\'timeformat_default\']),
687 array(\'format\' => \'%B %d, %Y, %I:%M:%S %p\', \'title\' => $txt[\'timeformat_easy1\']),
688 array(\'format\' => \'%B %d, %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy2\']),
689 array(\'format\' => \'%Y-%m-%d, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy3\']),
690 array(\'format\' => \'%d %B %Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy4\']),
691 array(\'format\' => \'%d-%m-%Y, %H:%M:%S\', \'title\' => $txt[\'timeformat_easy5\'])
692 );
693
694 $context[\'member\'][\'time_format\'] = $cur_profile[\'time_format\'];
695 $context[\'current_forum_time\'] = timeformat(time() - $user_info[\'time_offset\'] * 3600, false);
696 $context[\'current_forum_time_js\'] = strftime(\'%Y,\' . ((int) strftime(\'%m\', time() + $modSettings[\'time_offset\'] * 3600) - 1) . \',%d,%H,%M,%S\', time() + $modSettings[\'time_offset\'] * 3600);
697 $context[\'current_forum_time_hour\'] = (int) strftime(\'%H\', forum_time(false));
698 return true;
699 '),
700 ),
701 'time_offset' => array(
702 'type' => 'callback',
703 'callback_func' => 'timeoffset_modify',
704 'permission' => 'profile_extra',
705 'preload' => create_function('', '
706 global $context, $cur_profile;
707 $context[\'member\'][\'time_offset\'] = $cur_profile[\'time_offset\'];
708 return true;
709 '),
710 'input_validate' => create_function('&$value', '
711 // Validate the time_offset...
712 $value = (float) strtr($value, \',\', \'.\');
713
714 if ($value < -23.5 || $value > 23.5)
715 return \'bad_offset\';
716
717 return true;
718 '),
719 ),
720 'usertitle' => array(
721 'type' => 'text',
722 'label' => $txt['custom_title'],
723 'log_change' => true,
724 'size' => 50,
725 'permission' => 'profile_title',
726 'enabled' => !empty($modSettings['titlesEnable']),
727 ),
728 'website_title' => array(
729 'type' => 'text',
730 'label' => $txt['website_title'],
731 'subtext' => $txt['include_website_url'],
732 'size' => 50,
733 'permission' => 'profile_extra',
734 'link_with' => 'website',
735 ),
736 'website_url' => array(
737 'type' => 'text',
738 'label' => $txt['website_url'],
739 'subtext' => $txt['complete_url'],
740 'size' => 50,
741 'permission' => 'profile_extra',
742 // Fix the URL...
743 'input_validate' => create_function('&$value', '
744
745 if (strlen(trim($value)) > 0 && strpos($value, \'://\') === false)
746 $value = \'http://\' . $value;
747 if (strlen($value) < 8 || (substr($value, 0, 7) !== \'http://\' && substr($value, 0, 8) !== \'https://\'))
748 $value = \'\';
749 return true;
750 '),
751 'link_with' => 'website',
752 ),
753 'yim' => array(
754 'type' => 'text',
755 'label' => $txt['yim'],
756 'subtext' => $txt['your_yim'],
757 'size' => 24,
758 'input_attr' => array('maxlength="32"'),
759 'permission' => 'profile_extra',
760 ),
761 );
762
763 $disabled_fields = !empty($modSettings['disabled_profile_fields']) ? explode(',', $modSettings['disabled_profile_fields']) : array();
764 // For each of the above let's take out the bits which don't apply - to save memory and security!
765 foreach ($profile_fields as $key => $field)
766 {
767 // Do we have permission to do this?
768 if (isset($field['permission']) && !allowedTo(($context['user']['is_owner'] ? array($field['permission'] . '_own', $field['permission'] . '_any') : $field['permission'] . '_any')) && !allowedTo($field['permission']))
769 unset($profile_fields[$key]);
770
771 // Is it enabled?
772 if (isset($field['enabled']) && !$field['enabled'])
773 unset($profile_fields[$key]);
774
775 // Is it specifically disabled?
776 if (in_array($key, $disabled_fields) || (isset($field['link_with']) && in_array($field['link_with'], $disabled_fields)))
777 unset($profile_fields[$key]);
778 }
779 }
780
781 // Setup the context for a page load!
782 function setupProfileContext($fields)
783 {
784 global $profile_fields, $context, $cur_profile, $smcFunc, $txt;
785
786 // Make sure we have this!
787 loadProfileFields(true);
788
789 // First check for any linked sets.
790 foreach ($profile_fields as $key => $field)
791 if (isset($field['link_with']) && in_array($field['link_with'], $fields))
792 $fields[] = $key;
793
794 // Some default bits.
795 $context['profile_prehtml'] = '';
796 $context['profile_posthtml'] = '';
797 $context['profile_javascript'] = '';
798 $context['profile_onsubmit_javascript'] = '';
799
800 $i = 0;
801 $last_type = '';
802 foreach ($fields as $key => $field)
803 {
804 if (isset($profile_fields[$field]))
805 {
806 // Shortcut.
807 $cur_field = &$profile_fields[$field];
808
809 // Does it have a preload and does that preload succeed?
810 if (isset($cur_field['preload']) && !$cur_field['preload']())
811 continue;
812
813 // If this is anything but complex we need to do more cleaning!
814 if ($cur_field['type'] != 'callback' && $cur_field['type'] != 'hidden')
815 {
816 if (!isset($cur_field['label']))
817 $cur_field['label'] = isset($txt[$field]) ? $txt[$field] : $field;
818
819 // Everything has a value!
820 if (!isset($cur_field['value']))
821 {
822 $cur_field['value'] = isset($cur_profile[$field]) ? $cur_profile[$field] : '';
823 }
824
825 // Any input attributes?
826 $cur_field['input_attr'] = !empty($cur_field['input_attr']) ? implode(',', $cur_field['input_attr']) : '';
827 }
828
829 // Was there an error with this field on posting?
830 if (isset($context['profile_errors'][$field]))
831 $cur_field['is_error'] = true;
832
833 // Any javascript stuff?
834 if (!empty($cur_field['js_submit']))
835 $context['profile_onsubmit_javascript'] .= $cur_field['js_submit'];
836 if (!empty($cur_field['js']))
837 $context['profile_javascript'] .= $cur_field['js'];
838
839 // Any template stuff?
840 if (!empty($cur_field['prehtml']))
841 $context['profile_prehtml'] .= $cur_field['prehtml'];
842 if (!empty($cur_field['posthtml']))
843 $context['profile_posthtml'] .= $cur_field['posthtml'];
844
845 // Finally put it into context?
846 if ($cur_field['type'] != 'hidden')
847 {
848 $last_type = $cur_field['type'];
849 $context['profile_fields'][$field] = &$profile_fields[$field];
850 }
851 }
852 // Bodge in a line break - without doing two in a row ;)
853 elseif ($field == 'hr' && $last_type != 'hr' && $last_type != '')
854 {
855 $last_type = 'hr';
856 $context['profile_fields'][$i++]['type'] = 'hr';
857 }
858 }
859
860 // Free up some memory.
861 unset($profile_fields);
862 }
863
864 // Save the profile changes.
865 function saveProfileFields()
866 {
867 global $profile_fields, $profile_vars, $context, $old_profile, $post_errors, $sourcedir, $modSettings, $cur_profile, $smcFunc;
868
869 // Load them up.
870 loadProfileFields();
871
872 // This makes things easier...
873 $old_profile = $cur_profile;
874
875 // This allows variables to call activities when they save - by default just to reload their settings
876 $context['profile_execute_on_save'] = array();
877 if ($context['user']['is_owner'])
878 $context['profile_execute_on_save']['reload_user'] = 'profileReloadUser';
879
880 // Assume we log nothing.
881 $context['log_changes'] = array();
882
883 // Cycle through the profile fields working out what to do!
884 foreach ($profile_fields as $key => $field)
885 {
886 if (!isset($_POST[$key]) || !empty($field['is_dummy']))
887 continue;
888
889 // What gets updated?
890 $db_key = isset($field['save_key']) ? $field['save_key'] : $key;
891
892 // Right - we have something that is enabled, we can act upon and has a value posted to it. Does it have a validation function?
893 if (isset($field['input_validate']))
894 {
895 $is_valid = $field['input_validate']($_POST[$key]);
896 // An error occured - set it as such!
897 if ($is_valid !== true)
898 {
899 // Is this an actual error?
900 if ($is_valid !== false)
901 {
902 $post_errors[$key] = $is_valid;
903 $profile_fields[$key]['is_error'] = $is_valid;
904 }
905 // Retain the old value.
906 $cur_profile[$key] = $_POST[$key];
907 continue;
908 }
909 }
910
911 // Are we doing a cast?
912 $field['cast_type'] = empty($field['cast_type']) ? $field['type'] : $field['cast_type'];
913
914 // Finally, clean up certain types.
915 if ($field['cast_type'] == 'int')
916 $_POST[$key] = (int) $_POST[$key];
917 elseif ($field['cast_type'] == 'float')
918 $_POST[$key] = (float) $_POST[$key];
919 elseif ($field['cast_type'] == 'check')
920 $_POST[$key] = !empty($_POST[$key]) ? 1 : 0;
921
922 // If we got here we're doing OK.
923 if ($field['type'] != 'hidden' && (!isset($old_profile[$key]) || $_POST[$key] != $old_profile[$key]))
924 {
925 // Set the save variable.
926 $profile_vars[$db_key] = $_POST[$key];
927 // And update the user profile.
928 $cur_profile[$key] = $_POST[$key];
929
930 // Are we logging it?
931 if (!empty($field['log_change']) && isset($old_profile[$key]))
932 $context['log_changes'][$key] = array(
933 'previous' => $old_profile[$key],
934 'new' => $_POST[$key],
935 );
936 }
937
938 // Logging group changes are a bit different...
939 if ($key == 'id_group' && $field['log_change'])
940 {
941 profileLoadGroups();
942
943 // Any changes to primary group?
944 if ($_POST['id_group'] != $old_profile['id_group'])
945 {
946 $context['log_changes']['id_group'] = array(
947 'previous' => !empty($old_profile[$key]) && isset($context['member_groups'][$old_profile[$key]]) ? $context['member_groups'][$old_profile[$key]]['name'] : '',
948 'new' => !empty($_POST[$key]) && isset($context['member_groups'][$_POST[$key]]) ? $context['member_groups'][$_POST[$key]]['name'] : '',
949 );
950 }
951
952 // Prepare additional groups for comparison.
953 $additional_groups = array(
954 'previous' => !empty($old_profile['additional_groups']) ? explode(',', $old_profile['additional_groups']) : array(),
955 'new' => !empty($_POST['additional_groups']) ? array_diff($_POST['additional_groups'], array(0)) : array(),
956 );
957
958 sort($additional_groups['previous']);
959 sort($additional_groups['new']);
960
961 // What about additional groups?
962 if ($additional_groups['previous'] != $additional_groups['new'])
963 {
964 foreach ($additional_groups as $type => $groups)
965 {
966 foreach ($groups as $id => $group)
967 {
968 if (isset($context['member_groups'][$group]))
969 $additional_groups[$type][$id] = $context['member_groups'][$group]['name'];
970 else
971 unset($additional_groups[$type][$id]);
972 }
973 $additional_groups[$type] = implode(', ', $additional_groups[$type]);
974 }
975
976 $context['log_changes']['additional_groups'] = $additional_groups;
977 }
978 }
979 }
980
981 //!!! Temporary
982 if ($context['user']['is_owner'])
983 $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own'));
984 else
985 $changeOther = allowedTo('profile_extra_any');
986 if ($changeOther && empty($post_errors))
987 {
988 makeThemeChanges($context['id_member'], isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']);
989 if (!empty($_REQUEST['sa']))
990 makeCustomFieldChanges($context['id_member'], $_REQUEST['sa'], false);
991 }
992
993 // Free memory!
994 unset($profile_fields);
995 }
996
997 // Save the profile changes....
998 function saveProfileChanges(&$profile_vars, &$post_errors, $memID)
999 {
1000 global $user_info, $txt, $modSettings, $user_profile;
1001 global $context, $settings, $sourcedir;
1002 global $smcFunc;
1003
1004 // These make life easier....
1005 $old_profile = &$user_profile[$memID];
1006
1007 // Permissions...
1008 if ($context['user']['is_owner'])
1009 {
1010 $changeIdentity = allowedTo(array('profile_identity_any', 'profile_identity_own'));
1011 $changeOther = allowedTo(array('profile_extra_any', 'profile_extra_own'));
1012 }
1013 else
1014 {
1015 $changeIdentity = allowedTo('profile_identity_any');
1016 $changeOther = allowedTo('profile_extra_any');
1017 }
1018
1019 // Arrays of all the changes - makes things easier.
1020 $profile_bools = array(
1021 'notify_announcements', 'notify_send_body',
1022 );
1023 $profile_ints = array(
1024 'notify_regularity',
1025 'notify_types',
1026 );
1027 $profile_floats = array(
1028 );
1029 $profile_strings = array(
1030 'buddy_list',
1031 'ignore_boards',
1032 );
1033
1034 if (isset($_POST['sa']) && $_POST['sa'] == 'ignoreboards' && empty($_POST['ignore_brd']))
1035 $_POST['ignore_brd'] = array();
1036
1037 unset($_POST['ignore_boards']); // Whatever it is set to is a dirty fithy thing. Kinda like our minds.
1038 if (isset($_POST['ignore_brd']))
1039 {
1040 if (!is_array($_POST['ignore_brd']))
1041 $_POST['ignore_brd'] = array ($_POST['ignore_brd']);
1042
1043 foreach ($_POST['ignore_brd'] as $k => $d)
1044 {
1045 $d = (int) $d;
1046 if ($d != 0)
1047 $_POST['ignore_brd'][$k] = $d;
1048 else
1049 unset($_POST['ignore_brd'][$k]);
1050 }
1051 $_POST['ignore_boards'] = implode(',', $_POST['ignore_brd']);
1052 unset($_POST['ignore_brd']);
1053
1054 }
1055
1056 // Here's where we sort out all the 'other' values...
1057 if ($changeOther)
1058 {
1059 makeThemeChanges($memID, isset($_POST['id_theme']) ? (int) $_POST['id_theme'] : $old_profile['id_theme']);
1060 //makeAvatarChanges($memID, $post_errors);
1061 makeNotificationChanges($memID);
1062 if (!empty($_REQUEST['sa']))
1063 makeCustomFieldChanges($memID, $_REQUEST['sa'], false);
1064
1065 foreach ($profile_bools as $var)
1066 if (isset($_POST[$var]))
1067 $profile_vars[$var] = empty($_POST[$var]) ? '0' : '1';
1068 foreach ($profile_ints as $var)
1069 if (isset($_POST[$var]))
1070 $profile_vars[$var] = $_POST[$var] != '' ? (int) $_POST[$var] : '';
1071 foreach ($profile_floats as $var)
1072 if (isset($_POST[$var]))
1073 $profile_vars[$var] = (float) $_POST[$var];
1074 foreach ($profile_strings as $var)
1075 if (isset($_POST[$var]))
1076 $profile_vars[$var] = $_POST[$var];
1077 }
1078 }
1079
1080 // Make any theme changes that are sent with the profile..
1081 function makeThemeChanges($memID, $id_theme)
1082 {
1083 global $modSettings, $smcFunc, $context;
1084
1085 $reservedVars = array(
1086 'actual_theme_url',
1087 'actual_images_url',
1088 'base_theme_dir',
1089 'base_theme_url',
1090 'default_images_url',
1091 'default_theme_dir',
1092 'default_theme_url',
1093 'default_template',
1094 'images_url',
1095 'number_recent_posts',
1096 'smiley_sets_default',
1097 'theme_dir',
1098 'theme_id',
1099 'theme_layers',
1100 'theme_templates',
1101 'theme_url',
1102 );
1103
1104 // Can't change reserved vars.
1105 if ((isset($_POST['options']) && array_intersect($_POST['options'], $reservedVars) != array()) || (isset($_POST['default_options']) && array_intersect($_POST['default_options'], $reservedVars) != array()))
1106 fatal_lang_error('no_access', false);
1107
1108 // Don't allow any overriding of custom fields with default or non-default options.
1109 $request = $smcFunc['db_query']('', '
1110 SELECT col_name
1111 FROM {db_prefix}custom_fields
1112 WHERE active = {int:is_active}',
1113 array(
1114 'is_active' => 1,
1115 )
1116 );
1117 $custom_fields = array();
1118 while ($row = $smcFunc['db_fetch_assoc']($request))
1119 $custom_fields[] = $row['col_name'];
1120 $smcFunc['db_free_result']($request);
1121
1122 // These are the theme changes...
1123 $themeSetArray = array();
1124 if (isset($_POST['options']) && is_array($_POST['options']))
1125 {
1126 foreach ($_POST['options'] as $opt => $val)
1127 {
1128 if (in_array($opt, $custom_fields))
1129 continue;
1130
1131 // These need to be controlled.
1132 if ($opt == 'topics_per_page' || $opt == 'messages_per_page')
1133 $val = max(0, min($val, 50));
1134
1135 $themeSetArray[] = array($memID, $id_theme, $opt, is_array($val) ? implode(',', $val) : $val);
1136 }
1137 }
1138
1139 $erase_options = array();
1140 if (isset($_POST['default_options']) && is_array($_POST['default_options']))
1141 foreach ($_POST['default_options'] as $opt => $val)
1142 {
1143 if (in_array($opt, $custom_fields))
1144 continue;
1145
1146 // These need to be controlled.
1147 if ($opt == 'topics_per_page' || $opt == 'messages_per_page')
1148 $val = max(0, min($val, 50));
1149
1150 $themeSetArray[] = array($memID, 1, $opt, is_array($val) ? implode(',', $val) : $val);
1151 $erase_options[] = $opt;
1152 }
1153
1154 // If themeSetArray isn't still empty, send it to the database.
1155 if (empty($context['password_auth_failed']))
1156 {
1157 if (!empty($themeSetArray))
1158 {
1159 $smcFunc['db_insert']('replace',
1160 '{db_prefix}themes',
1161 array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
1162 $themeSetArray,
1163 array('id_member', 'id_theme', 'variable')
1164 );
1165 }
1166
1167 if (!empty($erase_options))
1168 {
1169 $smcFunc['db_query']('', '
1170 DELETE FROM {db_prefix}themes
1171 WHERE id_theme != {int:id_theme}
1172 AND variable IN ({array_string:erase_variables})
1173 AND id_member = {int:id_member}',
1174 array(
1175 'id_theme' => 1,
1176 'id_member' => $memID,
1177 'erase_variables' => $erase_options
1178 )
1179 );
1180 }
1181
1182 $themes = explode(',', $modSettings['knownThemes']);
1183 foreach ($themes as $t)
1184 cache_put_data('theme_settings-' . $t . ':' . $memID, null, 60);
1185 }
1186 }
1187
1188 // Make any notification changes that need to be made.
1189 function makeNotificationChanges($memID)
1190 {
1191 global $smcFunc;
1192
1193 // Update the boards they are being notified on.
1194 if (isset($_POST['edit_notify_boards']) && !empty($_POST['notify_boards']))
1195 {
1196 // Make sure only integers are deleted.
1197 foreach ($_POST['notify_boards'] as $index => $id)
1198 $_POST['notify_boards'][$index] = (int) $id;
1199
1200 // id_board = 0 is reserved for topic notifications.
1201 $_POST['notify_boards'] = array_diff($_POST['notify_boards'], array(0));
1202
1203 $smcFunc['db_query']('', '
1204 DELETE FROM {db_prefix}log_notify
1205 WHERE id_board IN ({array_int:board_list})
1206 AND id_member = {int:selected_member}',
1207 array(
1208 'board_list' => $_POST['notify_boards'],
1209 'selected_member' => $memID,
1210 )
1211 );
1212 }
1213
1214 // We are editing topic notifications......
1215 elseif (isset($_POST['edit_notify_topics']) && !empty($_POST['notify_topics']))
1216 {
1217 foreach ($_POST['notify_topics'] as $index => $id)
1218 $_POST['notify_topics'][$index] = (int) $id;
1219
1220 // Make sure there are no zeros left.
1221 $_POST['notify_topics'] = array_diff($_POST['notify_topics'], array(0));
1222
1223 $smcFunc['db_query']('', '
1224 DELETE FROM {db_prefix}log_notify
1225 WHERE id_topic IN ({array_int:topic_list})
1226 AND id_member = {int:selected_member}',
1227 array(
1228 'topic_list' => $_POST['notify_topics'],
1229 'selected_member' => $memID,
1230 )
1231 );
1232 }
1233 }
1234
1235 // Save any changes to the custom profile fields...
1236 function makeCustomFieldChanges($memID, $area, $sanitize = true)
1237 {
1238 global $context, $smcFunc, $user_profile, $user_info, $modSettings;
1239
1240 if ($sanitize && isset($_POST['customfield']))
1241 $_POST['customfield'] = htmlspecialchars__recursive($_POST['customfield']);
1242
1243 $where = $area == 'register' ? 'show_reg != 0' : 'show_profile = {string:area}';
1244
1245 // Load the fields we are saving too - make sure we save valid data (etc).
1246 $request = $smcFunc['db_query']('', '
1247 SELECT col_name, field_name, field_desc, field_type, field_length, field_options, default_value, show_reg, mask, private
1248 FROM {db_prefix}custom_fields
1249 WHERE ' . $where . '
1250 AND active = {int:is_active}',
1251 array(
1252 'is_active' => 1,
1253 'area' => $area,
1254 )
1255 );
1256 $changes = array();
1257 $log_changes = array();
1258 while ($row = $smcFunc['db_fetch_assoc']($request))
1259 {
1260 /* This means don't save if:
1261 - The user is NOT an admin.
1262 - The data is not freely viewable and editable by users.
1263 - The data is not invisible to users but editable by the owner (or if it is the user is not the owner)
1264 - The area isn't registration, and if it is that the field is not suppossed to be shown there.
1265 */
1266 if ($row['private'] != 0 && !allowedTo('admin_forum') && ($memID != $user_info['id'] || $row['private'] != 2) && ($area != 'register' || $row['show_reg'] == 0))
1267 continue;
1268
1269 // Validate the user data.
1270 if ($row['field_type'] == 'check')
1271 $value = isset($_POST['customfield'][$row['col_name']]) ? 1 : 0;
1272 elseif ($row['field_type'] == 'select' || $row['field_type'] == 'radio')
1273 {
1274 $value = $row['default_value'];
1275 foreach (explode(',', $row['field_options']) as $k => $v)
1276 if (isset($_POST['customfield'][$row['col_name']]) && $_POST['customfield'][$row['col_name']] == $k)
1277 $value = $v;
1278 }
1279 // Otherwise some form of text!
1280 else
1281 {
1282 $value = isset($_POST['customfield'][$row['col_name']]) ? $_POST['customfield'][$row['col_name']] : '';
1283 if ($row['field_length'])
1284 $value = $smcFunc['substr']($value, 0, $row['field_length']);
1285
1286 // Any masks?
1287 if ($row['field_type'] == 'text' && !empty($row['mask']) && $row['mask'] != 'none')
1288 {
1289 //!!! We never error on this - just ignore it at the moment...
1290 if ($row['mask'] == 'email' && (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $value) === 0 || strlen($value) > 255))
1291 $value = '';
1292 elseif ($row['mask'] == 'number')
1293 {
1294 $value = (int) $value;
1295 }
1296 elseif (substr($row['mask'], 0, 5) == 'regex' && preg_match(substr($row['mask'], 5), $value) === 0)
1297 $value = '';
1298 }
1299 }
1300
1301 // Did it change?
1302 if (!isset($user_profile[$memID]['options'][$row['col_name']]) || $user_profile[$memID]['options'][$row['col_name']] != $value)
1303 {
1304 $log_changes[] = array(
1305 'action' => 'customfield_' . $row['col_name'],
1306 'id_log' => 2,
1307 'log_time' => time(),
1308 'id_member' => $memID,
1309 'ip' => $user_info['ip'],
1310 'extra' => serialize(array('previous' => !empty($user_profile[$memID]['options'][$row['col_name']]) ? $user_profile[$memID]['options'][$row['col_name']] : '', 'new' => $value, 'applicator' => $user_info['id'])),
1311 );
1312 $changes[] = array(1, $row['col_name'], $value, $memID);
1313 $user_profile[$memID]['options'][$row['col_name']] = $value;
1314 }
1315 }
1316 $smcFunc['db_free_result']($request);
1317
1318 // Make those changes!
1319 if (!empty($changes) && empty($context['password_auth_failed']))
1320 {
1321 $smcFunc['db_insert']('replace',
1322 '{db_prefix}themes',
1323 array('id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534', 'id_member' => 'int'),
1324 $changes,
1325 array('id_theme', 'variable', 'id_member')
1326 );
1327 if (!empty($log_changes) && !empty($modSettings['modlog_enabled']))
1328 $smcFunc['db_insert']('',
1329 '{db_prefix}log_actions',
1330 array(
1331 'action' => 'string', 'id_log' => 'int', 'log_time' => 'int', 'id_member' => 'int', 'ip' => 'string-16',
1332 'extra' => 'string-65534',
1333 ),
1334 $log_changes,
1335 array('id_action')
1336 );
1337 }
1338 }
1339
1340 // Show all the users buddies, as well as a add/delete interface.
1341 function editBuddyIgnoreLists($memID)
1342 {
1343 global $sourcedir, $context, $txt, $scripturl, $modSettings, $user_profile;
1344
1345 // Do a quick check to ensure people aren't getting here illegally!
1346 if (!$context['user']['is_owner'] || empty($modSettings['enable_buddylist']))
1347 fatal_lang_error('no_access', false);
1348
1349 // Can we email the user direct?
1350 $context['can_moderate_forum'] = allowedTo('moderate_forum');
1351
1352 $subActions = array(
1353 'buddies' => array('editBuddies', $txt['editBuddies']),
1354 'ignore' => array('editIgnoreList', $txt['editIgnoreList']),
1355 );
1356
1357 $context['list_area'] = isset($_GET['sa']) && isset($subActions[$_GET['sa']]) ? $_GET['sa'] : 'buddies';
1358
1359 // Create the tabs for the template.
1360 $context[$context['profile_menu_name']]['tab_data'] = array(
1361 'title' => $txt['editBuddyIgnoreLists'],
1362 'description' => $txt['buddy_ignore_desc'],
1363 'icon' => 'profile_sm.gif',
1364 'tabs' => array(
1365 'buddies' => array(),
1366 'ignore' => array(),
1367 ),
1368 );
1369
1370 // Pass on to the actual function.
1371 $context['sub_template'] = $subActions[$context['list_area']][0];
1372 $subActions[$context['list_area']][0]($memID);
1373 }
1374
1375 // Show all the users buddies, as well as a add/delete interface.
1376 function editBuddies($memID)
1377 {
1378 global $txt, $scripturl, $modSettings;
1379 global $context, $user_profile, $memberContext, $smcFunc;
1380
1381 // For making changes!
1382 $buddiesArray = explode(',', $user_profile[$memID]['buddy_list']);
1383 foreach ($buddiesArray as $k => $dummy)
1384 if ($dummy == '')
1385 unset($buddiesArray[$k]);
1386
1387 // Removing a buddy?
1388 if (isset($_GET['remove']))
1389 {
1390 checkSession('get');
1391
1392 // Heh, I'm lazy, do it the easy way...
1393 foreach ($buddiesArray as $key => $buddy)
1394 if ($buddy == (int) $_GET['remove'])
1395 unset($buddiesArray[$key]);
1396
1397 // Make the changes.
1398 $user_profile[$memID]['buddy_list'] = implode(',', $buddiesArray);
1399 updateMemberData($memID, array('buddy_list' => $user_profile[$memID]['buddy_list']));
1400
1401 // Redirect off the page because we don't like all this ugly query stuff to stick in the history.
1402 redirectexit('action=profile;area=lists;sa=buddies;u=' . $memID);
1403 }
1404 elseif (isset($_POST['new_buddy']))
1405 {
1406 // Prepare the string for extraction...
1407 $_POST['new_buddy'] = strtr($smcFunc['htmlspecialchars']($_POST['new_buddy'], ENT_QUOTES), array('&quot;' => '"'));
1408 preg_match_all('~"([^"]+)"~', $_POST['new_buddy'], $matches);
1409 $new_buddies = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST['new_buddy']))));
1410
1411 foreach ($new_buddies as $k => $dummy)
1412 {
1413 $new_buddies[$k] = strtr(trim($new_buddies[$k]), array('\'' => '&#039;'));
1414
1415 if (strlen($new_buddies[$k]) == 0 || in_array($new_buddies[$k], array($user_profile[$memID]['member_name'], $user_profile[$memID]['real_name'])))
1416 unset($new_buddies[$k]);
1417 }
1418
1419 if (!empty($new_buddies))
1420 {
1421 // Now find out the id_member of the buddy.
1422 $request = $smcFunc['db_query']('', '
1423 SELECT id_member
1424 FROM {db_prefix}members
1425 WHERE member_name IN ({array_string:new_buddies}) OR real_name IN ({array_string:new_buddies})
1426 LIMIT {int:count_new_buddies}',
1427 array(
1428 'new_buddies' => $new_buddies,
1429 'count_new_buddies' => count($new_buddies),
1430 )
1431 );
1432
1433 // Add the new member to the buddies array.
1434 while ($row = $smcFunc['db_fetch_assoc']($request))
1435 $buddiesArray[] = (int) $row['id_member'];
1436 $smcFunc['db_free_result']($request);
1437
1438 // Now update the current users buddy list.
1439 $user_profile[$memID]['buddy_list'] = implode(',', $buddiesArray);
1440 updateMemberData($memID, array('buddy_list' => $user_profile[$memID]['buddy_list']));
1441 }
1442
1443 // Back to the buddy list!
1444 redirectexit('action=profile;area=lists;sa=buddies;u=' . $memID);
1445 }
1446
1447 // Get all the users "buddies"...
1448 $buddies = array();
1449
1450 if (!empty($buddiesArray))
1451 {
1452 $result = $smcFunc['db_query']('', '
1453 SELECT id_member
1454 FROM {db_prefix}members
1455 WHERE id_member IN ({array_int:buddy_list})
1456 ORDER BY real_name
1457 LIMIT {int:buddy_list_count}',
1458 array(
1459 'buddy_list' => $buddiesArray,
1460 'buddy_list_count' => substr_count($user_profile[$memID]['buddy_list'], ',') + 1,
1461 )
1462 );
1463 while ($row = $smcFunc['db_fetch_assoc']($result))
1464 $buddies[] = $row['id_member'];
1465 $smcFunc['db_free_result']($result);
1466 }
1467
1468 $context['buddy_count'] = count($buddies);
1469
1470 // Load all the members up.
1471 loadMemberData($buddies, false, 'profile');
1472
1473 // Setup the context for each buddy.
1474 $context['buddies'] = array();
1475 foreach ($buddies as $buddy)
1476 {
1477 loadMemberContext($buddy);
1478 $context['buddies'][$buddy] = $memberContext[$buddy];
1479 }
1480 }
1481
1482 // Allows the user to view their ignore list, as well as the option to manage members on it.
1483 function editIgnoreList($memID)
1484 {
1485 global $txt, $scripturl, $modSettings;
1486 global $context, $user_profile, $memberContext, $smcFunc;
1487
1488 // For making changes!
1489 $ignoreArray = explode(',', $user_profile[$memID]['pm_ignore_list']);
1490 foreach ($ignoreArray as $k => $dummy)
1491 if ($dummy == '')
1492 unset($ignoreArray[$k]);
1493
1494 // Removing a member from the ignore list?
1495 if (isset($_GET['remove']))
1496 {
1497 checkSession('get');
1498
1499 // Heh, I'm lazy, do it the easy way...
1500 foreach ($ignoreArray as $key => $id_remove)
1501 if ($id_remove == (int) $_GET['remove'])
1502 unset($ignoreArray[$key]);
1503
1504 // Make the changes.
1505 $user_profile[$memID]['pm_ignore_list'] = implode(',', $ignoreArray);
1506 updateMemberData($memID, array('pm_ignore_list' => $user_profile[$memID]['pm_ignore_list']));
1507
1508 // Redirect off the page because we don't like all this ugly query stuff to stick in the history.
1509 redirectexit('action=profile;area=lists;sa=ignore;u=' . $memID);
1510 }
1511 elseif (isset($_POST['new_ignore']))
1512 {
1513 // Prepare the string for extraction...
1514 $_POST['new_ignore'] = strtr($smcFunc['htmlspecialchars']($_POST['new_ignore'], ENT_QUOTES), array('&quot;' => '"'));
1515 preg_match_all('~"([^"]+)"~', $_POST['new_ignore'], $matches);
1516 $new_entries = array_unique(array_merge($matches[1], explode(',', preg_replace('~"[^"]+"~', '', $_POST['new_ignore']))));
1517
1518 foreach ($new_entries as $k => $dummy)
1519 {
1520 $new_entries[$k] = strtr(trim($new_entries[$k]), array('\'' => '&#039;'));
1521
1522 if (strlen($new_entries[$k]) == 0 || in_array($new_entries[$k], array($user_profile[$memID]['member_name'], $user_profile[$memID]['real_name'])))
1523 unset($new_entries[$k]);
1524 }
1525
1526 if (!empty($new_entries))
1527 {
1528 // Now find out the id_member for the members in question.
1529 $request = $smcFunc['db_query']('', '
1530 SELECT id_member
1531 FROM {db_prefix}members
1532 WHERE member_name IN ({array_string:new_entries}) OR real_name IN ({array_string:new_entries})
1533 LIMIT {int:count_new_entries}',
1534 array(
1535 'new_entries' => $new_entries,
1536 'count_new_entries' => count($new_entries),
1537 )
1538 );
1539
1540 // Add the new member to the buddies array.
1541 while ($row = $smcFunc['db_fetch_assoc']($request))
1542 $ignoreArray[] = (int) $row['id_member'];
1543 $smcFunc['db_free_result']($request);
1544
1545 // Now update the current users buddy list.
1546 $user_profile[$memID]['pm_ignore_list'] = implode(',', $ignoreArray);
1547 updateMemberData($memID, array('pm_ignore_list' => $user_profile[$memID]['pm_ignore_list']));
1548 }
1549
1550 // Back to the list of pityful people!
1551 redirectexit('action=profile;area=lists;sa=ignore;u=' . $memID);
1552 }
1553
1554 // Initialise the list of members we're ignoring.
1555 $ignored = array();
1556
1557 if (!empty($ignoreArray))
1558 {
1559 $result = $smcFunc['db_query']('', '
1560 SELECT id_member
1561 FROM {db_prefix}members
1562 WHERE id_member IN ({array_int:ignore_list})
1563 ORDER BY real_name
1564 LIMIT {int:ignore_list_count}',
1565 array(
1566 'ignore_list' => $ignoreArray,
1567 'ignore_list_count' => substr_count($user_profile[$memID]['pm_ignore_list'], ',') + 1,
1568 )
1569 );
1570 while ($row = $smcFunc['db_fetch_assoc']($result))
1571 $ignored[] = $row['id_member'];
1572 $smcFunc['db_free_result']($result);
1573 }
1574
1575 $context['ignore_count'] = count($ignored);
1576
1577 // Load all the members up.
1578 loadMemberData($ignored, false, 'profile');
1579
1580 // Setup the context for each buddy.
1581 $context['ignore_list'] = array();
1582 foreach ($ignored as $ignore_member)
1583 {
1584 loadMemberContext($ignore_member);
1585 $context['ignore_list'][$ignore_member] = $memberContext[$ignore_member];
1586 }
1587 }
1588
1589 function account($memID)
1590 {
1591 global $context, $txt;
1592
1593 loadThemeOptions($memID);
1594 if (allowedTo(array('profile_identity_own', 'profile_identity_any')))
1595 loadCustomFields($memID, 'account');
1596
1597 $context['sub_template'] = 'edit_options';
1598 $context['page_desc'] = $txt['account_info'];
1599
1600 setupProfileContext(
1601 array(
1602 'member_name', 'real_name', 'date_registered', 'posts', 'lngfile', 'hr',
1603 'id_group', 'hr',
1604 'email_address', 'hide_email', 'show_online', 'hr',
1605 'passwrd1', 'passwrd2', 'hr',
1606 'secret_question', 'secret_answer',
1607 )
1608 );
1609 }
1610
1611 function forumProfile($memID)
1612 {
1613 global $context, $user_profile, $user_info, $txt, $modSettings;
1614
1615 loadThemeOptions($memID);
1616 if (allowedTo(array('profile_extra_own', 'profile_extra_any')))
1617 loadCustomFields($memID, 'forumprofile');
1618
1619 $context['sub_template'] = 'edit_options';
1620 $context['page_desc'] = $txt['forumProfile_info'];
1621
1622 setupProfileContext(
1623 array(
1624 'avatar_choice', 'hr', 'personal_text', 'hr',
1625 'bday1', 'location', 'gender', 'hr',
1626 'icq', 'aim', 'msn', 'yim', 'hr',
1627 'usertitle', 'signature', 'hr',
1628 'karma_good', 'hr',
1629 'website_title', 'website_url',
1630 )
1631 );
1632 }
1633
1634 // Allow the edit of *someone elses* personal message settings.
1635 function pmprefs($memID)
1636 {
1637 global $sourcedir, $context, $txt, $scripturl;
1638
1639 loadThemeOptions($memID);
1640 loadCustomFields($memID, 'pmprefs');
1641
1642 $context['sub_template'] = 'edit_options';
1643 $context['page_desc'] = $txt['pm_settings_desc'];
1644
1645 setupProfileContext(
1646 array(
1647 'pm_prefs',
1648 )
1649 );
1650 }
1651
1652 // Recursive function to retrieve avatar files
1653 function getAvatars($directory, $level)
1654 {
1655 global $context, $txt, $modSettings;
1656
1657 $result = array();
1658
1659 // Open the directory..
1660 $dir = dir($modSettings['avatar_directory'] . (!empty($directory) ? '/' : '') . $directory);
1661 $dirs = array();
1662 $files = array();
1663
1664 if (!$dir)
1665 return array();
1666
1667 while ($line = $dir->read())
1668 {
1669 if (in_array($line, array('.', '..', 'blank.gif', 'index.php')))
1670 continue;
1671
1672 if (is_dir($modSettings['avatar_directory'] . '/' . $directory . (!empty($directory) ? '/' : '') . $line))
1673 $dirs[] = $line;
1674 else
1675 $files[] = $line;
1676 }
1677 $dir->close();
1678
1679 // Sort the results...
1680 natcasesort($dirs);
1681 natcasesort($files);
1682
1683 if ($level == 0)
1684 {
1685 $result[] = array(
1686 'filename' => 'blank.gif',
1687 'checked' => in_array($context['member']['avatar']['server_pic'], array('', 'blank.gif')),
1688 'name' => $txt['no_pic'],
1689 'is_dir' => false
1690 );
1691 }
1692
1693 foreach ($dirs as $line)
1694 {
1695 $tmp = getAvatars($directory . (!empty($directory) ? '/' : '') . $line, $level + 1);
1696 if (!empty($tmp))
1697 $result[] = array(
1698 'filename' => htmlspecialchars($line),
1699 'checked' => strpos($context['member']['avatar']['server_pic'], $line . '/') !== false,
1700 'name' => '[' . htmlspecialchars(str_replace('_', ' ', $line)) . ']',
1701 'is_dir' => true,
1702 'files' => $tmp
1703 );
1704 unset($tmp);
1705 }
1706
1707 foreach ($files as $line)
1708 {
1709 $filename = substr($line, 0, (strlen($line) - strlen(strrchr($line, '.'))));
1710 $extension = substr(strrchr($line, '.'), 1);
1711
1712 // Make sure it is an image.
1713 if (strcasecmp($extension, 'gif') != 0 && strcasecmp($extension, 'jpg') != 0 && strcasecmp($extension, 'jpeg') != 0 && strcasecmp($extension, 'png') != 0 && strcasecmp($extension, 'bmp') != 0)
1714 continue;
1715
1716 $result[] = array(
1717 'filename' => htmlspecialchars($line),
1718 'checked' => $line == $context['member']['avatar']['server_pic'],
1719 'name' => htmlspecialchars(str_replace('_', ' ', $filename)),
1720 'is_dir' => false
1721 );
1722 if ($level == 1)
1723 $context['avatar_list'][] = $directory . '/' . $line;
1724 }
1725
1726 return $result;
1727 }
1728
1729 function theme($memID)
1730 {
1731 global $txt, $context, $user_profile, $modSettings, $settings, $user_info, $smcFunc;
1732
1733 loadThemeOptions($memID);
1734 if (allowedTo(array('profile_extra_own', 'profile_extra_any')))
1735 loadCustomFields($memID, 'theme');
1736
1737 $context['sub_template'] = 'edit_options';
1738 $context['page_desc'] = $txt['theme_info'];
1739
1740 setupProfileContext(
1741 array(
1742 'id_theme', 'smiley_set', 'hr',
1743 'time_format', 'time_offset', 'hr',
1744 'theme_settings',
1745 )
1746 );
1747 }
1748
1749 // Changing authentication method? Only appropriate for people using OpenID.
1750 function authentication($memID, $saving = false)
1751 {
1752 global $context, $cur_profile, $sourcedir, $txt, $post_errors, $modSettings;
1753
1754 loadLanguage('Login');
1755
1756 // We are saving?
1757 if ($saving)
1758 {
1759 // Moving to password passed authentication?
1760 if ($_POST['authenticate'] == 'passwd')
1761 {
1762 // Didn't enter anything?
1763 if ($_POST['passwrd1'] == '')
1764 $post_errors[] = 'no_password';
1765 // Do the two entries for the password even match?
1766 elseif (!isset($_POST['passwrd2']) || $_POST['passwrd1'] != $_POST['passwrd2'])
1767 $post_errors[] = 'bad_new_password';
1768 // Is it valid?
1769 else
1770 {
1771 require_once($sourcedir . '/Subs-Auth.php');
1772 $passwordErrors = validatePassword($_POST['passwrd1'], $cur_profile['member_name'], array($cur_profile['real_name'], $cur_profile['email_address']));
1773
1774 // Were there errors?
1775 if ($passwordErrors != null)
1776 $post_errors[] = 'password_' . $passwordErrors;
1777 }
1778
1779 if (empty($post_errors))
1780 {
1781 // Integration?
1782 call_integration_hook('integrate_reset_pass', array($cur_profile['member_name'], $cur_profile['member_name'], $_POST['passwrd1']));
1783
1784 // Go then.
1785 $passwd = sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd1']));
1786
1787 // Do the important bits.
1788 updateMemberData($memID, array('openid_uri' => '', 'passwd' => $passwd));
1789 if ($context['user']['is_owner'])
1790 setLoginCookie(60 * $modSettings['cookieTime'], $memID, sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt']));
1791
1792 redirectexit('action=profile;u=' . $memID);
1793 }
1794
1795 return true;
1796 }
1797 // Not right yet!
1798 elseif ($_POST['authenticate'] == 'openid' && !empty($_POST['openid_identifier']))
1799 {
1800 require_once($sourcedir . '/Subs-OpenID.php');
1801 $_POST['openid_identifier'] = smf_openID_canonize($_POST['openid_identifier']);
1802
1803 if (smf_openid_member_exists($_POST['openid_identifier']))
1804 $post_errors[] = 'openid_in_use';
1805 elseif (empty($post_errors))
1806 {
1807 // Authenticate using the new OpenID URI first to make sure they didn't make a mistake.
1808 if ($context['user']['is_owner'])
1809 {
1810 $_SESSION['new_openid_uri'] = $_POST['openid_identifier'];
1811
1812 smf_openID_validate($_POST['openid_identifier'], false, null, 'change_uri');
1813 }
1814 else
1815 updateMemberData($memID, array('openid_uri' => $_POST['openid_identifier']));
1816 }
1817 }
1818 }
1819
1820 // Some stuff.
1821 $context['member']['openid_uri'] = $cur_profile['openid_uri'];
1822 $context['auth_method'] = empty($cur_profile['openid_uri']) ? 'password' : 'openid';
1823 $context['sub_template'] = 'authentication_method';
1824 }
1825
1826 // Display the notifications and settings for changes.
1827 function notification($memID)
1828 {
1829 global $txt, $scripturl, $user_profile, $user_info, $context, $modSettings, $smcFunc, $sourcedir, $settings;
1830
1831 // Gonna want this for the list.
1832 require_once($sourcedir . '/Subs-List.php');
1833
1834 // Fine, start with the board list.
1835 $listOptions = array(
1836 'id' => 'board_notification_list',
1837 'width' => '100%',
1838 'no_items_label' => $txt['notifications_boards_none'] . '<br /><br />' . $txt['notifications_boards_howto'],
1839 'no_items_align' => 'left',
1840 'base_href' => $scripturl . '?action=profile;u=' . $memID . ';area=notification',
1841 'default_sort_col' => 'board_name',
1842 'get_items' => array(
1843 'function' => 'list_getBoardNotifications',
1844 'params' => array(
1845 $memID,
1846 ),
1847 ),
1848 'columns' => array(
1849 'board_name' => array(
1850 'header' => array(
1851 'value' => $txt['notifications_boards'],
1852 'class' => 'lefttext first_th',
1853 ),
1854 'data' => array(
1855 'function' => create_function('$board', '
1856 global $settings, $txt;
1857
1858 $link = $board[\'link\'];
1859
1860 if ($board[\'new\'])
1861 $link .= \' <a href="\' . $board[\'href\'] . \'"><img src="\' . $settings[\'lang_images_url\'] . \'/new.gif" alt="\' . $txt[\'new\'] . \'" /></a>\';
1862
1863 return $link;
1864 '),
1865 ),
1866 'sort' => array(
1867 'default' => 'name',
1868 'reverse' => 'name DESC',
1869 ),
1870 ),
1871 'delete' => array(
1872 'header' => array(
1873 'value' => '<input type="checkbox" class="input_check" onclick="invertAll(this, this.form);" />',
1874 'style' => 'width: 4%;',
1875 ),
1876 'data' => array(
1877 'sprintf' => array(
1878 'format' => '<input type="checkbox" name="notify_boards[]" value="%1$d" class="input_check" />',
1879 'params' => array(
1880 'id' => false,
1881 ),
1882 ),
1883 'style' => 'text-align: center;',
1884 ),
1885 ),
1886 ),
1887 'form' => array(
1888 'href' => $scripturl . '?action=profile;area=notification;save',
1889 'include_sort' => true,
1890 'include_start' => true,
1891 'hidden_fields' => array(
1892 'u' => $memID,
1893 'sa' => $context['menu_item_selected'],
1894 $context['session_var'] => $context['session_id'],
1895 ),
1896 ),
1897 'additional_rows' => array(
1898 array(
1899 'position' => 'bottom_of_list',
1900 'value' => '<input type="submit" name="edit_notify_boards" value="' . $txt['notifications_update'] . '" class="button_submit" />',
1901 'align' => 'right',
1902 ),
1903 ),
1904 );
1905
1906 // Create the board notification list.
1907 createList($listOptions);
1908
1909 // Now do the topic notifications.
1910 $listOptions = array(
1911 'id' => 'topic_notification_list',
1912 'width' => '100%',
1913 'items_per_page' => $modSettings['defaultMaxMessages'],
1914 'no_items_label' => $txt['notifications_topics_none'] . '<br /><br />' . $txt['notifications_topics_howto'],
1915 'no_items_align' => 'left',
1916 'base_href' => $scripturl . '?action=profile;u=' . $memID . ';area=notification',
1917 'default_sort_col' => 'last_post',
1918 'get_items' => array(
1919 'function' => 'list_getTopicNotifications',
1920 'params' => array(
1921 $memID,
1922 ),
1923 ),
1924 'get_count' => array(
1925 'function' => 'list_getTopicNotificationCount',
1926 'params' => array(
1927 $memID,
1928 ),
1929 ),
1930 'columns' => array(
1931 'subject' => array(
1932 'header' => array(
1933 'value' => $txt['notifications_topics'],
1934 'class' => 'lefttext first_th',
1935 ),
1936 'data' => array(
1937 'function' => create_function('$topic', '
1938 global $settings, $txt;
1939
1940 $link = $topic[\'link\'];
1941
1942 if ($topic[\'new\'])
1943 $link .= \' <a href="\' . $topic[\'new_href\'] . \'"><img src="\' . $settings[\'lang_images_url\'] . \'/new.gif" alt="\' . $txt[\'new\'] . \'" /></a>\';
1944
1945 $link .= \'<br /><span class="smalltext"><em>\' . $txt[\'in\'] . \' \' . $topic[\'board_link\'] . \'</em></span>\';
1946
1947 return $link;
1948 '),
1949 ),
1950 'sort' => array(
1951 'default' => 'ms.subject',
1952 'reverse' => 'ms.subject DESC',
1953 ),
1954 ),
1955 'started_by' => array(
1956 'header' => array(
1957 'value' => $txt['started_by'],
1958 'class' => 'lefttext',
1959 ),
1960 'data' => array(
1961 'db' => 'poster_link',
1962 ),
1963 'sort' => array(
1964 'default' => 'real_name_col',
1965 'reverse' => 'real_name_col DESC',
1966 ),
1967 ),
1968 'last_post' => array(
1969 'header' => array(
1970 'value' => $txt['last_post'],
1971 'class' => 'lefttext',
1972 ),
1973 'data' => array(
1974 'sprintf' => array(
1975 'format' => '<span class="smalltext">%1$s<br />' . $txt['by'] . ' %2$s</span>',
1976 'params' => array(
1977 'updated' => false,
1978 'poster_updated_link' => false,
1979 ),
1980 ),
1981 ),
1982 'sort' => array(
1983 'default' => 'ml.id_msg DESC',
1984 'reverse' => 'ml.id_msg',
1985 ),
1986 ),
1987 'delete' => array(
1988 'header' => array(
1989 'value' => '<input type="checkbox" class="input_check" onclick="invertAll(this, this.form);" />',
1990 'style' => 'width: 4%;',
1991 ),
1992 'data' => array(
1993 'sprintf' => array(
1994 'format' => '<input type="checkbox" name="notify_topics[]" value="%1$d" class="input_check" />',
1995 'params' => array(
1996 'id' => false,
1997 ),
1998 ),
1999 'style' => 'text-align: center;',
2000 ),
2001 ),
2002 ),
2003 'form' => array(
2004 'href' => $scripturl . '?action=profile;area=notification;save',
2005 'include_sort' => true,
2006 'include_start' => true,
2007 'hidden_fields' => array(
2008 'u' => $memID,
2009 'sa' => $context['menu_item_selected'],
2010 $context['session_var'] => $context['session_id'],
2011 ),
2012 ),
2013 'additional_rows' => array(
2014 array(
2015 'position' => 'bottom_of_list',
2016 'value' => '<input type="submit" name="edit_notify_topics" value="' . $txt['notifications_update'] . '" class="button_submit" />',
2017 'align' => 'right',
2018 ),
2019 ),
2020 );
2021
2022 // Create the notification list.
2023 createList($listOptions);
2024
2025 // What options are set?
2026 $context['member'] += array(
2027 'notify_announcements' => $user_profile[$memID]['notify_announcements'],
2028 'notify_send_body' => $user_profile[$memID]['notify_send_body'],
2029 'notify_types' => $user_profile[$memID]['notify_types'],
2030 'notify_regularity' => $user_profile[$memID]['notify_regularity'],
2031 );
2032
2033 loadThemeOptions($memID);
2034 }
2035
2036 function list_getTopicNotificationCount($memID)
2037 {
2038 global $smcFunc, $user_info, $context, $modSettings;
2039
2040 $request = $smcFunc['db_query']('', '
2041 SELECT COUNT(*)
2042 FROM {db_prefix}log_notify AS ln' . (!$modSettings['postmod_active'] && $user_info['query_see_board'] === '1=1' ? '' : '
2043 INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic)') . ($user_info['query_see_board'] === '1=1' ? '' : '
2044 INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board)') . '
2045 WHERE ln.id_member = {int:selected_member}' . ($user_info['query_see_board'] === '1=1' ? '' : '
2046 AND {query_see_board}') . ($modSettings['postmod_active'] ? '
2047 AND t.approved = {int:is_approved}' : ''),
2048 array(
2049 'selected_member' => $memID,
2050 'is_approved' => 1,
2051 )
2052 );
2053 list ($totalNotifications) = $smcFunc['db_fetch_row']($request);
2054 $smcFunc['db_free_result']($request);
2055
2056 return $totalNotifications;
2057 }
2058
2059 function list_getTopicNotifications($start, $items_per_page, $sort, $memID)
2060 {
2061 global $smcFunc, $txt, $scripturl, $user_info, $context, $modSettings;
2062
2063 // All the topics with notification on...
2064 $request = $smcFunc['db_query']('', '
2065 SELECT
2066 IFNULL(lt.id_msg, IFNULL(lmr.id_msg, -1)) + 1 AS new_from, b.id_board, b.name,
2067 t.id_topic, ms.subject, ms.id_member, IFNULL(mem.real_name, ms.poster_name) AS real_name_col,
2068 ml.id_msg_modified, ml.poster_time, ml.id_member AS id_member_updated,
2069 IFNULL(mem2.real_name, ml.poster_name) AS last_real_name
2070 FROM {db_prefix}log_notify AS ln
2071 INNER JOIN {db_prefix}topics AS t ON (t.id_topic = ln.id_topic' . ($modSettings['postmod_active'] ? ' AND t.approved = {int:is_approved}' : '') . ')
2072 INNER JOIN {db_prefix}boards AS b ON (b.id_board = t.id_board AND {query_see_board})
2073 INNER JOIN {db_prefix}messages AS ms ON (ms.id_msg = t.id_first_msg)
2074 INNER JOIN {db_prefix}messages AS ml ON (ml.id_msg = t.id_last_msg)
2075 LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = ms.id_member)
2076 LEFT JOIN {db_prefix}members AS mem2 ON (mem2.id_member = ml.id_member)
2077 LEFT JOIN {db_prefix}log_topics AS lt ON (lt.id_topic = t.id_topic AND lt.id_member = {int:current_member})
2078 LEFT JOIN {db_prefix}log_mark_read AS lmr ON (lmr.id_board = b.id_board AND lmr.id_member = {int:current_member})
2079 WHERE ln.id_member = {int:selected_member}
2080 ORDER BY {raw:sort}
2081 LIMIT {int:offset}, {int:items_per_page}',
2082 array(
2083 'current_member' => $user_info['id'],
2084 'is_approved' => 1,
2085 'selected_member' => $memID,
2086 'sort' => $sort,
2087 'offset' => $start,
2088 'items_per_page' => $items_per_page,
2089 )
2090 );
2091 $notification_topics = array();
2092 while ($row = $smcFunc['db_fetch_assoc']($request))
2093 {
2094 censorText($row['subject']);
2095
2096 $notification_topics[] = array(
2097 'id' => $row['id_topic'],
2098 'poster_link' => empty($row['id_member']) ? $row['real_name_col'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member'] . '">' . $row['real_name_col'] . '</a>',
2099 'poster_updated_link' => empty($row['id_member_updated']) ? $row['last_real_name'] : '<a href="' . $scripturl . '?action=profile;u=' . $row['id_member_updated'] . '">' . $row['last_real_name'] . '</a>',
2100 'subject' => $row['subject'],
2101 'href' => $scripturl . '?topic=' . $row['id_topic'] . '.0',
2102 'link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.0">' . $row['subject'] . '</a>',
2103 'new' => $row['new_from'] <= $row['id_msg_modified'],
2104 'new_from' => $row['new_from'],
2105 'updated' => timeformat($row['poster_time']),
2106 'new_href' => $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['new_from'] . '#new',
2107 'new_link' => '<a href="' . $scripturl . '?topic=' . $row['id_topic'] . '.msg' . $row['new_from'] . '#new">' . $row['subject'] . '</a>',
2108 'board_link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
2109 );
2110 }
2111 $smcFunc['db_free_result']($request);
2112
2113 return $notification_topics;
2114 }
2115
2116 function list_getBoardNotifications($start, $items_per_page, $sort, $memID)
2117 {
2118 global $smcFunc, $txt, $scripturl, $user_info;
2119
2120 $request = $smcFunc['db_query']('', '
2121 SELECT b.id_board, b.name, IFNULL(lb.id_msg, 0) AS board_read, b.id_msg_updated
2122 FROM {db_prefix}log_notify AS ln
2123 INNER JOIN {db_prefix}boards AS b ON (b.id_board = ln.id_board)
2124 LEFT JOIN {db_prefix}log_boards AS lb ON (lb.id_board = b.id_board AND lb.id_member = {int:current_member})
2125 WHERE ln.id_member = {int:selected_member}
2126 AND {query_see_board}
2127 ORDER BY ' . $sort,
2128 array(
2129 'current_member' => $user_info['id'],
2130 'selected_member' => $memID,
2131 )
2132 );
2133 $notification_boards = array();
2134 while ($row = $smcFunc['db_fetch_assoc']($request))
2135 $notification_boards[] = array(
2136 'id' => $row['id_board'],
2137 'name' => $row['name'],
2138 'href' => $scripturl . '?board=' . $row['id_board'] . '.0',
2139 'link' => '<a href="' . $scripturl . '?board=' . $row['id_board'] . '.0">' . $row['name'] . '</a>',
2140 'new' => $row['board_read'] < $row['id_msg_updated']
2141 );
2142 $smcFunc['db_free_result']($request);
2143
2144 return $notification_boards;
2145 }
2146
2147 function loadThemeOptions($memID)
2148 {
2149 global $context, $options, $cur_profile, $smcFunc;
2150
2151 if (isset($_POST['default_options']))
2152 $_POST['options'] = isset($_POST['options']) ? $_POST['options'] + $_POST['default_options'] : $_POST['default_options'];
2153
2154 if ($context['user']['is_owner'])
2155 {
2156 $context['member']['options'] = $options;
2157 if (isset($_POST['options']) && is_array($_POST['options']))
2158 foreach ($_POST['options'] as $k => $v)
2159 $context['member']['options'][$k] = $v;
2160 }
2161 else
2162 {
2163 $request = $smcFunc['db_query']('', '
2164 SELECT id_member, variable, value
2165 FROM {db_prefix}themes
2166 WHERE id_theme IN (1, {int:member_theme})
2167 AND id_member IN (-1, {int:selected_member})',
2168 array(
2169 'member_theme' => (int) $cur_profile['id_theme'],
2170 'selected_member' => $memID,
2171 )
2172 );
2173 $temp = array();
2174 while ($row = $smcFunc['db_fetch_assoc']($request))
2175 {
2176 if ($row['id_member'] == -1)
2177 {
2178 $temp[$row['variable']] = $row['value'];
2179 continue;
2180 }
2181
2182 if (isset($_POST['options'][$row['variable']]))
2183 $row['value'] = $_POST['options'][$row['variable']];
2184 $context['member']['options'][$row['variable']] = $row['value'];
2185 }
2186 $smcFunc['db_free_result']($request);
2187
2188 // Load up the default theme options for any missing.
2189 foreach ($temp as $k => $v)
2190 {
2191 if (!isset($context['member']['options'][$k]))
2192 $context['member']['options'][$k] = $v;
2193 }
2194 }
2195 }
2196
2197 function ignoreboards($memID)
2198 {
2199 global $txt, $user_info, $context, $modSettings, $smcFunc, $cur_profile;
2200
2201 // Have the admins enabled this option?
2202 if (empty($modSettings['allow_ignore_boards']))
2203 fatal_lang_error('ignoreboards_disallowed', 'user');
2204
2205 // Find all the boards this user is allowed to see.
2206 $request = $smcFunc['db_query']('order_by_board_order', '
2207 SELECT b.id_cat, c.name AS cat_name, b.id_board, b.name, b.child_level,
2208 '. (!empty($cur_profile['ignore_boards']) ? 'b.id_board IN ({array_int:ignore_boards})' : '0') . ' AS is_ignored
2209 FROM {db_prefix}boards AS b
2210 LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
2211 WHERE {query_see_board}
2212 AND redirect = {string:empty_string}',
2213 array(
2214 'ignore_boards' => !empty($cur_profile['ignore_boards']) ? explode(',', $cur_profile['ignore_boards']) : array(),
2215 'empty_string' => '',
2216 )
2217 );
2218 $context['num_boards'] = $smcFunc['db_num_rows']($request);
2219 $context['categories'] = array();
2220 while ($row = $smcFunc['db_fetch_assoc']($request))
2221 {
2222 // This category hasn't been set up yet..
2223 if (!isset($context['categories'][$row['id_cat']]))
2224 $context['categories'][$row['id_cat']] = array(
2225 'id' => $row['id_cat'],
2226 'name' => $row['cat_name'],
2227 'boards' => array()
2228 );
2229
2230 // Set this board up, and let the template know when it's a child. (indent them..)
2231 $context['categories'][$row['id_cat']]['boards'][$row['id_board']] = array(
2232 'id' => $row['id_board'],
2233 'name' => $row['name'],
2234 'child_level' => $row['child_level'],
2235 'selected' => $row['is_ignored'],
2236 );
2237 }
2238 $smcFunc['db_free_result']($request);
2239
2240 // Now, let's sort the list of categories into the boards for templates that like that.
2241 $temp_boards = array();
2242 foreach ($context['categories'] as $category)
2243 {
2244 // Include a list of boards per category for easy toggling.
2245 $context['categories'][$category['id']]['child_ids'] = array_keys($category['boards']);
2246
2247 $temp_boards[] = array(
2248 'name' => $category['name'],
2249 'child_ids' => array_keys($category['boards'])
2250 );
2251 $temp_boards = array_merge($temp_boards, array_values($category['boards']));
2252 }
2253
2254 $max_boards = ceil(count($temp_boards) / 2);
2255 if ($max_boards == 1)
2256 $max_boards = 2;
2257
2258 // Now, alternate them so they can be shown left and right ;).
2259 $context['board_columns'] = array();
2260 for ($i = 0; $i < $max_boards; $i++)
2261 {
2262 $context['board_columns'][] = $temp_boards[$i];
2263 if (isset($temp_boards[$i + $max_boards]))
2264 $context['board_columns'][] = $temp_boards[$i + $max_boards];
2265 else
2266 $context['board_columns'][] = array();
2267 }
2268
2269 loadThemeOptions($memID);
2270 }
2271
2272 // Load all the languages for the profile.
2273 function profileLoadLanguages()
2274 {
2275 global $context, $modSettings, $settings, $cur_profile, $language, $smcFunc;
2276
2277 $context['profile_languages'] = array();
2278
2279 // Get our languages!
2280 getLanguages(true, true);
2281
2282 // Setup our languages.
2283 foreach ($context['languages'] as $lang)
2284 {
2285 $context['profile_languages'][$lang['filename']] = strtr($lang['name'], array('-utf8' => ''));
2286 }
2287 ksort($context['profile_languages']);
2288
2289 // Return whether we should proceed with this.
2290 return count($context['profile_languages']) > 1 ? true : false;
2291 }
2292
2293 // Load all the group info for the profile.
2294 function profileLoadGroups()
2295 {
2296 global $cur_profile, $txt, $context, $smcFunc, $user_settings;
2297
2298 $context['member_groups'] = array(
2299 0 => array(
2300 'id' => 0,
2301 'name' => $txt['no_primary_membergroup'],
2302 'is_primary' => $cur_profile['id_group'] == 0,
2303 'can_be_additional' => false,
2304 'can_be_primary' => true,
2305 )
2306 );
2307 $curGroups = explode(',', $cur_profile['additional_groups']);
2308
2309 // Load membergroups, but only those groups the user can assign.
2310 $request = $smcFunc['db_query']('', '
2311 SELECT group_name, id_group, hidden
2312 FROM {db_prefix}membergroups
2313 WHERE id_group != {int:moderator_group}
2314 AND min_posts = {int:min_posts}' . (allowedTo('admin_forum') ? '' : '
2315 AND group_type != {int:is_protected}') . '
2316 ORDER BY min_posts, CASE WHEN id_group < {int:newbie_group} THEN id_group ELSE 4 END, group_name',
2317 array(
2318 'moderator_group' => 3,
2319 'min_posts' => -1,
2320 'is_protected' => 1,
2321 'newbie_group' => 4,
2322 )
2323 );
2324 while ($row = $smcFunc['db_fetch_assoc']($request))
2325 {
2326 // We should skip the administrator group if they don't have the admin_forum permission!
2327 if ($row['id_group'] == 1 && !allowedTo('admin_forum'))
2328 continue;
2329
2330 $context['member_groups'][$row['id_group']] = array(
2331 'id' => $row['id_group'],
2332 'name' => $row['group_name'],
2333 'is_primary' => $cur_profile['id_group'] == $row['id_group'],
2334 'is_additional' => in_array($row['id_group'], $curGroups),
2335 'can_be_additional' => true,
2336 'can_be_primary' => $row['hidden'] != 2,
2337 );
2338 }
2339 $smcFunc['db_free_result']($request);
2340
2341 $context['member']['group_id'] = $user_settings['id_group'];
2342
2343 return true;
2344 }
2345
2346 // Load key signature context data.
2347 function profileLoadSignatureData()
2348 {
2349 global $modSettings, $context, $txt, $cur_profile, $smcFunc;
2350
2351 // Signature limits.
2352 list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
2353 $sig_limits = explode(',', $sig_limits);
2354
2355 $context['signature_enabled'] = isset($sig_limits[0]) ? $sig_limits[0] : 0;
2356 $context['signature_limits'] = array(
2357 'max_length' => isset($sig_limits[1]) ? $sig_limits[1] : 0,
2358 'max_lines' => isset($sig_limits[2]) ? $sig_limits[2] : 0,
2359 'max_images' => isset($sig_limits[3]) ? $sig_limits[3] : 0,
2360 'max_smileys' => isset($sig_limits[4]) ? $sig_limits[4] : 0,
2361 'max_image_width' => isset($sig_limits[5]) ? $sig_limits[5] : 0,
2362 'max_image_height' => isset($sig_limits[6]) ? $sig_limits[6] : 0,
2363 'max_font_size' => isset($sig_limits[7]) ? $sig_limits[7] : 0,
2364 'bbc' => !empty($sig_bbc) ? explode(',', $sig_bbc) : array(),
2365 );
2366 // Kept this line in for backwards compatibility!
2367 $context['max_signature_length'] = $context['signature_limits']['max_length'];
2368 // Warning message for signature image limits?
2369 $context['signature_warning'] = '';
2370 if ($context['signature_limits']['max_image_width'] && $context['signature_limits']['max_image_height'])
2371 $context['signature_warning'] = sprintf($txt['profile_error_signature_max_image_size'], $context['signature_limits']['max_image_width'], $context['signature_limits']['max_image_height']);
2372 elseif ($context['signature_limits']['max_image_width'] || $context['signature_limits']['max_image_height'])
2373 $context['signature_warning'] = sprintf($txt['profile_error_signature_max_image_' . ($context['signature_limits']['max_image_width'] ? 'width' : 'height')], $context['signature_limits'][$context['signature_limits']['max_image_width'] ? 'max_image_width' : 'max_image_height']);
2374
2375 $context['show_spellchecking'] = !empty($modSettings['enableSpellChecking']) && function_exists('pspell_new');
2376
2377 $context['member']['signature'] = empty($cur_profile['signature']) ? '' : str_replace(array('<br />', '<', '>', '"', '\''), array("\n", '&lt;', '&gt;', '&quot;', '&#039;'), $cur_profile['signature']);
2378
2379 return true;
2380 }
2381
2382 // Load avatar context data.
2383 function profileLoadAvatarData()
2384 {
2385 global $context, $cur_profile, $modSettings, $scripturl;
2386
2387 $context['avatar_url'] = $modSettings['avatar_url'];
2388
2389 // Default context.
2390 $context['member']['avatar'] += array(
2391 'custom' => stristr($cur_profile['avatar'], 'http://') ? $cur_profile['avatar'] : 'http://',
2392 'selection' => $cur_profile['avatar'] == '' || stristr($cur_profile['avatar'], 'http://') ? '' : $cur_profile['avatar'],
2393 'id_attach' => $cur_profile['id_attach'],
2394 'filename' => $cur_profile['filename'],
2395 'allow_server_stored' => allowedTo('profile_server_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
2396 'allow_upload' => allowedTo('profile_upload_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
2397 'allow_external' => allowedTo('profile_remote_avatar') || (!$context['user']['is_owner'] && allowedTo('profile_extra_any')),
2398 );
2399
2400 if ($cur_profile['avatar'] == '' && $cur_profile['id_attach'] > 0 && $context['member']['avatar']['allow_upload'])
2401 {
2402 $context['member']['avatar'] += array(
2403 'choice' => 'upload',
2404 'server_pic' => 'blank.gif',
2405 'external' => 'http://'
2406 );
2407 $context['member']['avatar']['href'] = empty($cur_profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $cur_profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $cur_profile['filename'];
2408 }
2409 elseif (stristr($cur_profile['avatar'], 'http://') && $context['member']['avatar']['allow_external'])
2410 $context['member']['avatar'] += array(
2411 'choice' => 'external',
2412 'server_pic' => 'blank.gif',
2413 'external' => $cur_profile['avatar']
2414 );
2415 elseif ($cur_profile['avatar'] != '' && file_exists($modSettings['avatar_directory'] . '/' . $cur_profile['avatar']) && $context['member']['avatar']['allow_server_stored'])
2416 $context['member']['avatar'] += array(
2417 'choice' => 'server_stored',
2418 'server_pic' => $cur_profile['avatar'] == '' ? 'blank.gif' : $cur_profile['avatar'],
2419 'external' => 'http://'
2420 );
2421 else
2422 $context['member']['avatar'] += array(
2423 'choice' => 'none',
2424 'server_pic' => 'blank.gif',
2425 'external' => 'http://'
2426 );
2427
2428 // Get a list of all the avatars.
2429 if ($context['member']['avatar']['allow_server_stored'])
2430 {
2431 $context['avatar_list'] = array();
2432 $context['avatars'] = is_dir($modSettings['avatar_directory']) ? getAvatars('', 0) : array();
2433 }
2434 else
2435 $context['avatars'] = array();
2436
2437 // Second level selected avatar...
2438 $context['avatar_selected'] = substr(strrchr($context['member']['avatar']['server_pic'], '/'), 1);
2439 return true;
2440 }
2441
2442 // Save a members group.
2443 function profileSaveGroups(&$value)
2444 {
2445 global $profile_vars, $old_profile, $context, $smcFunc, $cur_profile;
2446
2447 // Do we need to protect some groups?
2448 if (!allowedTo('admin_forum'))
2449 {
2450 $request = $smcFunc['db_query']('', '
2451 SELECT id_group
2452 FROM {db_prefix}membergroups
2453 WHERE group_type = {int:is_protected}',
2454 array(
2455 'is_protected' => 1,
2456 )
2457 );
2458 $protected_groups = array(1);
2459 while ($row = $smcFunc['db_fetch_assoc']($request))
2460 $protected_groups[] = $row['id_group'];
2461 $smcFunc['db_free_result']($request);
2462
2463 $protected_groups = array_unique($protected_groups);
2464 }
2465
2466 // The account page allows the change of your id_group - but not to a protected group!
2467 if (empty($protected_groups) || count(array_intersect(array((int) $value, $old_profile['id_group']), $protected_groups)) == 0)
2468 $value = (int) $value;
2469 // ... otherwise it's the old group sir.
2470 else
2471 $value = $old_profile['id_group'];
2472
2473 // Find the additional membergroups (if any)
2474 if (isset($_POST['additional_groups']) && is_array($_POST['additional_groups']))
2475 {
2476 $additional_groups = array();
2477 foreach ($_POST['additional_groups'] as $group_id)
2478 {
2479 $group_id = (int) $group_id;
2480 if (!empty($group_id) && (empty($protected_groups) || !in_array($group_id, $protected_groups)))
2481 $additional_groups[] = $group_id;
2482 }
2483
2484 // Put the protected groups back in there if you don't have permission to take them away.
2485 $old_additional_groups = explode(',', $old_profile['additional_groups']);
2486 foreach ($old_additional_groups as $group_id)
2487 {
2488 if (!empty($protected_groups) && in_array($group_id, $protected_groups))
2489 $additional_groups[] = $group_id;
2490 }
2491
2492 if (implode(',', $additional_groups) !== $old_profile['additional_groups'])
2493 {
2494 $profile_vars['additional_groups'] = implode(',', $additional_groups);
2495 $cur_profile['additional_groups'] = implode(',', $additional_groups);
2496 }
2497 }
2498
2499 // Too often, people remove delete their own account, or something.
2500 if (in_array(1, explode(',', $old_profile['additional_groups'])) || $old_profile['id_group'] == 1)
2501 {
2502 $stillAdmin = $value == 1 || (isset($additional_groups) && in_array(1, $additional_groups));
2503
2504 // If they would no longer be an admin, look for any other...
2505 if (!$stillAdmin)
2506 {
2507 $request = $smcFunc['db_query']('', '
2508 SELECT id_member
2509 FROM {db_prefix}members
2510 WHERE (id_group = {int:admin_group} OR FIND_IN_SET({int:admin_group}, additional_groups) != 0)
2511 AND id_member != {int:selected_member}
2512 LIMIT 1',
2513 array(
2514 'admin_group' => 1,
2515 'selected_member' => $context['id_member'],
2516 )
2517 );
2518 list ($another) = $smcFunc['db_fetch_row']($request);
2519 $smcFunc['db_free_result']($request);
2520
2521 if (empty($another))
2522 fatal_lang_error('at_least_one_admin', 'critical');
2523 }
2524 }
2525
2526 // If we are changing group status, update permission cache as necessary.
2527 if ($value != $old_profile['id_group'] || isset($profile_vars['additional_groups']))
2528 {
2529 if ($context['user']['is_owner'])
2530 $_SESSION['mc']['time'] = 0;
2531 else
2532 updateSettings(array('settings_updated' => time()));
2533 }
2534
2535 return true;
2536 }
2537
2538 // The avatar is incredibly complicated, what with the options... and what not.
2539 function profileSaveAvatarData(&$value)
2540 {
2541 global $modSettings, $sourcedir, $smcFunc, $profile_vars, $cur_profile, $context;
2542
2543 $memID = $context['id_member'];
2544 if (empty($memID) && !empty($context['password_auth_failed']))
2545 return false;
2546
2547 require_once($sourcedir . '/ManageAttachments.php');
2548
2549 // We need to know where we're going to be putting it..
2550 if (!empty($modSettings['custom_avatar_enabled']))
2551 {
2552 $uploadDir = $modSettings['custom_avatar_dir'];
2553 $id_folder = 1;
2554 }
2555 elseif (!empty($modSettings['currentAttachmentUploadDir']))
2556 {
2557 if (!is_array($modSettings['attachmentUploadDir']))
2558 $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
2559
2560 // Just use the current path for temp files.
2561 $uploadDir = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
2562 $id_folder = $modSettings['currentAttachmentUploadDir'];
2563 }
2564 else
2565 {
2566 $uploadDir = $modSettings['attachmentUploadDir'];
2567 $id_folder = 1;
2568 }
2569
2570 $downloadedExternalAvatar = false;
2571 if ($value == 'external' && allowedTo('profile_remote_avatar') && strtolower(substr($_POST['userpicpersonal'], 0, 7)) == 'http://' && strlen($_POST['userpicpersonal']) > 7 && !empty($modSettings['avatar_download_external']))
2572 {
2573 if (!is_writable($uploadDir))
2574 fatal_lang_error('attachments_no_write', 'critical');
2575
2576 require_once($sourcedir . '/Subs-Package.php');
2577
2578 $url = parse_url($_POST['userpicpersonal']);
2579 $contents = fetch_web_data('http://' . $url['host'] . (empty($url['port']) ? '' : ':' . $url['port']) . str_replace(' ', '%20', trim($url['path'])));
2580
2581 if ($contents != false && $tmpAvatar = fopen($uploadDir . '/avatar_tmp_' . $memID, 'wb'))
2582 {
2583 fwrite($tmpAvatar, $contents);
2584 fclose($tmpAvatar);
2585
2586 $downloadedExternalAvatar = true;
2587 $_FILES['attachment']['tmp_name'] = $uploadDir . '/avatar_tmp_' . $memID;
2588 }
2589 }
2590
2591 if ($value == 'none')
2592 {
2593 $profile_vars['avatar'] = '';
2594
2595 // Reset the attach ID.
2596 $cur_profile['id_attach'] = 0;
2597 $cur_profile['attachment_type'] = 0;
2598 $cur_profile['filename'] = '';
2599
2600 removeAttachments(array('id_member' => $memID));
2601 }
2602 elseif ($value == 'server_stored' && allowedTo('profile_server_avatar'))
2603 {
2604 $profile_vars['avatar'] = strtr(empty($_POST['file']) ? (empty($_POST['cat']) ? '' : $_POST['cat']) : $_POST['file'], array('&amp;' => '&'));
2605 $profile_vars['avatar'] = preg_match('~^([\w _!@%*=\-#()\[\]&.,]+/)?[\w _!@%*=\-#()\[\]&.,]+$~', $profile_vars['avatar']) != 0 && preg_match('/\.\./', $profile_vars['avatar']) == 0 && file_exists($modSettings['avatar_directory'] . '/' . $profile_vars['avatar']) ? ($profile_vars['avatar'] == 'blank.gif' ? '' : $profile_vars['avatar']) : '';
2606
2607 // Clear current profile...
2608 $cur_profile['id_attach'] = 0;
2609 $cur_profile['attachment_type'] = 0;
2610 $cur_profile['filename'] = '';
2611
2612 // Get rid of their old avatar. (if uploaded.)
2613 removeAttachments(array('id_member' => $memID));
2614 }
2615 elseif ($value == 'external' && allowedTo('profile_remote_avatar') && strtolower(substr($_POST['userpicpersonal'], 0, 7)) == 'http://' && empty($modSettings['avatar_download_external']))
2616 {
2617 // We need these clean...
2618 $cur_profile['id_attach'] = 0;
2619 $cur_profile['attachment_type'] = 0;
2620 $cur_profile['filename'] = '';
2621
2622 // Remove any attached avatar...
2623 removeAttachments(array('id_member' => $memID));
2624
2625 $profile_vars['avatar'] = str_replace('%20', '', preg_replace('~action(?:=|%3d)(?!dlattach)~i', 'action-', $_POST['userpicpersonal']));
2626
2627 if ($profile_vars['avatar'] == 'http://' || $profile_vars['avatar'] == 'http:///')
2628 $profile_vars['avatar'] = '';
2629 // Trying to make us do something we'll regret?
2630 elseif (substr($profile_vars['avatar'], 0, 7) != 'http://')
2631 return 'bad_avatar';
2632 // Should we check dimensions?
2633 elseif (!empty($modSettings['avatar_max_height_external']) || !empty($modSettings['avatar_max_width_external']))
2634 {
2635 // Now let's validate the avatar.
2636 $sizes = url_image_size($profile_vars['avatar']);
2637
2638 if (is_array($sizes) && (($sizes[0] > $modSettings['avatar_max_width_external'] && !empty($modSettings['avatar_max_width_external'])) || ($sizes[1] > $modSettings['avatar_max_height_external'] && !empty($modSettings['avatar_max_height_external']))))
2639 {
2640 // Houston, we have a problem. The avatar is too large!!
2641 if ($modSettings['avatar_action_too_large'] == 'option_refuse')
2642 return 'bad_avatar';
2643 elseif ($modSettings['avatar_action_too_large'] == 'option_download_and_resize')
2644 {
2645 require_once($sourcedir . '/Subs-Graphics.php');
2646 if (downloadAvatar($profile_vars['avatar'], $memID, $modSettings['avatar_max_width_external'], $modSettings['avatar_max_height_external']))
2647 {
2648 $profile_vars['avatar'] = '';
2649 $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id'];
2650 $cur_profile['filename'] = $modSettings['new_avatar_data']['filename'];
2651 $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type'];
2652 }
2653 else
2654 return 'bad_avatar';
2655 }
2656 }
2657 }
2658 }
2659 elseif (($value == 'upload' && allowedTo('profile_upload_avatar')) || $downloadedExternalAvatar)
2660 {
2661 if ((isset($_FILES['attachment']['name']) && $_FILES['attachment']['name'] != '') || $downloadedExternalAvatar)
2662 {
2663 // Get the dimensions of the image.
2664 if (!$downloadedExternalAvatar)
2665 {
2666 if (!is_writable($uploadDir))
2667 fatal_lang_error('attachments_no_write', 'critical');
2668
2669 if (!move_uploaded_file($_FILES['attachment']['tmp_name'], $uploadDir . '/avatar_tmp_' . $memID))
2670 fatal_lang_error('attach_timeout', 'critical');
2671
2672 $_FILES['attachment']['tmp_name'] = $uploadDir . '/avatar_tmp_' . $memID;
2673 }
2674
2675 $sizes = @getimagesize($_FILES['attachment']['tmp_name']);
2676
2677 // No size, then it's probably not a valid pic.
2678 if ($sizes === false)
2679 return 'bad_avatar';
2680 // Check whether the image is too large.
2681 elseif ((!empty($modSettings['avatar_max_width_upload']) && $sizes[0] > $modSettings['avatar_max_width_upload']) || (!empty($modSettings['avatar_max_height_upload']) && $sizes[1] > $modSettings['avatar_max_height_upload']))
2682 {
2683 if (!empty($modSettings['avatar_resize_upload']))
2684 {
2685 // Attempt to chmod it.
2686 @chmod($uploadDir . '/avatar_tmp_' . $memID, 0644);
2687
2688 require_once($sourcedir . '/Subs-Graphics.php');
2689 if (!downloadAvatar($uploadDir . '/avatar_tmp_' . $memID, $memID, $modSettings['avatar_max_width_upload'], $modSettings['avatar_max_height_upload']))
2690 return 'bad_avatar';
2691
2692 // Reset attachment avatar data.
2693 $cur_profile['id_attach'] = $modSettings['new_avatar_data']['id'];
2694 $cur_profile['filename'] = $modSettings['new_avatar_data']['filename'];
2695 $cur_profile['attachment_type'] = $modSettings['new_avatar_data']['type'];
2696 }
2697 else
2698 return 'bad_avatar';
2699 }
2700 elseif (is_array($sizes))
2701 {
2702 // Now try to find an infection.
2703 require_once($sourcedir . '/Subs-Graphics.php');
2704 if (!checkImageContents($_FILES['attachment']['tmp_name'], !empty($modSettings['avatar_paranoid'])))
2705 {
2706 // It's bad. Try to re-encode the contents?
2707 if (empty($modSettings['avatar_reencode']) || (!reencodeImage($_FILES['attachment']['tmp_name'], $sizes[2])))
2708 return 'bad_avatar';
2709 // We were successful. However, at what price?
2710 $sizes = @getimagesize($_FILES['attachment']['tmp_name']);
2711 // Hard to believe this would happen, but can you bet?
2712 if ($sizes === false)
2713 return 'bad_avatar';
2714 }
2715
2716 $extensions = array(
2717 '1' => 'gif',
2718 '2' => 'jpg',
2719 '3' => 'png',
2720 '6' => 'bmp'
2721 );
2722
2723 $extension = isset($extensions[$sizes[2]]) ? $extensions[$sizes[2]] : 'bmp';
2724 $mime_type = 'image/' . ($extension === 'jpg' ? 'jpeg' : ($extension === 'bmp' ? 'x-ms-bmp' : $extension));
2725 $destName = 'avatar_' . $memID . '_' . time() . '.' . $extension;
2726 list ($width, $height) = getimagesize($_FILES['attachment']['tmp_name']);
2727 $file_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, false, null, true) : '';
2728
2729 // Remove previous attachments this member might have had.
2730 removeAttachments(array('id_member' => $memID));
2731
2732 $smcFunc['db_insert']('',
2733 '{db_prefix}attachments',
2734 array(
2735 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string', 'file_hash' => 'string', 'fileext' => 'string', 'size' => 'int',
2736 'width' => 'int', 'height' => 'int', 'mime_type' => 'string', 'id_folder' => 'int',
2737 ),
2738 array(
2739 $memID, (empty($modSettings['custom_avatar_enabled']) ? 0 : 1), $destName, $file_hash, $extension, filesize($_FILES['attachment']['tmp_name']),
2740 (int) $width, (int) $height, $mime_type, $id_folder,
2741 ),
2742 array('id_attach')
2743 );
2744
2745 $cur_profile['id_attach'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
2746 $cur_profile['filename'] = $destName;
2747 $cur_profile['attachment_type'] = empty($modSettings['custom_avatar_enabled']) ? 0 : 1;
2748
2749 $destinationPath = $uploadDir . '/' . (empty($file_hash) ? $destName : $cur_profile['id_attach'] . '_' . $file_hash);
2750 if (!rename($_FILES['attachment']['tmp_name'], $destinationPath))
2751 {
2752 // I guess a man can try.
2753 removeAttachments(array('id_member' => $memID));
2754 fatal_lang_error('attach_timeout', 'critical');
2755 }
2756
2757 // Attempt to chmod it.
2758 @chmod($uploadDir . '/' . $destinationPath, 0644);
2759 }
2760 $profile_vars['avatar'] = '';
2761
2762 // Delete any temporary file.
2763 if (file_exists($uploadDir . '/avatar_tmp_' . $memID))
2764 @unlink($uploadDir . '/avatar_tmp_' . $memID);
2765 }
2766 // Selected the upload avatar option and had one already uploaded before or didn't upload one.
2767 else
2768 $profile_vars['avatar'] = '';
2769 }
2770 else
2771 $profile_vars['avatar'] = '';
2772
2773 // Setup the profile variables so it shows things right on display!
2774 $cur_profile['avatar'] = $profile_vars['avatar'];
2775
2776 return false;
2777 }
2778
2779 // Validate the signature!
2780 function profileValidateSignature(&$value)
2781 {
2782 global $sourcedir, $modSettings, $smcFunc, $txt;
2783
2784 require_once($sourcedir . '/Subs-Post.php');
2785
2786 // Admins can do whatever they hell they want!
2787 if (!allowedTo('admin_forum'))
2788 {
2789 // Load all the signature limits.
2790 list ($sig_limits, $sig_bbc) = explode(':', $modSettings['signature_settings']);
2791 $sig_limits = explode(',', $sig_limits);
2792 $disabledTags = !empty($sig_bbc) ? explode(',', $sig_bbc) : array();
2793
2794 $unparsed_signature = strtr(un_htmlspecialchars($value), array("\r" => '', '&#039' => '\''));
2795 // Too long?
2796 if (!empty($sig_limits[1]) && $smcFunc['strlen']($unparsed_signature) > $sig_limits[1])
2797 {
2798 $_POST['signature'] = trim(htmlspecialchars($smcFunc['substr']($unparsed_signature, 0, $sig_limits[1]), ENT_QUOTES));
2799 $txt['profile_error_signature_max_length'] = sprintf($txt['profile_error_signature_max_length'], $sig_limits[1]);
2800 return 'signature_max_length';
2801 }
2802 // Too many lines?
2803 if (!empty($sig_limits[2]) && substr_count($unparsed_signature, "\n") >= $sig_limits[2])
2804 {
2805 $txt['profile_error_signature_max_lines'] = sprintf($txt['profile_error_signature_max_lines'], $sig_limits[2]);
2806 return 'signature_max_lines';
2807 }
2808 // Too many images?!
2809 if (!empty($sig_limits[3]) && (substr_count(strtolower($unparsed_signature), '[img') + substr_count(strtolower($unparsed_signature), '<img')) > $sig_limits[3])
2810 {
2811 $txt['profile_error_signature_max_image_count'] = sprintf($txt['profile_error_signature_max_image_count'], $sig_limits[3]);
2812 return 'signature_max_image_count';
2813 }
2814 // What about too many smileys!
2815 $smiley_parsed = $unparsed_signature;
2816 parsesmileys($smiley_parsed);
2817 $smiley_count = substr_count(strtolower($smiley_parsed), '<img') - substr_count(strtolower($unparsed_signature), '<img');
2818 if (!empty($sig_limits[4]) && $sig_limits[4] == -1 && $smiley_count > 0)
2819 return 'signature_allow_smileys';
2820 elseif (!empty($sig_limits[4]) && $sig_limits[4] > 0 && $smiley_count > $sig_limits[4])
2821 {
2822 $txt['profile_error_signature_max_smileys'] = sprintf($txt['profile_error_signature_max_smileys'], $sig_limits[4]);
2823 return 'signature_max_smileys';
2824 }
2825 // Maybe we are abusing font sizes?
2826 if (!empty($sig_limits[7]) && preg_match_all('~\[size=([\d\.]+)?(px|pt|em|x-large|larger)~i', $unparsed_signature, $matches) !== false && isset($matches[2]))
2827 {
2828 foreach ($matches[1] as $ind => $size)
2829 {
2830 $limit_broke = 0;
2831 // Attempt to allow all sizes of abuse, so to speak.
2832 if ($matches[2][$ind] == 'px' && $size > $sig_limits[7])
2833 $limit_broke = $sig_limits[7] . 'px';
2834 elseif ($matches[2][$ind] == 'pt' && $size > ($sig_limits[7] * 0.75))
2835 $limit_broke = ((int) $sig_limits[7] * 0.75) . 'pt';
2836 elseif ($matches[2][$ind] == 'em' && $size > ((float) $sig_limits[7] / 16))
2837 $limit_broke = ((float) $sig_limits[7] / 16) . 'em';
2838 elseif ($matches[2][$ind] != 'px' && $matches[2][$ind] != 'pt' && $matches[2][$ind] != 'em' && $sig_limits[7] < 18)
2839 $limit_broke = 'large';
2840
2841 if ($limit_broke)
2842 {
2843 $txt['profile_error_signature_max_font_size'] = sprintf($txt['profile_error_signature_max_font_size'], $limit_broke);
2844 return 'signature_max_font_size';
2845 }
2846 }
2847 }
2848 // The difficult one - image sizes! Don't error on this - just fix it.
2849 if ((!empty($sig_limits[5]) || !empty($sig_limits[6])))
2850 {
2851 // Get all BBC tags...
2852 preg_match_all('~\[img(\s+width=([\d]+))?(\s+height=([\d]+))?(\s+width=([\d]+))?\s*\](?:<br />)*([^<">]+?)(?:<br />)*\[/img\]~i', $unparsed_signature, $matches);
2853 // ... and all HTML ones.
2854 preg_match_all('~<img\s+src=(?:")?((?:http://|ftp://|https://|ftps://).+?)(?:")?(?:\s+alt=(?:")?(.*?)(?:")?)?(?:\s?/)?>~i', $unparsed_signature, $matches2, PREG_PATTERN_ORDER);
2855 // And stick the HTML in the BBC.
2856 if (!empty($matches2))
2857 {
2858 foreach ($matches2[0] as $ind => $dummy)
2859 {
2860 $matches[0][] = $matches2[0][$ind];
2861 $matches[1][] = '';
2862 $matches[2][] = '';
2863 $matches[3][] = '';
2864 $matches[4][] = '';
2865 $matches[5][] = '';
2866 $matches[6][] = '';
2867 $matches[7][] = $matches2[1][$ind];
2868 }
2869 }
2870
2871 $replaces = array();
2872 // Try to find all the images!
2873 if (!empty($matches))
2874 {
2875 foreach ($matches[0] as $key => $image)
2876 {
2877 $width = -1; $height = -1;
2878
2879 // Does it have predefined restraints? Width first.
2880 if ($matches[6][$key])
2881 $matches[2][$key] = $matches[6][$key];
2882 if ($matches[2][$key] && $sig_limits[5] && $matches[2][$key] > $sig_limits[5])
2883 {
2884 $width = $sig_limits[5];
2885 $matches[4][$key] = $matches[4][$key] * ($width / $matches[2][$key]);
2886 }
2887 elseif ($matches[2][$key])
2888 $width = $matches[2][$key];
2889 // ... and height.
2890 if ($matches[4][$key] && $sig_limits[6] && $matches[4][$key] > $sig_limits[6])
2891 {
2892 $height = $sig_limits[6];
2893 if ($width != -1)
2894 $width = $width * ($height / $matches[4][$key]);
2895 }
2896 elseif ($matches[4][$key])
2897 $height = $matches[4][$key];
2898
2899 // If the dimensions are still not fixed - we need to check the actual image.
2900 if (($width == -1 && $sig_limits[5]) || ($height == -1 && $sig_limits[6]))
2901 {
2902 $sizes = url_image_size($matches[7][$key]);
2903 if (is_array($sizes))
2904 {
2905 // Too wide?
2906 if ($sizes[0] > $sig_limits[5] && $sig_limits[5])
2907 {
2908 $width = $sig_limits[5];
2909 $sizes[1] = $sizes[1] * ($width / $sizes[0]);
2910 }
2911 // Too high?
2912 if ($sizes[1] > $sig_limits[6] && $sig_limits[6])
2913 {
2914 $height = $sig_limits[6];
2915 if ($width == -1)
2916 $width = $sizes[0];
2917 $width = $width * ($height / $sizes[1]);
2918 }
2919 elseif ($width != -1)
2920 $height = $sizes[1];
2921 }
2922 }
2923
2924 // Did we come up with some changes? If so remake the string.
2925 if ($width != -1 || $height != -1)
2926 $replaces[$image] = '[img' . ($width != -1 ? ' width=' . round($width) : '') . ($height != -1 ? ' height=' . round($height) : '') . ']' . $matches[7][$key] . '[/img]';
2927 }
2928 if (!empty($replaces))
2929 $value = str_replace(array_keys($replaces), array_values($replaces), $value);
2930 }
2931 }
2932 // Any disabled BBC?
2933 $disabledSigBBC = implode('|', $disabledTags);
2934 if (!empty($disabledSigBBC))
2935 {
2936 if (preg_match('~\[(' . $disabledSigBBC . ')~i', $unparsed_signature, $matches) !== false && isset($matches[1]))
2937 {
2938 $disabledTags = array_unique($disabledTags);
2939 $txt['profile_error_signature_disabled_bbc'] = sprintf($txt['profile_error_signature_disabled_bbc'], implode(', ', $disabledTags));
2940 return 'signature_disabled_bbc';
2941 }
2942 }
2943 }
2944
2945 preparsecode($value);
2946 return true;
2947 }
2948
2949 // Validate an email address.
2950 function profileValidateEmail($email, $memID = 0)
2951 {
2952 global $smcFunc, $context;
2953
2954 $email = strtr($email, array('&#039;' => '\''));
2955
2956 // Check the name and email for validity.
2957 if (trim($email) == '')
2958 return 'no_email';
2959 if (preg_match('~^[0-9A-Za-z=_+\-/][0-9A-Za-z=_\'+\-/\.]*@[\w\-]+(\.[\w\-]+)*(\.[\w]{2,6})$~', $email) == 0)
2960 return 'bad_email';
2961
2962 // Email addresses should be and stay unique.
2963 $request = $smcFunc['db_query']('', '
2964 SELECT id_member
2965 FROM {db_prefix}members
2966 WHERE ' . ($memID != 0 ? 'id_member != {int:selected_member} AND ' : '') . '
2967 email_address = {string:email_address}
2968 LIMIT 1',
2969 array(
2970 'selected_member' => $memID,
2971 'email_address' => $email,
2972 )
2973 );
2974 if ($smcFunc['db_num_rows']($request) > 0)
2975 return 'email_taken';
2976 $smcFunc['db_free_result']($request);
2977
2978 return true;
2979 }
2980
2981 // Reload a users settings.
2982 function profileReloadUser()
2983 {
2984 global $sourcedir, $modSettings, $context, $cur_profile, $smcFunc, $profile_vars;
2985
2986 // Log them back in - using the verify password as they must have matched and this one doesn't get changed by anyone!
2987 if (isset($_POST['passwrd2']) && $_POST['passwrd2'] != '')
2988 {
2989 require_once($sourcedir . '/Subs-Auth.php');
2990 setLoginCookie(60 * $modSettings['cookieTime'], $context['id_member'], sha1(sha1(strtolower($cur_profile['member_name']) . un_htmlspecialchars($_POST['passwrd2'])) . $cur_profile['password_salt']));
2991 }
2992
2993 loadUserSettings();
2994 writeLog();
2995 }
2996
2997 // Send the user a new activation email if they need to reactivate!
2998 function profileSendActivation()
2999 {
3000 global $sourcedir, $profile_vars, $txt, $context, $scripturl, $smcFunc, $cookiename, $cur_profile, $language, $modSettings;
3001
3002 require_once($sourcedir . '/Subs-Post.php');
3003
3004 // Shouldn't happen but just in case.
3005 if (empty($profile_vars['email_address']))
3006 return;
3007
3008 $replacements = array(
3009 'ACTIVATIONLINK' => $scripturl . '?action=activate;u=' . $context['id_member'] . ';code=' . $profile_vars['validation_code'],
3010 'ACTIVATIONCODE' => $profile_vars['validation_code'],
3011 'ACTIVATIONLINKWITHOUTCODE' => $scripturl . '?action=activate;u=' . $context['id_member'],
3012 );
3013
3014 // Send off the email.
3015 $emaildata = loadEmailTemplate('activate_reactivate', $replacements, empty($cur_profile['lngfile']) || empty($modSettings['userLanguage']) ? $language : $cur_profile['lngfile']);
3016 sendmail($profile_vars['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 0);
3017
3018 // Log the user out.
3019 $smcFunc['db_query']('', '
3020 DELETE FROM {db_prefix}log_online
3021 WHERE id_member = {int:selected_member}',
3022 array(
3023 'selected_member' => $context['id_member'],
3024 )
3025 );
3026 $_SESSION['log_time'] = 0;
3027 $_SESSION['login_' . $cookiename] = serialize(array(0, '', 0));
3028
3029 if (isset($_COOKIE[$cookiename]))
3030 $_COOKIE[$cookiename] = '';
3031
3032 loadUserSettings();
3033
3034 $context['user']['is_logged'] = false;
3035 $context['user']['is_guest'] = true;
3036
3037 // Send them to the done-with-registration-login screen.
3038 loadTemplate('Register');
3039
3040 $context['page_title'] = $txt['profile'];
3041 $context['sub_template'] = 'after';
3042 $context['title'] = $txt['activate_changed_email_title'];
3043 $context['description'] = $txt['activate_changed_email_desc'];
3044
3045 // We're gone!
3046 obExit();
3047 }
3048
3049 // Function to allow the user to choose group membership etc...
3050 function groupMembership($memID)
3051 {
3052 global $txt, $scripturl, $user_profile, $user_info, $context, $modSettings, $smcFunc;
3053
3054 $curMember = $user_profile[$memID];
3055 $context['primary_group'] = $curMember['id_group'];
3056
3057 // Can they manage groups?
3058 $context['can_manage_membergroups'] = allowedTo('manage_membergroups');
3059 $context['can_manage_protected'] = allowedTo('admin_forum');
3060 $context['can_edit_primary'] = $context['can_manage_protected'];
3061 $context['update_message'] = isset($_GET['msg']) && isset($txt['group_membership_msg_' . $_GET['msg']]) ? $txt['group_membership_msg_' . $_GET['msg']] : '';
3062
3063 // Get all the groups this user is a member of.
3064 $groups = explode(',', $curMember['additional_groups']);
3065 $groups[] = $curMember['id_group'];
3066
3067 // Ensure the query doesn't croak!
3068 if (empty($groups))
3069 $groups = array(0);
3070 // Just to be sure...
3071 foreach ($groups as $k => $v)
3072 $groups[$k] = (int) $v;
3073
3074 // Get all the membergroups they can join.
3075 $request = $smcFunc['db_query']('', '
3076 SELECT mg.id_group, mg.group_name, mg.description, mg.group_type, mg.online_color, mg.hidden,
3077 IFNULL(lgr.id_member, 0) AS pending
3078 FROM {db_prefix}membergroups AS mg
3079 LEFT JOIN {db_prefix}log_group_requests AS lgr ON (lgr.id_member = {int:selected_member} AND lgr.id_group = mg.id_group)
3080 WHERE (mg.id_group IN ({array_int:group_list})
3081 OR mg.group_type > {int:nonjoin_group_id})
3082 AND mg.min_posts = {int:min_posts}
3083 AND mg.id_group != {int:moderator_group}
3084 ORDER BY group_name',
3085 array(
3086 'group_list' => $groups,
3087 'selected_member' => $memID,
3088 'nonjoin_group_id' => 1,
3089 'min_posts' => -1,
3090 'moderator_group' => 3,
3091 )
3092 );
3093 // This beast will be our group holder.
3094 $context['groups'] = array(
3095 'member' => array(),
3096 'available' => array()
3097 );
3098 while ($row = $smcFunc['db_fetch_assoc']($request))
3099 {
3100 // Can they edit their primary group?
3101 if (($row['id_group'] == $context['primary_group'] && $row['group_type'] > 1) || ($row['hidden'] != 2 && $context['primary_group'] == 0 && in_array($row['id_group'], $groups)))
3102 $context['can_edit_primary'] = true;
3103
3104 // If they can't manage (protected) groups, and it's not publically joinable or already assigned, they can't see it.
3105 if (((!$context['can_manage_protected'] && $row['group_type'] == 1) || (!$context['can_manage_membergroups'] && $row['group_type'] == 0)) && $row['id_group'] != $context['primary_group'])
3106 continue;
3107
3108 $context['groups'][in_array($row['id_group'], $groups) ? 'member' : 'available'][$row['id_group']] = array(
3109 'id' => $row['id_group'],
3110 'name' => $row['group_name'],
3111 'desc' => $row['description'],
3112 'color' => $row['online_color'],
3113 'type' => $row['group_type'],
3114 'pending' => $row['pending'],
3115 'is_primary' => $row['id_group'] == $context['primary_group'],
3116 'can_be_primary' => $row['hidden'] != 2,
3117 // Anything more than this needs to be done through account settings for security.
3118 'can_leave' => $row['id_group'] != 1 && $row['group_type'] > 1 ? true : false,
3119 );
3120 }
3121 $smcFunc['db_free_result']($request);
3122
3123 // Add registered members on the end.
3124 $context['groups']['member'][0] = array(
3125 'id' => 0,
3126 'name' => $txt['regular_members'],
3127 'desc' => $txt['regular_members_desc'],
3128 'type' => 0,
3129 'is_primary' => $context['primary_group'] == 0 ? true : false,
3130 'can_be_primary' => true,
3131 'can_leave' => 0,
3132 );
3133
3134 // No changing primary one unless you have enough groups!
3135 if (count($context['groups']['member']) < 2)
3136 $context['can_edit_primary'] = false;
3137
3138 // In the special case that someone is requesting membership of a group, setup some special context vars.
3139 if (isset($_REQUEST['request']) && isset($context['groups']['available'][(int) $_REQUEST['request']]) && $context['groups']['available'][(int) $_REQUEST['request']]['type'] == 2)
3140 $context['group_request'] = $context['groups']['available'][(int) $_REQUEST['request']];
3141 }
3142
3143 // This function actually makes all the group changes...
3144 function groupMembership2($profile_vars, $post_errors, $memID)
3145 {
3146 global $user_info, $sourcedir, $context, $user_profile, $modSettings, $txt, $smcFunc, $scripturl, $language;
3147
3148 // Let's be extra cautious...
3149 if (!$context['user']['is_owner'] || empty($modSettings['show_group_membership']))
3150 isAllowedTo('manage_membergroups');
3151 if (!isset($_REQUEST['gid']) && !isset($_POST['primary']))
3152 fatal_lang_error('no_access', false);
3153
3154 checkSession(isset($_GET['gid']) ? 'get' : 'post');
3155
3156 $old_profile = &$user_profile[$memID];
3157 $context['can_manage_membergroups'] = allowedTo('manage_membergroups');
3158 $context['can_manage_protected'] = allowedTo('admin_forum');
3159
3160 // By default the new primary is the old one.
3161 $newPrimary = $old_profile['id_group'];
3162 $addGroups = array_flip(explode(',', $old_profile['additional_groups']));
3163 $canChangePrimary = $old_profile['id_group'] == 0 ? 1 : 0;
3164 $changeType = isset($_POST['primary']) ? 'primary' : (isset($_POST['req']) ? 'request' : 'free');
3165
3166 // One way or another, we have a target group in mind...
3167 $group_id = isset($_REQUEST['gid']) ? (int) $_REQUEST['gid'] : (int) $_POST['primary'];
3168 $foundTarget = $changeType == 'primary' && $group_id == 0 ? true : false;
3169
3170 // Sanity check!!
3171 if ($group_id == 1)
3172 isAllowedTo('admin_forum');
3173 // Protected groups too!
3174 else
3175 {
3176 $request = $smcFunc['db_query']('', '
3177 SELECT group_type
3178 FROM {db_prefix}membergroups
3179 WHERE id_group = {int:current_group}
3180 LIMIT {int:limit}',
3181 array(
3182 'current_group' => $group_id,
3183 'limit' => 1,
3184 )
3185 );
3186 list ($is_protected) = $smcFunc['db_fetch_row']($request);
3187 $smcFunc['db_free_result']($request);
3188
3189 if ($is_protected == 1)
3190 isAllowedTo('admin_forum');
3191 }
3192
3193 // What ever we are doing, we need to determine if changing primary is possible!
3194 $request = $smcFunc['db_query']('', '
3195 SELECT id_group, group_type, hidden, group_name
3196 FROM {db_prefix}membergroups
3197 WHERE id_group IN ({int:group_list}, {int:current_group})',
3198 array(
3199 'group_list' => $group_id,
3200 'current_group' => $old_profile['id_group'],
3201 )
3202 );
3203 while ($row = $smcFunc['db_fetch_assoc']($request))
3204 {
3205 // Is this the new group?
3206 if ($row['id_group'] == $group_id)
3207 {
3208 $foundTarget = true;
3209 $group_name = $row['group_name'];
3210
3211 // Does the group type match what we're doing - are we trying to request a non-requestable group?
3212 if ($changeType == 'request' && $row['group_type'] != 2)
3213 fatal_lang_error('no_access', false);
3214 // What about leaving a requestable group we are not a member of?
3215 elseif ($changeType == 'free' && $row['group_type'] == 2 && $old_profile['id_group'] != $row['id_group'] && !isset($addGroups[$row['id_group']]))
3216 fatal_lang_error('no_access', false);
3217 elseif ($changeType == 'free' && $row['group_type'] != 3 && $row['group_type'] != 2)
3218 fatal_lang_error('no_access', false);
3219
3220 // We can't change the primary group if this is hidden!
3221 if ($row['hidden'] == 2)
3222 $canChangePrimary = false;
3223 }
3224
3225 // If this is their old primary, can we change it?
3226 if ($row['id_group'] == $old_profile['id_group'] && ($row['group_type'] > 1 || $context['can_manage_membergroups']) && $canChangePrimary !== false)
3227 $canChangePrimary = 1;
3228
3229 // If we are not doing a force primary move, don't do it automatically if current primary is not 0.
3230 if ($changeType != 'primary' && $old_profile['id_group'] != 0)
3231 $canChangePrimary = false;
3232
3233 // If this is the one we are acting on, can we even act?
3234 if ((!$context['can_manage_protected'] && $row['group_type'] == 1) || (!$context['can_manage_membergroups'] && $row['group_type'] == 0))
3235 $canChangePrimary = false;
3236 }
3237 $smcFunc['db_free_result']($request);
3238
3239 // Didn't find the target?
3240 if (!$foundTarget)
3241 fatal_lang_error('no_access', false);
3242
3243 // Final security check, don't allow users to promote themselves to admin.
3244 if ($context['can_manage_membergroups'] && !allowedTo('admin_forum'))
3245 {
3246 $request = $smcFunc['db_query']('', '
3247 SELECT COUNT(permission)
3248 FROM {db_prefix}permissions
3249 WHERE id_group = {int:selected_group}
3250 AND permission = {string:admin_forum}
3251 AND add_deny = {int:not_denied}',
3252 array(
3253 'selected_group' => $group_id,
3254 'not_denied' => 1,
3255 'admin_forum' => 'admin_forum',
3256 )
3257 );
3258 list ($disallow) = $smcFunc['db_fetch_row']($request);
3259 $smcFunc['db_free_result']($request);
3260
3261 if ($disallow)
3262 isAllowedTo('admin_forum');
3263 }
3264
3265 // If we're requesting, add the note then return.
3266 if ($changeType == 'request')
3267 {
3268 $request = $smcFunc['db_query']('', '
3269 SELECT id_member
3270 FROM {db_prefix}log_group_requests
3271 WHERE id_member = {int:selected_member}
3272 AND id_group = {int:selected_group}',
3273 array(
3274 'selected_member' => $memID,
3275 'selected_group' => $group_id,
3276 )
3277 );
3278 if ($smcFunc['db_num_rows']($request) != 0)
3279 fatal_lang_error('profile_error_already_requested_group');
3280 $smcFunc['db_free_result']($request);
3281
3282 // Log the request.
3283 $smcFunc['db_insert']('',
3284 '{db_prefix}log_group_requests',
3285 array(
3286 'id_member' => 'int', 'id_group' => 'int', 'time_applied' => 'int', 'reason' => 'string-65534',
3287 ),
3288 array(
3289 $memID, $group_id, time(), $_POST['reason'],
3290 ),
3291 array('id_request')
3292 );
3293
3294 // Send an email to all group moderators etc.
3295 require_once($sourcedir . '/Subs-Post.php');
3296
3297 // Do we have any group moderators?
3298 $request = $smcFunc['db_query']('', '
3299 SELECT id_member
3300 FROM {db_prefix}group_moderators
3301 WHERE id_group = {int:selected_group}',
3302 array(
3303 'selected_group' => $group_id,
3304 )
3305 );
3306 $moderators = array();
3307 while ($row = $smcFunc['db_fetch_assoc']($request))
3308 $moderators[] = $row['id_member'];
3309 $smcFunc['db_free_result']($request);
3310
3311 // Otherwise this is the backup!
3312 if (empty($moderators))
3313 {
3314 require_once($sourcedir . '/Subs-Members.php');
3315 $moderators = membersAllowedTo('manage_membergroups');
3316 }
3317
3318 if (!empty($moderators))
3319 {
3320 $request = $smcFunc['db_query']('', '
3321 SELECT id_member, email_address, lngfile, member_name, mod_prefs
3322 FROM {db_prefix}members
3323 WHERE id_member IN ({array_int:moderator_list})
3324 AND notify_types != {int:no_notifications}
3325 ORDER BY lngfile',
3326 array(
3327 'moderator_list' => $moderators,
3328 'no_notifications' => 4,
3329 )
3330 );
3331 while ($row = $smcFunc['db_fetch_assoc']($request))
3332 {
3333 // Check whether they are interested.
3334 if (!empty($row['mod_prefs']))
3335 {
3336 list(,, $pref_binary) = explode('|', $row['mod_prefs']);
3337 if (!($pref_binary & 4))
3338 continue;
3339 }
3340
3341 $replacements = array(
3342 'RECPNAME' => $row['member_name'],
3343 'APPYNAME' => $old_profile['member_name'],
3344 'GROUPNAME' => $group_name,
3345 'REASON' => $_POST['reason'],
3346 'MODLINK' => $scripturl . '?action=moderate;area=groups;sa=requests',
3347 );
3348
3349 $emaildata = loadEmailTemplate('request_membership', $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
3350 sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, null, false, 2);
3351 }
3352 $smcFunc['db_free_result']($request);
3353 }
3354
3355 return $changeType;
3356 }
3357 // Otherwise we are leaving/joining a group.
3358 elseif ($changeType == 'free')
3359 {
3360 // Are we leaving?
3361 if ($old_profile['id_group'] == $group_id || isset($addGroups[$group_id]))
3362 {
3363 if ($old_profile['id_group'] == $group_id)
3364 $newPrimary = 0;
3365 else
3366 unset($addGroups[$group_id]);
3367 }
3368 // ... if not, must be joining.
3369 else
3370 {
3371 // Can we change the primary, and do we want to?
3372 if ($canChangePrimary)
3373 {
3374 if ($old_profile['id_group'] != 0)
3375 $addGroups[$old_profile['id_group']] = -1;
3376 $newPrimary = $group_id;
3377 }
3378 // Otherwise it's an additional group...
3379 else
3380 $addGroups[$group_id] = -1;
3381 }
3382 }
3383 // Finally, we must be setting the primary.
3384 elseif ($canChangePrimary)
3385 {
3386 if ($old_profile['id_group'] != 0)
3387 $addGroups[$old_profile['id_group']] = -1;
3388 if (isset($addGroups[$group_id]))
3389 unset($addGroups[$group_id]);
3390 $newPrimary = $group_id;
3391 }
3392
3393 // Finally, we can make the changes!
3394 foreach ($addGroups as $id => $dummy)
3395 if (empty($id))
3396 unset($addGroups[$id]);
3397 $addGroups = implode(',', array_flip($addGroups));
3398
3399 // Ensure that we don't cache permissions if the group is changing.
3400 if ($context['user']['is_owner'])
3401 $_SESSION['mc']['time'] = 0;
3402 else
3403 updateSettings(array('settings_updated' => time()));
3404
3405 updateMemberData($memID, array('id_group' => $newPrimary, 'additional_groups' => $addGroups));
3406
3407 return $changeType;
3408 }
3409
3410 ?>